#Need help with the logic for game loop

125 messages · Page 1 of 1 (latest)

fallen barn
#
#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;
}
true baneBOT
#

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.

fallen barn
#

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): 
scarlet spade
#

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

fallen barn
#

How would I even do that?

scarlet spade
#

i can't see any getch calls there (and its alos windows specific iirc)

fallen barn
#

meant getchar()

#

apologies, it's been a long night.

scarlet spade
# fallen barn How would I even do that?

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);
}```
scarlet spade
# fallen barn meant ``getchar()``

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 next getchar call as explained above

fallen barn
#

What exactly is the buffer here, though? Is stdin a buffer?

#

I haven't created a buffer anywhere

scarlet spade
#

stdin (or any FILE * object) is backed by an internal buffer (usually. again assuming no immediate mode)

fallen barn
#

Gonna sleep on it a bit, I'll get back to it in a bit.

waxen gale
fallen barn
#

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

scarlet spade
fallen barn
#

Ben Eater is always amazing

fallen barn
#

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

scarlet spade
#

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`
fallen barn
#

I brought out the rubber_duckie

#
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

scarlet spade
#

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;
}```
fallen barn
#

ayo?

fallen barn
#
#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

fallen barn
#

!solved

true baneBOT
#

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

fallen barn
#
#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

waxen gale
#

those bitwise ANDs seem sus

fallen barn
#

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

waxen gale
#

hmm nevermind, the AND is OK as long as the idea is to return either 1 or 2, or 0

#

right

fallen barn
#

it's coindidence rather than intentional

#

But a welcome coincidence

waxen gale
#

😄

#

you can claim to your professor you meant to do it 🙂

fallen barn
#

This isn't homework, this is just a personal project

waxen gale
#

alright

fallen barn
#

Just got back to C after a long time of inactivity so a lot to relearn

waxen gale
#

even better, more motivation to learn

#

you could make the win state checking into something that looks like matrix multiplication with bits

fallen barn
#

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.

waxen gale
#

board state can live in a single uint32_t

fallen barn
#

I showed the code to a friend and this was his response lol

fallen barn
#

Oh right 32 is bit not byte

waxen gale
#

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

fallen barn
#

kekw

waxen gale
fallen barn
#

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

waxen gale
#

;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));
}

subtle jettyBOT
#
Program Output
2
waxen gale
#

ah

fallen barn
#

I mean sure it's fast and cool but it's not really readable

#

Always feels cool to do cool bitshifting stuff

waxen gale
#

;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));

}

subtle jettyBOT
#
Program Output
2
waxen gale
#

Just a sec finishing a win state check

fallen barn
#

you're having fun huh lol

waxen gale
#

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

waxen gale
#

;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));

}

subtle jettyBOT
#
Program Output
top row: 2
mid row: 1
bot row: 2
waxen gale
#

Lol

#

I fucked up

#

Ok there. I'm tired enough to actually get y*columns + x wrong lol

waxen flower
#

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

waxen gale
waxen flower
# waxen gale 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

waxen gale
waxen flower
#

ON A PHONE?

waxen gale
#

Termux.

#

oh and the discord compiler bot too

fallen barn
# waxen flower btw, you really don't want to do this <@258232478748246018>

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.

waxen flower
#

just don't do it?

#

they can just go into the bodies of the if statements

fallen barn
#

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

waxen flower
#

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

#
  1. never duplicate code
  2. 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

waxen flower
waxen flower
fallen barn
#

To each their own, I've already said I'm going to be replacing the gotos before you pointed it out.

waxen flower
#

i'm pretty sure you have a severe misconception though

waxen flower
fallen barn
#

Who said I thought I couldn't? I just thought it was cleaner.

#

Makes no difference.

waxen flower
#

...you think it's cleaner?

#

damn

#

i've never heard of gotos looking cleaner for anyone lol, you're a first

fallen barn
# waxen flower 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;
}
waxen flower
#

ohh ok

fallen barn
#

Yeah like I said just a lookup table since all of the labels had the same structure

#

Actually I need to change the logic

#
if (idx < 0 || idx > 8 || board[idx] == 'o')
if (idx < 0 || idx > 8 || board[idx] != 0)