diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..63e9001 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d3f414e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index cda3199..4276c64 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,11 @@ junit-jupiter-engine test + + org.yaml + snakeyaml + 2.2 + diff --git a/src/main/java/com/javarush/khmelov/lesson13/controller/EditUser.java b/src/main/java/com/javarush/khmelov/lesson13/controller/EditUser.java deleted file mode 100644 index 9d3a70d..0000000 --- a/src/main/java/com/javarush/khmelov/lesson13/controller/EditUser.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.javarush.khmelov.lesson13.controller; - -import com.javarush.khmelov.lesson13.entity.User; -import com.javarush.khmelov.lesson13.service.UserService; -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; -import java.util.Optional; - -@WebServlet("/edit-user") -public class EditUser extends HttpServlet { - - private final UserService userService=UserService.USER_SERVICE; - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String strId = req.getParameter("id"); - long id = Long.parseLong(strId); - User user = userService.get(id).orElseThrow(); - req.setAttribute("user", user); - req.getRequestDispatcher("WEB-INF/edit-user.jsp").forward(req, resp); - } -} diff --git a/src/main/java/com/javarush/khmelov/lesson13/controller/GameServlet.java b/src/main/java/com/javarush/khmelov/lesson13/controller/GameServlet.java new file mode 100644 index 0000000..afcc682 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/controller/GameServlet.java @@ -0,0 +1,77 @@ +package com.javarush.khmelov.lesson13.controller; + +import com.javarush.khmelov.lesson13.model.GameResult; +import com.javarush.khmelov.lesson13.model.Player; +import com.javarush.khmelov.lesson13.model.Scene; +import com.javarush.khmelov.lesson13.service.GameEngine; +import com.javarush.khmelov.lesson13.service.PlayerService; +import com.javarush.khmelov.lesson13.service.QuestRegistry; +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 { + + private final PlayerService playerService = PlayerService.getInstance(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + HttpSession session = req.getSession(); + + String login = (String) session.getAttribute("currentPlayerLogin"); + String questId = (String) session.getAttribute("questId"); + + if (login == null || questId == null) { + resp.sendRedirect(req.getContextPath() + "/login"); + return; + } + + Player player = playerService.findByLogin(login); + if (player == null) { + resp.sendRedirect(req.getContextPath() + "/login"); + return; + } + req.setAttribute("player", player); + + String questPath = QuestRegistry.getPath(questId); + GameEngine engine = new GameEngine(questPath); + + String sceneId = (String) session.getAttribute("sceneId"); + if (sceneId == null) { + sceneId = engine.getStartSceneId(); + session.setAttribute("sceneId", sceneId); + } + + String choiceId = req.getParameter("choice"); + if (choiceId != null) { + GameResult result = engine.makeChoice(sceneId, choiceId); + + if (result.isGameOver()) { + if (result.isWin()) { + player.recordWin(); + } else { + player.recordLoss(); + } + } + + sceneId = result.getNextSceneId(); + session.setAttribute("sceneId", sceneId); + + req.setAttribute("gameOver", result.isGameOver()); + req.setAttribute("win", result.isWin()); + } + + Scene scene = engine.getScene(sceneId); + req.setAttribute("scene", scene); + + req.getRequestDispatcher("/WEB-INF/game.jsp").forward(req, resp); + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/controller/ListUserServlet.java b/src/main/java/com/javarush/khmelov/lesson13/controller/ListUserServlet.java deleted file mode 100644 index b57f105..0000000 --- a/src/main/java/com/javarush/khmelov/lesson13/controller/ListUserServlet.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.javarush.khmelov.lesson13.controller; - -import com.javarush.khmelov.lesson13.entity.User; -import com.javarush.khmelov.lesson13.service.UserService; -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; -import java.util.Collection; - -@WebServlet("/list-user") -public class ListUserServlet extends HttpServlet { - - private UserService userService=UserService.USER_SERVICE; - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - Collection users = userService.getAll(); - req.setAttribute("users", users); - - req.getRequestDispatcher("WEB-INF/list-user.jsp") - .forward(req, resp); - } -} diff --git a/src/main/java/com/javarush/khmelov/lesson13/controller/LoginServlet.java b/src/main/java/com/javarush/khmelov/lesson13/controller/LoginServlet.java new file mode 100644 index 0000000..f95be78 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/controller/LoginServlet.java @@ -0,0 +1,62 @@ +package com.javarush.khmelov.lesson13.controller; + +import com.javarush.khmelov.lesson13.model.Player; +import com.javarush.khmelov.lesson13.service.PlayerService; +import com.javarush.khmelov.lesson13.service.QuestRegistry; +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("/login") +public class LoginServlet extends HttpServlet { + + private final PlayerService playerService = PlayerService.getInstance(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + req.setAttribute("players", playerService.getAll()); + req.setAttribute("quests", QuestRegistry.getQuestIds()); + req.getRequestDispatcher("/WEB-INF/login.jsp").forward(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + String login = req.getParameter("login"); + String password = req.getParameter("password"); + String questId = req.getParameter("questId"); + String action = req.getParameter("action"); // login / register + + try { + Player player; + + if ("register".equals(action)) { + player = playerService.register(login, password); + } else { + player = playerService.login(login, password); + } + + HttpSession session = req.getSession(true); + session.setAttribute("currentPlayerLogin", player.getLogin()); + session.setAttribute("questId", questId); + session.removeAttribute("sceneId"); + + resp.sendRedirect(req.getContextPath() + "/game"); + + } catch (IllegalArgumentException e) { + req.setAttribute("error", e.getMessage()); + req.setAttribute("players", playerService.getAll()); + req.setAttribute("quests", QuestRegistry.getQuestIds()); + req.getRequestDispatcher("/WEB-INF/login.jsp").forward(req, resp); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/javarush/khmelov/lesson13/controller/IndexServlet.java b/src/main/java/com/javarush/khmelov/lesson13/controller/RestartServlet.java similarity index 54% rename from src/main/java/com/javarush/khmelov/lesson13/controller/IndexServlet.java rename to src/main/java/com/javarush/khmelov/lesson13/controller/RestartServlet.java index df6b2f8..9d92aa1 100644 --- a/src/main/java/com/javarush/khmelov/lesson13/controller/IndexServlet.java +++ b/src/main/java/com/javarush/khmelov/lesson13/controller/RestartServlet.java @@ -1,19 +1,23 @@ package com.javarush.khmelov.lesson13.controller; -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("") -public class IndexServlet extends HttpServlet { +@WebServlet("/restart") +public class RestartServlet extends HttpServlet { + @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - req.getRequestDispatcher("WEB-INF/index.jsp") - .forward(req, resp); + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + + HttpSession session = req.getSession(); + session.invalidate(); + resp.sendRedirect(req.getContextPath() + "/login"); } } diff --git a/src/main/java/com/javarush/khmelov/lesson13/entity/Role.java b/src/main/java/com/javarush/khmelov/lesson13/entity/Role.java deleted file mode 100644 index da96e72..0000000 --- a/src/main/java/com/javarush/khmelov/lesson13/entity/Role.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.javarush.khmelov.lesson13.entity; - -public enum Role { - USER, ADMIN, GUEST -} diff --git a/src/main/java/com/javarush/khmelov/lesson13/entity/User.java b/src/main/java/com/javarush/khmelov/lesson13/entity/User.java deleted file mode 100644 index c21573a..0000000 --- a/src/main/java/com/javarush/khmelov/lesson13/entity/User.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.javarush.khmelov.lesson13.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/lesson13/model/Choice.java b/src/main/java/com/javarush/khmelov/lesson13/model/Choice.java new file mode 100644 index 0000000..9ead60e --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/model/Choice.java @@ -0,0 +1,19 @@ +package com.javarush.khmelov.lesson13.model; + +import lombok.Getter; + +public class Choice { + + @Getter + private final String id; + @Getter + private final String text; + @Getter + private final String nextSceneId; + + public Choice(String id, String text, String nextSceneId) { + this.id = id; + this.text = text; + this.nextSceneId = nextSceneId; + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/model/GameResult.java b/src/main/java/com/javarush/khmelov/lesson13/model/GameResult.java new file mode 100644 index 0000000..c540030 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/model/GameResult.java @@ -0,0 +1,19 @@ +package com.javarush.khmelov.lesson13.model; + +import lombok.Getter; + +public class GameResult { + + @Getter + private final String nextSceneId; + @Getter + private final boolean gameOver; + @Getter + private final boolean win; + + public GameResult(String nextSceneId, boolean gameOver, boolean win) { + this.nextSceneId = nextSceneId; + this.gameOver = gameOver; + this.win = win; + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/model/Player.java b/src/main/java/com/javarush/khmelov/lesson13/model/Player.java new file mode 100644 index 0000000..2a9142d --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/model/Player.java @@ -0,0 +1,41 @@ +package com.javarush.khmelov.lesson13.model; + +import lombok.Getter; + +public class Player { + + @Getter + private final String login; + @Getter + private final String passwordHash; + @Getter + private int gamesPlayed; + @Getter + private int wins; + @Getter + private int losses; + + public Player(String login, String passwordHash) { + + this.login = login; + this.passwordHash = passwordHash; + } + + public boolean checkPassword(String rawPassword) { + return passwordHash.equals(hash(rawPassword)); + } + + public void recordWin() { + gamesPlayed++; + wins++; + } + + public void recordLoss() { + gamesPlayed++; + losses++; + } + + private static String hash(String input) { + return Integer.toHexString(input.hashCode()); + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/model/Scene.java b/src/main/java/com/javarush/khmelov/lesson13/model/Scene.java new file mode 100644 index 0000000..a6856eb --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/model/Scene.java @@ -0,0 +1,21 @@ +package com.javarush.khmelov.lesson13.model; + +import lombok.Getter; + +import java.util.List; + +public class Scene { + @Getter + private final String id; + @Getter + private final String text; + @Getter + private final List choices; + + public Scene(String id, String text, List choices) { + this.id = id; + this.text = text; + this.choices = choices; + } + +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/quest/ChoiceConfig.java b/src/main/java/com/javarush/khmelov/lesson13/quest/ChoiceConfig.java new file mode 100644 index 0000000..beba97f --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/quest/ChoiceConfig.java @@ -0,0 +1,31 @@ +package com.javarush.khmelov.lesson13.quest; + +public class ChoiceConfig { + private String id; + private String text; + private String next; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getNext() { + return next; + } + + public void setNext(String next) { + this.next = next; + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/quest/QuestConfig.java b/src/main/java/com/javarush/khmelov/lesson13/quest/QuestConfig.java new file mode 100644 index 0000000..01795b1 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/quest/QuestConfig.java @@ -0,0 +1,25 @@ +package com.javarush.khmelov.lesson13.quest; + +import java.util.Map; + +public class QuestConfig { + private String start; + private Map scenes; + + public String getStart() { + return start; + } + + public void setStart(String start) { + this.start = start; + } + + public Map getScenes() { + return scenes; + } + + public void setScenes(Map scenes) { + this.scenes = scenes; + } +} + diff --git a/src/main/java/com/javarush/khmelov/lesson13/quest/SceneConfig.java b/src/main/java/com/javarush/khmelov/lesson13/quest/SceneConfig.java new file mode 100644 index 0000000..8f13a28 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/quest/SceneConfig.java @@ -0,0 +1,24 @@ +package com.javarush.khmelov.lesson13.quest; + +import java.util.List; + +public class SceneConfig { + private String text; + private List choices; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public List getChoices() { + return choices; + } + + public void setChoices(List choices) { + this.choices = choices; + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/repository/Repository.java b/src/main/java/com/javarush/khmelov/lesson13/repository/Repository.java deleted file mode 100644 index 4b00e2a..0000000 --- a/src/main/java/com/javarush/khmelov/lesson13/repository/Repository.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.javarush.khmelov.lesson13.repository; - -import com.javarush.khmelov.lesson13.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/lesson13/repository/UserRepository.java b/src/main/java/com/javarush/khmelov/lesson13/repository/UserRepository.java deleted file mode 100644 index 1c5e627..0000000 --- a/src/main/java/com/javarush/khmelov/lesson13/repository/UserRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.javarush.khmelov.lesson13.repository; - -import com.javarush.khmelov.lesson13.entity.Role; -import com.javarush.khmelov.lesson13.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/lesson13/service/GameEngine.java b/src/main/java/com/javarush/khmelov/lesson13/service/GameEngine.java new file mode 100644 index 0000000..65b2d8c --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/service/GameEngine.java @@ -0,0 +1,50 @@ +package com.javarush.khmelov.lesson13.service; + +import com.javarush.khmelov.lesson13.model.Choice; +import com.javarush.khmelov.lesson13.model.GameResult; +import com.javarush.khmelov.lesson13.model.Scene; + +import java.util.Map; + +public class GameEngine { + + private final Map scenes; + private final String startSceneId; + + public GameEngine(String questPath) { + LoadedQuest quest = QuestLoader.load(questPath); + this.scenes = quest.getScenes(); + this.startSceneId = quest.getStartScene(); + } + + public Scene getScene(String sceneId) { + return scenes.get(sceneId); + } + + public String getStartSceneId() { + return startSceneId; + } + + public GameResult makeChoice(String sceneId, String choiceId) { + + Scene currentScene = scenes.get(sceneId); + if (currentScene == null) { + return new GameResult(startSceneId, false, false); + } + + Choice choice = currentScene.getChoices().stream() + .filter(c -> c.getId().equals(choiceId)) + .findFirst() + .orElse(null); + + if (choice == null) { + return new GameResult(sceneId, false, false); + } + + String nextSceneId = choice.getNextSceneId(); + boolean isGameOver = nextSceneId.equals("win") || nextSceneId.equals("lose"); + boolean isWin = nextSceneId.equals("win"); + + return new GameResult(nextSceneId, isGameOver, isWin); + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/service/LoadedQuest.java b/src/main/java/com/javarush/khmelov/lesson13/service/LoadedQuest.java new file mode 100644 index 0000000..1545ef4 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/service/LoadedQuest.java @@ -0,0 +1,24 @@ +package com.javarush.khmelov.lesson13.service; + +import com.javarush.khmelov.lesson13.model.Scene; + +import java.util.Map; + +public class LoadedQuest { + + private final String startScene; + private final Map scenes; + + public LoadedQuest(String startScene, Map scenes) { + this.startScene = startScene; + this.scenes = scenes; + } + + public String getStartScene() { + return startScene; + } + + public Map getScenes() { + return scenes; + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/service/PlayerService.java b/src/main/java/com/javarush/khmelov/lesson13/service/PlayerService.java new file mode 100644 index 0000000..c5990cd --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/service/PlayerService.java @@ -0,0 +1,62 @@ +package com.javarush.khmelov.lesson13.service; + +import com.javarush.khmelov.lesson13.model.Player; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class PlayerService { + + private static final PlayerService INSTANCE = new PlayerService(); + private final Map players = new ConcurrentHashMap<>(); + + private PlayerService() {} + + public void clear() { + players.clear(); + } + + public static PlayerService getInstance() { + return INSTANCE; + } + + public Player register(String login, String password) { + if (login == null || login.isBlank()) { + throw new IllegalArgumentException("Логин не может быть пустым"); + } + if (password == null || password.isBlank()) { + throw new IllegalArgumentException("Пароль не может быть пустым"); + } + if (players.containsKey(login)) { + throw new IllegalArgumentException("Игрок с таким логином уже существует"); + } + + Player player = new Player(login, hash(password)); + players.put(login, player); + return player; + } + + public Player login(String login, String password) { + Player player = players.get(login); + if (player == null) { + throw new IllegalArgumentException("Игрок не найден"); + } + if (!player.checkPassword(password)) { + throw new IllegalArgumentException("Неверный пароль"); + } + return player; + } + + public Player findByLogin(String login) { + return players.get(login); + } + + public Collection getAll() { + return players.values(); + } + + private static String hash(String input) { + return Integer.toHexString(input.hashCode()); + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/service/QuestLoader.java b/src/main/java/com/javarush/khmelov/lesson13/service/QuestLoader.java new file mode 100644 index 0000000..ebf3f97 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/service/QuestLoader.java @@ -0,0 +1,48 @@ +package com.javarush.khmelov.lesson13.service; + +import com.javarush.khmelov.lesson13.model.Choice; +import com.javarush.khmelov.lesson13.model.Scene; +import com.javarush.khmelov.lesson13.quest.*; + +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class QuestLoader { + + public static LoadedQuest load(String path) { + LoaderOptions options = new LoaderOptions(); + Constructor constructor = new Constructor(QuestConfig.class, options); + Yaml yaml = new Yaml(constructor); + + InputStream input = QuestLoader.class + .getClassLoader() + .getResourceAsStream(path); + + if (input == null) { + throw new RuntimeException("Quest file not found: " + path); + } + + QuestConfig config = yaml.load(input); + + Map scenes = new HashMap<>(); + + for (var entry : config.getScenes().entrySet()) { + String id = entry.getKey(); + SceneConfig sc = entry.getValue(); + + List choices = sc.getChoices().stream() + .map(c -> new Choice(c.getId(), c.getText(), c.getNext())) + .toList(); + + scenes.put(id, new Scene(id, sc.getText(), choices)); + } + + return new LoadedQuest(config.getStart(), scenes); + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/service/QuestRegistry.java b/src/main/java/com/javarush/khmelov/lesson13/service/QuestRegistry.java new file mode 100644 index 0000000..3da319a --- /dev/null +++ b/src/main/java/com/javarush/khmelov/lesson13/service/QuestRegistry.java @@ -0,0 +1,53 @@ +package com.javarush.khmelov.lesson13.service; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class QuestRegistry { + + private static final String QUESTS_DIR = "quests"; + private static final Map QUESTS = new HashMap<>(); + + static { + loadQuests(); + } + + private static void loadQuests() { + try { + ClassLoader classLoader = QuestRegistry.class.getClassLoader(); + URL url = classLoader.getResource(QUESTS_DIR); + + if (url == null) { + throw new RuntimeException("Quests directory not found in resources"); + } + + Path questsPath = Paths.get(url.toURI()); + + try (DirectoryStream stream = + Files.newDirectoryStream(questsPath, "*.yaml")) { + + for (Path file : stream) { + String fileName = file.getFileName().toString(); + String questId = fileName.replace(".yaml", ""); + QUESTS.put(questId, QUESTS_DIR + "/" + fileName); + } + } + + } catch (IOException | URISyntaxException e) { + throw new RuntimeException("Failed to load quests", e); + } + } + + public static Set getQuestIds() { + return QUESTS.keySet(); + } + + public static String getPath(String questId) { + return QUESTS.get(questId); + } +} diff --git a/src/main/java/com/javarush/khmelov/lesson13/service/UserService.java b/src/main/java/com/javarush/khmelov/lesson13/service/UserService.java deleted file mode 100644 index 0c7a311..0000000 --- a/src/main/java/com/javarush/khmelov/lesson13/service/UserService.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.javarush.khmelov.lesson13.service; - -import com.javarush.khmelov.lesson13.entity.User; -import com.javarush.khmelov.lesson13.repository.Repository; -import com.javarush.khmelov.lesson13.repository.UserRepository; - -import java.util.Collection; -import java.util.Optional; - -public enum UserService { - - USER_SERVICE; - - private final Repository userRepository = new 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/resources/quests/dungeon.yaml b/src/main/resources/quests/dungeon.yaml new file mode 100644 index 0000000..e8b7e34 --- /dev/null +++ b/src/main/resources/quests/dungeon.yaml @@ -0,0 +1,30 @@ +start: cell + +scenes: + cell: + text: "Ты очнулся в темнице. Слышишь шаги." + choices: + - id: wait + text: "Затаиться" + next: guard + - id: scream + text: "Кричать" + next: lose + + guard: + text: "Страж проходит мимо. Есть шанс сбежать." + choices: + - id: run + text: "Бежать" + next: win + - id: stop + text: "Остановиться" + next: lose + + win: + text: "Ты сбежал. Победа!" + choices: [] + + lose: + text: "Страж убил тебя." + choices: [] diff --git a/src/main/resources/quests/forest.yaml b/src/main/resources/quests/forest.yaml new file mode 100644 index 0000000..049fae5 --- /dev/null +++ b/src/main/resources/quests/forest.yaml @@ -0,0 +1,140 @@ +start: clearing + +scenes: + clearing: + text: "Ты приходишь в себя на лесной поляне. Холодно, туман стелется по земле." + choices: + - id: explore + text: "Осмотреться вокруг" + next: crossroads + - id: shout + text: "Крикнуть и позвать на помощь" + next: wolves + - id: hide + text: "Спрятаться в кустах" + next: bushes + + crossroads: + text: "Ты находишь развилку трёх троп." + choices: + - id: left + text: "Пойти по левой тропе" + next: river + - id: center + text: "Пойти прямо" + next: hut + - id: right + text: "Пойти по правой тропе" + next: swamp + + bushes: + text: "Ты затаился. Кажется, кто-то проходит мимо." + choices: + - id: wait + text: "Продолжать ждать" + next: crossroads + - id: run + text: "Резко выбежать" + next: wolves + - id: sleep + text: "Попробовать уснуть" + next: lose # ← ИСПРАВЛЕНО + + wolves: + text: "Из тумана появляются волки." + choices: + - id: fight + text: "Попытаться отбиться" + next: lose + - id: climb + text: "Залезть на дерево" + next: tree + - id: run + text: "Бежать без оглядки" + next: swamp + + tree: + text: "Ты на дереве. Волки ждут внизу." + choices: + - id: wait + text: "Переждать" + next: crossroads + - id: jump + text: "Прыгнуть и бежать" + next: swamp + - id: shout + text: "Кричать" + next: lose + + river: + text: "Ты выходишь к бурной реке." + choices: + - id: swim + text: "Попытаться переплыть" + next: lose + - id: bridge + text: "Искать мост" + next: bridge + - id: follow + text: "Идти вдоль берега" + next: hut + + bridge: + text: "Ты находишь старый мост." + choices: + - id: cross + text: "Перейти мост" + next: village + - id: inspect + text: "Осмотреть мост" + next: lose # ← ловушка = поражение + - id: return + text: "Вернуться назад" + next: river + + hut: + text: "Перед тобой старая хижина." + choices: + - id: enter + text: "Зайти внутрь" + next: win + - id: knock + text: "Постучать" + next: village + - id: bypass + text: "Обойти хижину" + next: lose # ← ловушка = поражение + + swamp: + text: "Ты увязаешь в болоте." + choices: + - id: struggle + text: "Вырываться" + next: lose + - id: calm + text: "Успокоиться и искать выход" + next: crossroads + - id: call + text: "Звать на помощь" + next: lose + + village: + text: "Ты выходишь к деревне. Люди смотрят настороженно." + choices: + - id: talk + text: "Поговорить с жителями" + next: win + - id: steal + text: "Попытаться украсть припасы" + next: lose + - id: leave + text: "Уйти обратно в лес" + next: crossroads + + win: + text: "Ты находишь помощь и выбираешься из леса. Победа!" + choices: [] + + lose: + text: "Это был твой последний выбор." + choices: [] diff --git a/src/main/resources/quests/space.yaml b/src/main/resources/quests/space.yaml new file mode 100644 index 0000000..dc3e5f3 --- /dev/null +++ b/src/main/resources/quests/space.yaml @@ -0,0 +1,30 @@ +start: start + +scenes: + start: + text: "Ты потерял память. Принять вызов НЛО?" + choices: + - id: yes + text: "Принять вызов" + next: bridge + - id: no + text: "Отклонить вызов" + next: lose + + bridge: + text: "Ты поднимаешься на мостик. Капитан ждёт ответа." + choices: + - id: truth + text: "Сказать правду" + next: win + - id: lie + text: "Солгать" + next: lose + + win: + text: "Тебя вернули домой. Победа!" + choices: [] + + lose: + text: "Ты проиграл." + choices: [] 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 746bdaf..0000000 --- a/src/main/webapp/WEB-INF/edit-user.jsp +++ /dev/null @@ -1,69 +0,0 @@ -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> - - - Edit ${requestScope.user.login} - - - - -
- - -
-
- - - Edit user ${requestScope.user.login} - - -
- -
- - help -
-
- - -
- -
- - 3...32 symbols -
-
- - -
- -
- -
-
- - -
- -
- - -
-
- -
-
-
- - diff --git a/src/main/webapp/WEB-INF/game.jsp b/src/main/webapp/WEB-INF/game.jsp new file mode 100644 index 0000000..a1187e2 --- /dev/null +++ b/src/main/webapp/WEB-INF/game.jsp @@ -0,0 +1,139 @@ +<%@ page contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + + + + + + Quest + + + + + + +
+ +
+
+ +
+ +
+ 📜 Квест: ${sessionScope.questId} +
+ +
+ 🧙 ${sessionScope.currentPlayerLogin}, твой выбор определит судьбу… +
+ +
+ 🎮 Игр: ${player.gamesPlayed} + | 🏆 Побед: ${player.wins} + | 💀 Поражений: ${player.losses} +
+ +

+ ${scene.text} +

+ + + + +
+
+ + + +
+
+
+ + +
+
+ ${win ? '🏆 Победа!' : '☠ Поражение'} +
+

+ ${win ? 'Приключение завершено.' : 'Судьба была сурова.'} +

+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ Quest · v1.0 · by Andrew Lazareff +
+ +
+ + + + + diff --git a/src/main/webapp/WEB-INF/index.jsp b/src/main/webapp/WEB-INF/index.jsp deleted file mode 100644 index f273a96..0000000 --- a/src/main/webapp/WEB-INF/index.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - - - - JSP - Hello Page - - -

<%= "Hello Page!" %> -

-
-User list -
- - - \ 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 ea59fcc..0000000 --- a/src/main/webapp/WEB-INF/list-user.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - List User - - -

User list

- - - id=${user.id}. Edit ${user.login}
-
- - - diff --git a/src/main/webapp/WEB-INF/login.jsp b/src/main/webapp/WEB-INF/login.jsp new file mode 100644 index 0000000..67e6024 --- /dev/null +++ b/src/main/webapp/WEB-INF/login.jsp @@ -0,0 +1,113 @@ +<<%@ page contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + + + + + + Quest + + + + + + +
+ +
+
+ +
+ +

+ 🧙 Выбор героя +

+ + + +
+ ${error} +
+
+ + + +
Существующий герой
+ +
+ + + + + + + + +
+ +
+
+ + +
Новый герой
+ +
+ + + + + + + + +
+ +
+
+
+ +
+ 🧭 Quest · v1.0 · by Andrew Lazareff +
+ +
+ + + diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css new file mode 100644 index 0000000..e69de29 diff --git a/src/main/webapp/images/img.png b/src/main/webapp/images/img.png deleted file mode 100644 index 1000db3..0000000 Binary files a/src/main/webapp/images/img.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..0d27b8d --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,4 @@ +<%@ page contentType="text/html; charset=UTF-8" %> +<% + response.sendRedirect(request.getContextPath() + "/login"); +%> \ No newline at end of file diff --git a/src/test/java/com/javarush/khmelov/lesson13/model/PlayerTest.java b/src/test/java/com/javarush/khmelov/lesson13/model/PlayerTest.java new file mode 100644 index 0000000..b8ac766 --- /dev/null +++ b/src/test/java/com/javarush/khmelov/lesson13/model/PlayerTest.java @@ -0,0 +1,19 @@ +package com.javarush.khmelov.lesson13.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PlayerTest { + + @Test + void winIncrementsStats() { + Player p = new Player("test", "hash"); + + p.recordWin(); + + assertEquals(1, p.getGamesPlayed()); + assertEquals(1, p.getWins()); + assertEquals(0, p.getLosses()); + } +} diff --git a/src/test/java/com/javarush/khmelov/lesson13/service/GameEngineTest.java b/src/test/java/com/javarush/khmelov/lesson13/service/GameEngineTest.java new file mode 100644 index 0000000..25a0ac7 --- /dev/null +++ b/src/test/java/com/javarush/khmelov/lesson13/service/GameEngineTest.java @@ -0,0 +1,55 @@ +package com.javarush.khmelov.lesson13.service; + +import com.javarush.khmelov.lesson13.model.GameResult; +import com.javarush.khmelov.lesson13.model.Scene; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class GameEngineTest { + + private final GameEngine engine = + new GameEngine("quests/space.yaml"); + + @Test + void startSceneExists() { + Scene scene = engine.getScene(engine.getStartSceneId()); + + assertNotNull(scene); + assertEquals(engine.getStartSceneId(), scene.getId()); + } + + @Test + void winPathWorks() { + GameResult step1 = + engine.makeChoice(engine.getStartSceneId(), "yes"); + assertFalse(step1.isGameOver()); + + GameResult step2 = + engine.makeChoice(step1.getNextSceneId(), "go"); + assertFalse(step2.isGameOver()); + + GameResult step3 = + engine.makeChoice(step2.getNextSceneId(), "truth"); + assertTrue(step3.isGameOver()); + assertTrue(step3.isWin()); + } + + @Test + void losePathWorks() { + GameResult result = + engine.makeChoice(engine.getStartSceneId(), "no"); + + assertTrue(result.isGameOver()); + assertFalse(result.isWin()); + } + + @Test + void wrongChoiceDoesNothing() { + GameResult result = + engine.makeChoice(engine.getStartSceneId(), "wrong"); + + assertFalse(result.isGameOver()); + assertEquals(engine.getStartSceneId(), result.getNextSceneId()); + } +} diff --git a/src/test/java/com/javarush/khmelov/lesson13/service/PlayerServiceTest.java b/src/test/java/com/javarush/khmelov/lesson13/service/PlayerServiceTest.java new file mode 100644 index 0000000..0a7420d --- /dev/null +++ b/src/test/java/com/javarush/khmelov/lesson13/service/PlayerServiceTest.java @@ -0,0 +1,61 @@ +package com.javarush.khmelov.lesson13.service; + +import com.javarush.khmelov.lesson13.model.Player; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; + +class PlayerServiceTest { + + private PlayerService service; + + @BeforeEach + void setUp() { + service = PlayerService.getInstance(); + service.clear(); + } + + @Test + void registerCreatesNewPlayer() { + Player p = service.register("hero", "123"); + assertEquals("hero", p.getLogin()); + } + + @Test + void registerWithExistingLoginThrows() { + service.register("hero", "123"); + + assertThrows( + IllegalArgumentException.class, + () -> service.register("hero", "456") + ); + } + + @Test + void loginWithCorrectPasswordWorks() { + service.register("hero", "123"); + + Player p = service.login("hero", "123"); + assertEquals("hero", p.getLogin()); + } + + @Test + void loginWithWrongPasswordThrows() { + service.register("hero", "123"); + + assertThrows( + IllegalArgumentException.class, + () -> service.login("hero", "wrong") + ); + } + + @Test + void loginOfMissingPlayerThrows() { + assertThrows( + IllegalArgumentException.class, + () -> service.login("ghost", "123") + ); + } +} diff --git a/src/test/java/com/javarush/khmelov/lesson13/service/QuestLoaderTest.java b/src/test/java/com/javarush/khmelov/lesson13/service/QuestLoaderTest.java new file mode 100644 index 0000000..47abfaf --- /dev/null +++ b/src/test/java/com/javarush/khmelov/lesson13/service/QuestLoaderTest.java @@ -0,0 +1,17 @@ +package com.javarush.khmelov.lesson13.service; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class QuestLoaderTest { + + @Test + void questLoadsCorrectly() { + LoadedQuest quest = QuestLoader.load("quests/dungeon.yaml"); + + assertNotNull(quest); + assertNotNull(quest.getStartScene()); + assertFalse(quest.getScenes().isEmpty()); + } +} diff --git a/src/test/java/com/javarush/khmelov/lesson13/service/QuestRegistryTest.java b/src/test/java/com/javarush/khmelov/lesson13/service/QuestRegistryTest.java new file mode 100644 index 0000000..108b944 --- /dev/null +++ b/src/test/java/com/javarush/khmelov/lesson13/service/QuestRegistryTest.java @@ -0,0 +1,27 @@ +package com.javarush.khmelov.lesson13.service; + +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class QuestRegistryTest { + + @Test + void questsAreLoaded() { + Set quests = QuestRegistry.getQuestIds(); + + assertFalse(quests.isEmpty()); + assertTrue(quests.contains("space")); + assertTrue(quests.contains("dungeon")); + } + + @Test + void questPathExists() { + String path = QuestRegistry.getPath("space"); + + assertNotNull(path); + assertTrue(path.endsWith(".yaml")); + } +} \ No newline at end of file