วันนี้เราจะเขียนเกม Tic-Tac-Toe โดยใช้ servlets และ JSP
โครงการนี้จะแตกต่างจากโครงการก่อนหน้าเล็กน้อย มันจะประกอบด้วยงานไม่เพียง แต่ยังมีคำอธิบายวิธีการทำ นั่นคือจะเป็นโครงการจากซีรีส์ "HOW TO ... "
คำแนะนำ:
- แยกจากที่เก็บ: https://github.com/CodeGymCC/project-servlet.git
- ดาวน์โหลดเวอร์ชันของโครงการลงในคอมพิวเตอร์ของคุณ
- ตั้งค่าการเปิดตัวแอปพลิเคชันใน IDEA:
- Alt + Shift + F9 -> Edit Configurations... -> Alt + insert -> tom (ในแถบค้นหา) -> Local
- หลังจากนั้นคุณต้องคลิก "กำหนดค่า" และระบุตำแหน่งที่ดาวน์โหลดและคลายไฟล์เก็บถาวรด้วย Tomcat
- ในแท็บ "ปรับใช้": Alt + แทรก -> สิ่งประดิษฐ์... -> tic-tac-toe:war ระเบิด -> ตกลง
- ในช่อง "บริบทของแอปพลิเคชัน" ให้เว้นเฉพาะ "/" (เครื่องหมายทับ)
- กด "สมัคร"
- ปิดหน้าต่างการตั้งค่า
- ทำการทดสอบครั้งแรกของการกำหนดค่าที่กำหนดเอง หากทำทุกอย่างถูกต้อง เบราว์เซอร์เริ่มต้นของคุณจะเปิดขึ้น ซึ่งจะเป็น:
- เปิด ไฟล์ "pom.xml " มีการอ้างอิง 2 รายการใน บล็อก "การพึ่งพา"
javax.servlet-api
รับผิดชอบข้อกำหนดของเซิร์ฟเล็ต จำเป็นต้องมี ขอบเขต"ให้ไว้"ในระหว่างการพัฒนา แต่ไม่จำเป็นสำหรับรันไทม์ (Tomcat มีการอ้างอิงนี้อยู่แล้วในโฟลเดอร์ lib)jstl
– ถือได้ว่าเป็นเครื่องมือแม่แบบ- มี 3 ไฟล์ ใน โฟลเดอร์ “webapp” :
index.jsp
- นี่คือเทมเพลตของเรา (คล้ายกับหน้า HTML) มันจะมีมาร์กอัปและสคริปต์ เป็นไฟล์ชื่อ“ดัชนี”ที่ให้ไว้เป็นหน้าแรก หากไม่มีการกำหนดค่า ซึ่งเราเห็นในขั้นตอนที่ 3/static/main.css
- ไฟล์สำหรับสไตล์ เช่นเดียวกับในโครงการก่อนหน้านี้ ทุกอย่างขึ้นอยู่กับคุณ วาดตามที่คุณต้องการ/static/jquery-3.6.0.min.js
- การพึ่งพาส่วนหน้าซึ่งเซิร์ฟเวอร์ของเราจะแจกจ่ายเป็นแบบคงที่- แพ็คเกจ"com.tictactoe"จะมีโค้ด Java ทั้งหมด ตอนนี้มี 2 คลาสคือ
Sign
- enum ซึ่งรับผิดชอบในการ"ข้าม / ศูนย์ / โมฆะ "Field
คือสนามของเรา คลาสนี้มี แผนที่ "ฟิลด์ " หลักการของการจัดเก็บข้อมูลจะเป็นดังนี้: เซลล์ของฟิลด์ tic-tac-toe จะมีหมายเลขจากศูนย์ ในบรรทัดแรก 0, 1 และ 2 ในบรรทัดที่สอง: 3, 4 และ 5 และอื่น ๆ นอกจากนี้ยังมี 3 วิธี “getEmptyFieldIndex”ค้นหาเซลล์ว่างเซลล์แรก (ใช่ คู่ต่อสู้ของเราจะไม่ฉลาดนัก) "checkWin" ตรวจสอบว่าเกมจบลงหรือไม่ ถ้ามีแถวที่มีกากบาทสามตัว ก็จะส่งกลับค่ากากบาท ถ้ามีแถวที่มีเลขศูนย์สามตัว ก็จะส่งกลับค่าศูนย์ มิฉะนั้นจะว่างเปล่า "getFieldData" - ส่งคืนค่าของ แผนที่ "ฟิลด์"เป็นรายการที่เรียงลำดับจากน้อยไปหามาก- คำอธิบายเกี่ยวกับเทมเพลตเสร็จสิ้นแล้ว ตอนนี้คุณสามารถเริ่มงานได้ เริ่มจากการวาดตารางขนาด 3 คูณ 3 โดยเพิ่มโค้ดต่อไปนี้ใน“index.jsp” :
จากนั้นเราจะลบตัวเลขในตารางและแทนที่ด้วยกากบาท ศูนย์ หรือฟิลด์ว่าง นอกจากนี้ ภายในแท็ก "head" ให้รวมไฟล์สไตล์ไว้ด้วย ในการดำเนินการนี้ ให้เพิ่มบรรทัด:<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>
<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; }
- ตอนนี้มาเพิ่มฟังก์ชันต่อไปนี้: เมื่อเซลล์ถูกคลิก คำขอจะถูกส่งไปยังเซิร์ฟเวอร์ ซึ่งเราจะส่งดัชนีของเซลล์ที่ถูกคลิกเป็นพารามิเตอร์ งานนี้สามารถแบ่งออกเป็นสองส่วน: ส่งคำขอจากส่วนหน้า ยอมรับคำขอบนเซิร์ฟเวอร์ เรามาเริ่มกันที่ด้านหน้าเพื่อการเปลี่ยนแปลง
มา เพิ่ม พารามิเตอร์ "onclick" ให้กับแท็ ก"d" แต่ละ แท็ก ในค่า เราระบุการเปลี่ยนแปลงของหน้าปัจจุบันเป็น URL ที่ระบุ เซิร์ฟเล็ตที่จะรับผิดชอบตรรกะจะมี URL “/logic ” และจะใช้พารามิเตอร์ที่เรียกว่า"คลิก" . ดังนั้นเราจะส่งดัชนีของเซลล์ที่ผู้ใช้คลิก
คุณสามารถตรวจสอบว่าทุกอย่างถูกต้องผ่านแผงผู้พัฒนาในเบราว์เซอร์ ตัวอย่างเช่น ใน Chrome จะเปิดขึ้นพร้อมกับปุ่มF12 จากการคลิกที่เซลล์ที่มีดัชนี 4 รูปภาพจะเป็นดังนี้: เราได้รับข้อผิดพลาดเนื่องจากเรายังไม่ได้สร้างเซิร์ฟเล็ตที่สามารถส่งเซิร์ฟเวอร์ไปยังที่อยู่" ตรรกะ"<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>
- ในแพ็คเกจ "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; } }
- ตอนนี้เราคลิกได้แล้ว แต่ยังไม่ใช่เกม เพื่อให้เกมมีลอจิก คุณจะต้องบันทึกสถานะของเกม (ที่กากบาทและที่ศูนย์อยู่) ระหว่างคำขอ วิธีที่ง่ายที่สุดในการทำเช่นนี้คือการจัดเก็บข้อมูลนี้ในเซสชัน ด้วยวิธีนี้ เซสชันจะถูกเก็บ ไว้บนเซิร์ฟเวอร์ และไคลเอ็นต์จะได้รับ ID เซสชันในคุกกี้ชื่อ“JSESSIONID” แต่ไม่จำเป็นต้องสร้างเซสชันทุกครั้ง แต่เฉพาะตอนเริ่มเกมเท่านั้น มาเริ่มเซิร์ฟเล็ตอื่นสำหรับสิ่งนี้ ซึ่งเราจะเรียกว่า" InitServlet" เราจะแทนที่เมธอด "doGet"ในนั้นซึ่งเราจะสร้างเซสชันใหม่ สร้างสนามเด็กเล่น ใส่สนามเด็กเล่นนี้และรายการประเภทลงชื่อเข้าใช้แอตทริบิวต์เซสชัน และส่ง "ส่งต่อ"ไปที่ index.jsp หน้าหนังสือ. รหัส:
และอย่าลืม เรามาเปลี่ยนหน้าเริ่มต้นที่เปิดในเบราว์เซอร์หลังจากเริ่มเซิร์ฟเวอร์เป็น“/start” : ตอนนี้หลังจากรีสตาร์ทเซิร์ฟเวอร์และคลิกที่เซลล์ใดๆ ของฟิลด์ในเมนูผู้พัฒนาเบราว์เซอร์ในส่วน"ส่วนหัวคำขอ"จะมีคุกกี้ที่มี ID เซสชัน: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” เราต้องทำงานกับ เมธอด “ doGet” มาเพิ่มพฤติกรรมนี้ให้กับเมธอด:
- เราจะได้ วัตถุ"ฟิลด์"ของประเภทฟิลด์จากเซสชัน (เราจะนำไปใช้กับ เมธอด "extractField" )
- วางกากบาทที่ผู้ใช้คลิก (จนถึงขณะนี้ไม่มีการตรวจสอบใดๆ)
ลักษณะการทำงานยังไม่เปลี่ยนแปลง แต่ถ้าคุณเริ่มเซิร์ฟเวอร์ในการแก้ไขจุดบกพร่องและตั้งค่าเบรกพอยต์บนบรรทัดที่ส่งการเปลี่ยนเส้นทาง คุณจะเห็น "อวัยวะภายใน" ของวัตถุ " ข้อมูล" ที่นั่น "CROSS"ปรากฏใต้ดัชนีที่ถูกคลิก@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; }
- ตอนนี้ได้เวลาแสดงกากบาทที่ส่วนหน้าแล้ว ในการทำเช่น นี้ เราจะทำงานกับ ไฟล์ “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>
ตอนนี้ เมื่อคุณคลิกที่เซลล์ กากบาทจะปรากฏขึ้นที่นั่น:
- ในส่วน <head> เพิ่ม:
- เราได้ดำเนินการแล้ว ตอนนี้ถึงคราวของ "ศูนย์" แล้ว และขอเพิ่มการตรวจสอบสองสามข้อที่นี่ เพื่อไม่ให้สัญญาณถูกวางไว้ในเซลล์ที่ถูกครอบครองแล้ว
- คุณต้องตรวจสอบว่าเซลล์ที่ถูกคลิกว่างเปล่า มิฉะนั้น เราจะไม่ดำเนินการใดๆ และส่งผู้ใช้ไปยังหน้าเดียวกันโดยไม่เปลี่ยนพารามิเตอร์เซสชัน
- เนื่องจากจำนวนเซลล์บนสนามเป็นเลขคี่ จึงเป็นไปได้ว่าจะมีการวางกากบาท แต่ไม่มีที่ว่างสำหรับศูนย์ ดังนั้น หลังจากที่เราใส่เครื่องหมายกากบาทแล้ว เราจะพยายามรับดัชนีของเซลล์ที่ว่าง (เมธอด 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; } }
- ในขั้นตอนนี้ คุณสามารถใส่เครื่องหมายกากบาท AI ตอบด้วยเลขศูนย์ แต่ไม่มีการตรวจสอบว่าจะหยุดเกมเมื่อใด สิ่งนี้สามารถเป็นได้สามกรณี:
- หลังจากการเคลื่อนไม้กางเขนครั้งต่อไป แนวไม้กางเขนสามเส้นก็ก่อตัวขึ้น
- หลังจากการย้อนกลับครั้งต่อไปด้วยศูนย์ เส้นศูนย์สามเส้นก็ก่อตัวขึ้น
- หลังจากการข้ามครั้งต่อไปเซลล์ว่างก็สิ้นสุดลง
ลักษณะเฉพาะของวิธีนี้คือ หากพบผู้ชนะ เราจะเพิ่มพารามิเตอร์อื่นในเซสชัน ซึ่งเราจะเปลี่ยนการแสดงผลใน"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; }
- มาเพิ่มการเรียกเมธอด “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; } }
- ในแง่ของพฤติกรรม แทบไม่มีอะไรเปลี่ยนแปลง (ยกเว้นว่าหากสัญญาณใดสัญญาณหนึ่งชนะ เลขศูนย์จะไม่ถูกวางอีกต่อไป ลองใช้ พารามิเตอร์ "winner" ใน "index.jsp"และแสดงผู้ชนะ เราใช้คำสั่งหลังตาราง:
c:set
c: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>
- หากมีผู้ชนะ คุณต้องสามารถแก้แค้นได้ ในการทำเช่นนี้ คุณต้องมีปุ่มที่จะส่งคำขอไปยังเซิร์ฟเวอร์ และเซิร์ฟเวอร์จะทำให้เซสชันปัจจุบันใช้ไม่ได้และเปลี่ยนเส้นทางคำขอกลับไปที่“ /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"); } }
- ใน“index.jsp”ใน ส่วน “head”ให้เขียนสคริปต์“jquery ” เราจะส่งคำขอไปยังเซิร์ฟเวอร์โดยใช้ไลบรารีนี้
- ยังคงต้องพิจารณาสถานการณ์สุดท้าย จะเป็นอย่างไรหากผู้ใช้วางกากบาท ไม่มีทางชนะ และไม่มีที่สำหรับศูนย์ นี่คือการจับฉลาก และเราจะดำเนินการทันที:
- ใน เซสชัน "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>
- ใน เซสชัน "LogicServlet"ให้เพิ่มพารามิเตอร์อื่น"draw"อัปเดตฟิลด์"data"และส่งการเปลี่ยนเส้นทางไปที่"index.jsp" :
เป็นอันเสร็จสิ้นการเขียนเกม
รหัสของคลาสและไฟล์ที่ใช้ทำงาน
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;
}
GO TO FULL VERSION