refactored to multiple games implementation
This commit is contained in:
304
games/tic_tac_toe.cpp
Normal file
304
games/tic_tac_toe.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
// ============================================================================
|
||||
// TIC-TAC-TOE GAME IMPLEMENTATION
|
||||
// ============================================================================
|
||||
// Game logic, input handling, and rendering for Tic-Tac-Toe
|
||||
|
||||
#include "tic_tac_toe.h"
|
||||
#include "board_config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// Font reference from display system
|
||||
extern Font font_5x5_obj;
|
||||
|
||||
TicTacToeGame::TicTacToeGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui)
|
||||
: Game(width, height, renderer, gui) {
|
||||
// Initialize statistics to zero
|
||||
state.x_wins = 0;
|
||||
state.o_wins = 0;
|
||||
state.ties = 0;
|
||||
}
|
||||
|
||||
void TicTacToeGame::init() {
|
||||
// Clear the board
|
||||
for (int row = 0; row < 3; row++) {
|
||||
for (int col = 0; col < 3; col++) {
|
||||
state.board[row][col] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
state.current_player = 1; // X starts
|
||||
state.winner = 0;
|
||||
state.selected_row = 1; // Start in center
|
||||
state.selected_col = 1;
|
||||
state.game_over = false;
|
||||
state.total_moves = 0;
|
||||
|
||||
// Keep win statistics across games
|
||||
// state.x_wins, state.o_wins, state.ties remain unchanged
|
||||
}
|
||||
|
||||
uint8_t TicTacToeGame::check_winner() {
|
||||
// Check rows
|
||||
for (int row = 0; row < 3; row++) {
|
||||
if (state.board[row][0] != 0 &&
|
||||
state.board[row][0] == state.board[row][1] &&
|
||||
state.board[row][1] == state.board[row][2]) {
|
||||
return state.board[row][0];
|
||||
}
|
||||
}
|
||||
|
||||
// Check columns
|
||||
for (int col = 0; col < 3; col++) {
|
||||
if (state.board[0][col] != 0 &&
|
||||
state.board[0][col] == state.board[1][col] &&
|
||||
state.board[1][col] == state.board[2][col]) {
|
||||
return state.board[0][col];
|
||||
}
|
||||
}
|
||||
|
||||
// Check diagonals
|
||||
if (state.board[0][0] != 0 &&
|
||||
state.board[0][0] == state.board[1][1] &&
|
||||
state.board[1][1] == state.board[2][2]) {
|
||||
return state.board[0][0];
|
||||
}
|
||||
|
||||
if (state.board[0][2] != 0 &&
|
||||
state.board[0][2] == state.board[1][1] &&
|
||||
state.board[1][1] == state.board[2][0]) {
|
||||
return state.board[0][2];
|
||||
}
|
||||
|
||||
// Check for tie (board full)
|
||||
bool board_full = true;
|
||||
for (int row = 0; row < 3; row++) {
|
||||
for (int col = 0; col < 3; col++) {
|
||||
if (state.board[row][col] == 0) {
|
||||
board_full = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!board_full) break;
|
||||
}
|
||||
|
||||
if (board_full) return 3; // Tie
|
||||
return 0; // No winner yet
|
||||
}
|
||||
|
||||
bool TicTacToeGame::update(const InputEvent& event) {
|
||||
bool needs_refresh = false;
|
||||
|
||||
switch (event.type) {
|
||||
case INPUT_TOUCH_DOWN: {
|
||||
// If game is over, restart on touch
|
||||
if (state.game_over) {
|
||||
init();
|
||||
needs_refresh = true;
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Touch down at (%d,%d)\n", event.x, event.y);
|
||||
|
||||
// Calculate board position (must match draw()!)
|
||||
int board_x = width - BOARD_SIZE - 20;
|
||||
|
||||
// Check if touch is within board
|
||||
if (event.x >= board_x && event.x < board_x + BOARD_SIZE &&
|
||||
event.y >= BOARD_Y && event.y < BOARD_Y + BOARD_SIZE) {
|
||||
|
||||
int touched_col = (event.x - board_x) / CELL_SIZE;
|
||||
int touched_row = (event.y - BOARD_Y) / CELL_SIZE;
|
||||
|
||||
// Clamp to valid range (safety check)
|
||||
if (touched_row >= 0 && touched_row < 3 && touched_col >= 0 && touched_col < 3) {
|
||||
// Place piece if cell is empty
|
||||
if (state.board[touched_row][touched_col] == 0) {
|
||||
state.board[touched_row][touched_col] = state.current_player;
|
||||
state.total_moves++;
|
||||
|
||||
// Check for winner
|
||||
state.winner = check_winner();
|
||||
if (state.winner != 0) {
|
||||
state.game_over = true;
|
||||
if (state.winner == 1) state.x_wins++;
|
||||
else if (state.winner == 2) state.o_wins++;
|
||||
else if (state.winner == 3) state.ties++;
|
||||
} else {
|
||||
// Switch player
|
||||
state.current_player = (state.current_player == 1) ? 2 : 1;
|
||||
}
|
||||
|
||||
needs_refresh = true;
|
||||
|
||||
printf("Touch at [%d,%d] (pixel %d,%d) by player %d\n",
|
||||
touched_row, touched_col, event.x, event.y,
|
||||
state.current_player == 1 ? 2 : 1);
|
||||
} else {
|
||||
printf("Cell [%d,%d] already occupied\n", touched_row, touched_col);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("Touch outside board: %d,%d\n", event.x, event.y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case INPUT_BUTTON_0:
|
||||
// KEY0: Move selection (for button-only boards)
|
||||
if (!state.game_over) {
|
||||
// Move to next cell, skipping occupied ones
|
||||
int attempts = 0;
|
||||
do {
|
||||
state.selected_col++;
|
||||
if (state.selected_col > 2) {
|
||||
state.selected_col = 0;
|
||||
state.selected_row++;
|
||||
if (state.selected_row > 2) {
|
||||
state.selected_row = 0;
|
||||
}
|
||||
}
|
||||
attempts++;
|
||||
// If we've tried all 9 cells, stop (game might be full)
|
||||
if (attempts >= 9) break;
|
||||
} while (state.board[state.selected_row][state.selected_col] != 0);
|
||||
|
||||
needs_refresh = true;
|
||||
printf("Selection moved to [%d,%d]\n", state.selected_row, state.selected_col);
|
||||
} else {
|
||||
// Restart game
|
||||
init();
|
||||
needs_refresh = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case INPUT_BUTTON_1:
|
||||
// KEY1: Place piece at selected position
|
||||
if (!state.game_over) {
|
||||
if (state.board[state.selected_row][state.selected_col] == 0) {
|
||||
state.board[state.selected_row][state.selected_col] = state.current_player;
|
||||
state.total_moves++;
|
||||
|
||||
// Check for winner
|
||||
state.winner = check_winner();
|
||||
if (state.winner != 0) {
|
||||
state.game_over = true;
|
||||
if (state.winner == 1) state.x_wins++;
|
||||
else if (state.winner == 2) state.o_wins++;
|
||||
else if (state.winner == 3) state.ties++;
|
||||
} else {
|
||||
// Switch player
|
||||
state.current_player = (state.current_player == 1) ? 2 : 1;
|
||||
}
|
||||
|
||||
needs_refresh = true;
|
||||
|
||||
printf("Piece placed at [%d,%d]\n", state.selected_row, state.selected_col);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return needs_refresh;
|
||||
}
|
||||
|
||||
void TicTacToeGame::draw() {
|
||||
// Draw main window
|
||||
LowLevelWindow *w1 = gui->draw_new_window(10, 10, width - 20, height - 20, "Tic-Tac-Toe");
|
||||
|
||||
renderer->set_font(&font_5x5_obj);
|
||||
|
||||
// Draw current player or game result
|
||||
if (state.game_over) {
|
||||
if (state.winner == 1) {
|
||||
renderer->draw_string(20, 40, "X WINS!", true);
|
||||
} else if (state.winner == 2) {
|
||||
renderer->draw_string(20, 40, "O WINS!", true);
|
||||
} else {
|
||||
renderer->draw_string(20, 40, "TIE GAME!", true);
|
||||
}
|
||||
renderer->draw_string(20, 55, "Touch or KEY0 to restart", true);
|
||||
} else {
|
||||
char turn_text[30];
|
||||
snprintf(turn_text, sizeof(turn_text), "Turn: %s", state.current_player == 1 ? "X" : "O");
|
||||
renderer->draw_string(20, 40, turn_text, true);
|
||||
renderer->draw_string(20, 55, "Touch cell or use keys", true);
|
||||
}
|
||||
|
||||
// Draw game board (use same layout as touch detection!)
|
||||
int board_x = width - BOARD_SIZE - 20;
|
||||
|
||||
// Draw current turn indicator (large, on left side)
|
||||
if (!state.game_over) {
|
||||
int indicator_x = 60;
|
||||
int indicator_y = height / 2;
|
||||
int piece_offset = CELL_SIZE / 4;
|
||||
|
||||
if (state.current_player == 1) {
|
||||
// Draw large X
|
||||
renderer->draw_line(indicator_x - piece_offset, indicator_y - piece_offset,
|
||||
indicator_x + piece_offset, indicator_y + piece_offset, true, 4);
|
||||
renderer->draw_line(indicator_x + piece_offset, indicator_y - piece_offset,
|
||||
indicator_x - piece_offset, indicator_y + piece_offset, true, 4);
|
||||
} else {
|
||||
// Draw large O
|
||||
int piece_radius = CELL_SIZE / 4;
|
||||
renderer->draw_circle(indicator_x, indicator_y, piece_radius, true);
|
||||
renderer->draw_circle(indicator_x, indicator_y, piece_radius - 1, true);
|
||||
renderer->draw_circle(indicator_x, indicator_y, piece_radius - 2, true);
|
||||
renderer->draw_circle(indicator_x, indicator_y, piece_radius - 3, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw grid lines
|
||||
for (int i = 1; i < 3; i++) {
|
||||
// Vertical lines
|
||||
renderer->draw_line(board_x + i * CELL_SIZE, BOARD_Y,
|
||||
board_x + i * CELL_SIZE, BOARD_Y + BOARD_SIZE, true, 2);
|
||||
// Horizontal lines
|
||||
renderer->draw_line(board_x, BOARD_Y + i * CELL_SIZE,
|
||||
board_x + BOARD_SIZE, BOARD_Y + i * CELL_SIZE, true, 2);
|
||||
}
|
||||
|
||||
// Draw outer border
|
||||
renderer->draw_rectangle(board_x, BOARD_Y, BOARD_SIZE, BOARD_SIZE, true, 3);
|
||||
|
||||
// Draw X's and O's
|
||||
for (int row = 0; row < 3; row++) {
|
||||
for (int col = 0; col < 3; col++) {
|
||||
int cell_x = board_x + col * CELL_SIZE;
|
||||
int cell_y = BOARD_Y + row * CELL_SIZE;
|
||||
int center_x = cell_x + CELL_SIZE / 2;
|
||||
int center_y = cell_y + CELL_SIZE / 2;
|
||||
|
||||
// Highlight selected cell (for button navigation)
|
||||
if (!state.game_over && row == state.selected_row && col == state.selected_col) {
|
||||
renderer->draw_rectangle(cell_x + 5, cell_y + 5, CELL_SIZE - 10, CELL_SIZE - 10, true, 1);
|
||||
}
|
||||
|
||||
if (state.board[row][col] == 1) {
|
||||
// Draw X
|
||||
int offset = CELL_SIZE / 4;
|
||||
renderer->draw_line(center_x - offset, center_y - offset,
|
||||
center_x + offset, center_y + offset, true, 3);
|
||||
renderer->draw_line(center_x + offset, center_y - offset,
|
||||
center_x - offset, center_y + offset, true, 3);
|
||||
} else if (state.board[row][col] == 2) {
|
||||
// Draw O
|
||||
int radius = CELL_SIZE / 4;
|
||||
renderer->draw_circle(center_x, center_y, radius, true);
|
||||
renderer->draw_circle(center_x, center_y, radius - 1, true);
|
||||
renderer->draw_circle(center_x, center_y, radius - 2, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw statistics at bottom
|
||||
char stats[60];
|
||||
snprintf(stats, sizeof(stats), "X:%d O:%d Tie:%d Moves:%d",
|
||||
state.x_wins, state.o_wins, state.ties, state.total_moves);
|
||||
renderer->draw_string(20, height - 40, stats, true);
|
||||
}
|
||||
78
games/tic_tac_toe.h
Normal file
78
games/tic_tac_toe.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// ============================================================================
|
||||
// TIC-TAC-TOE GAME HEADER
|
||||
// ============================================================================
|
||||
// Concrete implementation of the Game interface for Tic-Tac-Toe
|
||||
|
||||
#ifndef TIC_TAC_TOE_H
|
||||
#define TIC_TAC_TOE_H
|
||||
|
||||
#include "game.h"
|
||||
|
||||
/**
|
||||
* @brief Tic-Tac-Toe game implementation
|
||||
*
|
||||
* Classic two-player Tic-Tac-Toe game with:
|
||||
* - Touch input: Tap cells to place pieces
|
||||
* - Button input: KEY0 moves selection, KEY1 places piece
|
||||
* - Win detection: Rows, columns, and diagonals
|
||||
* - Statistics tracking: Wins, ties, total moves
|
||||
* - Visual feedback: Selection highlight, turn indicator
|
||||
*/
|
||||
class TicTacToeGame : public Game {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new Tic-Tac-Toe game
|
||||
* @param width Display width in pixels
|
||||
* @param height Display height in pixels
|
||||
* @param renderer Pointer to low-level rendering interface
|
||||
* @param gui Pointer to GUI drawing primitives
|
||||
*/
|
||||
TicTacToeGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui);
|
||||
|
||||
/**
|
||||
* @brief Initialize game state (reset board, keep statistics)
|
||||
*/
|
||||
void init() override;
|
||||
|
||||
/**
|
||||
* @brief Update game state based on input event
|
||||
* @param event Input event from InputManager
|
||||
* @return true if screen redraw is needed
|
||||
*/
|
||||
bool update(const InputEvent& event) override;
|
||||
|
||||
/**
|
||||
* @brief Draw the game to the display buffer
|
||||
*/
|
||||
void draw() override;
|
||||
|
||||
private:
|
||||
// Game state
|
||||
struct GameState {
|
||||
uint8_t board[3][3]; // 0=empty, 1=X, 2=O
|
||||
uint8_t current_player; // 1=X, 2=O
|
||||
uint8_t winner; // 0=none, 1=X wins, 2=O wins, 3=tie
|
||||
uint8_t selected_row; // Currently selected cell (for button navigation)
|
||||
uint8_t selected_col;
|
||||
bool game_over;
|
||||
|
||||
// Game statistics
|
||||
uint32_t x_wins;
|
||||
uint32_t o_wins;
|
||||
uint32_t ties;
|
||||
uint32_t total_moves;
|
||||
} state;
|
||||
|
||||
/**
|
||||
* @brief Check if there's a winner on the board
|
||||
* @return 0=no winner, 1=X wins, 2=O wins, 3=tie
|
||||
*/
|
||||
uint8_t check_winner();
|
||||
|
||||
// Board layout constants
|
||||
static const int BOARD_SIZE = 200;
|
||||
static const int CELL_SIZE = BOARD_SIZE / 3;
|
||||
static const int BOARD_Y = 80; // Y position below title
|
||||
};
|
||||
|
||||
#endif // TIC_TAC_TOE_H
|
||||
Reference in New Issue
Block a user