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