1
\$\begingroup\$

Six month ago I started studying C as my first programming language and a couple of weeks ago I decided to write snake as my first program. The program is written with ncurses and contains four modules: main, layout, controls and gameplay. I haven't added any features yet (score, pause, welcome screen etc.).

Since it is my first program I would really appreciate any comments on how to improve my code or make it more efficient. You may also comment on the program design on a more general level. Many thanks!

main.c

#define _DEFAULT_SOURCE

#include "layout.h"
#include "controls.h"
#include "gameplay.h"
#include <unistd.h>
#include <ctype.h>

struct snake *head = NULL;
struct food cheese;
chtype body;
int snakey = 5;
int snakex = 5;
int ch;
bool horizontal = true;
bool run = true;

void init_game(void);
int delay(int csec, bool horizontal);
bool check_game(void);
bool eat_cheese();
void change_direction(void);
void update_window(void);

int main(void)
{
    srand(time(0));

    init_game();
    generate_cheese(&cheese, head);
    direction = right;

    while (true) {

        if (check_game())
            break;
        
        if (eat_cheese())
            continue;

        ch = delay(10, horizontal);
        if (ch != ERR) {
            delay(7, horizontal);
        }
        if (tolower(ch) == 'q')
            break;

        change_direction();
        update_window();
    }

    delay(100, horizontal);
    endwin();
    return 0;
}

void init_game(void)
{
    WIN win;

    system("resize -s 31 100");
    initscr();
    raw();
    keypad(stdscr, true);
    nodelay(stdscr, true);
    noecho();
    curs_set(false);
    win_params(&win);
    create_box(&win);
    refresh();

}

int delay(int csec, bool horizontal)
{
    int ch;

    for (int i = 0; i < csec; i++) {
        ch = getch();
        if (horizontal == true) {
            if (ch == KEY_UP | ch == KEY_DOWN) {
                return ch;
            }
        } else if (horizontal == false) {
            if (ch == KEY_LEFT | ch == KEY_RIGHT) {
                return ch;
            }
        }
        if (ch == 'q') {
            return ch;
        }
        flushinp();
        usleep(10000);
    }
    return ERR;
}

bool check_game(void)
{
    if (border_limit(head) || autocannibalism(head, snakey, snakex)) {
        mvprintw(head->y, head->x, "*");
        return true;
    }
    return false;

}

bool eat_cheese()
{

    if (cheese.y == snakey && cheese.x == snakex) {
        direction(&snakey, &snakex, &body);
        push_to_snake(&head, snakey, snakex, body);
        generate_cheese(&cheese, head);
        print_window(head, cheese);
        return true;
        }
    return false;
}

void change_direction(void)
{
    switch(ch) {
        case KEY_UP: 
             direction = up;
             horizontal = false;
             break;
        case KEY_DOWN: 
             direction = down;
             horizontal = false;
            break;
        case KEY_LEFT: 
            direction = left;
            horizontal = true;
            break;
        case KEY_RIGHT: 
            direction = right;
            horizontal = true;
            break;
    }
}

void update_window(void)
{
    direction(&snakey, &snakex, &body);
    pop_from_snake(&head);
    push_to_snake(&head, snakey, snakex, body);
    print_window(head, cheese);
}

layout.h

#ifndef LAYOUT_H
#define LAYOUT_H

#include <ncurses.h>

#define BORDER_STARTY 2
#define BORDER_STARTX 2
#define BORDER_HEIGHT 27
#define BORDER_WIDTH 96

typedef struct _win_border_struct {
    chtype ls, rs, ts, bs,
           tl, tr, bl, br;
} WIN_BORDER;

typedef struct _WIN_struct {
    int startx, starty;
    int height, width;
    WIN_BORDER border;
} WIN;

void win_params(WIN *p_win);
void create_box(WIN *p_win);

#endif

layout.c

#include "layout.h"

void win_params(WIN *p_win)
{
    p_win->height = BORDER_HEIGHT;
    p_win->width = BORDER_WIDTH;
    p_win->starty = BORDER_STARTY;
    p_win->startx = BORDER_STARTX;

    p_win->border.ls = ACS_VLINE;
    p_win->border.rs = ACS_VLINE;
    p_win->border.ts = ACS_HLINE;
    p_win->border.bs = ACS_HLINE;
    p_win->border.tl = ACS_ULCORNER;
    p_win->border.tr = ACS_URCORNER;
    p_win->border.bl = ACS_LLCORNER;
    p_win->border.br = ACS_LRCORNER;
}

void create_box(WIN *p_win)
{
    int x, y, w, h;

    x = p_win->startx;
    y = p_win->starty;
    w = p_win->width;
    h = p_win->height;

    mvaddch(y, x, p_win->border.tl);
    mvaddch(y, x + w, p_win->border.tr);
    mvaddch(y + h, x, p_win->border.bl);
    mvaddch(y + h, x + w, p_win->border.br);
    mvhline(y, x + 1, p_win->border.ts, w - 1);
    mvhline(y + h, x + 1, p_win->border.bs, w - 1);
    mvvline(y + 1, x, p_win->border.ls, h - 1);
    mvvline(y + 1, x + w, p_win->border.rs, h - 1);

    refresh();

}

controls.h

#ifndef CONTROLS_H
#define CONTROLS_H

#include "layout.h"

void (*direction)(int *snakey, int *snakex, chtype *body);
void left(int *snakey, int *snakex, chtype *body);
void right(int *snakey, int *snakex, chtype *body);
void up(int *snakey, int *snakex, chtype *body);
void down(int *snakey, int *snakex, chtype *body);

#endif

controls.c

#include "controls.h"

void left(int *snakey, int *snakex, chtype *body)
{
    (*snakex)--;
    *body = ACS_CKBOARD;
}

void right(int *snakey, int *snakex, chtype *body)
{
    (*snakex)++;
    *body = ACS_CKBOARD;
}

void up(int *snakey, int *snakex, chtype *body)
{
    (*snakey)--;
    *body = ACS_CKBOARD;
}

void down(int *snakey, int *snakex, chtype *body)
{
    (*snakey)++;
    *body = ACS_CKBOARD;
}

gameplay.h

#ifndef GAMEPLAY_H
#define GAMEPLAY_H

#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include "layout.h"

struct food {
    int y;
    int x;
};

struct snake {
    chtype body;
    int x;
    int y;
    struct snake *next;
};

bool border_limit(struct snake *head);
bool autocannibalism(struct snake *head, int snakey, int snakex);
void generate_cheese(struct food *cheese, struct snake *head);
bool check_overlap(struct food *cheese, struct snake *head);
void push_to_snake(struct snake **head, int y, int x, chtype body);
void pop_from_snake(struct snake **head);
void clear_window(struct food cheese);
void print_window(struct snake *head, struct food cheese);

#endif

gameplay.c

#include "gameplay.h"

#define CHEESE ACS_DIAMOND


bool border_limit(struct snake *head)
{
    if (head == NULL) {
        return FALSE;
    } else if (head->y == BORDER_STARTY | head->y == BORDER_HEIGHT + BORDER_STARTY |
        head->x == BORDER_STARTX | head->x == BORDER_WIDTH + BORDER_STARTX) {
        return TRUE;
    }

    return FALSE;
}

bool autocannibalism(struct snake *head, int snakey, int snakex)
{
    if (head == NULL) {
        return FALSE;
    } else {
        head = head->next;
    }

    while (head) {
        if (snakey == head->y && snakex == head->x) {
            return TRUE;
        }
        head = head->next; 
    }

    return FALSE;
}

void generate_cheese(struct food *cheese, struct snake *head)
{
    while (true) {
        cheese->y = (rand() % (BORDER_HEIGHT - 1)) + BORDER_STARTY + 1; 
        cheese->x = (rand() % (BORDER_WIDTH - 1)) + BORDER_STARTX + 1;

        if (check_overlap(cheese, head)) {
            continue;
        } else {
            mvaddch(cheese->y, cheese->x, CHEESE);
            break;
        }
    }
}

bool check_overlap(struct food *cheese, struct snake *head)
{
    while (head != NULL) {
        if (cheese->y == head->y && cheese->x == head->x) {
            return true;
        }
        head = head->next;
    }
    return false;
}

void push_to_snake(struct snake **head, int y, int x, chtype body)
{
    struct snake *new_snake;

    new_snake = malloc(sizeof(struct snake));
    if (new_snake == NULL) {
        printf("Error: malloc failed in %s", __func__);
        exit(EXIT_FAILURE);

    }

    new_snake->body = body;
    new_snake->y = y;
    new_snake->x = x;
    new_snake->next = *head;
    *head = new_snake;
}

void pop_from_snake(struct snake **head)
{
    struct snake *tail = *head;

    while (tail != NULL) {
        if (tail->next == NULL) {
            *head = tail->next;
            free(tail);
            break;
        }
        head = &tail->next;
        tail = tail->next;
    }
}

void clear_window(struct food cheese)
{
    for (int i = BORDER_STARTX + 1; i < BORDER_HEIGHT + 2; i++) {
        for (int j = BORDER_STARTY + 1; j < BORDER_WIDTH + 2; j++) {
                mvprintw(i, j, " ");
        }
    }
    mvaddch(cheese.y, cheese.x, CHEESE);
}

void print_window(struct snake *head, struct food cheese)
{
    clear_window(cheese);

    while (head) {
        mvaddch(head->y, head->x, head->body);
        head = head->next;
    }

    refresh();
}
\$\endgroup\$
1
  • \$\begingroup\$ border_limit should use the || operator in the if statement, not |. \$\endgroup\$ – 1201ProgramAlarm Feb 23 at 18:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.