From 591bc55c3dd196a007f0a68f1fa5391bfb417f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B0=D1=88=D0=B0?= Date: Fri, 2 May 2025 19:35:19 +0200 Subject: [PATCH] refresh --- a4/.gitignore | 54 ++++++++++++++++++++ a4/LICENSE | 29 +++++++++++ a4/Makefile | 17 +++++++ a4/README.md | 78 +++++++++++++++++++++++++++++ a4/game.c | 45 +++++++++++++++++ a4/game.h | 18 +++++++ a4/main.c | 19 +++++++ a4/world.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++ a4/world.h | 113 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 508 insertions(+) create mode 100644 a4/.gitignore create mode 100644 a4/LICENSE create mode 100644 a4/Makefile create mode 100644 a4/README.md create mode 100644 a4/game.c create mode 100644 a4/game.h create mode 100644 a4/main.c create mode 100644 a4/world.c create mode 100644 a4/world.h diff --git a/a4/.gitignore b/a4/.gitignore new file mode 100644 index 0000000..ecf4432 --- /dev/null +++ b/a4/.gitignore @@ -0,0 +1,54 @@ +game + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/a4/LICENSE b/a4/LICENSE new file mode 100644 index 0000000..39d1664 --- /dev/null +++ b/a4/LICENSE @@ -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. diff --git a/a4/Makefile b/a4/Makefile new file mode 100644 index 0000000..973aa93 --- /dev/null +++ b/a4/Makefile @@ -0,0 +1,17 @@ +CC=gcc +CFLAGS=-std=c99 -Wall -g + +all: game + +%.o: %.c + @echo "Компиляция $<..." + @$(CC) $(CFLAGS) -c $< -o $@ + +game: main.o game.o world.o + @echo "Линкуем и создаём игру..." + @$(CC) $^ -lcurses -lm -o game + +clean: + @echo "Очистка..." + @rm -f *.o game + diff --git a/a4/README.md b/a4/README.md new file mode 100644 index 0000000..b57958c --- /dev/null +++ b/a4/README.md @@ -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. + diff --git a/a4/game.c b/a4/game.c new file mode 100644 index 0000000..6a44f08 --- /dev/null +++ b/a4/game.c @@ -0,0 +1,45 @@ +#include "game.h" +#include "world.h" +#include +#include "world.h" +#include +#include +void* init_game(void) { + struct game* state = (struct game*)calloc(1, sizeof(struct game)); + if (!state) { + return NULL; + } + state->cat_x = 5; + state->cat_y = 5; + state->cat_dx = 1; + state->cat_dy = 1; + state->mouse_x = 15; + state->mouse_y = 10; + snprintf(state->message, sizeof(state->message), "Давай, попробуй поймать меня!"); + return state; +} + +int game_event(struct event* event, void* g_state) { + struct game* game = (struct game*)g_state; + if (!game) return 0; + + if (event->type == KEY_SREDO) { + switch (event->key) { + case KEY_UP: game->cat_y -= game->cat_dy; break; + case KEY_DOWN: game->cat_y += game->cat_dy; break; + case KEY_LEFT: game->cat_x -= game->cat_dx; break; + case KEY_RIGHT: game->cat_x += game->cat_dx; break; + default: break; + } + } + + game->mouse_x += (rand() % 3) - 1; + game->mouse_y += (rand() % 3) - 1; + + if (game->cat_x == game->mouse_x && game->cat_y == game->mouse_y) { + snprintf(game->message, sizeof(game->message), "Мышь поймана! Ура!"); + } + + return 0; +} + diff --git a/a4/game.h b/a4/game.h new file mode 100644 index 0000000..8b1d6ca --- /dev/null +++ b/a4/game.h @@ -0,0 +1,18 @@ +#ifndef _GAME_H_INCLUDE_ +#define _GAME_H_INCLUDE_ + +#include "world.h" + +struct game { + int cat_dx, cat_dy; + int cat_x, cat_y; + int mouse_x, mouse_y; + int cat_dx_position, cat_dy_position; + char message[100]; +}; + +void* init_game(void); +int game_event(struct event* event, void* game); + +#endif + diff --git a/a4/main.c b/a4/main.c new file mode 100644 index 0000000..4eb0b50 --- /dev/null +++ b/a4/main.c @@ -0,0 +1,19 @@ +#include "game.h" +#include "world.h" +#include +#include "world.h" +#include +int main(void) { + void* game_state = init_game(); + if (!game_state) { + fprintf(stderr, "Не удалось инициализировать игру!\n"); + return 1; + } + + world_run(game_state, game_event); + + free(game_state); + + return 0; +} + diff --git a/a4/world.c b/a4/world.c new file mode 100644 index 0000000..5a770fd --- /dev/null +++ b/a4/world.c @@ -0,0 +1,135 @@ +#include "world.h" +#include "game.h" +#include +#include +#include +#include + +int TIMEOUT = 100; + +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) { + snprintf(msg, sizeof(msg), "%s: width %d is out of bounds (0,%d)", source, x, COLS); + abort_game(msg); + } + if (y < 0 || y >= LINES) { + snprintf(msg, sizeof(msg), "%s: height %d is out of bounds (0,%d)", source, y, LINES); + abort_game(msg); + } +} + +void clear_screen() { + mvaddch(0, 0, ' '); + int screenchars = LINES * COLS; + for (int i = 1; i < screenchars; i++) { + 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 world_run(void* (*init_game)(), int (*world_event)(struct event* event, void* game)) { + void* game = init_game(); + if (!game) { + return -1; + } + + timeout(TIMEOUT); + 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; + event.time_ms = start_time; + + while (1) { + memset(&event, 0, sizeof(struct event)); + event.height = LINES; + event.width = COLS; + event.type = EVENT_START; + event.time_ms = clock(); + + world_event(&event, game); + + if (event.key == ERR) { + event.type = EVENT_TIMEOUT; + last_timeout = clock(); + event.time_ms = last_timeout + TIMEOUT; + } else if (event.key == KEY_MOUSE) { + event.type = EVENT_MOUSE; + MEVENT mouse_event; + 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) { // ESC key + event.type = EVENT_ESC; + } + } + + event.time_ms = clock(); + world_event(&event, game); + timeout(TIMEOUT); + } + + return 0; +} + diff --git a/a4/world.h b/a4/world.h new file mode 100644 index 0000000..73be057 --- /dev/null +++ b/a4/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