feat: TicTacToe interaction
This commit is contained in:
parent
171f760d4e
commit
910808eb6e
@ -3,5 +3,6 @@ package cz.jzitnik.chronos.interactions;
|
|||||||
public enum Interaction {
|
public enum Interaction {
|
||||||
Farmer,
|
Farmer,
|
||||||
Cashier,
|
Cashier,
|
||||||
Librarian
|
Librarian,
|
||||||
|
Innkeeper,
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ import cz.jzitnik.chronos.entities.Player;
|
|||||||
import cz.jzitnik.chronos.entities.Character;
|
import cz.jzitnik.chronos.entities.Character;
|
||||||
import cz.jzitnik.chronos.interactions.list.Hangman;
|
import cz.jzitnik.chronos.interactions.list.Hangman;
|
||||||
import cz.jzitnik.chronos.interactions.list.RockPaperScissors;
|
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.interactions.list.wordle.Wordle;
|
||||||
import cz.jzitnik.chronos.payload.responses.InteractionResponse;
|
import cz.jzitnik.chronos.payload.responses.InteractionResponse;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -17,6 +18,8 @@ public class InteractionService {
|
|||||||
private Hangman hangman;
|
private Hangman hangman;
|
||||||
@Autowired
|
@Autowired
|
||||||
private Wordle wordle;
|
private Wordle wordle;
|
||||||
|
@Autowired
|
||||||
|
private TicTacToe ticTacToe;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface Function3<T, U, V, W> {
|
public interface Function3<T, U, V, W> {
|
||||||
@ -28,6 +31,7 @@ public class InteractionService {
|
|||||||
case Farmer -> rockPaperScissors::play;
|
case Farmer -> rockPaperScissors::play;
|
||||||
case Cashier -> hangman::play;
|
case Cashier -> hangman::play;
|
||||||
case Librarian -> wordle::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_characters.add(librarian);
|
||||||
library.setCharacters(library_characters);
|
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(outside);
|
||||||
rooms.add(stodola);
|
rooms.add(stodola);
|
||||||
rooms.add(shop);
|
rooms.add(shop);
|
||||||
rooms.add(library);
|
rooms.add(library);
|
||||||
|
rooms.add(inn);
|
||||||
|
|
||||||
game.setRooms(rooms);
|
game.setRooms(rooms);
|
||||||
|
|
||||||
|
@ -3,5 +3,6 @@ package cz.jzitnik.api.types;
|
|||||||
public enum Interaction {
|
public enum Interaction {
|
||||||
Farmer,
|
Farmer,
|
||||||
Cashier,
|
Cashier,
|
||||||
Librarian
|
Librarian,
|
||||||
|
Innkeeper
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,10 @@ public class Interactions {
|
|||||||
Wordle wordle = new Wordle(character, apiService, playerKey);
|
Wordle wordle = new Wordle(character, apiService, playerKey);
|
||||||
wordle.play();
|
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