ttt.c 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /*
  2. * ttt - Simple arbitrary-size tic-tac-toe game
  3. * Copyright (C) 2015 David McMackins II
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as published by
  7. * the Free Software Foundation, version 3 only.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU Affero General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include <stdio.h>
  18. #include <time.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <stdbool.h>
  22. #include <ctype.h>
  23. #include <stdint.h>
  24. #ifndef BOARD_SIZE
  25. /**
  26. * @brief The square dimensions of the game board (best when >=1 and <=26).
  27. */
  28. # define BOARD_SIZE 3
  29. #endif
  30. /**
  31. * @brief Data structure for move position.
  32. */
  33. struct move
  34. {
  35. uint8_t h; // horizontal position
  36. uint8_t v; // vertical position
  37. };
  38. /**
  39. * @brief Storage for the board state.
  40. */
  41. char (*board)[BOARD_SIZE];
  42. /**
  43. * @brief Calculates the number of spaces by which to bump the board.
  44. * @param s The size of the board for which to bump.
  45. * @return The bump size for the given board.
  46. */
  47. static uint8_t
  48. get_bump (uint8_t s)
  49. {
  50. uint8_t i = 1;
  51. while (s > 10)
  52. {
  53. ++i;
  54. s /= 10;
  55. }
  56. return i;
  57. }
  58. /**
  59. * @brief Draws horizontal bar.
  60. */
  61. static void
  62. draw_horiz_bar (void)
  63. {
  64. uint8_t i;
  65. for (i = 0; i <= get_bump (BOARD_SIZE); ++i)
  66. putchar (' ');
  67. putchar ('+');
  68. for (i = 0; i < BOARD_SIZE; ++i)
  69. printf ("---+");
  70. putchar ('\n');
  71. }
  72. /**
  73. * @brief Draws the board on the screen with some white space above it.
  74. */
  75. static void
  76. draw_board (void)
  77. {
  78. uint8_t i, j, bump = get_bump (BOARD_SIZE);
  79. printf ("\n\n\n");
  80. for (i = 0; i < bump; ++i)
  81. putchar (' ');
  82. for (i = 0; i < BOARD_SIZE; ++i)
  83. printf (" %c", i + 'A');
  84. putchar ('\n');
  85. draw_horiz_bar ();
  86. for (i = 0; i < BOARD_SIZE; ++i)
  87. {
  88. printf ("%d", i);
  89. // make sure board lines up on all rows
  90. uint8_t b = bump - get_bump (i+1);
  91. for (j = 0; j < b; ++j)
  92. putchar (' ');
  93. printf (" |");
  94. for (j = 0; j < BOARD_SIZE; ++j)
  95. printf (" %c |", board[i][j]);
  96. putchar ('\n');
  97. draw_horiz_bar ();
  98. }
  99. }
  100. /**
  101. * @brief Gets a player's desired move.
  102. * @param player The player for which to obtain the move.
  103. * @return Move data based on player input.
  104. */
  105. static struct move
  106. get_move (char player)
  107. {
  108. char h;
  109. struct move m = {
  110. .h = BOARD_SIZE,
  111. .v = BOARD_SIZE
  112. };
  113. printf ("%c's move: ", player);
  114. scanf ("%c%hhu", &h, &m.v);
  115. // discard extra input to prevent cheating
  116. while (getchar () != '\n')
  117. ;
  118. m.h = toupper (h) - 'A';
  119. return m;
  120. }
  121. /**
  122. * @brief Checks if a player has won.
  123. * @param player The player for which to check win status.
  124. * @return true if the player has BOARD_SIZE in a row.
  125. */
  126. static bool
  127. is_winner (char player)
  128. {
  129. int16_t i, j;
  130. // check rows
  131. for (i = 0; i < BOARD_SIZE; ++i)
  132. for (j = 0; j < BOARD_SIZE; ++j)
  133. {
  134. if (board[i][j] != player)
  135. break;
  136. if ((BOARD_SIZE - 1) == j)
  137. return true;
  138. }
  139. // check columns
  140. for (i = 0; i < BOARD_SIZE; ++i)
  141. for (j = 0; j < BOARD_SIZE; ++j)
  142. {
  143. if (board[j][i] != player)
  144. break;
  145. if ((BOARD_SIZE - 1) == j)
  146. return true;
  147. }
  148. // check diagonal from top-left to bottom-right
  149. for (i = 0, j = 0; i < BOARD_SIZE && j < BOARD_SIZE; ++i, ++j)
  150. {
  151. if (board[i][j] != player)
  152. break;
  153. if ((BOARD_SIZE - 1) == i)
  154. return true;
  155. }
  156. // check diagonal from top-right to bottom-left
  157. for (i = 0, j = BOARD_SIZE-1; i < BOARD_SIZE && j >= 0; ++i, --j)
  158. {
  159. if (board[i][j] != player)
  160. break;
  161. if (0 == j)
  162. return true;
  163. }
  164. return false;
  165. }
  166. /**
  167. * @brief Checks if the board is in a winning state.
  168. * @return The player who wins or 0 if no winner.
  169. */
  170. static char
  171. find_winner (void)
  172. {
  173. char players[] = {'X', 'O'};
  174. uint8_t i;
  175. for (i = 0; i < 2; ++i)
  176. if (is_winner (players[i]))
  177. return players[i];
  178. return 0;
  179. }
  180. int
  181. main (void)
  182. {
  183. char turn = 'X', winner;
  184. uint16_t num_turns = 0;
  185. board = malloc (sizeof (char[BOARD_SIZE][BOARD_SIZE]));
  186. if (NULL == board)
  187. {
  188. fputs ("Error allocating board", stderr);
  189. return 1;
  190. }
  191. uint8_t i, j;
  192. for (i = 0; i < BOARD_SIZE; ++i)
  193. for (j = 0; j < BOARD_SIZE; ++j)
  194. board[i][j] = ' ';
  195. while (!(winner = find_winner ()) && num_turns < BOARD_SIZE * BOARD_SIZE)
  196. {
  197. draw_board ();
  198. struct move m = get_move (turn);
  199. if (m.h >= BOARD_SIZE || m.v >= BOARD_SIZE)
  200. {
  201. fputs ("That space is not on the board!", stderr);
  202. continue;
  203. }
  204. if (' ' != board[m.v][m.h])
  205. {
  206. fputs ("That space is already taken!", stderr);
  207. continue;
  208. }
  209. board[m.v][m.h] = turn;
  210. turn = turn == 'X' ? 'O' : 'X';
  211. ++num_turns;
  212. }
  213. draw_board ();
  214. free (board);
  215. if (!winner)
  216. puts ("No one wins. :(");
  217. else
  218. printf ("%c wins!\n", winner);
  219. return 0;
  220. }