Шесть месяцев назад я начал изучать C как свой первый язык программирования, а пару недель назад я решил написать snake как свою первую программу. Программа написана на ncurses и содержит четыре модуля: основной, макет, элементы управления и игровой процесс. Я еще не добавил никаких функций (счет, пауза, экран приветствия и т. Д.).
Поскольку это моя первая программа, я был бы очень признателен за любые комментарии о том, как улучшить мой код или сделать его более эффективным. Вы также можете прокомментировать структуру программы на более общем уровне. Большое спасибо!
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();
}
control.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
control.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();
}