This commit is contained in:
Patrik Seman 2023-05-05 17:42:51 +02:00
parent 419d47c345
commit 778b627b48
12 changed files with 660 additions and 0 deletions

29
a3/LICENSE Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2019, Daniel Hládek
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* 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.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
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.

14
a3/Makefile Normal file
View File

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

78
a3/README.md Normal file
View File

@ -0,0 +1,78 @@
# World Game Library
Learn asycnronous C programming by a game.
The library implements a game loop for a character-based ncurses game;
The library finds out the event types:
- start and end
- mouse events
- keyboard events
- screen resize events
It can print colors and arbitrary characters on the screen.
Running is interrupted when character is drawn out of the screen.
## Installation and Running
Make sure, that `ncurses` is installed.
Clone this repository.
Compile:
```c
make
```
Run:
```c
./game
```
## Make your own game
The game happens in a rectangular world of colorful characters.
Your goal as a programmer is to modify the world according to the pressed keys and the internal game state.
The library implements the game loop and draws the world to screen.
Your game in file `game.c` consists of two functions:
- `void* init_game()` is called in the beginning. Here you can initialize the internal state of the game.
The function should return a pointer to an instance of the initial game state `game`.
- `int game_event(struct event* event,void* game)`
is called by the game loop in periodic interval or when a key was pressed. Non-zero return value or `Ctrl+C` key interrupts the game loop.
The world variable represents state of two-dimensional grid of characters on the screen. The screen matrix looks like this:
```
origin
[0,0] width
+--------------------+
h | |
e | |
i | |
g | |
h | |
t | |
+--------------------+
```
The world has the following parameters:
- `int height`: y-dimension of the grid.
- `int width`: x-dimension of the grid.
- `int interval`: maximum time between steps in milliseconds.
### The Event Function
The `int game_event(struct event* event,void* game)`
function should:
1. Read the game state (from the `void* game`) pointer.
1. Examine the pressed key from event pointer. If the `key` variable is non-zero, a key was pressed. According to the pressed key, modify the game state `game`.
1. Draw the game state. In the beginning of the step function the screen is empty.
1. Returning non-zero value ends the game loop.

BIN
a3/game Executable file

Binary file not shown.

187
a3/game.c Normal file
View File

@ -0,0 +1,187 @@
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "world.h"
#include "game.h"
// Start is called one in the beginning
void* init_game(){
// Allocate memory for the state
struct game* st = calloc(1,(sizeof(struct game)));
// Initialize state
int vygenerovaneCislo = 0;
srand(time(NULL));
for (int i = 0; i < POCET_MYSI; i++) {
if(COLS > LINES){
vygenerovaneCislo = (rand() % (LINES - 4)) + 2;
}
else{
vygenerovaneCislo = rand() % (COLS - 4) + 2;
}
st->mousex[i] = vygenerovaneCislo;
st->mousey[i] = vygenerovaneCislo;
}
st->catx = 0;
st->caty = 0;
st->catx_position = 15;
st->caty_position = 15;
// Store pointer to the state to the world variable
return st;
}
// Step is called in a loop once in interval.
// It should modify the state and draw it.
int game_event(struct event* event,void* game){
// Get state pointer
struct game* state = game;
char msg[200];
sprintf(msg,"%d",event->type);
set_message(msg,10,0);
if ( event->type == EVENT_ESC){
// Non zero means finish the loop and stop the game.
return 1;
}
// Read game variable and update the eventstate
// Is mouse caught ?
int chytenemysi = 0;
for (int i = 0; i < POCET_MYSI; i++) {
if (state->caty_position == state->mousey[i] && state->catx_position == state->mousex[i]) {
state->mousey[i] = -1;
state->mousex[i]= -1;
}
}
for (int i = 0; i < POCET_MYSI; i++) {
if (state->mousex[i] == -1) chytenemysi++;
}
if (chytenemysi == POCET_MYSI) {
clear_screen();
set_message("Macka zjedla mysi.", (COLS / 2) - (strlen("Macka zjadla mysi.") / 2), LINES / 2);
return 0;
}
if (event->type == EVENT_TIMEOUT) {
// Move cat
//state->catx_position += state->catx;
//state->caty_position += state->caty;
int cx = state->catx_position + state->catx;
int cy = state->caty_position + state->caty;
if(cx < 2){
state->catx_position += abs(cx) + 10;
}
else if(cy < 2){
state->caty_position += abs(cy) + 10;
}
else if(cx == COLS - 1){
state->catx_position -= (cx - COLS + 10);
}
else if(cy == LINES - 1){
state->caty_position -= (cy - LINES + 10);
}
else {
state->catx_position = cx;
state->caty_position = cy;
}
// random mouse movement
int m;
for (int i = 0; i < POCET_MYSI; i++) {
if (state->mousey[i] == -1) continue;
m = rand() % 15;
if(m==0){
if(state->mousey[i] == 2){
state->mousey[i] = 2;
}
else {
state->mousey[i] -= 1;
}
}
else if(m==1){
if(state->mousey[i] == LINES - 1){
state->mousey[i] = LINES - 10;
}
else{
state->mousey[i] += 1;
}
}
else if(m==2){
if(state->mousex[i] == 2){
state->mousex[i] = 2;
}
else{
state->mousex[i] -= 1;
}
}
else if(m==3){
if(state->mousex[i] == COLS - 1){
state->mousex[i] = COLS - 10;
}
else{
state->mousex[i] += 1;
}
}
}
// Je myska mimo plochy
}
else if (event->type == EVENT_KEY){
// Move cat according to keyboard
if ( event->key == KEY_UP){
state->catx = 0;
state->caty = -1;
}
else if ( event->key == KEY_DOWN){
state->catx = 0;
state->caty = 1;
}
else if ( event->key == KEY_LEFT){
state->catx = -1;
state->caty = 0;
}
else if ( event->key == KEY_RIGHT){
state->catx = 1;
state->caty = 0;
}
}
// Draw world state
//
// Draw cat
char countmessage[30];
sprintf(countmessage,"Pocet chytenych mysi: %d", chytenemysi);
clear_screen();
set_message(countmessage, COLS-25, 1);
set_color_cell('c',state->catx_position,state->caty_position,COLOR_YELLOW,COLOR_RED);
// Draw mouses
for (int i = 0; i < POCET_MYSI; i++) {
if(state->mousex[i] == -1){
continue;
}
set_cell('m', state->mousex[i], state->mousey[i]);
}
//vykresli stenu dookola
for (int i = 0; i < COLS; i++){
set_color_cell('#', i, 0, 0, COLOR_YELLOW);
set_color_cell('#', i, LINES - 1, 0, COLOR_YELLOW);
}
for (int i = 0; i < LINES; i++){
set_color_cell('#', 0, i, 0, COLOR_YELLOW);
set_color_cell('#', COLS - 1, i, 0, COLOR_YELLOW);
}
set_message( state->message,1,0);
return 0;
}

32
a3/game.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef _GAME_H_INCLUDE_
#define _GAME_H_INCLUDE_
#include "world.h"
#define POCET_MYSI 5
// Set of variables that expresses state of the game.
//
struct game {
// X speed of the cat
int catx;
// Y speed of the cat
int caty;
// X position of the cat
int catx_position;
// Y opsition of the cat
int caty_position;;
// X position of the mouses
int mousex[POCET_MYSI];
// Y position of the mouses
int mousey[POCET_MYSI];
// Funky message
char message[100];
};
// Returns pointer to newly allocated state
void* init_game();
// Changes world according to the game state (pressed key, screen size or other event)
int game_event(struct event* event,void* game);
#endif

BIN
a3/game.o Normal file

Binary file not shown.

9
a3/main.c Normal file
View File

@ -0,0 +1,9 @@
#include "game.h"
#include "world.h"
#include <stdlib.h>
int main(int argc, char** argv){
start_world(init_game,game_event,free);
return 0;
}

BIN
a3/main.o Normal file

Binary file not shown.

198
a3/world.c Normal file
View File

@ -0,0 +1,198 @@
#include "world.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int TIMEOUT;
void abort_game(const char* message){
endwin();
puts(message);
exit(1);
}
void check_bounds(const char* source,int x, int y){
char msg[200];
if (x < 0 || x >= COLS){
sprintf(msg,"%s:: width %d is out of bounds (0,%d)",source,x,COLS);
abort_game(msg);
}
if (y < 0 || y >= LINES){
sprintf(msg,"%s:: height %d is out of bounds (0,%d)",source,y,LINES);
abort_game(msg);
}
}
void clear_screen(){
// Clear screen
mvaddch(0,0,' ');
int screenchars = LINES*COLS;
for (int j = 1; j < screenchars;j++ ){
addch(' ');
}
}
void game_speed(int value){
if (value < 0){
abort_game("world_seed:: cannot be negative\n");
}
TIMEOUT =value;
}
void set_message(const char* message,int x,int y) {
int l = strlen(message);
for (int i = 0; i < l; i++){
check_bounds("set_message",x+i,y);
set_cell(message[i],x+i,y);
}
}
void assert_message(int event,const char* message){
if (event == 0){
abort_game(message);
}
}
void set_cell(int character,int x,int y) {
check_bounds("set_cell",x,y);
set_color_cell(character,x,y,COLOR_WHITE,COLOR_BLACK);
}
void set_color_cell(int character,int x,int y,short front_color,short back_color){
check_bounds("set_color_cell",x,y);
if (has_colors()){
int pair = COLOR_COUNT * front_color + back_color;
attron(COLOR_PAIR(pair));
mvaddch(y,x,character);
attroff(COLOR_PAIR(pair));
}
else{
mvaddch(y,x,character);
}
}
int start_world(void* (*init_game)(),int (*world_event)(struct event* event,void* game),void (*destroy_game)(void*)){
srand(time(NULL));
int r = 1;
// Speed global variable
TIMEOUT = 175;
if (initscr() == NULL){
// TODO Which Error?
puts("Curses Error.");
return -1;
}
noecho(); // Nevypisuj vstup na obrazovku
cbreak(); // Zabudni starý vstup
nodelay(stdscr,TRUE); // Nečakaj na stlačenie
keypad(stdscr,TRUE); // Aktivuje šípky
curs_set(FALSE); // Neviditeľný kurzor
/* Get all the mouse events */
mousemask(ALL_MOUSE_EVENTS, NULL);
MEVENT mouse_event;
if (has_colors()){ // Zistenie či terminál podporuje farby
start_color();
for (int i = 0; i < COLOR_COUNT;i++){
for (int j = 0; j < COLOR_COUNT;j++){
init_pair(i * COLOR_COUNT + j, i,j);
}
}
}
else {
puts("No colors!\n");
}
void* game = NULL;
if (init_game != NULL){
game = init_game();
assert_message(game != NULL,"init_game:: should return non null pointer");
}
timeout(TIMEOUT);
// Initial step
struct event event;
memset(&event,0,sizeof(struct event));
event.height = LINES;
event.width = COLS;
event.type = EVENT_START;
clock_t start_time = clock();
clock_t last_timeout = start_time;
clock_t next_timeout = last_timeout + TIMEOUT;
event.time_ms = start_time;
// Start event
r = world_event(&event,game);
refresh();
while (!r) {
memset(&event,0,sizeof(struct event));
event.height = LINES;
event.width = COLS;
event.key = getch();
// No key was pressed
if (event.key == ERR){
event.type = EVENT_TIMEOUT;
last_timeout = clock();
next_timeout = last_timeout + TIMEOUT;
}
// Mouse event
else if (event.key == KEY_MOUSE ){
event.type = EVENT_MOUSE;
if(getmouse(&mouse_event) == OK){
event.mouse_x = mouse_event.x;
event.mouse_y = mouse_event.y;
if(mouse_event.bstate & BUTTON1_PRESSED){
event.mouse_left = 1;
}
if(mouse_event.bstate & BUTTON2_PRESSED){
event.mouse_middle = 1;
}
if(mouse_event.bstate & BUTTON3_PRESSED){
event.mouse_right = 1;
}
}
}
else if (event.key == KEY_RESIZE) {
event.type = EVENT_RESIZE;
}
else{
event.type = EVENT_KEY;
if (event.key == 27){
int k = getch();
if (k == -1){
// Esc Was pressed
event.type = EVENT_ESC;
}
else {
// Alt was pressed
event.key = k;
event.alt_key = 1;
}
}
}
// Draw new world
event.time_ms = clock();
r = world_event(&event,game);
refresh();
event.time_ms = clock();
// set new timeout
int nt = next_timeout - event.time_ms;
//printf("%d\n",nt);
if (nt > 0){
timeout(nt);
}
else {
timeout(TIMEOUT);
next_timeout = event.time_ms + TIMEOUT;
}
}
memset(&event,0,sizeof(struct event));
event.height = LINES;
event.width = COLS;
event.type = EVENT_END;
event.time_ms = clock();
world_event(&event,game);
if (destroy_game != NULL){
destroy_game(game);
}
endwin();
return r;
};

113
a3/world.h Normal file
View File

@ -0,0 +1,113 @@
#ifndef _WORLD_H_
#define _WORLD_H_
#include <curses.h>
/**
* World represented as a rectangular matrix of colorful characters.
*
* Point [0,0] is displayed the upper left corner of the screen.
*
*/
enum event_type {
EVENT_START,
EVENT_TIMEOUT,
EVENT_KEY,
EVENT_MOUSE,
EVENT_RESIZE,
EVENT_ESC,
EVENT_END,
};
struct event {
/**
* Last width of the screen.
*/
int width;
/**
* Last height of the screen.
*/
int height;
/**
* Last pressed key or Curses event.
*
* Special event values:
* ERR if timeout,
* KEY_RESIZE if screen resize
* KEY_EVENT, other event,
* KEY_MOUSE, mouse clicked
*
* Key values:
*
* ' ' Space
* KEY_DOWN Arrow down
* KEY_UP Arrow up
* KEY_LEFT Arrow left
* KEY_RIGHT Arrow right
* KEY_A1 Upper left of keypad
* KEY_A3 Upper right of keypad
* KEY_B2 Center of keypad
* KEY_C1 Lower left of keypad
* KEY_C3 Lower right of keypad
*
* KEY_ENTER
* KEY_BACKSPACE
*/
int key;
int alt_key;
enum event_type type;
int mouse_x;
int mouse_y;
int mouse_left;
int mouse_right;
int mouse_middle;
long int time_ms;
};
/**
* Sets cell to a state.
* @param event
* @param x coordinate of cell
* @param y coordinate of cell
* @param new state of the cell
*/
void set_cell(int character,int x,int y);
/**
* COLOR_BLACK 0
* COLOR_RED 1
* COLOR_GREEN 2
* COLOR_YELLOW 3
* COLOR_BLUE 4
* COLOR_MAGENTA 5
* COLOR_CYAN 6
* COLOR_WHITE 7
*/
#define COLOR_COUNT 8
void set_color_cell(int character,int x,int y,short front_color,short back_color);
/**
*
* @param event
* @param number of commandline arguments
* @param init_world
* @param destroy_world
*
* void init_world(struct event* w);
* Initializes user state.
* Free user state.
* @param event
*/
int start_world(void* (*init_game)(),int (*world_event)(struct event* event,void* game),void (*destroy_game)(void* game));
void game_speed(int value);
void set_message(const char* message,int x,int y);
void clear_screen();
#endif

BIN
a3/world.o Normal file

Binary file not shown.