diff --git a/Oleksandr-JR-Example/Dockerfile b/Denys/Dockerfile similarity index 54% rename from Oleksandr-JR-Example/Dockerfile rename to Denys/Dockerfile index 27415c9..7990aa4 100644 --- a/Oleksandr-JR-Example/Dockerfile +++ b/Denys/Dockerfile @@ -7,7 +7,7 @@ RUN mvn clean package -DskipTests FROM tomcat:9.0-jdk17 -# @TODO Replace Oleksandr-JR-Example.war with your app name -COPY --from=build /app/target/Oleksandr-JR-Example.war /usr/local/tomcat/webapps/ROOT.war + +COPY --from=build /app/target/Denys.war /usr/local/tomcat/webapps/ROOT.war EXPOSE 8080 CMD ["catalina.sh", "run"] diff --git a/Oleksandr-JR-Example/docker-compose.yml b/Denys/docker-compose.yml similarity index 100% rename from Oleksandr-JR-Example/docker-compose.yml rename to Denys/docker-compose.yml diff --git a/Oleksandr-JR-Example/pom.xml b/Denys/pom.xml similarity index 85% rename from Oleksandr-JR-Example/pom.xml rename to Denys/pom.xml index e89780d..2113d10 100644 --- a/Oleksandr-JR-Example/pom.xml +++ b/Denys/pom.xml @@ -2,10 +2,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 ua.com.javarush.gnew - Oleksandr-JR-Example + Denys war 1.0-SNAPSHOT - Oleksandr-JR-Example Maven Webapp + Denys Maven Webapp http://maven.apache.org @@ -50,6 +50,13 @@ test + + org.mockito + mockito-inline + 5.2.0 + test + + javax.servlet javax.servlet-api @@ -87,9 +94,20 @@ ${commons-lang3.version} + + jakarta.servlet.jsp.jstl + jakarta.servlet.jsp.jstl-api + 2.0.0 + + + org.glassfish.web + jakarta.servlet.jsp.jstl + 2.0.0 + + - Oleksandr-JR-Example + Denys diff --git a/Denys/src/main/java/controller/QuizServlet.java b/Denys/src/main/java/controller/QuizServlet.java new file mode 100644 index 0000000..766e792 --- /dev/null +++ b/Denys/src/main/java/controller/QuizServlet.java @@ -0,0 +1,64 @@ +package controller; + +import model.Question; +import repository.QuestionRepository; + +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; + +@WebServlet(name = "QuizServlet", value = "/quiz") +public class QuizServlet extends HttpServlet { + private static final QuestionRepository questionRepository = QuestionRepository.getInstance(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + HttpSession session = req.getSession(); + + if ("true".equals(req.getParameter("restart"))) { + session.removeAttribute("currentIndex"); + + Integer games = (Integer) session.getAttribute("gamesPlayed"); + if (games == null) games = 0; + session.setAttribute("gamesPlayed", games + 1); + } + + Integer currentIndex = (Integer) session.getAttribute("currentIndex"); + if (currentIndex == null) { + currentIndex = 0; + session.setAttribute("currentIndex", currentIndex); + } + + Question question = questionRepository.getAll().get(currentIndex); + req.setAttribute("question", question); + req.setAttribute("qIndex", currentIndex); + + getServletContext().getRequestDispatcher("/quiz.jsp").forward(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + HttpSession session = req.getSession(); + + int qIndex = Integer.parseInt(req.getParameter("qIndex")); + int answerIndex = Integer.parseInt(req.getParameter("answerIndex")); + + Question question = questionRepository.getAll().get(qIndex); + + if (question.getNextQuestions() == null || question.getNextQuestions().isEmpty()) { + getServletContext().getRequestDispatcher("/result.jsp").forward(req, resp); + return; + } + + int nextIndex = question.getNextQuestions().get(answerIndex); + session.setAttribute("currentIndex", nextIndex); + + resp.sendRedirect("quiz"); + } +} + + diff --git a/Oleksandr-JR-Example/src/main/java/model/Question.java b/Denys/src/main/java/model/Question.java similarity index 70% rename from Oleksandr-JR-Example/src/main/java/model/Question.java rename to Denys/src/main/java/model/Question.java index 17b3279..1ec2848 100644 --- a/Oleksandr-JR-Example/src/main/java/model/Question.java +++ b/Denys/src/main/java/model/Question.java @@ -9,8 +9,7 @@ @Builder public class Question { private String text; - private ArrayList answers; - - private int correctAnswer; -} + private ArrayList nextQuestions; + private Integer correctAnswer; +} \ No newline at end of file diff --git a/Denys/src/main/java/repository/QuestionRepository.java b/Denys/src/main/java/repository/QuestionRepository.java new file mode 100644 index 0000000..f6a64b6 --- /dev/null +++ b/Denys/src/main/java/repository/QuestionRepository.java @@ -0,0 +1,86 @@ +package repository; + +import model.Question; + +import java.util.ArrayList; +import java.util.List; + +public class QuestionRepository { + private static QuestionRepository INSTANCE; + + private final ArrayList questions = new ArrayList<>(); + + private QuestionRepository() { + questions.add(Question.builder() + .text("Вам пропонують почати дослідження всесвіту. Прийняти пропозицію?") + .answers(new ArrayList<>(List.of("Прийняти", "Відхилити"))) + .nextQuestions(new ArrayList<>(List.of(1, 2))) + .build()); + + questions.add(Question.builder() + .text("Штовхнути темну матерію?") + .answers(new ArrayList<>(List.of("Так", "Ні"))) + .nextQuestions(new ArrayList<>(List.of(3, 5))) + .build()); + + questions.add(Question.builder() + .text("У результаті вашого відказу, дослідження було перенесене на великий термін. Поразка") + .answers(new ArrayList<>()) + .nextQuestions(new ArrayList<>()) + .build()); + + questions.add(Question.builder() + .text("Випарувалася невелика чорна діра, та залишився новий елемент під назвою — частка Бога! Що робимо далі?") + .answers(new ArrayList<>(List.of("Збільшити навантаження!", "Дослідити частку"))) + .nextQuestions(new ArrayList<>(List.of(4, 9))) + .build()); + + questions.add(Question.builder() + .text("Чорна діра вбила все живе! Поразка.") + .answers(new ArrayList<>()) + .nextQuestions(new ArrayList<>()) + .build()); + + questions.add(Question.builder() + .text("Що будете робити?") + .answers(new ArrayList<>(List.of("Розвинути проект світлої матерії", "До дому."))) + .nextQuestions(new ArrayList<>(List.of(6, 2))) + .build()); + + questions.add(Question.builder() + .text("Скільки матерії додати до коллайдеру?") + .answers(new ArrayList<>(List.of("6 ммоль", "100 ммоль"))) + .nextQuestions(new ArrayList<>(List.of(7, 8))) + .build()); + + questions.add(Question.builder() + .text("Людство позбулося енергетичної кризи. Перемога!") + .answers(new ArrayList<>()) + .nextQuestions(new ArrayList<>()) + .build()); + + questions.add(Question.builder() + .text("Нейтронна бомба вбила все живе! Поразка!") + .answers(new ArrayList<>()) + .nextQuestions(new ArrayList<>()) + .build()); + + questions.add(Question.builder() + .text("Людство позбулося енергетичної кризи. Перемога!") + .answers(new ArrayList<>()) + .nextQuestions(new ArrayList<>()) + .build()); + } + + public static QuestionRepository getInstance() { + if (INSTANCE == null) { + INSTANCE = new QuestionRepository(); + } + + return INSTANCE; + } + + public ArrayList getAll() { + return questions; + } +} diff --git a/Oleksandr-JR-Example/src/main/webapp/WEB-INF/web.xml b/Denys/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from Oleksandr-JR-Example/src/main/webapp/WEB-INF/web.xml rename to Denys/src/main/webapp/WEB-INF/web.xml diff --git a/Denys/src/main/webapp/css/style.css b/Denys/src/main/webapp/css/style.css new file mode 100644 index 0000000..606735f --- /dev/null +++ b/Denys/src/main/webapp/css/style.css @@ -0,0 +1,49 @@ +body { + margin: 0; + font-family: 'Segoe UI', sans-serif; + background: radial-gradient(circle, #0f2027, #203a43, #2c5364); + color: #fff; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; +} + +h1, h2 { + font-size: 3em; + margin-bottom: 10px; + text-align: center; + text-shadow: 0 0 10px #00ffff; +} + +p { + font-size: 1.5em; + text-align: center; + margin-bottom: 30px; +} + +a.button, button { + background: #00ffff; + color: #000; + padding: 12px 25px; + border-radius: 10px; + font-weight: bold; + font-size: 1.2em; + border: none; + cursor: pointer; + transition: 0.3s; +} + +a.button:hover, button:hover { + background: #00cccc; + box-shadow: 0 0 10px #00ffff; +} + +form { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + margin-top: 30px; +} diff --git a/Denys/src/main/webapp/index.jsp b/Denys/src/main/webapp/index.jsp new file mode 100644 index 0000000..bc7fe2e --- /dev/null +++ b/Denys/src/main/webapp/index.jsp @@ -0,0 +1,14 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + Коллайдер + + + +

Коллайдер

+

Вітаємо в інтерактивному квесті!
Почни своє проходження прямо зараз!

+Почати гру + + diff --git a/Denys/src/main/webapp/quiz.jsp b/Denys/src/main/webapp/quiz.jsp new file mode 100644 index 0000000..e8c28c2 --- /dev/null +++ b/Denys/src/main/webapp/quiz.jsp @@ -0,0 +1,47 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="model.Question" %> +<%@ page import="java.util.*" %> +<% + Question question = (Question) request.getAttribute("question"); + int qIndex = (Integer) request.getAttribute("qIndex"); + Integer gamesPlayed = (Integer) session.getAttribute("gamesPlayed"); + if (gamesPlayed == null) gamesPlayed = 0; + + String text = question.getText().toLowerCase(); + boolean isEnding = text.contains("поразка") || text.contains("перемога"); +%> + + + + + Квест + + + + +

<%= question.getText() %>

+ +

Кількість пройдених ігор: <%= gamesPlayed %>

+ +<% if (isEnding) { %> +Почати заново +<% } else { %> +
+ + <% + List answers = question.getAnswers(); + for (int i = 0; i < answers.size(); i++) { + %> + + <% + } + %> + +<% } %> + + + + + diff --git a/Denys/src/main/webapp/result.jsp b/Denys/src/main/webapp/result.jsp new file mode 100644 index 0000000..2f6d7d0 --- /dev/null +++ b/Denys/src/main/webapp/result.jsp @@ -0,0 +1,19 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + Результат + + + + +

Гра завершена!

+ +

Дякуємо за гру!

+

Ви вже зіграли <%= session.getAttribute("gamesPlayed") != null ? session.getAttribute("gamesPlayed") : 1 %> раз(ів).

+ +Почати заново + + + diff --git a/Denys/src/test/java/controller/QuizServletTest.java b/Denys/src/test/java/controller/QuizServletTest.java new file mode 100644 index 0000000..a14f766 --- /dev/null +++ b/Denys/src/test/java/controller/QuizServletTest.java @@ -0,0 +1,88 @@ +package controller; + +import model.Question; +import repository.QuestionRepository; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.*; + +public class QuizServletTest { + + @InjectMocks + private QuizServlet realServlet; + + private QuizServlet servlet; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Mock + private HttpSession session; + + @Mock + private ServletContext servletContext; + + @Mock + private RequestDispatcher dispatcher; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + + servlet = spy(realServlet); + when(request.getSession()).thenReturn(session); + when(request.getServletContext()).thenReturn(servletContext); + doReturn(servletContext).when(servlet).getServletContext(); + } + + @Test + public void testDoGet_NewGame_ShouldStartAtIndexZero() throws Exception { + when(session.getAttribute("currentIndex")).thenReturn(null); + when(servletContext.getRequestDispatcher("/quiz.jsp")).thenReturn(dispatcher); + + servlet.doGet(request, response); + + verify(session).setAttribute("currentIndex", 0); + verify(dispatcher).forward(request, response); + } + + @Test + public void testDoPost_ValidAnswer_RedirectsToNextQuestion() throws Exception { + QuestionRepository repo = QuestionRepository.getInstance(); + repo.getAll().clear(); + repo.getAll().add( + Question.builder() + .text("Test?") + .answers(new ArrayList<>(List.of("Yes", "No"))) + .nextQuestions(new ArrayList<>(List.of(1, 2))) + .build() + ); + + when(request.getParameter("qIndex")).thenReturn("0"); + when(request.getParameter("answerIndex")).thenReturn("1"); + when(request.getSession()).thenReturn(session); + + servlet.doPost(request, response); + + verify(session).setAttribute("currentIndex", 2); + verify(response).sendRedirect("quiz"); + } +} + diff --git a/Denys/src/test/java/model/QuestionRepositoryTest.java b/Denys/src/test/java/model/QuestionRepositoryTest.java new file mode 100644 index 0000000..2a12719 --- /dev/null +++ b/Denys/src/test/java/model/QuestionRepositoryTest.java @@ -0,0 +1,74 @@ +package model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import repository.QuestionRepository; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class QuestionRepositoryTest { + + private QuestionRepository repository; + + @BeforeEach + public void setup() { + repository = QuestionRepository.getInstance(); + repository.getAll().clear(); + + repository.getAll().add( + Question.builder() + .text("Question 0?") + .answers(new ArrayList<>(List.of("Answer 1", "Answer 2"))) + .nextQuestions(new ArrayList<>(List.of(1, 2))) + .build() + ); + + repository.getAll().add( + Question.builder() + .text("Question 1?") + .answers(new ArrayList<>()) + .nextQuestions(new ArrayList<>()) + .build() + ); + + repository.getAll().add( + Question.builder() + .text("Question 2?") + .answers(new ArrayList<>()) + .nextQuestions(new ArrayList<>()) + .build() + ); + } + + @Test + public void testQuestionsAreLoaded() { + ArrayList questions = repository.getAll(); + assertFalse(questions.isEmpty(), "Questions should be loaded"); + } + + @Test + public void testTwoAnswers() { + Question q0 = repository.getAll().get(0); + assertEquals(2, q0.getAnswers().size(), "First question should have two answers"); + } + + @Test + public void testNavigation() { + Question q0 = repository.getAll().get(0); + int next = q0.getNextQuestions().get(0); + Question q1 = repository.getAll().get(next); + assertNotNull(q1, "Should navigate to next question"); + } + + @Test + public void testHaveNoNext() { + for (Question q : repository.getAll()) { + if (q.getAnswers().isEmpty()) { + assertTrue(q.getNextQuestions().isEmpty(), "Terminal questions should not have next steps"); + } + } + } +} diff --git a/Oleksandr-JR-Example/src/main/java/controller/QuizServlet.java b/Oleksandr-JR-Example/src/main/java/controller/QuizServlet.java deleted file mode 100644 index ae63a3d..0000000 --- a/Oleksandr-JR-Example/src/main/java/controller/QuizServlet.java +++ /dev/null @@ -1,35 +0,0 @@ -package controller; - -import model.Question; -import repository.QuestionRepository; - -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; -import java.util.ArrayList; - -@WebServlet(name = "QuizServlet", value = "/quiz") -public class QuizServlet extends HttpServlet { - private static final QuestionRepository questionRepository = QuestionRepository.getInstance(); - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - - ArrayList questions = questionRepository.getAll(); - - - req.getSession().setAttribute("questions", questions); - - // Forwarding to the JSP page - getServletContext().getRequestDispatcher("/quiz.jsp").forward(req, resp); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - // Placeholder for POST handling logic - resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "POST method is not supported."); - } -} diff --git a/Oleksandr-JR-Example/src/main/java/repository/QuestionRepository.java b/Oleksandr-JR-Example/src/main/java/repository/QuestionRepository.java deleted file mode 100644 index c64ac92..0000000 --- a/Oleksandr-JR-Example/src/main/java/repository/QuestionRepository.java +++ /dev/null @@ -1,42 +0,0 @@ -package repository; - -import model.Question; - -import java.util.ArrayList; - -public class QuestionRepository { - private static QuestionRepository INSTANCE; - - private final ArrayList questions = new ArrayList<>(); - - - private QuestionRepository() { - ArrayList answers = new ArrayList<>(); - answers.add("A"); - answers.add("B"); - answers.add("C"); - answers.add("Programming language"); - - questions.add(Question.builder() - .text("What is java") - .answers(answers) - .correctAnswer(3) - .build()); - } - - public static QuestionRepository getInstance() { - if (INSTANCE == null) { - INSTANCE = new QuestionRepository(); - } - - return INSTANCE; - - } - - - public ArrayList getAll() { - return questions; - } - - -} diff --git a/Oleksandr-JR-Example/src/main/webapp/index.jsp b/Oleksandr-JR-Example/src/main/webapp/index.jsp deleted file mode 100644 index c38169b..0000000 --- a/Oleksandr-JR-Example/src/main/webapp/index.jsp +++ /dev/null @@ -1,5 +0,0 @@ - - -

Hello World!

- - diff --git a/Oleksandr-JR-Example/src/main/webapp/quiz.jsp b/Oleksandr-JR-Example/src/main/webapp/quiz.jsp deleted file mode 100644 index 95a7681..0000000 --- a/Oleksandr-JR-Example/src/main/webapp/quiz.jsp +++ /dev/null @@ -1,31 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ page contentType="text/html;charset=UTF-8" %> - - - - - - - - FQ HTML - - - - - - - - - -<%= request.getSession().getAttribute("questions") %> - - - - - diff --git a/README.md b/README.md index d8f5056..dd0326b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,37 @@ -# M3-Quest-NEW +# QuestGame -Do not edit this file, please create a readme file in your module \ No newline at end of file +## Overview + +QuestGame is an interactive web-based quiz game implemented as a Java web application. +Players answer a series of questions, branching the story depending on their choices. + +--- + +## Technologies Used + +- **Java 17** — main programming language +- **Servlet API 4.0** — to handle HTTP requests and sessions +- **JSP** — for server-side page rendering +- **JUnit 5** — unit testing framework +- **Mockito** — for mocking in tests +- **Maven** — build and dependency management +- **Apache Tomcat 9** — servlet container to deploy the web application + +--- + +## Project Structure + +- `src/main/java` — Java servlet controllers and business logic +- `src/main/webapp` — JSP pages, static resources (CSS) +- `src/test/java` — unit tests +- `pom.xml` — Maven configuration + +--- + +## How to Build and Run + +### Prerequisites + +- Java 17 installed +- Maven installed +- Docker installed (optional, for containerized run) \ No newline at end of file