feat: Implemented interactions and rooms

Now you can interact with characters and go between rooms
This commit is contained in:
jzitnik-dev 2024-12-21 16:08:05 +01:00
parent 6786eb1be8
commit ce1710cfef
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
22 changed files with 510 additions and 105 deletions

View File

@ -1,5 +1,6 @@
package cz.jzitnik.chronos.controllers; package cz.jzitnik.chronos.controllers;
import cz.jzitnik.chronos.entities.Interaction;
import cz.jzitnik.chronos.entities.Item; import cz.jzitnik.chronos.entities.Item;
import cz.jzitnik.chronos.interactions.InteractionService; import cz.jzitnik.chronos.interactions.InteractionService;
import cz.jzitnik.chronos.payload.requests.InteractionRequest; import cz.jzitnik.chronos.payload.requests.InteractionRequest;
@ -27,6 +28,27 @@ public class CharacterController {
@Autowired @Autowired
private CharacterRepository characterRepository; private CharacterRepository characterRepository;
@GetMapping("/interaction")
@CheckUser
public ResponseEntity<UnifiedResponse<Interaction, Error>> 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") @PostMapping("/interact")
@CheckUser @CheckUser
public ResponseEntity<UnifiedResponse<InteractionResponse, Error>> interact(@RequestParam String playerKey, @RequestBody InteractionRequest interactionRequest) { public ResponseEntity<UnifiedResponse<InteractionResponse, Error>> interact(@RequestParam String playerKey, @RequestBody InteractionRequest interactionRequest) {

View File

@ -1,11 +1,15 @@
package cz.jzitnik.chronos.controllers; 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.errors.NotFoundError;
import cz.jzitnik.chronos.payload.requests.PlayerNameRequest; import cz.jzitnik.chronos.payload.requests.PlayerNameRequest;
import cz.jzitnik.chronos.payload.responses.UnifiedResponse; import cz.jzitnik.chronos.payload.responses.UnifiedResponse;
import cz.jzitnik.chronos.repository.GameRepository; import cz.jzitnik.chronos.repository.GameRepository;
import cz.jzitnik.chronos.repository.ItemRepository;
import cz.jzitnik.chronos.repository.PlayerRepository; import cz.jzitnik.chronos.repository.PlayerRepository;
import cz.jzitnik.chronos.services.GameService; import cz.jzitnik.chronos.services.GameService;
import cz.jzitnik.chronos.utils.anotations.CheckUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -24,6 +28,9 @@ public class PlayerController {
@Autowired @Autowired
private GameService gameService; private GameService gameService;
@Autowired
private ItemRepository itemRepository;
@PostMapping("/new") @PostMapping("/new")
public ResponseEntity<UnifiedResponse<String, Error>> newPlayer(@RequestParam String gameKey, @RequestBody PlayerNameRequest playerNameRequest) { public ResponseEntity<UnifiedResponse<String, Error>> newPlayer(@RequestParam String gameKey, @RequestBody PlayerNameRequest playerNameRequest) {
var gameOptional = gameRepository.findByGameKey(gameKey); var gameOptional = gameRepository.findByGameKey(gameKey);
@ -42,4 +49,42 @@ public class PlayerController {
UnifiedResponse.success(player.getPlayerKey()) UnifiedResponse.success(player.getPlayerKey())
); );
} }
@GetMapping("/me")
@CheckUser
public ResponseEntity<UnifiedResponse<Player, Error>> getMe(@RequestParam String playerKey) {
var player = playerRepository.findByPlayerKey(playerKey).get();
return ResponseEntity.ok(UnifiedResponse.success(player));
}
@PostMapping("/use_item")
@CheckUser
public ResponseEntity<UnifiedResponse<Object, Error>> 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));
}
} }

View File

@ -2,6 +2,7 @@ package cz.jzitnik.chronos.entities;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@ -24,4 +25,10 @@ public class Interaction {
@OneToOne(mappedBy = "interactionData") @OneToOne(mappedBy = "interactionData")
@JsonIgnore @JsonIgnore
private Character character; private Character character;
public Interaction(String startText, String interactedWithText, Character character) {
this.startText = startText;
this.interactedWithText = interactedWithText;
this.character = character;
}
} }

View File

@ -1,6 +1,17 @@
package cz.jzitnik.chronos.entities; package cz.jzitnik.chronos.entities;
import cz.jzitnik.chronos.payload.errors.ItemNotUsableException;
public enum ItemType { public enum ItemType {
KEY_FRAGMENT, 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");
}
}
} }

View File

@ -10,6 +10,8 @@ import cz.jzitnik.chronos.repository.PlayerRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Optional;
import java.util.Random; import java.util.Random;
@Service @Service
@ -48,30 +50,42 @@ public class InteractionService {
player.setLuck(false); player.setLuck(false);
if (playerWins) { if (playerWins) {
player.addItem(new Item(ItemType.KEY_FRAGMENT, player)); var item = new Item(ItemType.KEY_FRAGMENT, player);
player.addItem(item);
playerRepository.save(player); playerRepository.save(player);
character.setInteractedWith(true); character.setInteractedWith(true);
characterRepository.save(character); characterRepository.save(character);
return new InteractionResponse(true, "Vyhrál jsi, dostáváš jeden fragment klíče."); var items = new ArrayList<Item>();
items.add(item);
return new InteractionResponse(true, "Vyhrál jsi, dostáváš ode mě jeden fragment klíče.", items);
} else { } 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 { } else {
// Standard rock-paper-scissors game logic // Standard rock-paper-scissors game logic
if (userChoice == characterChoice) { 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)) { } 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); playerRepository.save(player);
character.setInteractedWith(true); character.setInteractedWith(true);
characterRepository.save(character); characterRepository.save(character);
return new InteractionResponse(true, "Vyhrál jsi, dostáváš jeden fragment klíče."); var items = new ArrayList<Item>();
items.add(item);
return new InteractionResponse(true, "Vyhrál jsi, dostáváš ode mě jeden fragment klíče.", items);
} else { } 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<>());
} }
} }
}); });

View File

@ -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());
}
}

View File

@ -1,13 +1,17 @@
package cz.jzitnik.chronos.payload.responses; package cz.jzitnik.chronos.payload.responses;
import cz.jzitnik.chronos.entities.Item;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
@Getter @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
public class InteractionResponse { public class InteractionResponse {
private boolean success; private boolean success;
private String responseText; private String responseText;
private List<Item> items;
} }

View File

@ -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<Item, Long> {
}

View File

@ -38,9 +38,14 @@ public class InitGameService {
var farmar = new Character( var farmar = new Character(
"Farmář", "Farmář",
stodola, 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.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_characters.add(farmar);
stodola.setCharacters(stodola_characters); stodola.setCharacters(stodola_characters);

View File

@ -46,7 +46,7 @@ public class Main {
} }
} }
Cli.info("Prosím počkejte...");
chronos.waitForStart(); chronos.waitForStart();
Cli.info("Hra začala!"); Cli.info("Hra začala!");
System.out.println("\n\n"); System.out.println("\n\n");

View File

@ -1,11 +1,10 @@
package cz.jzitnik.api; package cz.jzitnik.api;
import cz.jzitnik.api.requests.InteractionRequest;
import cz.jzitnik.api.requests.PlayerNameRequest; import cz.jzitnik.api.requests.PlayerNameRequest;
import cz.jzitnik.api.responses.InteractionResponse;
import cz.jzitnik.api.responses.UnifiedResponse; import cz.jzitnik.api.responses.UnifiedResponse;
import cz.jzitnik.api.types.Game; import cz.jzitnik.api.types.*;
import cz.jzitnik.api.types.Room;
import cz.jzitnik.api.types.TakeItemsResponse;
import cz.jzitnik.api.types.TestGameKeyResponse;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.Body; import retrofit2.http.Body;
import retrofit2.http.GET; import retrofit2.http.GET;
@ -27,6 +26,17 @@ public interface ApiService {
@Body PlayerNameRequest requestBody @Body PlayerNameRequest requestBody
); );
@GET("game/players/me")
Call<UnifiedResponse<Player, Error>> getMe(
@Query("playerKey") String playerKey
);
@POST("game/players/use_item")
Call<UnifiedResponse<Object, Error>> useItem(
@Query("playerKey") String playerKey,
@Query("itemId") Long itemId
);
@POST("game/start") @POST("game/start")
Call<UnifiedResponse<Object, Error>> startGame( Call<UnifiedResponse<Object, Error>> startGame(
@Query("playerKey") String playerKey, @Query("playerKey") String playerKey,
@ -59,4 +69,16 @@ public interface ApiService {
@Query("playerKey") String playerKey, @Query("playerKey") String playerKey,
@Query("characterId") Long characterId @Query("characterId") Long characterId
); );
@GET("game/characters/interaction")
Call<UnifiedResponse<InteractionData, Error>> getInteractionData(
@Query("playerKey") String playerKey,
@Query("characterId") Long characterId
);
@POST("game/characters/interact")
Call<UnifiedResponse<InteractionResponse, Error>> interact(
@Query("playerKey") String playerKey,
@Body InteractionRequest interactionRequest
);
} }

View File

@ -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;
}

View File

@ -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<Item> items;
}

View File

@ -9,4 +9,9 @@ public class Item {
private Long id; private Long id;
private ItemType itemType; private ItemType itemType;
@Override
public String toString() {
return itemType.toString();
}
} }

View File

@ -1,6 +1,27 @@
package cz.jzitnik.api.types; package cz.jzitnik.api.types;
import java.util.HashSet;
import java.util.Set;
public enum ItemType { public enum ItemType {
KEY_FRAGMENT, KEY_FRAGMENT,
LUCK_POTION LUCK_POTION;
private static final Set<ItemType> 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";
};
}
} }

View File

@ -1,5 +1,6 @@
package cz.jzitnik.api.types; package cz.jzitnik.api.types;
import cz.jzitnik.utils.Cli;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -15,4 +16,30 @@ public class Player {
private List<Item> inventory; private List<Item> inventory;
private Room currentRoom; 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;
}
} }

View File

@ -15,4 +15,13 @@ public class Room {
private List<Item> items; private List<Item> items;
private List<Character> characters; private List<Character> characters;
@Override
public String toString() {
if (name.equals("Outside")) {
return "Venek";
}
return name;
}
} }

View File

@ -2,10 +2,8 @@ package cz.jzitnik.game;
import cz.jzitnik.api.ApiService; import cz.jzitnik.api.ApiService;
import cz.jzitnik.api.requests.PlayerNameRequest; import cz.jzitnik.api.requests.PlayerNameRequest;
import cz.jzitnik.api.types.*;
import cz.jzitnik.api.types.Character; 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.game.interactions.Interactions;
import cz.jzitnik.utils.Cli; import cz.jzitnik.utils.Cli;
import cz.jzitnik.utils.ConfigPathProvider; import cz.jzitnik.utils.ConfigPathProvider;
@ -220,15 +218,11 @@ public class Chronos {
Cli.info("Nyní se nacházíte v místnosti " + room.getName()); 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."); Cli.type("V místnosti je prázno... Ani jedna duše.");
} } else {
for (Character character : room.getCharacters()) { talk(characters);
talkWith(character);
}
if (!room.getCharacters().isEmpty()) {
interactWithAny(room.getCharacters());
} }
var responseRooms = apiService.getAllRooms(localData.getUserSecret()).execute(); var responseRooms = apiService.getAllRooms(localData.getUserSecret()).execute();
@ -237,46 +231,26 @@ public class Chronos {
System.out.println(); System.out.println();
Cli.info("Nyni si vyberte do jaké místnosti chcete jít."); 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); visit(rooms.get(selectedIndex).getId(), true);
} }
private void interactWithAny(List<Character> characters) throws IOException { public void talk(List<Character> characters) throws IOException {
if (characters.stream().anyMatch(character -> character.getInteraction() != null)) { List<String> commands = new ArrayList<>(characters.stream().map(chachar -> "Promluvit si s " + chachar.getName()).toList());
List<Character> interactablePostavy = characters.stream().filter( commands.add("Přejít do jiné místnosti");
character -> character.getInteraction() != null && CommandPalette commandPalette = new CommandPalette(commands, apiService, localData.getUserSecret());
!character.isInteractedWith() int selectedIndex = commandPalette.displayIndex();
).toList();
if (interactablePostavy.isEmpty()) { if (selectedIndex == commands.size() - 1) {
return; // Přejít do jiné místnosti
} return;
List<String> 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());
} }
}
public void talkWith(Character character) throws IOException { // Dangerous I would say but whatever
Cli.type("V místnosti je " + character.getName()); Character character = characters.get(selectedIndex);
System.out.println(); System.out.println();
Cli.type(Cli.Colors.YELLOW + character.getName() + ": " + Cli.Colors.RESET + character.getDialog()); Cli.type(character, character.getDialog());
if (!character.getInventory().isEmpty()) { if (!character.getInventory().isEmpty()) {
var res = apiService.takeItems(getLocalData().getUserSecret(), character.getId()).execute(); var res = apiService.takeItems(getLocalData().getUserSecret(), character.getId()).execute();
@ -285,9 +259,19 @@ public class Chronos {
if (body == TakeItemsResponse.ALREADY_TAKEN) { 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."); Cli.type(Cli.Colors.YELLOW + character.getName() + ": " + Cli.Colors.RESET + "Už jsem ti mé itemy dal. Už pro tebe nic nemám.");
} else { } else {
var characters = character.getInventory().stream().map(item -> item.getItemType().toString()).toList(); Cli.gotItems(character.getInventory());
Cli.info("Dostal jste: " + String.join(", ", characters));
} }
} }
// 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());
} }
} }

View File

@ -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<Command> commands;
private ApiService apiService;
private String playerKey;
public CommandPalette(List<String> 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<Command> 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();
}
};
}
}

View File

@ -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<ItemType, Integer> itemCounts = new HashMap<>();
List<Item> 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<ItemType, Integer> 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!");
}
}
}

View File

@ -1,12 +1,40 @@
package cz.jzitnik.game.interactions; 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.api.types.Character;
import cz.jzitnik.utils.Cli;
import java.io.IOException;
import java.util.Arrays;
public class Interactions { 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()) { switch (character.getInteraction()) {
case Farmer -> { 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());
}
} }
} }
} }

View File

@ -1,5 +1,8 @@
package cz.jzitnik.utils; package cz.jzitnik.utils;
import cz.jzitnik.api.types.Character;
import cz.jzitnik.api.types.Item;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
@ -53,15 +56,11 @@ public class Cli {
int padding = (totalWidth - text.length()) / 2; int padding = (totalWidth - text.length()) / 2;
StringBuilder sb = new StringBuilder(totalWidth); StringBuilder sb = new StringBuilder(totalWidth);
for (int i = 0; i < padding; i++) { sb.append("=".repeat(Math.max(0, padding)));
sb.append("=");
}
sb.append(" "); sb.append(" ");
sb.append(text); sb.append(text);
sb.append(" "); sb.append(" ");
for (int i = 0; i < padding; i++) { sb.append("=".repeat(Math.max(0, padding)));
sb.append("=");
}
while (sb.length() < totalWidth) { while (sb.length() < totalWidth) {
sb.insert(0, "="); sb.insert(0, "=");
@ -76,13 +75,9 @@ public class Cli {
int padding = (totalWidth - text.length()) / 2; int padding = (totalWidth - text.length()) / 2;
StringBuilder sb = new StringBuilder(totalWidth); StringBuilder sb = new StringBuilder(totalWidth);
for (int i = 0; i < padding; i++) { sb.append(" ".repeat(Math.max(0, padding)));
sb.append(" ");
}
sb.append(text); sb.append(text);
for (int i = 0; i < padding; i++) { sb.append(" ".repeat(Math.max(0, padding)));
sb.append(" ");
}
while (sb.length() < totalWidth) { while (sb.length() < totalWidth) {
sb.insert(0, " "); sb.insert(0, " ");
@ -125,6 +120,18 @@ public class Cli {
System.out.println(); System.out.println();
} }
public static void gotItems(List<Item> 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) { public static void type(String text) {
Cli.typeText(Cli.wrapText(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. * Displays a menu with the provided options and returns the selected option.
* *