diff --git a/backend/src/main/java/cz/jzitnik/chronos/controllers/CharacterController.java b/backend/src/main/java/cz/jzitnik/chronos/controllers/CharacterController.java index 4e90c16..0fd4ac4 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/controllers/CharacterController.java +++ b/backend/src/main/java/cz/jzitnik/chronos/controllers/CharacterController.java @@ -1,5 +1,6 @@ package cz.jzitnik.chronos.controllers; +import cz.jzitnik.chronos.entities.Interaction; import cz.jzitnik.chronos.entities.Item; import cz.jzitnik.chronos.interactions.InteractionService; import cz.jzitnik.chronos.payload.requests.InteractionRequest; @@ -27,6 +28,27 @@ public class CharacterController { @Autowired private CharacterRepository characterRepository; + @GetMapping("/interaction") + @CheckUser + public ResponseEntity> getInteractionData(@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)); + } + if (!characterOptional.get().getRoom().equals(player.getCurrentRoom())) { + return ResponseEntity.badRequest().body(UnifiedResponse.failure(null)); + } + + var interaction = characterOptional.get().getInteractionData(); + + return ResponseEntity.ok( + UnifiedResponse.success(interaction) + ); + } + @PostMapping("/interact") @CheckUser public ResponseEntity> interact(@RequestParam String playerKey, @RequestBody InteractionRequest interactionRequest) { 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 bd0fca3..26dc96c 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/controllers/PlayerController.java +++ b/backend/src/main/java/cz/jzitnik/chronos/controllers/PlayerController.java @@ -1,11 +1,15 @@ package cz.jzitnik.chronos.controllers; +import cz.jzitnik.chronos.entities.Player; +import cz.jzitnik.chronos.payload.errors.ItemNotUsableException; import cz.jzitnik.chronos.payload.errors.NotFoundError; import cz.jzitnik.chronos.payload.requests.PlayerNameRequest; import cz.jzitnik.chronos.payload.responses.UnifiedResponse; import cz.jzitnik.chronos.repository.GameRepository; +import cz.jzitnik.chronos.repository.ItemRepository; 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; @@ -24,6 +28,9 @@ public class PlayerController { @Autowired private GameService gameService; + @Autowired + private ItemRepository itemRepository; + @PostMapping("/new") public ResponseEntity> newPlayer(@RequestParam String gameKey, @RequestBody PlayerNameRequest playerNameRequest) { var gameOptional = gameRepository.findByGameKey(gameKey); @@ -42,4 +49,42 @@ public class PlayerController { UnifiedResponse.success(player.getPlayerKey()) ); } + + @GetMapping("/me") + @CheckUser + public ResponseEntity> getMe(@RequestParam String playerKey) { + var player = playerRepository.findByPlayerKey(playerKey).get(); + + return ResponseEntity.ok(UnifiedResponse.success(player)); + } + + @PostMapping("/use_item") + @CheckUser + public ResponseEntity> useItem(@RequestParam String playerKey, @RequestParam Long itemId) { + var player = playerRepository.findByPlayerKey(playerKey).get(); + var itemOptional = itemRepository.findById(itemId); + + if (itemOptional.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(UnifiedResponse.failure(new NotFoundError("Item was not found!"))); + } + + var item = itemOptional.get(); + + if (!player.getInventory().contains(item)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(UnifiedResponse.failure(new Error("You do not own this item!"))); + } + + // Use the item + try { + item.getItemType().useItem(player); + } catch (ItemNotUsableException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(UnifiedResponse.failure(e.toError())); + } + + playerRepository.save(player); + + itemRepository.delete(item); + + return ResponseEntity.ok(UnifiedResponse.success(null)); + } } diff --git a/backend/src/main/java/cz/jzitnik/chronos/entities/Interaction.java b/backend/src/main/java/cz/jzitnik/chronos/entities/Interaction.java index f0bbf4f..d2ba7fd 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/entities/Interaction.java +++ b/backend/src/main/java/cz/jzitnik/chronos/entities/Interaction.java @@ -2,6 +2,7 @@ package cz.jzitnik.chronos.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -24,4 +25,10 @@ public class Interaction { @OneToOne(mappedBy = "interactionData") @JsonIgnore private Character character; + + public Interaction(String startText, String interactedWithText, Character character) { + this.startText = startText; + this.interactedWithText = interactedWithText; + this.character = character; + } } diff --git a/backend/src/main/java/cz/jzitnik/chronos/entities/ItemType.java b/backend/src/main/java/cz/jzitnik/chronos/entities/ItemType.java index 9a311db..3085752 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/entities/ItemType.java +++ b/backend/src/main/java/cz/jzitnik/chronos/entities/ItemType.java @@ -1,6 +1,17 @@ package cz.jzitnik.chronos.entities; +import cz.jzitnik.chronos.payload.errors.ItemNotUsableException; + public enum ItemType { KEY_FRAGMENT, - LUCK_POTION + LUCK_POTION; + + public void useItem(Player player) throws ItemNotUsableException { + switch (this) { + case LUCK_POTION -> { + player.setLuck(true); + } + default -> throw new ItemNotUsableException("Item " + this + " is not usable"); + } + } } diff --git a/backend/src/main/java/cz/jzitnik/chronos/interactions/InteractionService.java b/backend/src/main/java/cz/jzitnik/chronos/interactions/InteractionService.java index 64e2706..714cfd2 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/interactions/InteractionService.java +++ b/backend/src/main/java/cz/jzitnik/chronos/interactions/InteractionService.java @@ -10,6 +10,8 @@ import cz.jzitnik.chronos.repository.PlayerRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.Optional; import java.util.Random; @Service @@ -48,30 +50,42 @@ public class InteractionService { player.setLuck(false); if (playerWins) { - player.addItem(new Item(ItemType.KEY_FRAGMENT, player)); + var item = new Item(ItemType.KEY_FRAGMENT, player); + player.addItem(item); playerRepository.save(player); character.setInteractedWith(true); characterRepository.save(character); - return new InteractionResponse(true, "Vyhrál jsi, dostáváš jeden fragment klíče."); + var items = new ArrayList(); + items.add(item); + + return new InteractionResponse(true, "Vyhrál jsi, dostáváš ode mě jeden fragment klíče.", items); } else { - return new InteractionResponse(false, "Prohrál jsi"); + character.setInteractedWith(true); + characterRepository.save(character); + return new InteractionResponse(false, "Prohrál jsi. Žádný fragment klíče ode mě už nedostaneš. Zkus to u někoho jiného.", new ArrayList<>()); } } 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."); + return new InteractionResponse(false, "Remíza. Jestli chceš můžeš si se mnou zahrát ještě jednou.", new ArrayList<>()); } else if ((userChoice == 0 && characterChoice == 2) || (userChoice == 1 && characterChoice == 0) || (userChoice == 2 && characterChoice == 1)) { - player.addItem(new Item(ItemType.KEY_FRAGMENT, player)); + var item = new Item(ItemType.KEY_FRAGMENT, player); + player.addItem(item); playerRepository.save(player); character.setInteractedWith(true); characterRepository.save(character); - return new InteractionResponse(true, "Vyhrál jsi, dostáváš jeden fragment klíče."); + var items = new ArrayList(); + items.add(item); + + return new InteractionResponse(true, "Vyhrál jsi, dostáváš ode mě jeden fragment klíče.", items); } else { - return new InteractionResponse(false, "Prohrál jsi"); + character.setInteractedWith(true); + characterRepository.save(character); + return new InteractionResponse(false, "Prohrál jsi. Žádný fragment klíče ode mě už nedostaneš. Zkus to u někoho jiného.", new ArrayList<>()); } } }); diff --git a/backend/src/main/java/cz/jzitnik/chronos/payload/errors/ItemNotUsableException.java b/backend/src/main/java/cz/jzitnik/chronos/payload/errors/ItemNotUsableException.java new file mode 100644 index 0000000..4befcc1 --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/errors/ItemNotUsableException.java @@ -0,0 +1,17 @@ +package cz.jzitnik.chronos.payload.errors; + +public class ItemNotUsableException extends Exception { + public static class ItemNotUsableError extends Error { + public ItemNotUsableError (String message) { + super(message); + } + } + + public ItemNotUsableException(String message) { + super(message); + } + + public ItemNotUsableError toError() { + return new ItemNotUsableError(super.getMessage()); + } +} 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 index 9a6824d..0e5503b 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/payload/responses/InteractionResponse.java +++ b/backend/src/main/java/cz/jzitnik/chronos/payload/responses/InteractionResponse.java @@ -1,13 +1,17 @@ package cz.jzitnik.chronos.payload.responses; +import cz.jzitnik.chronos.entities.Item; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import java.util.List; + @Getter @Setter @AllArgsConstructor public class InteractionResponse { private boolean success; private String responseText; + private List items; } \ No newline at end of file diff --git a/backend/src/main/java/cz/jzitnik/chronos/repository/ItemRepository.java b/backend/src/main/java/cz/jzitnik/chronos/repository/ItemRepository.java new file mode 100644 index 0000000..c32790e --- /dev/null +++ b/backend/src/main/java/cz/jzitnik/chronos/repository/ItemRepository.java @@ -0,0 +1,10 @@ +package cz.jzitnik.chronos.repository; + +import cz.jzitnik.chronos.entities.Item; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ItemRepository extends JpaRepository { + +} 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 1b3b809..7967c2e 100644 --- a/backend/src/main/java/cz/jzitnik/chronos/services/InitGameService.java +++ b/backend/src/main/java/cz/jzitnik/chronos/services/InitGameService.java @@ -38,9 +38,14 @@ public class InitGameService { var farmar = new Character( "Farmář", stodola, - "Ahoj já jsem farmář a budeš se mnou hrát kámen nůžky papír." + "Ahoj já jsem farmář a budeš se mnou hrát kámen nůžky papír. Dialog doplnit." ); farmar.setInteraction(Interaction.Farmer); + farmar.setInteractionData(new cz.jzitnik.chronos.entities.Interaction( + "Tak si zahrajeme kámen nůžky papír", + "Se mnou jsi již hrál kámen nůžky papír. Znovu už ti fragment klíče nedám", + farmar + )); stodola_characters.add(farmar); stodola.setCharacters(stodola_characters); diff --git a/frontend/src/main/java/cz/jzitnik/Main.java b/frontend/src/main/java/cz/jzitnik/Main.java index 11ed3b0..513c590 100644 --- a/frontend/src/main/java/cz/jzitnik/Main.java +++ b/frontend/src/main/java/cz/jzitnik/Main.java @@ -46,7 +46,7 @@ public class Main { } } - + Cli.info("Prosím počkejte..."); chronos.waitForStart(); Cli.info("Hra začala!"); System.out.println("\n\n"); diff --git a/frontend/src/main/java/cz/jzitnik/api/ApiService.java b/frontend/src/main/java/cz/jzitnik/api/ApiService.java index 63d1718..b2f506b 100644 --- a/frontend/src/main/java/cz/jzitnik/api/ApiService.java +++ b/frontend/src/main/java/cz/jzitnik/api/ApiService.java @@ -1,11 +1,10 @@ package cz.jzitnik.api; +import cz.jzitnik.api.requests.InteractionRequest; import cz.jzitnik.api.requests.PlayerNameRequest; +import cz.jzitnik.api.responses.InteractionResponse; 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 cz.jzitnik.api.types.*; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.GET; @@ -27,6 +26,17 @@ public interface ApiService { @Body PlayerNameRequest requestBody ); + @GET("game/players/me") + Call> getMe( + @Query("playerKey") String playerKey + ); + + @POST("game/players/use_item") + Call> useItem( + @Query("playerKey") String playerKey, + @Query("itemId") Long itemId + ); + @POST("game/start") Call> startGame( @Query("playerKey") String playerKey, @@ -59,4 +69,16 @@ public interface ApiService { @Query("playerKey") String playerKey, @Query("characterId") Long characterId ); + + @GET("game/characters/interaction") + Call> getInteractionData( + @Query("playerKey") String playerKey, + @Query("characterId") Long characterId + ); + + @POST("game/characters/interact") + Call> interact( + @Query("playerKey") String playerKey, + @Body InteractionRequest interactionRequest + ); } diff --git a/frontend/src/main/java/cz/jzitnik/api/requests/InteractionRequest.java b/frontend/src/main/java/cz/jzitnik/api/requests/InteractionRequest.java new file mode 100644 index 0000000..fabaeda --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/requests/InteractionRequest.java @@ -0,0 +1,13 @@ +package cz.jzitnik.api.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/frontend/src/main/java/cz/jzitnik/api/responses/InteractionResponse.java b/frontend/src/main/java/cz/jzitnik/api/responses/InteractionResponse.java new file mode 100644 index 0000000..79de247 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/api/responses/InteractionResponse.java @@ -0,0 +1,17 @@ +package cz.jzitnik.api.responses; + +import cz.jzitnik.api.types.Item; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class InteractionResponse { + private boolean success; + private String responseText; + private List items; +} diff --git a/frontend/src/main/java/cz/jzitnik/api/types/Item.java b/frontend/src/main/java/cz/jzitnik/api/types/Item.java index 7d64576..cb9deb5 100644 --- a/frontend/src/main/java/cz/jzitnik/api/types/Item.java +++ b/frontend/src/main/java/cz/jzitnik/api/types/Item.java @@ -9,4 +9,9 @@ public class Item { private Long id; private ItemType itemType; + + @Override + public String toString() { + return itemType.toString(); + } } diff --git a/frontend/src/main/java/cz/jzitnik/api/types/ItemType.java b/frontend/src/main/java/cz/jzitnik/api/types/ItemType.java index 8451f0a..5178ee8 100644 --- a/frontend/src/main/java/cz/jzitnik/api/types/ItemType.java +++ b/frontend/src/main/java/cz/jzitnik/api/types/ItemType.java @@ -1,6 +1,27 @@ package cz.jzitnik.api.types; +import java.util.HashSet; +import java.util.Set; + public enum ItemType { KEY_FRAGMENT, - LUCK_POTION + LUCK_POTION; + + private static final Set usableItems = new HashSet<>(); + + static { + usableItems.add(ItemType.LUCK_POTION); + } + + public boolean isUsable() { + return usableItems.contains(this); + } + + @Override + public String toString() { + return switch (this) { + case LUCK_POTION -> "Lektvar štěstí"; + case KEY_FRAGMENT -> "Fragment klíče"; + }; + } } diff --git a/frontend/src/main/java/cz/jzitnik/api/types/Player.java b/frontend/src/main/java/cz/jzitnik/api/types/Player.java index 78ec8dd..102dcc7 100644 --- a/frontend/src/main/java/cz/jzitnik/api/types/Player.java +++ b/frontend/src/main/java/cz/jzitnik/api/types/Player.java @@ -1,5 +1,6 @@ package cz.jzitnik.api.types; +import cz.jzitnik.utils.Cli; import lombok.Getter; import lombok.Setter; @@ -15,4 +16,30 @@ public class Player { private List inventory; private Room currentRoom; + + private boolean luck; + + @Override + public String toString() { + String sb = "Hráč: " + + Cli.Colors.BLUE + + name + + Cli.Colors.RESET + + "\n\n" + + "Vlastnosti:\n" + + "Id: " + + id + + "\n" + + "Štěstí: " + + (luck ? Cli.Colors.GREEN : Cli.Colors.RED) + + (luck ? "Ano" : "Ne") + + Cli.Colors.RESET + + "\n" + + "Místnost: " + + Cli.Colors.BLUE + + currentRoom + + Cli.Colors.RESET; + + return sb; + } } diff --git a/frontend/src/main/java/cz/jzitnik/api/types/Room.java b/frontend/src/main/java/cz/jzitnik/api/types/Room.java index 8ecf2ba..870ee04 100644 --- a/frontend/src/main/java/cz/jzitnik/api/types/Room.java +++ b/frontend/src/main/java/cz/jzitnik/api/types/Room.java @@ -15,4 +15,13 @@ public class Room { private List items; private List characters; + + @Override + public String toString() { + if (name.equals("Outside")) { + return "Venek"; + } + + return name; + } } diff --git a/frontend/src/main/java/cz/jzitnik/game/Chronos.java b/frontend/src/main/java/cz/jzitnik/game/Chronos.java index 8a01649..dce6e50 100644 --- a/frontend/src/main/java/cz/jzitnik/game/Chronos.java +++ b/frontend/src/main/java/cz/jzitnik/game/Chronos.java @@ -2,10 +2,8 @@ package cz.jzitnik.game; import cz.jzitnik.api.ApiService; import cz.jzitnik.api.requests.PlayerNameRequest; +import cz.jzitnik.api.types.*; 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.game.interactions.Interactions; import cz.jzitnik.utils.Cli; import cz.jzitnik.utils.ConfigPathProvider; @@ -220,15 +218,11 @@ public class Chronos { Cli.info("Nyní se nacházíte v místnosti " + room.getName()); } - if (room.getCharacters().isEmpty()) { + var characters = room.getCharacters(); + if (characters.isEmpty()) { Cli.type("V místnosti je prázno... Ani jedna duše."); - } - for (Character character : room.getCharacters()) { - talkWith(character); - } - - if (!room.getCharacters().isEmpty()) { - interactWithAny(room.getCharacters()); + } else { + talk(characters); } var responseRooms = apiService.getAllRooms(localData.getUserSecret()).execute(); @@ -237,46 +231,26 @@ public class Chronos { System.out.println(); Cli.info("Nyni si vyberte do jaké místnosti chcete jít."); - int selectedIndex = Cli.selectOptionIndex(rooms.stream().map(Room::getName).toList()); + int selectedIndex = Cli.selectOptionIndex(rooms.stream().map(Room::toString).toList()); visit(rooms.get(selectedIndex).getId(), true); } - private void interactWithAny(List characters) throws IOException { - if (characters.stream().anyMatch(character -> character.getInteraction() != null)) { - List interactablePostavy = characters.stream().filter( - character -> character.getInteraction() != null && - !character.isInteractedWith() - ).toList(); + public void talk(List characters) throws IOException { + List commands = new ArrayList<>(characters.stream().map(chachar -> "Promluvit si s " + chachar.getName()).toList()); + commands.add("Přejít do jiné místnosti"); + CommandPalette commandPalette = new CommandPalette(commands, apiService, localData.getUserSecret()); + int selectedIndex = commandPalette.displayIndex(); - if (interactablePostavy.isEmpty()) { - return; - } - - List options = new ArrayList<>(interactablePostavy.stream().map(Character::getName).toList()); - options.add("Neinteragovat s nikým"); - - Cli.info("Můžete interagovat s následujícími postavami:"); - int selectedIndex = Cli.selectOptionIndex(options); - - if (selectedIndex == options.size() - 1) { - return; - } - - var character = characters.get(selectedIndex); - - Interactions.runInteraction(character); - - var currentRoom = apiService.getCurrentRoom(localData.getUserSecret()).execute().body().getData(); - - interactWithAny(currentRoom.get().getCharacters().stream().filter(chachar -> !chachar.isInteractedWith()).toList()); + if (selectedIndex == commands.size() - 1) { + // Přejít do jiné místnosti + return; } - } - public void talkWith(Character character) throws IOException { - Cli.type("V místnosti je " + character.getName()); + // Dangerous I would say but whatever + Character character = characters.get(selectedIndex); System.out.println(); - Cli.type(Cli.Colors.YELLOW + character.getName() + ": " + Cli.Colors.RESET + character.getDialog()); + Cli.type(character, character.getDialog()); if (!character.getInventory().isEmpty()) { var res = apiService.takeItems(getLocalData().getUserSecret(), character.getId()).execute(); @@ -285,9 +259,19 @@ public class Chronos { 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)); + Cli.gotItems(character.getInventory()); } } + + // Interact with the character if it is interactable + if (character.getInteraction() != null) { + Interactions.runInteraction(character, apiService, localData.getUserSecret()); + } + + // Refetch the characters + var res = apiService.getCurrentRoom(localData.getUserSecret()).execute(); + var room = res.body().getData().get(); + + talk(room.getCharacters()); } } \ No newline at end of file diff --git a/frontend/src/main/java/cz/jzitnik/game/CommandPalette.java b/frontend/src/main/java/cz/jzitnik/game/CommandPalette.java new file mode 100644 index 0000000..9b51cd9 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/game/CommandPalette.java @@ -0,0 +1,99 @@ +package cz.jzitnik.game; + +import cz.jzitnik.api.ApiService; +import cz.jzitnik.utils.Cli; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +public class CommandPalette { + public enum CommandType { + CUSTOM, + INVENTORY_OPEN, + USER_PROFILE, + EXIT, + } + + @Getter + @Setter + @AllArgsConstructor + public static class Command { + private String name; + private CommandType commandType; + + public Command(String name) { + this.name = name; + this.commandType = CommandType.CUSTOM; + } + + @Override + public String toString() { + if (commandType == CommandType.CUSTOM) { + return name; + } + + return Cli.Colors.BLUE + name + Cli.Colors.RESET; + } + } + + private List commands; + private ApiService apiService; + private String playerKey; + + public CommandPalette(List commands, ApiService apiService, String playerKey) { + this.commands = commands.stream().map(Command::new).toList(); + this.apiService = apiService; + this.playerKey = playerKey; + } + + public int displayIndex() throws IOException { + List allCommands = new ArrayList<>(commands); + + // Command that can be used anywhere + allCommands.add(new Command("Otevřít inventář", CommandType.INVENTORY_OPEN)); + allCommands.add(new Command("Otevřít profil uživatele", CommandType.USER_PROFILE)); + allCommands.add(new Command("Odejít ze hry", CommandType.EXIT)); + + System.out.println("\n"); + var selectedOption = Cli.selectOptionIndex(allCommands.stream().map(Command::toString).toList()); + + var command = allCommands.get(selectedOption); + + return switch (command.getCommandType()) { + case CUSTOM -> selectedOption; + case EXIT -> { + System.out.println("Exiting game..."); + System.exit(0); + + yield 0; + } + case INVENTORY_OPEN -> { + var response = apiService.getMe(playerKey).execute(); + assert response.body() != null; + var body = response.body().getData(); + var me = body.get(); + + var inventory = new Inventory(me, apiService, playerKey); + inventory.display(); + + yield displayIndex(); + } + case USER_PROFILE -> { + var response = apiService.getMe(playerKey).execute(); + assert response.body() != null; + var body = response.body().getData(); + var me = body.get(); + + System.out.println("\n\n" + me); + + yield displayIndex(); + } + }; + } +} diff --git a/frontend/src/main/java/cz/jzitnik/game/Inventory.java b/frontend/src/main/java/cz/jzitnik/game/Inventory.java new file mode 100644 index 0000000..2774a74 --- /dev/null +++ b/frontend/src/main/java/cz/jzitnik/game/Inventory.java @@ -0,0 +1,69 @@ +package cz.jzitnik.game; + +import cz.jzitnik.api.ApiService; +import cz.jzitnik.api.types.Item; +import cz.jzitnik.api.types.ItemType; +import cz.jzitnik.api.types.Player; +import cz.jzitnik.utils.Cli; +import lombok.AllArgsConstructor; + +import java.io.IOException; +import java.util.*; + +@AllArgsConstructor +public class Inventory { + private Player player; + private ApiService apiService; + private String playerKey; + + + public void display() throws IOException { + System.out.println(); + Cli.info("Váš inventář:"); + + // Create a map to group items by ItemType and count occurrences + Map itemCounts = new HashMap<>(); + List usableItemList = new ArrayList<>(); + + for (Item item : player.getInventory()) { + ItemType itemType = item.getItemType(); + + // If the item is usable, add it to the usableItemList + if (itemType.isUsable()) { + usableItemList.add(item); + } else { + // Otherwise, count it in the itemCounts map + itemCounts.put(itemType, itemCounts.getOrDefault(itemType, 0) + 1); + } + } + + // Display grouped non-usable items + for (Map.Entry entry : itemCounts.entrySet()) { + System.out.println(entry.getValue() + "x " + entry.getKey()); + } + + // Display usable items at the end + for (Item usableItem : usableItemList) { + System.out.println("1x " + Cli.Colors.BLUE + usableItem + Cli.Colors.RESET + " (lze využít)"); + } + + + if (!usableItemList.isEmpty()) { + System.out.println("\n\nNyní si můžete vybrat jaký item chcete využít"); + var options = new ArrayList<>(usableItemList.stream().map(Item::toString).toList()); + options.add("Zavřít inventář"); + + var selectedIndex = Cli.selectOptionIndex(options); + + if (selectedIndex == options.size() - 1) { + return; + } + + var item = usableItemList.get(selectedIndex); + + apiService.useItem(playerKey, item.getId()).execute(); + + Cli.info(item + " byl využit!"); + } + } +} diff --git a/frontend/src/main/java/cz/jzitnik/game/interactions/Interactions.java b/frontend/src/main/java/cz/jzitnik/game/interactions/Interactions.java index 72de356..1f37e16 100644 --- a/frontend/src/main/java/cz/jzitnik/game/interactions/Interactions.java +++ b/frontend/src/main/java/cz/jzitnik/game/interactions/Interactions.java @@ -1,12 +1,40 @@ package cz.jzitnik.game.interactions; +import cz.jzitnik.api.ApiService; +import cz.jzitnik.api.requests.InteractionRequest; import cz.jzitnik.api.types.Character; +import cz.jzitnik.utils.Cli; + +import java.io.IOException; +import java.util.Arrays; public class Interactions { - public static void runInteraction(Character character) { + public static void runInteraction(Character character, ApiService apiService, String playerKey) throws IOException { + var response = apiService.getInteractionData(playerKey, character.getId()).execute(); + var interactionData = response.body().getData().get(); + + if (character.isInteractedWith()) { + Cli.type(character, interactionData.getInteractedWithText()); + return; + } + + Cli.type(character, interactionData.getStartText()); + switch (character.getInteraction()) { case Farmer -> { - System.out.println("Inplement interaction with farmer"); + var options = new String[] {"Kámen", "Nůžky", "Papír"}; + var selected = Cli.selectOptionIndex(Arrays.stream(options).toList()); + + var requestData = new InteractionRequest(String.valueOf(selected), character.getId()); + + var res = apiService.interact(playerKey, requestData).execute(); + var interactionResponse = res.body().getData().get(); + + Cli.type(character, interactionResponse.getResponseText()); + + if (interactionResponse.isSuccess()) { + Cli.gotItems(interactionResponse.getItems()); + } } } } diff --git a/frontend/src/main/java/cz/jzitnik/utils/Cli.java b/frontend/src/main/java/cz/jzitnik/utils/Cli.java index 0988219..a7cfade 100644 --- a/frontend/src/main/java/cz/jzitnik/utils/Cli.java +++ b/frontend/src/main/java/cz/jzitnik/utils/Cli.java @@ -1,5 +1,8 @@ package cz.jzitnik.utils; +import cz.jzitnik.api.types.Character; +import cz.jzitnik.api.types.Item; + import java.util.List; import java.util.Scanner; @@ -53,15 +56,11 @@ public class Cli { int padding = (totalWidth - text.length()) / 2; StringBuilder sb = new StringBuilder(totalWidth); - for (int i = 0; i < padding; i++) { - sb.append("="); - } + sb.append("=".repeat(Math.max(0, padding))); sb.append(" "); sb.append(text); sb.append(" "); - for (int i = 0; i < padding; i++) { - sb.append("="); - } + sb.append("=".repeat(Math.max(0, padding))); while (sb.length() < totalWidth) { sb.insert(0, "="); @@ -76,13 +75,9 @@ public class Cli { int padding = (totalWidth - text.length()) / 2; StringBuilder sb = new StringBuilder(totalWidth); - for (int i = 0; i < padding; i++) { - sb.append(" "); - } + sb.append(" ".repeat(Math.max(0, padding))); sb.append(text); - for (int i = 0; i < padding; i++) { - sb.append(" "); - } + sb.append(" ".repeat(Math.max(0, padding))); while (sb.length() < totalWidth) { sb.insert(0, " "); @@ -125,6 +120,18 @@ public class Cli { System.out.println(); } + public static void gotItems(List items) { + if (items.isEmpty()) { + return; + } + + var itemsMapped = items.stream().map(Item::toString).toList(); + Cli.info("Dostal jste: " + String.join(", ", itemsMapped)); + } + + public static void type(Character character, String text) { + Cli.type(Cli.Colors.YELLOW + character.getName() + ": " + Cli.Colors.RESET + text); + } public static void type(String text) { Cli.typeText(Cli.wrapText(text)); } @@ -171,37 +178,6 @@ public class Cli { } } - /** - * 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 int selectOptionIndex(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 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(); - } - } - } - /** * Displays a menu with the provided options and returns the selected option. *