Tic Tac Toe done
This commit is contained in:
2
lib/SDL
2
lib/SDL
Submodule lib/SDL updated: 9f8425a7a9...87a83787a3
Submodule lib/openal-soft updated: 2fd52d58cd...d66107e9f0
@ -17,7 +17,7 @@ enum TicTacToeTileState Dawn::ticTacToeDetermineWinner(
|
||||
// Check rows
|
||||
for(i = 0; i < 9; i += 3) {
|
||||
if(board.at(i) == board.at(i + 1) && board.at(i) == board.at(i + 2) && board.at(i) != 0) {
|
||||
*winningCombo = { i, (uint8_t)i + 1, (uint8_t)i + 2 };
|
||||
*winningCombo = { i, (uint8_t)(i + 0x01), (uint8_t)(i + 0x02) };
|
||||
return board.at(i);
|
||||
}
|
||||
}
|
||||
@ -25,21 +25,102 @@ enum TicTacToeTileState Dawn::ticTacToeDetermineWinner(
|
||||
// Check columns
|
||||
for(i = 0; i < 3; i++) {
|
||||
if(board.at(i) == board.at(i + 3) && board.at(i) == board.at(i + 6) && board.at(i) != 0) {
|
||||
*winningCombo = { i, (uint8_t)i + 3, (uint8_t)i + 6 };
|
||||
*winningCombo = { i, (uint8_t)(i + 0x03), (uint8_t)(i + 0x06) };
|
||||
return board.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Check diagonals
|
||||
if(board.at(0) == board.at(4) && board.at(0) == board.at(8) && board.at(0) != 0) {
|
||||
*winningCombo = {0, 4, 8};
|
||||
*winningCombo = { 0, 0x04, 0x08 };
|
||||
return board.at(0);
|
||||
}
|
||||
|
||||
if(board.at(2) == board.at(4) && board.at(2) == board.at(6) && board.at(2) != 0) {
|
||||
*winningCombo = { 2, 4, 6 };
|
||||
*winningCombo = { 0x02, 0x04, 0x06 };
|
||||
return board.at(2);
|
||||
}
|
||||
|
||||
return TIC_TAC_TOE_EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Dawn::ticTacToeGetBoardScore(
|
||||
std::map<uint8_t, enum TicTacToeTileState> board,
|
||||
enum TicTacToeTileState player
|
||||
) {
|
||||
int32_t score = 0;
|
||||
uint8_t lines[8][3] = {
|
||||
{0, 1, 2}, {3, 4, 5}, {6, 7, 8},
|
||||
{0, 3, 6}, {1, 4, 7}, {2, 5, 8},
|
||||
{0, 4, 8}, {2, 4, 6}
|
||||
};
|
||||
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
uint8_t countPlayer = 0;
|
||||
uint8_t countEmpty = 0;
|
||||
|
||||
for (uint8_t j = 0; j < 3; j++) {
|
||||
if (board[lines[i][j]] == player) {
|
||||
countPlayer++;
|
||||
} else if (board[lines[i][j]] == TIC_TAC_TOE_EMPTY) {
|
||||
countEmpty++;
|
||||
}
|
||||
}
|
||||
|
||||
if (countPlayer == 2 && countEmpty == 1) {
|
||||
score += 10;
|
||||
} else if (countPlayer == 1 && countEmpty == 2) {
|
||||
score += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
|
||||
uint8_t Dawn::ticTacToeGetAiMove(
|
||||
std::map<uint8_t, enum TicTacToeTileState> board,
|
||||
enum TicTacToeTileState player
|
||||
) {
|
||||
std::vector<uint8_t> winningCombo;
|
||||
|
||||
// First, check if there's an immediate winning move for the AI
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
if(board[i] != TIC_TAC_TOE_EMPTY) continue;
|
||||
board[i] = player;
|
||||
if(ticTacToeDetermineWinner(board, &winningCombo) == player) {
|
||||
board[i] = TIC_TAC_TOE_EMPTY;
|
||||
return i;
|
||||
}
|
||||
board[i] = TIC_TAC_TOE_EMPTY;
|
||||
}
|
||||
|
||||
// Next, check if the player has an immediate winning move and block it
|
||||
auto opponent = (player == TIC_TAC_TOE_NOUGHT) ? TIC_TAC_TOE_CROSS : TIC_TAC_TOE_NOUGHT;
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
if(board[i] != TIC_TAC_TOE_EMPTY) continue;
|
||||
board[i] = opponent;
|
||||
if(ticTacToeDetermineWinner(board, &winningCombo) == opponent) {
|
||||
board[i] = TIC_TAC_TOE_EMPTY;
|
||||
return i;
|
||||
}
|
||||
board[i] = TIC_TAC_TOE_EMPTY;
|
||||
}
|
||||
|
||||
// If neither player has an immediate winning move, use the simple heuristic to choose a move
|
||||
uint8_t bestMove = -1;
|
||||
int32_t bestScore = -1000;
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
if(board[i] != TIC_TAC_TOE_EMPTY) continue;
|
||||
board[i] = player;
|
||||
auto score = ticTacToeGetBoardScore(board, player);
|
||||
board[i] = TIC_TAC_TOE_EMPTY;
|
||||
|
||||
if(score > bestScore) {
|
||||
bestMove = i;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMove;
|
||||
}
|
||||
|
@ -13,12 +13,41 @@ namespace Dawn {
|
||||
TIC_TAC_TOE_CROSS
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the winner of the given board.
|
||||
*
|
||||
* @param board Tic tac toe board.
|
||||
* @param winningCombo The output winning combo (if any).
|
||||
* @return The winning player, or EMPTY if no winner is present.
|
||||
*/
|
||||
enum TicTacToeTileState ticTacToeDetermineWinner(
|
||||
const std::map<uint8_t, enum TicTacToeTileState> board,
|
||||
std::vector<uint8_t> *winningCombo
|
||||
);
|
||||
|
||||
int32_t ticTacToeGetAiMove(
|
||||
/**
|
||||
* Returns the score / value of a given board for the given player. Mostly
|
||||
* used by the AI to determine whether a given board is better or worse than
|
||||
* any other.
|
||||
*
|
||||
* @param board Board to get the score of.
|
||||
* @param player Player to get the score for.
|
||||
* @return The weighted score of this board.
|
||||
*/
|
||||
int32_t ticTacToeGetBoardScore(
|
||||
std::map<uint8_t, enum TicTacToeTileState> board,
|
||||
enum TicTacToeTileState player
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns which cell should be used by the given player for their AI as the
|
||||
* best move for them.
|
||||
*
|
||||
* @param board Tic tac toe board.
|
||||
* @param player Player to get the AI move for.
|
||||
* @return The recommended cell to fill.
|
||||
*/
|
||||
uint8_t ticTacToeGetAiMove(
|
||||
std::map<uint8_t, enum TicTacToeTileState> board,
|
||||
enum TicTacToeTileState player
|
||||
);
|
||||
|
@ -13,8 +13,10 @@ using namespace Dawn;
|
||||
TicTacToeGame::TicTacToeGame(SceneItem *item) :
|
||||
SceneItemComponent(item),
|
||||
winner(TIC_TAC_TOE_EMPTY),
|
||||
nextMove(TIC_TAC_TOE_NOUGHT)
|
||||
{}
|
||||
nextMove(TIC_TAC_TOE_NOUGHT),
|
||||
gameOver(false)
|
||||
{
|
||||
}
|
||||
|
||||
void TicTacToeGame::onStart() {
|
||||
// Map tiles by tile number = tile
|
||||
@ -26,71 +28,98 @@ void TicTacToeGame::onStart() {
|
||||
}
|
||||
|
||||
useEffect([&]{
|
||||
if(nextMove != TIC_TAC_TOE_CROSS) return;
|
||||
std::cout << "AI Move" << std::endl;
|
||||
}, nextMove);
|
||||
|
||||
useEffect([&]{
|
||||
if(winner == TIC_TAC_TOE_NOUGHT) return;
|
||||
std::cout << "Winner is " << winner << std::endl;
|
||||
}, winner);
|
||||
}, gameOver);
|
||||
|
||||
useEvent([&](float_t delta) {
|
||||
// Only allow player input if it's their turn.
|
||||
if(nextMove != TIC_TAC_TOE_NOUGHT) return;
|
||||
|
||||
// Get mouse in screen space.
|
||||
auto mouse = getGame()->inputManager.getAxis2D(INPUT_BIND_MOUSE_X, INPUT_BIND_MOUSE_Y);
|
||||
mouse *= 2.0f;
|
||||
mouse -= glm::vec2(1, 1);
|
||||
if(winner != TIC_TAC_TOE_EMPTY) return;
|
||||
|
||||
Camera *camera = getScene()->findComponent<Camera>();
|
||||
if(camera == nullptr) return;
|
||||
|
||||
struct Ray3D ray;
|
||||
ray.origin = camera->transform->getWorldPosition();
|
||||
ray.direction = camera->getRayDirectionFromScreenSpace(mouse);
|
||||
|
||||
|
||||
// Find the hovered tile (if any)
|
||||
TicTacToeTile *hovered = nullptr;
|
||||
auto results = getPhysics()->raycast3DAll(ray);
|
||||
auto itResult = results.begin();
|
||||
while(itResult != results.end()) {
|
||||
auto result = *itResult;
|
||||
auto tile = result.collider->item->getComponent<TicTacToeTile>();
|
||||
if(tile == nullptr) {
|
||||
++itResult;
|
||||
continue;
|
||||
bool_t isPlayerMove = nextMove == TIC_TAC_TOE_NOUGHT;
|
||||
|
||||
// Get hovered tile (for player move only)
|
||||
if(isPlayerMove) {
|
||||
// Get mouse in screen space.
|
||||
auto mouse = getGame()->inputManager.getAxis2D(INPUT_BIND_MOUSE_X, INPUT_BIND_MOUSE_Y);
|
||||
mouse *= 2.0f;
|
||||
mouse -= glm::vec2(1, 1);
|
||||
|
||||
struct Ray3D ray;
|
||||
ray.origin = camera->transform->getWorldPosition();
|
||||
ray.direction = camera->getRayDirectionFromScreenSpace(mouse);
|
||||
|
||||
// Find the hovered tile (if any)
|
||||
auto results = getPhysics()->raycast3DAll(ray);
|
||||
auto itResult = results.begin();
|
||||
while(itResult != results.end()) {
|
||||
auto result = *itResult;
|
||||
auto tile = result.collider->item->getComponent<TicTacToeTile>();
|
||||
if(tile == nullptr) {
|
||||
++itResult;
|
||||
continue;
|
||||
}
|
||||
|
||||
hovered = tile;
|
||||
break;
|
||||
}
|
||||
|
||||
hovered = tile;
|
||||
break;
|
||||
}
|
||||
|
||||
// Now update the state of each tile, also get the state while we are at it
|
||||
std::map<uint8_t, enum TicTacToeTileState> tileMap;
|
||||
|
||||
// Now update the hover state(s)
|
||||
auto itTiles = tiles.begin();
|
||||
uint8_t tilesLeft = 0;
|
||||
while(itTiles != tiles.end()) {
|
||||
auto t = itTiles->second;
|
||||
|
||||
if(t == hovered) {
|
||||
if(t->tileState == TIC_TAC_TOE_EMPTY && getGame()->inputManager.isPressed(INPUT_BIND_MOUSE_CLICK)) {
|
||||
t->tileState = nextMove;
|
||||
nextMove = nextMove == TIC_TAC_TOE_NOUGHT ? TIC_TAC_TOE_CROSS : TIC_TAC_TOE_NOUGHT;
|
||||
} else if(t->tileState == TIC_TAC_TOE_EMPTY) {
|
||||
t->hovered = true;
|
||||
}
|
||||
t->hovered = true;
|
||||
} else {
|
||||
t->hovered = false;
|
||||
}
|
||||
tileMap[itTiles->second->tile] = itTiles->second->tileState;
|
||||
if(t->tileState == TIC_TAC_TOE_EMPTY) tilesLeft++;
|
||||
++itTiles;
|
||||
}
|
||||
|
||||
if(tilesLeft == 0) {
|
||||
winner = TIC_TAC_TOE_EMPTY;
|
||||
gameOver = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(isPlayerMove) {
|
||||
if(getGame()->inputManager.isPressed(INPUT_BIND_MOUSE_CLICK)) {
|
||||
this->makeMove(hovered->tile, nextMove);
|
||||
}
|
||||
} else if(nextMove != TIC_TAC_TOE_NOUGHT) {
|
||||
auto board = this->getBoard();
|
||||
auto move = ticTacToeGetAiMove(board, nextMove);
|
||||
this->makeMove(move, nextMove);
|
||||
}
|
||||
|
||||
// Determine winner
|
||||
auto board = this->getBoard();
|
||||
std::vector<uint8_t> winningCombo;
|
||||
winner = ticTacToeDetermineWinner(tileMap, &winningCombo);
|
||||
winner = ticTacToeDetermineWinner(board, &winningCombo);
|
||||
if(winner != TIC_TAC_TOE_EMPTY) gameOver = true;
|
||||
}, getScene()->eventSceneUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
std::map<uint8_t, enum TicTacToeTileState> TicTacToeGame::getBoard() {
|
||||
std::map<uint8_t, enum TicTacToeTileState> tileMap;
|
||||
|
||||
auto itTiles = tiles.begin();
|
||||
while(itTiles != tiles.end()) {
|
||||
auto t = itTiles->second;
|
||||
tileMap[t->tile] = t->tileState;
|
||||
++itTiles;
|
||||
}
|
||||
|
||||
return tileMap;
|
||||
}
|
||||
|
||||
void TicTacToeGame::makeMove(uint8_t tile, enum TicTacToeTileState player) {
|
||||
this->tiles[tile]->tileState = player;
|
||||
nextMove = player == TIC_TAC_TOE_NOUGHT ? TIC_TAC_TOE_CROSS : TIC_TAC_TOE_NOUGHT;
|
||||
}
|
||||
|
@ -11,12 +11,16 @@
|
||||
namespace Dawn {
|
||||
class TicTacToeGame : public SceneItemComponent {
|
||||
public:
|
||||
StateProperty<TicTacToeTileState> winner;
|
||||
enum TicTacToeTileState winner;
|
||||
StateProperty<bool_t> gameOver;
|
||||
std::map<int32_t, TicTacToeTile*> tiles;
|
||||
StateProperty<enum TicTacToeTileState> nextMove;
|
||||
|
||||
TicTacToeGame(SceneItem *item);
|
||||
|
||||
std::map<uint8_t, enum TicTacToeTileState> getBoard();
|
||||
void makeMove(uint8_t tile, enum TicTacToeTileState player);
|
||||
|
||||
void onStart() override;
|
||||
};
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "TicTacToeTile.hpp"
|
||||
#include "scene/SceneItem.hpp"
|
||||
#include "game/DawnGame.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
@ -18,13 +19,13 @@ TicTacToeTile::TicTacToeTile(SceneItem *item) :
|
||||
void TicTacToeTile::onStart() {
|
||||
auto cb = [&]{
|
||||
auto sprite = this->item->getComponent<TiledSprite>();
|
||||
if(this->hovered) {
|
||||
if(this->hovered && tileState == TIC_TAC_TOE_EMPTY) {
|
||||
sprite->setTile(0x03);
|
||||
} else {
|
||||
sprite->setTile(tileState);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(cb, { &tileState, &hovered });
|
||||
cb();
|
||||
}
|
||||
|
||||
useEffect(cb, tileState);
|
||||
useEffect(cb, hovered)();
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ namespace Dawn {
|
||||
uint8_t tile;
|
||||
|
||||
TicTacToeTile(SceneItem *item);
|
||||
|
||||
void onStart() override;
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user