diff --git a/pom.xml b/pom.xml index 78ee59d..0065e7e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,92 +3,95 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - com.javarush.khmelov - project-ledzeppelin + com.quest + BlackOrchidQuest 1.0-SNAPSHOT - ProjectLedzeppelin war + BlackOrchidQuest + Текстовый квест на сервлетах и JSP для Tomcat 10 + + 11 + 11 UTF-8 - 21 - 21 - 5.10.2 + false - - org.springframework.boot - spring-boot-starter-parent - 3.3.5 - - - - - - org.springframework.boot - spring-boot-dependencies - pom - import - 3.3.5 - - - - + jakarta.servlet jakarta.servlet-api + 6.0.0 provided + + + + jakarta.servlet.jsp + jakarta.servlet.jsp-api + 3.1.0 + provided + + + jakarta.servlet.jsp.jstl jakarta.servlet.jsp.jstl-api + 3.0.0 org.glassfish.web jakarta.servlet.jsp.jstl + 3.0.1 - - org.projectlombok - lombok - provided - - - org.junit.jupiter - junit-jupiter-api - test - + org.junit.jupiter - junit-jupiter-engine + junit-jupiter + 5.10.0 test - + BlackOrchidQuest + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.apache.maven.plugins maven-war-plugin 3.4.0 + + false + + + org.apache.maven.plugins - maven-compiler-plugin + maven-surefire-plugin + 3.1.2 - - - org.projectlombok - lombok - 1.18.34 - - + + **/*Test.java + - - \ No newline at end of file + + diff --git a/src/main/java/com/javarush/khmelov/cmd/Command.java b/src/main/java/com/javarush/khmelov/cmd/Command.java deleted file mode 100644 index fd4035b..0000000 --- a/src/main/java/com/javarush/khmelov/cmd/Command.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.javarush.khmelov.cmd; - -import jakarta.servlet.http.HttpServletRequest; - -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public interface Command { - - default String doGet(HttpServletRequest request) { - return getView(); - } - - default String doPost(HttpServletRequest request) { - return getView(); - } - - default String getView() { - String simpleName = this.getClass().getSimpleName(); - return convertCamelCaseToKebabStyle(simpleName); - } - - private static String convertCamelCaseToKebabStyle(String string) { - String snakeName = string.chars() - .mapToObj(s -> String.valueOf((char) s)) - .flatMap(s -> s.matches("[A-Z]") - ? Stream.of("-", s) - : Stream.of(s)) - .collect(Collectors.joining()) - .toLowerCase(); - return snakeName.startsWith("-") - ? snakeName.substring(1) - : snakeName; - } - - -} diff --git a/src/main/java/com/javarush/khmelov/cmd/EditUser.java b/src/main/java/com/javarush/khmelov/cmd/EditUser.java deleted file mode 100644 index ae191b4..0000000 --- a/src/main/java/com/javarush/khmelov/cmd/EditUser.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.javarush.khmelov.cmd; - -import com.javarush.khmelov.entity.Role; -import com.javarush.khmelov.entity.User; -import com.javarush.khmelov.service.UserService; -import jakarta.servlet.http.HttpServletRequest; - -import java.util.Optional; - - -@SuppressWarnings("unused") -public class EditUser implements Command { - - private final UserService userService; - - public EditUser(UserService userService) { - this.userService = userService; - } - - - @Override - public String doGet(HttpServletRequest req) { - String stringId = req.getParameter("id"); - if (stringId != null) { - long id = Long.parseLong(stringId); - Optional optionalUser = userService.get(id); - if (optionalUser.isPresent()) { - User user = optionalUser.get(); - req.setAttribute("user", user); - } - } - return getView(); - } - - @Override - public String doPost(HttpServletRequest req) { - User user = User.builder() - .login(req.getParameter("login")) - .password(req.getParameter("password")) - .role(Role.valueOf(req.getParameter("role"))) - .build(); - if (req.getParameter("create") != null) { - userService.create(user); - } else if (req.getParameter("update") != null) { - user.setId(Long.parseLong(req.getParameter("id"))); - userService.update(user); - } - return getView() + "?id=" + user.getId(); - } - - -} \ No newline at end of file diff --git a/src/main/java/com/javarush/khmelov/cmd/ListUser.java b/src/main/java/com/javarush/khmelov/cmd/ListUser.java deleted file mode 100644 index 9257917..0000000 --- a/src/main/java/com/javarush/khmelov/cmd/ListUser.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.javarush.khmelov.cmd; - -import com.javarush.khmelov.entity.User; -import com.javarush.khmelov.service.UserService; -import jakarta.servlet.http.HttpServletRequest; - -import java.util.Collection; - -@SuppressWarnings("unused") -public class ListUser implements Command { - - private final UserService userService; - - public ListUser(UserService userService) { - this.userService = userService; - } - - @Override - public String doGet(HttpServletRequest request) { - Collection users = userService.getAll(); - request.setAttribute("users", users); - return getView(); - } - - -} \ No newline at end of file diff --git a/src/main/java/com/javarush/khmelov/cmd/StartPage.java b/src/main/java/com/javarush/khmelov/cmd/StartPage.java deleted file mode 100644 index d268f93..0000000 --- a/src/main/java/com/javarush/khmelov/cmd/StartPage.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.javarush.khmelov.cmd; - -@SuppressWarnings("unused") -public class StartPage implements Command { - -} diff --git a/src/main/java/com/javarush/khmelov/config/Winter.java b/src/main/java/com/javarush/khmelov/config/Winter.java deleted file mode 100644 index 48bd8a7..0000000 --- a/src/main/java/com/javarush/khmelov/config/Winter.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.javarush.khmelov.config; - -import lombok.SneakyThrows; - -import java.lang.reflect.Constructor; -import java.util.concurrent.ConcurrentHashMap; - -public class Winter { - - public static ConcurrentHashMap, Object> components = new ConcurrentHashMap<>(); - - - @SuppressWarnings("unchecked") - @SneakyThrows - public static T find(Class aClass) { - Object component = components.get(aClass); - if (component == null) { - Constructor constructor = aClass.getConstructors()[0]; - Class[] parameterTypes = constructor.getParameterTypes(); - Object[] parameters = new Object[parameterTypes.length]; - for (int i = 0; i < parameters.length; i++) { - parameters[i] = Winter.find(parameterTypes[i]); - } - Object newInstance = constructor.newInstance(parameters); - components.put(aClass, newInstance); - } - return (T) components.get(aClass); - } -} diff --git a/src/main/java/com/javarush/khmelov/controller/FrontController.java b/src/main/java/com/javarush/khmelov/controller/FrontController.java deleted file mode 100644 index 33242b2..0000000 --- a/src/main/java/com/javarush/khmelov/controller/FrontController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.javarush.khmelov.controller; - -import com.javarush.khmelov.cmd.Command; -import com.javarush.khmelov.config.Winter; -import com.javarush.khmelov.entity.Role; -import jakarta.servlet.ServletConfig; -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({"", "/home", "/list-user", "/edit-user"}) -public class FrontController extends HttpServlet { - - private final HttpResolver httpResolver = Winter.find(HttpResolver.class); - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - Command command = httpResolver.resolve(req); - String view = command.doGet(req); - String jsp = getJsp(view); - req.getRequestDispatcher(jsp).forward(req, resp); - } - - @Override - public void init(ServletConfig config) { - config.getServletContext().setAttribute("roles", Role.values()); - } - - private static String getJsp(String view) { - return "/WEB-INF/" + view + ".jsp"; - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - Command command = httpResolver.resolve(req); - String redirect = command.doPost(req); - resp.sendRedirect(redirect); - } -} diff --git a/src/main/java/com/javarush/khmelov/controller/HttpResolver.java b/src/main/java/com/javarush/khmelov/controller/HttpResolver.java deleted file mode 100644 index 18bb761..0000000 --- a/src/main/java/com/javarush/khmelov/controller/HttpResolver.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.javarush.khmelov.controller; - -import com.javarush.khmelov.cmd.Command; -import com.javarush.khmelov.config.Winter; -import jakarta.servlet.http.HttpServletRequest; - -public class HttpResolver { - - public Command resolve(HttpServletRequest request) { - // /cmd-example - try { - String requestURI = request.getRequestURI(); - requestURI = requestURI.equals("/") ? "/start-page" : requestURI; - String kebabName = requestURI.split("[?#/]")[1]; - String simpleName = convertKebabStyleToCamelCase(kebabName); - String fullName = Command.class.getPackageName() + "." + simpleName; - Class aClass = Class.forName(fullName); - return (Command) Winter.find(aClass); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - private String convertKebabStyleToCamelCase(String input) { - StringBuilder result = new StringBuilder(); - boolean capitalizeNext = true; - for (char c : input.toCharArray()) { - if (c == '-') { - capitalizeNext = true; - } else { - if (capitalizeNext) { - result.append(Character.toUpperCase(c)); - capitalizeNext = false; - } else { - result.append(Character.toLowerCase(c)); - } - } - } - return result.toString(); - } -} diff --git a/src/main/java/com/javarush/khmelov/entity/Role.java b/src/main/java/com/javarush/khmelov/entity/Role.java deleted file mode 100644 index 5ae365f..0000000 --- a/src/main/java/com/javarush/khmelov/entity/Role.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.javarush.khmelov.entity; - -public enum Role { - USER, ADMIN, GUEST -} diff --git a/src/main/java/com/javarush/khmelov/entity/User.java b/src/main/java/com/javarush/khmelov/entity/User.java deleted file mode 100644 index f7fa2d6..0000000 --- a/src/main/java/com/javarush/khmelov/entity/User.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.javarush.khmelov.entity; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class User { - - private Long id; - - private String login; - - private String password; - - private Role role; - - public String getImage() { //TODO move to DTO - return "image-" + id; - } - -} diff --git a/src/main/java/com/javarush/khmelov/repository/Repository.java b/src/main/java/com/javarush/khmelov/repository/Repository.java deleted file mode 100644 index f1abdac..0000000 --- a/src/main/java/com/javarush/khmelov/repository/Repository.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.javarush.khmelov.repository; - -import com.javarush.khmelov.entity.User; - -import java.util.Collection; -import java.util.Optional; - -public interface Repository { - - Collection getAll(); - - Optional get(long id); - - void create(T entity); - - void update(T entity); - - void delete(T entity); -} diff --git a/src/main/java/com/javarush/khmelov/repository/UserRepository.java b/src/main/java/com/javarush/khmelov/repository/UserRepository.java deleted file mode 100644 index 58b32ea..0000000 --- a/src/main/java/com/javarush/khmelov/repository/UserRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.javarush.khmelov.repository; - -import com.javarush.khmelov.entity.Role; -import com.javarush.khmelov.entity.User; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicLong; - -public class UserRepository implements Repository { - - private final Map map = new HashMap<>(); - - public static final AtomicLong id = new AtomicLong(System.currentTimeMillis()); - - public UserRepository() { - map.put(1L, new User(1L, "Alisa", "qwerty", Role.USER)); - map.put(2L, new User(2L, "Bob", "", Role.GUEST)); - map.put(3L, new User(3L, "Carl", "admin", Role.ADMIN)); - map.put(4L, new User(4L, "Khmelov", "admin", Role.ADMIN)); - } - - @Override - public Collection getAll() { - return map.values(); - } - - @Override - public Optional get(long id) { - return Optional.ofNullable(map.get(id)); - } - - @Override - public void create(User entity) { - entity.setId(id.incrementAndGet()); - update(entity); - } - - @Override - public void update(User entity) { - map.put(entity.getId(), entity); - } - - @Override - public void delete(User entity) { - map.remove(entity.getId()); - } -} diff --git a/src/main/java/com/javarush/khmelov/service/UserService.java b/src/main/java/com/javarush/khmelov/service/UserService.java deleted file mode 100644 index b17527c..0000000 --- a/src/main/java/com/javarush/khmelov/service/UserService.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.javarush.khmelov.service; - -import com.javarush.khmelov.entity.User; -import com.javarush.khmelov.repository.UserRepository; - -import java.util.Collection; -import java.util.Optional; - -public class UserService { - - private final UserRepository userRepository; - - public UserService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - public void create(User user) { - userRepository.create(user); - } - - public void update(User user) { - userRepository.update(user); - } - - public void delete(User user) { - userRepository.delete(user); - } - - public Collection getAll() { - return userRepository.getAll(); - } - - public Optional get(long id) { - return userRepository.get(id); - } -} diff --git a/src/main/java/com/javarush/toporov/quest/model/Quest.java b/src/main/java/com/javarush/toporov/quest/model/Quest.java new file mode 100644 index 0000000..1425ece --- /dev/null +++ b/src/main/java/com/javarush/toporov/quest/model/Quest.java @@ -0,0 +1,30 @@ +package com.javarush.toporov.quest.model; + +import java.util.HashMap; +import java.util.Map; + +public class Quest { + private String name; + private String prologue; + private Map steps; + + public Quest(String name, String prologue) { + this.name = name; + this.prologue = prologue; + this.steps = new HashMap<>(); + } + + public String getName() { + return name; + } + + public String getPrologue() { return prologue; } + + public void addStep(QuestStep step) { + steps.put(step.getId(), step); + } + + public QuestStep getStep(int id) { + return steps.get(id); + } +} diff --git a/src/main/java/com/javarush/toporov/quest/model/QuestStep.java b/src/main/java/com/javarush/toporov/quest/model/QuestStep.java new file mode 100644 index 0000000..82883dc --- /dev/null +++ b/src/main/java/com/javarush/toporov/quest/model/QuestStep.java @@ -0,0 +1,26 @@ +package com.javarush.toporov.quest.model; + +import java.util.Map; + +public class QuestStep { + private int id; + private String text; + private Map options; + private boolean isEnd; + private boolean isWin; + + public QuestStep(int id, String text, Map options, boolean isEnd, boolean isWin) { + this.id = id; + this.text = text; + this.options = options; + this.isEnd = isEnd; + this.isWin = isWin; + } + + public int getId() { return id; } + public String getText() { return text; } + public Map getOptions() { return options; } + public boolean isEnd() { return isEnd; } + public boolean isWin() { return isWin; } +} + diff --git a/src/main/java/com/javarush/toporov/quest/servlet/AuthServlet.java b/src/main/java/com/javarush/toporov/quest/servlet/AuthServlet.java new file mode 100644 index 0000000..6d91b62 --- /dev/null +++ b/src/main/java/com/javarush/toporov/quest/servlet/AuthServlet.java @@ -0,0 +1,45 @@ +package com.javarush.toporov.quest.servlet; + +import com.javarush.toporov.quest.util.UserRepository; +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("/auth") +public class AuthServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String login = request.getParameter("login"); + String pass = request.getParameter("password"); + String action = request.getParameter("action"); + + if ("register".equals(action)) { + if (UserRepository.register(login, pass)) { + request.getSession().setAttribute("user", login); + response.sendRedirect("index.jsp"); + } else { + sendError(request, response, "Пользователь уже существует!"); + } + } else { + if (UserRepository.check(login, pass)) { + request.getSession().setAttribute("user", login); + // Установим начальное кол-во игр, если новый сеанс + if (request.getSession().getAttribute("gamesCount") == null) { + request.getSession().setAttribute("gamesCount", 0); + } + response.sendRedirect("index.jsp"); + } else { + sendError(request, response, "Неверный логин или пароль!"); + } + } + } + + private void sendError(HttpServletRequest req, HttpServletResponse resp, String msg) throws ServletException, IOException { + req.setAttribute("error", msg); + req.getRequestDispatcher("login.jsp").forward(req, resp); + } +} diff --git a/src/main/java/com/javarush/toporov/quest/servlet/GameServlet.java b/src/main/java/com/javarush/toporov/quest/servlet/GameServlet.java new file mode 100644 index 0000000..8f42732 --- /dev/null +++ b/src/main/java/com/javarush/toporov/quest/servlet/GameServlet.java @@ -0,0 +1,69 @@ +package com.javarush.toporov.quest.servlet; + +import com.javarush.toporov.quest.model.Quest; +import com.javarush.toporov.quest.model.QuestStep; +import com.javarush.toporov.quest.util.QuestData; +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; + +@WebServlet("/game") +public class GameServlet extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + HttpSession session = request.getSession(); + + if (session.getAttribute("user") == null) { + response.sendRedirect("login.jsp"); + return; + } + String questName = (String) session.getAttribute("questName"); + if (questName == null) { + questName = "Черная Орхидея"; + session.setAttribute("questName", questName); + } + + Quest quest = QuestData.getQuest(questName); + + Integer stepId = (Integer) session.getAttribute("stepId"); + if (stepId == null) { + stepId = 1; // Если потеряли id, начинаем с начала + session.setAttribute("stepId", stepId); + } + + QuestStep step = quest.getStep(stepId); + request.setAttribute("step", step); + + request.getRequestDispatcher("/game.jsp").forward(request, response); + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String choice = request.getParameter("choice"); + HttpSession session = request.getSession(); + String questName = (String) session.getAttribute("questName"); + Quest quest = QuestData.getQuest(questName); + Integer stepId = (Integer) session.getAttribute("stepId"); + QuestStep step = quest.getStep(stepId); + + Integer nextStep = step.getOptions().get(choice); + + if (nextStep == null || nextStep == -1) { + session.setAttribute("result", "LOSE"); + response.sendRedirect("result.jsp"); + return; + } + if (nextStep == -2) { + session.setAttribute("result", "WIN"); + response.sendRedirect("result.jsp"); + return; + } + + session.setAttribute("stepId", nextStep); + response.sendRedirect("game"); + } +} diff --git a/src/main/java/com/javarush/toporov/quest/servlet/RestartServlet.java b/src/main/java/com/javarush/toporov/quest/servlet/RestartServlet.java new file mode 100644 index 0000000..0784b85 --- /dev/null +++ b/src/main/java/com/javarush/toporov/quest/servlet/RestartServlet.java @@ -0,0 +1,24 @@ +package com.javarush.toporov.quest.servlet; + + +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; + +@WebServlet("/restart") +public class RestartServlet extends HttpServlet { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + HttpSession session = request.getSession(); + session.removeAttribute("stepId"); + session.removeAttribute("result"); + + Integer gamesCount = (Integer) session.getAttribute("gamesCount"); + session.setAttribute("gamesCount", (gamesCount == null ? 0 : gamesCount) + 1); + response.sendRedirect("game"); + } + } diff --git a/src/main/java/com/javarush/toporov/quest/servlet/StartServlet.java b/src/main/java/com/javarush/toporov/quest/servlet/StartServlet.java new file mode 100644 index 0000000..1b752fb --- /dev/null +++ b/src/main/java/com/javarush/toporov/quest/servlet/StartServlet.java @@ -0,0 +1,51 @@ +package com.javarush.toporov.quest.servlet; + +import com.javarush.toporov.quest.model.Quest; +import com.javarush.toporov.quest.util.QuestData; +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; + +@WebServlet("/start") +public class StartServlet extends HttpServlet { + + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + doPost(request, response); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + HttpSession session = request.getSession(); + + if (session.getAttribute("user") == null) { + response.sendRedirect("login.jsp"); + return; + } + + String questName = request.getParameter("questName"); + + + Quest quest = QuestData.getQuest(questName); + if (quest == null) { + response.sendRedirect("index.jsp"); + return; + } + + + session.setAttribute("questName", questName); + session.setAttribute("questPrologue", quest.getPrologue()); + session.setAttribute("stepId", 1); + session.removeAttribute("result"); + + + response.sendRedirect("prologue.jsp"); + } +} \ No newline at end of file diff --git a/src/main/java/com/javarush/toporov/quest/util/QuestData.java b/src/main/java/com/javarush/toporov/quest/util/QuestData.java new file mode 100644 index 0000000..0a24ebb --- /dev/null +++ b/src/main/java/com/javarush/toporov/quest/util/QuestData.java @@ -0,0 +1,225 @@ +package com.javarush.toporov.quest.util; + + +import com.javarush.toporov.quest.model.Quest; +import com.javarush.toporov.quest.model.QuestStep; + +import java.util.*; + +public class QuestData { + + private static Map quests = new HashMap<>(); + + static { + // ===================== Quest 1: The Black Orchid ===================== + String prologueTheBlackOrchid = "Дождь в этом городе никогда не смывает грехи, он лишь загоняет их глубже в канализацию. \n" + + "Вы — детектив Алекс Мерсер. Последнюю неделю вы спали не больше трех часов в сутки. \n" + + "Звонок раздался в 3:00 ночи. Голос дежурного дрожал: 'Профессор найден мертвым. Это не просто убийство, Алекс. Это послание'.\n" + + "Вы докуриваете сигарету, берете плащ и выезжаете к башне корпорации 'Эгида'."; + Quest blackOrchid = new Quest("Черная Орхидея", prologueTheBlackOrchid); + blackOrchid.addStep(new QuestStep(1, + "Кабинет на 42-м этаже пропах дорогим табаком и жженой изоляцией. Профессор обмяк в кресле, его левая рука свесилась до пола. Под ней — разбитая ампула с черным осадком. На столе горит лампа, выхватывая из темноты чертежи нейроинтерфейса. В воздухе висит легкий аромат женских духов. С чего начнете?", + Map.of("Осмотреть тело", 2, "Осмотреть кабинет", 3, "Включить диктофон", 5, "Допросить охранника", 4), + false, false)); + blackOrchid.addStep(new QuestStep(2, + "На бледной шее профессора виден багровый след от укола. Лицо застыло в гримасе ужаса, а пальцы правой руки судорожно сжимают край стола. Кажется, он пытался что-то написать перед смертью. Что предпримете?", + Map.of("Взять образцы для токсикологии", 6, "Обыскать карманы", 7, "Решить, что это естественная смерть", -1), + false, false)); + blackOrchid.addStep(new QuestStep(3, + "Вы отодвигаете тяжелые шторы. За ними скрыт сейф, вмонтированный в стену. Рядом стоит включенный компьютер, экран которого заливает комнату синим светом. На краю стола лежит кожаный дневник с инициалами профессора. Ваша цель?", + Map.of("Взломать компьютер", 10, "Открыть сейф", 8, "Уйти и вернуться позже", -1, "Прочитать дневник", 9), + false, false)); + blackOrchid.addStep(new QuestStep(4, + "Охранник в помятой форме нервно теребит кобуру. Его глаза бегают по комнате, избегая взгляда на тело. Он явно видел больше, чем говорит. Как надавите на него?", + Map.of("Давить логикой", 11, "Попытаться вызвать доверие", 12, "Запугать", -1), + false, false)); + blackOrchid.addStep(new QuestStep(5, + "Диктофон щелкает. Слышны звуки борьбы, звон стекла и прерывистый шепот профессора: 'Если ты слышишь это — значит, я не успел… проект стал неуправляем... она слишком близко…'. Запись обрывается резким шумом. Чей это голос на фоне?", + Map.of("Игнорировать", -1, "Отправить на экспертизу", 13, "Немедленно ехать к жене", 14), + false, false)); + blackOrchid.addStep(new QuestStep(6, + "Токсин редкий, военного образца.", + Map.of("Проверить лабораторию", 15, "Рассказать полиции", -1), + false, false)); + blackOrchid.addStep(new QuestStep(7, + "В кармане фотография женщины с подписью 'Она знает слишком много'.", + Map.of("Считать улику неважной", -1, "Найти эту женщину", 14), + false, false)); + blackOrchid.addStep(new QuestStep(8, + "В сейфе флешка с пометкой 'Проект Орхидея'.", + Map.of("Уничтожить", -1, "Спрятать флешку", -1, "Попробовать расшифровать данные", 16), + false, false)); + blackOrchid.addStep(new QuestStep(9, + "Дневник профессора: 'Если эксперимент завершат — сотрут память всем участникам'.", + Map.of("Найти участников эксперимента", 17, "Игнорировать", -1, "Сжечь дневник", -1), + false, false)); + blackOrchid.addStep(new QuestStep(10, + "Компьютер зашифрован.", + Map.of("Разбить компьютер", -1, "Обратиться к хакеру", 18), + false, false)); + blackOrchid.addStep(new QuestStep(11, + "Охранник говорит: ночью видел женщину в лаборатории.", + Map.of("Арестовать охранника", -1, "Узнать описание", 14), + false, false)); + blackOrchid.addStep(new QuestStep(12, + "Охранник упоминает людей из корпорации.", + Map.of("Узнать название корпорации", 19, "Прекратить допрос", -1), + false, false)); + blackOrchid.addStep(new QuestStep(13, + "Экспертиза голоса: это не жена, а коллега профессора.", + Map.of("Удалить запись", -1, "Найти учёную", 20), + false, false)); + blackOrchid.addStep(new QuestStep(14, + "Жена профессора: 'Он хотел закрыть проект'.", + Map.of("Арестовать", -1, "Проверить алиби", 21, "Поверить ей", -1), + false, false)); + blackOrchid.addStep(new QuestStep(15, + "В лаборатории пропал нейропрепарат.", + Map.of("Проверить журналы доступа", 22, "Уйти", -1), + false, false)); + blackOrchid.addStep(new QuestStep(16, + "Флешка раскрывает видеозапись убийства, монтаж очевиден.", + Map.of("Обнародовать", -1, "Отдать эксперту", 23), + false, false)); + blackOrchid.addStep(new QuestStep(17, + "Один участник эксперимента сошёл с ума.", + Map.of("Игнорировать", -1, "Допросить его", 24), + false, false)); + blackOrchid.addStep(new QuestStep(18, + "Хакер находит переписку профессора с корпорацией NeuroLab.", + Map.of("Уничтожить данные", -1, "Найти представителя корпорации", 19), + false, false)); + blackOrchid.addStep(new QuestStep(19, + "Корпорация финансировала эксперимент по стиранию памяти.", + Map.of("Закрыть дело", -1, "Найти заказчика", 25), + false, false)); + blackOrchid.addStep(new QuestStep(20, + "Учёная признаётся: 'Жена профессора приказала продолжить эксперимент'.", + Map.of("Проверить её слова", 21, "Сдать учёную полиции", -1), + false, false)); + blackOrchid.addStep(new QuestStep(21, + "Сопоставляешь алиби жены с видеозаписью.", + Map.of("Закрыть расследование", -1, "Сравнить время и место", 26), + false, false)); + blackOrchid.addStep(new QuestStep(22, + "Журналы доступа подделаны.", + Map.of("Игнорировать", -1, "Найти того, кто подделал", 24), + false, false)); + blackOrchid.addStep(new QuestStep(23, + "Эксперт подтверждает: видео — фальшивка.", + Map.of("Уничтожить запись", -1, "Найти, кому выгодно", 26), + false, false)); + blackOrchid.addStep(new QuestStep(24, + "Свидетель: 'В ночь убийства профессор спорил с женой'.", + Map.of("Не верить", -1, "Проверить жену", 26), + false, false)); + blackOrchid.addStep(new QuestStep(25, + "Ты узнаёшь: заказчик эксперимента — жена профессора.", + Map.of("Собрать доказательства", 27, "Уйти", -1), + false, false)); + blackOrchid.addStep(new QuestStep(26, + "Все улики сходятся на одном человеке.", + Map.of("Подготовить дело для суда", 27, "Скрыть правду", -1), + false, false)); + blackOrchid.addStep(new QuestStep(27, + "Зал суда замер в тишине. Вы выкладываете на стол неопровержимые доказательства: расшифрованную переписку и результаты экспертизы. Жена профессора бледнеет, понимая, что её империя рушится. Справедливость восторжествовала, детектив. Но этот город найдет новый способ вас разочаровать.", + Map.of("Передать материалы в суд", -2, "Взять деньги корпорации", -1), + true, true)); + + quests.put(blackOrchid.getName(), blackOrchid); + + // ===================== Quest 2: The Time Loop ===================== + String prologueTheTimeLoop = "Голова раскалывается. Во рту металлический привкус. Вы открываете глаза и видите календарь на стене: 24 сентября.\n" + + "Но ведь 24 сентября было вчера... Вы точно помните, как запускали Коллайдер. Помните взрыв. Помните крики.\n" + + "Почему вы снова здесь? Почему кофе на столе еще горячий? Кажется, эксперимент пошел не по плану, и вы — единственный, кто это помнит."; + + Quest timeLoop = new Quest("Петля времени", prologueTheTimeLoop); + timeLoop.addStep(new QuestStep(1, + "Мониторы мигают алым. Вы лежите на холодном кафеле, в висках пульсирует боль. В метре от вас — ваше собственное тело, но постаревшее на тридцать лет. Лаборатория пуста, если не считать гула стабилизаторов портала, которые вот-вот взорвутся. Вы заперты в бесконечном мгновении. Ваши действия?", + Map.of("Проверить тело... себя?", 2, "Осмотреть оборудование", 3, "Поговорить с коллегой (если он жив)", -1), false, false)); + timeLoop.addStep(new QuestStep(2, + "На теле следы странной жидкости.", + Map.of("Проанализировать жидкость", 4, "Игнорировать", -1), + false, false)); + timeLoop.addStep(new QuestStep(3, + "Провода змеятся по полу, искрясь от перегрузки. Главная консоль заблокирована, но на вспомогательном экране бегут строки кода инициации портала. Кажется, установку пытались остановить грубой силой. Куда полезете?", + Map.of("Взломать компьютер", 5, "Осмотреть записи", 4, "Попытаться починить машину времени", 6), + false, false)); + timeLoop.addStep(new QuestStep(4, + "Жидкость нестабильна, могла вызвать галлюцинации.", + Map.of("Найти всех, кто имел доступ", 7, "Игнорировать", -1), + false, false)); + timeLoop.addStep(new QuestStep(5, + "Портал может замыкать время в петле.", + Map.of("Использовать портал", 6, "Уничтожить данные", -1), + false, false)); + timeLoop.addStep(new QuestStep(6, + "Вы стоите перед зевом портала. Пространство внутри него закручивается в невозможную воронку, поглощая свет и звук. Если прыгнуть сейчас, можно вернуться в точку 'ноль', но риск расщепиться на атомы велик как никогда. Решайтесь.", + Map.of("Прыгнуть в портал", 8, "Попытаться отключить петлю", 7, "Игнорировать", -1), + false, false)); + timeLoop.addStep(new QuestStep(7, + "Ты выяснил, кто убийца.", + Map.of("Объявить подозреваемого", 9, "Скрыть доказательства", -1), + false, false)); + timeLoop.addStep(new QuestStep(8, + "Ослепительная вспышка. Вы открываете глаза в своей постели. Тишина. На календаре 25 сентября. Кофе на столе холодный, но мир за окном — настоящий. Петля разорвана. Вы победили время, но его шрамы навсегда останутся в вашей памяти.", + Map.of("Изменить эксперимент", -2, "Не менять ничего", -1), + true, true)); + timeLoop.addStep(new QuestStep(9, + "Вы указываете пальцем на своего коллегу. 'Это были вы! Вы подстроили перегрузку!'. \n" + + "Коллега бледнеет, его рука тянется к тревожной кнопке. 'Ты ничего не докажешь, \n" + + "в следующей петле тебя здесь не будет!'. Лаборатория начинает вибрировать. \n" + + "Нужно действовать немедленно, пока портал не поглотил всё.", + Map.of("Обезвредить его", 8, "Прыгнуть в портал, чтобы всё исправить", 8, "Сдаться", -1), + false, false)); + quests.put(timeLoop.getName(), timeLoop); + + // ===================== Quest 3: King Arthur ===================== + String prologueKingArthur = "Легенды гласят, что истинный Король не тот, кто носит корону, а тот, кто готов отдать жизнь за Камелот.\n" + + "Тьма сгущается над замком. Король Артур болен странным недугом, а Мерлин исчез три дня назад.\n" + + "Вы — молодой рыцарь, которому доверили последнюю надежду королевства. Меч в ножнах тяжел, но ваше сердце твердо."; + + Quest kingArthur = new Quest("Король Артур: Проклятый трон", prologueKingArthur); + kingArthur.addStep(new QuestStep(1, + "Тяжелые дубовые двери тронного зала приоткрыты. Внутри — гробовая тишина, нарушаемая лишь свистящим дыханием Артура. Король бледен, его вены на руках потемнели, напоминая корни мертвого дерева. Мерлина нет, но на его кафедре осталась записка на языке друидов. Стража замерла, ожидая вашего слова. Что сделаете?", + Map.of("Идти прямо к трону", 2, "Обследовать замок", 3, "Отправиться в лес", 4), + false, false)); + kingArthur.addStep(new QuestStep(2, + "Король ослаблен проклятием.", + Map.of("Найти лекарство в аптеке", 5, "Игнорировать и готовиться к битве", -1), + false, false)); + kingArthur.addStep(new QuestStep(3, + "Замок пуст, слышны шёпоты.", + Map.of("Осмотреть библиотеку", 6, "Проверить доспехи", 5), + false, false)); + kingArthur.addStep(new QuestStep(4, + "Древний лес окутан туманом, который, кажется, шепчет ваше имя. Птицы молчат, а деревья сплетают свои ветви, преграждая путь. Где-то вдали виден свет костра, а с другой стороны — руины старой часовни. Где искать ответы?", + Map.of("Идти на поляну", 6, "Следовать за тенями", -1, "Искать руины", 7), + false, false)); + kingArthur.addStep(new QuestStep(5, + "Ты находишь лекарство, кто-то следит за тобой.", + Map.of("Вернуться к королю", 8, "Поймать шпиона", 7), + false, false)); + kingArthur.addStep(new QuestStep(6, + "В библиотеке находишь книгу заклинаний.", + Map.of("Использовать заклинание", 8, "Игнорировать", -1), + false, false)); + kingArthur.addStep(new QuestStep(7, + "В тени руин стоит фигура в черном саване. Это не Мерлин. Глаза незнакомца горят колдовским огнем. 'Ты принес эликсир, рыцарь? Отдай его мне, и я сделаю тебя королем этих руин. Артур уже мертв внутри'. Тьма начинает стягиваться вокруг вас. Ваш выбор?", + Map.of("Бросить вызов", 9, "Попытаться договориться", 8), + false, false)); + kingArthur.addStep(new QuestStep(8, + "Золотистый свет эликсира разгоняет тени в зале. Король делает глубокий вдох, и его глаза проясняются. Проклятие разрушено. Камелот спасен, а предатели бежали в ужасе перед вашей доблестью. Вы доказали, что честь сильнее любой магии. Да здравствует Король!", + Map.of("Использовать заклинание", -2, "Оставить всё как есть", -1), + true, true)); + quests.put(kingArthur.getName(), kingArthur); + } + + public static Quest getQuest(String name) { + return quests.get(name); + } + + public static Set getQuestNames() { + return quests.keySet(); + } +} diff --git a/src/main/java/com/javarush/toporov/quest/util/UserRepository.java b/src/main/java/com/javarush/toporov/quest/util/UserRepository.java new file mode 100644 index 0000000..3bc72a9 --- /dev/null +++ b/src/main/java/com/javarush/toporov/quest/util/UserRepository.java @@ -0,0 +1,28 @@ +package com.javarush.toporov.quest.util; + +import java.util.HashMap; +import java.util.Map; + +public class UserRepository { + private static final Map users = new HashMap<>(); + + static { + users.put("admin", "admin"); + } + + public static boolean register(String login, String password) { + if (login == null || login.isEmpty() || users.containsKey(login)) { + return false; // Пользователь уже есть или логин пустой + } + users.put(login, password); + return true; + } + + public static void save(String login, String password) { + users.put(login, password); + } + + public static boolean check(String login, String password) { + return users.containsKey(login) && users.get(login).equals(password); + } +} diff --git a/src/main/webapp/WEB-INF/edit-user.jsp b/src/main/webapp/WEB-INF/edit-user.jsp deleted file mode 100644 index f274104..0000000 --- a/src/main/webapp/WEB-INF/edit-user.jsp +++ /dev/null @@ -1,73 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@include file="head.jsp" %> - -
-
-
- - - Edit user: - - -
- -
- - min 3 symbols -
-
- - -
- -
- - min 8 symb -
-
- - - -
- -
- -
-
- - -
- -
- - - - - - - -
-
- -
-
-
- - diff --git a/src/main/webapp/WEB-INF/head.jsp b/src/main/webapp/WEB-INF/head.jsp deleted file mode 100644 index 2f7b9f2..0000000 --- a/src/main/webapp/WEB-INF/head.jsp +++ /dev/null @@ -1,11 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - Title - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/list-user.jsp b/src/main/webapp/WEB-INF/list-user.jsp deleted file mode 100644 index dd52c55..0000000 --- a/src/main/webapp/WEB-INF/list-user.jsp +++ /dev/null @@ -1,9 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@include file="head.jsp"%> - - - ${user.login} - - - - diff --git a/src/main/webapp/WEB-INF/start-page.jsp b/src/main/webapp/WEB-INF/start-page.jsp deleted file mode 100644 index 0531c1c..0000000 --- a/src/main/webapp/WEB-INF/start-page.jsp +++ /dev/null @@ -1,8 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@include file="head.jsp"%> - -

<%= "Hello World!" %> -

-
-List Users - diff --git a/src/main/webapp/game.jsp b/src/main/webapp/game.jsp new file mode 100644 index 0000000..085b428 --- /dev/null +++ b/src/main/webapp/game.jsp @@ -0,0 +1,59 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="jakarta.tags.core" %> + + + ${sessionScope.questName} + + + +
+
+ КВЕСТ: ${sessionScope.questName} +
+ Вернуться к выбору +
Агент: ${sessionScope.user}
+
+
+ +
+ ${step.text} +
+ +
+
+ + + +
+ +
+
+ + \ No newline at end of file diff --git a/src/main/webapp/images/cat.png b/src/main/webapp/images/cat.png deleted file mode 100644 index 41771a1..0000000 Binary files a/src/main/webapp/images/cat.png and /dev/null differ diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 0000000..800792e --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,63 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="jakarta.tags.core" %> + + + Выбор приключения + + + + + +<%-- Проверка: если не залогинен, кидаем на логин --%> + + + + +
+

Добро пожаловать в Архив

+ + +
+ + + +
+ Нуарный детектив. Вам предстоит раскрыть загадочное убийство профессора, балансируя между корпоративными интригами и безумными учеными. +
+ + +
+ + Сменить пользователя +
+ + \ No newline at end of file diff --git a/src/main/webapp/login.jsp b/src/main/webapp/login.jsp new file mode 100644 index 0000000..65b1e59 --- /dev/null +++ b/src/main/webapp/login.jsp @@ -0,0 +1,26 @@ + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Вход в Квест + + + +

Доступ к архивам

+
+

${error}

+
+
+
+ + +
+
+ + \ No newline at end of file diff --git a/src/main/webapp/prologue.jsp b/src/main/webapp/prologue.jsp new file mode 100644 index 0000000..63b82c1 --- /dev/null +++ b/src/main/webapp/prologue.jsp @@ -0,0 +1,75 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="jakarta.tags.core" %> + + + Предыстория + + + +
+

Дело: ${sessionScope.questName}

+ +
+ ${sessionScope.questPrologue} +
+ + <%-- Ссылка ведет на GameServlet, который откроет шаг 1 --%> + Приступить к задаче +
+ + \ No newline at end of file diff --git a/src/main/webapp/result.jsp b/src/main/webapp/result.jsp new file mode 100644 index 0000000..64a0ae9 --- /dev/null +++ b/src/main/webapp/result.jsp @@ -0,0 +1,36 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="c" uri="jakarta.tags.core" %> + + + Финал истории + + + +
+ + +
ДЕЛО ЗАКРЫТО
+

"Справедливость восторжествовала, но цена оказалась высока... Город может спать спокойно. Пока что."

+
+ +
ТУПИК
+

"В этом городе легко исчезнуть. Еще один нераскрытый кейс, который покроется пылью в архиве."

+
+
+ + +
+ + \ No newline at end of file diff --git a/src/test/java/com/javarush/toporov/quest/test/QuestTest.java b/src/test/java/com/javarush/toporov/quest/test/QuestTest.java new file mode 100644 index 0000000..36975ff --- /dev/null +++ b/src/test/java/com/javarush/toporov/quest/test/QuestTest.java @@ -0,0 +1,35 @@ +package com.javarush.toporov.quest.test; + +import com.javarush.toporov.quest.model.Quest; +import com.javarush.toporov.quest.model.QuestStep; +import com.javarush.toporov.quest.util.QuestData; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class QuestTest { + + @Test + void testStepRetrieval() { + Quest quest = QuestData.getQuest("Черная Орхидея"); + QuestStep step = quest.getStep(1); + assertNotNull(step); + assertEquals(4, step.getOptions().size()); + } + + @Test + void testWinStep() { + Quest quest = QuestData.getQuest("Черная Орхидея"); + QuestStep winStep = quest.getStep(27); + assertTrue(winStep.isEnd()); + assertTrue(winStep.isWin()); + } + + @Test + void testQuestLogic() { + Quest quest = QuestData.getQuest("Петля времени"); + QuestStep step1 = quest.getStep(1); + + Integer nextStepId = step1.getOptions().get("Проверить тело коллеги"); + assertEquals(2, nextStepId); + } +}