diff --git a/pom.xml b/pom.xml
index cb226e88..6b671e54 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,21 +12,35 @@
UTF-8
- 18
- 18
+ 21
+ 21
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 3.2.2
+ pom
+ import
+
+
+
+
- javax.servlet
- javax.servlet-api
- 4.0.1
+ jakarta.servlet
+ jakarta.servlet-api
provided
- javax.servlet
- jstl
- 1.2
+ jakarta.servlet.jsp.jstl
+ jakarta.servlet.jsp.jstl-api
+
+
+ org.glassfish.web
+ jakarta.servlet.jsp.jstl
diff --git a/src/main/java/com/tictactoe/controller/InitServlet.java b/src/main/java/com/tictactoe/controller/InitServlet.java
new file mode 100644
index 00000000..dfcd48b7
--- /dev/null
+++ b/src/main/java/com/tictactoe/controller/InitServlet.java
@@ -0,0 +1,37 @@
+package com.tictactoe.controller;
+
+import com.tictactoe.entity.Field;
+import com.tictactoe.entity.Sign;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.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();
+
+ // Получение списка значений поля
+ List data = field.getFieldData();
+
+ // Добавление в сессию параметров поля (нужно будет для хранения состояния между запросами)
+ currentSession.setAttribute("field", field);
+ // и значений поля, отсортированных по индексу (нужно для отрисовки крестиков и ноликов)
+ currentSession.setAttribute("data", data);
+
+ // Перенаправление запроса на страницу index.jsp через сервер
+ getServletContext().getRequestDispatcher("/WEB-INF/index.jsp").forward(req, resp);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/tictactoe/controller/LogicServlet.java b/src/main/java/com/tictactoe/controller/LogicServlet.java
new file mode 100644
index 00000000..67845f8c
--- /dev/null
+++ b/src/main/java/com/tictactoe/controller/LogicServlet.java
@@ -0,0 +1,125 @@
+package com.tictactoe.controller;
+
+import com.tictactoe.entity.Field;
+import com.tictactoe.entity.Sign;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.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);
+ Sign currentSign;
+ int index = getSelectedIndex(req);
+
+ // получаем индекс ячейки, по которой произошел клик
+
+ currentSign = field.getField().get(index);
+ // Проверяем, что ячейка, по которой был клик пустая.
+ // Иначе ничего не делаем и отправляем пользователя на ту же страницу без изменений
+ // параметров в сессии
+ if (Sign.EMPTY != currentSign) {
+ RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/index.jsp");
+ dispatcher.forward(req, resp);
+ return;
+ }
+ // ставим крестик в ячейке, по которой кликнул пользователь
+ field.getField().put(index, Sign.CROSS);
+ // Проверяем, не победил ли крестик после добавления последнего клика пользователя
+ if (checkWin(resp, req, currentSession, field)) {
+ return;
+ }
+ // Получаем пустую ячейку поля
+ int emptyFieldIndex = field.getEmptyFieldIndex();
+
+ if (emptyFieldIndex >= 0) {
+ field.getField().put(emptyFieldIndex, Sign.NOUGHT);
+ // Проверяем, не победил ли нолик после добавление последнего нолика
+ if (checkWin(resp, req, currentSession, field)) {
+ return;
+ }
+ }
+ // Если пустой ячейки нет и никто не победил - значит это ничья
+ else {
+ // Добавляем в сессию флаг, который сигнализирует что произошла ничья
+ currentSession.setAttribute("draw", true);
+
+ // Считаем список значков
+ List data = field.getFieldData();
+
+ // Обновляем этот список в сессии
+ currentSession.setAttribute("data", data);
+
+ // Шлем редирект
+ RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/index.jsp");
+ dispatcher.forward(req, resp);
+ return;
+ }
+
+ // Считаем список значков
+ List data = field.getFieldData();
+
+ // Обновляем объект поля и список значков в сессии
+ currentSession.setAttribute("data", data);
+ currentSession.setAttribute("field", field);
+
+ RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/index.jsp");
+ dispatcher.forward(req, resp);
+
+ }
+
+
+ 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;
+ }
+
+ /**
+ * Метод проверяет, нет ли трех крестиков/ноликов в ряд.
+ * Возвращает true/false
+ */
+ private boolean checkWin(HttpServletResponse resp, HttpServletRequest req, HttpSession currentSession, Field field) throws IOException, ServletException {
+ Sign winner = field.checkWin();
+ if (Sign.CROSS == winner || Sign.NOUGHT == winner) {
+ // Добавляем флаг, который показывает что кто-то победил
+ currentSession.setAttribute("winner", winner);
+
+ // Считаем список значков
+ List data = field.getFieldData();
+
+ // Обновляем этот список в сессии
+ currentSession.setAttribute("data", data);
+
+ // Шлем редирект
+ RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/index.jsp");
+ dispatcher.forward(req, resp);
+ return true;
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/tictactoe/controller/RestartServlet.java b/src/main/java/com/tictactoe/controller/RestartServlet.java
new file mode 100644
index 00000000..79594939
--- /dev/null
+++ b/src/main/java/com/tictactoe/controller/RestartServlet.java
@@ -0,0 +1,17 @@
+package com.tictactoe.controller;
+
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+
+@WebServlet(name = "RestartServlet", value = "/restart")
+public class RestartServlet extends HttpServlet {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ req.getSession().invalidate();
+ resp.sendRedirect("/start");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/tictactoe/Field.java b/src/main/java/com/tictactoe/entity/Field.java
similarity index 95%
rename from src/main/java/com/tictactoe/Field.java
rename to src/main/java/com/tictactoe/entity/Field.java
index c52d2a0d..8f94df38 100644
--- a/src/main/java/com/tictactoe/Field.java
+++ b/src/main/java/com/tictactoe/entity/Field.java
@@ -1,4 +1,4 @@
-package com.tictactoe;
+package com.tictactoe.entity;
import java.util.HashMap;
import java.util.List;
@@ -54,7 +54,7 @@ public Sign checkWin() {
for (List winPossibility : winPossibilities) {
if (field.get(winPossibility.get(0)) == field.get(winPossibility.get(1))
&& field.get(winPossibility.get(0)) == field.get(winPossibility.get(2))) {
- return field.get(winPossibility.get(0));
+ return field.get(winPossibility.getFirst());
}
}
return Sign.EMPTY;
diff --git a/src/main/java/com/tictactoe/Sign.java b/src/main/java/com/tictactoe/entity/Sign.java
similarity index 87%
rename from src/main/java/com/tictactoe/Sign.java
rename to src/main/java/com/tictactoe/entity/Sign.java
index e9db74d2..d4191e32 100644
--- a/src/main/java/com/tictactoe/Sign.java
+++ b/src/main/java/com/tictactoe/entity/Sign.java
@@ -1,4 +1,4 @@
-package com.tictactoe;
+package com.tictactoe.entity;
public enum Sign {
EMPTY(' '),
diff --git a/src/main/webapp/WEB-INF/index.jsp b/src/main/webapp/WEB-INF/index.jsp
new file mode 100644
index 00000000..9ad6e6aa
--- /dev/null
+++ b/src/main/webapp/WEB-INF/index.jsp
@@ -0,0 +1,65 @@
+<%@ page import="com.tictactoe.entity.Sign" %>
+<%@ page contentType="text/html;charset=UTF-8"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+
+
+ Tic-Tac-Toe
+
+ ">
+
+
+Tic-Tac-Toe
+
+
+ | ${data.get(0).getSign()} |
+ ${data.get(1).getSign()} |
+ ${data.get(2).getSign()} |
+
+
+ | ${data.get(3).getSign()} |
+ ${data.get(4).getSign()} |
+ ${data.get(5).getSign()} |
+
+
+ | ${data.get(6).getSign()} |
+ ${data.get(7).getSign()} |
+ ${data.get(8).getSign()} |
+
+
+
+
+
+
+
+ CROSSES WIN!
+
+
+
+ NOUGHTS WIN!
+
+
+
+ IT'S A DRAW
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000..24917953
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp
deleted file mode 100644
index 964cc071..00000000
--- a/src/main/webapp/index.jsp
+++ /dev/null
@@ -1,17 +0,0 @@
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
-
-
- Tic-Tac-Toe
-
-
-Tic-Tac-Toe
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/webapp/static/main.css b/src/main/webapp/static/main.css
index e69de29b..dd98a687 100644
--- a/src/main/webapp/static/main.css
+++ b/src/main/webapp/static/main.css
@@ -0,0 +1,19 @@
+body {
+ text-align: center;
+}
+table {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+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;
+}