Forget that connect 4 function. Apart from being unnecessarily cryptic, checking for a win in connect 4 is more complex than in tac-tac-toe.
In tic-tac-toe there are eight lines to check for - three rows, three columns and two diagonals. The first thing I'm going to suggest is using a single-dimensional array for the board, so the array indexes are as follows...
+---+---+---+
| 0 | 1 | 2 |
+---+---+---+
| 3 | 4 | 5 |
+---+---+---+
| 6 | 7 | 8 |
+---+---+---+
The reason for using a two-dimensional array would be to make it easier to identify locations and easier to loop over them. In this case, I'm going to argue that because the board is so small, it's actually easier to avoid the two dimensions issue. I'm also going to avoid loops, at least for a first attempt, since the scale of the problem is small enough to allow that. So...
bool line_is_full (Board* p_board, char p_player, int p1, int p2, int p3)
{
return ( (p_board [p1] == p_player)
&& (p_board [p2] == p_player)
&& (p_board [p3] == p_player));
}
bool board_has_win (Board* p_board, char p_player)
{
return ( line_is_full (p_board, p_player, 0, 1, 2)
|| line_is_full (p_board, p_player, 3, 4, 5)
|| line_is_full (p_board, p_player, 6, 7, 8)
|| line_is_full (p_board, p_player, 0, 3, 6)
|| line_is_full (p_board, p_player, 1, 4, 7)
|| line_is_full (p_board, p_player, 2, 5, 8)
|| line_is_full (p_board, p_player, 0, 4, 8)
|| line_is_full (p_board, p_player, 2, 4, 6));
}
Only checking for one player winning is valid because the only player you need to check for is the player who has just made a move. I'm also not checking for a draw here - just keep count of how many moves have been made to handle that.
Obvious things that could be improved...
The blank lines in board_has_win
are there to emphasize groups of similar cases. You could have a loop over the three rows and a loop over the three columns.
Alternatively, board_has_win
is an "unwound" loop over all eight lines. There are several ways this could be expressed using a loop. Possibly the most sensible is to have a data table (giving the three cell numbers for each of the eight lines), so you can directly loop over the eight lines.
line_is_full
could obviously be rewritten as a loop over the three cells if only those cells were passed as an array.
Alternatively, for each line (given the way I've specified them), (p2-p1) == (p3-p2)
- there's a constant stepping distance between cells. Also (p1+p3)/2 == p2
- the position of the middle cell is the average of the two end cells. That means given any two cells (provided you know which two) you can determine the first and the stepping distance and loop over all three.
Personally I'd ignore points 3 and 4 - having a loop rather than three cases isn't really worth even that small amount of extra complexity. I'd probably focus on point 2 - that table of cell-numbers for each row could have other uses in the program.
Here's a tip I think I first saw back in ye olden days when computer magazines would print listings in BASIC. Each of the lines has three cells. What happens if you assign O
the value 1
and X
the value 4
(and zero for empty)? Consider the total of the values for the three cells in a line - here's a table...
0 .... All three cells empty
1 .... One O, two empty cells
2 .... Two Os, one empty cell
3 .... Three Os - O wins
4 .... One X, two empty cells
5 .... One X, one O, one empty cell
6 .... One X, two Os
7 .... Impossible
8 .... Two Xs, one empty cell
9 .... Two Xs, one O
10 .... Impossible
11 .... Impossible
12 .... Three Xs - X wins
13+ ... Impossible
This is a kind of special-case hash table. This is useful not only for spotting a win but also for a very simple AI.