This commit is contained in:
Lukáš Dratva 2019-06-04 07:30:46 +02:00
commit adea577dec
8 changed files with 601 additions and 0 deletions

15
pvjc19cv9/Makefile Normal file
View File

@ -0,0 +1,15 @@
CFLAGS=-std=c99 -Wall -g
CC=gcc
all: game
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm *.o
rm game
game: main.o game.o world.o snake.o
$(CC) main.o game.o world.o snake.o -ltermbox -lm -o game

17
pvjc19cv9/README.md Normal file
View File

@ -0,0 +1,17 @@
# Snake Master
Implement Snake game.
Make the game to pass the automatic tests and make the game to be nice.
## Files
Please do not change file names.
- `snake.h`: you implementation should follow this header.
- `snake.c`: implemement the game according to the documentation in header file in this file to pass automatic tests.
- `Makefile`: rules to build the game.
- `game.c`: modify this file to change the appereance of the game and the initial state.
- `main.c`: trivial main function that runs the game
- `world.c`: world game loop and ASCII graphics library (do not change).
- `world.h`: world library interface (do not change).

86
pvjc19cv9/game.c Normal file
View File

@ -0,0 +1,86 @@
#include <termbox.h>
#include <stdlib.h>
#include <string.h>
#include "world.h"
#include "snake.h"
// Start is called one in the beginning
void start(struct world* world,int argc, char** argv){
// Allocate memory for the state
struct state* st = calloc(1,(sizeof(struct state)));
// Store pointer to the state to the world variable
world->state = st;
st->snake = NULL;
st->sx = 0;
st->sy = -1;
int cy = world->height/2;
int cx = world->width/2;
st->snake = add_snake(st->snake,cx + 5 ,cy);
st->snake = add_snake(st->snake,cx + 4,cy);
st->snake = add_snake(st->snake,cx + 3,cy);
st->snake = add_snake(st->snake,cx + 2,cy);
st->snake = add_snake(st->snake,cx + 1,cy);
st->snake = add_snake(st->snake,cx,cy);
int h = world->height;
int w = world->width;
st->foodx[0] = rand() % w;
st->foody[0] = rand() % h;
st->foodx[1] = rand() % w;
st->foody[1] = rand() % h;
st->foodx[2] = rand() % w;
st->foody[2] = rand() % h;
st->foodx[3] = rand() % w;
st->foody[3] = rand() % h;
st->foodx[4] = rand() % w;
st->foody[4] = rand() % h;
}
// Step is called in a loop once in interval.
// It should modify the state and draw it.
int step(struct world* w,int key){
// Get state pointer
struct state* st = w->state;
enum direction dir = DIR_NONE;
if (key == TB_KEY_ARROW_LEFT){
dir = DIR_LEFT;
}
else if (key == TB_KEY_ARROW_RIGHT){
dir = DIR_RIGHT;
}
else if (key == TB_KEY_ARROW_UP){
dir = DIR_UP;
}
else if (key == TB_KEY_ARROW_DOWN){
dir = DIR_DOWN;
}
int r = step_state(st,dir,w->width,w->height);
struct snake* sn = st->snake;
while (sn != NULL){
set_character(w,sn->x,sn->y,'x');
sn = sn->next;
}
for (int i = 0 ; i < FOOD_COUNT; i++){
if (st->foodx[i] >= 0 && st->foody[i] >= 0){
set_character(w,st->foodx[i],st->foody[i],'*');
}
}
if (r){
set_message(w,20,20,"Koniec");
}
if (key == TB_KEY_ESC){
// Non zero means finish the loop and stop the game.
return 1;
}
return 0;
}
// Stop is called after game loop is finished
void stop(struct world* world){
// Free memory for game state
struct state* st = world->state;
free_snake(st->snake);
free(st);
}

5
pvjc19cv9/main.c Normal file
View File

@ -0,0 +1,5 @@
#include "world.h"
int main(int argc, char** argv){
game(argc,argv);
};

151
pvjc19cv9/snake.c Normal file
View File

@ -0,0 +1,151 @@
#include "snake.h"
#include <stdlib.h>
struct snake* add_snake(struct snake* snake,int x,int y){
struct snake* new=calloc(sizeof(struct snake),1);
new->x=x;
new->y=y;
new->next=snake;
return new;
}
struct snake* remove_snake(struct snake* snake){
if(snake==NULL)return NULL;
if(snake->next==NULL){
free(snake);
return NULL;
}
struct snake* temp=snake;
while(temp->next->next!=NULL){
temp=snake->next;
}
free(temp->next);
temp->next=NULL;
return snake;
}
void free_snake(struct snake* sn){
if(sn==NULL)return;
if(sn->next!=NULL)free_snake(sn->next);
free(sn);
sn=NULL;
}
int step_state(struct state* st,enum direction dir,int width,int height){
if(st==NULL)return 0;
switch(dir){
case DIR_DOWN:st->sx=0;st->sy=1;break;
case DIR_UP:st->sx=0;st->sy=-1;break;
case DIR_LEFT:st->sx=-1;st->sy=0;break;
case DIR_RIGHT:st->sx=1;st->sy=0;break;
default:break;
}
int newx=st->snake->x+st->sx;
if(newx>width)return END_WALL;
if(newx<0)return END_WALL;
int newy=st->snake->y+st->sy;
if(newy>height)return END_WALL;
if(newy<0)return END_WALL;
struct snake* pos=st->snake;
while(pos!=NULL){
if(pos->x==newx && pos->y==newy){
return END_SNAKE;
}
pos=pos->next;
}
for(int i=0;i<FOOD_COUNT;i++){
if(st->foodx[i]==newx && st->foody[i]==newy){
st->foodx[i]=-1;
st->foody[i]=-1;
add_snake(st->snake,newx,newy);
return END_CONTINUE;
}
}
for(int i=0;i<=FOOD_COUNT;i++){
if(i==FOOD_COUNT)return END_FOOD;
if(st->foodx>=0)break;
}
st->snake=add_snake(st->snake,newx,newy);
remove_snake(st->snake);
return END_CONTINUE;
}

128
pvjc19cv9/snake.h Normal file
View File

@ -0,0 +1,128 @@
#ifndef snake_h_INCLUDED
#define snake_h_INCLUDED
// Number of food items on the plane
#define FOOD_COUNT 5
/**
* One part of the snake;
*
* The snake is a linked list;
*/
struct snake {
// x position of the snake part
int x;
// y position of the snake part
int y;
// Pointer to the next snake part.
// The last part of the snake has NULL pointer to the next part.
struct snake* next;
};
/**
* Direction of the snake
*/
enum direction {
// No direction command was given
DIR_NONE,
// Up arrow, direction to north
DIR_UP,
// Down arrow, direction to south
DIR_DOWN,
// Left arow, direction to west
DIR_LEFT,
// Right arrow, direction east.
DIR_RIGHT
};
enum endgame {
// Continue the game
END_CONTINUE = 0,
// Snake hit a wall
END_WALL,
// Snake hit itself
END_SNAKE,
// No food left
END_FOOD,
// Other reason to end
END_USER
};
/**
* State of the game.
*
* The state consists of the snake, its speed and food on the plane.
*
* The snake is a linked list of snake parts.
*
* Speed vector is a vector added to the last head position to create a new head.
*
* Food are points on the plane. Food with negative coordinates meads food is already eaten.
*/
struct state {
// Snake as a linked list
struct snake* snake;
// X of the speed vector
int sx;
// Y of the speed vector
int sy;
// X of the food positions
int foodx[FOOD_COUNT];
// Y of the food positions
int foody[FOOD_COUNT];
};
/**
* Add a new snake part with given position. The new snake part becomes the new head.
* @param head of the snake.
* @param x coordinate of the new head;
* @param y coordinate of the new head.
* @return new head of the snake.
*/
struct snake* add_snake(struct snake* snake,int x,int y);
/**
* Remove the last snake part.
* The last snake part should always have NULL next pointer.
* @param head of the snake.
* @return new head of the snake.
*/
struct snake* remove_snake(struct snake* snake);
/**
* Remove each snake part;
* @param head of the snake.
*/
void free_snake(struct snake* sn);
/**
* Change game state.
*
* The function should first set the speed vector according to the direction.
* If direction is:
* - DIR_NONE, speed vector does not change
* - DIR_UP, speed vector is 0,-1
* - DIR_DOWN, speed vector is 0,1
* - DIR_LEFT, speed vector is -1, 0
* - DIR_RIGHT, speed vector is 1, 0
*
* Then it calculates the new position of the head according to the old position
* of the snake head and the speed vector.
*
* - If the new position is on the snake, end the game, return END_SNAKE.
* - If the new position is on the plane, add new snake part on the new position and remove the last part of the snake, return END_CONTINUE.
* - If the new position is on the food, mark food as eaten and add new snake part on the position of the food. Return END_CONTINUE.
* - If there is no food left, end the game, return END_FOOD.
*
* @param old state of the game.
* @param direction command, one of "enum direction".
* @param width of the plane.
* @param height of the plane.
* @return reason to end the game according to enum endgame.
*/
int step_state(struct state* state,enum direction dir,int width,int height);
#endif // snake_h_INCLUDED

117
pvjc19cv9/world.c Normal file
View File

@ -0,0 +1,117 @@
#include "world.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termbox.h>
#include <time.h>
void destroy_world(struct world* world){
stop(world);
tb_shutdown();
}
void end_message(struct world* world, const char* message){
destroy_world(world);
puts(message);
exit(0);
}
void assert_message(int event, struct world* world, const char* message){
if (event == 0){
end_message(world,message);
}
}
void init_world(struct world* w) {
assert_message(w != NULL,w,"init_world:: world is NULL");
w->height = tb_height();
w->width = tb_width();
if (w->interval <= 0){
w->interval = 100;
}
}
void set_color_character(struct world* w,int x,int y,int character,uint16_t foreground,uint16_t background) {
assert_message(w != NULL,w,"set_character:: world is NULL");
if (x < 0 || x >= w->width){
char msg[100];
sprintf(msg,"set_character:: width %d is out of bounds (0,%d)",x,w->width);
end_message(w,msg);
}
if (y < 0 || y >= w->height){
char msg[100];
sprintf(msg,"set_character:: height %d is out of bounds (0,%d)",y,w->height);
end_message(w,msg);
}
tb_change_cell(x,y,character,foreground,background);
}
void set_character(struct world* w,int x,int y,int character) {
set_color_character(w,x,y,character,TB_WHITE,TB_BLACK);
}
void set_message(struct world* w,int x,int y,const char* message) {
int l = strlen(message);
for (int i = 0; i < l; i++){
set_character(w,x+i,y,message[i]);
}
}
void set_color_message(struct world* w,int x,int y,const char* message,int character,uint16_t foreground,uint16_t background) {
int l = strlen(message);
for (int i = 0; i < l; i++){
set_color_character(w,x+i,y,message[i],foreground,background);
}
}
int step_world(struct world* world,int eventkey){
init_world(world);
tb_clear();
int r = step(world,eventkey);
tb_present();
return r;
}
void game(int argc, char** argv){
srand(time(NULL));
int r = tb_init();
if (r < 0){
puts("Termbox Error.");
return;
}
struct tb_event event;
struct world world;
memset(&world,0,sizeof(struct world));
init_world(&world);
start(&world,argc,argv);
r = step_world(&world,WORLD_START_EVENT);
while (!r) {
int t = tb_peek_event(&event,world.interval);
if (t == -1){
end_message(&world,"termox poll error");
}
int eventkey = WORLD_TIMEOUT_EVENT;
if (event.type == TB_EVENT_KEY){
eventkey = event.key;
if (event.ch > 0){
eventkey = event.ch;
}
}
else if (event.type == TB_EVENT_RESIZE){
eventkey = WORLD_RESIZE_EVENT;
}
else if (event.type == TB_EVENT_MOUSE){
// Ignore mouse events
continue;
}
r = step_world(&world,eventkey);
if (eventkey == TB_KEY_CTRL_C){
r = 1;
}
else if (eventkey == TB_KEY_CTRL_D){
r = 1;
}
}
destroy_world(&world);
};

82
pvjc19cv9/world.h Normal file
View File

@ -0,0 +1,82 @@
#ifndef _GAME_H_
#define _GAME_H_
// Signalizes game next loop
// (abuses ctrl tilda keypress code)
#define WORLD_TIMEOUT_EVENT 0
// Signlizes the first step of the game loop
// (abuses ctrl a keypress)
#define WORLD_START_EVENT 1
// Signalizes resize of the screen (width and height have changed)
// (abuses ctrl b keypress)
#define WORLD_RESIZE_EVENT 2
#include <termbox.h>
/**
* Game world represented as a rectangular matrix of colorful characters.
*
* Point [0,0] is displayed the upper left corner of the screen.
*
*/
struct world {
/**
* Width of the screen.
*/
int width;
/**
* Height of the screen.
*/
int height;
/**
* State of the game managed by the programmer.
*/
void* state;
/**
* Interval to wait for next step.
*/
int interval;
};
/**
* Sets cell to a state.
* @param world
* @param x coordinate of cell
* @param y coordinate of cell
* @param new state of the cell
*/
void set_character(struct world* w,int x,int y,int value);
void set_message(struct world* w,int x,int y,const char* message);
/**
* Runs the game loop.
* @param number of commandline arguments
* @param commandline arguments
*/
void game(int argc,char** argv);
/**
* Initializes user state.
* @param world
* @param number of commandline arguments
* @param commandline arguments
*
*/
void start(struct world*,int argc,char** argv);
/**
* Free user state.
* @param world
*/
void stop(struct world* world);
/**
* Changes state of the world according to pressed keys or events.
* @param world
* @param code of the event. Event is WORLD_START_EVENT for the first step, WORLD_TIMEOUT_EVENT for regular step or Termbox key code (TB_KEY_*) for key press event. Mouse events are ignored.
*/
int step(struct world* world,int key);
#endif