Сьогодні писатимемо гру «Хрестики-Нолики» із використанням сервлетів та JSP.
Цей проєкт трохи відрізнятиметься від попередніх. У ньому будуть не лише завдання, а й пояснення, як їх зробити. Тобто це буде проєкт із серії «HOW TO…».
Інструкція:
- Зробити fork із репозиторію: https://github.com/vasylmalik/project-servlet.git
- Завантажити свою версію проєкту на комп'ютер.
- Налаштувати запуск програми в IDEA:
- Alt + Shift + F9 -> Edit Configurations ... -> Alt + insert -> tom (у рядок пошуку) -> Local.
- Після цього потрібно натиснути “CONFIGURE” та вказати, куди завантажено і розпаковано архів із Tomcat.
- У вкладці "Deployment": Alt + insert -> Artifact ... -> tic-tac-toe: war exploded -> OK.
- У полі “Application context”: залишити лише “/” (слеш).
- Натиснути “APPLY”.
- Закрити вікно налаштування.
- Зробити перший пробний запуск налаштованої конфігурації. Якщо все зробити правильно – відкриється твій браузер за замовчуванням, у якому буде:
- Відкрий файл “pom.xml”. У блоці “dependencies” є дві залежності.
javax.servlet-api
– відповідає за специфікацію сервлетів. Scope "provided" потрібен під час розробки, але не потрібен у рантаймі (у Tomcat вже є ця залежність у папці lib).jstl
– можна розглядати як шаблонизатор.- У папці "webapp" є 3 файли:
index.jsp
– це наш шаблон (аналог HTML-сторінки). У ньому буде розмітка та скрипти. Саме файл із назвою “index” віддається як початкова сторінка, якщо немає будь-яких конфігурацій, як ми побачили в п.3./static/main.css
– файл для стилів. Як і в минулому проєкті, тут все на твій розсуд, розфарбовуй як хочеш./static/jquery-3.6.0.min.js
– фронтенд залежність, яку наш сервер роздаватиме як статику.- У пакеті com.tictactoe буде весь Java-код. Наразі там є 2 класи:
Sign
– enum, який відповідає за “хрестик/нуль/порожнеча”.Field
– це наше поле. У цьому класі є карта "field". Принцип зберігання даних буде таким: осередки поля хрестиків-нуліків пронумеровані з нуля. У першому рядку 0, 1 та 2. У другому: 3, 4 та 5, тощо. Є також 3 методи. "getEmptyFieldIndex" шукає перший незайнятий осередок (так, суперник у нас буде не дуже розумним). "checkWin" перевіряє, чи не завершена гра. Якщо є ряд із трьох хрестиків – повертає хрестик, якщо із трьох нуликів – нулик. Інакше – порожнечу. "getFieldData" – повертає значення карти "field" як список, відсортований за зростанням індексів.- Пояснення щодо темпліту завершено: тепер можна братися за виконання завдання. Почнемо з того, що намалюємо таблицю 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-адресу. Сервлет, який відповідатиме за логіку, матиме URL “/logic” і буде приймати параметр під назвою “click”. Так будемо передавати індекс ячейки, на яку клікнув користувач.
Перевірити, що все зроблено правильно, можна через панель розробника в браузері. Наприклад, у Chrome вона відкривається кнопкою F12. В результаті кліка на ячейку з індексом 4 картина буде така: Помилки ми отримуємо тому, що сервлет, який може віддати сервер за адресою "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>
- У пакеті "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; } }
- Коли в нас з'явилося сховище, в якому ми можемо зберігати стан між запитами клієнта (браузера), можна починати писати логіку гри. Логіка у нас знаходиться в “LogicServlet”. Працювати потрібно з методом “doGet”. Давай додамо до методу таку поведінку:
- отримаємо об'єкт "field" типу Field із сесії (винесемо в метод "extractField").
- поставимо хрестик там, де клікнув користувач (поки що без будь-яких перевірок).
Поведінка поки не змінилася, але якщо запустити сервер у дебаг режимі та поставити брейкпоінт на рядку, звідки надсилається редирект, можна подивитися "нутрощі" об'єкта "data". Там справді з'являється “CROSS” під індексом, на який був клік.@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Отримуємо поточну сесію HttpSession currentSession = req.getSession(); // Отримуємо об'єкт ігрового поля з сесії Field field = extractField(currentSession); // Отримуємо індекс ячейки, на яку відбувся клік int index = getSelectedIndex(req); // Ставимо хрестик в ячейці, на яку клікнув користувач field.getField().put(index, Sign.CROSS); // Рахуємо список значків List<Sign> data = field.getFieldData(); // Оновлюємо об'єкт поля і список значків у сесії 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 { // Отримуємо поточну сесію HttpSession currentSession = req.getSession(); // Отримуємо об'єкт ігрового поля з сесії Field field = extractField(currentSession); // Отримуємо індекс ячейки, на яку відбувся клік int index = getSelectedIndex(req); Sign currentSign = field.getField().get(index); // Перевіряємо, що ячейка, на яку відбувся клік, порожня. // В іншому випадку нічого не робимо і посилаємо користувача на ту ж саму сторінку без змін // параметрів у сесії if (Sign.EMPTY != currentSign) { RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/index.jsp"); dispatcher.forward(req, resp); return; } // Ставимо хрестик в ячейці, на яку клікнув користувач field.getField().put(index, Sign.CROSS); // Отримуємо порожню ячейку поля int emptyFieldIndex = field.getEmptyFieldIndex(); if (emptyFieldIndex >= 0) { field.getField().put(emptyFieldIndex, Sign.NOUGHT); } // Рахуємо список значків List<Sign> data = field.getFieldData(); // Оновлюємо об'єкт поля і список значків у сесії 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” у наступних пунктах./** * Метод перевіряє, чи нема трьох хрестиків/нуликів в один ряд. * Повертає true/false */ private boolean checkWin(HttpServletResponse response, HttpSession currentSession, Field field) throws IOException { Sign winner = field.checkWin(); if (Sign.CROSS == winner || Sign.NOUGHT == winner) { // Додаємо прапорець, який показує, що хтось переміг currentSession.setAttribute("winner", winner); // Рахуємо список значків List<Sign> data = field.getFieldData(); // Оновлюємо цей список у сесії currentSession.setAttribute("data", data); // Шлемо редирект response.sendRedirect("/index.jsp"); return true; } return false; }
- Додамо до методу "doGet" двічі виклик методу "checkWin". Перший раз після встановлення хрестика, другий – після встановлення нулика.
// Перевіряємо, чи не переміг хрестик після додавання останнього кліку користувача if (checkWin(resp, currentSession, field)) { return; }
if (emptyFieldIndex >= 0) { field.getField().put(emptyFieldIndex, Sign.NOUGHT); // Перевіряємо, чи не переміг нулик після додавання останнього нулика if (checkWin(resp, currentSession, field)) { return; } }
- У поведінці майже нічого не змінилося (окрім того, що у разі перемоги одного зі знаків перестають ставитися нулики. Давай у “index.jsp” використаємо параметр “winner” та виведемо переможця. Використовуємо директиви
c:set
іc:if
після таблиці:
Якщо переможуть хрестики, виведеться повідомлення “CROSSES WIN!”, якщо нулі – “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”.
- У “index.jsp” у секції “head” пропишемо скрипт “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>
- Створимо новий сервлет, який обслуговуватиме URL “/restart”.
Після перемоги з'явиться кнопка "Start again". Після натискання на неї поле повністю очиститься, і гра почнеться спочатку.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 (emptyFieldIndex >= 0) { … } // Якщо порожньої ячейки нема і ніхто не переміг – це нічия else { // Додаємо до сесії прапорець, який сигналізує, що відбулася нічия currentSession.setAttribute("draw", true); // Рахуємо список значків List<Sign> data = field.getFieldData(); // Оновлюємо цей список у сесії currentSession.setAttribute("data", data); // Шлемо редирект 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":
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 {
// Створення нової сесії
HttpSession currentSession = req.getSession(true);
// Створення ігрового поля поля
Field field = new Field();
Map<Integer, Sign> fieldData = field.getField();
// Отримання списку значень поля
List<Sign> data = field.getFieldData();
// Додавання до сесії параметрів поля (буде потрібно для зберігання стану між запитами)
currentSession.setAttribute("field", field);
// і значень поля, відсортованих за індексом (потрібно для промалювання хрестиків і нуликів)
currentSession.setAttribute("data", data);
// Перенаправлення запиту на сторінку index.jsp через сервер
getServletContext().getRequestDispatcher("/index.jsp").forward(req, resp);
}
}
І щоб не забути, давай стартову сторінку, яка відкривається в браузері після запуску сервера, змінимо на “/start”: Тепер після перезапуску сервера та кліку на будь-яку ячейку поля в меню розробника браузера в секції “Request Headers” буде cookies з ідентифікатором сесії:
На цьому написання гри завершено.
Код класів та файлів, з якими ми працювали
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 {
// Створення нової сесії
HttpSession currentSession = req.getSession(true);
// Створення ігрового поля
Field field = new Field();
Map<Integer, Sign> fieldData = field.getField();
// Отримання списку значень поля
List<Sign> data = field.getFieldData();
// Додавання до сесії параметрів поля (потрібно буде для зберігання стану між запитами)
currentSession.setAttribute("field", field);
// та значень поля, що відсортовані за індексом (потрібно для промальовки хрестиків і нуликів)
currentSession.setAttribute("data", data);
// Перенаправлення запиту на сторінку index.jsp через сервер
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 {
// Отримуємо поточну сесію
HttpSession currentSession = req.getSession();
// Отримуємо об'єкт ігрового поля з сесії
Field field = extractField(currentSession);
// Отримуємо індекс ячейки, на яку відбувся клік
int index = getSelectedIndex(req);
Sign currentSign = field.getField().get(index);
// Перевіряємо, що ячейка, на яку клікнули, порожня.
// В іншому випадку нічого не робимо і направляємо користувача на ту ж сторінку без змін
// параметрів у сесії
if (Sign.EMPTY != currentSign) {
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/index.jsp");
dispatcher.forward(req, resp);
return;
}
// ставимо хрестик у ячейці, по якій клікнув користувач
field.getField().put(index, Sign.CROSS);
// Перевіряємо, чи не переміг хрестик після додавання останнього кліка користувача
if (checkWin(resp, currentSession, field)) {
return;
}
// Отримуємо порожню ячейку поля
int emptyFieldIndex = field.getEmptyFieldIndex();
if (emptyFieldIndex >= 0) {
field.getField().put(emptyFieldIndex, Sign.NOUGHT);
// Перевіряємо, чи не переміг нулик після додавання останнього нулика
if (checkWin(resp, currentSession, field)) {
return;
}
}
// Якщо порожньої ячейки нема і ніхто не переміг – це нічия
else {
// Додаємо до сесії прапорець, який сигналізує, що відбулася нічия
currentSession.setAttribute("draw", true);
// Рахуємо список значків
List<Sign> data = field.getFieldData();
// Оновлюємо цей список в сесії
currentSession.setAttribute("data", data);
// Шлемо редирект
resp.sendRedirect("/index.jsp");
return;
}
// Рахуємо список значків
List<Sign> data = field.getFieldData();
// Оновлюємо об'єкт поля і список значків у сесії
currentSession.setAttribute("data", data);
currentSession.setAttribute("field", field);
resp.sendRedirect("/index.jsp");
}
/**
* Метод перевіряє, чи нема трьох хрестиків/нуликов в ряд.
* Повертає true/false
*/
private boolean checkWin(HttpServletResponse response, HttpSession currentSession, Field field) throws IOException {
Sign winner = field.checkWin();
if (Sign.CROSS == winner || Sign.NOUGHT == winner) {
// Додаємо прапорець, який показує, що хтось переміг
currentSession.setAttribute("winner", winner);
// Рахуємо список значків
List<Sign> data = field.getFieldData();
// Оновлюємо цей список у сесїі
currentSession.setAttribute("data", data);
// Шлемо редирект
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;
}
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ