feat: TicTacToe interaction
This commit is contained in:
parent
171f760d4e
commit
910808eb6e
@ -3,5 +3,6 @@ package cz.jzitnik.chronos.interactions;
|
||||
public enum Interaction {
|
||||
Farmer,
|
||||
Cashier,
|
||||
Librarian
|
||||
Librarian,
|
||||
Innkeeper,
|
||||
}
|
@ -4,6 +4,7 @@ import cz.jzitnik.chronos.entities.Player;
|
||||
import cz.jzitnik.chronos.entities.Character;
|
||||
import cz.jzitnik.chronos.interactions.list.Hangman;
|
||||
import cz.jzitnik.chronos.interactions.list.RockPaperScissors;
|
||||
import cz.jzitnik.chronos.interactions.list.TicTacToe;
|
||||
import cz.jzitnik.chronos.interactions.list.wordle.Wordle;
|
||||
import cz.jzitnik.chronos.payload.responses.InteractionResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -17,6 +18,8 @@ public class InteractionService {
|
||||
private Hangman hangman;
|
||||
@Autowired
|
||||
private Wordle wordle;
|
||||
@Autowired
|
||||
private TicTacToe ticTacToe;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Function3<T, U, V, W> {
|
||||
@ -28,6 +31,7 @@ public class InteractionService {
|
||||
case Farmer -> rockPaperScissors::play;
|
||||
case Cashier -> hangman::play;
|
||||
case Librarian -> wordle::play;
|
||||
case Innkeeper -> ticTacToe::play;
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
package cz.jzitnik.chronos.interactions.list;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cz.jzitnik.chronos.entities.Character;
|
||||
import cz.jzitnik.chronos.entities.Item;
|
||||
import cz.jzitnik.chronos.entities.ItemType;
|
||||
import cz.jzitnik.chronos.entities.Player;
|
||||
import cz.jzitnik.chronos.interactions.InteractionPlayer;
|
||||
import cz.jzitnik.chronos.payload.responses.InteractionResponse;
|
||||
import cz.jzitnik.chronos.repository.CharacterRepository;
|
||||
import cz.jzitnik.chronos.services.ItemService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
@Service
|
||||
public class TicTacToe implements InteractionPlayer {
|
||||
@Autowired
|
||||
private CharacterRepository characterRepository;
|
||||
@Autowired
|
||||
private ItemService itemService;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
private static class TicTacToeMemory {
|
||||
private String[] board;
|
||||
private boolean playerTurn;
|
||||
}
|
||||
|
||||
private static TicTacToeMemory readMemory(String memory) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try {
|
||||
return objectMapper.readValue(memory, TicTacToeMemory.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error reading memory.");
|
||||
}
|
||||
}
|
||||
|
||||
private static String writeMemory(TicTacToeMemory memory) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try {
|
||||
return objectMapper.writeValueAsString(memory);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
private static class TicTacToeResponse {
|
||||
public enum Type {
|
||||
GAME_CREATED,
|
||||
GAME_WON,
|
||||
GAME_TIED,
|
||||
GAME_CONTINUE,
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private String[] board;
|
||||
private boolean playerTurn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResponse play(Player player, Character character, String data) {
|
||||
if (character.getInteractionData().getPlayer() == null) {
|
||||
try {
|
||||
return initGame(player, character);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!character.getInteractionData().getPlayer().getId().equals(player.getId())) {
|
||||
return new InteractionResponse(false, "already_playing", new ArrayList<>());
|
||||
}
|
||||
|
||||
try {
|
||||
return makeMove(player, character, data);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private InteractionResponse initGame(Player player, Character character) throws JsonProcessingException {
|
||||
var interactionData = character.getInteractionData();
|
||||
interactionData.setPlayer(player);
|
||||
|
||||
String[] board = new String[9];
|
||||
for (int i = 0; i < 9; i++) {
|
||||
board[i] = "_";
|
||||
}
|
||||
|
||||
interactionData.setMemory(writeMemory(new TicTacToeMemory(board, true)));
|
||||
|
||||
characterRepository.save(character);
|
||||
|
||||
var response = new TicTacToeResponse(TicTacToeResponse.Type.GAME_CREATED, board, true);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
return new InteractionResponse(true, objectMapper.writeValueAsString(response), new ArrayList<>());
|
||||
}
|
||||
|
||||
private InteractionResponse makeMove(Player player, Character character, String data) throws JsonProcessingException {
|
||||
TicTacToeMemory gameMemory = readMemory(character.getInteractionData().getMemory());
|
||||
String[] board = gameMemory.getBoard();
|
||||
boolean playerTurn = gameMemory.isPlayerTurn();
|
||||
|
||||
if (data.equals("get_data")) {
|
||||
var response = new TicTacToeResponse(TicTacToeResponse.Type.GAME_CONTINUE, board, playerTurn);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return new InteractionResponse(false, objectMapper.writeValueAsString(response), new ArrayList<>());
|
||||
}
|
||||
|
||||
if (playerTurn) {
|
||||
int move = Integer.parseInt(data);
|
||||
if (move < 0 || move > 8 || !board[move].equals("_")) {
|
||||
return new InteractionResponse(false, "invalid_move", new ArrayList<>());
|
||||
}
|
||||
|
||||
board[move] = "O";
|
||||
if (checkWin(board, "O")) {
|
||||
return gameOver(character, TicTacToeResponse.Type.GAME_WON, board);
|
||||
} else if (isBoardFull(board)) {
|
||||
return gameOver(character, TicTacToeResponse.Type.GAME_TIED, board);
|
||||
} else {
|
||||
gameMemory.setPlayerTurn(false);
|
||||
character.getInteractionData().setMemory(writeMemory(gameMemory));
|
||||
characterRepository.save(character);
|
||||
return computerMove(player, character, board);
|
||||
}
|
||||
}
|
||||
|
||||
return new InteractionResponse(false, "not_your_turn", new ArrayList<>());
|
||||
}
|
||||
|
||||
private InteractionResponse computerMove(Player player, Character character, String[] board) throws JsonProcessingException {
|
||||
Random random = new Random();
|
||||
int move;
|
||||
do {
|
||||
move = random.nextInt(9);
|
||||
} while (!board[move].equals("_"));
|
||||
|
||||
board[move] = "X";
|
||||
if (checkWin(board, "X")) {
|
||||
return gameOver(character, TicTacToeResponse.Type.GAME_WON, board);
|
||||
} else if (isBoardFull(board)) {
|
||||
return gameOver(character, TicTacToeResponse.Type.GAME_TIED, board);
|
||||
}
|
||||
|
||||
TicTacToeMemory gameMemory = readMemory(character.getInteractionData().getMemory());
|
||||
gameMemory.setPlayerTurn(true);
|
||||
gameMemory.setBoard(board);
|
||||
character.getInteractionData().setMemory(writeMemory(gameMemory));
|
||||
characterRepository.save(character);
|
||||
|
||||
var response = new TicTacToeResponse(TicTacToeResponse.Type.GAME_CONTINUE, board, true);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return new InteractionResponse(false, objectMapper.writeValueAsString(response), new ArrayList<>());
|
||||
}
|
||||
|
||||
private boolean checkWin(String[] board, String player) {
|
||||
String[][] winPatterns = {
|
||||
{ "0", "1", "2" }, { "3", "4", "5" }, { "6", "7", "8" }, // rows
|
||||
{ "0", "3", "6" }, { "1", "4", "7" }, { "2", "5", "8" }, // columns
|
||||
{ "0", "4", "8" }, { "2", "4", "6" } // diagonals
|
||||
};
|
||||
|
||||
for (String[] pattern : winPatterns) {
|
||||
if (board[Integer.parseInt(pattern[0])].equals(player) &&
|
||||
board[Integer.parseInt(pattern[1])].equals(player) &&
|
||||
board[Integer.parseInt(pattern[2])].equals(player)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isBoardFull(String[] board) {
|
||||
for (String cell : board) {
|
||||
if (cell.equals("_")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private InteractionResponse gameOver(Character character, TicTacToeResponse.Type type, String[] board) throws JsonProcessingException {
|
||||
if (type != TicTacToeResponse.Type.GAME_TIED) {
|
||||
character.setInteractedWith(true);
|
||||
characterRepository.save(character);
|
||||
}
|
||||
|
||||
if (type == TicTacToeResponse.Type.GAME_WON) {
|
||||
var item = new Item(ItemType.KEY_FRAGMENT, character.getInteractionData().getPlayer());
|
||||
itemService.addItem(character.getInteractionData().getPlayer(), item);
|
||||
}
|
||||
|
||||
var response = new TicTacToeResponse(type, board, false);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
return new InteractionResponse(true, objectMapper.writeValueAsString(response), new ArrayList<>());
|
||||
}
|
||||
}
|
@ -89,11 +89,32 @@ public class InitGameService {
|
||||
library_characters.add(librarian);
|
||||
library.setCharacters(library_characters);
|
||||
|
||||
// Hospoda
|
||||
var inn = new Room(
|
||||
"Hospoda",
|
||||
game
|
||||
);
|
||||
var inn_characters = new ArrayList<Character>();
|
||||
var innkeeper = new Character(
|
||||
"Hostinský",
|
||||
inn,
|
||||
"Ahoj já jsem hostinský a budeš se mnou hrát piškvorky."
|
||||
);
|
||||
innkeeper.setInteraction(Interaction.Innkeeper);
|
||||
innkeeper.setInteractionData(new cz.jzitnik.chronos.entities.Interaction(
|
||||
"Tak si zahrajeme piškvorky.",
|
||||
"Se mnou někdo již hrál piškvorky. Další fragment klíče nemám.",
|
||||
innkeeper
|
||||
));
|
||||
inn_characters.add(innkeeper);
|
||||
inn.setCharacters(inn_characters);
|
||||
|
||||
|
||||
rooms.add(outside);
|
||||
rooms.add(stodola);
|
||||
rooms.add(shop);
|
||||
rooms.add(library);
|
||||
rooms.add(inn);
|
||||
|
||||
game.setRooms(rooms);
|
||||
|
||||
|
@ -3,5 +3,6 @@ package cz.jzitnik.api.types;
|
||||
public enum Interaction {
|
||||
Farmer,
|
||||
Cashier,
|
||||
Librarian
|
||||
Librarian,
|
||||
Innkeeper
|
||||
}
|
||||
|
@ -45,6 +45,10 @@ public class Interactions {
|
||||
Wordle wordle = new Wordle(character, apiService, playerKey);
|
||||
wordle.play();
|
||||
}
|
||||
case Innkeeper -> {
|
||||
TicTacToe ticTacToe = new TicTacToe(character, apiService, playerKey);
|
||||
ticTacToe.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,125 @@
|
||||
package cz.jzitnik.game.interactions;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cz.jzitnik.api.ApiService;
|
||||
import cz.jzitnik.api.requests.InteractionRequest;
|
||||
import cz.jzitnik.api.responses.InteractionResponse;
|
||||
import cz.jzitnik.api.types.Character;
|
||||
import cz.jzitnik.utils.Cli;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.Console;
|
||||
import java.io.IOException;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class TicTacToe {
|
||||
private Character character;
|
||||
private ApiService apiService;
|
||||
private String playerKey;
|
||||
|
||||
@Getter
|
||||
private static class TicTacToeResponse {
|
||||
public enum Type {
|
||||
GAME_CREATED,
|
||||
GAME_WON,
|
||||
GAME_TIED,
|
||||
GAME_CONTINUE,
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private String[] board;
|
||||
private boolean playerTurn;
|
||||
}
|
||||
|
||||
public void play() throws IOException {
|
||||
var response = apiService.isInteracting(playerKey, character.getId()).execute();
|
||||
var interacting = response.body().getData().get();
|
||||
|
||||
if (!interacting) {
|
||||
init();
|
||||
} else {
|
||||
// Get data where we have left off
|
||||
var body = apiService.interact(playerKey, new InteractionRequest("get_data", character.getId())).execute().body();
|
||||
var data = body.getData().get();
|
||||
gameContinue(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void init() throws IOException {
|
||||
var body = apiService.interact(playerKey, new InteractionRequest("", character.getId())).execute().body();
|
||||
var data = body.getData().get();
|
||||
gameContinue(data);
|
||||
}
|
||||
|
||||
private void gameContinue(InteractionResponse data) throws IOException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
var ticTacToeData = objectMapper.readValue(data.getResponseText(), TicTacToeResponse.class);
|
||||
|
||||
switch (ticTacToeData.getType()) {
|
||||
case GAME_WON -> {
|
||||
System.out.println(formatBoard(ticTacToeData.getBoard()));
|
||||
Cli.type(character, "Vyhrál jsi!");
|
||||
Cli.gotItems(data.getItems());
|
||||
}
|
||||
case GAME_TIED -> Cli.type(character, "Prohrál jsi! Hra skončila remízou.");
|
||||
case GAME_CONTINUE, GAME_CREATED -> {
|
||||
System.out.println("Aktuální stav pole:");
|
||||
System.out.println(formatBoard(ticTacToeData.getBoard()));
|
||||
|
||||
if (ticTacToeData.isPlayerTurn()) {
|
||||
var move = askForMove(ticTacToeData.getBoard());
|
||||
|
||||
var response = apiService.interact(playerKey, new InteractionRequest(String.valueOf(move), character.getId())).execute();
|
||||
var body = response.body().getData().get();
|
||||
|
||||
gameContinue(body);
|
||||
} else {
|
||||
System.out.println("Čekání na tah počítače...");
|
||||
// Wait for computer to play its move
|
||||
var response = apiService.interact(playerKey, new InteractionRequest("get_data", character.getId())).execute();
|
||||
var body = response.body().getData().get();
|
||||
|
||||
gameContinue(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String formatBoard(String[] board) {
|
||||
StringBuilder boardRepresentation = new StringBuilder();
|
||||
for (int i = 0; i < 9; i++) {
|
||||
boardRepresentation.append(board[i]);
|
||||
if ((i + 1) % 3 == 0) {
|
||||
boardRepresentation.append("\n");
|
||||
} else {
|
||||
boardRepresentation.append(" ");
|
||||
}
|
||||
}
|
||||
return boardRepresentation.toString();
|
||||
}
|
||||
|
||||
private int askForMove(String[] board) {
|
||||
Console console = System.console();
|
||||
String data = console.readLine("Zadejte číslo pole (1-9): ");
|
||||
|
||||
try {
|
||||
int move = Integer.parseInt(data);
|
||||
|
||||
if (move < 1 || move > 9) {
|
||||
Cli.error("Neplatný vstup! Číslo musí být mezi 1 a 9.");
|
||||
return askForMove(board);
|
||||
}
|
||||
|
||||
if (!board[move].equals("_")) {
|
||||
Cli.error("Toto pole již bylo obsazeno!");
|
||||
return askForMove(board);
|
||||
}
|
||||
|
||||
return move - 1;
|
||||
} catch (NumberFormatException e) {
|
||||
Cli.error("Neplatný vstup! Zadejte číslo.");
|
||||
return askForMove(board);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user