// ============================================================================ // 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 #include // 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); } #ifdef BUTTON_KEY0_PIN renderer->draw_string(20, 55, "Touch or KEY0 to restart", true); #else renderer->draw_string(20, 55, "Touch to restart", true); #endif } 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); #ifdef BUTTON_KEY0_PIN renderer->draw_string(20, 55, "Touch cell or use keys", true); #else renderer->draw_string(20, 55, "Touch cell to play", true); #endif } // 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); }