#include <stdio.h>
const int winstates[8][3] = {
{0,1,2}, {3,4,5}, {6,7,8}, // horizontal
{0,3,6}, {1,4,7}, {2,5,8}, // vertical
{0,4,8}, {2,4,6} // diagonal
};
void
print_board(char *board)
{
for (int x = 0; x < 3; ++x) {
for (int y = 0; y < 3; ++y)
printf("%c ", board[x * 3 + y]);
putchar('\n');
}
}
// Applies the axiom that if A = B, and B = C, then A = C.
// Returns true if board[winstate[0]] == board[winstate[1]] == board[winstate[2]]
int
check_winstate(char *board, int const *winstate)
{
if (board[winstate[0]] != board[winstate[1]])
return 0;
return board[winstate[0]] == board[winstate[2]];
}
int
check_win(char *board)
{
for (size_t i = 0; i < 8; ++i)
if (check_winstate(board, winstates[i]))
return board[winstates[i][0]];
return -1;
}
int
game_step(char *board, char player)
{
while (check_win(board) != -1)
{
print_board(board);
printf("Select a square (0-9): ");
int c = getchar() - '0';
if (c < 0 || c > 9 || c == 'o')
{
puts("Selection out of range");
continue; // I assume this is a source of headache here.
}
board[c] = player;
}
if (check_win(board) == player)
goto exit_win;
else if (check_win(board) == '\0')
goto exit_draw;
else
goto exit_loss;
exit_win:
printf("You win (%c)\n", player);
return 0;
exit_loss:
printf("You lose (%c)\n", player);
return 0;
exit_draw:
puts("Draw");
return 0;
}
int
main(void)
{
char board[9] =
"..."
"..."
"...";
char player = 'x';
while (game_step(board, player))
;
return 0;
}
#Need help with the logic for game loop
125 messages · Page 1 of 1 (latest)
When your question is answered use !solved to mark the question as resolved.
Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.
Been working on this for about a day at this point, first time trying to make a game, and I'm struggling somewhat with the logic inside of the game loop. I'm getting duplicate outputs of game boards and the "selection out of range" check.
❯ ./a.out
. . .
. . .
. . .
Select a square (0-9):
0
x . .
. . .
. . .
Select a square (0-9):
Selection out of range
x . .
. . .
. . .
Select a square (0-9):
1
x x .
. . .
. . .
Select a square (0-9):
Selection out of range
x x .
. . .
. . .
Select a square (0-9):
2
x x x
. . .
. . .
Select a square (0-9):
Selection out of range
x x x
. . .
. . .
Select a square (0-9):
in case its still relevant: assuming your terminal isn't in immediate mode - typing 0 and pressing enter (as an example) will put 2 chars into your buffer. '0' and '\n'. meaning getchar will read that '\n' as a valid char, kicking if (c < 0 || c > 9 || c == 'o') into action.
you probably want to clear your buffer before reading any new chars
It is still current, I thought getch() would only read a single character, based on the name. Can't say I've ever had to create a buffer for one character lol
How would I even do that?
i can't see any getch calls there (and its alos windows specific iirc)
you read every char, until you encounter a newline (or EOF). simple as that. comsider:
void clear(FILE *stream) {
int c = EOF;
do {
c = fgetc(stream);
// one can check for errors in the stream here. one can alos check for EOF here instead
} while(c != '\n' && c != EOF);
}```
in that case:
It is still current, I thought getch() would only read a single character,
is correct. problem is - you'd be leaving the'\n'in the buffer, which will be consumed by the nextgetcharcall as explained above
What exactly is the buffer here, though? Is stdin a buffer?
I haven't created a buffer anywhere
stdin (or any FILE * object) is backed by an internal buffer (usually. again assuming no immediate mode)
Gonna sleep on it a bit, I'll get back to it in a bit.
Sleeping is a good idea for getting code to work. Anyway, about the buffer thing: it's the OS that manages the stream buffers (stdin, stdout, stderr)
Is this something that's talked about in the standard that I can read or is it something OS specific that I'd need to google around for?
Not really sure how to go about learning about this stuff
https://cigix.me/c17#7.21.2
https://en.cppreference.com/w/c/io/FILE
that said the buffer is an implementation detail. setvbuf might be worth looking into as well, and if memory serve BenEeater posted a vid mentioning the rational of such a buffer not too long ago
Ben Eater is always amazing
Alright I've fixed up the code a bit so now the input is fine
int
get_digit()
{
int c = getchar() - '0';
int flush;
while ((flush = getchar()) != '\n' && flush != EOF);
return c;
}
int
game_step(char *board, char player)
{
while (check_win(board) != -1)
{
print_board(board);
printf("Select a square (0-8): ");
int idx = get_digit();
if (idx < 0 || idx > 8 || board[idx] == 'o')
{
puts("Invalid input");
}
else {
board[idx] = player;
}
}
if (check_win(board) == player)
goto exit_win;
else if (check_win(board) == '\0')
goto exit_draw;
else
goto exit_loss;
exit_win:
printf("You win (%c)\n", player);
return 0;
exit_loss:
printf("You lose (%c)\n", player);
return 0;
exit_draw:
puts("Draw");
return 0;
}
Now my issue is this not resulting in a win.
❯ ./a.out
. . .
. . .
. . .
Select a square (0-8): 0
x . .
. . .
. . .
Select a square (0-8): 1
x x .
. . .
. . .
Select a square (0-8): 2
x x x
. . .
. . .
Select a square (0-8):
So I guess my main while loop conditional is wrong
not really. this
int
check_win(char *board)
{
for (size_t i = 0; i < 8; ++i)
if (check_winstate(board, winstates[i]))
return board[winstates[i][0]];
return -1;
}```
is wrong. specifically `return board[winstates[i][0]];`. in the above snippet you posted `return board[winstates[0][0]];` is `0`
I brought out the 
int
check_win(char *board)
{
for (size_t i = 0; i < 8; ++i)
if (check_winstate(board, winstates[i]))
{
printf("%c\n", board[winstates[i][0]]);
return board[winstates[i][0]];
}
return '\0';
}
this function is fucked
46 is '.'
it'll always return that because the board is initialized as such
why not just
int
check_win(char *board)
{
int win = 0;
for (size_t i = 0; i < 8; ++i)
{
win |= check_winstate(board, winstates[i]);
}
return win;
}```
ayo?
#include <stdio.h>
const int winstates[8][3] = {
{0,1,2}, {3,4,5}, {6,7,8}, // horizontal
{0,3,6}, {1,4,7}, {2,5,8}, // vertical
{0,4,8}, {2,4,6} // diagonal
};
void
print_board(char *board)
{
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j) {
char c;
if (board[i*3+j] == 0)
c = '.';
else if (board[i*3+j] == 1)
c = 'x';
else if (board[i*3+j] == 2)
c = 'o';
else
c = '_';
putchar(c);
}
putchar('\n');
}
}
int
check_winstate(char *board, int const *winstate)
{
return board[winstate[0]] & board[winstate[1]] & board[winstate[2]];
}
int
check_win(char *board)
{
for (size_t i = 0; i < 8; ++i)
if (check_winstate(board, winstates[i]))
{
return board[winstates[i][0]];
}
return 0;
}
int
get_digit()
{
int c = getchar() - '0';
int flush;
while ((flush = getchar()) != '\n' && flush != EOF);
return c;
}
int
game_step(char *board, char player)
{
while (check_win(board) == 0)
{
print_board(board);
printf("Select a square (0-8): ");
int idx = get_digit();
if (idx < 0 || idx > 8 || board[idx] == 'o')
{
puts("Invalid input");
}
else {
board[idx] = player;
}
}
if (check_win(board) == player)
goto exit_win;
else if (check_win(board) == 0)
goto exit_draw;
else
goto exit_loss;
exit_win:
printf("You win (%c)\n", player);
return 0;
exit_loss:
printf("You lose (%c)\n", player);
return 0;
exit_draw:
puts("Draw");
return 0;
}
int
main(void)
{
char board[9] = {0,0,0,0,0,0,0,0,0};
char player = 'x';
while (game_step(board, player))
;
return 0;
}
just a lil' messed up with the printing
but we got there in the end
Thank you and let us know if you have any more questions!
This thread is now set to auto-hide after an hour of inactivity
#include <stdio.h>
const int winstates[8][3] = {
{0,1,2}, {3,4,5}, {6,7,8}, // horizontal
{0,3,6}, {1,4,7}, {2,5,8}, // vertical
{0,4,8}, {2,4,6} // diagonal
};
void
print_board(char *board)
{
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j) {
char c;
if (board[i*3+j] == 0)
c = '.';
else if (board[i*3+j] == 1)
c = 'x';
else if (board[i*3+j] == 2)
c = 'o';
else
c = '_';
putchar(c);
}
putchar('\n');
}
}
int
is_winstate(char *board, int const *winstate)
{
return board[winstate[0]] & board[winstate[1]] & board[winstate[2]];
}
int
check_win(char *board)
{
for (size_t i = 0; i < 8; ++i)
if (is_winstate(board, winstates[i]))
{
return board[winstates[i][0]];
}
return 0;
}
int
get_digit()
{
int c = getchar() - '0';
int flush;
while ((flush = getchar()) != '\n' && flush != EOF);
return c;
}
int
game_step(char *board, char player)
{
while (check_win(board) == 0)
{
print_board(board);
printf("Select a square (0-8): ");
int idx = get_digit();
if (idx < 0 || idx > 8 || board[idx] == 'o')
{
puts("Invalid input");
}
else {
board[idx] = player;
}
}
if (check_win(board) == player)
goto exit_win;
else if (check_win(board) == 0)
goto exit_draw;
else
goto exit_loss;
exit_win:
printf("You win (%c)\n", player);
print_board(board);
return 0;
exit_loss:
printf("You lose (%c)\n", player);
print_board(board);
return 0;
exit_draw:
puts("Draw");
print_board(board);
return 0;
}
int
main(void)
{
char board[9] = {0,0,0,0,0,0,0,0,0};
char player = 1;
while (game_step(board, player))
;
return 0;
}
Here's the full thing
Tomorrow is time to write the opponent
those bitwise ANDs seem sus
I mean, the values are 0, 1, and 2, which are over 2-bit binary:
00
01
10
There's no overlap of 1s so any mismatch will result in a 0
hmm nevermind, the AND is OK as long as the idea is to return either 1 or 2, or 0
right
This isn't homework, this is just a personal project
alright
Just got back to C after a long time of inactivity so a lot to relearn
even better, more motivation to learn
you could make the win state checking into something that looks like matrix multiplication with bits
Things I'm thinking to change is to use chess format (A1-C3), and to also clean up the exit labels to a LUT because the exit cases are identical save for the string being printed. Since check_win already returns the id of the winning player I can just LUT that directly instead of the current goto pattern.
board state can live in a single uint32_t
I showed the code to a friend and this was his response lol
Isn't the boardstate just living in a single 9-byte array?
Oh right 32 is bit not byte
this is the friend who spends a week optimizing fprintf(stderr, "non-recoverable error\n"); exit(1); to handle multi-threaded GPU accelerated robotic dildo platforms
kekw
I mean if you have 9 cells with a state that is one of three possible, you can put that in 9x2 bits == 18 bits, then do bitwise AND and bit shifts to check all combinations fairly quickly
I believe code should be readable to the maintainers. I am not at a level where I can understand that sort of bit-shifting syntax, it'd bite me in the ass in the future.
I wrote a sudoku solver as a project a few months ago that was a single 81-cell array that was solved using bitwise operations, I couldn't tell you today what that code does
My good sir, are you alright? You've been typing for the last like 15 minutes lol
;compile ```c
#include <stdint.h>
#include <stdio.h>
#define get_cell(brd,x,y) (((brd) >> (x)*(y)2U) & 3U)
#define set_cell(brd,x,y,val)
(((brd) & ~(3U << (x)(y)2U))
| (((val) & 3U) << (x)(y)*2U))
int main(void) {
uint32_t board = 0;
board = set_cell(board,1,1,2);
printf("%u\n", get_cell(board,1,1));
}
Program Output
2
imaami | 73ms | c | x86-64 gcc 13.2 | godbolt.org
ah
I mean sure it's fast and cool but it's not really readable
Always feels cool to do cool bitshifting stuff
;compile ```c
#include <stdint.h>
#include <stdio.h>
#define cell_shift(x,y) ((x)*(y)2U)
#define get_cell(brd,x,y) (((brd) >> (x)(y)2U) & 3U)
#define set_cell(brd,x,y,val)
(((brd) & ~(3U << (x)(y)2U))
| (((val) & 3U) << (x)(y)2U))
#define def_cell(x,y,val)
(((val) & 3U) << (x)(y)*2U)
int main(void) {
// diagonal captured by player 2
uint32_t board = def_cell(0,0,2) | def_cell(1,1,2) | def_cell(2,2,2);
uint32_t state = (board >> cell_shift(0,0)) & (board >> cell_shift(1,1)) & (board >> cell_shift(2,2));
printf("%u\n", get_cell(state,0,0));
}
Program Output
2
imaami | 109ms | c | x86-64 gcc 13.2 | godbolt.org
Just a sec finishing a win state check
you're having fun huh lol
Yeah
Ok, there the state variable is aligned to have the 0-3-8 diagonal cells on top of eachother
Then the bitwise AND does the thing
Ofc some shifts are convenient for getting more than 1 cell state
;compile ```c
#include <stdint.h>
#include <stdio.h>
#define cell_shift(x,y) (((y)*3U+(x))*2U)
#define get_cell(brd,x,y) (((brd) >> ((y)*3U+(x))*2U) & 3U)
#define set_cell(brd,x,y,val)
(((brd) & ~(3U << ((y)*3U+(x))*2U))
| (((val) & 3U) << ((y)*3U+(x))*2U))
#define def_cell(x,y,val)
(((val) & 3U) << ((y)*3U+(x))*2U)
int main(void) {
uint32_t board =
def_cell(0,0,2) | def_cell(1,0,2) | def_cell(2,0,2) |
def_cell(0,1,1) | def_cell(1,1,1) | def_cell(2,1,1) |
def_cell(0,2,2) | def_cell(1,2,2) | def_cell(2,2,2);
uint32_t state = (board >> cell_shift(0,0)) & (board >> cell_shift(1,0)) & (board >> cell_shift(2,0));
printf("top row: %u\nmid row: %u\nbot row: %u\n", get_cell(state,0,0), get_cell(state,0,1), get_cell(state,0,2));
}
Program Output
top row: 2
mid row: 1
bot row: 2
imaami | 47ms | c | x86-64 gcc 13.2 | godbolt.org
what the fuck
why are you using macros 😭
btw, you really don't want to do this @fallen barn
avoid gotos unless it's specifically for cleanup, and you really do not need gotos there for any reason
it's adding to the lines of code, making it more unreadable
quick sketch
i mean it just seems like it could easily be implemented in an inline function and just be more readable and better supported by the compiler
sure, for a quick PoC on the phone, nah
ON A PHONE?
Generally I agree that gotos are a bad idea, the defense here was that they're immediately exiting and doesn't go to other labels, which also keeps the exit stuff to the end where it belongs, but I will be replacing them with a LUT since their structure is exactly the same save for the string being printed.
they're just unnecessary here though
just don't do it?
they can just go into the bodies of the if statements
I wrote it before I had finished the main logic of the game loop
Wasn't sure how the structure of it all would look like, don't worry, I will be removing them.
It's a habit I picked up from a previous project of mine
int
game_step(char *board, char player)
{
while (check_win(board) == 0)
{
print_board(board);
printf("Select a square (0-8): ");
int idx = get_digit();
if (idx < 0 || idx > 8 || board[idx] == 'o')
{
puts("Invalid input");
}
else {
board[idx] = player;
}
}
if (check_win(board) == player)
printf("You win (%c)\n", player);
else if (check_win(board) == 0) printf("You win (%c)\n", player);
else puts("Draw");
print_board(board);
return 0;
}```
all you need
your defense gets overridden by the fact that there's no need for it at all, it's a ton of code duplication
- never duplicate code
- don't use gotos UNLESS ABSOLUTELY NECESSARY! You can use do {} while(0) and place breaks inside to exit in the middle, and the only real excuse you can have for them is that they are decent(?) for cleanup of resources that you always need to do at an end of a function. THIS IS TO DEDUPLICATE CODE
if you're duplicating code for gotos, that's the complete opposite of what they are supposed to be used for
even in this case, they're not needed in any way
putting the bodies into the if statements works completely fine
To each their own, I've already said I'm going to be replacing the gotos before you pointed it out.
i'm pretty sure you have a severe misconception though
why did you think you couldn't just put the bodies of the gotos inside the ifs here?
...you think it's cleaner?
damn
i've never heard of gotos looking cleaner for anyone lol, you're a first
Here's the updated game loop:
static const char *winres_str[] = {
"Draw",
"Win",
"Lose"
};
int
game_step(char *board, char player)
{
while (check_win(board) == 0)
{
print_board(board);
printf("Select a square (0-8): ");
int idx = get_digit();
if (idx < 0 || idx > 8 || board[idx] == 'o')
{
puts("Invalid input");
}
else {
board[idx] = player;
}
}
print_board(board);
printf("%s\n", winres_str[check_win(board)]);
return 0;
}
ohh ok