今天我們將使用 servlet 和 JSP 編寫一個井字遊戲。
該項目將與之前的項目略有不同。它不僅包含任務,還包含如何執行任務的說明。也就是說,它將是“HOW TO ...”系列中的一個項目。
操作說明:
- 從存儲庫中分叉:https ://github.com/vasylmalik/project-servlet.git
- 將您的項目版本下載到您的計算機。
- 在 IDEA 中設置應用程序啟動:
- Alt + Shift + F9 -> 編輯配置... -> Alt + 插入 -> tom(進入搜索欄)-> Local。
- 之後,您需要單擊“配置”並指明下載和解壓縮帶有 Tomcat 的存檔文件的位置。
- 在“部署”選項卡中:Alt + insert -> Artifact ... -> tic-tac-toe:war exploded -> OK。
- 在“應用程序上下文”字段中:僅保留“/”(斜杠)。
- 按“應用”。
- 關閉設置窗口。
- 對自定義配置進行首次試運行。如果一切都正確完成,您的默認瀏覽器將打開,其中將是:
- 打開“pom.xml”文件。“依賴項”塊中有 2 個依賴項。
javax.servlet-api
負責servlets的規範。在開發期間需要“提供”的範圍,但在運行時不需要(Tomcat 已經在 lib 文件夾中具有此依賴項)。jstl
– 可以被認為是一個模板引擎。- “webapp”文件夾中有 3 個文件:
index.jsp
- 這是我們的模板(類似於 HTML 頁面)。它將包含標記和腳本。如果沒有我們在第 3 步中看到的配置,它就是作為初始頁面給出的名為“index”的文件。/static/main.css
- 樣式文件。和之前的項目一樣,這裡的一切都由你決定,想怎麼畫就怎麼畫。/static/jquery-3.6.0.min.js
- 我們的服務器將作為靜態分發的前端依賴項。- “com.tictactoe”包將包含所有 Java 代碼。現在有2個類:
Sign
- 枚舉,負責“交叉/零/無效”。Field
是我們的領域。這個類有一個“字段”映射。數據存儲的原則如下:井字遊戲字段的單元格從零開始編號。在第一行中是 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; }
- 現在讓我們添加以下功能:單擊單元格時,將向服務器發送請求,在該請求中我們將傳遞被單擊的單元格的索引作為參數。這個任務可以分為兩個部分:從前端發送請求,在服務器上接受請求。讓我們從前面開始改變。
讓我們為每個“d” 標籤添加一個“onclick”參數。在值中,我們指示當前頁面更改為指定的 URL。負責邏輯的 servlet 將具有 URL “/logic”。它需要一個名為“click”的參數。所以我們將傳遞用戶點擊的單元格的索引。
您可以通過瀏覽器中的開發人員面板檢查一切是否正確完成。例如,在 Chrome 中,它使用F12按鈕打開。單擊索引為 4 的單元格後,圖片將如下所示: 我們得到一個錯誤,因為我們還沒有創建一個可以將服務器發送到地址“logic”的 servlet 。<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”方法。
讓我們添加一個方法來獲取被單擊的單元格的索引。您還需要添加一個映射(此 servlet 將攔截請求的地址)。我建議通過註釋來做到這一點(但如果你喜歡困難,你也可以使用 web.xml)。通用servlet代碼:
現在,當單擊任何單元格時,我們將獲得該單元格在服務器上的索引(您可以通過在調試中運行服務器來確保)。並且將重定向到進行點擊的同一頁面。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; } }
- 現在我們可以點擊了,但這還不是遊戲。為了使遊戲具有邏輯性,您需要在請求之間保存遊戲狀態(叉在哪裡,零在哪裡)。最簡單的方法是將此數據存儲在會話中。使用這種方法,會話將存儲在服務器上,客戶端將在名為“JSESSIONID”的 cookie 中收到一個會話 ID 。但是session不需要每次都創建,只需要在遊戲開始的時候創建即可。讓我們為此啟動另一個 servlet,我們將其稱為“InitServlet”。我們將覆蓋其中的“doGet”方法,在該方法中我們將創建一個新會話,創建一個運動場,將這個運動場和一個 Sign 類型的列表放在會話屬性中,並將“ forward”發送到 index.jsp頁。代碼:
不要忘記,讓我們將啟動服務器後在瀏覽器中打開的起始頁更改為“/start”: 現在,在重新啟動服務器並單擊“請求標頭” 部分的瀏覽器開發人員菜單中的字段的任何單元格後,將會有一個帶有會話 ID 的 cookie: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”方法。讓我們將此行為添加到方法中:
- 我們將從會話中獲取 Field 類型的“字段”對象(我們將其取出到“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> 部分添加:
- 我們已經採取行動,現在輪到“零”了。讓我們在這裡添加一些檢查,以便標誌不會放置在已佔用的單元格中。
- 您需要檢查單擊的單元格是否為空。否則,我們什麼也不做,將用戶發送到同一頁面而不更改會話參數。
- 由於該字段上的單元格數量是奇數,因此可能放置了十字,但沒有空間放置零。因此,我們在打叉之後,嘗試獲取一個未被佔用的單元格的索引(Field類的getEmptyFieldIndex方法)。如果索引不是負數,則在那裡放一個零。 代碼:
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; }
- 讓我們向“doGet”方法添加兩次對“checkWin ”方法的調用。第一次設置十字後,第二次 - 設置零後。
// 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; } }
- 在行為方面,幾乎沒有任何變化(除瞭如果其中一個符號獲勝,則不再放置零。讓我們在“index.jsp”中使用“winner”參數並顯示獲勝者。我們在表格
c:set
後面使用指令:c:if
如果十字線獲勝,則顯示“十字線獲勝!” 的信息 ,如果零是“NOUGHTS WIN!” . 結果,我們可以獲得兩個銘文之一:<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”。
- 在“head”部分的“index.jsp”中,編寫腳本“jquery”。使用這個庫,我們將向服務器發送請求。
<script src="<c:url value="/static/jquery-3.6.0.min.js"/>"></script>
- 在“index.jsp”的“script”部分,添加一個可以向服務器發送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>
- 讓我們創建一個將為“/restart” URL 提供服務的新 servlet 。
勝利後會出現“重新開始”按鈕。點擊後,場地將被徹底清空,遊戲重新開始。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"); } }
- 在“head”部分的“index.jsp”中,編寫腳本“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”:
這樣就完成了遊戲的編寫。
他們使用的類和文件的代碼
初始化Servlet
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);
}
}
邏輯服務小程序
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;
}
}
重啟Servlet
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>
主.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