diff --git a/pom.xml b/pom.xml
index cb226e88..06fa0c5c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,15 +18,20 @@
- javax.servlet
- javax.servlet-api
- 4.0.1
+ jakarta.servlet
+ jakarta.servlet-api
+ 6.0.0
provided
- javax.servlet
- jstl
- 1.2
+ jakarta.servlet.jsp.jstl
+ jakarta.servlet.jsp.jstl-api
+ 3.0.0
+
+
+ org.glassfish.web
+ jakarta.servlet.jsp.jstl
+ 3.0.1
diff --git a/src/main/java/com/tictactoe/Field.java b/src/main/java/com/tictactoe/Field.java
index c52d2a0d..10551169 100644
--- a/src/main/java/com/tictactoe/Field.java
+++ b/src/main/java/com/tictactoe/Field.java
@@ -52,9 +52,11 @@ 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));
+ Sign sign0 = field.get(winPossibility.get(0));
+ if ((sign0 != Sign.EMPTY)
+ && (sign0 == field.get(winPossibility.get(1)))
+ && (sign0 == field.get(winPossibility.get(2)))) {
+ return sign0;
}
}
return Sign.EMPTY;
diff --git a/src/main/java/com/tictactoe/IndexServlet.java b/src/main/java/com/tictactoe/IndexServlet.java
new file mode 100644
index 00000000..3b4f2cbe
--- /dev/null
+++ b/src/main/java/com/tictactoe/IndexServlet.java
@@ -0,0 +1,19 @@
+package com.tictactoe;
+
+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 java.io.IOException;
+
+@WebServlet("")
+public class IndexServlet extends HttpServlet {
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ req.getRequestDispatcher("/start")
+ .forward(req, resp);
+ }
+}
diff --git a/src/main/java/com/tictactoe/LogicServlet.java b/src/main/java/com/tictactoe/LogicServlet.java
new file mode 100644
index 00000000..6217bec5
--- /dev/null
+++ b/src/main/java/com/tictactoe/LogicServlet.java
@@ -0,0 +1,90 @@
+package com.tictactoe;
+
+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 {
+
+ private static final String PATH_INDEX = "WEB-INF/index.jsp";
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ HttpSession currentSession = req.getSession();
+ Field field = extractField(currentSession);
+
+ if (checkWin(req, resp, currentSession, field)) {
+ return;
+ }
+
+ int index = getSelectedIndex(req);
+ Sign currentSign = field.getField().get(index);
+ if (Sign.EMPTY != currentSign) {
+ req.getRequestDispatcher(PATH_INDEX)
+ .forward(req, resp);
+ return;
+ }
+ field.getField().put(index, Sign.CROSS);
+ if (checkWin(req, resp, currentSession, field)) {
+ return;
+ }
+
+ int emptyFieldIndex = field.getEmptyFieldIndex();
+ if (emptyFieldIndex >= 0) {
+ field.getField().put(emptyFieldIndex, Sign.NOUGHT);
+ if (checkWin(req, resp, currentSession, field)) {
+ return;
+ }
+ } else {
+ currentSession.setAttribute("draw", true);
+ List data = field.getFieldData();
+ currentSession.setAttribute("data", data);
+ req.getRequestDispatcher(PATH_INDEX)
+ .forward(req, resp);
+ return;
+ }
+
+ List data = field.getFieldData();
+ currentSession.setAttribute("data", data);
+ req.getRequestDispatcher(PATH_INDEX)
+ .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;
+ }
+
+ private boolean checkWin(HttpServletRequest req,
+ HttpServletResponse resp,
+ HttpSession currentSession,
+ Field field) throws IOException, ServletException {
+ Sign winner = field.checkWin();
+ if (winner != Sign.EMPTY) {
+ currentSession.setAttribute("winner", winner);
+ List data = field.getFieldData();
+ currentSession.setAttribute("data", data);
+ req.getRequestDispatcher(PATH_INDEX)
+ .forward(req, resp);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/tictactoe/RestartServlet.java b/src/main/java/com/tictactoe/RestartServlet.java
new file mode 100644
index 00000000..6607c339
--- /dev/null
+++ b/src/main/java/com/tictactoe/RestartServlet.java
@@ -0,0 +1,20 @@
+package com.tictactoe;
+
+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 java.io.IOException;
+
+@WebServlet(name = "RestartServlet", value = "/restart")
+public class RestartServlet extends HttpServlet {
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ req.getSession().invalidate();
+ req.getRequestDispatcher("/start")
+ .forward(req, resp);
+ }
+}
diff --git a/src/main/java/com/tictactoe/StartServlet.java b/src/main/java/com/tictactoe/StartServlet.java
new file mode 100644
index 00000000..17fffce5
--- /dev/null
+++ b/src/main/java/com/tictactoe/StartServlet.java
@@ -0,0 +1,26 @@
+package com.tictactoe;
+
+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 = "InitServlet", value = "/start")
+public class StartServlet 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);
+ req.getRequestDispatcher("WEB-INF/index.jsp")
+ .forward(req, resp);
+ }
+}
diff --git a/src/main/webapp/WEB-INF/index.jsp b/src/main/webapp/WEB-INF/index.jsp
new file mode 100644
index 00000000..614b1831
--- /dev/null
+++ b/src/main/webapp/WEB-INF/index.jsp
@@ -0,0 +1,80 @@
+<%@ page import="com.tictactoe.Sign" %>
+<%@ page contentType="text/html;charset=UTF-8"%>
+
+
+
+ Tic-Tac-Toe
+
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+ ">
+
+
+
+
Tic-Tac-Toe
+
+
+
+
+ X
+
+
+ O
+
+
+
+
+
+
+
+
+
+
+
+ IT'S A DRAW
+
+
+
+
+
+
+
+ CROSSES WIN!
+
+
+
+
+
+
+
+ NOUGHTS WIN!
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..8c7a0baa 100644
--- a/src/main/webapp/static/main.css
+++ b/src/main/webapp/static/main.css
@@ -0,0 +1,112 @@
+body {
+ margin: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+ font-family: 'Segoe UI', sans-serif;
+ color: white;
+}
+
+.game-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+h1 {
+ font-weight: 300;
+ letter-spacing: 3px;
+ margin-bottom: 20px;
+ text-transform: uppercase;
+ opacity: 0.8;
+}
+
+.board {
+ display: grid;
+ grid-template-columns: repeat(3, 100px);
+ grid-template-rows: repeat(3, 100px);
+ gap: 15px;
+ padding: 20px;
+ background: rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(15px);
+ border-radius: 20px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
+}
+
+.cell {
+ width: 100px;
+ height: 100px;
+ background: rgba(255, 255, 255, 0.03);
+ border-radius: 12px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 3rem;
+ font-weight: bold;
+ cursor: pointer;
+ user-select: none;
+ -webkit-user-select: none;
+ transition: all 0.2s ease;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.cell:hover {
+ background: rgba(255, 255, 255, 0.1);
+ transform: scale(1.02);
+}
+
+.cell.x {
+ color: #ff4d6d;
+ text-shadow: 0 0 10px #ff4d6d;
+}
+
+.cell.o {
+ color: #00d4ff;
+ text-shadow: 0 0 10px #00d4ff;
+}
+
+.controls {
+ height: 120px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-top: 10px;
+ width: 100%;
+ visibility: hidden;
+ opacity: 0;
+ transition: opacity 0.4s ease;
+}
+
+.controls.show {
+ visibility: visible;
+ opacity: 1;
+}
+
+#status {
+ font-size: 1.2rem;
+ margin-bottom: 15px;
+ height: 1.5rem;
+}
+
+.restart-btn {
+ padding: 10px 25px;
+ font-size: 1rem;
+ border: none;
+ border-radius: 50px;
+ background: #4ecca3;
+ color: #1a1a2e;
+ font-weight: bold;
+ cursor: pointer;
+ transition: 0.3s;
+ box-shadow: 0 4px 15px rgba(78, 204, 163, 0.3);
+}
+
+.restart-btn:hover {
+ background: #45b691;
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(78, 204, 163, 0.4);
+}