-5
\$\begingroup\$

I just ended the console snake game and want to review it. I hope you'll review my codestyle (e.g. I don't know what to do with #ifdef inserts in init_game() and end_game() functions) and leave some comments about the game logic, maybe about the view (draw function). Edit: thank you pacmaninbw for remark! I added missed game logic for both windows and linux (but linux is still with a bug)

main.c

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <stddef.h>
#include <stdbool.h>

#ifdef WIN32
    #include <conio.h>   // getch
    #include <windows.h> // SetConsoleCursorPosition
#endif

#ifdef __linux__
    #include <termio.h>   // for getch imitation
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #define  CSI "\033["
    static struct termios old, new;
#endif


/** Types **/
#define nullptr ((void *) 0)

typedef enum cell_state_t {
    CS_EMPTY,
    CS_EAT,
    CS_SHEAD,
    CS_STAIL
} cell_state_t;

typedef enum game_state_t {
    GS_ACTIVE,
    GS_PAUSED,
    GS_VICTORY,
    GS_DEFEAT
} game_state_t;

typedef enum direction_t {
    D_N,
    D_S,
    D_W,
    D_E
} direction_t;

typedef enum key_map_t {
    KM_N,
    KM_UP     = 'w',
    KM_DOWN   = 's',
    KM_LEFT   = 'a',
    KM_RIGHT  = 'd',
    KM_PAUSE  = 'p',
    KM_EXIT   = 'z'
} key_map_t;

enum {
    F_ROWS   = 10,
    F_COLS   = 10,
    F_TOTAL  = F_ROWS * F_COLS
};

typedef struct point_t {
    int x, y;
}  point_t;

typedef struct Snake {
    point_t p;
    direction_t dir;
    size_t tail_size;
    point_t tail[F_TOTAL - 1];
} Snake;


/** Globals **/
static cell_state_t field[F_ROWS][F_COLS];
static Snake        snake;
static game_state_t game_state;
static unsigned int score;


/** Functions **/
#ifdef WIN32
void clear_screen(void)
{
    system("cls");
}

void restore_cursor_pos(void)
{
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), (COORD) {0, 0});
}

void snake_sleep(unsigned int ms)
{
    Sleep(ms);
}
#endif

#ifdef __linux__
void clear_screen(void)
{
    system("clear");
}

void restore_cursor_pos(void)
{
    printf(CSI"1;1H");
}

/* these functions were taken from
*  https://stackoverflow.com/questions/7469139/what-is-the-equivalent-to-getch-getche-in-linux
*/
void initTermios(bool echo)
{
  tcgetattr(0, &old); /* grab old terminal i/o settings */
  new = old;
  new.c_lflag &= (unsigned int) ~ICANON; /* disable buffered i/o */
  if (echo) {
      new.c_lflag |= (unsigned int) ECHO; /* set echo mode */
  } else {
      new.c_lflag &= (unsigned int) ~ECHO; /* set no echo mode */
  }
  tcsetattr(0, TCSANOW, &new); /* use these new terminal i/o settings now */
}

/* Restore old terminal i/o settings */
void resetTermios(void)
{
  tcsetattr(0, TCSANOW, &old);
}

int getch(void)
{
    return getchar();
}

bool kbhit(void)
{
  struct timeval tv;
  fd_set rdfs;

  tv.tv_sec = 0;
  tv.tv_usec = 0;

  FD_ZERO(&rdfs);
  FD_SET(STDIN_FILENO, &rdfs);

  select(STDIN_FILENO+1, &rdfs, NULL, NULL, &tv);
  return FD_ISSET(STDIN_FILENO, &rdfs);

}

void snake_sleep(unsigned int ms)
{
    usleep(ms * 1000u);
}
#endif

direction_t get_opposit_dir(direction_t d1)
{
    switch (d1) {
    case D_N: return D_S;
    case D_S: return D_N;
    case D_W: return D_E;
    case D_E: return D_W;
    }

    return D_N;
}

key_map_t get_key_from_user(void)
{
    int c;
    c = tolower(getch());
    switch (c) {
    case 'w':  return KM_UP;
    case 's':  return KM_DOWN;
    case 'a':  return KM_LEFT;
    case 'd':  return KM_RIGHT;
    case 'p':  return KM_PAUSE;
    case 'z':  return KM_EXIT;
    default:   return KM_N;
    }
}

void place_eat(void)
{
    int x, y;
    do {
        x = rand() % F_COLS;
        y = rand() % F_ROWS;
    } while (field[y][x] != CS_EMPTY);

    field[y][x] = CS_EAT;
}

void move_snake(direction_t dir, bool ate)
{
    point_t buf1, buf2;

    buf1 = snake.p;
    if (get_opposit_dir(snake.dir) != dir)
        snake.dir = dir;

    field[snake.p.y][snake.p.x] = CS_EMPTY;
    switch (snake.dir) {
    case D_N:
        snake.p.y = (snake.p.y - 1 + F_ROWS) % F_ROWS;
        break;

    case D_S:
        snake.p.y = (snake.p.y + 1) % F_ROWS;
        break;

    case D_W:
        snake.p.x = (snake.p.x - 1 + F_COLS) % F_COLS;
        break;

    case D_E:
        snake.p.x = (snake.p.x + 1) % F_COLS;
        break;
    }

    for (size_t i = 0; i < snake.tail_size; ++i) {
        field[snake.tail[i].y][snake.tail[i].x] = CS_EMPTY;

        buf2 = snake.tail[i];
        snake.tail[i] = buf1;
        buf1 = buf2;

        field[snake.tail[i].y][snake.tail[i].x] = CS_STAIL;
    }

    if (ate) {
        snake.tail[snake.tail_size] = buf1;
        field[buf1.y][buf1.x] = CS_STAIL;
        ++snake.tail_size;
    }
}

void draw_screen(void)
{
    restore_cursor_pos();
    for (size_t i = 0; i < F_ROWS; ++i) {
        for (size_t j = 0; j < F_COLS; ++j) {
            switch (field[i][j]) {
            case CS_EMPTY:
                putchar(' ');
                break;

            case CS_SHEAD:
                putchar('o');
                break;

            case CS_STAIL:
                putchar('*');
                break;

            case CS_EAT:
                putchar('e');
                break;
            }
            putchar(' ');
        }

        if (i == 2)
            printf("\tScore: %d", score);
        else if (i == 3)
            printf("\tTo pause/unpause press 'p', to exit press 'z'");
        putchar('\n');
    }
}

void init_game(void)
{
#ifdef __linux__
    initTermios(0);
#endif

    srand((unsigned int) time(nullptr));
    clear_screen();

    for (size_t i = 0; i < F_ROWS; ++i)
        for (size_t j = 0; j < F_COLS; ++j)
            field[i][j] = CS_EMPTY;

    snake.p = (point_t) {4, 4};
    snake.dir = D_N;
    snake.tail_size = 0;
    field[snake.p.y][snake.p.x] = CS_SHEAD;

    place_eat();

    game_state = GS_ACTIVE;
    score = 0;
}

void main_loop(void)
{
    direction_t nd   = D_N;
    key_map_t   k   = KM_N;
    bool        ate = false;

    draw_screen();
    while ((game_state == GS_ACTIVE || game_state == GS_PAUSED) &&
           (k = get_key_from_user()) != KM_EXIT) {

        if (k == KM_PAUSE)
            game_state = (game_state == GS_PAUSED) ? GS_ACTIVE : GS_PAUSED;

        if (game_state == GS_PAUSED)
            continue;

        switch (k) {
        case KM_UP:
            nd = D_N;
            break;

        case KM_DOWN:
            nd = D_S;
            break;

        case KM_LEFT:
            nd = D_W;
            break;

        case KM_RIGHT:
            nd = D_E;
            break;

        default:
            nd = snake.dir;
        }

        do {
            move_snake(nd, ate);
            ate = false;
            if (field[snake.p.y][snake.p.x] == CS_EAT) {
                ate = true;
                score += 100;
                place_eat();
            }

            if (field[snake.p.y][snake.p.x] == CS_STAIL)
                game_state = GS_DEFEAT;

            field[snake.p.y][snake.p.x] = CS_SHEAD;
            draw_screen();

            if (snake.tail_size == F_TOTAL - 1)
                game_state = GS_VICTORY;

            snake_sleep(100);
        } while (!kbhit() && game_state == GS_ACTIVE);
    }

}

void end_game(void)
{

    if (game_state == GS_VICTORY)
        printf("\n\nVICTORY!\n");
    else
        printf("\n\nDEFEAT!\n");

    printf("To exit press any key.\n");
    getch();

#ifdef __linux__
    resetTermios();
#endif
}

int main()
{
    init_game();
    main_loop();
    end_game();
    return 0;
}

\$\endgroup\$

closed as off-topic by πάντα ῥεῖ, pacmaninbw, 200_success, dfhwze, Oh My Goodness Jun 29 at 5:11

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Code not implemented or not working as intended: Code Review is a community where programmers peer-review your working code to address issues such as security, maintainability, performance, and scalability. We require that the code be working correctly, to the best of the author's knowledge, before proceeding with a review." – πάντα ῥεῖ, pacmaninbw, 200_success, dfhwze, Oh My Goodness
If this question can be reworded to fit the rules in the help center, please edit the question.

  • 5
    \$\begingroup\$ If you look on the lower right of this question you will find some links that can help you with the timing in the "Related" section. Right now your question is off-topic because the program isn't working as expected. \$\endgroup\$ – pacmaninbw Jun 28 at 15:15
  • 3
    \$\begingroup\$ i see the revision comment for edit number 4 was “Removed bug that interfere with compilation” that doesn’t resolve the issue mentioned at the end of the description (i.e. “linux is still with a bug”), does it? if so, then it would be advisable for you to remove that text. \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jun 29 at 23:29
  • \$\begingroup\$ @SᴀᴍOnᴇᴌᴀ no, bug with compilation was a silly mistake. Bug I'm talking about -- bug with i/o, in the kbhit() function implementation for linux. I didn't find any solution for it so I wrote about it in description. Do I need to remove that text then? \$\endgroup\$ – oss Jun 30 at 20:07
  • \$\begingroup\$ if the code works to the best of your knowledge (i.e. has no bugs and produces desired output) then I would remove that text. \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jul 1 at 18:10
  • \$\begingroup\$ Edited the description and removed a comment about the bug in the code \$\endgroup\$ – oss Jul 6 at 20:17
2
\$\begingroup\$

the program does not cleanly compile.

When compiling, always enable the warnings, then fix those warnings. ( for gcc, at a minimum use: -Wall -Wextra -Wconversion -pedantic -std=gnu11 ) Note: other compilers use different options to produce the same results

The posted code results in:

gcc    -ggdb -Wall -Wextra -Wconversion -pedantic -std=gnu11  -c "untitled.c"  ...

untitled.c: In function ‘initTermios’:
untitled.c:119:15: warning: negative integer implicitly converted to unsigned type [-Wsign-conversion]
   new.c_lflag &= ~ICANON; /* disable buffered i/o */
               ^~

untitled.c:123:19: warning: negative integer implicitly converted to unsigned type [-Wsign-conversion]
       new.c_lflag &= ~ECHO; /* set no echo mode */
                   ^~

untitled.c: In function ‘snake_sleep’:
untitled.c:157:12: warning: conversion to ‘__useconds_t {aka unsigned int}’ from ‘long int’ may alter its value [-Wconversion]
     usleep(ms * 1000L);
            ^~

untitled.c: In function ‘place_eat’:
untitled.c:190:13: warning: conversion to ‘uint16_t {aka short unsigned int}’ from ‘int’ may alter its value [-Wconversion]
         x = rand() % F_COLS;
             ^~~~

untitled.c:191:13: warning: conversion to ‘uint16_t {aka short unsigned int}’ from ‘int’ may alter its value [-Wconversion]
         y = rand() % F_ROWS;
             ^~~~

untitled.c: In function ‘move_snake’:
untitled.c:208:21: warning: conversion to ‘int16_t {aka short int}’ from ‘int’ may alter its value [-Wconversion]
         snake.p.y = (snake.p.y - 1 + F_ROWS) % F_ROWS;
                     ^

untitled.c:212:21: warning: conversion to ‘int16_t {aka short int}’ from ‘int’ may alter its value [-Wconversion]
         snake.p.y = (snake.p.y + 1) % F_ROWS;
                     ^

untitled.c:216:21: warning: conversion to ‘int16_t {aka short int}’ from ‘int’ may alter its value [-Wconversion]
         snake.p.x = (snake.p.x - 1 + F_COLS) % F_COLS;
                     ^

untitled.c:220:21: warning: conversion to ‘int16_t {aka short int}’ from ‘int’ may alter its value [-Wconversion]
         snake.p.x = (snake.p.x + 1) % F_COLS;
                     ^

untitled.c: In function ‘init_game’:
untitled.c:280:11: warning: conversion to ‘unsigned int’ from ‘time_t {aka long int}’ may alter its value [-Wconversion]
     srand(time(nullptr));
           ^~~~

untitled.c: In function ‘get_opposit_dir’:
untitled.c:169:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^
\$\endgroup\$
  • 1
    \$\begingroup\$ > -Wall -Wextra -Wconversion -pedantic -std=gnu11 Oh, I didn't know about this flags. I used only -Wall -std=c11. Thank you, updated post. \$\endgroup\$ – oss Jun 28 at 23:04
  • 1
    \$\begingroup\$ If the code is fixed now, please remove your answer. This is not Stack Overflow and we have a policy against answering questions which do not fit the scope of the site. \$\endgroup\$ – Mast Jul 3 at 11:06
  • \$\begingroup\$ @Mast, Not my code, so I have no idea if the code is fixed. However, the OP wanted a code review. That is what I gave the OP \$\endgroup\$ – user3629249 Jul 6 at 0:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.