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/StoryFiles.java b/src/main/java/com/javarush/khmelov/config/StoryFiles.java new file mode 100644 index 0000000..f88412d --- /dev/null +++ b/src/main/java/com/javarush/khmelov/config/StoryFiles.java @@ -0,0 +1,9 @@ +package com.javarush.khmelov.config; + +public class StoryFiles { + private StoryFiles() {} + + // Resource path inside src/main/resources + public static final String ALIEN_CHALLENGE_RESOURCE = "alien_challenge.txt"; + public static final String ALIEN_CHALLENGE_CODE = "alien_challenge"; +} diff --git a/src/main/java/com/javarush/khmelov/config/StoryLoader.java b/src/main/java/com/javarush/khmelov/config/StoryLoader.java new file mode 100644 index 0000000..89770d8 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/config/StoryLoader.java @@ -0,0 +1,107 @@ +package com.javarush.khmelov.config; + +import com.javarush.khmelov.entity.Choice; +import com.javarush.khmelov.entity.EndingType; +import com.javarush.khmelov.entity.Story; +import com.javarush.khmelov.entity.StoryNode; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class StoryLoader { + + // Analyze story file + public Story load(String code, InputStream in) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + + String title = "Untitled Story"; + Map temp = new LinkedHashMap<>(); + String currentKey = null; + + String raw; + int lineNo = 0; + + while ((raw = br.readLine()) != null) { + lineNo++; + String line = raw.trim(); + if (line.isEmpty()) continue; + + if (line.startsWith("TITLE:")) { + title = line.substring("TITLE:".length()).trim(); + continue; + } + + if (line.startsWith("[") && line.endsWith("]") && line.length() > 2) { + currentKey = line.substring(1, line.length() - 1).trim(); + temp.putIfAbsent(currentKey, new MutableNode(currentKey)); + continue; + } + + if (currentKey == null) { + throw new StoryParseException("Line outside node block at " + lineNo + ": " + raw); + } + + MutableNode node = temp.get(currentKey); + + if (line.startsWith("TEXT:")) { + node.text = line.substring("TEXT:".length()).trim(); + continue; + } + + if (line.startsWith("CHOICE:")) { + String rest = line.substring("CHOICE:".length()).trim(); + int arrow = rest.indexOf("->"); + if (arrow < 0) throw new StoryParseException("Bad CHOICE at " + lineNo + ": " + raw); + String label = rest.substring(0, arrow).trim(); + String next = rest.substring(arrow + 2).trim(); + node.choices.add(new Choice(label, next)); + continue; + } + + if (line.startsWith("END:")) { + String val = line.substring("END:".length()).trim(); + node.endingType = EndingType.fromRussian(val); + continue; + } + + throw new StoryParseException("Unknown directive at " + lineNo + ": " + raw); + } + + if (!temp.containsKey("START")) throw new StoryParseException("Missing [START] node."); + + // build nodes + Map nodes = new LinkedHashMap<>(); + for (MutableNode mn : temp.values()) { + if (mn.text == null) throw new StoryParseException("Node [" + mn.key + "] missing TEXT:"); + nodes.put(mn.key, new StoryNode(mn.key, mn.text, mn.choices, mn.endingType)); + } + + // validate links + for (StoryNode n : nodes.values()) { + for (Choice c : n.getChoices()) { + if (!nodes.containsKey(c.getNextNodeKey())) { + throw new StoryParseException("Node [" + n.getKey() + "] points to missing node: " + c.getNextNodeKey()); + } + } + } + + return new Story(code, title, nodes); + + } catch (IOException e) { + throw new StoryParseException("Failed to load story: " + code, e); + } + } + + private static final class MutableNode { + final String key; + String text; + EndingType endingType; + final List choices = new ArrayList<>(); + + MutableNode(String key) { this.key = key; } + } +} \ No newline at end of file diff --git a/src/main/java/com/javarush/khmelov/config/StoryParseException.java b/src/main/java/com/javarush/khmelov/config/StoryParseException.java new file mode 100644 index 0000000..53e48fc --- /dev/null +++ b/src/main/java/com/javarush/khmelov/config/StoryParseException.java @@ -0,0 +1,6 @@ +package com.javarush.khmelov.config; + +public class StoryParseException extends RuntimeException { + public StoryParseException(String message) { super(message); } + public StoryParseException(String message, Throwable cause) { super(message, cause); } +} 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/Controller.java b/src/main/java/com/javarush/khmelov/controller/Controller.java new file mode 100644 index 0000000..01edbbc --- /dev/null +++ b/src/main/java/com/javarush/khmelov/controller/Controller.java @@ -0,0 +1,8 @@ +package com.javarush.khmelov.controller; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public interface Controller { + String handle(HttpServletRequest req, HttpServletResponse resp) throws Exception; +} diff --git a/src/main/java/com/javarush/khmelov/controller/FrontController.java b/src/main/java/com/javarush/khmelov/controller/FrontController.java index 33242b2..dd10b06 100644 --- a/src/main/java/com/javarush/khmelov/controller/FrontController.java +++ b/src/main/java/com/javarush/khmelov/controller/FrontController.java @@ -1,43 +1,73 @@ 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 com.javarush.khmelov.config.StoryFiles; +import com.javarush.khmelov.config.StoryLoader; +import com.javarush.khmelov.entity.Story; +import com.javarush.khmelov.repository.InMemoryStoryRepository; 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.InputStream; +import java.util.HashMap; +import java.util.Map; import java.io.IOException; -@WebServlet({"", "/home", "/list-user", "/edit-user"}) +@WebServlet({"", "/home"}) public class FrontController extends HttpServlet { - private final HttpResolver httpResolver = Winter.find(HttpResolver.class); + private final Map routes = new HashMap<>(); @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); + public void init() throws ServletException { + InMemoryStoryRepository repo = new InMemoryStoryRepository(); + StoryLoader loader = new StoryLoader(); + + try (InputStream in = Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream(StoryFiles.ALIEN_CHALLENGE_RESOURCE)) { + + if (in == null) { + throw new ServletException("Story resource not found on classpath: " + + StoryFiles.ALIEN_CHALLENGE_RESOURCE + + " (expected: src/main/resources/" + StoryFiles.ALIEN_CHALLENGE_RESOURCE + ")"); + } + + Story story = loader.load(StoryFiles.ALIEN_CHALLENGE_CODE, in); + repo.put(story); + + } catch (Exception e) { + throw new ServletException("Failed to bootstrap stories", e); + } + + StoryController storyController = new StoryController(repo); + routes.put("/", storyController); + routes.put("/home", storyController); } @Override - public void init(ServletConfig config) { - config.getServletContext().setAttribute("roles", Role.values()); + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + dispatch(req, resp); } - private static String getJsp(String view) { - return "/WEB-INF/" + view + ".jsp"; + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + dispatch(req, resp); } - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - Command command = httpResolver.resolve(req); - String redirect = command.doPost(req); - resp.sendRedirect(redirect); + private void dispatch(HttpServletRequest req, HttpServletResponse resp) { + try { + String path = req.getServletPath(); + Controller controller = routes.getOrDefault(path, routes.get("/")); + String view = controller.handle(req, resp); + req.getRequestDispatcher(view).forward(req, resp); + } catch (Exception ex) { + req.setAttribute("errorMessage", ex.getMessage()); + try { + req.getRequestDispatcher("/WEB-INF/error.jsp").forward(req, resp); + } catch (Exception ignored) {} + } } } 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/controller/StoryController.java b/src/main/java/com/javarush/khmelov/controller/StoryController.java new file mode 100644 index 0000000..ad012a4 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/controller/StoryController.java @@ -0,0 +1,71 @@ +package com.javarush.khmelov.controller; + +import com.javarush.khmelov.entity.EndingType; +import com.javarush.khmelov.entity.GameSession; +import com.javarush.khmelov.entity.Story; +import com.javarush.khmelov.entity.StoryNode; +import com.javarush.khmelov.repository.StoryRepository; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +public class StoryController implements Controller { + + private final StoryRepository storyRepository; + private static final String SESSION_STATS_KEY = "GAME_STATS"; + private static final String SESSION_LAST_ENDED_NODE_KEY = "LAST_ENDED_NODE"; + + public StoryController(StoryRepository storyRepository) { + this.storyRepository = storyRepository; + } + + @Override + public String handle(HttpServletRequest req, HttpServletResponse resp) { + + final String storyCode; + String paramStory = req.getParameter("story"); + storyCode = paramStory == null || paramStory.isBlank() ? "alien_challenge" : paramStory; + + final String nodeKey; + String paramNode = req.getParameter("node"); + nodeKey = paramNode == null || paramNode.isBlank() ? "START" : paramNode; + + // Session Stats + HttpSession session = req.getSession(true); + GameSession stats = (GameSession) session.getAttribute(SESSION_STATS_KEY); + if (stats == null) { + stats = new GameSession(); + session.setAttribute(SESSION_STATS_KEY, stats); + } + if ("START".equalsIgnoreCase(nodeKey)) { + session.removeAttribute(SESSION_LAST_ENDED_NODE_KEY); + } + + Story story = storyRepository.findByCode(storyCode) + .orElseThrow(() -> new IllegalArgumentException("Story not found: " + storyCode)); + + StoryNode node = story.getNode(nodeKey); + + // When final end - count + if (node.isEnding()) { + String lastEndedNode = (String) session.getAttribute(SESSION_LAST_ENDED_NODE_KEY); + + if (lastEndedNode == null || !lastEndedNode.equals(node.getKey())) { + if (node.getEndingType() == EndingType.WIN) { + stats.recordWin(); + } else if (node.getEndingType() == EndingType.LOSE) { + stats.recordLoss(); + } + session.setAttribute(SESSION_LAST_ENDED_NODE_KEY, node.getKey()); + } + } + + req.setAttribute("story", story); + req.setAttribute("node", node); + + req.setAttribute("stats", stats); + + return "/WEB-INF/story.jsp"; + } +} diff --git a/src/main/java/com/javarush/khmelov/entity/Choice.java b/src/main/java/com/javarush/khmelov/entity/Choice.java new file mode 100644 index 0000000..7c06597 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/entity/Choice.java @@ -0,0 +1,14 @@ +package com.javarush.khmelov.entity; + +public final class Choice { + private final String label; + private final String nextNodeKey; + + public Choice(String label, String nextNodeKey) { + this.label = label; + this.nextNodeKey = nextNodeKey; + } + + public String getLabel() { return label; } + public String getNextNodeKey() { return nextNodeKey; } +} diff --git a/src/main/java/com/javarush/khmelov/entity/EndingType.java b/src/main/java/com/javarush/khmelov/entity/EndingType.java new file mode 100644 index 0000000..6c30411 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/entity/EndingType.java @@ -0,0 +1,13 @@ +package com.javarush.khmelov.entity; + +public enum EndingType { + WIN, + LOSE; + + public static EndingType fromRussian(String s) { + String val = s == null ? "" : s.trim().toLowerCase(); + if (val.contains("побед")) return WIN; + if (val.contains("поражен")) return LOSE; + throw new IllegalArgumentException("Unknown END value: " + s); + } +} diff --git a/src/main/java/com/javarush/khmelov/entity/GameSession.java b/src/main/java/com/javarush/khmelov/entity/GameSession.java new file mode 100644 index 0000000..e6fc282 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/entity/GameSession.java @@ -0,0 +1,23 @@ +package com.javarush.khmelov.entity; + +import java.io.Serializable; + +public class GameSession implements Serializable { + private int gamesPlayed; + private int wins; + private int losses; + + public int getGamesPlayed() { return gamesPlayed; } + public int getWins() { return wins; } + public int getLosses() { return losses; } + + public void recordWin() { + gamesPlayed++; + wins++; + } + + public void recordLoss() { + gamesPlayed++; + losses++; + } +} 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/Story.java b/src/main/java/com/javarush/khmelov/entity/Story.java new file mode 100644 index 0000000..b33abc9 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/entity/Story.java @@ -0,0 +1,27 @@ +package com.javarush.khmelov.entity; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class Story { + private final String code; + private final String title; + private final Map nodes; + + public Story(String code, String title, Map nodes) { + this.code = code; + this.title = title; + this.nodes = Collections.unmodifiableMap(new LinkedHashMap<>(nodes)); + } + + public String getCode() { return code; } + public String getTitle() { return title; } + public Map getNodes() { return nodes; } + + public StoryNode getNode(String key) { + StoryNode node = nodes.get(key); + if (node == null) throw new IllegalArgumentException("Unknown node: " + key); + return node; + } +} diff --git a/src/main/java/com/javarush/khmelov/entity/StoryNode.java b/src/main/java/com/javarush/khmelov/entity/StoryNode.java new file mode 100644 index 0000000..2281d25 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/entity/StoryNode.java @@ -0,0 +1,25 @@ +package com.javarush.khmelov.entity; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class StoryNode { + private final String key; + private final String text; + private final List choices; + private final EndingType endingType; // null if not ending + + public StoryNode(String key, String text, List choices, EndingType endingType) { + this.key = key; + this.text = text; + this.choices = Collections.unmodifiableList(new ArrayList<>(choices)); + this.endingType = endingType; + } + + public String getKey() { return key; } + public String getText() { return text; } + public List getChoices() { return choices; } + public EndingType getEndingType() { return endingType; } + public boolean isEnding() { return endingType != null; } +} 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/InMemoryStoryRepository.java b/src/main/java/com/javarush/khmelov/repository/InMemoryStoryRepository.java new file mode 100644 index 0000000..be9dcd3 --- /dev/null +++ b/src/main/java/com/javarush/khmelov/repository/InMemoryStoryRepository.java @@ -0,0 +1,20 @@ +package com.javarush.khmelov.repository; +import com.javarush.khmelov.entity.Story; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class InMemoryStoryRepository implements StoryRepository { + + private final Map storage = new ConcurrentHashMap<>(); + + public void put(Story story) { + storage.put(story.getCode(), story); + } + + @Override + public Optional findByCode(String code) { + return Optional.ofNullable(storage.get(code)); + } +} 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/StoryRepository.java b/src/main/java/com/javarush/khmelov/repository/StoryRepository.java new file mode 100644 index 0000000..bfef37b --- /dev/null +++ b/src/main/java/com/javarush/khmelov/repository/StoryRepository.java @@ -0,0 +1,9 @@ +package com.javarush.khmelov.repository; + +import com.javarush.khmelov.entity.Story; + +import java.util.Optional; + +public interface StoryRepository { + Optional findByCode(String code); +} 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/resources/alien_challenge.txt b/src/main/resources/alien_challenge.txt new file mode 100644 index 0000000..f68e57c --- /dev/null +++ b/src/main/resources/alien_challenge.txt @@ -0,0 +1,38 @@ +TITLE: Alien Challenge + +[START] +TEXT: Ты потерял память. Принять вызов НЛО? +CHOICE: Принять вызов -> ACCEPT_CHALLENGE +CHOICE: Отклонить вызов -> DECLINE_CHALLENGE + + +[DECLINE_CHALLENGE] +TEXT: Ты отклонил вызов. +END: Поражение + + +[ACCEPT_CHALLENGE] +TEXT: Ты принял вызов. Поднимаешься на мостик к капитану? +CHOICE: Подняться на мостик -> GO_TO_BRIDGE +CHOICE: Отказаться подниматься на мостик -> REFUSE_BRIDGE + + +[REFUSE_BRIDGE] +TEXT: Ты не пошел на переговоры. +END: Поражение + + +[GO_TO_BRIDGE] +TEXT: Ты поднялся на мостик. Ты кто? +CHOICE: Рассказать правду о себе -> TELL_TRUTH +CHOICE: Солгать о себе -> LIE_ABOUT_SELF + + +[TELL_TRUTH] +TEXT: Тебя вернули домой. +END: Победа + + +[LIE_ABOUT_SELF] +TEXT: Твоя ложь разоблачили. +END: Поражение diff --git a/src/main/webapp/WEB-INF/error.jsp b/src/main/webapp/WEB-INF/error.jsp new file mode 100644 index 0000000..a65536a --- /dev/null +++ b/src/main/webapp/WEB-INF/error.jsp @@ -0,0 +1,17 @@ +<%-- + Created by IntelliJ IDEA. + User: mkoda + Date: 2/11/2026 + Time: 10:35 PM + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + +Error + +

Ошибка

+

<%= request.getAttribute("errorMessage") %>

+Back + + diff --git a/src/main/webapp/WEB-INF/story.jsp b/src/main/webapp/WEB-INF/story.jsp new file mode 100644 index 0000000..e041231 --- /dev/null +++ b/src/main/webapp/WEB-INF/story.jsp @@ -0,0 +1,92 @@ +<%-- + Created by IntelliJ IDEA. + User: Kuzmin Mykhaylo +--%> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" language="java" %> +<%@ page import="com.javarush.khmelov.entity.Story" %> +<%@ page import="com.javarush.khmelov.entity.Choice" %> +<%@ page import="com.javarush.khmelov.entity.EndingType" %> +<%@ page import="com.javarush.khmelov.entity.StoryNode" %> +<%@ page import="com.javarush.khmelov.entity.GameSession" %> +<% + Story story = (Story) request.getAttribute("story"); + StoryNode node = (StoryNode) request.getAttribute("node"); + GameSession stats = (GameSession) request.getAttribute("stats"); +%> + + + + + <%= story.getTitle() %> + + + +
+
+

<%= story.getTitle() %>

+

<%= node.getText() %>

+
+ +
+ <% for (Choice c : node.getChoices()) { %> + + <%= c.getLabel() %> + + <% } %> +
+ + <% if (node.isEnding()) { %> +
+ <% if (node.getEndingType() == EndingType.WIN) { %> + Победа + <% } else { %> + Поражение + <% } %> +
+ + <% } %> +
+ +
+ Статистика сессии: + Игр: <%= stats.getGamesPlayed() %>, + Побед: <%= stats.getWins() %>, + Поражений: <%= stats.getLosses() %> +
+ +