Compare commits

..

No commits in common. "master" and "1.1" have entirely different histories.
master ... 1.1

24 changed files with 191 additions and 329 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@ build/
csnake
*.core
include/config.h
include/config.mk
config.mk

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.5)
project(csnake VERSION 1.2 LANGUAGES C)
project(csnake VERSION 1.0 LANGUAGES C)
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
message(FATAL_ERROR "In-source builds are not allowed.")
@ -10,29 +10,14 @@ add_executable(csnake
src/screen.c
src/input.c
src/player.c
src/sleep.c
src/platform/getch.c
src/platform/game.c
)
set_target_properties(csnake PROPERTIES C_STANDARD 11)
set_target_properties(csnake PROPERTIES C_STANDARD 99)
set_target_properties(csnake PROPERTIES C_EXTENSIONS FALSE)
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
target_link_libraries(csnake pthread)
elseif(${MINGW})
set(CMAKE_C_FLAGS "-D_UCRT")
target_link_libraries(csnake ucrt)
endif()
target_include_directories(csnake PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
set(FIELD_SIZE 10 CACHE STRING "Size of game field")
set(SIZE 10 CACHE STRING "Size of game field")
set(DEFX 0 CACHE STRING "Start x")
set(DEFY 0 CACHE STRING "Start y")
set(SLEEP 1000 CACHE STRING "Sleep between frames (ms)")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/templates/config.h.in
${CMAKE_CURRENT_SOURCE_DIR}/include/config.h
)
configure_file(include/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/config.h)

View File

@ -1,9 +0,0 @@
Copyright 2024 nakidai
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,30 +1,24 @@
include include/config.mk
include config.mk
INCLUDE = -Iinclude
RM = rm -f
SRCDIR = src
OBJDIR = obj
SRC = main.c screen.c input.c player.c sleep.c platform/getch.c platform/game.c
SRC = main.c screen.c input.c player.c
OBJ = $(addprefix $(OBJDIR)/,$(SRC:.c=.o))
# Link pthread on FreeBSD
DEFLDFLAGS += $(shell if echo "" | cc -E -dM -xc - | grep __FreeBSD__ > /dev/null 2>&1; then echo "-lpthread"; fi)
# Use ucrt on MinGW
DEFLDFLAGS += $(shell if echo "" | cc -E -dM -xc - | grep __MINGW32__ > /dev/null 2>&1; then echo "-lucrt"; fi)
DEFCFLAGS += $(shell if echo "" | cc -E -dM -xc - | grep __MINGW32__ > /dev/null 2>&1; then echo "-D_UCRT"; fi)
all: $(OUT)
$(OBJDIR)/platform:
mkdir -p $(OBJDIR)/platform
obj:
mkdir obj
$(OBJDIR)/%.o: $(SRCDIR)/%.c
$(CC) -c -std=c11 -o $@ $< $(CFLAGS) $(DEFCFLAGS) $(INCLUDE)
$(CC) -c -o $@ $< $(CFLAGS) $(INCLUDE)
$(OUT): $(OBJDIR)/platform $(OBJ)
$(CC) -o $@ $(OBJ) $(LDFLAGS) $(DEFLDFLAGS)
$(OUT): obj $(OBJ)
$(CC) -o $@ $(OBJ) $(LDFLAGS)
clean:
$(RM) $(OUT) $(OBJDIR)/*.o $(OBJDIR)/platform/*.o
$(RM) $(OUT) $(OBJDIR)/*
.PHONY: default clean

View File

@ -10,7 +10,7 @@ Supported platforms
--
Was tested on:
- FreeBSD 13.2
- Windows 11 (both MSVC and MinGW)
- Windows 11 (both MinGW and MSVC)
- Linux 6.5.8 (glibc 2.38-7)
Building

47
configure vendored
View File

@ -1,37 +1,25 @@
#!/bin/sh
usage()
{
if [[ "$*" == *"--help"* ]] || [[ "$*" == *"-h"* ]]
then
echo "Use environment variables to pass values:
CC - compiler (default: cc)
CFLAGS - flags for compiler
LDFLAGS - flags for linker
OUT - out file (default: csnake
FIELD_SIZE - size of game field
DEFX - start x
DEFY - start y
SLEEP - sleep between frames (ms)"
CC - compiler (default: cc)
CFLAGS - flags for compiler
LDFLAGS - flags for linker
OUT - out file (default: csnake
SIZE - size of game field
DEFX - start x
DEFY - start y"
exit 1
}
while test $# -gt 0; do
case "$1" in
-h) usage
;;
--help) usage
;;
esac
shift
done
fi
CC=${CC:-cc}
CFLAGS=${CFLAGS:-}
LDFLAGS=${LDFLAGS:-}
OUT=${OUT:-csnake}
FIELD_SIZE=${FIELD_SIZE:-10}
SIZE=${SIZE:-10}
DEFX=${DEFX:-0}
DEFY=${DEFY:-0}
SLEEP=${SLEEP:-1000}
echo "Makefile configuration:"
echo "Compiler: $CC"
@ -40,10 +28,15 @@ echo "LDFLAGS: $LDFLAGS"
echo "Out file: $OUT"
echo
echo "Code configuration:"
echo "Field size: $FIELD_SIZE"
echo "Size: $SIZE"
echo "Start x: $DEFX"
echo "Start y: $DEFY"
echo "Sleep: $SLEEP"
eval "echo \"$(cat templates/config.mk.in)\"" > include/config.mk
eval "echo \"$(cat templates/config.h.in)\"" > include/config.h
echo "CC = $CC
CFLAGS = $CFLAGS
LDFLAGS = $LDFLAGS
OUT = $OUT" > config.mk
echo "#define SIZE $SIZE
#define DEFX $DEFX
#define DEFY $DEFY" > include/config.h

3
include/config.h.in Normal file
View File

@ -0,0 +1,3 @@
#define SIZE ${SIZE}
#define DEFX ${DEFX}
#define DEFY ${DEFY}

View File

@ -3,14 +3,16 @@
#include <stdbool.h>
#include "platform/thread.h"
typedef struct input_args_t
{
int *out;
int *out;
bool *alive;
} InputArgs;
ThreadR input(void *vargp);
#ifdef _WIN32
void input(void *vargp);
#else
void *input(void *vargp);
#endif
#endif /* __INPUT_H__ */

View File

@ -1,6 +0,0 @@
#ifndef __PLATFORM_GAME_H__
#define __PLATFORM_GAME_H__
void platformGameInit(void);
#endif /* __PLATFORM_GAME_H__ */

View File

@ -1,15 +0,0 @@
#ifndef __GETCH_H__
#define __GETCH_H__
#ifdef _WIN32
#include <conio.h>
#define getch _getch
inline int getchInit(void) { return 0; }
#else
int getch(void);
void getchInit(void);
void getchResetTerminalStateHandler(int sig);
void getchResetTerminalState(void);
#endif /* _WIN32 */
#endif /* __GETCH_H__ */

View File

@ -1,29 +0,0 @@
#ifndef __PLATFORM_SCREEN_H__
#define __PLATFORM_SCREEN_H__
#ifdef _WIN32
#ifdef __MINGW32__
#include <windows.h>
#else
#include <Windows.h>
#endif /* __MINGW32__ */
#else
#include <stdio.h>
#endif /* _WIN32 */
#ifdef _WIN32
static inline void resetCoordinates(void)
{
HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(output, (COORD){0});
}
#else
static inline void resetCoordinates(void)
{
printf("\e[1;1H\e[2J");
}
#endif /* _WIN32 */
#endif /* __PLATFORM_SCREEN_H__ */

View File

@ -1,29 +0,0 @@
#ifndef __THREAD_H__
#define __THREAD_H__
#ifdef _WIN32
#include <process.h>
#else
#include <pthread.h>
#include <stddef.h>
#endif /* _WIN32 */
#ifdef _WIN32
typedef void ThreadR;
#define ThreadReturn return
#else
typedef void* ThreadR;
#define ThreadReturn return NULL
#endif /* _WIN32 */
typedef ThreadR (*Thread)(void *);
static inline void threadCreate(Thread function, void *args)
{
#ifdef _WIN32
_beginthread(function, 0, args);
#else
pthread_create(&(pthread_t){0}, 0, function, args);
#endif /* _WIN32 */
}
#endif /* __THREAD_H__ */

View File

@ -21,10 +21,11 @@ struct player_t
int score;
};
void playerCreate(Player *buffer, Direction direction, int x, int y, int score);
Player *playerCreate(Direction direction, int x, int y, int score);
void playerFree(Player *player);
bool playerCheckSelfCollision(Player player);
bool playerCheckFoodCollision(Player player, Food food);
bool playerCheckSelfCollision(Player *player);
bool playerCheckFoodCollision(Player *player, Food food);
bool playerDoTick(Player *player, Food food);
#endif /* __PLAYER_H__ */

View File

@ -1,8 +1,6 @@
#ifndef __SCREEN_H__
#define __SCREEN_H__
#include <string.h>
typedef char Point;
typedef struct screen_t
{
@ -11,18 +9,11 @@ typedef struct screen_t
Point *screen;
} Screen;
void screenCreate(Screen *buffer, int width, int height, Point fill_value);
void screenShow(Screen screen);
static inline Point *screenGetPoint(Screen screen, int x, int y)
{
return screen.screen + x + (y * screen.width);
}
static inline void screenSet(Screen screen, Point fill_value)
{
memset(screen.screen, fill_value, screen.width * screen.height * sizeof(char));
}
Screen *screenCreate(int width, int height, Point fill_value);
void screenFree(Screen *screen);
Point *screenGetPoint(Screen *screen, int x, int y);
void screenShow(Screen *screen);
void screenSet(Screen *screen, char fill_value);
#endif /* __SCREEN_H__ */

View File

@ -1,6 +0,0 @@
#ifndef __SLEEP_H__
#define __SLEEP_H__
void sleepMS(int msec);
#endif /* __SLEEP_H__ */

View File

@ -1,11 +1,40 @@
#include "input.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <conio.h>
#else
#include <unistd.h>
#include <termios.h>
#endif
#include "input.h"
#include "platform/thread.h"
#include "platform/getch.h"
#ifdef _WIN32
#define getch _getch
#else
int getch(void)
{
char buf = 0;
struct termios old = { 0 };
fflush(stdout);
if (tcgetattr(0, &old) < 0) perror("tcsetattr()");
old.c_lflag &= ~ICANON; // local modes = Non Canonical mode
old.c_lflag &= ~ECHO; // local modes = Disable echo.
old.c_cc[VMIN] = 1; // control chars (MIN value) = 1
old.c_cc[VTIME] = 0; // control chars (TIME value) = 0 (No time)
if (tcsetattr(0, TCSANOW, &old) < 0) perror("tcsetattr ICANON");
if (read(0, &buf, 1) < 0) perror("read()");
old.c_lflag |= ICANON; // local modes = Canonical mode
old.c_lflag |= ECHO; // local modes = Enable echo.
if (tcsetattr(0, TCSADRAIN, &old) < 0) perror ("tcsetattr ~ICANON");
return (int)buf;
}
#endif
ThreadR input(void *vargp)
#ifdef _WIN32
void input(void *vargp)
#else
void *input(void *vargp)
#endif
{
int *out = ((InputArgs *)vargp)->out;
bool *alive = ((InputArgs *)vargp)->alive;
@ -14,5 +43,7 @@ ThreadR input(void *vargp)
{
*out = getch();
}
ThreadReturn;
#ifndef _WIN32
return NULL;
#endif
}

View File

@ -2,91 +2,112 @@
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#ifdef _WIN32
#include <Windows.h>
#include <process.h>
#else
#include <pthread.h>
#endif
#include "input.h"
#include "screen.h"
#include "player.h"
#include "food.h"
#include "config.h"
#include "sleep.h"
#include "platform/thread.h"
#include "platform/screen.h"
#include "platform/game.h"
void drawPlayer(Player player, Screen screen)
void drawPlayer(Player *player, Screen *screen)
{
PlayerNode *node;
for (node = player.tail; node != NULL; node = node->next)
for (node = player->tail; node != NULL; node = node->next)
*screenGetPoint(screen, node->x, node->y) = '#';
}
Food generateFood(Player player)
Food generateFood(Player *player)
{
Food food;
do
{
food = (Food){rand() % FIELD_SIZE, rand() % FIELD_SIZE};
food = (Food){rand() % SIZE, rand() % SIZE};
} while (playerCheckFoodCollision(player, food));
return food;
}
#ifdef _WIN32
void resetCoordinates(void)
{
HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(output, (COORD){0});
}
#else
void resetCoordinates(void)
{
printf("\e[1;1H\e[2J");
}
#endif
int main(int argc, char **argv)
{
srand((unsigned int)time(NULL));
platformGameInit();
Player *player = playerCreate(DOWN, DEFX, DEFY, 0);
Screen *screen = screenCreate(SIZE, SIZE, ' ');
int i;
int head_x, head_y;
Food food = generateFood(player);
Player player; playerCreate(&player, DOWN, DEFX, DEFY, 0);
Screen screen; screenCreate(&screen, FIELD_SIZE, FIELD_SIZE, ' ');
Food food = generateFood(player);
int key = 0;
bool running = true;
bool *running = malloc(sizeof(bool)); *running = true;
int *key = malloc(sizeof(char)); *key = 0;
bool stopped = false;
int head_x, head_y;
InputArgs input_args = (InputArgs){ key, running };
threadCreate(input, &(InputArgs){ &key, &running });
while (running)
#ifdef _WIN32
_beginthread(input, 0, &input_args);
#else
pthread_t input_thread;
pthread_create(&input_thread, NULL, input, &input_args);
#endif
while (*running)
{
screenSet(screen, ' ');
drawPlayer(player, screen);
*screenGetPoint(screen, food.x, food.y) = '@';
resetCoordinates();
screenShow(screen);
for (int i = 0; i < FIELD_SIZE*2; ++i) putchar('-');
printf("\nScore: %d\n", player.score);
for (i = 0; i < SIZE*2; ++i) putchar('-');
printf("\nScore: %d\n", player->score);
sleepMS(SLEEP);
switch (key)
#ifdef _WIN32
Sleep(1000L);
#else
nanosleep(&(struct timespec){.tv_sec = 1}, NULL);
#endif
switch (*key)
{
case 'q':
running = false; return 0;
*running = false; return 0;
case 'p':
stopped = !stopped; break;
case 'w':
if (player.direction == DOWN) break;
player.direction = UP; break;
if (player->direction == DOWN) break;
player->direction = UP; break;
case 'd':
if (player.direction == LEFT) break;
player.direction = RIGHT; break;
if (player->direction == LEFT) break;
player->direction = RIGHT; break;
case 's':
if (player.direction == UP) break;
player.direction = DOWN; break;
if (player->direction == UP) break;
player->direction = DOWN; break;
case 'a':
if (player.direction == RIGHT) break;
player.direction = LEFT; break;
} key = 0;
if (player->direction == RIGHT) break;
player->direction = LEFT; break;
} *key = 0;
if (stopped) continue;
if (playerDoTick(&player, food) && player.score < FIELD_SIZE*FIELD_SIZE - 1)
if (playerDoTick(player, food) && player->score < SIZE*SIZE - 1)
food = generateFood(player);
head_x = player.head->x;
head_y = player.head->y;
if (head_x >= FIELD_SIZE || head_x < 0 || head_y >= FIELD_SIZE || head_y < 0 || playerCheckSelfCollision(player))
head_x = player->head->x;
head_y = player->head->y;
if (head_x >= SIZE || head_x < 0 || head_y >= SIZE || head_y < 0 || playerCheckSelfCollision(player))
{
running = false;
*running = false;
break;
}
}

View File

@ -1,24 +0,0 @@
#ifdef _WIN32
void platformGameInit(void) {}
#else
#include <signal.h>
#include <stdlib.h>
#include "platform/getch.h"
void platformGameInit(void)
{
getchInit();
atexit(getchResetTerminalState);
signal(SIGINT, getchResetTerminalStateHandler);
signal(SIGABRT, getchResetTerminalStateHandler);
signal(SIGFPE, getchResetTerminalStateHandler);
signal(SIGILL, getchResetTerminalStateHandler);
signal(SIGSEGV, getchResetTerminalStateHandler);
signal(SIGTERM, getchResetTerminalStateHandler);
}
#endif /* _WIN32 */

View File

@ -1,43 +0,0 @@
#ifndef _WIN32
#include <unistd.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
static struct termios DefaultState = {0};
int getch(void)
{
char buf = 0;
struct termios old = {0};
fflush(stdout);
if (tcgetattr(0, &old) < 0) perror("tcsetattr()");
old.c_lflag &= ~ICANON; // local modes = Non Canonical mode
old.c_lflag &= ~ECHO; // local modes = Disable echo.
old.c_cc[VMIN] = 1; // control chars (MIN value) = 1
old.c_cc[VTIME] = 0; // control chars (TIME value) = 0 (No time)
if (tcsetattr(0, TCSANOW, &old) < 0) perror("tcsetattr ICANON");
if (read(0, &buf, 1) < 0) perror("read()");
old.c_lflag |= ICANON; // local modes = Canonical mode
old.c_lflag |= ECHO; // local modes = Enable echo.
if (tcsetattr(0, TCSADRAIN, &old) < 0) perror ("tcsetattr ~ICANON");
return (int)buf;
}
void getchResetTerminalState(void)
{
tcsetattr(0, TCSANOW, &DefaultState);
}
void getchResetTerminalStateHandler(int sig)
{
getchResetTerminalState();
_Exit(sig);
}
void getchInit(void)
{
tcgetattr(0, &DefaultState);
}
#endif /* !_WIN32 */

View File

@ -2,32 +2,39 @@
#include "player.h"
void playerCreate(Player *buffer, Direction direction, int x, int y, int score)
Player *playerCreate(Direction direction, int x, int y, int score)
{
Player *player = (Player *)malloc(sizeof(Player));
PlayerNode *head = (PlayerNode *)malloc(sizeof(PlayerNode));
head->x = x;
head->y = y;
head->next = NULL;
buffer->tail = head;
buffer->head = head;
buffer->score = score;
buffer->direction = direction;
player->tail = head;
player->head = head;
player->score = score;
player->direction = direction;
return player;
}
bool playerCheckFoodCollision(Player player, Food food)
void playerFree(Player *player)
{
for (PlayerNode *node = player.tail; node != NULL; node = node->next)
}
bool playerCheckFoodCollision(Player *player, Food food)
{
for (PlayerNode *node = player->tail; node != NULL; node = node->next)
if (node->x == food.x && node->y == food.y)
return true;
return false;
}
bool playerCheckSelfCollision(Player player)
bool playerCheckSelfCollision(Player *player)
{
PlayerNode *nodei, *nodej;
for (nodei = player.tail; nodei != NULL; nodei = nodei->next)
for (nodei = player->tail; nodei != NULL; nodei = nodei->next)
for (nodej = nodei->next; nodej != NULL; nodej = nodej->next)
if (nodei->x == nodej->x && nodei->y == nodej->y)
return true;

View File

@ -1,24 +1,36 @@
#include "screen.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "screen.h"
void screenCreate(Screen *buffer, int width, int height, Point fill_value)
Screen *screenCreate(int width, int height, Point fill_value)
{
Point *screen = malloc(width * height * sizeof(Point));
memset(screen, fill_value, width * height * sizeof(Point));
buffer->width = width;
buffer->height = height;
buffer->screen = screen;
Screen *out = malloc(sizeof(Screen));
out->width = width;
out->height = height;
out->screen = screen;
return out;
}
void screenShow(Screen screen)
void screenFree(Screen *screen)
{
free(screen->screen);
free(screen);
}
Point *screenGetPoint(Screen *screen, int x, int y)
{
return screen->screen + x + (y * screen->width);
}
void screenShow(Screen *screen)
{
int x, y, i;
int width = screen.width;
int height = screen.height;
int width = screen->width;
int height = screen->height;
Point point;
for (y = 0; y < height; ++y)
@ -31,3 +43,8 @@ void screenShow(Screen screen)
putchar('\n');
}
}
void screenSet(Screen *screen, Point fill_value)
{
memset(screen->screen, fill_value, screen->width * screen->height * sizeof(char));
}

View File

@ -1,14 +0,0 @@
#include <time.h>
static long long int getMS()
{
struct timespec ts;
timespec_get(&ts, TIME_UTC);
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
void sleepMS(int msec)
{
long long int end = getMS() + msec;
while (getMS() < end);
}

View File

@ -1,4 +0,0 @@
#define FIELD_SIZE ${FIELD_SIZE}
#define DEFX ${DEFX}
#define DEFY ${DEFY}
#define SLEEP ${SLEEP}

View File

@ -1,4 +0,0 @@
CC = ${CC}
CFLAGS = ${CFLAGS}
LDFLAGS = ${LDFLAGS}
OUT = ${OUT}