diff --git a/du8/Makefile b/du8/Makefile new file mode 100644 index 0000000..4633fdf --- /dev/null +++ b/du8/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g + +all: piskorky + +piskorky: main.o game.o world.o + $(CC) $(CFLAGS) -o piskorky main.o game.o world.o -lncurses + +main.o: main.c game.h world.h + $(CC) $(CFLAGS) -c main.c + +game.o: game.c game.h world.h + $(CC) $(CFLAGS) -c game.c + +world.o: world.c world.h + $(CC) $(CFLAGS) -c world.c + +clean: + rm -f *.o piskorky \ No newline at end of file diff --git a/du8/README.md b/du8/README.md new file mode 100644 index 0000000..13411c1 --- /dev/null +++ b/du8/README.md @@ -0,0 +1,23 @@ +# Piškvorky (Tic-Tac-Toe) – Terminalová hra s knižnicou `world` + +## 🕹️ 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. + +## 🛠️ Preklad a spustenie +```bash +make +./piskorky +``` + +## 🧠 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á). + +## 📚 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 new file mode 100644 index 0000000..fc49341 --- /dev/null +++ b/du8/game.c @@ -0,0 +1,95 @@ +#include "game.h" +#include "world.h" + +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 draw(void *state) { + GameState *game = (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); + } + } + 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!")); + } +} + +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; + } + } + 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; + } +} + +void key_handler(int key, void *state) { + GameState *game = (GameState *)state; + if (game->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; + } + } break; + } +} + +void update(void *state) { } \ No newline at end of file diff --git a/du8/game.h b/du8/game.h new file mode 100644 index 0000000..4182c5a --- /dev/null +++ b/du8/game.h @@ -0,0 +1,20 @@ +#ifndef GAME_H +#define GAME_H + +#define SIZE 3 + +typedef enum { EMPTY, X, O } Cell; +typedef struct { + Cell board[SIZE][SIZE]; + int current_player; + int cursor_x, cursor_y; + int game_over; + Cell winner; +} GameState; + +void game_init(GameState *game); +void draw(void *state); +void update(void *state); +void key_handler(int key, void *state); + +#endif \ No newline at end of file diff --git a/du8/game.o b/du8/game.o new file mode 100644 index 0000000..0ffb294 Binary files /dev/null and b/du8/game.o differ diff --git a/du8/main.c b/du8/main.c new file mode 100644 index 0000000..d8c2050 --- /dev/null +++ b/du8/main.c @@ -0,0 +1,9 @@ +#include "world.h" +#include "game.h" + +int main() { + GameState game; + game_init(&game); + world_init(&game, draw, key_handler, update); + return 0; +} \ No newline at end of file diff --git a/du8/main.o b/du8/main.o new file mode 100644 index 0000000..31a00c3 Binary files /dev/null and b/du8/main.o differ diff --git a/du8/world.c b/du8/world.c new file mode 100644 index 0000000..a45c109 --- /dev/null +++ b/du8/world.c @@ -0,0 +1,198 @@ +#include "world.h" +#include +#include +#include +#include +#include + +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 = 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); + 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.h b/du8/world.h new file mode 100644 index 0000000..73be057 --- /dev/null +++ b/du8/world.h @@ -0,0 +1,113 @@ +#ifndef _WORLD_H_ +#define _WORLD_H_ + +#include + +/** + * 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 diff --git a/du8/world.o b/du8/world.o new file mode 100644 index 0000000..80c25f1 Binary files /dev/null and b/du8/world.o differ