This commit is contained in:
Denis Landa 2025-06-08 12:52:57 +02:00
parent 84ae02ac8e
commit 7464cedbed
9 changed files with 180 additions and 300 deletions

View File

@ -1,10 +1,10 @@
CC=gcc CC=gcc
CFLAGS = -Wall -Wextra -g CFLAGS=-Wall -std=c99
all: piskorky all: game
piskorky: main.o game.o world.o game: main.o game.o world.o
$(CC) $(CFLAGS) -o piskorky main.o game.o world.o -lncurses $(CC) $(CFLAGS) -o game main.o game.o world.o -lncurses
main.o: main.c game.h world.h main.o: main.c game.h world.h
$(CC) $(CFLAGS) -c main.c $(CC) $(CFLAGS) -c main.c
@ -16,4 +16,5 @@ world.o: world.c world.h
$(CC) $(CFLAGS) -c world.c $(CC) $(CFLAGS) -c world.c
clean: clean:
rm -f *.o piskorky rm -f *.o game

View File

@ -1,23 +1,78 @@
# Piškvorky (Tic-Tac-Toe) Terminalová hra s knižnicou `world` # World Game Library
## 🕹️ Ako hrať Learn asycnronous C programming by a game.
- 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 The library implements a game loop for a character-based ncurses game;
```bash
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 make
./piskorky
``` ```
## 🧠 Kód Štruktúry a funkcie Run:
- `GameState`: štruktúra uchovávajúca stav hry (pole, hráč, kurzor, výherca).
- `game_init`: inicializuje hru. ```c
- `draw`: vykresľuje mriežku a obsah. ./game
- `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`.

View File

@ -1,95 +1,65 @@
#include "game.h" #include "game.h"
#include "world.h" #include "world.h"
#include <string.h>
void game_init(GameState *game) { void init_game(GameState *state) {
for (int y = 0; y < SIZE; ++y) memset(state->board, ' ', sizeof(state->board));
for (int x = 0; x < SIZE; ++x) state->cursor_x = 0;
game->board[y][x] = EMPTY; state->cursor_y = 0;
game->current_player = X; state->current_player = 0;
game->cursor_x = 0; state->game_over = 0;
game->cursor_y = 0;
game->game_over = 0;
game->winner = EMPTY;
} }
void draw(void *state) { void draw_game(const GameState *state) {
GameState *game = (GameState *)state;
world_clear(); world_clear();
for (int y = 0; y < SIZE; ++y) { for (int y = 0; y < BOARD_SIZE; y++) {
for (int x = 0; x < SIZE; ++x) { for (int x = 0; x < BOARD_SIZE; x++) {
world_set_color((x == game->cursor_x && y == game->cursor_y) ? 2 : 1); int wx = 5 + x * 4;
char *symbol = "[ ]"; int wy = 3 + y * 2;
if (game->board[y][x] == X) symbol = "[X]"; if (x == state->cursor_x && y == state->cursor_y)
else if (game->board[y][x] == O) symbol = "[O]"; world_draw_char(wx, wy, state->board[y][x], COLOR_RED);
world_draw_string(x * 4, y * 2, symbol); else
world_draw_char(wx, wy, state->board[y][x], COLOR_WHITE);
} }
} }
if (game->game_over) { world_draw_text(0, 0, "Hráč: %c", state->current_player ? 'O' : 'X');
world_set_color(3); if (state->game_over)
world_draw_string(0, SIZE * 2 + 1, game->winner == EMPTY ? "Remiza!" : world_draw_text(0, 1, "Koniec hry! Stlač Q na ukončenie.");
(game->winner == X ? "Vyhral X!" : "Vyhral O!")); world_display();
}
} }
void check_winner(GameState *game) { int check_winner(const GameState *state) {
for (int i = 0; i < SIZE; ++i) { char p[] = {'X', 'O'};
if (game->board[i][0] != EMPTY && for (int i = 0; i < 2; i++) {
game->board[i][0] == game->board[i][1] && char c = p[i];
game->board[i][1] == game->board[i][2]) { for (int j = 0; j < BOARD_SIZE; j++) {
game->winner = game->board[i][0]; if (state->board[j][0] == c && state->board[j][1] == c && state->board[j][2] == c) return 1;
game->game_over = 1; if (state->board[0][j] == c && state->board[1][j] == c && state->board[2][j] == c) return 1;
return;
} }
if (game->board[0][i] != EMPTY && if (state->board[0][0] == c && state->board[1][1] == c && state->board[2][2] == c) return 1;
game->board[0][i] == game->board[1][i] && if (state->board[0][2] == c && state->board[1][1] == c && state->board[2][0] == c) return 1;
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;
} }
return 0;
} }
void key_handler(int key, void *state) { void handle_key(GameState *state, int key) {
GameState *game = (GameState *)state; if (state->game_over) return;
if (game->game_over) return;
switch (key) { switch (key) {
case 'w': if (game->cursor_y > 0) game->cursor_y--; break; case KEY_UP: if (state->cursor_y > 0) state->cursor_y--; break;
case 's': if (game->cursor_y < SIZE - 1) game->cursor_y++; break; case KEY_DOWN: if (state->cursor_y < BOARD_SIZE - 1) state->cursor_y++; break;
case 'a': if (game->cursor_x > 0) game->cursor_x--; break; case KEY_LEFT: if (state->cursor_x > 0) state->cursor_x--; break;
case 'd': if (game->cursor_x < SIZE - 1) game->cursor_x++; break; case KEY_RIGHT: if (state->cursor_x < BOARD_SIZE - 1) state->cursor_x++; break;
case ' ': { case '\n':
if (game->board[game->cursor_y][game->cursor_x] == EMPTY) { case ' ':
game->board[game->cursor_y][game->cursor_x] = game->current_player; if (state->board[state->cursor_y][state->cursor_x] == ' ') {
check_winner(game); state->board[state->cursor_y][state->cursor_x] = state->current_player ? 'O' : 'X';
if (!game->game_over) if (check_winner(state)) {
game->current_player = (game->current_player == X) ? O : X; state->game_over = 1;
} } else {
} break; state->current_player = !state->current_player;
}
}
break;
} }
} }
void update(void *state) { }

View File

@ -1,20 +1,28 @@
#ifndef GAME_H #ifndef GAME_H
#define 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 { typedef struct {
Cell board[SIZE][SIZE]; char board[BOARD_SIZE][BOARD_SIZE]; // Hracia plocha 3x3
int current_player; int cursor_x; // Pozícia kurzora (X)
int cursor_x, cursor_y; int cursor_y; // Pozícia kurzora (Y)
int game_over; int current_player; // 0 = X, 1 = O
Cell winner; int game_over; // 0 = pokračuje, 1 = koniec
} GameState; } GameState;
void game_init(GameState *game); // Inicializácia hry
void draw(void *state); void init_game(GameState *state);
void update(void *state);
void key_handler(int key, void *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

Binary file not shown.

View File

@ -1,9 +1,18 @@
#include "world.h"
#include "game.h" #include "game.h"
#include "world.h"
int main() { int main() {
GameState game; GameState game;
game_init(&game); init_game(&game);
world_init(&game, draw, key_handler, update); 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; return 0;
} }

Binary file not shown.

View File

@ -1,198 +1,35 @@
#include <ncurses.h>
#include "world.h" #include "world.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
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(); endwin();
puts(message);
exit(1);
} }
void check_bounds(const char* source,int x, int y){ int world_get_key() {
char msg[200]; return getch();
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(){ void world_clear() {
// Clear screen clear();
mvaddch(0,0,' ');
int screenchars = LINES*COLS;
for (int j = 1; j < screenchars;j++ ){
addch(' ');
}
} }
void game_speed(int value){ void world_draw_char(int x, int y, char c) {
if (value < 0){ mvaddch(y, x, c);
abort_game("world_seed:: cannot be negative\n");
}
TIMEOUT =value;
} }
void set_message(const char* message,int x,int y) { void world_draw_text(int x, int y, const char *text) {
int l = strlen(message); mvprintw(y, x, "%s", text);
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){ void world_display() {
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(); 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;
};

Binary file not shown.