diff --git a/Anna/Dockerfile b/Anna/Dockerfile new file mode 100644 index 0000000..71827d5 --- /dev/null +++ b/Anna/Dockerfile @@ -0,0 +1,12 @@ +FROM maven:3.8.5-openjdk-17-slim AS build +WORKDIR /app +COPY pom.xml . +COPY src ./src +RUN mvn clean package -DskipTests + + +FROM tomcat:9.0-jdk17 + +COPY --from=build /app/target/Anna.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] \ No newline at end of file diff --git a/Anna/README.md b/Anna/README.md new file mode 100644 index 0000000..f2460f4 --- /dev/null +++ b/Anna/README.md @@ -0,0 +1,44 @@ +# Gods' Trials - Text-Based Adventure Game + +This is a text-based adventure game where the player must pass the trials of the gods to obtain their power and prevent +a catastrophe. The game is built using Java (Servlets, JSP, JSTL, Maven, JUnit) and runs on Tomcat 9. It uses Bootstrap +5 for styling. + +This project is deployed on **Render** https://m3-quest-new-2ziq.onrender.com + +--- + +## 📌 Features + +- Interactive Story: Answer questions from the gods and make the right choices. +- Session-Based Progress: The game tracks your progress using session attributes. +- Dynamic UI: Each question displays a corresponding god’s image. +- Game Over & Restart: If you fail a challenge, you can restart the game. + +--- + +## 🚀 Getting Started + +### Prerequisites + +- Java 11+ (Recommended: OpenJDK 17) +- Maven (For dependency management) +- Tomcat 9 (For running the application) + +### Installation + +Clone the repository: + +git clone https://github.com/Anna-Yavorska/M3-Quest-NEW + +## 🛠️ Technologies Used + +**Backend**: Java, Servlets, JSP, JSTL + +**Frontend**: Bootstrap 5, CSS + +**Build Tool**: Maven + +**Testing**: JUnit 5 + +**Server**: Tomcat 9 \ No newline at end of file diff --git a/Anna/docker-compose.yml b/Anna/docker-compose.yml new file mode 100644 index 0000000..26f6aad --- /dev/null +++ b/Anna/docker-compose.yml @@ -0,0 +1,5 @@ +services: + web: + build: . + ports: + - "8080:8080" \ No newline at end of file diff --git a/Anna/pom.xml b/Anna/pom.xml new file mode 100644 index 0000000..260dd35 --- /dev/null +++ b/Anna/pom.xml @@ -0,0 +1,105 @@ + + 4.0.0 + org.example + Anna + war + 1.0-SNAPSHOT + Anna Maven Webapp + http://maven.apache.org + + + central + https://repo.maven.apache.org/maven2 + + + + + UTF-8 + + 17 + + 1.18.36 + + 5.12.0 + + 4.0.1 + 3.0.1 + 1.2 + + ${java.version} + ${java.version} + 3.3.2 + + + + + + org.junit.jupiter + junit-jupiter-engine + 5.12.0 + test + + + + javax.servlet + javax.servlet-api + ${javax.servlet-api.version} + provided + + + + javax.servlet + jstl + ${jstl.version} + + + + org.projectlombok + lombok + ${lombok.version} + + + + org.glassfish.web + jakarta.servlet.jsp.jstl + ${jakarta.servlet.jsp.jstl.version} + + + + jakarta.servlet.jsp.jstl + jakarta.servlet.jsp.jstl-api + ${jakarta.servlet.jsp.jstl.version} + + + + + Anna + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + + + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + + + diff --git a/Anna/src/main/java/controller/QuizServlet.java b/Anna/src/main/java/controller/QuizServlet.java new file mode 100644 index 0000000..91fd093 --- /dev/null +++ b/Anna/src/main/java/controller/QuizServlet.java @@ -0,0 +1,75 @@ +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; +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 { + HttpSession session = req.getSession(); + + if (session.getAttribute("gameOver") != null && (boolean) session.getAttribute("gameOver")) { + session.invalidate(); + session = req.getSession(true); + } + + ArrayList questions = questionRepository.getQuestions(); + session.setAttribute("questions", questions); + + Integer currentIndex = (Integer) session.getAttribute("currentIndex"); + if (currentIndex == null) { + currentIndex = 0; + session.setAttribute("currentIndex", currentIndex); + } + + Question currentQuestion = questions.get(currentIndex); + session.setAttribute("currentQuestionText", currentQuestion.getText()); + session.setAttribute("currentQuestionAnswers", currentQuestion.getAnswers()); + + getServletContext().getRequestDispatcher("/quiz.jsp").forward(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + HttpSession session = req.getSession(); + ArrayList questions = (ArrayList) session.getAttribute("questions"); + Integer currentIndex = (Integer) session.getAttribute("currentIndex"); + + if (questions == null || currentIndex == null) { + resp.sendRedirect("index.jsp"); + return; + } + + Question currentQuestion = questions.get(currentIndex); + int correctAnswer = currentQuestion.getCorrectAnswer(); + int userAnswer = Integer.parseInt(req.getParameter("answer")); + + if (userAnswer == correctAnswer) { + currentIndex++; + if (currentIndex < questions.size()) { + session.setAttribute("currentIndex", currentIndex); + resp.sendRedirect("quiz"); + } else { + session.setAttribute("gameOver", true); + session.setAttribute("win", true); + resp.sendRedirect("result.jsp"); + } + } else { + session.setAttribute("gameOver", true); + session.setAttribute("win", false); + resp.sendRedirect("result.jsp"); + } + } +} \ No newline at end of file diff --git a/Anna/src/main/java/model/Question.java b/Anna/src/main/java/model/Question.java new file mode 100644 index 0000000..076e10f --- /dev/null +++ b/Anna/src/main/java/model/Question.java @@ -0,0 +1,15 @@ +package model; + +import lombok.Builder; +import lombok.Data; + +import java.util.ArrayList; + +@Data +@Builder +public class Question { + private String text; + private ArrayList answers; + private int correctAnswer; + +} diff --git a/Anna/src/main/java/repository/QuestionRepository.java b/Anna/src/main/java/repository/QuestionRepository.java new file mode 100644 index 0000000..c4777d8 --- /dev/null +++ b/Anna/src/main/java/repository/QuestionRepository.java @@ -0,0 +1,56 @@ +package repository; + +import model.Question; + +import java.util.ArrayList; +import java.util.List; + +public class QuestionRepository { + private static QuestionRepository INSTANCE; + private ArrayList questions; + + private QuestionRepository() { + questions = new ArrayList<>(); + + questions.add(Question.builder() + .text("Грім роздирає небо, і ти стоїш перед Зевсом. Він каже: “Смертний, щоб отримати силу блискавки, ти маєш знати, що найважливіше для правителя. Що ти обереш?”") + .answers(new ArrayList<>(List.of("Справедливість", "Сила", "Хитрість", "Помста"))) + .correctAnswer(0) + .build()); + + questions.add(Question.builder() + .text("Хвилі розступаються, і перед тобою постає Посейдон. “Лише той, хто розуміє природу океану, може керувати його силою. Що є головною сутністю моря?”") + .answers(new ArrayList<>(List.of("Гнів", "Спокій", "Непередбачуваність", "Глибина"))) + .correctAnswer(2) + .build()); + + questions.add(Question.builder() + .text("Афіна спостерігає за тобою пильним поглядом. “Перемогу здобувають не лише мечем, а й розумом. Що важливіше у битві?”") + .answers(new ArrayList<>(List.of("Сміливість", "Стратегія", "Швидкість", "Честь"))) + .correctAnswer(1) + .build()); + + questions.add(Question.builder() + .text("Ти спускаєшся в Підземний світ, і перед тобою Аїд. “Смертний, чи ти розумієш істину життя? Що найбільше лякає людей?”") + .answers(new ArrayList<>(List.of("Біль", "Забуття", "Темрява", "Самотність"))) + .correctAnswer(1) + .build()); + + questions.add(Question.builder() + .text("Арес грізно дивиться на тебе. “Лише найсильніший воїн заслуговує силу богів! Що є справжньою сутністю війни?”") + .answers(new ArrayList<>(List.of("Руйнування", "Слава", "Виживання", "Влада"))) + .correctAnswer(2) + .build()); + } + + public static QuestionRepository getInstance() { + if (INSTANCE == null) { + INSTANCE = new QuestionRepository(); + } + return INSTANCE; + } + + public ArrayList getQuestions() { + return questions; + } +} diff --git a/Anna/src/main/webapp/WEB-INF/web.xml b/Anna/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..f70f3c6 --- /dev/null +++ b/Anna/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,7 @@ + + + + Archetype Created Web Application + diff --git a/Anna/src/main/webapp/css/styles.css b/Anna/src/main/webapp/css/styles.css new file mode 100644 index 0000000..02b5260 --- /dev/null +++ b/Anna/src/main/webapp/css/styles.css @@ -0,0 +1,48 @@ +body { + background-color: #3e2723; + color: #ffcc80; + text-align: center; + padding: 20px; + font-family: 'Comic Sans MS', sans-serif; +} + +.container { + max-width: 800px; +} + +.text-shadow { + text-shadow: 3px 3px 5px rgba(0, 0, 0, 0.7); +} + +.btn-custom { + background-color: gold; + color: black; + font-size: 1.2em; + padding: 10px 20px; + border-radius: 10px; + border: none; +} + +.btn-custom:hover { + background-color: darkgoldenrod; +} + +.game-image { + border-radius: 10px; + margin-bottom: 20px; +} + +.card-title { + color: #000000; +} + +.card { + background-color: #baac8d; + color: #5E4B3C; +} + +.btn-primary { + color: #000000; + background-color: rgb(237, 202, 15); + border-color: black; +} \ No newline at end of file diff --git a/Anna/src/main/webapp/index.jsp b/Anna/src/main/webapp/index.jsp new file mode 100644 index 0000000..c2e4a5d --- /dev/null +++ b/Anna/src/main/webapp/index.jsp @@ -0,0 +1,28 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Випробування богів + + + + + +
+ Боги Олімпу +

Ласкаво просимо, смертний!

+

Світ на межі загибелі. Оракули передбачили катастрофу – гігантський вогняний астероїд мчить до + Землі. + Жодна смертна істота не здатна його зупинити. Але у тебе є шанс…

+

Боги Олімпу готові дарувати тобі силу, що зможе відвернути катастрофу. Проте їхні дари треба заслужити – + на тебе чекають випробування. Кожен з богів перевірить твою мудрість, мужність і вірність. + Помилка означає загибель.

+

Чи готовий ти прийняти виклик?

+
+ +
+
+ + \ No newline at end of file diff --git a/Anna/src/main/webapp/quiz.jsp b/Anna/src/main/webapp/quiz.jsp new file mode 100644 index 0000000..ee3398d --- /dev/null +++ b/Anna/src/main/webapp/quiz.jsp @@ -0,0 +1,48 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="java.util.ArrayList" %> +<% + String questionText = (String) session.getAttribute("currentQuestionText"); + ArrayList answers = (ArrayList) session.getAttribute("currentQuestionAnswers"); + + String godImage = "resources/images/gods.jpg"; + + if (questionText.contains("Зевс")) { + godImage = "resources/images/zeus.png"; + } else if (questionText.contains("Посейдон")) { + godImage = "resources/images/poseidon.webp"; + } else if (questionText.contains("Афіна")) { + godImage = "resources/images/athena.webp"; + } else if (questionText.contains("Аїд")) { + godImage = "resources/images/hades.webp"; + } else if (questionText.contains("Арес")) { + godImage = "resources/images/ares.jpg"; + } +%> + + + + Випробування богів + + + + +
+
+

Випробування богів

+ Бог Олімпу +
+

<%= questionText %> +

+
+ <% for (int i = 0; i < answers.size(); i++) { %> + + <% } %> +
+
+
+
+ + \ No newline at end of file diff --git a/Anna/src/main/webapp/resources/images/ares.jpg b/Anna/src/main/webapp/resources/images/ares.jpg new file mode 100644 index 0000000..83a7d49 Binary files /dev/null and b/Anna/src/main/webapp/resources/images/ares.jpg differ diff --git a/Anna/src/main/webapp/resources/images/athena.webp b/Anna/src/main/webapp/resources/images/athena.webp new file mode 100644 index 0000000..3cfb4f5 Binary files /dev/null and b/Anna/src/main/webapp/resources/images/athena.webp differ diff --git a/Anna/src/main/webapp/resources/images/gods.jpg b/Anna/src/main/webapp/resources/images/gods.jpg new file mode 100644 index 0000000..13455d7 Binary files /dev/null and b/Anna/src/main/webapp/resources/images/gods.jpg differ diff --git a/Anna/src/main/webapp/resources/images/hades.webp b/Anna/src/main/webapp/resources/images/hades.webp new file mode 100644 index 0000000..f0c3080 Binary files /dev/null and b/Anna/src/main/webapp/resources/images/hades.webp differ diff --git a/Anna/src/main/webapp/resources/images/poseidon.webp b/Anna/src/main/webapp/resources/images/poseidon.webp new file mode 100644 index 0000000..272304d Binary files /dev/null and b/Anna/src/main/webapp/resources/images/poseidon.webp differ diff --git a/Anna/src/main/webapp/resources/images/zeus.png b/Anna/src/main/webapp/resources/images/zeus.png new file mode 100644 index 0000000..ddbadb7 Binary files /dev/null and b/Anna/src/main/webapp/resources/images/zeus.png differ diff --git a/Anna/src/main/webapp/result.jsp b/Anna/src/main/webapp/result.jsp new file mode 100644 index 0000000..dadf09b --- /dev/null +++ b/Anna/src/main/webapp/result.jsp @@ -0,0 +1,29 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Результат + + + + + +
+

+ <% Boolean win = (Boolean) session.getAttribute("win"); %> + <% if (win != null && win) { %> + Боги Олімпу +

Вітаємо! Ти пройшов усі випробування! Ти гідний отримати дари від Нас, та зможеш + відвернути катастрофу!

+ <% } else { %> +

На жаль, ти програв. Боги не дали тобі сили.

+ <% } %> +

+
+ +
+
+ + \ No newline at end of file diff --git a/Anna/src/test/java/model/QuestionTest.java b/Anna/src/test/java/model/QuestionTest.java new file mode 100644 index 0000000..3694d47 --- /dev/null +++ b/Anna/src/test/java/model/QuestionTest.java @@ -0,0 +1,49 @@ +package model; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class QuestionTest { + @Test + void givenValidData_whenCreateQuestion_thenShouldHaveCorrectProperties() { + // given + ArrayList answers = new ArrayList<>(); + answers.add("Option 1"); + answers.add("Option 2"); + answers.add("Option 3"); + + // when + Question question = Question.builder() + .text("Test question?") + .answers(answers) + .correctAnswer(1) + .build(); + + // then + assertEquals("Test question?", question.getText(), "The question text should match"); + assertEquals(3, question.getAnswers().size(), "The answer list should contain 3 options"); + assertEquals(1, question.getCorrectAnswer(), "The correct answer index should be 1"); + } + + @Test + void givenQuestionWithThreeAnswers_whenGetCorrectAnswer_thenIndexShouldBeWithinBounds() { + // given + ArrayList answers = new ArrayList<>(List.of("A", "B", "C")); + + // when + Question question = Question.builder() + .text("What is the correct answer?") + .answers(answers) + .correctAnswer(2) + .build(); + + // then + assertTrue(question.getCorrectAnswer() >= 0 && question.getCorrectAnswer() < question.getAnswers().size(), + "The correct answer index should be within the bounds of the answer list"); + } +} \ No newline at end of file diff --git a/Anna/src/test/java/repository/QuestionRepositoryTest.java b/Anna/src/test/java/repository/QuestionRepositoryTest.java new file mode 100644 index 0000000..cae5a99 --- /dev/null +++ b/Anna/src/test/java/repository/QuestionRepositoryTest.java @@ -0,0 +1,55 @@ +package repository; + +import model.Question; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +class QuestionRepositoryTest { + private QuestionRepository questionRepository; + + @BeforeEach + void setUp() { + questionRepository = QuestionRepository.getInstance(); + } + + @Test + void givenQuestionRepository_whenGetQuestions_thenShouldReturnNonEmptyList() { + // given (QuestionRepository is initialized) + + // when + ArrayList questions = questionRepository.getQuestions(); + + // then + assertNotNull(questions, "The question list should not be null"); + assertFalse(questions.isEmpty(), "The question list should not be empty"); + } + + @Test + void givenQuestionRepository_whenGetQuestions_thenShouldReturnListOfFiveQuestions() { + // given (QuestionRepository is initialized) + + // when + ArrayList questions = questionRepository.getQuestions(); + + // then + assertEquals(5, questions.size(), "Expected 5 questions in the list"); + } + + @Test + void givenQuestionRepository_whenGetFirstQuestion_thenShouldHaveValidTextAndAnswers() { + // given (QuestionRepository is initialized) + + // when + Question question = questionRepository.getQuestions().get(0); + + // then + assertNotNull(question.getText(), "The question text should not be null"); + assertFalse(question.getAnswers().isEmpty(), "The answer list should not be empty"); + assertTrue(question.getCorrectAnswer() >= 0 && question.getCorrectAnswer() < question.getAnswers().size(), + "The correct answer index should be within the answer list range"); + } +} \ No newline at end of file