Jump to content

Push to an arbitary value in C

goatedpenguin
//snakes and ladders squares, the second sub index represents the destination sqaure
int snakes[8][2] = {{98, 79}, {95, 75}, {93, 73}, {87, 36}, {64, 60}, {62, 19}, {54, 34}, {17, 7}};
int ladders[8][2] = {{1, 38}, {4, 14}, {9, 31}, {21, 42}, {28, 84}, {51, 67}, {72, 91} , {80, 100}};

if (get_square_player_1 == snakes){
    //how can I say that if the user ends on any square within the snakes list that it will be pushed to the resepective sub index:
    //e.g get_square_player_1 == 98 send user back down to square 79
    //Thanks in advance FYI this code is just a snippet so not the whole program if your wondering. :) 
    }

 

Link to comment
Share on other sites

Link to post
Share on other sites

Set the player value to the second index for the respective array row?

 

Unless I'm missing something, this should be pretty trivial

Community Standards || Tech News Posting Guidelines

---======================================================================---

CPU: R5 3600 || GPU: RTX 3070|| Memory: 32GB @ 3200 || Cooler: Scythe Big Shuriken || PSU: 650W EVGA GM || Case: NR200P

Link to comment
Share on other sites

Link to post
Share on other sites

1 minute ago, Slottr said:

Set the player value to the second index for the respective array row?

 

I get that its a possible way to do it but is there a more robust way to do it instead of having to write many conditional blocks?

Link to comment
Share on other sites

Link to post
Share on other sites

15 minutes ago, goatedpenguin said:

I get that its a possible way to do it but is there a more robust way to do it instead of having to write many conditional blocks?

Theres multiple ways to go about it.

For instance you can iterate through the array to check for snakes/ladders; or you can create a switch statement to manually check each condition. 

 

You don't need to write conditionals for every state on the board 

Community Standards || Tech News Posting Guidelines

---======================================================================---

CPU: R5 3600 || GPU: RTX 3070|| Memory: 32GB @ 3200 || Cooler: Scythe Big Shuriken || PSU: 650W EVGA GM || Case: NR200P

Link to comment
Share on other sites

Link to post
Share on other sites

One minor optimization I'd make beforehand is to use a single array for snakes and ladders. Their start/end positions can't overlap and your program doesn't really need to know whether the square you land on is a snake or ladder. It simply has to update a player's position, if it's position matches a start position.

 

You can just iterate over the array, check if the first value is equal to the current player's position and if so, update the players position to the second value. Performance wise it would likely be faster to use a map/dictionary for that, but since the array isn't that big it won't really matter too much.

 

It's fine to keep two arrays, of course, you'd simply have to iterate them one after the other.

 

Spoiler
// Holds the position for each player, initialized to zero
int player_pos[num_players] = …;

// Toggles every round
int current_player = …;

// Contains snakes/ladders
int movers = [16][2] = {  }

while (nobody_has_won) {
    // Determine current_player
    // Throw die and update current player's position accordingly
    // Check if player has won, if so end game

    for (int n = 0; n < 16; ++n) {
        if (player_pos[current_player] == movers[n][0]) {
            player_pos[current_player] == movers[n][1];
            break;
        }
    }
}

 

 

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

Personally, I would optimize things by changing your snakes/ladders arrays into a single, one-dimensional "board" array. Each square on the board would be an index in the array, and the values would be how that square affects the position of the player (eg. a "snake" square could move the player -3 spaces). This way you don't need to loop over the whole array since the player's position lets you access it directly.

 

    int square_modifiers[] = { 0,0,3,0,-4,0,2,-2,0 };
    
    int player_position = 0;
    player_position += rand() % 6 + 1;
    
    printf("The player landed on %d\n", player_position);
    
    int modifier = square_modifiers[player_position]; // Todo: make sure position isn't out of bounds
    
    printf("The player %s %d spaces\n", (modifier > 0) ? "climbed" : "slid", modifier);
    player_position += modifier;
    
    printf("The player ended on %d\n", player_position);

 

Link to comment
Share on other sites

Link to post
Share on other sites

Dang, I was just about to suggest that as an alternative @QuantumRand 😅

 

It uses a tiny bit more memory, but you can avoid iteration altogether.

 

Just have to make sure that the offset at a position doesn't lead to another position with an offset.

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

1 minute ago, Eigenvektor said:

Just have to make sure that the offset at a position doesn't lead to another position with an offset.

The program would actually still run fine if you made that mistake, but it would look weird if it were a real Snakes and Ladders board.

 

@goatedpenguin as a fun challenge, you can try writing a function that generates a valid board of arbitrary length. There are a few good ways to achieve this and can be done in O(n) time.

Link to comment
Share on other sites

Link to post
Share on other sites

49 minutes ago, QuantumRand said:

The program would actually still run fine if you made that mistake, but it would look weird if it were a real Snakes and Ladders board.

@goatedpenguin

Yes, of course, but you might also end up creating an endless loop, effectively creating a soft lock.

 

Unless, of course you don't evaluate the square the player ends on after a move

 

~edit: You could still create a death trap where any valid die throw ends up on either a snake or a ladder, where every possible move afterwards alao ends on a snake/ladder 😈

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

On 2/7/2024 at 10:21 PM, QuantumRand said:

Personally, I would optimize things by changing your snakes/ladders arrays into a single, one-dimensional "board" array. Each square on the board would be an index in the array, and the values would be how that square affects the position of the player (eg. a "snake" square could move the player -3 spaces). This way you don't need to loop over the whole array since the player's position lets you access it directly.

 

    int square_modifiers[] = { 0,0,3,0,-4,0,2,-2,0 };
    
    int player_position = 0;
    player_position += rand() % 6 + 1;
    
    printf("The player landed on %d\n", player_position);
    
    int modifier = square_modifiers[player_position]; // Todo: make sure position isn't out of bounds
    
    printf("The player %s %d spaces\n", (modifier > 0) ? "climbed" : "slid", modifier);
    player_position += modifier;
    
    printf("The player ended on %d\n", player_position);

 

Can you explain this more I am not full understanding the square modifiers. Thanks. 

Link to comment
Share on other sites

Link to post
Share on other sites

Hi after taking a look at both solutions given I finally have almost* completed my code except that there is one problem, the for loop does not work as expected. Code is below, thanks @QuantumRand and @Eigenvektor for all the help. Below is just a code snippet. I also had asked chatgpt for just a lil bit of help on the snakes and ladders squares thing that I was asking at first 😅. Suggestions to improve the code logic will be greatly appreciated 🙂 

 

 for(turn_counter = 0; turn_counter < 100; turn_counter++){
            
            if(turn_counter % 2 == 0){
                puts("Hit any button to roll the dice!");
                getchar();
                get_square_player_1 += dice();
                if(get_square_player_1 == snakes[turn_counter][0]){
                    puts("Oops you landed on a snake down you go :( ");
                    get_square_player_1 = snakes[turn_counter][2];
                }
                else if(get_square_player_1 == ladders[turn_counter][0]){
                    puts("You have landed on a ladder, up you go!");
                    get_square_player_1 = ladders[turn_counter][0];
                } 
            }
            else if(turn_counter % 2 != 0){
                puts("Hit any button to roll the dice!");
                getchar();
                get_square_player_2 += dice();
                if(get_square_player_2 == snakes[turn_counter][0]){
                    puts("Oops you landed on a snake down you go :( ");
                    get_square_player_2 = snakes[turn_counter][2];
                }
                else if(get_square_player_2 == ladders[turn_counter][0]){
                    puts("You have landed on a ladder, up you go!");
                    get_square_player_2 = ladders[turn_counter][0];
                }
            }
            else if(turn_counter == 100){
                    if(get_square_player_1 == 100){
                        printf("%s won", player_1);
                        break;
                    }
                    else if(get_square_player_2 == 100){
                        printf("%s won", player_2);
                        break;
                    }
            }

        }

 

Link to comment
Share on other sites

Link to post
Share on other sites

One thing I would do is get rid of the player_1 and player_2 variables. Store the position of your players in an array and simply access it by "turn % num_players". This way you can easily support an arbitrary number of players.

 

Instead of a fixed number of turns, the game should run until either of the players has made it to the final square. If a player goes past the final square, they have to move back.

 

Why exactly do you access snakes and ladders with

snakes[turn_counter][0]

Your snakes and ladders shouldn't move, so their position should not depend on the turn. You need to compare the player's final position (after the die throw) to their position.

 

This looks like an off-by-one error:

get_square_player_2 = snakes[turn_counter][2]

The index should be [1] not [2]?

 

I have a working example, though it doesn't contain snakes or ladders yet.

Spoiler
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

long ask(char *value, long fallback) {
    char user_input[20] = {};
    char *remaining;

    printf("%-10s [%ld] : ", value, fallback);
    fgets(user_input, sizeof(user_input), stdin);

    if (!strcmp(user_input, "\n")) {
        return fallback;
    }

    return strtol(user_input, &remaining, 10);
}

void print_board(uint8_t board[], uint8_t width, uint8_t height, uint8_t players[], uint8_t num_players) {
    uint16_t num_tiles = width * height;
    uint8_t state[num_tiles];
    
    memcpy(state, board, num_tiles);

    for (uint8_t p = 0; p < num_players; ++p) {
        uint8_t pos = players[p];
        state[pos] = p + 1;
    }

    printf("\n");
    for (uint8_t y = 0; y < height; ++y) {
        for (uint8_t x = 0; x < width; ++x) {
            uint16_t tile = x + (y * width);

            printf("%i", state[tile]);
        }
        printf("\n");
    }
    printf("\n");
}

int main() {
    srand(time(NULL));

    printf("Snakes and Ladders\n");

    printf("\nNumber of\n");
    uint8_t num_players = ask("Players", 2);
    uint8_t num_snakes = ask("Snakes", 5);
    uint8_t num_ladders = ask("Ladders", 5);

    printf("\nBoard dimensions\n");
    uint8_t width = ask("Width", 10);
    uint8_t height = ask("Height", 10);

    uint16_t num_tiles = width * height;
    uint16_t max_pos = num_tiles -1;

    uint8_t board[num_tiles];
    uint8_t players[num_players];

    memset(board, 0, num_tiles);
    memset(players, 0, num_players);

    bool win_condition = false;
    uint16_t turn = 0;

    while(!win_condition) {
        uint8_t player_id = turn % num_players;

        uint16_t turn_num = turn + 1;
        uint8_t player_num = player_id + 1;

        uint8_t *player = &players[player_id];

        printf("\n----------------------------------------\n");
        printf("Turn %i, Player %i, Position %i\n", turn_num, player_num, *player + 1);
        printf("----------------------------------------\n");

        uint8_t die = rand() % 6 + 1;
        *player += die;

        printf("Player %i rolled a %i", player_num, die);

        if (*player > max_pos) {
            uint8_t overshoot = *player - max_pos;
            *player = max_pos - overshoot;

            printf(", ran past the goal by %i and has to go back to %i\n", overshoot, *player + 1);
        } else {
             printf(" and is now on %i\n", *player + 1);
        }

        print_board(board, width, height, players, num_players);

        if (*player == max_pos) {
            printf("Player %i has WON THE GAME\n", player_num);
            win_condition = true;
        } else if (die == 6) {
            printf("Player %i got a <6> and can roll one more time!\n", player_num);
        } else {
            ++turn;
        }

        printf("Press [Enter] to continue\n");
        getchar();
    }

    return 0;
}

 

 

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

18 hours ago, goatedpenguin said:

Can you explain this more I am not full understanding the square modifiers. Thanks. 

Basically, imagine that each index in that array is a square on the board. For the values, "0" would be a normal square (no snake or ladder). A negative or positive number would be a snake or ladder respectively. The number represents how many spaces you gain or lose when landing there.

 

In your example at the top of this post

//snakes and ladders squares, the second sub index represents the destination sqaure
int snakes[8][2] = {{98, 79}, {95, 75}, {93, 73}, {87, 36}, {64, 60}, {62, 19}, {54, 34}, {17, 7}};
int ladders[8][2] = {{1, 38}, {4, 14}, {9, 31}, {21, 42}, {28, 84}, {51, 67}, {72, 91} , {80, 100}};

You have your 2D array, each index is a snake/ladder. As you've seen, checking if a player is on a snake/ladder is tricky and you have to iterate over the whole array. Once you've found your snake/ladder square, you set the player's position to the destination square.

 

Here's my 1D array example if it were set up with the same board you've created (might be off by one square, I'm not sure how you're defining your starts/ends)

int snakes[8][2] = {{98, 79}, {95, 75}, {93, 73}, {87, 36}, {64, 60}, {62, 19}, {54, 34}, {17, 7}};
int ladders[8][2] = {{1, 38}, {4, 14}, {9, 31}, {21, 42}, {28, 84}, {51, 67}, {72, 91} , {80, 100}};

// Equivalent to above (this would make the same snakes and ladders board)
int square_modifiers[] = { 0, 37, 0, 0, 10, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, -10, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, -20, 0, 0, 0, 0, 0, 0, 0, -43, 0, -4, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, -51, 0, 0, 0, 0, 0, -20, 0, -20, 0, 0, -19, 0 } 

With the 1D array, instead of searching through the snakes/ladders arrays to see if a play is on a square, you just access the 1D array directly by using the player's position. By storing the change in the players position when they land on a square instead of which square the go to, it simplifies the logic.

// If the player is on a ladder position, add that to their position to get the destination.
// If the players is on a snake, we still add because it will be a negative value.
// If the players is on a normal square, still safe to add since it's 0. Player doesn't move to a different square.
player_position = players_postions + square_modifiers[player_position];

// Much simpler compared to:

if (check_if_snake(player_position)) { // Checking if the player is on a snake square
    player_position = get_snake_square(player_position)[1] // Get the snake that the player is on and access the destination value
}
if (check_if_ladder(player_position)) { // Checking if the player is on a ladder square
    player_position = get_ladder_square(player_position)[1] // Get the ladder that the player is on and access the destination value
}

 

Link to comment
Share on other sites

Link to post
Share on other sites

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

// function prototypes
int dice();
void users();

// driver code
int main() {
    srand(time(NULL));
    users();
    return 0;
}

int dice() {
    int dice_roll = rand() % 6 + 1;
    printf("you rolled %d!\n", dice_roll);
    return dice_roll;
}

void users() {
    char player_1[30];
    char player_2[30];
    int turn_counter;
    int get_square_player_1 = 0;
    int get_square_player_2 = 0;
    int snakes[8][2] = {{98, 79}, {95, 75}, {93, 73}, {87, 36}, {64, 60}, {62, 19}, {54, 34}, {17, 7}};
    int ladders[8][2] = {{1, 38}, {4, 14}, {9, 31}, {21, 42}, {28, 84}, {51, 67}, {72, 91}, {80, 100}};

    // user name input
    printf("Player 1 enter your name: ");
    scanf("%29s", player_1);
    getchar();
    
    printf("Player 2 enter your name: ");
    scanf("%29s", player_2);
    getchar();
    // Bread and butter of the program

    for (turn_counter = 0; turn_counter < 100; turn_counter++) {
        // logic error the program will not stop until turn_counter == 100 fix this shit.
        if (turn_counter % 2 == 0) {
            printf("It is %s's turn, hit any button to roll the dice!", player_1);
            scanf("%*c");
            get_square_player_1 += dice();
            if (get_square_player_1 == 100) {
                printf("%s won!\n", player_1);
                break;
            }
            if (get_square_player_1 == snakes[turn_counter][0]) {
                puts("Oops you landed on a snake down you go :( ");
                get_square_player_1 = snakes[turn_counter][1];
            } else if (get_square_player_1 == ladders[turn_counter][0]) {
                puts("You have landed on a ladder, up you go!");
                get_square_player_1 = ladders[turn_counter][1];
            }
        } else {
            printf("It is %s's turn, hit any button to roll the dice!", player_2);
            scanf("%*c");
            get_square_player_2 += dice();
            if (get_square_player_2 == 100) {
                printf("%s won!\n", player_2);
                break;
            }
            if (get_square_player_2 == snakes[turn_counter][0]) {
                puts("Oops you landed on a snake down you go :( ");
                get_square_player_2 = snakes[turn_counter][1];
            } else if (get_square_player_2 == ladders[turn_counter][0]) {
                puts("You have landed on a ladder, up you go!");
                get_square_player_2 = ladders[turn_counter][1];
            }
        }
    }
}

Here is my pretty much working code, it could use some improvements like showing a game counter etc. Can someone explain me what a struct is and how to use fgets? Thanks 🙂 

Link to comment
Share on other sites

Link to post
Share on other sites

Quote

Can someone explain me what a struct is and how to use fgets?

A struct is essentially a named collection/logical grouping of variables. If you find yourself passing around the same 10 variables on each method call, you put them in a struct and pass that around instead (or a pointer to it).

 

My example code above uses fgets. Basically you hand it an array to accept user input and a maximum size. This reduces the risk of a buffer overflow when reading user input.

 

I can post my complete code tonight, if you want, it uses both structs and fgets.

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

Quote

I can post my complete code tonight, if you want, it uses both structs and fgets.

Sure that would be great. Thanks 🙂. I might change the code to use fgets, show a counter, use square modifiers like @QuantumRand said and use structs for better organization.

Link to comment
Share on other sites

Link to post
Share on other sites

2 hours ago, goatedpenguin said:

Sure that would be great. Thanks 🙂. I might change the code to use fgets, show a counter, use square modifiers like @QuantumRand said and use structs for better organization.

Here's the code. Tested to work on Manjaro, compiled with GCC 13.2.1. The implementation is somewhat complex, though most of the code is concerned with rendering the 2D board, rather than actual game logic.

 

Room for improvement:

  • User input isn't sanitized, i.e. you can create any number of players or a board with impossible dimensions. If entered values exceed 8 bit, the behavior is undefined.
  • The placement of snakes and ladders is rather simplistic and contains minimal sanity checks. Ladders can overwrite snakes.
  • In rare cases an unwinnable board may be created (i.e. any reachable position is occupied by a snake)

If a ladder warps the player into a snake or vice versa, the game continues to warp the player until the player ends on a free cell, a configurable maximum number of moves has been exhausted or a loop is detected. Maximum moves or loop result in an immediate game over 💀

 

Spoiler
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

const uint8_t DEFAULT_PLAYERS = 2;

const uint8_t DEFAULT_BOARD_WIDTH = 12;
const uint8_t DEFAULT_BOARD_HEIGHT = 12;

const uint8_t DEFAULT_NUM_SNAKES = 50;
const uint8_t MIN_SNAKE_LEN = 3;
const uint8_t MAX_SNAKE_LEN = 15;

const uint8_t DEFAULT_NUM_LADDERS = 50;
const uint8_t MIN_LADDER_LEN = 5;
const uint8_t MAX_LADDER_LEN = 10;

// Maximum moves a player can make per turn. before game is aborted
const uint8_t MAX_CONSECUTIVE_MOVES = 40;

// To keep things aligned, an empty cell uses a 1em wide space
const char* EMPTY = "\u3000 ";

// Emojis for snake, ladder and player
const char* SNAKE = "\U0001F40D";
const char* LADDER = "\U0001FA9C";
const char* PLAYER = "\U0001F9CD";

// Unicode characters for cell borders
const char* H_LINE = "\u22EF";
const char* V_LINE = "\u22EE";

// Contains configuration data
struct snalConfig {
    uint8_t num_players;
    uint8_t num_snakes;
    uint8_t num_ladders;

    uint8_t min_snake_len;
    uint8_t max_snake_len;

    uint8_t min_ladder_len;
    uint8_t max_ladder_len;
};

// Constains board information
struct snalBoard {
    uint8_t width;
    uint8_t height;

    uint16_t num_cells;
    uint16_t max_pos;

    // Holds snakes & ladders
    int8_t *entities;

    // Holds player positions
    uint16_t *player_positions;
};

// Constains information about the current turn
struct snalTurn {
    uint16_t count;
    uint16_t name;

    uint8_t throw;

    uint8_t player_id;
    uint8_t player;

    // Pointer to current players position in board
    uint16_t *player_pos;
};

// Roll the dice
// See: https://stackoverflow.com/a/822361/7252334
uint16_t cast_die(uint16_t min, uint16_t max) {
    uint16_t n = max - min + 1;
    uint32_t end = RAND_MAX / n;
    end *= n;

    uint32_t r = rand();
    while (r >= end) {
        r = rand();
    }
    return min + (r % n);
}

// Ask for a value, return fallback if user simply presses return
long ask(char *value, long fallback) {
    char user_input[20] = {};
    char *remaining;

    printf("%-18s [%2ld]: ", value, fallback);
    fgets(user_input, sizeof(user_input), stdin);

    if (!strcmp(user_input, "\n")) {
        return fallback;
    }

    return strtol(user_input, &remaining, 10);
}

// Ask user for configuration values, return configuration
const struct snalConfig configure_game() {
    struct snalConfig config;

    printf("\nConfiguration\n");
    config.num_players = ask("Players", DEFAULT_PLAYERS);
    config.num_snakes = ask("Snakes", DEFAULT_NUM_SNAKES);
    config.num_ladders = ask("Ladders", DEFAULT_NUM_LADDERS);

    config.min_snake_len = ask("Min. snake length", MIN_SNAKE_LEN);
    config.max_snake_len = ask("Max. snake length", MAX_SNAKE_LEN);

    config.min_ladder_len = ask("Min. ladder length", MIN_LADDER_LEN);
    config.max_ladder_len = ask("Max. ladder length", MAX_LADDER_LEN);

    return config;
}

// Ask user for board dimensions, return board
const struct snalBoard create_board(const struct snalConfig *config) {
    struct snalBoard board;

    printf("\nBoard dimensions\n");
    board.width = ask("Width", DEFAULT_BOARD_WIDTH);
    board.height = ask("Height", DEFAULT_BOARD_HEIGHT);

    board.num_cells = board.width * board.height;
    board.max_pos = board.num_cells -1;

    board.entities = malloc(board.num_cells * sizeof *board.entities);
    board.player_positions = malloc(config->num_players * sizeof *board.player_positions);

    memset(board.entities, 0, board.num_cells);
    memset(board.player_positions, 0, config->num_players);

    return board;
}

// Free memory reserved by board
void free_board(const struct snalBoard *board) {
    free(board->player_positions);
    free(board->entities);
}

// Populate the board with snakes and ladders
// Snakes must never warp player to a position less than start
// Ladders must never warp player to the goal or beyond
// Try to prevent ladders that lead directly into a snake (the reverse can still happen)
void init_board(
    const struct snalConfig *config,
    struct snalBoard *board
) {
    for (int snake = 0; snake < config->num_snakes; ++snake) {
        int8_t offset = cast_die(config->min_snake_len, config->max_snake_len);
        uint16_t pos = cast_die(offset, board->num_cells - 2);

        board->entities[pos] = -offset;
    }

    for (int ladder = 0; ladder < config->num_ladders; ++ladder) {
        int8_t offset = cast_die(config->min_ladder_len, config->max_ladder_len);
        uint16_t pos = cast_die(1, board->num_cells - offset - 1);

        board->entities[pos] = offset;

        if (board->entities[pos + offset] == -offset) {
            if (offset >= MAX_LADDER_LEN) {
                board->entities[pos] = offset - 1;
            } else {
                board->entities[pos] = offset + 1;
            }
        }
    }
}

// Check if player moved beyond goal and bounce them back
void check_bounce(
    const struct snalTurn *turn,
    const struct snalBoard *board
) {
    printf(" to %i", *turn->player_pos + 1);

    if (*turn->player_pos > board->max_pos) {
        uint8_t overshoot = *turn->player_pos - board->max_pos;
        *turn->player_pos = board->max_pos - overshoot;

        printf(", ran past the goal by %i, back to position %i", overshoot, *turn->player_pos + 1);
    }

    if (turn->throw == 6) {
        printf(", and gets to roll again!");
    }
    printf("\n");
}

// Check if player landed on a snake or ladder, move player accordingly, repeat until player ends up in a free spot,
// the maximum number of moves has been exhausted or a loop is detected
void check_offset(
    const struct snalTurn *turn,
    const struct snalBoard *board
) {
    uint16_t positions[MAX_CONSECUTIVE_MOVES + 1];
    positions[0] = *turn->player_pos;

    uint8_t moves = 0;
    do {
        int8_t offset = board->entities[*turn->player_pos];
        if (!offset) {
            return;
        }

        *turn->player_pos += offset;

        if (offset > 0) {
            printf("Player %x found a %s and advances by %i to position %i\n", turn->player, LADDER, offset, *turn->player_pos + 1);
        } else if (offset < 0) {
            printf("Player %x met a %s and slides back by %i to position %i\n", turn->player, SNAKE, -offset, *turn->player_pos + 1);
        }

        // Check if we've warped to a position we've already been
        for(uint8_t n = 0; n < moves; ++n) {
            if (positions[n] == *turn->player_pos) {
                printf("Game over, player %x is dizzy from running in circles\n", turn->player);
                exit(1);
            }
        }

        ++moves;
        positions[moves] = *turn->player_pos;
    } while (moves < MAX_CONSECUTIVE_MOVES);

    if (moves >= MAX_CONSECUTIVE_MOVES) {
        printf("Game over, player %x died from exhaustion\n", turn->player);
        exit(1);
    }
}

// Initialize information of current turn
void init_turn(
    const struct snalConfig *config,
    const struct snalBoard *board,
    struct snalTurn *turn
) {
    turn->name = turn->count + 1;

    turn->player_id = turn->count % config->num_players;
    turn->player = turn->player_id + 1;

    turn->player_pos = &board->player_positions[turn->player_id];
}

// Throw the die and move the player
void play_turn(struct snalTurn *turn) {
    turn->throw = cast_die(1, 6);

    printf("Player %x rolls a [%i], moves from position %i", turn->player, turn->throw, *turn->player_pos + 1);
    *turn->player_pos += turn->throw;
}

// Check if player has won the game
bool evaluate_turn(
    const struct snalBoard *board,
    struct snalTurn *turn
) {
    if (*turn->player_pos == board->max_pos) {
        printf("Game over: Player %x HAS WON\n", turn->player);
        return true;
    }

    return false;
}

// Next turn, unless player is allowed to throw again
void advance_turn(struct snalTurn *turn) {
    if (turn->throw != 6) {
        ++turn->count;
    }
}

// Print current turn information
void print_turn(
    const struct snalConfig *config,
    const struct snalTurn *turn
) {
    uint16_t pos = *turn->player_pos + 1;

    printf("\n----------------------------------------\n");
    printf("Turn %i, Player %x of %x, Position %i\n", turn->name, turn->player, config->num_players, pos);
    printf("----------------------------------------\n");
}

// Convert player positions into values in grid to make rendering easier
uint8_t* map_cells_to_player(const struct snalConfig *config, const struct snalBoard *board) {
    uint8_t *cell_to_player = malloc(board->num_cells * sizeof(uint8_t));
    memset(cell_to_player, 0, board->num_cells);

    for (uint8_t player_id = 0; player_id < config->num_players; ++player_id) {
        uint16_t pos = board->player_positions[player_id];
        cell_to_player[pos] = player_id + 1;
    }

    return cell_to_player;
}

// Render the player at the current position, if any
void render_player(
    const struct snalBoard *board,
    const uint8_t *cell_to_player,
    const uint16_t x,
    const uint16_t y) {
    uint16_t dx = x % 3;

    if (dx != 1){
        return;
    }

    uint16_t pos = (x / 3) + (y / 3) * board->width;
    uint16_t player = cell_to_player[pos];

    if (player) {
        printf("%s%x", PLAYER, player);
    } else {
        printf("%s", EMPTY);
    }
}

// Render snake, ladder or empty for the current position
void render_offset(
    const struct snalBoard *board,
    const uint16_t x,
    const uint16_t y) {
    uint16_t dx = x % 3;

    if (dx != 1){
        return;
    }

    uint16_t pos = (x / 3) + (y / 3) * board->width;
    int8_t offset = board->entities[pos];

    if (!offset) {
        printf("%s", EMPTY);
    } else if (offset > 0) {
        printf("%s%x", LADDER, offset);
    } else if (offset < 0) {
        printf("%s%x", SNAKE, -offset);
    }
}

// Render a horizontal separator
void render_hor_line(const uint16_t width) {
    for(uint16_t x = 0; x < width * 1.3215; ++x) {
        printf("%s", H_LINE);
    }
}

// Render cells in the current row
void render_cells(
    const struct snalBoard *board,
    const uint8_t *cell_to_player,
    const uint16_t width,
    const uint16_t y,
    const uint16_t dy
) {
    for (uint16_t x = 0; x < width; ++x) {
        uint16_t dx = x % 3;

        if (dy == 0) {
            printf("%s", H_LINE);
        } else if (dx == 0) {
            printf("%s", V_LINE);
        } else if (dy == 1) {
            render_player(board, cell_to_player, x, y);
        } else if (dy == 2) {
            render_offset(board, x, y);
        }
    }
}

// Render the board
void render_board(
    const struct snalConfig *config,
    const struct snalBoard *board
) {
    uint8_t *cell_to_player = map_cells_to_player(config, board);

    printf("\n");
    uint16_t width = board->width * 3 + 1;
    uint16_t height = board->height * 3 + 1;

    for (uint16_t y = 0; y < height; ++y) {
        uint16_t dy = y % 3;

        if (dy == 0) {
            render_hor_line(width);
        } else {
            render_cells(board, cell_to_player, width, y, dy);
        }

        printf("\n");
    }

    free(cell_to_player);
    printf("\n");
}

int main() {
    srand(time(NULL));
    printf("Snakes and Ladders\n");

    const struct snalConfig config = configure_game();
    struct snalBoard board = create_board(&config);

    init_board(&config, &board);

    struct snalTurn turn;
    turn.count = 0;

    bool win_condition = false;
    while(!win_condition) {
        init_turn(&config, &board, &turn);
        print_turn(&config,&turn);

        play_turn(&turn);

        check_bounce(&turn, &board);
        check_offset(&turn, &board);
        render_board(&config, &board);

        win_condition = evaluate_turn(&board, &turn);

        if (!win_condition) {
            advance_turn(&turn);
            printf("Press [Enter] to continue\n");
            getchar();
        }
    }

    free_board(&board);
    return 0;
}

 

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

If you're curious, I threw together a quick solution using my approach as well.

Spoiler

 

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

struct Player {
    short position;
	char name[30];
};

struct State {
	short board_size;
	short player_count;
	int turn_count;
	struct Player* players;
	short* board;
} state;

void setup();
short* generate_board(short);
void game_loop();

// Not flawless. Small chance for an impossible board if last 6 squares are all snakes.
short* generate_board(short board_size) {
	
	// Allocating memory so that it is not cleared when function returns
    short* board = malloc(board_size * sizeof(short));
	
	// Keep track of which squares have snakes/ladders leading to them.
	short targets[board_size];
	short max_ladder = board_size / 2;
	
	for (short i = 1; i < board_size - 1; i++) {
		
		// random snake/ladder up to half the total number of squares.
		short modifier = rand() % max_ladder;
		
		// 50/50 chance to generate a ladder vs a snake
		modifier *= ((rand() % 2) == 0) ? 1 : -1;
		
		short destination = i + modifier;
		
		// Make sure we don't go off the baord
		if (destination >= board_size - 1 || destination < 0) {
			continue;
		}
		
		// Make sure not to send the player to another ladder/snake
		if (board[destination] != 0 || targets[destination] != 0) {
			continue;
		}
		board[i] = modifier;
		
		// mark that the destination square had a snake/ladder leading to it
		// so that it won't have a second snake/ladder leading to it.
		targets[destination] = 1;
	}
	
	return board;
}

void setup() {
	srand(time(NULL));
	
	printf("How many spaces should the board have? ");
	scanf("%hd", &state.board_size); // Todo: sanitize input
	
	state.board = generate_board(state.board_size);
	
	printf("How many players? ");
	scanf("%hd", &state.player_count); // Todo: sanitize input
	
	state.players = malloc(state.player_count * sizeof(struct Player));
	for (int i = 0; i < state.player_count; i++) {
		printf("Enter name for player %d: ", i+1);
		scanf("%s%*c", state.players[i].name);
		state.players[i].position = 0;
	}
}

void game_loop() {
	struct Player* current_player;
	
	do {
		current_player = &state.players[state.turn_count++ % state.player_count];
		
		printf("\n%s's turn. Press ENTER to roll.", current_player->name);
		getchar();
		
		short roll = rand() % 6 + 1;
		printf("\n%s rolled a %hd.", current_player->name, roll);
		
		short new_position = current_player->position + roll;
		current_player->position = (new_position >= state.board_size) ? state.board_size - 1: new_position;
		
		// Computers start counting squares at 0, so add 1
		printf("\n%s landed on square %d.", current_player->name, current_player->position + 1);
		
		short modifer = state.board[new_position];
		if (modifer == 0) {
			printf("\n");
			continue;
		}
		
		// Board generation ensures this won't be out of bounds...hopefully
		current_player->position += modifer;
		printf(
			"\nThat's a %s! %s %s to square %d.\n",
			(modifer > 0) ? "ladder" : "snake",
			current_player->name,
			(modifer > 0) ? "climbed" : "slid",
			current_player->position + 1
		);
	} while (current_player->position != state.board_size - 1);
	
	printf("\n%s wins!", current_player->name);
}

int main() {
	setup();
	game_loop();
}

 

Link to comment
Share on other sites

Link to post
Share on other sites

Thanks your code looks way more clean than mine does with all the structs 🙂 

Link to comment
Share on other sites

Link to post
Share on other sites

Hey guys I just want to give an update that I read a bit more on structs, the static keyword, strcmp(). I have to say these features are really neat to have, I finally understand why @QuantumRand was recommending me to use structs for this program 🙂 

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×