Console Connect 4
Author |
Message |
Insectoid
|
Posted: Sat Jan 05, 2013 6:21 pm Post subject: Console Connect 4 |
|
|
I haven't written any code in a while, and inspired by this thread, I decided to make my own. I've explained how it works in the comments.
Now, I'm not entirely sure if I've covered all of the win conditions. I think I did (except the draw), but if you find a broken play, let me know.
c: |
#include <stdio.h>
void drawTable (char[7][6]); //Obviously, this draws the table
int placeMarker (char table [7][6], int column, char player ); //self-explanatory. Returns 1 for success, 0 for failure (inputting to a full row, for example)
int win (char table [7][6], char player ); //return 1 if arg 'player' has won. 0 otherwise.
int main (){
char table [7][6]; //the game board
char player [2] = {'X', 'O'}; //each player's token, so I can change it easily.
int column; //the column a player has selected
int turn; //records which player's turn it is
//Initialize game board with empty space
for (int i = 0; i < 7; i++ ){
for (int j = 0; j < 6; j++ ){
table [i ][j ] = ' ';
}
}
for (turn = 0;!win (table, player [turn*- 1+ 1]); turn = turn*- 1 + 1){ //check for a win, and switch the turn
do {
printf ("Player %c, enter column number: ", player [turn ]);
scanf ("%d", &column );
} while (column < 0 || column > 6 || !placeMarker (table, column, player [turn ])); //Check for valid input and place a token
drawTable (table ); //Duh.
}
printf ("\n%c wins!\n\n", player [turn*- 1+ 1]); //Victory message. The player has to be switched or the wrong one will display.
}
int win (char table [7][6], char player ){
//check for vertical win
for (int i = 0; i < 7; i++ ){
for (int j = 0; j < 4; j++ ){
//I could probably clean this up a bit, but who really cares.
if (player == table [i ][j ] && player == table [i ][j+ 1] && player == table [i ][j+ 2] && player == table [i ][j+ 3]) return 1;
}
}
//check for horizontal win
for (int j = 0; j < 6; j++ ){
for (int i = 0; i < 5; i++ ){
if (player == table [i ][j ] && player == table [i+ 1][j ] && player == table [i+ 2][j ] && player == table [i+ 3][j ]) return 1;
}
}
//check for diagonal win
for (int i = 0; i < 4; i++ ){
for (int j = 0; j < 3; j++ ){
//holy shit this is a mess. I'm pretty sure it works, but I didn't actually confirm it.
if (player == table [i ][j ] && player == table [i+ 1][j+ 1] && player == table [i+ 2][j+ 2] && player == table [i+ 3][j+ 3]) return 1;
if (player == table [6-i ][j ] && player == table [5-i ][j+ 1] && player == table [4-i ][j+ 2] && player == table [3-i ][j+ 3]) return 1;
if (player == table [i ][5-j ] && player == table [i+ 1][4-j ] && player == table [i+ 2][3-j ] && player == table [i+ 3][2-j ]) return 1;
if (player == table [6-i ][5-j ] && player == table [5-i ][4-j ] && player == table [4-i ][3-j ] && player == table [3-i ][2-j ]) return 1;
}
}
return 0;
}
void drawTable (char table [7][6]){
printf ("\n\n.0.1.2.3.4.5.6.");
for (int j = 0; j < 6; j++ ){ //draw each cell of the table, with dividers
printf ("\n|");
for (int i = 0; i < 7; i++ ){
printf ("%c|", table [i ][j ]);
}
if (j == 5) printf ("\n\\^.^.^.^.^.^.^/"); //special case for the bottom row. If someone can make this look better, that'd be cool.
else printf ("\n|-|-|-|-|-|-|-|");
}
printf ("\n\n");
}
int placeMarker (char table [7][6], int column, char player ){
int i = column; //I wanted 'column' to be the parameter name but didn't want to use it in the function body.
for (int j = 0; j < 6; j++ ){
if (table [i ][j ] != ' ') {//move down the column to find the lowest empty cell
if (j == 0) return 0; //special case if the column is full
table [i ][j- 1] = player; //fill the previous cell (since this one is full)
return 1; //return success
}
}
table [i ][5] = player; //special case for an empty column
return 1;
} |
|
|
|
|
|
|
Sponsor Sponsor
|
|
|
Insectoid
|
Posted: Sat Jan 05, 2013 8:10 pm Post subject: Re: Console Connect 4 |
|
|
All right, now the game will end if there's a draw. At least, I'm pretty sure it will. Hopefully I didn't break something else along the way.
c: | #include <stdio.h>
void drawTable (char[7][6]); //Obviously, this draws the table
int placeMarker (char table [7][6], int column, char player ); //self-explanatory. Returns 1 for success, 0 for failure (inputting to a full row, for example)
int win (char table [7][6], char player ); //return 1 if arg 'player' has won. 0 otherwise.
int main (){
char table [7][6]; //the game board
char player [2] = {'X', 'O'}; //each player's token, so I can change it easily.
int column; //the column a player has selected
int turn; //records which player's turn it is
int turnCount; //keeps track of number of turns played
//Initialize game board with empty space
for (int i = 0; i < 7; i++ ){
for (int j = 0; j < 6; j++ ){
table [i ][j ] = ' ';
}
}
for (turnCount = 0;!win (table, player [turn*- 1+ 1])&&turnCount< 42; turn = (++turnCount )% 2){ //check for a win, and switch the turn
do {
printf ("Turn %d, Player %c, enter column number: ", turnCount, player [turn ]);
scanf ("%d", &column );
} while (column < 0 || column > 6 || !placeMarker (table, column, player [turn ])); //Check for valid input and place a token
drawTable (table ); //Duh.
}
if (turnCount >= 41) printf ("\nDraw!\n\n");
else printf ("\n%c wins!\n\n", player [turn*- 1+ 1]); //Victory message. The player has to be switched or the wrong one will display.
}
int win (char table [7][6], char player ){
//check for vertical win
for (int i = 0; i < 7; i++ ){
for (int j = 0; j < 4; j++ ){
//I could probably clean this up a bit, but who really cares.
if (player == table [i ][j ] && player == table [i ][j+ 1] && player == table [i ][j+ 2] && player == table [i ][j+ 3]) return 1;
}
}
//check for horizontal win
for (int j = 0; j < 6; j++ ){
for (int i = 0; i < 5; i++ ){
if (player == table [i ][j ] && player == table [i+ 1][j ] && player == table [i+ 2][j ] && player == table [i+ 3][j ]) return 1;
}
}
//check for diagonal win
for (int i = 0; i < 4; i++ ){
for (int j = 0; j < 3; j++ ){
//holy shit this is a mess. I'm pretty sure it works, but I didn't actually confirm it.
if (player == table [i ][j ] && player == table [i+ 1][j+ 1] && player == table [i+ 2][j+ 2] && player == table [i+ 3][j+ 3]) return 1;
if (player == table [6-i ][j ] && player == table [5-i ][j+ 1] && player == table [4-i ][j+ 2] && player == table [3-i ][j+ 3]) return 1;
if (player == table [i ][5-j ] && player == table [i+ 1][4-j ] && player == table [i+ 2][3-j ] && player == table [i+ 3][2-j ]) return 1;
if (player == table [6-i ][5-j ] && player == table [5-i ][4-j ] && player == table [4-i ][3-j ] && player == table [3-i ][2-j ]) return 1;
}
}
return 0;
}
void drawTable (char table [7][6]){
printf ("\n\n.0.1.2.3.4.5.6.");
for (int j = 0; j < 6; j++ ){ //draw each cell of the table, with dividers
printf ("\n|");
for (int i = 0; i < 7; i++ ){
printf ("%c|", table [i ][j ]);
}
if (j == 5) printf ("\n\\^.^.^.^.^.^.^/"); //special case for the bottom row. If someone can make this look better, that'd be cool.
else printf ("\n|-|-|-|-|-|-|-|");
}
printf ("\n\n");
}
int placeMarker (char table [7][6], int column, char player ){
int i = column; //I wanted 'column' to be the parameter name but didn't want to use it in the function body.
for (int j = 0; j < 6; j++ ){
if (table [i ][j ] != ' ') {//move down the column to find the lowest empty cell
if (j == 0) return 0; //special case if the column is full
table [i ][j- 1] = player; //fill the previous cell (since this one is full)
return 1; //return success
}
}
table [i ][5] = player; //special case for an empty column
return 1;
} |
|
|
|
|
|
|
Zren
|
Posted: Sun Jan 06, 2013 12:13 pm Post subject: RE:Console Connect 4 |
|
|
That win condition looks unholy.
Why bother checking the whole board? If you're guaranteed a board without a win condition at the beginning, you only have to check the win conditions relative to the played piece.
Something like:
code: |
bool isWinningPiece(board, pos):
player = board[pos]
// Check win conditions
for each vector in (up, NE, right, SE):
// Start with the origin being the played piece, then move the origin in the inverse direction to the vector. This should put the piece we're checking (isWinningPiece) in all 4 possible positions.
for i in range(4):
origin = pos
origin -= inverse(vector) * i
// Check each piece for a line
count = 0
for j in range(4):
(x,y) = (origin.x + vector.x * j, origin.y + vector.y * j)
if board[(x,y)] == player:
count++
if count >= 4:
return true
return false
|
A crazier way to do it would be to fire rays in opposite directions and get the distance the two rays travelled. The sum() + 1 >= 4 then you win!
I like how you did the tie game condition, even if you abused the hell out of the for statement. |
|
|
|
|
|
Insectoid
|
Posted: Sun Jan 06, 2013 12:30 pm Post subject: RE:Console Connect 4 |
|
|
Hmm, I dunno how I missed that solution for the win condition. "Hur dur, player 2 can't win on player 1's turn, but let's check all the pieces that can't possibly win anyway".
As for the for loops, I love doing that. It's completely illegible, sure, but it's so much fun to write. It's always fun to look at your old code and think, how the hell did I do that? Unless you actually have to use that code. Then it sucks. |
|
|
|
|
|
Insectoid
|
Posted: Sun Jan 06, 2013 6:54 pm Post subject: Re: Console Connect 4 |
|
|
All right. I've re-written my win function (currently under win_). It's still a little long, and still a little messy, but it's much better. I'm using a half-adder to iterate over 3 vectors that I pass to another function that counts the consecutive like tokens on that vector. I still need a special case for the (-1, 1) vector because the adder can't return a -1.
My for loops are getting beyond ridiculous now. I've kinda just started putting the loop body into the incrementor, which isn't really cool; it's just a mess. I don't really like using commas in for loops. I don't think I'm gonna do that anymore.
c: |
#include <stdio.h>
void drawTable (char[7][6]); //Obviously, this draws the table
int placeMarker (char table [7][6], int column, char player ); //self-explanatory. Returns 1 for success, 0 for failure (inputting to a full row, for example)
int win (char table [7][6], char player ); //return 1 if arg 'player' has won. 0 otherwise.
int win_ (char table [7][6], int column );
int checkLine (char table [7][6], int column, int x, int y );
int main (){
char table [7][6]; //the game board
char player [2] = {'X', 'O'}; //each player's token, so I can change it easily.
int column= 0; //the column a player has selected
int turn; //records which player's turn it is
int turnCount; //keeps track of number of turns played
//Initialize game board with empty space
for (int i = 0; i < 7; i++ ){
for (int j = 0; j < 6; j++ ){
table [i ][j ] = ' ';
}
}
drawTable (table );
//for (turnCount = 0;!win(table, player[turn*-1+1])&&turnCount<42; turn = (++turnCount)%2){ //check for a win, and switch the turn
for (turnCount = 0;!win_ (table, column )&&turnCount< 42; turn = (++turnCount )% 2){
do {
printf ("Turn %d, Player %c, enter column number: ", turnCount, player [turn ]);
scanf ("%d", &column );
} while (column < 0 || column > 6 || !placeMarker (table, column, player [turn ])); //Check for valid input and place a token
drawTable (table ); //Duh.
}
if (turnCount >= 41) printf ("\nDraw!\n\n");
else printf ("\n%c wins!\n\n", player [turn*- 1+ 1]); //Victory message. The player has to be switched or the wrong one will display.
}
//This isn't ever called atm. I've left it in so at least I still have a win detector that works.
int win (char table [7][6], char player ){
//check for vertical win
for (int i = 0; i < 7; i++ ){
for (int j = 0; j < 4; j++ ){
//I could probably clean this up a bit, but who really cares.
if (player == table [i ][j ] && player == table [i ][j+ 1] && player == table [i ][j+ 2] && player == table [i ][j+ 3]) return 1;
}
}
//check for horizontal win
for (int j = 0; j < 6; j++ ){
for (int i = 0; i < 5; i++ ){
if (player == table [i ][j ] && player == table [i+ 1][j ] && player == table [i+ 2][j ] && player == table [i+ 3][j ]) return 1;
}
}
//check for diagonal win
for (int i = 0; i < 4; i++ ){
for (int j = 0; j < 3; j++ ){
//holy shit this is a mess. I'm pretty sure it works, but I didn't actually confirm it.
if (player == table [i ][j ] && player == table [i+ 1][j+ 1] && player == table [i+ 2][j+ 2] && player == table [i+ 3][j+ 3]) return 1;
if (player == table [6-i ][j ] && player == table [5-i ][j+ 1] && player == table [4-i ][j+ 2] && player == table [3-i ][j+ 3]) return 1;
if (player == table [i ][5-j ] && player == table [i+ 1][4-j ] && player == table [i+ 2][3-j ] && player == table [i+ 3][2-j ]) return 1;
if (player == table [6-i ][5-j ] && player == table [5-i ][4-j ] && player == table [4-i ][3-j ] && player == table [3-i ][2-j ]) return 1;
}
}
return 0;
}
//I've considered changing this to only search in one direction (cut the 2nd big for loop)
//and return total instead. Let win_ iterate over both directions instead and add the totals
//together. But I haven't found a cool loop to do that iteration yet.
int checkLine (char table [7][6], int column, int x, int y ){
int i = column;
int j;
for (j = 0; j < 7 && table [i ][j ] == ' '; j++ );
if (j> 5) return 0; //empty column
int total = 0;
for (int _i = i+x, _j = j+y;table [_i ][_j ] == table [i ][j ] && _i < 7 && _j < 6; _i+=x,_j+=y,total++ );
for (int _i = i-x, _j=j-y;table [_i ][_j ] == table [i ][j ] && _i >= 0 && _j >= 0;_i-=x,_j-=y,total++ ); //check the other direction
if (total >= 3) return 1;
return 0;
}
int win_ (char table [7][6], int column ){
for (int x= 0, y= 1; (x|y );x ^= y, y = !y ){ //I really, really like this loop. But it misses the (-1, 1) vector.
if (checkLine (table, column, x, y )) return 1;
}
if (checkLine (table, column, - 1, 1)) return 1; //I want to change the above loop to include this case.
return 0;
}
void drawTable (char table [7][6]){
printf ("\n\n.0.1.2.3.4.5.6.");
for (int j = 0; j < 6; j++ ){ //draw each cell of the table, with dividers
printf ("\n|");
for (int i = 0; i < 7; i++ ){
printf ("%c|", table [i ][j ]);
}
}
printf ("\n\\^.^.^.^.^.^.^/");
printf ("\n\n");
}
int placeMarker (char table [7][6], int column, char player ){
int i = column; //I wanted 'column' to be the parameter name but didn't want to use it in the function body.
for (int j = 0; j < 6; j++ ){
if (table [i ][j ] != ' ') {//move down the column to find the lowest empty cell
if (j == 0) return 0; //special case if the column is full
table [i ][j- 1] = player; //fill the previous cell (since this one is full)
return 1; //return success
}
}
table [i ][5] = player; //special case for an empty column
return 1;
} |
|
|
|
|
|
|
Insectoid
|
Posted: Sun Jan 06, 2013 7:32 pm Post subject: RE:Console Connect 4 |
|
|
Blah. I relented and switched to an array of vectors (sort of).
c: | int win_ (char table[7][6], int column){
int vectors[8] = {0,1,-1,0,1,1,-1,1};
//for (int x=0, y=1;(x|y);x ^= y, y = !y){ //I really, really like this loop. But it misses the (-1, 1) vector.
// if (checkLine (table, column, x, y)) return 1;
//}
for (int *i = vectors; i < vectors+8; i+=2){
if (checkLine (table, column, *i, *(i+1))) return 1;
}
if (checkLine (table, column, -1, 1)) return 1; //I want to change the above loop to include this case.
return 0;
} |
|
|
|
|
|
|
Zren
|
Posted: Thu Jan 10, 2013 11:11 pm Post subject: RE:Console Connect 4 |
|
|
Finally got around to deciphering your crazy algorithms.
Is ^= different from != ?
Ah, so it is.
http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B
Bitwise XOR assignment a ^= b a = a ^ b
Hmmm. So win_ origionally ran like so:
code: |
x y
0 1
1 0
1 1
0 0 -> exit before running.
|
You could calculate the 4 vectors iteratively with:
(x = 1, y = -1; x >= 0; x -= y == 1, y += y < 1)
The second one was pretty interesting as I've never dealt with pointers in such a fashion. |
|
|
|
|
|
Insectoid
|
Posted: Fri Jan 11, 2013 9:08 am Post subject: RE:Console Connect 4 |
|
|
Quote: The second one was pretty interesting as I've never dealt with pointers in such a fashion.
It's a pretty common way of iterating over an array in C (common enough that my prof taught it, anyway), the only difference being that I incremented by 2s.
Also, I just realized that I forgot to comment out the special case that no longer is a special case.
Your for loop is excellent and I'm going to use it. I never thought to add/subtract a boolean operation to an integer. That's a neat trick that I'm going to remember.
So, here's the semi-final product. Semi-final because it's done unless I find something else cool to do with it.
c: | #include <stdio.h>
void drawTable (char[7][6]); //Obviously, this draws the table
int placeMarker (char table [7][6], int column, char player ); //self-explanatory. Returns 1 for success, 0 for failure (inputting to a full row, for example)
int win (char table [7][6], int column ); //return 1 if arg 'player' has won. 0 otherwise.
int checkLine (char table [7][6], int column, int x, int y );
int main (){
char table [7][6]; //the game board
char player [2] = {'X', 'O'}; //each player's token, so I can change it easily.
int column= 0; //the column a player has selected
int turn; //records which player's turn it is
int turnCount; //keeps track of number of turns played
//Initialize game board with empty space
for (int i = 0; i < 7; i++ ){
for (int j = 0; j < 6; j++ ){
table [i ][j ] = ' ';
}
}
drawTable (table );
for (turnCount = 0;!win (table, column )&&turnCount< 42; turn = (++turnCount )% 2){//check for a win/tie, switch the turn, and increment turn counter
do {
printf ("Turn %d, Player %c, enter column number: ", turnCount, player [turn ]);
scanf ("%d", &column );
} while (column < 0 || column > 6 || !placeMarker (table, column, player [turn ])); //Check for valid input and place a token
drawTable (table ); //Duh.
}
if (turnCount >= 41) printf ("\nDraw!\n\n");
else printf ("\n%c wins!\n\n", player [turn*- 1+ 1]); //Victory message. The player has to be switched or the wrong one will display.
}
//Given a vector x,y, checks for a win in table at the top token in column
int checkLine (char table [7][6], int column, int x, int y ){
int i = column; //I'd rather use i,j than row,column
int j;
for (j = 0; j < 7 && table [i ][j ] == ' '; j++ ); //find the first token in column
if (j> 5) return 0; //empty column
int total = 0; //total consecutive tokens on the vector
for (int _i = i+x, _j = j+y;table [_i ][_j ] == table [i ][j ] && _i < 7 && _j < 6; _i+=x,_j+=y,total++ ); //tally up tokens in the ray (x,y)
for (int _i = i-x, _j=j-y;table [_i ][_j ] == table [i ][j ] && _i >= 0 && _j >= 0;_i-=x,_j-=y,total++ ); //tally up tokens in the ray (-x, -y)
if (total >= 3) return 1; //return 1 if there are 4 in a row (token at (i,j) is not counted)
return 0;
}
int win (char table [7][6], int column ){
for (int x = 1, y = - 1; x >= 0; x -= y == 1, y += y < 1) { //Iterate over vectors [(1,-1),(1,0),(1,1),(0,1)]. Thanks to Zren of compsci.ca for figuring out this for loop.
if (checkLine (table, column, x, y )) return 1; //return 1 if a win is found
}
return 0; //no win case
}
void drawTable (char table [7][6]){
printf ("\n\n.0.1.2.3.4.5.6.");
for (int j = 0; j < 6; j++ ){ //draw each cell of the table, with dividers
printf ("\n|");
for (int i = 0; i < 7; i++ ){
printf ("%c|", table [i ][j ]);
}
}
printf ("\n\\^.^.^.^.^.^.^/");
printf ("\n\n");
}
int placeMarker (char table [7][6], int column, char player ){
int i = column; //I wanted 'column' to be the parameter name but didn't want to use it in the function body.
for (int j = 0; j < 6; j++ ){
if (table [i ][j ] != ' ') {//move down the column to find the lowest empty cell
if (j == 0) return 0; //special case if the column is full
table [i ][j- 1] = player; //fill the previous cell (since this one is full)
return 1; //return success
}
}
table [i ][5] = player; //special case for an empty column
return 1;
} |
|
|
|
|
|
|
Sponsor Sponsor
|
|
|
QuantumPhysics
|
Posted: Sun Feb 03, 2013 9:37 pm Post subject: RE:Console Connect 4 |
|
|
I like the structure of your for statements. Almost professional |
|
|
|
|
|
Insectoid
|
Posted: Sun Feb 03, 2013 10:17 pm Post subject: RE:Console Connect 4 |
|
|
I think I'd have an aneurism if I saw anything like this in production code.
Also, this for loop: x: | for (int _i = i+x, _j = j+y;table[_i][_j] == table[i][j] && _i <7 && _j <6; _i+=x,_j+=y,total++); | should really be more like this: c: | for (int _i = i+x, _j = j+y;table[_i][_j] == table[i][j] && _i <7 && _j <6; _i+=x,_j+=y)total++; | Otherwise, I'm effectively putting the body of the loop inside the iterator, which is technically legal but really bad form. |
|
|
|
|
|
Tony
|
Posted: Mon Feb 04, 2013 3:52 am Post subject: Re: Console Connect 4 |
|
|
Insectoid @ Sun Jan 06, 2013 6:54 pm wrote: All right. I've re-written my win function (currently under win_).
Source control. Get yourself a GitHub account. Use it. It's wonderful.
More elaborate explanation: you can branch (create a derivative copy) of your project and experiment there. If the new attempt at the win function ends up being garbage, just trash the entire thing and check out the copy of the code from any point in the past. If the new function is a success, then merge the changes back into trunk/mainline/main/whatever-you-call-authoritative-working-version. Source control is not just for team projects -- I've been using git for personal school assignments. |
Tony's programming blog. DWITE - a programming contest. |
|
|
|
|
Insectoid
|
Posted: Mon Feb 04, 2013 8:39 am Post subject: RE:Console Connect 4 |
|
|
I dunno. I don't write code often anymore (though I really should) since I'm busy with other things right now. I never write large projects either, and most changes can be written and tested in half an hour. If it's a major change, I'll just make a new copy of the file. The last thing I want to do while writing something is to waste time figuring out my source control. That's also why I don't use complicated bloated IDEs. Too much crap going on. All I need is syntax highlighting. Anything else just gets in the way. |
|
|
|
|
|
|
|