diff --git a/BearLibTerminal.h b/BearLibTerminal.h new file mode 100644 index 0000000..e112b7c --- /dev/null +++ b/BearLibTerminal.h @@ -0,0 +1,766 @@ +/* +* BearLibTerminal +* Copyright (C) 2013-2017 Cfyz +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +* of the Software, and to permit persons to whom the Software is furnished to do +* so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef BEARLIBTERMINAL_H +#define BEARLIBTERMINAL_H + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifdef __GNUC__ +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1) +#pragma GCC diagnostic ignored "-Wformat-nonliteral" /* False-positive when wrapping vsnprintf. */ +#endif /* __GNUC__ >= 4.1 */ +#endif + +#include +#include +#include +#include +#include +#include +#if defined(__cplusplus) +#include +#endif + +/* + * Keyboard scancodes for events/states + */ +#define TK_A 0x04 +#define TK_B 0x05 +#define TK_C 0x06 +#define TK_D 0x07 +#define TK_E 0x08 +#define TK_F 0x09 +#define TK_G 0x0A +#define TK_H 0x0B +#define TK_I 0x0C +#define TK_J 0x0D +#define TK_K 0x0E +#define TK_L 0x0F +#define TK_M 0x10 +#define TK_N 0x11 +#define TK_O 0x12 +#define TK_P 0x13 +#define TK_Q 0x14 +#define TK_R 0x15 +#define TK_S 0x16 +#define TK_T 0x17 +#define TK_U 0x18 +#define TK_V 0x19 +#define TK_W 0x1A +#define TK_X 0x1B +#define TK_Y 0x1C +#define TK_Z 0x1D +#define TK_1 0x1E +#define TK_2 0x1F +#define TK_3 0x20 +#define TK_4 0x21 +#define TK_5 0x22 +#define TK_6 0x23 +#define TK_7 0x24 +#define TK_8 0x25 +#define TK_9 0x26 +#define TK_0 0x27 +#define TK_RETURN 0x28 +#define TK_ENTER 0x28 +#define TK_ESCAPE 0x29 +#define TK_BACKSPACE 0x2A +#define TK_TAB 0x2B +#define TK_SPACE 0x2C +#define TK_MINUS 0x2D /* - */ +#define TK_EQUALS 0x2E /* = */ +#define TK_LBRACKET 0x2F /* [ */ +#define TK_RBRACKET 0x30 /* ] */ +#define TK_BACKSLASH 0x31 /* \ */ +#define TK_SEMICOLON 0x33 /* ; */ +#define TK_APOSTROPHE 0x34 /* ' */ +#define TK_GRAVE 0x35 /* ` */ +#define TK_COMMA 0x36 /* , */ +#define TK_PERIOD 0x37 /* . */ +#define TK_SLASH 0x38 /* / */ +#define TK_F1 0x3A +#define TK_F2 0x3B +#define TK_F3 0x3C +#define TK_F4 0x3D +#define TK_F5 0x3E +#define TK_F6 0x3F +#define TK_F7 0x40 +#define TK_F8 0x41 +#define TK_F9 0x42 +#define TK_F10 0x43 +#define TK_F11 0x44 +#define TK_F12 0x45 +#define TK_PAUSE 0x48 /* Pause/Break */ +#define TK_INSERT 0x49 +#define TK_HOME 0x4A +#define TK_PAGEUP 0x4B +#define TK_DELETE 0x4C +#define TK_END 0x4D +#define TK_PAGEDOWN 0x4E +#define TK_RIGHT 0x4F /* Right arrow */ +#define TK_LEFT 0x50 /* Left arrow */ +#define TK_DOWN 0x51 /* Down arrow */ +#define TK_UP 0x52 /* Up arrow */ +#define TK_KP_DIVIDE 0x54 /* '/' on numpad */ +#define TK_KP_MULTIPLY 0x55 /* '*' on numpad */ +#define TK_KP_MINUS 0x56 /* '-' on numpad */ +#define TK_KP_PLUS 0x57 /* '+' on numpad */ +#define TK_KP_ENTER 0x58 +#define TK_KP_1 0x59 +#define TK_KP_2 0x5A +#define TK_KP_3 0x5B +#define TK_KP_4 0x5C +#define TK_KP_5 0x5D +#define TK_KP_6 0x5E +#define TK_KP_7 0x5F +#define TK_KP_8 0x60 +#define TK_KP_9 0x61 +#define TK_KP_0 0x62 +#define TK_KP_PERIOD 0x63 /* '.' on numpad */ +#define TK_SHIFT 0x70 +#define TK_CONTROL 0x71 +#define TK_ALT 0x72 + +/* + * Mouse events/states + */ +#define TK_MOUSE_LEFT 0x80 /* Buttons */ +#define TK_MOUSE_RIGHT 0x81 +#define TK_MOUSE_MIDDLE 0x82 +#define TK_MOUSE_X1 0x83 +#define TK_MOUSE_X2 0x84 +#define TK_MOUSE_MOVE 0x85 /* Movement event */ +#define TK_MOUSE_SCROLL 0x86 /* Mouse scroll event */ +#define TK_MOUSE_X 0x87 /* Cusor position in cells */ +#define TK_MOUSE_Y 0x88 +#define TK_MOUSE_PIXEL_X 0x89 /* Cursor position in pixels */ +#define TK_MOUSE_PIXEL_Y 0x8A +#define TK_MOUSE_WHEEL 0x8B /* Scroll direction and amount */ +#define TK_MOUSE_CLICKS 0x8C /* Number of consecutive clicks */ + +/* + * If key was released instead of pressed, it's code will be OR'ed with TK_KEY_RELEASED: + * a) pressed 'A': 0x04 + * b) released 'A': 0x04|VK_KEY_RELEASED = 0x104 + */ +#define TK_KEY_RELEASED 0x100 + +/* + * Virtual key-codes for internal terminal states/variables. + * These can be accessed via terminal_state function. + */ +#define TK_WIDTH 0xC0 /* Terminal window size in cells */ +#define TK_HEIGHT 0xC1 +#define TK_CELL_WIDTH 0xC2 /* Character cell size in pixels */ +#define TK_CELL_HEIGHT 0xC3 +#define TK_COLOR 0xC4 /* Current foregroung color */ +#define TK_BKCOLOR 0xC5 /* Current background color */ +#define TK_LAYER 0xC6 /* Current layer */ +#define TK_COMPOSITION 0xC7 /* Current composition state */ +#define TK_CHAR 0xC8 /* Translated ANSI code of last produced character */ +#define TK_WCHAR 0xC9 /* Unicode codepoint of last produced character */ +#define TK_EVENT 0xCA /* Last dequeued event */ +#define TK_FULLSCREEN 0xCB /* Fullscreen state */ + +/* + * Other events + */ +#define TK_CLOSE 0xE0 +#define TK_RESIZED 0xE1 + +/* + * Generic mode enum. + * Right now it is used for composition option only. + */ +#define TK_OFF 0 +#define TK_ON 1 + +/* + * Input result codes for terminal_read function. + */ +#define TK_INPUT_NONE 0 +#define TK_INPUT_CANCELLED -1 + +/* + * Text printing alignment. + */ +#define TK_ALIGN_DEFAULT 0 +#define TK_ALIGN_LEFT 1 +#define TK_ALIGN_RIGHT 2 +#define TK_ALIGN_CENTER 3 +#define TK_ALIGN_TOP 4 +#define TK_ALIGN_BOTTOM 8 +#define TK_ALIGN_MIDDLE 12 + +/* + * Terminal uses unsigned 32-bit value for color representation in ARGB order (0xAARRGGBB), e. g. + * a) solid red is 0xFFFF0000 + * b) half-transparent green is 0x8000FF00 + */ +typedef uint32_t color_t; + +typedef struct dimensions_t_ +{ + int width; + int height; +} +dimensions_t; + +#if defined(BEARLIBTERMINAL_STATIC_BUILD) +# define TERMINAL_API +#elif defined(_WIN32) +# if defined(BEARLIBTERMINAL_BUILDING_LIBRARY) +# define TERMINAL_API __declspec(dllexport) +# else +# define TERMINAL_API __declspec(dllimport) +# endif +#else +# if defined(BEARLIBTERMINAL_BUILDING_LIBRARY) && __GNUC__ >= 4 +# define TERMINAL_API __attribute__ ((visibility ("default"))) +# else +# define TERMINAL_API +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +TERMINAL_API int terminal_open(); +TERMINAL_API void terminal_close(); +TERMINAL_API int terminal_set8(const int8_t* value); +TERMINAL_API int terminal_set16(const int16_t* value); +TERMINAL_API int terminal_set32(const int32_t* value); +TERMINAL_API void terminal_refresh(); +TERMINAL_API void terminal_clear(); +TERMINAL_API void terminal_clear_area(int x, int y, int w, int h); +TERMINAL_API void terminal_crop(int x, int y, int w, int h); +TERMINAL_API void terminal_layer(int index); +TERMINAL_API void terminal_color(color_t color); +TERMINAL_API void terminal_bkcolor(color_t color); +TERMINAL_API void terminal_composition(int mode); +TERMINAL_API void terminal_font8(const int8_t* name); +TERMINAL_API void terminal_font16(const int16_t* name); +TERMINAL_API void terminal_font32(const int32_t* name); +TERMINAL_API void terminal_put(int x, int y, int code); +TERMINAL_API void terminal_put_ext(int x, int y, int dx, int dy, int code, color_t* corners); +TERMINAL_API int terminal_pick(int x, int y, int index); +TERMINAL_API color_t terminal_pick_color(int x, int y, int index); +TERMINAL_API color_t terminal_pick_bkcolor(int x, int y); +TERMINAL_API void terminal_print_ext8(int x, int y, int w, int h, int align, const int8_t* s, int* out_w, int* out_h); +TERMINAL_API void terminal_print_ext16(int x, int y, int w, int h, int align, const int16_t* s, int* out_w, int* out_h); +TERMINAL_API void terminal_print_ext32(int x, int y, int w, int h, int align, const int32_t* s, int* out_w, int* out_h); +TERMINAL_API void terminal_measure_ext8(int w, int h, const int8_t* s, int* out_w, int* out_h); +TERMINAL_API void terminal_measure_ext16(int w, int h, const int16_t* s, int* out_w, int* out_h); +TERMINAL_API void terminal_measure_ext32(int w, int h, const int32_t* s, int* out_w, int* out_h); +TERMINAL_API int terminal_has_input(); +TERMINAL_API int terminal_state(int code); +TERMINAL_API int terminal_read(); +TERMINAL_API int terminal_read_str8(int x, int y, int8_t* buffer, int max); +TERMINAL_API int terminal_read_str16(int x, int y, int16_t* buffer, int max); +TERMINAL_API int terminal_read_str32(int x, int y, int32_t* buffer, int max); +TERMINAL_API int terminal_peek(); +TERMINAL_API void terminal_delay(int period); +TERMINAL_API const int8_t* terminal_get8(const int8_t* key, const int8_t* default_); +TERMINAL_API const int16_t* terminal_get16(const int16_t* key, const int16_t* default_); +TERMINAL_API const int32_t* terminal_get32(const int32_t* key, const int32_t* default_); +TERMINAL_API color_t color_from_name8(const int8_t* name); +TERMINAL_API color_t color_from_name16(const int16_t* name); +TERMINAL_API color_t color_from_name32(const int32_t* name); +TERMINAL_API int terminal_put_array(int x, int y, int w, int h, const uint8_t* data, int row_stride, int column_stride, const void* layout, int char_size); + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif + +/* + * Utility macro trick which allows macro-in-macro expansion + */ +#define TERMINAL_CAT(a, b) TERMINAL_PRIMITIVE_CAT(a, b) +#define TERMINAL_PRIMITIVE_CAT(a, b) a ## b + +/* + * wchar_t has different sized depending on platform. Furthermore, it's size + * can be changed for GCC compiler. + */ +#if !defined(__SIZEOF_WCHAR_T__) +# if defined(_WIN32) +# define __SIZEOF_WCHAR_T__ 2 +# else +# define __SIZEOF_WCHAR_T__ 4 +# endif +#endif + +#if __SIZEOF_WCHAR_T__ == 2 +#define TERMINAL_WCHAR_SUFFIX 16 +#define TERMINAL_WCHAR_TYPE int16_t +#else // 4 +#define TERMINAL_WCHAR_SUFFIX 32 +#define TERMINAL_WCHAR_TYPE int32_t +#endif + +#if defined(__cplusplus) +#define TERMINAL_INLINE inline +#define TERMINAL_DEFAULT(value) = value +#else +#define TERMINAL_INLINE static inline +#define TERMINAL_DEFAULT(value) +#endif + +/* + * These functions provide inline string formatting support + * for terminal_setf, terminal_printf, etc. + * + * Using static termporary buffer is okay because terminal API is not + * required to be multiple-thread safe by design. + */ + +#define TERMINAL_VSPRINTF_MAXIMUM_BUFFER_SIZE 65536 + +TERMINAL_INLINE const char* terminal_vsprintf(const char* s, va_list args) +{ + static int buffer_size = 512; + static char* buffer = NULL; + int rc = 0; + + if (!s) + return NULL; + else if (!buffer) + buffer = (char*)malloc(buffer_size); + + while (1) + { + buffer[buffer_size-1] = '\0'; + rc = vsnprintf(buffer, buffer_size, s, args); + if (rc >= buffer_size || buffer[buffer_size-1] != '\0') + { + if (buffer_size >= TERMINAL_VSPRINTF_MAXIMUM_BUFFER_SIZE) + return NULL; + + buffer_size *= 2; + buffer = (char*)realloc(buffer, buffer_size); + } + else + { + break; + } + } + + return rc >= 0? buffer: NULL; +} + +TERMINAL_INLINE const wchar_t* terminal_vswprintf(const wchar_t* s, va_list args) +{ + static int buffer_size = 512; + static wchar_t* buffer = NULL; + int rc = 0; + + if (!s) + return NULL; + else if (!buffer) + buffer = (wchar_t*)malloc(buffer_size * sizeof(wchar_t)); + + while (1) + { + buffer[buffer_size-1] = L'\0'; +#if defined(_WIN32) + rc = _vsnwprintf(buffer, buffer_size, s, args); +#else + rc = vswprintf(buffer, buffer_size, s, args); +#endif + if (rc >= buffer_size || buffer[buffer_size-1] != L'\0') + { + if (buffer_size >= TERMINAL_VSPRINTF_MAXIMUM_BUFFER_SIZE) + return NULL; + + buffer_size *= 2; + buffer = (wchar_t*)realloc(buffer, buffer_size * sizeof(wchar_t)); + } + else + { + break; + } + } + + return rc >= 0? buffer: NULL; +} + +#define TERMINAL_FORMATTED_WRAP(type, call) \ + type ret; \ + va_list args; \ + va_start(args, s); \ + ret = call; \ + va_end(args); \ + return ret; + +#define TERMINAL_FORMATTED_WRAP_V(call) \ + va_list args; \ + va_start(args, s); \ + call; \ + va_end(args); + +/* + * This set of inline functions define basic name substitution + type cast: + * terminal_[w]xxxx -> terminal_xxxx{8|16|32} + */ + +TERMINAL_INLINE int terminal_set(const char* s) +{ + return terminal_set8((const int8_t*)s); +} + +TERMINAL_INLINE int terminal_setf(const char* s, ...) +{ + TERMINAL_FORMATTED_WRAP(int, terminal_set(terminal_vsprintf(s, args))) +} + +TERMINAL_INLINE int terminal_wset(const wchar_t* s) +{ + return TERMINAL_CAT(terminal_set, TERMINAL_WCHAR_SUFFIX)((const TERMINAL_WCHAR_TYPE*)s); +} + +TERMINAL_INLINE int terminal_wsetf(const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(int, terminal_wset(terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE void terminal_font(const char* name) +{ + terminal_font8((const int8_t*)name); +} + +TERMINAL_INLINE void terminal_wfont(const wchar_t* name) +{ + TERMINAL_CAT(terminal_font, TERMINAL_WCHAR_SUFFIX)((const TERMINAL_WCHAR_TYPE*)name); +} + +TERMINAL_INLINE dimensions_t terminal_print(int x, int y, const char* s) +{ + dimensions_t ret; + terminal_print_ext8(x, y, 0, 0, TK_ALIGN_DEFAULT, (const int8_t*)s, &ret.width, &ret.height); + return ret; +} + +TERMINAL_INLINE dimensions_t terminal_printf(int x, int y, const char* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_print(x, y, terminal_vsprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_wprint(int x, int y, const wchar_t* s) +{ + dimensions_t ret; + TERMINAL_CAT(terminal_print_ext, TERMINAL_WCHAR_SUFFIX)(x, y, 0, 0, TK_ALIGN_DEFAULT, (const TERMINAL_WCHAR_TYPE*)s, &ret.width, &ret.height); + return ret; +} + +TERMINAL_INLINE dimensions_t terminal_wprintf(int x, int y, const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wprint(x, y, terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_print_ext(int x, int y, int w, int h, int align, const char* s) +{ + dimensions_t ret; + terminal_print_ext8(x, y, w, h, align, (const int8_t*)s, &ret.width, &ret.height); + return ret; +} + +TERMINAL_INLINE dimensions_t terminal_printf_ext(int x, int y, int w, int h, int align, const char* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_print_ext(x, y, w, h, align, terminal_vsprintf(s, args))); +} + +TERMINAL_INLINE dimensions_t terminal_wprint_ext(int x, int y, int w, int h, int align, const wchar_t* s) +{ + dimensions_t ret; + TERMINAL_CAT(terminal_print_ext, TERMINAL_WCHAR_SUFFIX)(x, y, w, h, align, (const TERMINAL_WCHAR_TYPE*)s, &ret.width, &ret.height); + return ret; +} + +TERMINAL_INLINE dimensions_t terminal_wprintf_ext(int x, int y, int w, int h, int align, const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wprint_ext(x, y, w, h, align, terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_measure(const char* s) +{ + dimensions_t ret; + terminal_measure_ext8(0, 0, (const int8_t*)s, &ret.width, &ret.height); + return ret; +} + +TERMINAL_INLINE dimensions_t terminal_measuref(const char* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_measure(terminal_vsprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_wmeasure(const wchar_t* s) +{ + dimensions_t ret; + TERMINAL_CAT(terminal_measure_ext, TERMINAL_WCHAR_SUFFIX)(0, 0, (const TERMINAL_WCHAR_TYPE*)s, &ret.width, &ret.height); + return ret; +} + +TERMINAL_INLINE dimensions_t terminal_wmeasuref(const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wmeasure(terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_measure_ext(int w, int h, const char* s) +{ + dimensions_t ret; + terminal_measure_ext8(w, h, (const int8_t*)s, &ret.width, &ret.height); + return ret; +} + +TERMINAL_INLINE dimensions_t terminal_measuref_ext(int w, int h, const char* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_measure_ext(w, h, terminal_vsprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_wmeasure_ext(int w, int h, const wchar_t* s) +{ + dimensions_t ret; + TERMINAL_CAT(terminal_measure_ext, TERMINAL_WCHAR_SUFFIX)(w, h, (const TERMINAL_WCHAR_TYPE*)s, &ret.width, &ret.height); + return ret; +} + +TERMINAL_INLINE dimensions_t terminal_wmeasuref_ext(int w, int h, const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wmeasure_ext(w, h, terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE int terminal_read_str(int x, int y, char* buffer, int max) +{ + return terminal_read_str8(x, y, (int8_t*)buffer, max); +} + +TERMINAL_INLINE int terminal_read_wstr(int x, int y, wchar_t* buffer, int max) +{ + return TERMINAL_CAT(terminal_read_str, TERMINAL_WCHAR_SUFFIX)(x, y, (TERMINAL_WCHAR_TYPE*)buffer, max); +} + +TERMINAL_INLINE const char* terminal_get(const char* key, const char* default_ TERMINAL_DEFAULT((const char*)0)) +{ + return (const char*)terminal_get8((const int8_t*)key, (const int8_t*)default_); +} + +TERMINAL_INLINE const wchar_t* terminal_wget(const wchar_t* key, const wchar_t* default_ TERMINAL_DEFAULT((const wchar_t*)0)) +{ + return (const wchar_t*)TERMINAL_CAT(terminal_get, TERMINAL_WCHAR_SUFFIX)((const TERMINAL_WCHAR_TYPE*)key, (const TERMINAL_WCHAR_TYPE*)default_); +} + +TERMINAL_INLINE color_t color_from_name(const char* name) +{ + return color_from_name8((const int8_t*)name); +} + +TERMINAL_INLINE color_t color_from_wname(const wchar_t* name) +{ + return TERMINAL_CAT(color_from_name, TERMINAL_WCHAR_SUFFIX)((const TERMINAL_WCHAR_TYPE*)name); +} + +#ifdef __cplusplus +/* + * C++ supports function overloading, should take advantage of it. + */ + +TERMINAL_INLINE int terminal_set(const wchar_t* s) +{ + return terminal_wset(s); +} + +TERMINAL_INLINE int terminal_setf(const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(int, terminal_wset(terminal_vswprintf(s, args))); +} + +TERMINAL_INLINE void terminal_color(const char* name) +{ + terminal_color(color_from_name(name)); +} + +TERMINAL_INLINE void terminal_color(const wchar_t* name) +{ + terminal_color(color_from_wname(name)); +} + +TERMINAL_INLINE void terminal_bkcolor(const char* name) +{ + terminal_bkcolor(color_from_name(name)); +} + +TERMINAL_INLINE void terminal_bkcolor(const wchar_t* name) +{ + terminal_bkcolor(color_from_wname(name)); +} + +TERMINAL_INLINE void terminal_font(const wchar_t* name) +{ + terminal_wfont(name); +} + +TERMINAL_INLINE void terminal_put_ext(int x, int y, int dx, int dy, int code) +{ + terminal_put_ext(x, y, dx, dy, code, 0); +} + +TERMINAL_INLINE dimensions_t terminal_print(int x, int y, const wchar_t* s) +{ + return terminal_wprint(x, y, s); +} + +TERMINAL_INLINE dimensions_t terminal_printf(int x, int y, const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wprint(x, y, terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_print_ext(int x, int y, int w, int h, int align, const wchar_t* s) +{ + return terminal_wprint_ext(x, y, w, h, align, s); +} + +TERMINAL_INLINE dimensions_t terminal_printf_ext(int x, int y, int w, int h, int align, const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wprint_ext(x, y, w, h, align, terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_measure(const wchar_t* s) +{ + return terminal_wmeasure(s); +} + +TERMINAL_INLINE dimensions_t terminal_measuref(const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wmeasure(terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE dimensions_t terminal_measure_ext(int w, int h, const wchar_t* s) +{ + return terminal_wmeasure_ext(w, h, s); +} + +TERMINAL_INLINE dimensions_t terminal_measuref_ext(int w, int h, const wchar_t* s, ...) +{ + TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wmeasure_ext(w, h, terminal_vswprintf(s, args))) +} + +TERMINAL_INLINE int terminal_read_str(int x, int y, wchar_t* buffer, int max) +{ + return terminal_read_wstr(x, y, buffer, max); +} + +TERMINAL_INLINE color_t color_from_name(const wchar_t* name) +{ + return color_from_wname(name); +} + +TERMINAL_INLINE int terminal_pick(int x, int y) +{ + return terminal_pick(x, y, 0); +} + +TERMINAL_INLINE color_t terminal_pick_color(int x, int y) +{ + return terminal_pick_color(x, y, 0); +} + +TERMINAL_INLINE const wchar_t* terminal_get(const wchar_t* key, const wchar_t* default_ = (const wchar_t*)0) +{ + return terminal_wget(key, default_); +} + +template T terminal_get(const C* key, const T& default_ = T()) +{ + const C* result_str = terminal_get(key, (const C*)0); + if (result_str[0] == C(0)) + return default_; + T result; + return (bool)(std::basic_istringstream(result_str) >> result)? result: default_; +} + +#endif /* __cplusplus */ + +/* + * Color routines + */ +TERMINAL_INLINE color_t color_from_argb(uint8_t a, uint8_t r, uint8_t g, uint8_t b) +{ + return ((color_t)a << 24) | (r << 16) | (g << 8) | b; +} + +/* + * Other functional sugar + */ +TERMINAL_INLINE int terminal_check(int code) +{ + return terminal_state(code) > 0; +} + +/* + * WinMain entry point handling macro. This allows easier entry point definition. + * The macro will expand to proper WinMain stub regardless of header include order. + */ +#if defined(_WIN32) + +/* + * WinMain probe macro. It will expand to either X or X_WINDOWS_ depending on + * Windows.h header inclusion. + */ +#define TERMINAL_TAKE_CARE_OF_WINMAIN TERMINAL_WINMAIN_PROBE_IMPL(_WINDOWS_) +#define TERMINAL_WINMAIN_PROBE_IMPL(DEF) TERMINAL_PRIMITIVE_CAT(TERMINAL_WINMAIN_IMPL, DEF) + +/* + * Trivial no-arguments WinMain implementation. It just calls main. + */ +#define TERMINAL_WINMAIN_IMPL_BASE(INSTANCE_T, STRING_T)\ + extern "C" int main();\ + extern "C" int __stdcall WinMain(INSTANCE_T hInstance, INSTANCE_T hPrevInstance, STRING_T lpCmdLine, int nCmdShow)\ + {\ + return main();\ + } + +/* + * Macro expands to empty string. Windows.h is included thus code MUST use + * predefined types or else MSVC will complain. + */ +#define TERMINAL_WINMAIN_IMPL TERMINAL_WINMAIN_IMPL_BASE(HINSTANCE, LPSTR) + +/* + * Macro expands to macro name. Windows.h wasn't included, so WinMain will be + * defined with fundamental types (enough for linker to find it). + */ +#define TERMINAL_WINMAIN_IMPL_WINDOWS_ TERMINAL_WINMAIN_IMPL_BASE(void*, char*) + +#else + +/* + * Only Windows has WinMain but macro still must be defined for cross-platform + * applications. + */ +#define TERMINAL_TAKE_CARE_OF_WINMAIN + +#endif + +#endif // BEARLIBTERMINAL_H diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0a3b80e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.30) +project(roughlike) + +set(CMAKE_CXX_STANDARD 20) + +add_executable(roughlike main.cpp) diff --git a/a.out b/a.out new file mode 100755 index 0000000..16c7018 Binary files /dev/null and b/a.out differ diff --git a/cmake-build-debug/.cmake/api/v1/reply/index-2025-05-25T10-24-30-0784.json b/cmake-build-debug/.cmake/api/v1/reply/index-2025-06-28T20-55-18-0749.json similarity index 100% rename from cmake-build-debug/.cmake/api/v1/reply/index-2025-05-25T10-24-30-0784.json rename to cmake-build-debug/.cmake/api/v1/reply/index-2025-06-28T20-55-18-0749.json diff --git a/cmake-build-debug/CMakeFiles/clion-Debug-log.txt b/cmake-build-debug/CMakeFiles/clion-Debug-log.txt index 4bf78b4..36991ea 100644 --- a/cmake-build-debug/CMakeFiles/clion-Debug-log.txt +++ b/cmake-build-debug/CMakeFiles/clion-Debug-log.txt @@ -1 +1,4 @@ -CMakeLists.txt not found in /home/doctor/CLionProjects/roughlike Select CMakeLists.txt +/home/doctor/Downloads/CLion-2024.3.4/clion-2024.3.4/bin/cmake/linux/x64/bin/cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=/home/doctor/Downloads/CLion-2024.3.4/clion-2024.3.4/bin/ninja/linux/x64/ninja -G Ninja -S /home/doctor/CLionProjects/roughlike -B /home/doctor/CLionProjects/roughlike/cmake-build-debug +-- Configuring done (0.0s) +-- Generating done (0.0s) +-- Build files have been written to: /home/doctor/CLionProjects/roughlike/cmake-build-debug diff --git a/libBearLibTerminal.so b/libBearLibTerminal.so new file mode 100755 index 0000000..c1ca5ed Binary files /dev/null and b/libBearLibTerminal.so differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..2c07a40 --- /dev/null +++ b/main.cpp @@ -0,0 +1,546 @@ +#include "BearLibTerminal.h" +#include +#include +#include +#include +#include +#include +#include + +struct GameMapPoint { + int objId = -1; +}; +struct Effect { + int id = -1; + int value = 0; + int timing = 0; +}; +class GameObject { +public: + int heals; + int type = -1; + std::vector effects; + int x; + int y; + + void get_collision(){}; + std::vector send_collision(){}; + + virtual int move() { + return -1; + }; + + virtual int getType() { + return type; + } + + virtual int skin() { + return '.'; + }; +}; + +class Wall : public GameObject { +private: + int type = 1; +public: + void collision(){}; + int skin() override { + return 9608; + } +}; + +class Player : public GameObject { + +public: + int heals; + int type = 0; + int damage = 1; + std::vector effects; + + int x; + int y; + + Player(int x, int y):x(x), y(y) { + heals = 100; + }; + int getType() override { + return 0; + } + + void get_collision(std::vector effects) { + for (int i = 0; i < effects.size(); i++) { + if (effects[i].id == 1) { + heals -= effects[i].value; + } + if (effects[i].id == 2) { + heals += effects[i].value; + damage += 1; + } + } + + }; + + std::vector send_collision(bool isFirst) { + + std::vector effects; + if (isFirst){ + Effect e; + e.id = 1; + e.value = damage; + effects.push_back(e); + } + return effects; + }; + + void collision() { + + }; + + int skin() override { + return '@'; + } + + int move(int state) { + std::cout << state << std::endl; + std::cout <<"Heals:"<< heals << std::endl; + std::cout <<"Damage:"<< damage << std::endl; + if (state == 22) { + return 2; + } + if (state == 26) { + return 1; + } + if (state == 7) { + return 3; + } + if (state == 4) { + return 4; + } + return 0; + } +}; + +class Enemy : public GameObject { + +public: + + int heals = 2; + int type = 0; + bool is_alive = true; + bool remove = false; + + void set_remove() { + remove = true; + } + + bool is_remove() const { + return remove; + } + int getType() override { + return 0; + } + + bool is_dead() { + return !is_alive; + } + void get_collision(std::vector effects) { + for (int i = 0; i < effects.size(); i++) { + if (effects[i].id == 1) { + heals -= effects[i].value; + } + + } + if (heals < 1) { + is_alive = false; + } + std::cout << "Enemy Heals" << heals << std::endl; + }; + + std::vector send_collision(bool isFirst) const { + std::vector effects; + if (heals < 1 && !isFirst) { + + Effect ef; + ef.id = 2; + ef.value = 2; + effects.push_back(ef); + } + if (isFirst){ + Effect ef; + ef.id = 1; + ef.value = 1; + effects.push_back(ef); + } + return effects; + }; + std::vector send_collision(bool isFirst, + const std::vector>& map, + int currX, + int currY) const { + + int count = 0; + for (int dx = -10; dx <= 10; dx++) { + for (int dy = -10; dy <= 10; dy++) { + if (dx == 0 && dy == 0) continue; + int nx = currX + dx; + int ny = currY + dy; + + if (nx >= 0 && nx < map.size() && + ny >= 0 && ny < map[0].size()) { + std::cout << map[nx][ny].objId; + if (map[nx][ny].objId > 2) { + count++; + } + } + } + } + std::vector effects; + if (heals < 1 && !isFirst) { + + Effect ef; + ef.id = 2; + ef.value = 2; + effects.push_back(ef); + } + if (isFirst){ + Effect ef; + ef.id = 1; + ef.value = count+1; + effects.push_back(ef); + } + return effects; + + } + + int skin() override { + return 'E'; + } + int move()override { + return 0; + }; + int move(std::vector> map, int playerX, int playerY, int selfId, int& newX, int& newY, int currX, int currY) { + int dx = 0, dy = 0; + if (playerX < currX) dx = -1; + else if (playerX > currX) dx = 1; + if (playerY < currY) dy = -1; + else if (playerY > currY) dy = 1; + + int nx = currX + dx; + int ny = currY + dy; + + if (nx >= 0 && nx < map.size() && ny >= 0 && ny < map[0].size()) { + if (map[nx][ny].objId == -1 || map[nx][ny].objId == 1) { + newX = nx; + newY = ny; + return 1; + } + } + return 0; + } +}; + + + +struct GameWorld { + std::vector> map; + Player player = Player(0,0); + std::vector enemies; +}; + + +class LevelBuilder { +private: + GameWorld world; + std::vector> map; + int width = 0; + int height = 0; + std::mt19937 rng; + int playerX = 0, playerY = 0; + +public: + LevelBuilder(int width, int height) : width(width), height(height) { + map = std::vector>(width, std::vector(height)); + std::random_device rd; + rng = std::mt19937(rd()); + } + bool loadFromFile(const std::string& filename) + { + std::ifstream fin(filename); + if (!fin) { + std::cerr << "Файла не существует: " << filename << std::endl; + return false; + } + + std::vector lines; + std::string line; + while (std::getline(fin, line)) { + if (!line.empty() && line.back() == '\r') + line.pop_back(); + lines.push_back(line); + } + if (lines.empty()) return false; + + height = static_cast(lines.size()); + width = static_cast(lines[0].size()); + map.assign(width, std::vector(height, GameMapPoint{})); + world.enemies.clear(); // сбрасываем врагов + + int nextEnemyId = 2; // 0 – стены, 1 – игрок, 2+ – враги + for (int y = 0; y < height; ++y) { + if (static_cast(lines[y].size()) != width) { + std::cerr << "Map is not rectangular!\n"; + return false; + } + for (int x = 0; x < width; ++x) { + char c = lines[y][x]; + switch (c) { + case '#': + map[x][y].objId = 0; + break; + case '@': + map[x][y].objId = 1; + playerX = x; + playerY = y; + world.player = Player(playerX, playerY); + break; + case 'E': { + map[x][y].objId = nextEnemyId++; + world.enemies.emplace_back(); + break; + } + default: + map[x][y].objId = -1; // пусто + } + } + } + return true; + } + void buildBorders() { + for (int i = 0; i < height; i++) { + map[0][i].objId = 0; + map[width - 1][i].objId = 0; + } + for (int i = 0; i < width; i++) { + map[i][0].objId = 0; + map[i][height - 1].objId = 0; + } + } + + void buildPlayer() { + playerX = width / 2; + playerY = height / 2; + world.player = Player(playerX, playerY); + map[playerX][playerY].objId = 1; + } + + void buildEnemies(int count = 10) { + std::uniform_int_distribution distX(1, width - 2); + std::uniform_int_distribution distY(1, height - 2); + + int enemyId = 2; + while (count > 0) { + int x = distX(rng); + int y = distY(rng); + + if (map[x][y].objId == -1 && (abs(x - playerX) + abs(y - playerY)) > 2) { + map[x][y].objId = enemyId++; + world.enemies.push_back(Enemy()); + count--; + } + } + } + + void buildWalls(int chancePercent = 15) { + std::uniform_int_distribution chance(0, 100); + for (int i = 1; i < width - 1; i++) { + for (int j = 1; j < height - 1; j++) { + if (map[i][j].objId == -1 && chance(rng) < chancePercent) { + map[i][j].objId = 0; // wall + } + } + } + } + + GameWorld getResult() { + world.map = map; + return world; + } +}; + + +class GameCore { +private: + GameWorld world; + Wall standartWall; + int get_skin(int objId) { + if (objId == 0) { + return standartWall.skin(); + } + if (objId == 1) { + return world.player.skin(); + } + if (objId >= 2) { + return world.enemies[objId - 2].skin(); + } + return '.'; + } + void draw() { + terminal_clear(); + + int term_width = terminal_state(TK_WIDTH); + int term_height = terminal_state(TK_HEIGHT); + int stats_panel_width = 20; + + int cam_x = world.player.x - (term_width - stats_panel_width) / 2; + int cam_y = world.player.y - term_height / 2; + + for (int i = 0; i < term_width - stats_panel_width; i++) { + for (int j = 0; j < term_height; j++) { + int map_x = i + cam_x; + int map_y = j + cam_y; + + if (map_x >= 0 && map_x < world.map.size() && map_y >= 0 && map_y < world.map[0].size()) { + int skin = get_skin(world.map[map_x][map_y].objId); + terminal_put(i, j, skin); + } + } + } + + int stats_x = term_width - stats_panel_width + 1; + terminal_print(stats_x, 1, "[color=orange]STATS"); + terminal_print(stats_x, 3, ("HP: " + std::to_string(world.player.heals)).c_str()); + terminal_print(stats_x, 4, ("DMG: " + std::to_string(world.player.damage)).c_str()); + + terminal_refresh(); + } + + +public: + + GameCore(int wight, int hight, LevelBuilder& builder) { + + builder.buildBorders(); + builder.buildPlayer(); + builder.buildEnemies(50); // кол-во врагов + builder.buildWalls(15); // шанс стены + builder.loadFromFile("mapExsample.txt"); + /* + *builder.loadFromFile("mapExsample.txt") + */ + world = builder.getResult(); + + standartWall = Wall(); + } + + void move_player(int button) { + int state = world.player.move(button); + int x = world.player.x, y = world.player.y; + int x_new = world.player.x, y_new = world.player.y; + + if (state == 2){ + y_new = y_new + 1; + } + + if (state == 1){ + y_new = y_new - 1; + } + + if (state == 3){ + x_new = x_new + 1; + } + + if (state == 4){ + x_new = x_new - 1; + + } + if (world.map[x_new][y_new].objId == -1){ + world.map[x][y].objId = -1; + world.map[x_new][y_new].objId = 1; + world.player.x = x_new; + world.player.y = y_new; + }else { + if (world.map[x_new][y_new].objId == 0) { + return; + } + auto collisions = world.player.send_collision(true); + world.enemies[world.map[x_new][y_new].objId - 2].get_collision(collisions); + collisions = world.enemies[world.map[x_new][y_new].objId - 2].send_collision(false); + world.player.get_collision(collisions); + } + + } + + void ai_work_section() { + for (int i = 0; i < world.enemies.size(); i++) { + if (world.enemies[i].is_dead() && !world.enemies[i].is_remove()) { + for (int j = 0; j < world.map.size(); j++) { + for (int k = 0; k < world.map[0].size(); k++) { + if (world.map[j][k].objId == i + 2) { + world.map[j][k].objId = -1; + world.enemies[i].set_remove(); + break; + } + } + } + continue; + } + + int ex = -1, ey = -1; + for (int x = 0; x < world.map.size(); x++) { + for (int y = 0; y < world.map[0].size(); y++) { + if (world.map[x][y].objId == i + 2) { + ex = x; + ey = y; + break; + } + } + if (ex != -1) break; + } + + int newX, newY; + if (world.enemies[i].move(world.map, world.player.x, world.player.y, i + 2, newX, newY, ex, ey)) { + if (world.map[newX][newY].objId == -1) { + world.map[newX][newY].objId = i + 2; + world.map[ex][ey].objId = -1; + } else if (world.map[newX][newY].objId == 1) { + // атака на игрока + auto col = world.enemies[i].send_collision(true,world.map, newX,newY); + world.player.get_collision(col); + auto ret = world.player.send_collision(false); + world.enemies[i].get_collision(ret); + } + } + } + } + + void game_loop() { + while (true) { + draw(); + int button = terminal_read(); + if (button == 41) { + terminal_close(); + return; + } + move_player(button); + ai_work_section(); + } + + } + +}; + +int main() { + terminal_open(); + terminal_print(1, 1, "Приветствуем тебя путник!"); + terminal_refresh(); + terminal_read(); + terminal_clear(); + terminal_print(1, 1, "Это генератор подземелий,\n здесь ты можешь прогуляться и постараться уничтожить всех врагов!!"); + terminal_refresh(); + terminal_read(); + + LevelBuilder bilder = LevelBuilder(100, 100); + GameCore game_core(100,100,bilder); + game_core.game_loop(); + return 0; +} diff --git a/mapExsample.txt b/mapExsample.txt new file mode 100644 index 0000000..55cb4f9 --- /dev/null +++ b/mapExsample.txt @@ -0,0 +1,6 @@ +#################### +#.................E# +#..###............## +#.@.........E......# +#..............E...# +#################### diff --git a/my_game b/my_game new file mode 100755 index 0000000..146c934 Binary files /dev/null and b/my_game differ