วันนี้เราจะเขียนเกม Tic-Tac-Toe โดยใช้ servlets และ JSP

โครงการนี้จะแตกต่างจากโครงการก่อนหน้าเล็กน้อย มันจะประกอบด้วยงานไม่เพียง แต่ยังมีคำอธิบายวิธีการทำ นั่นคือจะเป็นโครงการจากซีรีส์ "HOW TO ... "

คำแนะนำ:

  1. แยกจากที่เก็บ: https://github.com/CodeGymCC/project-servlet.git
  2. ดาวน์โหลดเวอร์ชันของโครงการลงในคอมพิวเตอร์ของคุณ
  3. ตั้งค่าการเปิดตัวแอปพลิเคชันใน IDEA:
    • Alt + Shift + F9 -> Edit Configurations... -> Alt + insert -> tom (ในแถบค้นหา) -> Local
    • หลังจากนั้นคุณต้องคลิก "กำหนดค่า" และระบุตำแหน่งที่ดาวน์โหลดและคลายไฟล์เก็บถาวรด้วย Tomcat
    • ในแท็บ "ปรับใช้": Alt + แทรก -> สิ่งประดิษฐ์... -> tic-tac-toe:war ระเบิด -> ตกลง
    • ในช่อง "บริบทของแอปพลิเคชัน" ให้เว้นเฉพาะ "/" (เครื่องหมายทับ)
    • กด "สมัคร"
    • ปิดหน้าต่างการตั้งค่า
    • ทำการทดสอบครั้งแรกของการกำหนดค่าที่กำหนดเอง หากทำทุกอย่างถูกต้อง เบราว์เซอร์เริ่มต้นของคุณจะเปิดขึ้น ซึ่งจะเป็น:
  4. เปิด ไฟล์ "pom.xml " มีการอ้างอิง 2 รายการใน บล็อก "การพึ่งพา"
    • javax.servlet-apiรับผิดชอบข้อกำหนดของเซิร์ฟเล็ต จำเป็นต้องมี ขอบเขต"ให้ไว้"ในระหว่างการพัฒนา แต่ไม่จำเป็นสำหรับรันไทม์ (Tomcat มีการอ้างอิงนี้อยู่แล้วในโฟลเดอร์ lib)
    • jstl– ถือได้ว่าเป็นเครื่องมือแม่แบบ
  5. มี 3 ไฟล์ ใน โฟลเดอร์ “webapp” :
    • index.jsp- นี่คือเทมเพลตของเรา (คล้ายกับหน้า HTML) มันจะมีมาร์กอัปและสคริปต์ เป็นไฟล์ชื่อ“ดัชนี”ที่ให้ไว้เป็นหน้าแรก หากไม่มีการกำหนดค่า ซึ่งเราเห็นในขั้นตอนที่ 3
    • /static/main.css- ไฟล์สำหรับสไตล์ เช่นเดียวกับในโครงการก่อนหน้านี้ ทุกอย่างขึ้นอยู่กับคุณ วาดตามที่คุณต้องการ
    • /static/jquery-3.6.0.min.js- การพึ่งพาส่วนหน้าซึ่งเซิร์ฟเวอร์ของเราจะแจกจ่ายเป็นแบบคงที่
  6. แพ็คเกจ"com.tictactoe"จะมีโค้ด Java ทั้งหมด ตอนนี้มี 2 คลาสคือ
    • Sign- enum ซึ่งรับผิดชอบในการ"ข้าม / ศูนย์ / โมฆะ "
    • Fieldคือสนามของเรา คลาสนี้มี แผนที่ "ฟิลด์ " หลักการของการจัดเก็บข้อมูลจะเป็นดังนี้: เซลล์ของฟิลด์ tic-tac-toe จะมีหมายเลขจากศูนย์ ในบรรทัดแรก 0, 1 และ 2 ในบรรทัดที่สอง: 3, 4 และ 5 และอื่น ๆ นอกจากนี้ยังมี 3 วิธี “getEmptyFieldIndex”ค้นหาเซลล์ว่างเซลล์แรก (ใช่ คู่ต่อสู้ของเราจะไม่ฉลาดนัก) "checkWin" ตรวจสอบว่าเกมจบลงหรือไม่ ถ้ามีแถวที่มีกากบาทสามตัว ก็จะส่งกลับค่ากากบาท ถ้ามีแถวที่มีเลขศูนย์สามตัว ก็จะส่งกลับค่าศูนย์ มิฉะนั้นจะว่างเปล่า "getFieldData" - ส่งคืนค่าของ แผนที่ "ฟิลด์"เป็นรายการที่เรียงลำดับจากน้อยไปหามาก
  7. คำอธิบายเกี่ยวกับเทมเพลตเสร็จสิ้นแล้ว ตอนนี้คุณสามารถเริ่มงานได้ เริ่มจากการวาดตารางขนาด 3 คูณ 3 โดยเพิ่มโค้ดต่อไปนี้ใน“index.jsp” :
    <table>
    	<tr>
    		<td>0</td>
    		<td>1</td>
    		<td>2</td>
    	</tr>
    	<tr>
    		<td>3</td>
    		<td>4</td>
    		<td>5</td>
    	</tr>
    	<tr>
    		<td>6</td>
    		<td>7</td>
    		<td>8</td>
    	</tr>
    </table>
    จากนั้นเราจะลบตัวเลขในตารางและแทนที่ด้วยกากบาท ศูนย์ หรือฟิลด์ว่าง นอกจากนี้ ภายในแท็ก "head" ให้รวมไฟล์สไตล์ไว้ด้วย ในการดำเนินการนี้ ให้เพิ่มบรรทัด:<link href="static/main.css" rel="stylesheet">

    เนื้อหาของไฟล์สไตล์นั้นขึ้นอยู่กับคุณ ฉันใช้สิ่งนี้:
    td {
        border: 3px solid black;
        padding: 10px;
        border-collapse: separate;
        margin: 10px;
        width: 100px;
        height: 100px;
        font-size: 50px;
        text-align: center;
        empty-cells: show;
    }
    หลังจากรันแล้ว ผลลัพธ์ของฉันจะเป็นดังนี้:
  8. ตอนนี้มาเพิ่มฟังก์ชันต่อไปนี้: เมื่อเซลล์ถูกคลิก คำขอจะถูกส่งไปยังเซิร์ฟเวอร์ ซึ่งเราจะส่งดัชนีของเซลล์ที่ถูกคลิกเป็นพารามิเตอร์ งานนี้สามารถแบ่งออกเป็นสองส่วน: ส่งคำขอจากส่วนหน้า ยอมรับคำขอบนเซิร์ฟเวอร์ เรามาเริ่มกันที่ด้านหน้าเพื่อการเปลี่ยนแปลง

    มา เพิ่ม พารามิเตอร์ "onclick" ให้กับแท็ ก"d" แต่ละ แท็ก ในค่า เราระบุการเปลี่ยนแปลงของหน้าปัจจุบันเป็น URL ที่ระบุ เซิร์ฟเล็ตที่จะรับผิดชอบตรรกะจะมี URL “/logic ” และจะใช้พารามิเตอร์ที่เรียกว่า"คลิก" . ดังนั้นเราจะส่งดัชนีของเซลล์ที่ผู้ใช้คลิก
    <table>
        <tr>
            <td onclick="window.location='/logic?click=0'">0</td>
            <td onclick="window.location='/logic?click=1'">1</td>
            <td onclick="window.location='/logic?click=2'">2</td>
        </tr>
        <tr>
            <td onclick="window.location='/logic?click=3'">3</td>
            <td onclick="window.location='/logic?click=4'">4</td>
            <td onclick="window.location='/logic?click=5'">5</td>
        </tr>
        <tr>
            <td onclick="window.location='/logic?click=6'">6</td>
            <td onclick="window.location='/logic?click=7'">7</td>
            <td onclick="window.location='/logic?click=8'">8</td>
        </tr>
    </table>
    คุณสามารถตรวจสอบว่าทุกอย่างถูกต้องผ่านแผงผู้พัฒนาในเบราว์เซอร์ ตัวอย่างเช่น ใน Chrome จะเปิดขึ้นพร้อมกับปุ่มF12 จากการคลิกที่เซลล์ที่มีดัชนี 4 รูปภาพจะเป็นดังนี้: เราได้รับข้อผิดพลาดเนื่องจากเรายังไม่ได้สร้างเซิร์ฟเล็ตที่สามารถส่งเซิร์ฟเวอร์ไปยังที่อยู่" ตรรกะ"
  9. ในแพ็คเกจ "com.tictactoe" ให้สร้างคลาส"LogicServlet" ซึ่งควรได้รับ มาจากคลาส"javax.servlet.http.HttpServlet" ในคลาส ให้แทนที่ เมธอด " doGet"

    และลองเพิ่มเมธอดที่จะรับดัชนีของเซลล์ที่ถูกคลิก คุณต้องเพิ่มการแมปด้วย (ที่อยู่ซึ่งเซิร์ฟเล็ตนี้จะสกัดกั้นคำขอ) ฉันแนะนำให้ทำเช่นนี้ผ่านคำอธิบายประกอบ (แต่ถ้าคุณชอบความยุ่งยาก คุณสามารถใช้ web.xml ได้เช่นกัน) รหัสเซิร์ฟเล็ตทั่วไป:
    package com.tictactoe;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    
    @WebServlet(name = "LogicServlet", value = "/logic")
    public class LogicServlet extends HttpServlet {
        @Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            int index = getSelectedIndex(req);
            resp.sendRedirect("/index.jsp");
        }
    
    
        private int getSelectedIndex(HttpServletRequest request) {
            String click = request.getParameter("click");
            boolean isNumeric = click.chars().allMatch(Character::isDigit);
            return isNumeric ? Integer.parseInt(click) : 0;
        }
    
    }
    ตอนนี้เมื่อคลิกที่เซลล์ใด ๆ เราจะได้ดัชนีของเซลล์นี้บนเซิร์ฟเวอร์ (คุณสามารถตรวจสอบได้โดยการเรียกใช้เซิร์ฟเวอร์ในการแก้ไขข้อบกพร่อง) และจะมีการเปลี่ยนเส้นทางไปยังหน้าเดียวกับที่มีการคลิก
  10. ตอนนี้เราคลิกได้แล้ว แต่ยังไม่ใช่เกม เพื่อให้เกมมีลอจิก คุณจะต้องบันทึกสถานะของเกม (ที่กากบาทและที่ศูนย์อยู่) ระหว่างคำขอ วิธีที่ง่ายที่สุดในการทำเช่นนี้คือการจัดเก็บข้อมูลนี้ในเซสชัน ด้วยวิธีนี้ เซสชันจะถูกเก็บ ไว้บนเซิร์ฟเวอร์ และไคลเอ็นต์จะได้รับ ID เซสชันในคุกกี้ชื่อ“JSESSIONID” แต่ไม่จำเป็นต้องสร้างเซสชันทุกครั้ง แต่เฉพาะตอนเริ่มเกมเท่านั้น มาเริ่มเซิร์ฟเล็ตอื่นสำหรับสิ่งนี้ ซึ่งเราจะเรียกว่า" InitServlet" เราจะแทนที่เมธอด "doGet"ในนั้นซึ่งเราจะสร้างเซสชันใหม่ สร้างสนามเด็กเล่น ใส่สนามเด็กเล่นนี้และรายการประเภทลงชื่อเข้าใช้แอตทริบิวต์เซสชัน และส่ง "ส่งต่อ"ไปที่ index.jsp หน้าหนังสือ. รหัส:
    package com.tictactoe;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    
    @WebServlet(name = "InitServlet", value = "/start")
    public class InitServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // Create a new session
            HttpSession currentSession = req.getSession(true);
    
            // Create a playing field
            Field field = new Field();
            Map<Integer, Sign> fieldData = field.getField();
    
            // Get a list of field values
            List<Sign> data = field.getFieldData();
    
            // Adding field parameters to the session (needed to store state between requests)
            currentSession.setAttribute("field", field);
            // and field values ​​sorted by index (required for drawing crosses and zeroes)
            currentSession.setAttribute("data", data);
    
            // Redirect request to index.jsp page via server
            getServletContext().getRequestDispatcher("/index.jsp").forward(req, resp);
        }
    }
    และอย่าลืม เรามาเปลี่ยนหน้าเริ่มต้นที่เปิดในเบราว์เซอร์หลังจากเริ่มเซิร์ฟเวอร์เป็น“/start” : ตอนนี้หลังจากรีสตาร์ทเซิร์ฟเวอร์และคลิกที่เซลล์ใดๆ ของฟิลด์ในเมนูผู้พัฒนาเบราว์เซอร์ในส่วน"ส่วนหัวคำขอ"จะมีคุกกี้ที่มี ID เซสชัน:
  11. เมื่อเรามีที่เก็บซึ่งเราสามารถเก็บสถานะระหว่างคำขอจากไคลเอนต์ (เบราว์เซอร์) เราสามารถเริ่มเขียนตรรกะของเกมได้ ตรรกะที่เรามีอยู่ใน“ LogicServlet” เราต้องทำงานกับ เมธอด “ doGet” มาเพิ่มพฤติกรรมนี้ให้กับเมธอด:
    • เราจะได้ วัตถุ"ฟิลด์"ของประเภทฟิลด์จากเซสชัน (เราจะนำไปใช้กับ เมธอด "extractField" )
    • วางกากบาทที่ผู้ใช้คลิก (จนถึงขณะนี้ไม่มีการตรวจสอบใดๆ)
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Get the current session
        HttpSession currentSession = req.getSession();
    
        // Get the playfield object from the session
        Field field = extractField(currentSession);
    
        // get the index of the cell that was clicked
        int index = getSelectedIndex(req);
    
        // put a cross in the cell that the user clicked on
        field.getField().put(index, Sign.CROSS);
    
        // Read the list of icons
        List<Sign> data = field.getFieldData();
    
        // Update field object and icon list in session
        currentSession.setAttribute("data", data);
        currentSession.setAttribute("field", field);
    
        resp.sendRedirect("/index.jsp");
    }
    
    
    
    private Field extractField(HttpSession currentSession) {
        Object fieldAttribute = currentSession.getAttribute("field");
        if (Field.class != fieldAttribute.getClass()) {
            currentSession.invalidate();
            throw new RuntimeException("Session is broken, try one more time");
        }
        return (Field) fieldAttribute;
    }
    ลักษณะการทำงานยังไม่เปลี่ยนแปลง แต่ถ้าคุณเริ่มเซิร์ฟเวอร์ในการแก้ไขจุดบกพร่องและตั้งค่าเบรกพอยต์บนบรรทัดที่ส่งการเปลี่ยนเส้นทาง คุณจะเห็น "อวัยวะภายใน" ของวัตถุ " ข้อมูล" ที่นั่น "CROSS"ปรากฏใต้ดัชนีที่ถูกคลิก
  12. ตอนนี้ได้เวลาแสดงกากบาทที่ส่วนหน้าแล้ว ในการทำเช่น นี้ เราจะทำงานกับ ไฟล์ “index.jsp”และ เทคโนโลยี “JSTL”
    • ในส่วน <head> เพิ่ม:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    • ในตารางภายในบล็อก <td> แต่ละบล็อก ให้เปลี่ยนดัชนีเป็นโครงสร้างที่อนุญาตให้คุณคำนวณค่าต่างๆ ตัวอย่างเช่น สำหรับดัชนีศูนย์: <td onclick="window.location='/logic?click=0'">${data.get(0).getSign()}</td> ตอนนี้ เมื่อคุณคลิกที่เซลล์ กากบาทจะปรากฏขึ้นที่นั่น:
  13. เราได้ดำเนินการแล้ว ตอนนี้ถึงคราวของ "ศูนย์" แล้ว และขอเพิ่มการตรวจสอบสองสามข้อที่นี่ เพื่อไม่ให้สัญญาณถูกวางไว้ในเซลล์ที่ถูกครอบครองแล้ว
    • คุณต้องตรวจสอบว่าเซลล์ที่ถูกคลิกว่างเปล่า มิฉะนั้น เราจะไม่ดำเนินการใดๆ และส่งผู้ใช้ไปยังหน้าเดียวกันโดยไม่เปลี่ยนพารามิเตอร์เซสชัน
    • เนื่องจากจำนวนเซลล์บนสนามเป็นเลขคี่ จึงเป็นไปได้ว่าจะมีการวางกากบาท แต่ไม่มีที่ว่างสำหรับศูนย์ ดังนั้น หลังจากที่เราใส่เครื่องหมายกากบาทแล้ว เราจะพยายามรับดัชนีของเซลล์ที่ว่าง (เมธอด getEmptyFieldIndex ของคลาส Field) หากดัชนีไม่เป็นลบ ให้ใส่ศูนย์ไว้ที่นั่น รหัส:
      package com.tictactoe;
      
      import javax.servlet.RequestDispatcher;
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import javax.servlet.http.HttpSession;
      import java.io.IOException;
      import java.util.List;
      
      @WebServlet(name = "LogicServlet", value = "/logic")
      public class LogicServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              // Get the current session
              HttpSession currentSession = req.getSession();
      
              // Get the playfield object from the session
              Field field = extractField(currentSession);
      
              // get the index of the cell that was clicked
              int index = getSelectedIndex(req);
              Sign currentSign = field.getField().get(index);
      
              // Check if the clicked cell is empty.
              // Otherwise, we do nothing and send the user to the same page without changes
              // parameters in the session
              if (Sign.EMPTY != currentSign) {
                  RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/index.jsp");
                  dispatcher.forward(req, resp);
                  return;
              }
      
              // put a cross in the cell that the user clicked on
              field.getField().put(index, Sign.CROSS);
      
              // Get an empty field cell
              int emptyFieldIndex = field.getEmptyFieldIndex();
      
              if (emptyFieldIndex >= 0) {
                  field.getField().put(emptyFieldIndex, Sign.NOUGHT);
              }
      
              // Read the list of icons
              List<Sign> data = field.getFieldData();
      
              // Update field object and icon list in session
              currentSession.setAttribute("data", data);
              currentSession.setAttribute("field", field);
      
              resp.sendRedirect("/index.jsp");
          }
      
          private int getSelectedIndex(HttpServletRequest request) {
              String click = request.getParameter("click");
              boolean isNumeric = click.chars().allMatch(Character::isDigit);
              return isNumeric ? Integer.parseInt(click) : 0;
          }
      
          private Field extractField(HttpSession currentSession) {
              Object fieldAttribute = currentSession.getAttribute("field");
              if (Field.class != fieldAttribute.getClass()) {
                  currentSession.invalidate();
                  throw new RuntimeException("Session is broken, try one more time");
              }
              return (Field) fieldAttribute;
          }
      }
  14. ในขั้นตอนนี้ คุณสามารถใส่เครื่องหมายกากบาท AI ตอบด้วยเลขศูนย์ แต่ไม่มีการตรวจสอบว่าจะหยุดเกมเมื่อใด สิ่งนี้สามารถเป็นได้สามกรณี:
    • หลังจากการเคลื่อนไม้กางเขนครั้งต่อไป แนวไม้กางเขนสามเส้นก็ก่อตัวขึ้น
    • หลังจากการย้อนกลับครั้งต่อไปด้วยศูนย์ เส้นศูนย์สามเส้นก็ก่อตัวขึ้น
    • หลังจากการข้ามครั้งต่อไปเซลล์ว่างก็สิ้นสุดลง
    ลองเพิ่มวิธีการตรวจสอบว่ามีกากบาท / ศูนย์สามตัวในแถวหรือไม่:
    /**
     * The method checks if there are three X/O's in a row.
     * returns true/false
     */
    private boolean checkWin(HttpServletResponse response, HttpSession currentSession, Field field) throws IOException {
        Sign winner = field.checkWin();
        if (Sign.CROSS == winner || Sign.NOUGHT == winner) {
            // Add a flag to indicate that someone has won
            currentSession.setAttribute("winner", winner);
    
            // Read the list of icons
            List<Sign> data = field.getFieldData();
    
            // Update this list in session
            currentSession.setAttribute("data", data);
    
            // helmet redirect
            response.sendRedirect("/index.jsp");
            return true;
        }
        return false;
    }
    ลักษณะเฉพาะของวิธีนี้คือ หากพบผู้ชนะ เราจะเพิ่มพารามิเตอร์อื่นในเซสชัน ซึ่งเราจะเปลี่ยนการแสดงผลใน"index.jsp"ในย่อหน้าต่อไปนี้
  15. มาเพิ่มการเรียกเมธอด “checkWin ” สอง ครั้งกับเมธอด “doGet” ครั้งแรกหลังจากตั้งค่ากากบาท ครั้งที่สอง - หลังจากตั้งค่าศูนย์
    // Check if the cross won after adding the user's last click
    if (checkWin(resp, currentSession, field)) {
        return;
    }
    if (emptyFieldIndex >= 0) {
        field.getField().put(emptyFieldIndex, Sign.NOUGHT);
        // Check if the zero won after adding the last zero
        if (checkWin(resp, currentSession, field)) {
            return;
        }
    }
  16. ในแง่ของพฤติกรรม แทบไม่มีอะไรเปลี่ยนแปลง (ยกเว้นว่าหากสัญญาณใดสัญญาณหนึ่งชนะ เลขศูนย์จะไม่ถูกวางอีกต่อไป ลองใช้ พารามิเตอร์ "winner" ใน "index.jsp"และแสดงผู้ชนะ เราใช้คำสั่งหลังตาราง: c:setc:if
    <hr>
    <c:set var="CROSSES" value="<%=Sign.CROSS%>"/>
    <c:set var="NOUGHTS" value="<%=Sign.NOUGHT%>"/>
    
    <c:if test="${winner == CROSSES}">
        <h1>CROSSES WIN!</h1>
    </c:if>
    <c:if test="${winner == NOUGHTS}">
        <h1>NOUGHTS WIN!</h1>
    </c:if>
    หากครอสส์ชนะ ข้อความ“ครอสส์ วิน!” ถ้าเลขศูนย์คือ“ไม่ชนะ!” . เป็นผลให้เราได้รับหนึ่งในสองจารึก:
  17. หากมีผู้ชนะ คุณต้องสามารถแก้แค้นได้ ในการทำเช่นนี้ คุณต้องมีปุ่มที่จะส่งคำขอไปยังเซิร์ฟเวอร์ และเซิร์ฟเวอร์จะทำให้เซสชันปัจจุบันใช้ไม่ได้และเปลี่ยนเส้นทางคำขอกลับไปที่“ /start”
    • ใน“index.jsp”ใน ส่วน “head”ให้เขียนสคริปต์“jquery ” เราจะส่งคำขอไปยังเซิร์ฟเวอร์โดยใช้ไลบรารีนี้
      <script src="<c:url value="/static/jquery-3.6.0.min.js"/>"></script>
    • ใน“index.jsp”ใน ส่วน “สคริปต์”ให้เพิ่มฟังก์ชันที่สามารถส่งคำขอ POST ไปยังเซิร์ฟเวอร์ เราจะทำให้ฟังก์ชันซิงโครนัส และเมื่อการตอบสนองมาจากเซิร์ฟเวอร์ มันจะโหลดหน้าปัจจุบันอีกครั้ง
      <script>
          function restart() {
              $.ajax({
                  url: '/restart',
                  type: 'POST',
                  contentType: 'application/json;charset=UTF-8',
                  async: false,
                  success: function () {
                      location.reload();
                  }
              });
          }
      </script>
    • ภายใน บล็อก “c:if”ให้เพิ่มปุ่มที่เมื่อคลิกแล้ว จะเรียกใช้ฟังก์ชันที่เราเพิ่งเขียน:
      <c:if test="${winner == CROSSES}">
          <h1>CROSSES WIN!</h1>
          <button onclick="restart()">Start again</button>
      </c:if>
      <c:if test="${winner == NOUGHTS}">
          <h1>NOUGHTS WIN!</h1>
          <button onclick="restart()">Start again</button>
      </c:if>
    • มาสร้างเซิร์ฟเล็ตใหม่ที่จะให้บริการURL "/restart"
      package com.tictactoe;
      
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      @WebServlet(name = "RestartServlet", value = "/restart")
      public class RestartServlet extends HttpServlet {
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
              req.getSession().invalidate();
              resp.sendRedirect("/start");
          }
      }
      หลังจากชนะ ปุ่ม "เริ่มใหม่อีกครั้ง" จะปรากฏ ขึ้น หลังจากคลิกแล้ว ช่องจะถูกล้างทั้งหมด และเกมจะเริ่มต้นใหม่
  18. ยังคงต้องพิจารณาสถานการณ์สุดท้าย จะเป็นอย่างไรหากผู้ใช้วางกากบาท ไม่มีทางชนะ และไม่มีที่สำหรับศูนย์ นี่คือการจับฉลาก และเราจะดำเนินการทันที:
    • ใน เซสชัน "LogicServlet"ให้เพิ่มพารามิเตอร์อื่น"draw"อัปเดตฟิลด์"data"และส่งการเปลี่ยนเส้นทางไปที่"index.jsp" :
      // If such a cell exists
      if (emptyFieldIndex >= 0) {}
      // If there is no empty cell and no one wins, then it's a draw
      else {
          // Add a flag to the session that signals that a draw has occurred
          currentSession.setAttribute("draw", true);
      
          // Read the list of icons
          List<Sign> data = field.getFieldData();
      
          // Update this list in session
          currentSession.setAttribute("data", data);
      
          // helmet redirect
          response.sendRedirect("/index.jsp");
          return;
      }
    • ใน"index.jsp"เราจะประมวลผลพารามิเตอร์นี้:
      <c:if test="${draw}">
          <h1>IT'S A DRAW</h1>
          <br>
          <button onclick="restart()">Start again</button>
      </c:if>
      จากผลการจับฉลาก เราจะได้รับข้อความที่เกี่ยวข้องและข้อเสนอให้เริ่มต้นใหม่:

เป็นอันเสร็จสิ้นการเขียนเกม

รหัสของคลาสและไฟล์ที่ใช้ทำงาน

InitServlet

package com.tictactoe;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
import java.util.Map;

@WebServlet(name = "InitServlet", value = "/start")
public class InitServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Create a new session
        HttpSession currentSession = req.getSession(true);

        // Create a playing field
        Field field = new Field();
        Map<Integer, Sign> fieldData = field.getField();

        // Get a list of field values
        List<Sign> data = field.getFieldData();

        // Adding field parameters to the session (needed to store state between requests)
        currentSession.setAttribute("field", field);
        // and field values ​​sorted by index (required for drawing crosses and zeroes)
        currentSession.setAttribute("data", data);

        // Redirect request to index.jsp page via server
        getServletContext().getRequestDispatcher("/index.jsp").forward(req, resp);
    }
}

LogicServlet

package com.tictactoe;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "LogicServlet", value = "/logic")
public class LogicServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Get the current session
        HttpSession currentSession = req.getSession();

        // Get the playfield object from the session
        Field field = extractField(currentSession);

        // get the index of the cell that was clicked
        int index = getSelectedIndex(req);
        Sign currentSign = field.getField().get(index);

        // Check if the clicked cell is empty.
        // Otherwise, we do nothing and send the user to the same page without changes
        // parameters in the session
        if (Sign.EMPTY != currentSign) {
            RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/index.jsp");
            dispatcher.forward(req, resp);
            return;
        }

        // put a cross in the cell that the user clicked on
        field.getField().put(index, Sign.CROSS);

        // Check if the cross has won after adding the user's last click
        if (checkWin(resp, currentSession, field)) {
            return;
        }

        // Get an empty field cell
        int emptyFieldIndex = field.getEmptyFieldIndex();

        if (emptyFieldIndex >= 0) {
            field.getField().put(emptyFieldIndex, Sign.NOUGHT);
            // Check if the zero won after adding the last zero
            if (checkWin(resp, currentSession, field)) {
                return;
            }
        }
        // If there is no empty cell and no one wins, then it's a draw
        else {
            // Add a flag to the session that signals that a draw has occurred
            currentSession.setAttribute("draw", true);

            // Read the list of icons
            List<Sign> data = field.getFieldData();

            // Update this list in session
            currentSession.setAttribute("data", data);

            // helmet redirect
            resp.sendRedirect("/index.jsp");
            return;
        }

        // Read the list of icons
        List<Sign> data = field.getFieldData();

        // Update field object and icon list in session
        currentSession.setAttribute("data", data);
        currentSession.setAttribute("field", field);

        resp.sendRedirect("/index.jsp");
    }

    /**
     * The method checks if there are three X/O's in a row.
     * returns true/false
     */
    private boolean checkWin(HttpServletResponse response, HttpSession currentSession, Field field) throws IOException {
        Sign winner = field.checkWin();
        if (Sign.CROSS == winner || Sign.NOUGHT == winner) {
            // Add a flag to indicate that someone has won
            currentSession.setAttribute("winner", winner);

            // Read the list of icons
            List<Sign> data = field.getFieldData();

            // Update this list in session
            currentSession.setAttribute("data", data);

            // helmet redirect
            response.sendRedirect("/index.jsp");
            return true;
        }
        return false;
    }

    private int getSelectedIndex(HttpServletRequest request) {
        String click = request.getParameter("click");
        boolean isNumeric = click.chars().allMatch(Character::isDigit);
        return isNumeric ? Integer.parseInt(click) : 0;
    }

    private Field extractField(HttpSession currentSession) {
        Object fieldAttribute = currentSession.getAttribute("field");
        if (Field.class != fieldAttribute.getClass()) {
            currentSession.invalidate();
            throw new RuntimeException("Session is broken, try one more time");
        }
        return (Field) fieldAttribute;
    }
}

รีสตาร์ทเซิร์ฟเล็ต

package com.tictactoe;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "RestartServlet", value = "/restart")
public class RestartServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        req.getSession().invalidate();
        resp.sendRedirect("/start");
    }
}

ดัชนี jsp _

<%@ page import="com.tictactoe.Sign" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<!DOCTYPE html>
<html>
<head>
    <link href="static/main.css" rel="stylesheet">
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <script src="<c:url value="/static/jquery-3.6.0.min.js"/>"></script>
    <title>Tic-Tac-Toe</title>
</head>
<body>
<h1>Tic-Tac-Toe</h1>

<table>
    <tr>
        <td onclick="window.location='/logic?click=0'">${data.get(0).getSign()}</td>
        <td onclick="window.location='/logic?click=1'">${data.get(1).getSign()}</td>
        <td onclick="window.location='/logic?click=2'">${data.get(2).getSign()}</td>
    </tr>
    <tr>
        <td onclick="window.location='/logic?click=3'">${data.get(3).getSign()}</td>
        <td onclick="window.location='/logic?click=4'">${data.get(4).getSign()}</td>
        <td onclick="window.location='/logic?click=5'">${data.get(5).getSign()}</td>
    </tr>
    <tr>
        <td onclick="window.location='/logic?click=6'">${data.get(6).getSign()}</td>
        <td onclick="window.location='/logic?click=7'">${data.get(7).getSign()}</td>
        <td onclick="window.location='/logic?click=8'">${data.get(8).getSign()}</td>
    </tr>
</table>

<hr>
<c:set var="CROSSES" value="<%=Sign.CROSS%>"/>
<c:set var="NOUGHTS" value="<%=Sign.NOUGHT%>"/>

<c:if test="${winner == CROSSES}">
    <h1>CROSSES WIN!</h1>
    <button onclick="restart()">Start again</button>
</c:if>
<c:if test="${winner == NOUGHTS}">
    <h1>NOUGHTS WIN!</h1>
    <button onclick="restart()">Start again</button>
</c:if>
<c:if test="${draw}">
    <h1>IT'S A DRAW</h1>
    <button onclick="restart()">Start again</button>
</c:if>

<script>
    function restart() {
        $.ajax({
            url: '/restart',
            type: 'POST',
            contentType: 'application/json;charset=UTF-8',
            async: false,
            success: function () {
                location.reload();
            }
        });
    }
</script>

</body>
</html>

main.css _

td {
    border: 3px solid black;
    padding: 10px;
    border-collapse: separate;
    margin: 10px;
    width: 100px;
    height: 100px;
    font-size: 50px;
    text-align: center;
    empty-cells: show;
   }