Hoje vamos escrever um jogo Tic-Tac-Toe usando servlets e JSP.
Este projeto será um pouco diferente dos anteriores. Ele conterá não apenas tarefas, mas também explicações sobre como realizá-las. Ou seja, será um projeto da série "COMO FAZER...".
Instrução:
- Fork do repositório: https://github.com/CodeGymCC/project-servlet.git
- Baixe sua versão do projeto para o seu computador.
- Configure a inicialização do aplicativo no IDEA:
- Alt + Shift + F9 -> Editar configurações… -> Alt + inserir -> tom (na barra de pesquisa) -> Local.
- Depois disso, você precisa clicar em “CONFIGURAR” e indicar onde o arquivo com o Tomcat foi baixado e descompactado.
- Na aba “Deployment”: Alt + insert -> Artifact… -> tic-tac-toe:war explodid -> OK.
- No campo “Application context”: deixe apenas “/” (barra).
- Pressione "APLICAR".
- Feche a janela de configurações.
- Faça o primeiro teste da configuração personalizada. Se tudo for feito corretamente, seu navegador padrão será aberto, no qual estará:
- Abra o arquivo "pom.xml" . Existem 2 dependências no bloco “dependências” .
javax.servlet-api
é responsável pela especificação de servlets. O escopo "provided" é necessário durante o desenvolvimento, mas não é necessário em tempo de execução (o Tomcat já possui essa dependência na pasta lib).jstl
– pode ser considerado como um mecanismo de modelo.- Existem 3 arquivos na pasta “webapp” :
index.jsp
- este é o nosso modelo (semelhante à página HTML). Ele conterá marcação e scripts. É o arquivo chamado “index” que é dado como página inicial, caso não haja configurações, que vimos no passo 3./static/main.css
- arquivo para estilos. Como no projeto anterior, tudo aqui é com você, pinte como quiser./static/jquery-3.6.0.min.js
- dependência de frontend que nosso servidor distribuirá como estático.- O pacote "com.tictactoe" conterá todo o código Java. Neste momento existem 2 classes:
Sign
- enum, que é responsável pelo "cross / zero / void" .Field
é o nosso campo. Esta classe tem um mapa de "campo" . O princípio de armazenamento de dados será o seguinte: as células do campo jogo da velha são numeradas a partir de zero. Na primeira linha 0, 1 e 2. Na segunda: 3, 4 e 5. E assim por diante. Existem também 3 métodos. “getEmptyFieldIndex” procura a primeira célula vazia (sim, nosso oponente não será muito esperto). "checkWin" verifica se o jogo acabou. Se houver uma linha de três cruzes, ele retorna uma cruz; se houver uma linha de três zeros, ele retorna um zero. Caso contrário, está vazio. "getFieldData" - retorna os valores do mapa "campo" como uma lista classificada em ordem crescente de índice.- As explicações sobre o modelo estão concluídas, agora você pode iniciar a tarefa. Vamos começar desenhando uma tabela 3 por 3. Para fazer isso, adicione o seguinte código a “index.jsp” :
Em seguida, removeremos os números da tabela e os substituiremos por uma cruz, zero ou um campo vazio. Além disso, dentro da tag “head”, inclua o arquivo de estilo. Para fazer isso, adicione uma linha:<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">
O conteúdo do arquivo de estilo depende de você. Eu usei este:
Depois de executar, meu resultado ficou assim: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; }
- Agora vamos adicionar a seguinte funcionalidade: quando uma célula for clicada, uma solicitação será enviada ao servidor, na qual passaremos como parâmetro o índice da célula que foi clicada. Essa tarefa pode ser dividida em duas partes: enviar uma solicitação pela frente, aceitar uma solicitação no servidor. Vamos começar na frente para variar.
Vamos adicionar um parâmetro "onclick" a cada tag "d" . No valor, indicamos a alteração da página atual para o URL especificado. O servlet que será responsável pela lógica terá a URL “/logic” . E vai levar um parâmetro chamado “click” . Então vamos passar o índice da célula que o usuário clicou.
Você pode verificar se tudo foi feito corretamente por meio do painel do desenvolvedor no navegador. Por exemplo, no Chrome, abre com o botão F12 . Como resultado de clicar em uma célula com índice 4, a imagem será a seguinte: Obtemos um erro porque ainda não criamos um servlet que possa enviar o servidor para o endereço “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>
- No pacote "com.tictactoe" crie uma classe "LogicServlet" que deve ser derivada da classe "javax.servlet.http.HttpServlet" . Na classe, sobrescreva o método “doGet” .
E vamos adicionar um método que vai pegar o índice da célula que foi clicada. Você também precisa adicionar um mapeamento (o endereço no qual este servlet interceptará a solicitação). Sugiro fazer isso através de uma anotação (mas se você gosta de dificuldades, também pode usar o web.xml). Código geral do servlet:
Agora, ao clicar em qualquer célula, obteremos o índice desta célula no servidor (você pode ter certeza executando o servidor em debug). E haverá um redirecionamento para a mesma página de onde foi feito o clique.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; } }
- Agora podemos clicar, mas ainda não é um jogo. Para que o jogo tenha lógica, você precisa salvar o estado do jogo (onde estão as cruzes, onde estão os zeros) entre as solicitações. A maneira mais fácil de fazer isso é armazenar esses dados na sessão. Com essa abordagem, a sessão será armazenada no servidor e o cliente receberá um ID de sessão em um cookie denominado “JSESSIONID” . Mas a sessão não precisa ser criada toda vez, mas apenas no início do jogo. Vamos iniciar outro servlet para isso, que chamaremos de "InitServlet" . Vamos sobrescrever o método “doGet” nele , no qual iremos criar uma nova sessão, criar um campo de jogo, colocar este campo de jogo e uma lista do tipo Sign nos atributos da sessão, e enviar “ forward” para o index.jsp página. Código:
E para não esquecer, vamos mudar a página inicial que abre no navegador após iniciar o servidor para “/start” : Agora após reiniciar o servidor e clicar em qualquer célula do campo no menu do desenvolvedor do navegador na seção “Request Headers” , haverá um cookie com o ID da sessão: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); } }
- Quando temos um repositório no qual podemos armazenar o estado entre as solicitações do cliente (navegador), podemos começar a escrever a lógica do jogo. A lógica que temos está em “LogicServlet” . Precisamos trabalhar com o método “doGet” . Vamos adicionar este comportamento ao método:
- vamos pegar o objeto “field” do tipo Field da sessão (vamos retirá-lo para o método “extractField” ).
- coloque uma cruz onde o usuário clicou (até agora sem nenhuma verificação).
O comportamento ainda não mudou, mas se você iniciar o servidor em depuração e definir um breakpoint na linha para onde o redirecionamento é enviado, poderá ver as “entranhas” do objeto “dados ” . Ali, de fato, aparece “CROSS” abaixo do índice que foi clicado.@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; }
- Agora é hora de exibir a cruz no frontend. Para isso, trabalharemos com o arquivo “index.jsp” e a tecnologia “JSTL” .
- Na seção <head> adicione:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
- Na tabela dentro de cada bloco <td>, altere o índice para uma construção que permite calcular valores. Por exemplo, para o índice zero:
<td onclick="window.location='/logic?click=0'">${data.get(0).getSign()}</td>
Agora, ao clicar em uma célula, aparecerá uma cruz ali:
- Na seção <head> adicione:
- Fizemos a nossa jogada, agora é a vez do "zero". E vamos adicionar algumas verificações aqui, para que os sinais não sejam colocados em células já ocupadas.
- Você precisa verificar se a célula que foi clicada está vazia. Caso contrário, não fazemos nada e enviamos o usuário para a mesma página sem alterar os parâmetros da sessão.
- Como o número de células no campo é ímpar, é possível que tenha sido colocada uma cruz, mas não há espaço para um zero. Portanto, após colocarmos uma cruz, tentamos obter o índice de uma célula desocupada (o método getEmptyFieldIndex da classe Field). Se o índice não for negativo, coloque um zero lá. Código:
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; } }
- Nesta fase, você pode colocar cruzes, a IA responde com zeros. Mas não há verificação de quando parar o jogo. Isso pode ocorrer em três casos:
- após o próximo movimento da cruz, uma linha de três cruzes foi formada;
- após o próximo movimento de retorno com um zero, uma linha de três zeros foi formada;
- após o próximo movimento da cruz, as células vazias terminaram.
A peculiaridade deste método é que, se o vencedor for encontrado, adicionamos outro parâmetro à sessão, com o qual alteraremos a exibição em “index.jsp” nos parágrafos seguintes./** * 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; }
- Vamos adicionar uma chamada ao método “checkWin ” duas vezes ao método “doGet” . A primeira vez após definir a cruz, a segunda - após definir o zero.
// 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; } }
- Em termos de comportamento, quase nada mudou (exceto que se um dos sinais vencer, os zeros não serão mais colocados. Vamos usar o parâmetro “winner” em “index.jsp” e exibir o vencedor. Usamos diretivas após a tabela:
c:set
c:if
Se os cruzamentos vencerem, a mensagem “CROSSES WIN!” , se os zeros forem “NOUGTS WIN!” . Como resultado, podemos obter uma das duas inscrições:<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>
- Se houver um vencedor, você precisa se vingar. Para fazer isso, você precisa de um botão que enviará uma solicitação ao servidor. E o servidor invalidará a sessão atual e redirecionará a solicitação de volta para “/start” .
- Em “index.jsp” na seção “head” , escreva o script “jquery” . Usando esta biblioteca, enviaremos uma solicitação ao servidor.
<script src="<c:url value="/static/jquery-3.6.0.min.js"/>"></script>
- Em “index.jsp” na seção “script” , adicione uma função que pode enviar uma solicitação POST ao servidor. Faremos a função síncrona e, quando vier uma resposta do servidor, ela recarregará a página atual.
<script> function restart() { $.ajax({ url: '/restart', type: 'POST', contentType: 'application/json;charset=UTF-8', async: false, success: function () { location.reload(); } }); } </script>
- Dentro dos blocos “c:if” , adicione um botão que, ao ser clicado, chama a função que acabamos de escrever:
<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>
- Vamos criar um novo servlet que servirá a URL "/restart" .
Após a vitória, aparecerá o botão “Começar de novo” . Depois de clicar nele, o campo será totalmente limpo e o jogo será reiniciado.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"); } }
- Em “index.jsp” na seção “head” , escreva o script “jquery” . Usando esta biblioteca, enviaremos uma solicitação ao servidor.
- Resta considerar a última situação. E se o usuário colocar uma cruz, não houve vitória e não há lugar para zero? Então este é um empate e vamos processá-lo agora:
- Na sessão "LogicServlet" , adicione outro parâmetro "draw" , atualize o campo "data" e envie um redirecionamento para "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; }
- Em "index.jsp" iremos processar este parâmetro:
Como resultado de um sorteio, receberemos a mensagem correspondente e uma oferta para recomeçar:<c:if test="${draw}"> <h1>IT'S A DRAW</h1> <br> <button onclick="restart()">Start again</button> </c:if>
- Na sessão "LogicServlet" , adicione outro parâmetro "draw" , atualize o campo "data" e envie um redirecionamento para "index.jsp" :
Isso completa a escrita do jogo.
Código das classes e arquivos com os quais trabalharam
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;
}
}
RestartServlet
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 _
<%@ 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