diff --git a/backend/src/main/java/cz/jzitnik/chronos/controllers/CharacterController.java b/backend/src/main/java/cz/jzitnik/chronos/controllers/CharacterController.java new file mode 100644 index 0000000..4e90c16 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/controllers/CharacterController.java @@ -0,0 +1,88 @@ +package cz.jzitnik.chronos.controllers; + +import cz.jzitnik.chronos.entities.Item; +import cz.jzitnik.chronos.interactions.InteractionService; +import cz.jzitnik.chronos.payload.requests.InteractionRequest; +import cz.jzitnik.chronos.payload.responses.InteractionResponse; +import cz.jzitnik.chronos.payload.responses.TakeItemsResponse; +import cz.jzitnik.chronos.payload.responses.UnifiedResponse; +import cz.jzitnik.chronos.repository.CharacterRepository; +import cz.jzitnik.chronos.repository.PlayerRepository; +import cz.jzitnik.chronos.utils.anotations.CheckUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("/game/characters") +public class CharacterController { + @Autowired + private InteractionService interactionService; + + @Autowired + private PlayerRepository playerRepository; + + @Autowired + private CharacterRepository characterRepository; + + @PostMapping("/interact") + @CheckUser + public ResponseEntity> interact(@RequestParam String playerKey, @RequestBody InteractionRequest interactionRequest) { + var player = playerRepository.findByPlayerKey(playerKey).get(); + + var characterOptional = characterRepository.findById(interactionRequest.getCharacterId()); + + if (characterOptional.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(UnifiedResponse.failure(null)); + } + if (!characterOptional.get().getRoom().equals(player.getCurrentRoom())) { + return ResponseEntity.badRequest().body(UnifiedResponse.failure(null)); + } + if (characterOptional.get().isInteractedWith()) { + return ResponseEntity.noContent().build(); + } + + var interaction = characterOptional.get().getInteraction(); + + var interactionFunc = interactionService.mapInteraction(interaction); + + var interactionResponse = interactionFunc.apply(player, characterOptional.get(), interactionRequest.getData()); + + return ResponseEntity.ok( + UnifiedResponse.success(interactionResponse) + ); + } + + @PostMapping("/take_items") + @CheckUser + public ResponseEntity> takeItems(@RequestParam String playerKey, @RequestParam Long characterId) { + var player = playerRepository.findByPlayerKey(playerKey).get(); + + var characterOptional = characterRepository.findById(characterId); + + if (characterOptional.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(UnifiedResponse.failure(null)); + } + + var character = characterOptional.get(); + if (!character.getRoom().equals(player.getCurrentRoom())) { + return ResponseEntity.badRequest().body(UnifiedResponse.failure(null)); + } + + if (character.getPlayersWhoSaw().contains(player)) { + return ResponseEntity.ok(UnifiedResponse.success(TakeItemsResponse.ALREADY_TAKEN)); + } + + for (Item item : character.getInventory()) { + var itemClone = new Item(item.getItemType(), player); + player.addItem(itemClone); + } + player.getSeenCharacters().add(character); + + playerRepository.save(player); + + return ResponseEntity.ok(UnifiedResponse.success(TakeItemsResponse.DONE)); + } +} \ No newline at end of file diff --git a/backend/src/main/java/cz/jzitnik/chronos/controllers/GameController.java b/backend/src/main/java/cz/jzitnik/chronos/controllers/GameController.java index f8bbbdb..65426db 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/controllers/GameController.java +++ b/backend/src/main/java/cz/jzitnik/chronos/controllers/GameController.java @@ -1,10 +1,16 @@ package cz.jzitnik.chronos.controllers; import cz.jzitnik.chronos.entities.Game; +import cz.jzitnik.chronos.payload.errors.NotFoundError; +import cz.jzitnik.chronos.payload.responses.TestGameKeyResponse; import cz.jzitnik.chronos.payload.responses.UnifiedResponse; import cz.jzitnik.chronos.repository.GameRepository; +import cz.jzitnik.chronos.repository.PlayerRepository; import cz.jzitnik.chronos.services.GameService; +import cz.jzitnik.chronos.utils.anotations.CheckUser; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @CrossOrigin(origins = "*", maxAge = 3600) @@ -17,6 +23,9 @@ public class GameController { @Autowired private GameRepository gameRepository; + @Autowired + private PlayerRepository playerRepository; + @PostMapping("/new") public UnifiedResponse createGame() { var game = gameService.createGame(); @@ -24,4 +33,52 @@ public class GameController { return UnifiedResponse.success(game); } + + @PostMapping("/start") + @CheckUser + public ResponseEntity> startGame(@RequestParam String playerKey, @RequestParam String gameKey) { + var gameOptional = gameRepository.findByGameKey(gameKey); + var player = playerRepository.findByPlayerKey(playerKey).get(); + + if (gameOptional.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body( + UnifiedResponse.failure(new NotFoundError("Game with key " + gameKey + " was not found!")) + ); + } + + var game = gameOptional.get(); + + if (!game.getAdminPlayer().equals(player)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body( + UnifiedResponse.failure(new Error("Not authorized!")) + ); + } + + game.setStarted(true); + + gameRepository.save(game); + + return ResponseEntity.ok(UnifiedResponse.success(null)); + } + + @GetMapping("/test") + public ResponseEntity> testGameKey(@RequestParam String gameKey) { + var gameOptional = gameRepository.findByGameKey(gameKey); + + if (gameOptional.isEmpty()) { + return ResponseEntity.ok(UnifiedResponse.success(TestGameKeyResponse.INVALID_KEY)); + } + + var game = gameOptional.get(); + + if (game.isStarted()) { + return ResponseEntity.ok(UnifiedResponse.success(TestGameKeyResponse.GAME_STARTED)); + } + + if (game.getPlayers().size() >= 3) { + return ResponseEntity.ok(UnifiedResponse.success(TestGameKeyResponse.MAXIUM_PLAYERS)); + } + + return ResponseEntity.ok(UnifiedResponse.success(TestGameKeyResponse.WORKING)); + } } diff --git a/backend/src/main/java/cz/jzitnik/chronos/controllers/PlayerController.java b/backend/src/main/java/cz/jzitnik/chronos/controllers/PlayerController.java index c4268ff..bd0fca3 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/controllers/PlayerController.java +++ b/backend/src/main/java/cz/jzitnik/chronos/controllers/PlayerController.java @@ -38,8 +38,6 @@ public class PlayerController { var player = gameService.addPlayer(game, playerNameRequest.getName()); - gameRepository.save(game); - return ResponseEntity.ok( UnifiedResponse.success(player.getPlayerKey()) ); diff --git a/backend/src/main/java/cz/jzitnik/chronos/controllers/RoomController.java b/backend/src/main/java/cz/jzitnik/chronos/controllers/RoomController.java new file mode 100644 index 0000000..cf374b6 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/controllers/RoomController.java @@ -0,0 +1,60 @@ +package cz.jzitnik.chronos.controllers; + +import cz.jzitnik.chronos.entities.Room; +import cz.jzitnik.chronos.payload.responses.UnifiedResponse; +import cz.jzitnik.chronos.repository.PlayerRepository; +import cz.jzitnik.chronos.repository.RoomRepository; +import cz.jzitnik.chronos.services.GameService; +import cz.jzitnik.chronos.utils.anotations.CheckUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("/game/rooms") +public class RoomController { + @Autowired + private PlayerRepository playerRepository; + + @Autowired + private RoomRepository roomRepository; + + @Autowired + private GameService gameService; + + @GetMapping("/current_room") + @CheckUser + public ResponseEntity> getCurrentRoom(@RequestParam("playerKey") String playerKey) { + var player = playerRepository.findByPlayerKey(playerKey).get(); + + return ResponseEntity.ok( + UnifiedResponse.success(player.getCurrentRoom()) + ); + } + + @PostMapping("/move") + @CheckUser + public ResponseEntity> moveToRoom(@RequestParam("playerKey") String playerKey, @RequestParam("roomId") Long roomId) { + var player = playerRepository.findByPlayerKey(playerKey).get(); + + var roomOptional = roomRepository.findById(roomId); + + if (roomOptional.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(UnifiedResponse.failure(null)); + } + + var fromThisGame = gameService.isFromThisGame(player.getGame(), roomOptional.get()); + + if (!fromThisGame) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(UnifiedResponse.failure(new Error("Invalid roomId"))); + } + + player.setCurrentRoom(roomOptional.get()); + + playerRepository.save(player); + + return ResponseEntity.ok(UnifiedResponse.success(null)); + } +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/controllers/StatusController.java b/backend/src/main/java/cz/jzitnik/chronos/controllers/StatusController.java new file mode 100644 index 0000000..6650a99 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/controllers/StatusController.java @@ -0,0 +1,19 @@ +package cz.jzitnik.chronos.controllers; + +import cz.jzitnik.chronos.payload.responses.UnifiedResponse; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("/status") +public class StatusController { + + // Don't decode this base64, just go to /status endpoint, you'll be surprised + @GetMapping + public UnifiedResponse getStatus() { + return UnifiedResponse.success("working"); + } +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/entities/Character.java b/backend/src/main/java/cz/jzitnik/chronos/entities/Character.java index 97aa421..f5721b1 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/entities/Character.java +++ b/backend/src/main/java/cz/jzitnik/chronos/entities/Character.java @@ -2,7 +2,9 @@ package cz.jzitnik.chronos.entities; import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonManagedReference; +import cz.jzitnik.chronos.interactions.Interaction; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; @@ -15,6 +17,7 @@ import java.util.List; @Setter @Entity @NoArgsConstructor +@Table(name = "characters") public class Character { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -29,10 +32,16 @@ public class Character { @Enumerated(EnumType.STRING) private Interaction interaction; + private boolean interactedWith; + @ManyToOne @JsonBackReference(value = "room-characters") private Room room; + @ManyToMany(mappedBy = "seenCharacters", cascade = CascadeType.ALL) + @JsonIgnore + private List playersWhoSaw = new ArrayList<>(); + private String dialog; public Character(String name, Room room, String dialog) { diff --git a/backend/src/main/java/cz/jzitnik/chronos/entities/Game.java b/backend/src/main/java/cz/jzitnik/chronos/entities/Game.java index f13ebb5..280d3cd 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/entities/Game.java +++ b/backend/src/main/java/cz/jzitnik/chronos/entities/Game.java @@ -1,5 +1,6 @@ package cz.jzitnik.chronos.entities; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import com.fasterxml.jackson.annotation.JsonManagedReference; import lombok.Getter; @@ -26,9 +27,20 @@ public class Game { private List players = new ArrayList<>(); @OneToMany(mappedBy = "game", cascade = CascadeType.ALL) - @JsonManagedReference(value = "game-rooms") + @JsonIgnore private List rooms = new ArrayList<>(); + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "default_room_id", referencedColumnName = "id") + private Room defaultRoom; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "admin_player_id", referencedColumnName = "id") + @JsonIgnore + private Player adminPlayer; + + private boolean started = false; + public void addPlayer(Player player) { this.players.add(player); } diff --git a/backend/src/main/java/cz/jzitnik/chronos/entities/Item.java b/backend/src/main/java/cz/jzitnik/chronos/entities/Item.java index 7e6d753..a025040 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/entities/Item.java +++ b/backend/src/main/java/cz/jzitnik/chronos/entities/Item.java @@ -34,4 +34,9 @@ public class Item { this.itemType = itemType; this.charowner = charowner; } + + public Item(ItemType itemType, Player owner) { + this.itemType = itemType; + this.owner = owner; + } } diff --git a/backend/src/main/java/cz/jzitnik/chronos/entities/Player.java b/backend/src/main/java/cz/jzitnik/chronos/entities/Player.java index 0abee5d..7f713b6 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/entities/Player.java +++ b/backend/src/main/java/cz/jzitnik/chronos/entities/Player.java @@ -33,14 +33,29 @@ public class Player { private Game game; @ManyToOne - @Column(name = "current_room") + @JoinColumn(name = "current_room") private Room currentRoom; @Column(unique = true, nullable = false) @JsonIgnore private String playerKey; + private boolean luck = false; + + @ManyToMany + @JoinTable( + name = "character_player", + joinColumns = @JoinColumn(name = "player_id"), + inverseJoinColumns = @JoinColumn(name = "character_id") + ) + @JsonIgnore + private List seenCharacters = new ArrayList<>(); + public Player(String name) { this.name = name; } + + public void addItem(Item item) { + inventory.add(item); + } } diff --git a/backend/src/main/java/cz/jzitnik/chronos/entities/Room.java b/backend/src/main/java/cz/jzitnik/chronos/entities/Room.java index 99cf351..b6da9f4 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/entities/Room.java +++ b/backend/src/main/java/cz/jzitnik/chronos/entities/Room.java @@ -1,6 +1,5 @@ package cz.jzitnik.chronos.entities; -import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; @@ -31,13 +30,17 @@ public class Room { private List characters = new ArrayList<>(); @ManyToOne - @JsonBackReference(value = "game-rooms") + @JsonIgnore private Game game; - @OneToMany(mappedBy = "current_room", cascade = CascadeType.ALL) + @OneToMany(mappedBy = "currentRoom", cascade = CascadeType.ALL) @JsonIgnore private List players = new ArrayList<>(); + @OneToOne(mappedBy = "defaultRoom") + @JsonIgnore + private Game gameDefault; + public Room(String name, Game game) { this.name = name; this.game = game; diff --git a/backend/src/main/java/cz/jzitnik/chronos/interactions/Interaction.java b/backend/src/main/java/cz/jzitnik/chronos/interactions/Interaction.java new file mode 100644 index 0000000..1fe1fc3 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/interactions/Interaction.java @@ -0,0 +1,5 @@ +package cz.jzitnik.chronos.interactions; + +public enum Interaction { + Farmer +} \ No newline at end of file diff --git a/backend/src/main/java/cz/jzitnik/chronos/interactions/InteractionService.java b/backend/src/main/java/cz/jzitnik/chronos/interactions/InteractionService.java new file mode 100644 index 0000000..64e2706 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/interactions/InteractionService.java @@ -0,0 +1,80 @@ +package cz.jzitnik.chronos.interactions; + +import cz.jzitnik.chronos.entities.Item; +import cz.jzitnik.chronos.entities.ItemType; +import cz.jzitnik.chronos.entities.Player; +import cz.jzitnik.chronos.entities.Character; +import cz.jzitnik.chronos.payload.responses.InteractionResponse; +import cz.jzitnik.chronos.repository.CharacterRepository; +import cz.jzitnik.chronos.repository.PlayerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Random; + +@Service +public class InteractionService { + @Autowired + private PlayerRepository playerRepository; + + @Autowired + private CharacterRepository characterRepository; + + @FunctionalInterface + public interface Function3 { + W apply(T t, U u, V v); + } + + public Function3 mapInteraction(Interaction interaction) { + return switch (interaction) { + case Farmer -> ((player, character, data) -> { + Random random = new Random(); + boolean isLucky = player.isLuck(); + + int characterChoice = random.nextInt(3); + + int userChoice = switch (data) { + case "0" -> 0; // Rock + case "1" -> 1; // Paper + case "2" -> 2; // Scissors + default -> throw new IllegalArgumentException("Invalid choice"); + }; + + if (isLucky) { + // 50/50 chance + boolean playerWins = random.nextBoolean(); + + // Reset the luck potion after use + player.setLuck(false); + + if (playerWins) { + player.addItem(new Item(ItemType.KEY_FRAGMENT, player)); + playerRepository.save(player); + + character.setInteractedWith(true); + characterRepository.save(character); + + return new InteractionResponse(true, "Vyhrál jsi, dostáváš jeden fragment klíče."); + } else { + return new InteractionResponse(false, "Prohrál jsi"); + } + } else { + // Standard rock-paper-scissors game logic + if (userChoice == characterChoice) { + return new InteractionResponse(false, "Remíza, oběma hráčům se podařilo uhádnout."); + } else if ((userChoice == 0 && characterChoice == 2) || (userChoice == 1 && characterChoice == 0) || (userChoice == 2 && characterChoice == 1)) { + player.addItem(new Item(ItemType.KEY_FRAGMENT, player)); + playerRepository.save(player); + + character.setInteractedWith(true); + characterRepository.save(character); + + return new InteractionResponse(true, "Vyhrál jsi, dostáváš jeden fragment klíče."); + } else { + return new InteractionResponse(false, "Prohrál jsi"); + } + } + }); + }; + } +} \ No newline at end of file diff --git a/backend/src/main/java/cz/jzitnik/chronos/payload/errors/NotFoundError.java b/backend/src/main/java/cz/jzitnik/chronos/payload/errors/NotFoundError.java index f10e128..59b38ad 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/payload/errors/NotFoundError.java +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/errors/NotFoundError.java @@ -1,12 +1,7 @@ package cz.jzitnik.chronos.payload.errors; public class NotFoundError extends Error { - public NotFoundError() { - super("Not Found Error"); - } - public NotFoundError(String message) { super(message); } - } diff --git a/backend/src/main/java/cz/jzitnik/chronos/payload/errors/PlayerNotFoundError.java b/backend/src/main/java/cz/jzitnik/chronos/payload/errors/PlayerNotFoundError.java new file mode 100644 index 0000000..802e23e --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/errors/PlayerNotFoundError.java @@ -0,0 +1,7 @@ +package cz.jzitnik.chronos.payload.errors; + +public class PlayerNotFoundError extends Error { + public PlayerNotFoundError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/backend/src/main/java/cz/jzitnik/chronos/payload/requests/InteractionRequest.java b/backend/src/main/java/cz/jzitnik/chronos/payload/requests/InteractionRequest.java new file mode 100644 index 0000000..7f09c31 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/requests/InteractionRequest.java @@ -0,0 +1,13 @@ +package cz.jzitnik.chronos.payload.requests; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class InteractionRequest { + private String data; + private Long characterId; +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/payload/responses/InteractionResponse.java b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/InteractionResponse.java new file mode 100644 index 0000000..9a6824d --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/InteractionResponse.java @@ -0,0 +1,13 @@ +package cz.jzitnik.chronos.payload.responses; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class InteractionResponse { + private boolean success; + private String responseText; +} \ No newline at end of file diff --git a/backend/src/main/java/cz/jzitnik/chronos/payload/responses/TakeItemsResponse.java b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/TakeItemsResponse.java new file mode 100644 index 0000000..e30a806 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/TakeItemsResponse.java @@ -0,0 +1,6 @@ +package cz.jzitnik.chronos.payload.responses; + +public enum TakeItemsResponse { + DONE, + ALREADY_TAKEN +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/payload/responses/TestGameKeyResponse.java b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/TestGameKeyResponse.java new file mode 100644 index 0000000..393213f --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/TestGameKeyResponse.java @@ -0,0 +1,8 @@ +package cz.jzitnik.chronos.payload.responses; + +public enum TestGameKeyResponse { + WORKING, + GAME_STARTED, + INVALID_KEY, + MAXIUM_PLAYERS +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/payload/responses/UnifiedResponse.java b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/UnifiedResponse.java index 4404eee..60e077c 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/payload/responses/UnifiedResponse.java +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/UnifiedResponse.java @@ -5,14 +5,12 @@ import lombok.Setter; import java.util.Optional; +@Getter public class UnifiedResponse { - @Getter private final Optional data; - @Getter private final Optional error; - @Getter private final Boolean success; private UnifiedResponse(Optional data, Optional error, Boolean success) { diff --git a/backend/src/main/java/cz/jzitnik/chronos/repository/CharacterRepository.java b/backend/src/main/java/cz/jzitnik/chronos/repository/CharacterRepository.java new file mode 100644 index 0000000..e42f059 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/repository/CharacterRepository.java @@ -0,0 +1,11 @@ +package cz.jzitnik.chronos.repository; + +import cz.jzitnik.chronos.entities.Character; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + + +@Repository +public interface CharacterRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/repository/PlayerRepository.java b/backend/src/main/java/cz/jzitnik/chronos/repository/PlayerRepository.java index 927f953..61826b7 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/repository/PlayerRepository.java +++ b/backend/src/main/java/cz/jzitnik/chronos/repository/PlayerRepository.java @@ -4,7 +4,10 @@ import cz.jzitnik.chronos.entities.Player; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface PlayerRepository extends JpaRepository { boolean existsByPlayerKey(String playerKey); + Optional findByPlayerKey(String playerKey); } diff --git a/backend/src/main/java/cz/jzitnik/chronos/repository/RoomRepository.java b/backend/src/main/java/cz/jzitnik/chronos/repository/RoomRepository.java new file mode 100644 index 0000000..b18220b --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/repository/RoomRepository.java @@ -0,0 +1,11 @@ +package cz.jzitnik.chronos.repository; + +import cz.jzitnik.chronos.entities.Room; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + + +@Repository +public interface RoomRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/services/GameService.java b/backend/src/main/java/cz/jzitnik/chronos/services/GameService.java index 6e8d389..23bf20f 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/services/GameService.java +++ b/backend/src/main/java/cz/jzitnik/chronos/services/GameService.java @@ -2,6 +2,7 @@ package cz.jzitnik.chronos.services; import cz.jzitnik.chronos.entities.Game; import cz.jzitnik.chronos.entities.Player; +import cz.jzitnik.chronos.entities.Room; import cz.jzitnik.chronos.repository.GameRepository; import cz.jzitnik.chronos.repository.PlayerRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -59,7 +60,9 @@ public class GameService { Game newGame = new Game(); newGame.setGameKey(generateUniqueGameKey()); - initGameService.initGame(newGame); + var defaultRoom = initGameService.initGame(newGame); + + newGame.setDefaultRoom(defaultRoom); return gameRepository.save(newGame); } @@ -68,9 +71,21 @@ public class GameService { var player = new Player(playerName); player.setPlayerKey(generateUniquePlayerKey()); player.setGame(game); + player.setCurrentRoom(game.getDefaultRoom()); + + if (game.getPlayers().isEmpty()) { + game.setAdminPlayer(player); + } game.addPlayer(player); + gameRepository.save(game); + return player; } + + + public boolean isFromThisGame(Game game, Room room) { + return game.getRooms().stream().anyMatch(rm -> rm.equals(room)); + } } diff --git a/backend/src/main/java/cz/jzitnik/chronos/services/InitGameService.java b/backend/src/main/java/cz/jzitnik/chronos/services/InitGameService.java index a346592..1b3b809 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/services/InitGameService.java +++ b/backend/src/main/java/cz/jzitnik/chronos/services/InitGameService.java @@ -2,16 +2,16 @@ package cz.jzitnik.chronos.services; import cz.jzitnik.chronos.entities.*; import cz.jzitnik.chronos.entities.Character; +import cz.jzitnik.chronos.interactions.Interaction; import org.springframework.stereotype.Service; import java.util.ArrayList; @Service public class InitGameService { - public void initGame(Game game) { + public Room initGame(Game game) { var rooms = new ArrayList(); - // Outside var outside = new Room( "Outside", // Name @@ -48,5 +48,8 @@ public class InitGameService { rooms.add(stodola); game.setRooms(rooms); + + // Return a room that all players will spawn in + return outside; } } \ No newline at end of file diff --git a/backend/src/main/java/cz/jzitnik/chronos/utils/anotations/CheckUser.java b/backend/src/main/java/cz/jzitnik/chronos/utils/anotations/CheckUser.java new file mode 100644 index 0000000..468ca08 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/utils/anotations/CheckUser.java @@ -0,0 +1,11 @@ +package cz.jzitnik.chronos.utils.anotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CheckUser { +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/utils/anotations/UserCheckAspect.java b/backend/src/main/java/cz/jzitnik/chronos/utils/anotations/UserCheckAspect.java new file mode 100644 index 0000000..ec247b3 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/utils/anotations/UserCheckAspect.java @@ -0,0 +1,35 @@ +package cz.jzitnik.chronos.utils.anotations; + +import cz.jzitnik.chronos.payload.errors.PlayerNotFoundError; +import cz.jzitnik.chronos.payload.responses.UnifiedResponse; +import cz.jzitnik.chronos.repository.PlayerRepository; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class UserCheckAspect { + + @Autowired + private PlayerRepository playerRepository; + + @Around("@annotation(CheckUser)") + public Object checkUser(ProceedingJoinPoint joinPoint) throws Throwable { + String playerKey = (String) joinPoint.getArgs()[0]; // Assuming playerKey is the first argument + var playerOptional = playerRepository.findByPlayerKey(playerKey); + + if (playerOptional.isEmpty()) { + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(UnifiedResponse.failure(new PlayerNotFoundError("Player was not found!"))); + } + + // Proceed with the original method if the player exists + return joinPoint.proceed(); + } +} diff --git a/chronos/pom.xml b/chronos/pom.xml deleted file mode 100644 index 51a8a67..0000000 --- a/chronos/pom.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - 4.0.0 - - cz.jzitnik - chronos - 1.0-SNAPSHOT - - - 23 - 23 - UTF-8 - - - \ No newline at end of file diff --git a/chronos/src/main/java/cz/jzitnik/Main.java b/chronos/src/main/java/cz/jzitnik/Main.java deleted file mode 100644 index 65e9ffc..0000000 --- a/chronos/src/main/java/cz/jzitnik/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package cz.jzitnik; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello, World!"); - } -} \ No newline at end of file diff --git a/chronos/.gitignore b/frontend/.gitignore similarity index 100% rename from chronos/.gitignore rename to frontend/.gitignore diff --git a/chronos/.idea/.gitignore b/frontend/.idea/.gitignore similarity index 100% rename from chronos/.idea/.gitignore rename to frontend/.idea/.gitignore diff --git a/chronos/.idea/encodings.xml b/frontend/.idea/encodings.xml similarity index 100% rename from chronos/.idea/encodings.xml rename to frontend/.idea/encodings.xml diff --git a/chronos/.idea/misc.xml b/frontend/.idea/misc.xml similarity index 84% rename from chronos/.idea/misc.xml rename to frontend/.idea/misc.xml index 550fcd1..e122dea 100644 --- a/chronos/.idea/misc.xml +++ b/frontend/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/frontend/.idea/vcs.xml b/frontend/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/frontend/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/pom.xml b/frontend/pom.xml new file mode 100644 index 0000000..6a2fc3b --- /dev/null +++ b/frontend/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + cz.jzitnik + chronos + 1.0-SNAPSHOT + + + 23 + 23 + UTF-8 + 23 + + + + + com.squareup.retrofit2 + retrofit + 2.9.0 + + + + com.squareup.retrofit2 + converter-jackson + 2.9.0 + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.2 + + + + org.projectlombok + lombok + true + 1.18.36 + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + + \ No newline at end of file diff --git a/frontend/src/main/java/cz/jzitnik/Main.java b/frontend/src/main/java/cz/jzitnik/Main.java new file mode 100644 index 0000000..11ed3b0 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/Main.java @@ -0,0 +1,56 @@ +package cz.jzitnik; + +import cz.jzitnik.game.Chronos; +import cz.jzitnik.utils.Cli; + +import java.io.IOException; + +public class Main { + public static void main(String[] args) throws IOException { + Chronos chronos = new Chronos(); + + Cli.printHeader("Načítání hry Chronos"); + Cli.info("Načítání konfigurace..."); + + if (chronos.hasConfig()) { + chronos.loadData(); + Cli.success("Konfigurace byla úspěšně nalezena a načtena!"); + } else { + // Setup a server + Cli.info("Konfigurace nebyla nalezena! Vytvářím novou."); + chronos.setup(); + } + + if (!chronos.hasUser()) { + System.out.println(); + Cli.type("Nyní si buď vytvoříte vlastní hru a nebo se připojíte do již existující hry"); + System.out.println(); + + String[] options = {"Vytvořit novou hru", "Připojit se do již existující hry"}; + var option = Cli.selectOption(options); + + switch (option) { + case "Vytvořit novou hru": + chronos.createNewGame(); + chronos.addUser(); + Cli.success("Byl jste úspěšně zaregistrován jako první hráč!"); + chronos.adminPanel(); + break; + case "Připojit se do hry": + chronos.connectToExisting(); + chronos.addUser(); + Cli.success("Byl jste úspěšně zaregistrován jako hráč!"); + break; + default: + System.out.println("Error"); // This will never happen + } + } + + + chronos.waitForStart(); + Cli.info("Hra začala!"); + System.out.println("\n\n"); + Cli.printHeader("Chronos"); + chronos.visit(); + } +} \ No newline at end of file diff --git a/frontend/src/main/java/cz/jzitnik/api/ApiService.java b/frontend/src/main/java/cz/jzitnik/api/ApiService.java new file mode 100644 index 0000000..44cde47 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/ApiService.java @@ -0,0 +1,55 @@ +package cz.jzitnik.api; + +import cz.jzitnik.api.requests.PlayerNameRequest; +import cz.jzitnik.api.responses.UnifiedResponse; +import cz.jzitnik.api.types.Game; +import cz.jzitnik.api.types.Room; +import cz.jzitnik.api.types.TakeItemsResponse; +import cz.jzitnik.api.types.TestGameKeyResponse; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Query; + +public interface ApiService { + @GET("status") + Call> status(); + + @POST("game/new") + Call> newGame(); + + @POST("game/players/new") + Call> newPlayer( + @Query("gameKey") String gameKey, + @Body PlayerNameRequest requestBody + ); + + @POST("game/start") + Call> startGame( + @Query("playerKey") String playerKey, + @Query("gameKey") String gameKey + ); + + @GET("game/test") + Call> testGameKey( + @Query("gameKey") String gameKey + ); + + @GET("game/rooms/current_room") + Call> getCurrentRoom( + @Query("playerKey") String playerKey + ); + + @POST("game/rooms/move") + Call> moveToRoom( + @Query("playerKey") String playerKey, + @Query("roomId") Long roomId + ); + + @POST("game/characters/take_items") + Call> takeItems( + @Query("playerKey") String playerKey, + @Query("characterId") Long characterId + ); +} diff --git a/frontend/src/main/java/cz/jzitnik/api/requests/PlayerNameRequest.java b/frontend/src/main/java/cz/jzitnik/api/requests/PlayerNameRequest.java new file mode 100644 index 0000000..16d9555 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/requests/PlayerNameRequest.java @@ -0,0 +1,12 @@ +package cz.jzitnik.api.requests; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class PlayerNameRequest { + private String name; +} diff --git a/frontend/src/main/java/cz/jzitnik/api/responses/UnifiedResponse.java b/frontend/src/main/java/cz/jzitnik/api/responses/UnifiedResponse.java new file mode 100644 index 0000000..7850d3e --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/responses/UnifiedResponse.java @@ -0,0 +1,20 @@ +package cz.jzitnik.api.responses; + +import lombok.Getter; + +import java.util.Optional; + +@Getter +public class UnifiedResponse { + private Optional data; + private Optional error; + private Boolean success; + + public void setData(T data) { + this.data = Optional.ofNullable(data); + } + + public void setError(U error) { + this.error = Optional.ofNullable(error); + } +} diff --git a/frontend/src/main/java/cz/jzitnik/api/types/Character.java b/frontend/src/main/java/cz/jzitnik/api/types/Character.java new file mode 100644 index 0000000..09ab031 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/types/Character.java @@ -0,0 +1,24 @@ +package cz.jzitnik.api.types; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +public class Character { + private Long id; + + private String name; + + private List inventory; + + private Interaction interaction; + + private boolean interactedWith; + + private String dialog; +} diff --git a/frontend/src/main/java/cz/jzitnik/api/types/Game.java b/frontend/src/main/java/cz/jzitnik/api/types/Game.java new file mode 100644 index 0000000..7c0990e --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/types/Game.java @@ -0,0 +1,24 @@ +package cz.jzitnik.api.types; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class Game { + private Long id; + + private String gameKey; + + private List players; + + private Room defaultRoom; + + private boolean started; +} diff --git a/backend/src/main/java/cz/jzitnik/chronos/entities/Interaction.java b/frontend/src/main/java/cz/jzitnik/api/types/Interaction.java similarity index 51% rename from backend/src/main/java/cz/jzitnik/chronos/entities/Interaction.java rename to frontend/src/main/java/cz/jzitnik/api/types/Interaction.java index 65dff0a..319a1d2 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/entities/Interaction.java +++ b/frontend/src/main/java/cz/jzitnik/api/types/Interaction.java @@ -1,4 +1,4 @@ -package cz.jzitnik.chronos.entities; +package cz.jzitnik.api.types; public enum Interaction { Farmer diff --git a/frontend/src/main/java/cz/jzitnik/api/types/Item.java b/frontend/src/main/java/cz/jzitnik/api/types/Item.java new file mode 100644 index 0000000..7d64576 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/types/Item.java @@ -0,0 +1,12 @@ +package cz.jzitnik.api.types; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Item { + private Long id; + + private ItemType itemType; +} diff --git a/frontend/src/main/java/cz/jzitnik/api/types/ItemType.java b/frontend/src/main/java/cz/jzitnik/api/types/ItemType.java new file mode 100644 index 0000000..8451f0a --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/types/ItemType.java @@ -0,0 +1,6 @@ +package cz.jzitnik.api.types; + +public enum ItemType { + KEY_FRAGMENT, + LUCK_POTION +} diff --git a/frontend/src/main/java/cz/jzitnik/api/types/Player.java b/frontend/src/main/java/cz/jzitnik/api/types/Player.java new file mode 100644 index 0000000..78ec8dd --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/types/Player.java @@ -0,0 +1,18 @@ +package cz.jzitnik.api.types; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class Player { + private Long id; + + private String name; + + private List inventory; + + private Room currentRoom; +} diff --git a/frontend/src/main/java/cz/jzitnik/api/types/Room.java b/frontend/src/main/java/cz/jzitnik/api/types/Room.java new file mode 100644 index 0000000..8ecf2ba --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/types/Room.java @@ -0,0 +1,18 @@ +package cz.jzitnik.api.types; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class Room { + private Long id; + + private String name; + + private List items; + + private List characters; +} diff --git a/frontend/src/main/java/cz/jzitnik/api/types/TakeItemsResponse.java b/frontend/src/main/java/cz/jzitnik/api/types/TakeItemsResponse.java new file mode 100644 index 0000000..bf8a222 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/types/TakeItemsResponse.java @@ -0,0 +1,6 @@ +package cz.jzitnik.api.types; + +public enum TakeItemsResponse { + DONE, + ALREADY_TAKEN +} diff --git a/frontend/src/main/java/cz/jzitnik/api/types/TestGameKeyResponse.java b/frontend/src/main/java/cz/jzitnik/api/types/TestGameKeyResponse.java new file mode 100644 index 0000000..c2f9f75 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/types/TestGameKeyResponse.java @@ -0,0 +1,8 @@ +package cz.jzitnik.api.types; + +public enum TestGameKeyResponse { + WORKING, + GAME_STARTED, + INVALID_KEY, + MAXIUM_PLAYERS +} diff --git a/frontend/src/main/java/cz/jzitnik/game/Chronos.java b/frontend/src/main/java/cz/jzitnik/game/Chronos.java new file mode 100644 index 0000000..93e89ba --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/game/Chronos.java @@ -0,0 +1,243 @@ +package cz.jzitnik.game; + +import cz.jzitnik.api.ApiService; +import cz.jzitnik.api.requests.PlayerNameRequest; +import cz.jzitnik.api.types.Character; +import cz.jzitnik.api.types.Room; +import cz.jzitnik.api.types.TakeItemsResponse; +import cz.jzitnik.api.types.TestGameKeyResponse; +import cz.jzitnik.utils.Cli; +import cz.jzitnik.utils.ConfigPathProvider; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.io.Console; +import java.io.File; +import java.io.IOException; +import java.net.ConnectException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; + +import com.fasterxml.jackson.databind.ObjectMapper; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; +import retrofit2.http.GET; + +@NoArgsConstructor +@Getter +public class Chronos { + private static final String IPV4_PATTERN = + "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; + + private static boolean isValidIPv4(String ip) { + return ip.matches(IPV4_PATTERN); + } + + private LocalData localData = new LocalData(); + private ApiService apiService; + + public boolean hasConfig() { + var config_path = ConfigPathProvider.getPath(); + File file = new File(Path.of(config_path, "config.json").toString()); + + return file.exists(); + } + + public boolean hasUser() { + return localData.getUserSecret() != null; + } + + public void loadData() throws IOException { + var config_path = ConfigPathProvider.getPath(); + File file = new File(Path.of(config_path, "config.json").toString()); + String content = Files.readString(file.toPath()); + + ObjectMapper objectMapper = new ObjectMapper(); + + try { + this.localData = objectMapper.readValue(content, LocalData.class); + } catch (Exception e) { + e.printStackTrace(); + } + setupApi(localData.getServer()); + } + + private void setupApi(String baseUrl) { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(JacksonConverterFactory.create(new ObjectMapper())) + .build(); + + this.apiService = retrofit.create(ApiService.class); + } + + public void setup() throws IOException { + System.out.println("\n"); + Cli.type("Nyní se budeme muset připojit na Chronos server. Zadejte prosím IP adresu serveru na kterém běží chronos server.\nNapř: 127.0.0.1"); + Console console = System.console(); + + String ipaddr = null; + do { + ipaddr = console.readLine(ipaddr == null ? "Zadejte ip adresu serveru: " : "Zadejte platnou ip adresu serveru: "); + } while (!isValidIPv4(ipaddr)); + + Cli.info("Pokouším se připojit k serveru"); + + String baseUrl = "http://" + ipaddr + ":8080/"; + setupApi(baseUrl); + + try { + var response = apiService.status().execute().body(); + + if (response.getData().isEmpty() || !response.getData().get().equals("working")) { + Cli.error("Tento server pravděpodobně není Chronos server."); + setup(); + return; + } + } catch (ConnectException e) { + Cli.error("Nastala chyba při připojování k serveru, zkuste to znovu"); + setup(); + return; + } + + Cli.success("Server byl úspěšně nastaven"); + + this.localData.setServer(baseUrl); + this.localData.saveData(); + } + + public void createNewGame() throws IOException { + Cli.printHeader("Vytvoření hry"); + Cli.info("Vytvářím novou hru"); + var body = apiService.newGame().execute().body(); + + if (!Objects.requireNonNull(body).getSuccess()) { + System.err.println("Nastala chyba při vytváření hry!"); + System.exit(1); + } + + var data = body.getData().get(); + localData.setGameKey(data.getGameKey()); + + Cli.success("Byla úspěšně vytvořena hra"); + } + + public void addUser() throws IOException { + Cli.printHeader("Vytvoření hráče"); + Console console = System.console(); + + var name = console.readLine("Zadejte vaše jméno: "); + var newPlayerBody = apiService.newPlayer(localData.getGameKey(), new PlayerNameRequest(name)).execute().body(); + + if (!Objects.requireNonNull(newPlayerBody).getSuccess()) { + System.err.println("Nastala chyba při vytváření hráče!"); + System.exit(1); + } + + localData.setUserSecret(newPlayerBody.getData().get()); + } + + public void adminPanel() throws IOException { + Cli.printHeader("Čekání na připojení hráčů"); + System.out.println(); + Cli.center("Kód pro připojení:"); + System.out.print(Cli.Colors.GREEN); + Cli.center(localData.getGameKey()); + System.out.print(Cli.Colors.RESET); + Cli.center("(pomocí tohoto kódu se hráči můžou napojit)"); + System.out.println("\nPo startu hry už se nemůžou napojit noví hráči!"); + System.out.println("Stiskněte " + Cli.Colors.CYAN + "Enter" + Cli.Colors.RESET + " pro start hry..."); + System.console().readLine(); + + localData.saveData(); + apiService.startGame(localData.getUserSecret(), localData.getGameKey()).execute(); + } + + public void connectToExisting() throws IOException { + Console console = System.console(); + Cli.printHeader("Připojení do hry"); + System.out.println(); + String code = console.readLine("Zadejte kod pro připojení: "); + + var testGameKeyResponse = apiService.testGameKey(code).execute().body().getData().get(); + + if (testGameKeyResponse == TestGameKeyResponse.INVALID_KEY) { + Cli.error("Nebyla nalezena hra s tímto kódem!"); + connectToExisting(); + return; + } + + if (testGameKeyResponse == TestGameKeyResponse.GAME_STARTED) { + Cli.error("Tato hra již začala!"); + connectToExisting(); + return; + } + + if (testGameKeyResponse == TestGameKeyResponse.MAXIUM_PLAYERS) { + Cli.error("V této hře je již maximální počet hráčů!"); + connectToExisting(); + return; + } + + localData.setGameKey(code); + } + + public void waitForStart() throws IOException { + boolean started = false; + while (!started) { + var res = apiService.testGameKey(localData.getGameKey()).execute(); + var data = res.body().getData().get(); + + started = data == TestGameKeyResponse.GAME_STARTED; + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + public void visit() throws IOException { + var res = apiService.getCurrentRoom(localData.getUserSecret()).execute(); + var room = res.body().getData().get(); + visit(room, false); + } + public void visit(Room room) throws IOException { + visit(room, true); + } + public void visit(Room room, boolean changeRoom) throws IOException { + if (changeRoom) { + apiService.moveToRoom(localData.getUserSecret(), room.getId()); + } + + Cli.info("Nyní se nacházíte v místnosti " + room.getName()); + + if (room.getCharacters().isEmpty()) { + Cli.type("V místnosti je prázno... Ani jedna duše."); + } + for (Character character : room.getCharacters()) { + talkWith(character); + } + } + + + public void talkWith(Character character) throws IOException { + Cli.type("V místnosti je " + character.getName()); + System.out.println(); + Cli.type(Cli.Colors.YELLOW + character.getName() + ": " + Cli.Colors.RESET + character.getDialog()); + + if (!character.getInventory().isEmpty()) { + var res = apiService.takeItems(getLocalData().getUserSecret(), character.getId()).execute(); + var body = res.body().getData().get(); + + if (body == TakeItemsResponse.ALREADY_TAKEN) { + Cli.type(Cli.Colors.YELLOW + character.getName() + ": " + Cli.Colors.RESET + "Už jsem ti mé itemy dal. Už pro tebe nic nemám."); + } else { + var characters = character.getInventory().stream().map(item -> item.getItemType().toString()).toList(); + Cli.info("Dostal jste: " + String.join(", ", characters)); + } + } + } +} \ No newline at end of file diff --git a/frontend/src/main/java/cz/jzitnik/game/LocalData.java b/frontend/src/main/java/cz/jzitnik/game/LocalData.java new file mode 100644 index 0000000..fad4d53 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/game/LocalData.java @@ -0,0 +1,41 @@ +package cz.jzitnik.game; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import cz.jzitnik.utils.ConfigPathProvider; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +@Getter +@Setter +@NoArgsConstructor +public class LocalData { + private String server; + private String userSecret; + private String gameKey; + + public void saveData() { + ObjectMapper objectMapper = new ObjectMapper(); + try { + String configPath = ConfigPathProvider.getPath(); + File file = new File(Path.of(configPath, "config.json").toString()); + + File parentDir = file.getParentFile(); + if (!parentDir.exists()) { + parentDir.mkdirs(); + } + + objectMapper.writeValue(file, this); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/frontend/src/main/java/cz/jzitnik/utils/Cli.java b/frontend/src/main/java/cz/jzitnik/utils/Cli.java new file mode 100644 index 0000000..854ca9d --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/utils/Cli.java @@ -0,0 +1,172 @@ +package cz.jzitnik.utils; + +import java.util.Scanner; + +public class Cli { + public static class Colors { + public static final String RESET = "\033[0m"; // ANSI escape code to reset the terminal + + // Text colors + public static final String BLACK = "\033[0;30m"; + public static final String RED = "\033[0;31m"; + public static final String GREEN = "\033[0;32m"; + public static final String YELLOW = "\033[0;33m"; + public static final String BLUE = "\033[0;34m"; + public static final String PURPLE = "\033[0;35m"; + public static final String CYAN = "\033[0;36m"; + public static final String WHITE = "\033[0;37m"; + + // Bold text colors (in bold) + public static final String BOLD_BLACK = "\033[1;30m"; + public static final String BOLD_RED = "\033[1;31m"; + public static final String BOLD_GREEN = "\033[1;32m"; + public static final String BOLD_YELLOW = "\033[1;33m"; + public static final String BOLD_BLUE = "\033[1;34m"; + public static final String BOLD_PURPLE = "\033[1;35m"; + public static final String BOLD_CYAN = "\033[1;36m"; + public static final String BOLD_WHITE = "\033[1;37m"; + + // Background colors + public static final String BLACK_BACKGROUND = "\033[40m"; + public static final String RED_BACKGROUND = "\033[41m"; + public static final String GREEN_BACKGROUND = "\033[42m"; + public static final String YELLOW_BACKGROUND = "\033[43m"; + public static final String BLUE_BACKGROUND = "\033[44m"; + public static final String PURPLE_BACKGROUND = "\033[45m"; + public static final String CYAN_BACKGROUND = "\033[46m"; + public static final String WHITE_BACKGROUND = "\033[47m"; + + // Bold background colors + public static final String BOLD_BLACK_BACKGROUND = "\033[1;40m"; + public static final String BOLD_RED_BACKGROUND = "\033[1;41m"; + public static final String BOLD_GREEN_BACKGROUND = "\033[1;42m"; + public static final String BOLD_YELLOW_BACKGROUND = "\033[1;43m"; + public static final String BOLD_BLUE_BACKGROUND = "\033[1;44m"; + public static final String BOLD_PURPLE_BACKGROUND = "\033[1;45m"; + public static final String BOLD_CYAN_BACKGROUND = "\033[1;46m"; + public static final String BOLD_WHITE_BACKGROUND = "\033[1;47m"; + } + + public static void printHeader(String text) { + int totalWidth = 100; + int padding = (totalWidth - text.length()) / 2; + + StringBuilder sb = new StringBuilder(totalWidth); + for (int i = 0; i < padding; i++) { + sb.append("="); + } + sb.append(" "); + sb.append(text); + sb.append(" "); + for (int i = 0; i < padding; i++) { + sb.append("="); + } + + while (sb.length() < totalWidth) { + sb.insert(0, "="); + } + + System.out.println(); + System.out.println(sb); + } + + public static void center(String text) { + int totalWidth = 100; + int padding = (totalWidth - text.length()) / 2; + + StringBuilder sb = new StringBuilder(totalWidth); + for (int i = 0; i < padding; i++) { + sb.append(" "); + } + sb.append(text); + for (int i = 0; i < padding; i++) { + sb.append(" "); + } + + while (sb.length() < totalWidth) { + sb.insert(0, " "); + } + + System.out.println(sb); + } + + public static String wrapText(String text) { + return wrapText(text, 0); + } + + public static String wrapText(String text, int charsRemove) { + StringBuilder wrappedText = new StringBuilder(); + int line = 0; + + var words = text.split(" "); + + for (var word : words) { + if ((line + word.length() + 1) > (100 - charsRemove)) { + wrappedText.append("\n"); + line = 0; + } + line += word.length() + 1; + wrappedText.append(word).append(" "); + } + + return wrappedText.toString(); + } + + public static void typeText(String text) { + for (int i = 0; i < text.length(); i++) { + System.out.print(text.charAt(i)); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + System.out.println(); + } + + public static void type(String text) { + Cli.typeText(Cli.wrapText(text)); + } + + public static void info(String text) { + System.out.println(Colors.BLUE + "Info: " + Colors.RESET + Cli.wrapText(text, 6)); + } + public static void success(String text) { + System.out.println(Colors.GREEN + "Úspěch: " + Colors.RESET + Cli.wrapText(text, 8)); + } + public static void error(String text) { + System.out.println(Colors.RED + "Chyba: " + Colors.RESET + Cli.wrapText(text, 7)); + } + + + /** + * Displays a menu with the provided options and returns the selected option. + * + * @param options Array of options to display to the user. + * @return The selected option as a String. + */ + public static String selectOption(String[] options) { + Scanner scanner = new Scanner(System.in); + int choice; + + System.out.println("Vyberte možnost:"); + for (int i = 0; i < options.length; i++) { + System.out.println((i + 1) + ". " + options[i]); + } + + while (true) { + System.out.print("Vložte číslo možnosti: "); + if (scanner.hasNextInt()) { + choice = scanner.nextInt(); + if (choice >= 1 && choice <= options.length) { + return options[choice - 1]; + } else { + System.out.println("Neplatní možnost, vyberte číslo mezi 1 a " + options.length); + } + } else { + System.out.println("Neplatný vstup, vložte číslo."); + scanner.next(); + } + } + } +} diff --git a/frontend/src/main/java/cz/jzitnik/utils/ConfigPathProvider.java b/frontend/src/main/java/cz/jzitnik/utils/ConfigPathProvider.java new file mode 100644 index 0000000..3367a92 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/utils/ConfigPathProvider.java @@ -0,0 +1,19 @@ +package cz.jzitnik.utils; + +public class ConfigPathProvider { + public static String getPath() { + String configDir; + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + configDir = System.getenv("APPDATA") + "\\Chronos"; + } else if (os.contains("nix") || os.contains("nux")) { + configDir = System.getProperty("user.home") + "/.config/Chronos"; + } else if (os.contains("mac")) { + configDir = System.getProperty("user.home") + "/Library/Application Support/Chronos"; + } else { + configDir = System.getProperty("user.home") + "/Chronos"; + } + + return configDir; + } +}