diff --git a/du8/Makefile b/du8/Makefile index 4633fdf..2e32c1f 100644 --- a/du8/Makefile +++ b/du8/Makefile @@ -1,10 +1,10 @@ -CC = gcc -CFLAGS = -Wall -Wextra -g +CC=gcc +CFLAGS=-Wall -std=c99 -all: piskorky +all: game -piskorky: main.o game.o world.o - $(CC) $(CFLAGS) -o piskorky main.o game.o world.o -lncurses +game: main.o game.o world.o + $(CC) $(CFLAGS) -o game main.o game.o world.o -lncurses main.o: main.c game.h world.h $(CC) $(CFLAGS) -c main.c @@ -16,4 +16,5 @@ world.o: world.c world.h $(CC) $(CFLAGS) -c world.c clean: - rm -f *.o piskorky \ No newline at end of file + rm -f *.o game + diff --git a/du8/README.md b/du8/README.md index 13411c1..b57958c 100644 --- a/du8/README.md +++ b/du8/README.md @@ -1,23 +1,78 @@ -# Piškvorky (Tic-Tac-Toe) – Terminalová hra s knižnicou `world` +# World Game Library -## 🕹️ Ako hrať -- Ovládanie: `WASD` na pohyb, `medzerník` na položenie znaku. -- Hráči sa striedajú ako X a O. -- Hra končí výhrou jedného hráča alebo remízou. +Learn asycnronous C programming by a game. -## 🛠️ Preklad a spustenie -```bash +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 -./piskorky +``` + +Run: + +```c +./game ``` -## 🧠 Kód – Štruktúry a funkcie -- `GameState`: štruktúra uchovávajúca stav hry (pole, hráč, kurzor, výherca). -- `game_init`: inicializuje hru. -- `draw`: vykresľuje mriežku a obsah. -- `key_handler`: reaguje na stlačené klávesy. -- `update`: nepoužíva sa (hra nie je časovo riadená). +## 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. -## 📚 Zdroje -- Príklady z predmetu ZAP. -- Dokumentácia k `ncurses` a `world.h`. \ No newline at end of file diff --git a/du8/game.c b/du8/game.c index fc49341..53ce275 100644 --- a/du8/game.c +++ b/du8/game.c @@ -1,95 +1,65 @@ #include "game.h" #include "world.h" +#include -void game_init(GameState *game) { - for (int y = 0; y < SIZE; ++y) - for (int x = 0; x < SIZE; ++x) - game->board[y][x] = EMPTY; - game->current_player = X; - game->cursor_x = 0; - game->cursor_y = 0; - game->game_over = 0; - game->winner = EMPTY; +void init_game(GameState *state) { + memset(state->board, ' ', sizeof(state->board)); + state->cursor_x = 0; + state->cursor_y = 0; + state->current_player = 0; + state->game_over = 0; } -void draw(void *state) { - GameState *game = (GameState *)state; +void draw_game(const GameState *state) { world_clear(); - for (int y = 0; y < SIZE; ++y) { - for (int x = 0; x < SIZE; ++x) { - world_set_color((x == game->cursor_x && y == game->cursor_y) ? 2 : 1); - char *symbol = "[ ]"; - if (game->board[y][x] == X) symbol = "[X]"; - else if (game->board[y][x] == O) symbol = "[O]"; - world_draw_string(x * 4, y * 2, symbol); + for (int y = 0; y < BOARD_SIZE; y++) { + for (int x = 0; x < BOARD_SIZE; x++) { + int wx = 5 + x * 4; + int wy = 3 + y * 2; + if (x == state->cursor_x && y == state->cursor_y) + world_draw_char(wx, wy, state->board[y][x], COLOR_RED); + else + world_draw_char(wx, wy, state->board[y][x], COLOR_WHITE); } } - if (game->game_over) { - world_set_color(3); - world_draw_string(0, SIZE * 2 + 1, game->winner == EMPTY ? "Remiza!" : - (game->winner == X ? "Vyhral X!" : "Vyhral O!")); - } + world_draw_text(0, 0, "Hráč: %c", state->current_player ? 'O' : 'X'); + if (state->game_over) + world_draw_text(0, 1, "Koniec hry! Stlač Q na ukončenie."); + world_display(); } -void check_winner(GameState *game) { - for (int i = 0; i < SIZE; ++i) { - if (game->board[i][0] != EMPTY && - game->board[i][0] == game->board[i][1] && - game->board[i][1] == game->board[i][2]) { - game->winner = game->board[i][0]; - game->game_over = 1; - return; - } - if (game->board[0][i] != EMPTY && - game->board[0][i] == game->board[1][i] && - game->board[1][i] == game->board[2][i]) { - game->winner = game->board[0][i]; - game->game_over = 1; - return; +int check_winner(const GameState *state) { + char p[] = {'X', 'O'}; + for (int i = 0; i < 2; i++) { + char c = p[i]; + for (int j = 0; j < BOARD_SIZE; j++) { + if (state->board[j][0] == c && state->board[j][1] == c && state->board[j][2] == c) return 1; + if (state->board[0][j] == c && state->board[1][j] == c && state->board[2][j] == c) return 1; } + if (state->board[0][0] == c && state->board[1][1] == c && state->board[2][2] == c) return 1; + if (state->board[0][2] == c && state->board[1][1] == c && state->board[2][0] == c) return 1; } - if (game->board[0][0] != EMPTY && - game->board[0][0] == game->board[1][1] && - game->board[1][1] == game->board[2][2]) { - game->winner = game->board[0][0]; - game->game_over = 1; - return; - } - if (game->board[0][2] != EMPTY && - game->board[0][2] == game->board[1][1] && - game->board[1][1] == game->board[2][0]) { - game->winner = game->board[0][2]; - game->game_over = 1; - return; - } - int full = 1; - for (int y = 0; y < SIZE; ++y) - for (int x = 0; x < SIZE; ++x) - if (game->board[y][x] == EMPTY) - full = 0; - if (full) { - game->game_over = 1; - game->winner = EMPTY; - } + return 0; } -void key_handler(int key, void *state) { - GameState *game = (GameState *)state; - if (game->game_over) return; +void handle_key(GameState *state, int key) { + if (state->game_over) return; + switch (key) { - case 'w': if (game->cursor_y > 0) game->cursor_y--; break; - case 's': if (game->cursor_y < SIZE - 1) game->cursor_y++; break; - case 'a': if (game->cursor_x > 0) game->cursor_x--; break; - case 'd': if (game->cursor_x < SIZE - 1) game->cursor_x++; break; - case ' ': { - if (game->board[game->cursor_y][game->cursor_x] == EMPTY) { - game->board[game->cursor_y][game->cursor_x] = game->current_player; - check_winner(game); - if (!game->game_over) - game->current_player = (game->current_player == X) ? O : X; + case KEY_UP: if (state->cursor_y > 0) state->cursor_y--; break; + case KEY_DOWN: if (state->cursor_y < BOARD_SIZE - 1) state->cursor_y++; break; + case KEY_LEFT: if (state->cursor_x > 0) state->cursor_x--; break; + case KEY_RIGHT: if (state->cursor_x < BOARD_SIZE - 1) state->cursor_x++; break; + case '\n': + case ' ': + if (state->board[state->cursor_y][state->cursor_x] == ' ') { + state->board[state->cursor_y][state->cursor_x] = state->current_player ? 'O' : 'X'; + if (check_winner(state)) { + state->game_over = 1; + } else { + state->current_player = !state->current_player; + } } - } break; + break; } -} - -void update(void *state) { } \ No newline at end of file +} \ No newline at end of file diff --git a/du8/game.h b/du8/game.h index 4182c5a..1b7811a 100644 --- a/du8/game.h +++ b/du8/game.h @@ -1,20 +1,28 @@ #ifndef GAME_H #define GAME_H -#define SIZE 3 +#define BOARD_SIZE 3 -typedef enum { EMPTY, X, O } Cell; +// Štruktúra pre stav hry typedef struct { - Cell board[SIZE][SIZE]; - int current_player; - int cursor_x, cursor_y; - int game_over; - Cell winner; + char board[BOARD_SIZE][BOARD_SIZE]; // Hracia plocha 3x3 + int cursor_x; // Pozícia kurzora (X) + int cursor_y; // Pozícia kurzora (Y) + int current_player; // 0 = X, 1 = O + int game_over; // 0 = pokračuje, 1 = koniec } GameState; -void game_init(GameState *game); -void draw(void *state); -void update(void *state); -void key_handler(int key, void *state); +// Inicializácia hry +void init_game(GameState *state); + +// Spracovanie klávesy +void handle_key(GameState *state, int key); + +// Vykreslenie stavu hry +void draw_game(const GameState *state); + +// Kontrola výhercu +int check_winner(const GameState *state); + +#endif // GAME_H -#endif \ No newline at end of file diff --git a/du8/game.o b/du8/game.o deleted file mode 100644 index 0ffb294..0000000 Binary files a/du8/game.o and /dev/null differ diff --git a/du8/main.c b/du8/main.c index d8c2050..f6af1f4 100644 --- a/du8/main.c +++ b/du8/main.c @@ -1,9 +1,18 @@ -#include "world.h" #include "game.h" +#include "world.h" int main() { GameState game; - game_init(&game); - world_init(&game, draw, key_handler, update); + init_game(&game); + world_init(); + + while (1) { + draw_game(&game); + int key = world_get_key(); + if (key == 'q' || key == 'Q') break; + handle_key(&game, key); + } + + world_end(); return 0; -} \ No newline at end of file +} diff --git a/du8/main.o b/du8/main.o deleted file mode 100644 index 31a00c3..0000000 Binary files a/du8/main.o and /dev/null differ diff --git a/du8/world.c b/du8/world.c index a45c109..9974c0d 100644 --- a/du8/world.c +++ b/du8/world.c @@ -1,198 +1,35 @@ +#include #include "world.h" -#include -#include -#include -#include -#include -int TIMEOUT; +void world_init() { + initscr(); + noecho(); + curs_set(FALSE); + keypad(stdscr, TRUE); + timeout(-1); // Blokujúce čakanie na vstup +} -void abort_game(const char* message){ +void world_end() { 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); - } +int world_get_key() { + return getch(); } -void clear_screen(){ - // Clear screen - mvaddch(0,0,' '); - int screenchars = LINES*COLS; - for (int j = 1; j < screenchars;j++ ){ - addch(' '); - } +void world_clear() { + clear(); } -void game_speed(int value){ - if (value < 0){ - abort_game("world_seed:: cannot be negative\n"); - } - TIMEOUT =value; +void world_draw_char(int x, int y, char c) { + mvaddch(y, x, c); } -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 world_draw_text(int x, int y, const char *text) { + mvprintw(y, x, "%s", text); } -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 = 100; - 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); +void world_display() { 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; -}; +} + diff --git a/du8/world.o b/du8/world.o deleted file mode 100644 index 80c25f1..0000000 Binary files a/du8/world.o and /dev/null differ