123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- /*
- * ttt - Simple arbitrary-size tic-tac-toe game
- * Copyright (C) 2015 David McMackins II
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, version 3 only.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <stdio.h>
- #include <time.h>
- #include <stdlib.h>
- #include <string.h>
- #include <stdbool.h>
- #include <ctype.h>
- #include <stdint.h>
- #ifndef BOARD_SIZE
- /**
- * @brief The square dimensions of the game board (best when >=1 and <=26).
- */
- # define BOARD_SIZE 3
- #endif
- /**
- * @brief Data structure for move position.
- */
- struct move
- {
- uint8_t h; // horizontal position
- uint8_t v; // vertical position
- };
- /**
- * @brief Storage for the board state.
- */
- char (*board)[BOARD_SIZE];
- /**
- * @brief Calculates the number of spaces by which to bump the board.
- * @param s The size of the board for which to bump.
- * @return The bump size for the given board.
- */
- static uint8_t
- get_bump (uint8_t s)
- {
- uint8_t i = 1;
- while (s > 10)
- {
- ++i;
- s /= 10;
- }
- return i;
- }
- /**
- * @brief Draws horizontal bar.
- */
- static void
- draw_horiz_bar (void)
- {
- uint8_t i;
- for (i = 0; i <= get_bump (BOARD_SIZE); ++i)
- putchar (' ');
-
- putchar ('+');
- for (i = 0; i < BOARD_SIZE; ++i)
- printf ("---+");
- putchar ('\n');
- }
- /**
- * @brief Draws the board on the screen with some white space above it.
- */
- static void
- draw_board (void)
- {
- uint8_t i, j, bump = get_bump (BOARD_SIZE);
- printf ("\n\n\n");
- for (i = 0; i < bump; ++i)
- putchar (' ');
- for (i = 0; i < BOARD_SIZE; ++i)
- printf (" %c", i + 'A');
- putchar ('\n');
-
- draw_horiz_bar ();
- for (i = 0; i < BOARD_SIZE; ++i)
- {
- printf ("%d", i);
- // make sure board lines up on all rows
- uint8_t b = bump - get_bump (i+1);
- for (j = 0; j < b; ++j)
- putchar (' ');
- printf (" |");
- for (j = 0; j < BOARD_SIZE; ++j)
- printf (" %c |", board[i][j]);
- putchar ('\n');
- draw_horiz_bar ();
- }
- }
- /**
- * @brief Gets a player's desired move.
- * @param player The player for which to obtain the move.
- * @return Move data based on player input.
- */
- static struct move
- get_move (char player)
- {
- char h;
- struct move m = {
- .h = BOARD_SIZE,
- .v = BOARD_SIZE
- };
- printf ("%c's move: ", player);
- scanf ("%c%hhu", &h, &m.v);
- // discard extra input to prevent cheating
- while (getchar () != '\n')
- ;
- m.h = toupper (h) - 'A';
- return m;
- }
- /**
- * @brief Checks if a player has won.
- * @param player The player for which to check win status.
- * @return true if the player has BOARD_SIZE in a row.
- */
- static bool
- is_winner (char player)
- {
- int16_t i, j;
- // check rows
- for (i = 0; i < BOARD_SIZE; ++i)
- for (j = 0; j < BOARD_SIZE; ++j)
- {
- if (board[i][j] != player)
- break;
- if ((BOARD_SIZE - 1) == j)
- return true;
- }
- // check columns
- for (i = 0; i < BOARD_SIZE; ++i)
- for (j = 0; j < BOARD_SIZE; ++j)
- {
- if (board[j][i] != player)
- break;
- if ((BOARD_SIZE - 1) == j)
- return true;
- }
- // check diagonal from top-left to bottom-right
- for (i = 0, j = 0; i < BOARD_SIZE && j < BOARD_SIZE; ++i, ++j)
- {
- if (board[i][j] != player)
- break;
- if ((BOARD_SIZE - 1) == i)
- return true;
- }
- // check diagonal from top-right to bottom-left
- for (i = 0, j = BOARD_SIZE-1; i < BOARD_SIZE && j >= 0; ++i, --j)
- {
- if (board[i][j] != player)
- break;
- if (0 == j)
- return true;
- }
- return false;
- }
- /**
- * @brief Checks if the board is in a winning state.
- * @return The player who wins or 0 if no winner.
- */
- static char
- find_winner (void)
- {
- char players[] = {'X', 'O'};
- uint8_t i;
- for (i = 0; i < 2; ++i)
- if (is_winner (players[i]))
- return players[i];
- return 0;
- }
- int
- main (void)
- {
- char turn = 'X', winner;
- uint16_t num_turns = 0;
- board = malloc (sizeof (char[BOARD_SIZE][BOARD_SIZE]));
- if (NULL == board)
- {
- fputs ("Error allocating board", stderr);
- return 1;
- }
- uint8_t i, j;
- for (i = 0; i < BOARD_SIZE; ++i)
- for (j = 0; j < BOARD_SIZE; ++j)
- board[i][j] = ' ';
- while (!(winner = find_winner ()) && num_turns < BOARD_SIZE * BOARD_SIZE)
- {
- draw_board ();
- struct move m = get_move (turn);
- if (m.h >= BOARD_SIZE || m.v >= BOARD_SIZE)
- {
- fputs ("That space is not on the board!", stderr);
- continue;
- }
- if (' ' != board[m.v][m.h])
- {
- fputs ("That space is already taken!", stderr);
- continue;
- }
- board[m.v][m.h] = turn;
- turn = turn == 'X' ? 'O' : 'X';
- ++num_turns;
- }
- draw_board ();
- free (board);
- if (!winner)
- puts ("No one wins. :(");
- else
- printf ("%c wins!\n", winner);
- return 0;
- }
|