From 636dabd8b104b0184b8a33bd0cfe5cd5a15487a9 Mon Sep 17 00:00:00 2001 From: Julia <145168563+julia-aph@users.noreply.github.com> Date: Mon, 25 Mar 2024 00:34:59 -0500 Subject: [PATCH] rewrite progress rahh --- .vscode/c_cpp_properties.json | 21 ++ .vscode/settings.json | 55 ++++ checksums.txt | 1 + objects/controller.o | Bin 0 -> 2126 bytes objects/input.o | Bin 0 -> 2134 bytes objects/main.o | Bin 0 -> 4047 bytes objects/win.o | Bin 0 -> 3511 bytes run.py | 94 +++++++ source/fumotris.h | 17 ++ source/game/gametime.c | 21 ++ source/game/gametime.h | 6 + source/game/tetr.c | 79 ++++++ source/game/tetr.h | 28 ++ source/io/ctrl.c | 265 +++++++++++++++++++ source/io/ctrl.h | 114 +++++++++ source/io/input.c | 79 ++++++ source/io/input.h | 11 + source/io/platforms/win.c | 32 +++ source/io/platforms/win.h | 14 + source/io/platforms/winhandler.c | 140 ++++++++++ source/io/platforms/winhandler.h | 18 ++ source/io/term.c | 128 ++++++++++ source/io/term.h | 29 +++ source/main.c | 136 ++++++++++ source/test.cpp | 422 +++++++++++++++++++++++++++++++ test.exe | Bin 0 -> 79535 bytes 26 files changed, 1710 insertions(+) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/settings.json create mode 100644 checksums.txt create mode 100644 objects/controller.o create mode 100644 objects/input.o create mode 100644 objects/main.o create mode 100644 objects/win.o create mode 100644 run.py create mode 100644 source/fumotris.h create mode 100644 source/game/gametime.c create mode 100644 source/game/gametime.h create mode 100644 source/game/tetr.c create mode 100644 source/game/tetr.h create mode 100644 source/io/ctrl.c create mode 100644 source/io/ctrl.h create mode 100644 source/io/input.c create mode 100644 source/io/input.h create mode 100644 source/io/platforms/win.c create mode 100644 source/io/platforms/win.h create mode 100644 source/io/platforms/winhandler.c create mode 100644 source/io/platforms/winhandler.h create mode 100644 source/io/term.c create mode 100644 source/io/term.h create mode 100644 source/main.c create mode 100644 source/test.cpp create mode 100644 test.exe diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..4fa3689 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "windowsSdkVersion": "10.0.22000.0", + "compilerPath": "C:/mingw64/bin/gcc.exe", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "windows-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..646f1fa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,55 @@ +{ + "files.associations": { + "pthread.h": "c", + "fumotris.h": "c", + "string.h": "c", + "input.h": "c", + "stdint.h": "c", + "iso646.h": "c", + "win.h": "c", + "array": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "string_view": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "typeinfo": "cpp", + "execution": "cpp" + } +} \ No newline at end of file diff --git a/checksums.txt b/checksums.txt new file mode 100644 index 0000000..09411bb --- /dev/null +++ b/checksums.txt @@ -0,0 +1 @@ +{"source\\main.c": "3d2369e9333b52142cb9f019e877e434", "source\\io\\controller.c": "4459cc302f4b70b072f39b43e87a0c08", "source\\io\\input.c": "d3336a096a3ac2d792292d84dd20fb8f", "source\\io\\platforms\\win.c": "4eb14343a9cb40a7f6a9d9e43329c46e", "source\\io\\platforms\\winio.c": "b3a5e9d8f2e8ab4ec38d14007e4fc3f4"} \ No newline at end of file diff --git a/objects/controller.o b/objects/controller.o new file mode 100644 index 0000000000000000000000000000000000000000..6eb90111144d85f162b7bc1cf340dce29cc1305e GIT binary patch literal 2126 zcmbtV&ubGw6rRndnuD?t6e_6fp`ey_TWc-i4~W*Kj+=YYTw?i zaIJl@V*UWGu4wI$_!in=MZ5HBMLV~U<=V|ErhF9}cFPUD#2lGLTC?`nT-`XUF#gtD zrsZD@UP;y{30Ic1XH0um-D9p+%vHPk@biZYWe+dZG?0AGTvoiugJ!FPvw-oBUS33s zY}t&@H!|aMDeJ$Q;iZ4ImXMb6%zHzLsV%XdsH)&XJbXHUeLfm#Ay)ZMP4(MF69(yN z$cBdg2;B-H{T)L6MLL4J?P%ytz|=;hb;u!L(p`{az@!gDP63lX2{{W)`aI+!FzG9h z3@{Dr{re*VF*eZOe?Z?gXy*rpJBRyv_0s;nR9|oBwY1}QPNuV+MQfr%AHQbjTz!06 zKk8WN30FTemd{vDu|s$E?$-D0HF}J0f4^-2+aXalf_bZlFfk3Hg7r}hhD?^7R3G7`odwdRmntSWs;xj&Ik7VxN^n#`f%Z!;RZ?&XiKwDtEtecsR>YxQ8eXcf z;Lb@zl@@D9B}Ww(aTC&;lUzX}S9(j5yCKo6^xl@-U5V~VuaUKL7AzV+>x^Ai(o=cY znabrX$4CpFW;ziKh3QO1U;I7>EcdKEX?guhF%ibciu3_q_X>tAx)JK#CbJV3b#Rl} z2aEc-$xw}{o14rq7(K*LGV1JR4E@nle>a(1V5rNR%mXmg>w*csHX!k(^cx8O?FU~P z@~l;j;47p4DJJm3M7|($?F*RLu r?ex_EHEicIQ`bEL{o}6&luLm_>tHt)1rC-<*^`A-%0FLUuXn;<7A;wy literal 0 HcmV?d00001 diff --git a/objects/input.o b/objects/input.o new file mode 100644 index 0000000000000000000000000000000000000000..bcac4d2dd36e1d1ec9e91ebb128b59377fa7f86d GIT binary patch literal 2134 zcmZuyO=ufO6rT02QxQs6P4nY6ZP^qb++e9{Y)o20QQ|Hhw>5|)#yKQWrPV54V$168 zn8-df3bKk9vEWM}NB5LVdkt+UR89*?4*?Z&=rNZBg#=wl>ON?Wo5<`THON4x>i1nK=X(aV+JeOgakl!GfB&m8l z>o|jKVXlmRrQU$2z~Q1C_97%n63KY6-|rUq{np7(g-^Jj-Rq|T{|NlfLait`L4L_k zCiyZ@DL}9K_Dv<{sr!UhUTD;vi@Nh8wfPZR)1%bgp*0?{-Jq2}wM&0={1VjDcke>q z2)HzVZ@1gvzMEHH{?(%D@dq`14{*Ju9)}SuUH9> z@C%RKDfPySC>W^s(`XrJ>U!Ha)vA1Qt7Tky`Zl%zAlcNft9SJ4m0fA0&h~d>Ht)Y~ zw|U*v)r<6gbrZcop66-pCN8Mkpi-Sm4Z8DKre1jqIjKow{{Vmv>cy7&HtaRGJ@5nR z0rgT5NUY3b?KE%0hMW2dt?;cR=;=27jAPq-HVdZ}93vbzv?B^-?9IA=f8!1B1M6J~ z138JRw!}GY^@kNczR}f`g3h;&MM3-#1^I|wbIF{^weZ* z)v#G?*~rHnb8%eDu3Ae>%dTl>Y|~g|+PO?2XWGuVW>0*qeL4|85y!2#ybt;?424_* zeSo7M1N3u%?g2W&t&c!s1q#pcU5@rZzsHdR`5xsc2?(Pr6HnM~_3;WrX@Y@f@f%$~reSA&_{(=swwcML4baRa{{c1_e+d8p literal 0 HcmV?d00001 diff --git a/objects/main.o b/objects/main.o new file mode 100644 index 0000000000000000000000000000000000000000..b3ddca73e1456918cc70094fc57fab5234a8b077 GIT binary patch literal 4047 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 literal 0 HcmV?d00001 diff --git a/run.py b/run.py new file mode 100644 index 0000000..546fe20 --- /dev/null +++ b/run.py @@ -0,0 +1,94 @@ +import os, sys +import json, hashlib + + +def walk_source(path): + source_paths = [] + subdirs = [] + + for dirpath, dirnames, filenames in os.walk(path): + source_paths += [os.path.join(dirpath, f) for f in filenames if f.endswith(".c")] + subdirs.append(dirpath) + + return (source_paths, subdirs) + + +def get_checksums(source_files): + checksums = {} + + for path in source_files: + with open(path, "rb") as source_file: + source = source_file.read() + + checksum = hashlib.md5(source).hexdigest() + checksums[path] = checksum + + return checksums + + +def read_checksum_file(): + checksums = {} + + if not os.path.exists("checksums.txt"): + return checksums + + with open("checksums.txt", "rb") as checksum_file: + checksums = json.loads(checksum_file.read()) + + return checksums + + +def write_checksum_file(checksums): + with open("checksums.txt", "w+") as checksum_file: + checksum_file.write(json.dumps(checksums)) + + +def get_object_names(path): + object_names = [] + + for file in os.listdir(path): + if os.path.isfile(os.path.join(path, file)): + name = os.path.splitext(os.path.basename(file))[0] + + object_names.append(name) + + return object_names + + +def build(source_path, obj_path, out_path, recompile = False): + source_paths, subdirs = walk_source(source_path) + + if recompile: + result = os.system(f"gcc {' '.join(source_paths)} -I {' -I '.join(subdirs)} -o {out_path} -pthread -Wall -std=c17 -pedantic") + print(result) + return + + + checksums_before = read_checksum_file() + checksums_now = get_checksums(source_paths) + + object_names = get_object_names(obj_path) + compile_list = [] + + for path in checksums_now: + name = os.path.splitext(os.path.basename(path))[0] + + if path not in checksums_before or checksums_before[path] != checksums_now[path] or name not in object_names: + compile_list.append(path) + + if name in object_names: + object_names.remove(name) + + for object_name in object_names: + os.remove(f"{obj_path}\\{object_name}.o") + + for path in compile_list: + name = os.path.splitext(os.path.basename(path))[0] + + os.system(f"gcc -c {path} -I {' -I '.join(subdirs)} -o {obj_path}\\{name}.o -pthread -Wall -std=c17 -pedantic") + + write_checksum_file(checksums_now) + print(os.system(f"gcc {obj_path}\\*.o -o {out_path} -pthread -Wall -std=c17 -pedantic")) + + +build(sys.argv[1], sys.argv[2], sys.argv[3], True) \ No newline at end of file diff --git a/source/fumotris.h b/source/fumotris.h new file mode 100644 index 0000000..ad30993 --- /dev/null +++ b/source/fumotris.h @@ -0,0 +1,17 @@ +#pragma once +#include + +#define nullptr ((void*)0) + +typedef uint8_t u8; +typedef uint_fast8_t u8f; + +typedef uint16_t u16; +typedef uint_fast16_t u16f; + +typedef uint32_t u32; +typedef uint_fast32_t u32f; + +typedef uint64_t u64; +typedef uint_fast64_t u64f; + diff --git a/source/game/gametime.c b/source/game/gametime.c new file mode 100644 index 0000000..f9bc5df --- /dev/null +++ b/source/game/gametime.c @@ -0,0 +1,21 @@ +#include +#include + +#ifdef _WIN32 +#include "win.h" +#endif + +double GetTime() +{ + struct timespec ts; + timespec_get(&ts, TIME_UTC); + + return ts.tv_sec + (double)ts.tv_nsec / 1000000000.0; +} + +bool Wait(double seconds) +{ + #ifdef _WIN32 + return WindowsWait(seconds); + #endif +} \ No newline at end of file diff --git a/source/game/gametime.h b/source/game/gametime.h new file mode 100644 index 0000000..a660ebb --- /dev/null +++ b/source/game/gametime.h @@ -0,0 +1,6 @@ +#pragma once +#include + +double GetTime(); + +bool Wait(double seconds); \ No newline at end of file diff --git a/source/game/tetr.c b/source/game/tetr.c new file mode 100644 index 0000000..6fe475e --- /dev/null +++ b/source/game/tetr.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "fumotris.h" +#include "term.h" + +struct TetrMap { + size_t wid; + size_t hgt; + size_t area; + + int x; + int y; + u8 rot; + + u8 *blks; +}; + +struct TetrMap NewTetrMap(size_t wid, size_t hgt) +{ + return (struct TetrMap) { + wid, hgt, wid * hgt, + 0, 0, 0, + }; +} + +void TetrMapToTermBuf(struct TetrMap *map, struct TermBuf *term) +{ + static const u8f blk_colors[8] = { 8, 14, 11, 13, 10, 9, 12, 3 }; + + for (size_t y = 0; y < map->hgt; y++) { + for (size_t x = 0; x < map->wid; x++) { + size_t map_i = y * map->wid + x; + size_t buf_i = (y + map->y) * term->wid + (x + map->x) * 2; + + struct CharBlk4 *a = &term->blks[buf_i]; + struct CharBlk4 *b = &term->blks[buf_i + 1]; + + if (map->blks[map_i] == 0) { + a->ch = '('; + b->ch = ')'; + } else { + a->ch = '['; + b->ch = ']'; + } + + u8 fg = blk_colors[map->blks[map_i]]; + a->fg = fg; + b->fg = fg; + } + } +} + +bool TetrCollisionCheck(struct TetrMap *board, struct TetrMap *piece, int dx, int dy) +{ + size_t i = 0; + for (size_t y = piece->y + dy; y < piece->y + piece->hgt + dy; y++) { + for (size_t x = piece->x + dx; x < piece->x + piece->wid + dx; x++) { + if(piece->blks[i] == 0) + goto next; + + if(y >= board->hgt or x >= board->wid) + return false; + + size_t board_i = y * board->wid + x; + if(board->blks[board_i] != 0) + return false; + +next: + i++; + } + } + return true; +} \ No newline at end of file diff --git a/source/game/tetr.h b/source/game/tetr.h new file mode 100644 index 0000000..5efe26f --- /dev/null +++ b/source/game/tetr.h @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "fumotris.h" +#include "term.h" + +struct TetrMap { + size_t wid; + size_t hgt; + size_t area; + + int x; + int y; + u8 rot; + + u8 *blks; +}; + +struct TetrMap NewTetrMap(size_t wid, size_t hgt); + +void TetrMapToTermBuf(struct TetrMap *map, struct TermBuf *term); + +bool TetrCollisionCheck(struct TetrMap *board, struct TetrMap *piece, int dx, int dy); \ No newline at end of file diff --git a/source/io/ctrl.c b/source/io/ctrl.c new file mode 100644 index 0000000..83776c8 --- /dev/null +++ b/source/io/ctrl.c @@ -0,0 +1,265 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "fumotris.h" + +#define IO_BUF_SIZE 8 + +enum InputType { + KEY, + AXIS, + JOYSTICK, + WINDOW, + ESCAPE +}; + +struct InputRecord { + enum InputType type; + u16 id; + + union { + struct { + u32 value; + bool is_down; + } axis; + struct { + u32 x; + u32 y; + } joystick; + } data; + + double timestamp; +}; + +struct InputResult { + size_t count; + struct InputRecord buf[IO_BUF_SIZE]; +}; + +struct Axis { + union { + struct { + u32 value; + bool is_down; + } axis; + struct { + u32 x; + u32 y; + } joystick; + } data; + + double last_pressed; + double last_released; +}; + +enum KeyCode { + LEFT, + RIGHT, + SOFT_DROP, + HARD_DROP, + ROTATE_CCW, + ROTATE_CW, + ROTATE_180, + SWAP, + ESC +}; + +enum AxisCode { + VSCROLL, + HSCROLL +}; + +enum JoystickCode { + MOUSE +}; + +typedef u32 hashtype; +hashtype Hash(void *item, size_t size) +{ + u8* data = (u8*)item; + + u32 h = 98317; + for (size_t i = 0; i < size; i++) { + h ^= data[i]; + h *= 0x5bd1e995; + h ^= h >> 15; + } + + return h; +} + +struct ident { + u16 id; + enum InputType type; +}; + +hashtype hash_ident(u16f id, enum InputType type) +{ + struct ident obj = { id, type }; + return Hash(&obj, sizeof(struct ident)); +} + +struct ctrl_bkt { + hashtype bind_hash; + size_t index; + hashtype code_hash; + + struct Axis axis; +}; + +struct Ctrl { + size_t capacity; + size_t filled; + struct ctrl_bkt *bkts; + + pthread_mutex_t mutex; +}; +typedef struct Ctrl Ctrl; + +Ctrl NewCtrl(struct ctrl_bkt *bkts_prealloc, size_t capacity) +{ + Ctrl ctrl; + ctrl.capacity = capacity; + ctrl.filled = 0; + memset(bkts_prealloc, 0, sizeof(struct ctrl_bkt) * capacity); + ctrl.bkts = bkts_prealloc; + ctrl.mutex = PTHREAD_MUTEX_INITIALIZER; + return ctrl; +} + +struct ctrl_bkt *get_bkt(Ctrl *ctrl, size_t i) +{ + return &ctrl->bkts[i]; +} + +struct ctrl_bkt *find_bind(Ctrl *ctrl, hashtype bind_hash, hashtype search) +{ + size_t i = bind_hash % ctrl->capacity; + + struct ctrl_bkt *next; + for (size_t offset = 0; offset < ctrl->capacity; offset++) { + i = (i + 1) % ctrl->capacity; + + next = get_bkt(ctrl, i); + if (next->bind_hash != search) + continue; + + return next; + } + return nullptr; +} + +struct ctrl_bkt *find_code(Ctrl *ctrl, size_t *i, hashtype search) +{ + struct ctrl_bkt *next; + for (size_t offset = 0; offset < ctrl->capacity; offset++) { + *i = (*i + 1) % ctrl->capacity; + + next = get_bkt(ctrl, *i); + if (next->code_hash != search) + continue; + + return next; + } + return nullptr; +} + +bool CtrlMap(Ctrl *ctrl, u16f bind, u16f code, enum InputType type) +{ + if (ctrl->filled == ctrl->capacity) + return false; + + hashtype bind_hash = hash_ident(bind, type); + struct ctrl_bkt *bind_bkt = find_bind(ctrl, bind_hash, 0); + bind_bkt->bind_hash = bind_hash; + + hashtype code_hash = hash_ident(code, type); + size_t code_i = code_hash % ctrl->capacity; + struct ctrl_bkt *code_bkt = find_code(ctrl, &code_i, 0); + code_bkt->code_hash = code_hash; + + bind_bkt->index = code_i; + ctrl->filled += 1; + + return true; +} + +struct Axis *find_bind_axis(Ctrl *ctrl, u16f bind, enum InputType type) +{ + hashtype bind_hash = hash_ident(bind, KEY); + struct ctrl_bkt *bind_bkt = find_bind(ctrl, bind_hash, bind_hash); + if (bind_bkt == nullptr) + return nullptr; + + return &get_bkt(ctrl, bind_bkt->index)->axis; +} + +struct Axis *CtrlGet(Ctrl *ctrl, u16f code, enum InputType type) +{ + hashtype code_hash = hash_ident(code, type); + size_t code_i = code_hash % ctrl->capacity; + struct ctrl_bkt *code_bkt = find_code(ctrl, &code_i, code_hash); + if (code_bkt == nullptr) + return nullptr; + + return &code_bkt->axis; +} + +bool CtrlUpdateKey(Ctrl *ctrl, struct InputRecord *record) +{ + struct Axis *axis = find_bind_axis(ctrl, record->id, KEY); + if (axis == nullptr) + return false; + + if (record->data.axis.is_down) { + axis->last_pressed = record->timestamp; + } else { + axis->last_released = record->timestamp; + } + axis->data.axis.is_down = record->data.axis.is_down; + + return true; +} + +bool CtrlUpdateAxis(Ctrl *ctrl, struct InputRecord *record) +{ + struct Axis *axis = find_bind_axis(ctrl, record->id, AXIS); + if (axis == nullptr) + return false; + + axis->data.axis.value = record->data.axis.value; + axis->last_pressed = record->timestamp; + + return true; +} + +bool CtrlUpdateJoystick(Ctrl *ctrl, struct InputRecord *record) +{ + struct Axis *axis = find_bind_axis(ctrl, record->id, JOYSTICK); + if (axis == nullptr) + return false; + + axis->data.joystick.x = record->data.joystick.x; + axis->data.joystick.y = record->data.joystick.y; + axis->last_pressed = record->timestamp; + + return true; +} + +bool CtrlUpdateWindow(Ctrl *ctrl, struct InputRecord *record) +{ + struct Axis *axis = find_bind_axis(ctrl, record->id, WINDOW); + if (axis == nullptr) + return false; + + axis->data.joystick.x = record->data.joystick.x; + axis->data.joystick.y = record->data.joystick.y; + axis->last_pressed = record->timestamp; + + return true; +} \ No newline at end of file diff --git a/source/io/ctrl.h b/source/io/ctrl.h new file mode 100644 index 0000000..f6a8425 --- /dev/null +++ b/source/io/ctrl.h @@ -0,0 +1,114 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "fumotris.h" + +#define IO_BUF_SIZE 8 + +enum InputType { + KEY, + AXIS, + JOYSTICK, + WINDOW, + ESCAPE +}; + +struct InputRecord { + enum InputType type; + u16 id; + + union { + struct { + u32 value; + bool is_down; + } axis; + struct { + u32 x; + u32 y; + } joystick; + } data; + + double timestamp; +}; + +struct InputResult { + size_t count; + struct InputRecord buf[IO_BUF_SIZE]; +}; + +struct Axis { + union { + struct { + u32 value; + bool is_down; + } axis; + struct { + u32 x; + u32 y; + } joystick; + } data; + + double last_pressed; + double last_released; +}; + +enum KeyCode { + LEFT, + RIGHT, + SOFT_DROP, + HARD_DROP, + ROTATE_CCW, + ROTATE_CW, + ROTATE_180, + SWAP, + ESC +}; + +enum AxisCode { + VSCROLL, + HSCROLL +}; + +enum JoystickCode { + MOUSE +}; + +typedef u32 hashtype; + +struct ctrl_bkt { + hashtype bind_hash; + size_t index; + hashtype code_hash; + + struct Axis axis; +}; + +struct Ctrl { + size_t capacity; + size_t filled; + struct ctrl_bkt *bkts; + + pthread_mutex_t mutex; +}; +typedef struct Ctrl Ctrl; + +Ctrl NewCtrl(struct ctrl_bkt *bkts_prealloc, size_t capacity); + +bool CtrlMap(Ctrl *ctrl, u16f bind, u16f code, enum InputType type); + +struct Axis *CtrlGet(Ctrl *ctrl, u16f code, enum InputType type); + +bool CtrlUpdateKey(Ctrl *ctrl, struct InputRecord *record); + +bool CtrlUpdateAxis(Ctrl *ctrl, struct InputRecord *record); + +bool CtrlUpdateJoystick(Ctrl *ctrl, struct InputRecord *record); + +bool CtrlUpdateWindow(Ctrl *ctrl, struct InputRecord *record); + diff --git a/source/io/input.c b/source/io/input.c new file mode 100644 index 0000000..1d56aad --- /dev/null +++ b/source/io/input.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include + +#include "ctrl.h" +#include "fumotris.h" +#include "gametime.h" + +#ifdef _WIN32 +#include "win.h" +#endif + +bool dispatch(Ctrl *ctrl, struct InputRecord *record) +{ + switch (record->type) { + case KEY: + return CtrlUpdateKey(ctrl, record); + case AXIS: + return CtrlUpdateAxis(ctrl, record); + case JOYSTICK: + return CtrlUpdateJoystick(ctrl, record); + case WINDOW: + return CtrlUpdateWindow(ctrl, record); + case ESCAPE: + default: + return false; + } +} + +bool write_result(Ctrl *ctrl, struct InputResult *result) +{ + double now = GetTime(); + pthread_mutex_lock(&ctrl->mutex); + + for (size_t i = 0; i < result->count; i++) { + if (result->buf[i].type == ESCAPE) + return false; + result->buf[i].timestamp = now; + + dispatch(ctrl, &result->buf[i]); + } + + pthread_mutex_unlock(&ctrl->mutex); + return true; +} + +void *block_input(void *args) +{ + Ctrl *ctrl = args; + struct InputResult result; + +input_loop: + bool success; + #ifdef _WIN32 + success = WindowsBlockInput(&result); + #endif + + if (!success) { + exit(1); + } + + if (!write_result(ctrl, &result)) { + return nullptr; + } + + goto input_loop; + + return nullptr; +} + +void StartInput(Ctrl *ctrl) +{ + pthread_t input_thread; + pthread_create(&input_thread, nullptr, block_input, ctrl); + //pthread_join(input_thread, nullptr); +} \ No newline at end of file diff --git a/source/io/input.h b/source/io/input.h new file mode 100644 index 0000000..1688a2c --- /dev/null +++ b/source/io/input.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "ctrl.h" +#include "fumotris.h" + +void StartInput(Ctrl *ctrl); \ No newline at end of file diff --git a/source/io/platforms/win.c b/source/io/platforms/win.c new file mode 100644 index 0000000..efb36cd --- /dev/null +++ b/source/io/platforms/win.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include + +#include "input.h" +#include "winhandler.h" + +bool WindowsInit() +{ + if (!WinInitInputHandle()) + return false; + + if (!WinInitTimer()) + return false; + + if(!WinInitConsole()) + return false; + + return true; +} + +bool WindowsBlockInput(struct InputResult *result) +{ + return WinBlockInput(result); +} + +bool WindowsWait(double seconds) +{ + return WinWait(seconds); +} \ No newline at end of file diff --git a/source/io/platforms/win.h b/source/io/platforms/win.h new file mode 100644 index 0000000..7a89e9a --- /dev/null +++ b/source/io/platforms/win.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "input.h" + +bool WindowsInit(); + +bool WindowsBlockInput(struct InputResult *result); + +bool WindowsWait(double seconds); \ No newline at end of file diff --git a/source/io/platforms/winhandler.c b/source/io/platforms/winhandler.c new file mode 100644 index 0000000..f805b1c --- /dev/null +++ b/source/io/platforms/winhandler.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "fumotris.h" +#include "input.h" + +struct Windows { + HANDLE input_handle; + HANDLE timer; +}; +static struct Windows windows; + +bool WinInitInputHandle() +{ + windows.input_handle = GetStdHandle(STD_INPUT_HANDLE); + if (windows.input_handle == INVALID_HANDLE_VALUE) + return false; + return true; +} + +bool WinInitTimer() +{ + windows.timer = CreateWaitableTimer(NULL, TRUE, NULL); + if (!windows.timer) + return false; + return true; +} + +bool WinInitConsole() +{ + DWORD mode = ENABLE_EXTENDED_FLAGS + | ENABLE_PROCESSED_INPUT + | ENABLE_MOUSE_INPUT + | ENABLE_WINDOW_INPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING; + return SetConsoleMode(windows.input_handle, mode); +} + +void set_key_record(struct InputRecord *record, KEY_EVENT_RECORD win_key) +{ + record->type = KEY; + record->id = win_key.wVirtualKeyCode; + record->data.axis.is_down = win_key.bKeyDown; + + if (win_key.wVirtualKeyCode == VK_ESCAPE) + record->type = ESCAPE; +} + +bool set_mouse_record(struct InputRecord *record, MOUSE_EVENT_RECORD win_mouse) +{ + switch (win_mouse.dwEventFlags) { + case MOUSE_WHEELED: + record->type = AXIS; + record->id = 0; + record->data.axis.value = win_mouse.dwButtonState; + break; + case MOUSE_HWHEELED: + record->type = AXIS; + record->id = 1; + record->data.axis.value = win_mouse.dwButtonState; + break; + case MOUSE_MOVED: + record->type = JOYSTICK; + record->id = 0; + record->data.joystick.x = win_mouse.dwMousePosition.X; + record->data.joystick.y = win_mouse.dwMousePosition.Y; + break; + default: + return false; + } + return true; +} + +void set_window_record(struct InputRecord *record, WINDOW_BUFFER_SIZE_RECORD win_resize) +{ + record->type = WINDOW; + record->data.joystick.x = win_resize.dwSize.X; + record->data.joystick.y = win_resize.dwSize.Y; +} + +bool dispatch_record(struct InputRecord *record, INPUT_RECORD win_record) +{ + switch (win_record.EventType) { + case KEY_EVENT: + set_key_record(record, win_record.Event.KeyEvent); + return true; + case MOUSE_EVENT: + return set_mouse_record(record, win_record.Event.MouseEvent); + case WINDOW_BUFFER_SIZE_EVENT: + set_window_record(record, win_record.Event.WindowBufferSizeEvent); + return true; + default: + record->type = ESCAPE; + } + return true; +} + +bool WinBlockInput(struct InputResult *result) +{ + INPUT_RECORD buf[8]; + DWORD count; + + if (!ReadConsoleInput(windows.input_handle, buf, 8, &count)) + return false; + + size_t unused_offset = 0; + for (size_t i = 0; i < count; i++) { + struct InputRecord record; + + bool include = dispatch_record(&record, buf[i]); + if (record.type == ESCAPE) + return false; + if (!include) + unused_offset += 1; + + result->buf[i - unused_offset] = record; + } + result->count = count - unused_offset; + return true; +} + +bool WinWait(double seconds) +{ + LARGE_INTEGER duration; + duration.QuadPart = (u64)(-10000000.0 * seconds); + + if (!SetWaitableTimer(windows.timer, &duration, 0, NULL, NULL, FALSE)) + return false; + + DWORD result = WaitForSingleObject(windows.timer, INFINITE); + if (result != WAIT_OBJECT_0) + return false; + + return true; +} \ No newline at end of file diff --git a/source/io/platforms/winhandler.h b/source/io/platforms/winhandler.h new file mode 100644 index 0000000..25797d0 --- /dev/null +++ b/source/io/platforms/winhandler.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "fumotris.h" + +bool WinInitInputHandle(); + +bool WinInitTimer(); + +bool WinInitConsole(); + +bool WinBlockInput(struct InputResult *result); + +bool WinWait(double seconds); \ No newline at end of file diff --git a/source/io/term.c b/source/io/term.c new file mode 100644 index 0000000..2871b56 --- /dev/null +++ b/source/io/term.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "fumotris.h" + +struct CharBlk4 { + char ch; + u8 bg : 4; + u8 fg : 4; +}; + +struct TermBuf { + size_t wid; + size_t hgt; + size_t area; + struct CharBlk4 *blks; +}; + +struct TermBuf NewTermBuf(size_t wid, size_t hgt) +{ + return (struct TermBuf) { + wid, hgt, wid * hgt + }; +} + +size_t TermMaxChars(struct TermBuf *term) +{ + static const size_t max_color_str_len = 10; + static const size_t reset_str_len = 7; + + return reset_str_len + + (max_color_str_len + 1) * term->area + + (term->hgt - 1) + + 1; +} + +size_t printcol4(char *buf, size_t at, size_t max, u8f col, char ch) +{ + if (col < 8) + col += 30; + else + col += 82; + + return snprintf(buf + at, max - at, "\x1b[%um%c", col, ch); +} + +size_t printblk4(char *buf, size_t at, size_t max, struct CharBlk4 *blk) +{ + u8f bg; + if (blk->bg < 8) + bg = blk->bg + 40; + else + bg = blk->bg + 92; + + u8f fg; + if (blk->fg < 8) + fg = blk->fg + 30; + else + fg = blk->fg + 82; + + return snprintf(buf + at, max - at, "\x1b[%u;%um%c", bg, fg, blk->ch); +} + +size_t TermBufToChars(struct TermBuf *term, char *buf, size_t max_chars) +{ + u8f last_bg = 0; + u8f last_fg = 0; + + size_t filled = snprintf(buf, max_chars, "\x1b[H\x1b[0m"); + + for(size_t y = 0; y < term->hgt; y++) { + for(size_t x = 0; x < term->wid; x++) { + size_t i = y * term->wid + x; + struct CharBlk4 *blk = &term->blks[i]; + + // DEBUG + if (blk->ch == 0) + blk->ch = '#'; + // DEBUG + + if (blk->bg != 0 and blk->bg != last_bg) { + last_bg = blk->bg; + if (blk->fg != 0 and blk->fg != last_fg) { + filled += printblk4(buf, filled, max_chars, blk); + last_fg = blk->fg; + } else { + filled += printcol4(buf, filled, max_chars, blk->bg, blk->ch); + } + } else if (blk->fg != 0 and blk->fg != last_fg) { + filled += printcol4(buf, filled, max_chars, blk->fg, blk->ch); + last_fg = blk->fg; + } else { + buf[filled] = blk->ch; + filled += 1; + } + } + buf[filled] = '\n'; + filled += 1; + } + buf[filled] = 0; + return filled; +} + +/*int main() +{ + struct TermBuf term; + term.wid = 20; + term.hgt = 10; + term.area = 20 * 10; + struct CharBlk4 blks[term.area]; + memset(&blks, 0, sizeof(struct CharBlk4) * term.area); + term.blks = blks; + + size_t out_max = TermMaxChars(&term); + char out[out_max]; + memset(out, 0, out_max); + + TermBufToChars(&term, out, out_max); + + puts(out); + + return 0; +}*/ \ No newline at end of file diff --git a/source/io/term.h b/source/io/term.h new file mode 100644 index 0000000..8892b03 --- /dev/null +++ b/source/io/term.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "fumotris.h" + +struct CharBlk4 { + char ch; + u8 bg : 4; + u8 fg : 4; +}; + +struct TermBuf { + size_t wid; + size_t hgt; + size_t area; + struct CharBlk4 *blks; +}; + +struct TermBuf NewTermBuf(size_t wid, size_t hgt); + +size_t TermMaxChars(struct TermBuf *term); + +size_t TermBufToChars(struct TermBuf *term, char *buf, size_t max_chars); \ No newline at end of file diff --git a/source/main.c b/source/main.c new file mode 100644 index 0000000..4edf4da --- /dev/null +++ b/source/main.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "control.h" +#include "fumotris.h" +#include "term.h" +#include "tetr.h" + +#ifdef _WIN32 +#include "win.h" +#endif + +const enum KeyCode key_codes[] = { + LEFT, + RIGHT, + SOFT_DROP, + HARD_DROP, + ROTATE_CCW, + ROTATE_CW, + ROTATE_180, + SWAP, + ESC +}; +const u16f key_binds[] = { + 0x25, + 0x27, + 0x28, + 0x20, + 'Z', + 'X', + 'A', + 'C', + 0x1B +}; + +const enum AxisCode axis_codes[] = { + VSCROLL, + HSCROLL +}; +const u16f axis_binds[] = { 0, 1 }; + +u8 I[16] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 1, 1, 1, 1, + 0, 0, 0, 0 +}; + +void Loop(Ctrl *ctrl) +{ + struct TermBuf term = NewTermBuf(20, 20); + struct CharBlk4 term_blks[term.area]; + memset(term_blks, 0, sizeof(struct CharBlk4) * term.area); + term.blks = term_blks; + + size_t out_max = TermMaxChars(&term); + char out[out_max]; + memset(out, 0, out_max); + + struct TetrMap board = NewTetrMap(10, 20); + u8 board_blks[board.area]; + memset(board_blks, 0, board.area); + board.blks = board_blks; + + struct TetrMap falling = NewTetrMap(4, 4); + u8 falling_blks[falling.area]; + memcpy(falling_blks, I, falling.area); + falling.blks = falling_blks; + + for (int i = 0; i < 7779997; i++) { + TetrMapToTermBuf(&board, &term); + TetrMapToTermBuf(&falling, &term); + + size_t size = TermBufToChars(&term, out, out_max); + //puts(out); + puts("\x1b[H"); + + //falling.y += 1; + + for(int j = 0; j < ctrl->capacity; j++) { + printf("val:%u, is_down:%u, x:%u, y:%u, press:%f, relse:%f\n", + ctrl->bkts[j].axis.data.axis.value, ctrl->bkts[j].axis.data.axis.is_down, + ctrl->bkts[j].axis.data.joystick.x, ctrl->bkts[j].axis.data.joystick.y, + ctrl->bkts[j].axis.last_pressed, ctrl->bkts[j].axis.last_released); + } + /* + struct Axis *axis = CtrlGet(ctrl, LEFT, KEY); + struct Axis *right = CtrlGet(ctrl, RIGHT, KEY); + + printf("ctrl:%u\n", axis); + printf("ctrl:%u\n", right); + + printf("%f\n", axis->last_pressed); + + if (axis->data.axis.is_down) { + printf("left"); + falling.x -= 1; + } + + if (right->data.axis.is_down) { + printf("right"); + falling.x += 1; + }*/ + + WindowsWait(0.1); + } +} + +int main() +{ + WindowsInit(); + + struct ctrl_bkt bkts[16]; + Ctrl ctrl = NewCtrl(bkts, 16); + + for (size_t i = 0; i < 9; i++) { + CtrlMap(&ctrl, key_binds[i], key_codes[i], KEY); + } + for (size_t i = 0; i < 2; i++) { + CtrlMap(&ctrl, axis_codes[i], axis_binds[i], AXIS); + } + CtrlMap(&ctrl, 0, MOUSE, JOYSTICK); + CtrlMap(&ctrl, 0, 0, WINDOW); + + printf("set controls\n"); + + StartInput(&ctrl); + Loop(&ctrl); + + return 0; +} \ No newline at end of file diff --git a/source/test.cpp b/source/test.cpp new file mode 100644 index 0000000..15d0df3 --- /dev/null +++ b/source/test.cpp @@ -0,0 +1,422 @@ +#include +#include +#include +using namespace std; + +void gotoxy(int x, int y); +void setcolor(WORD color); +void setForeGroundAndBackGroundColor(int ForeGroundColor,int BackGroundColor); +void clearscreen(); +void drawpixel( unsigned char x, unsigned char y, unsigned char Color); +void drawpixel2( unsigned char x, unsigned char y, unsigned char Color, char character); +void drawcircle(int x, int y, int a, int b, int color); +void drawline(int x0, int y0, int x1, int y1, int color); +void drawfilledrectangle(unsigned char x1,unsigned char y1,unsigned char x2,unsigned char y2,unsigned char bkcol); +void drawframe(unsigned x,unsigned y,unsigned sx,unsigned sy,unsigned char col,unsigned char col2,char text_[]); +void drawwindow(unsigned x,unsigned y,unsigned sx,unsigned sy,unsigned char col,unsigned char col2,unsigned char bkcol,char text_[]); + +int main() +{ + gotoxy(1,23); + setcolor(7); + clearscreen(); + + cout<<"click anywhere in console window to write - hello world -\n\n\n\n\n\n\n\n\n\n\n\n\n" + "Press Ctrl+C to Exit"; + + HANDLE hout= GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); + INPUT_RECORD InputRecord; + DWORD Events; + COORD coord; + CONSOLE_CURSOR_INFO cci; + cci.dwSize = 25; + cci.bVisible = FALSE; + SetConsoleCursorInfo(hout, &cci); + SetConsoleMode(hin, ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT); + bool EXITGAME = false; + int buttonX=1, buttonY=1; + + drawpixel(buttonX,buttonY ,1); + gotoxy(buttonX+2,buttonY); + setcolor(3); + cout<<"<----- a button \n"; + + + while( !EXITGAME ) + { + + ReadConsoleInput(hin, &InputRecord, 1, &Events); + + + switch ( InputRecord.EventType ){ + case KEY_EVENT: // keyboard input + + + switch (InputRecord.Event.KeyEvent.wVirtualKeyCode) + { + case VK_ESCAPE: + EXITGAME = TRUE; + break; + + case VK_SPACE: + + break; + + + case VK_RETURN: + + break; + + case VK_LEFT: + // left key move player left + cout<<"VK_LEFT = "<= 0) { + ya -= asq * 2; + thresh -= ya; + wy--; + } + + xa += bsq * 2; + wx++; + + if (xa >= ya) + break; + + + drawpixel(x+wx, y-wy, color); + drawpixel(x-wx, y-wy, color); + drawpixel(x+wx, y+wy, color); + drawpixel(x-wx, y+wy, color); + } + + drawpixel(x+a, y, color); + drawpixel(x-a, y, color); + + wx = a; + wy = 0; + xa = bsq * 2 * a; + + ya = 0; + thresh = bsq / 4 - bsq * a; + + for (;;) { + thresh += ya + asq; + + if (thresh >= 0) { + xa -= bsq * 2; + thresh = thresh - xa; + wx--; + } + + ya += asq * 2; + wy++; + + if (ya > xa) + break; + + drawpixel(x+wx, y-wy, color); + drawpixel(x-wx, y-wy, color); + drawpixel(x+wx, y+wy, color); + drawpixel(x-wx, y+wy, color); + } +} + +//***************************************************************************** + +void drawline(int x0, int y0, int x1, int y1, int color){ + int pix = color; + int dy = y1 - y0; + int dx = x1 - x0; + int stepx, stepy; + + if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; } + if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; } + dy <<= 1; // dy is now 2*dy + dx <<= 1; // dx is now 2*dx + + drawpixel( x0, y0,pix); + if (dx > dy) { + int fraction = dy - (dx >> 1); // same as 2*dy - dx + while (x0 != x1) { + if (fraction >= 0) { + y0 += stepy; + fraction -= dx; // same as fraction -= 2*dx + } + x0 += stepx; + fraction += dy; // same as fraction -= 2*dy + drawpixel( x0, y0,pix); + } + } else { + int fraction = dx - (dy >> 1); + while (y0 != y1) { + if (fraction >= 0) { + x0 += stepx; + fraction -= dy; + } + y0 += stepy; + fraction += dx; + drawpixel( x0, y0,pix); + } + } +} + +//***************************************************************************** + + +void drawframe(unsigned x,unsigned y,unsigned sx,unsigned sy,unsigned char col,unsigned char col2,char text_[]){ + unsigned i,j,m;{ + + m=(sx-x); //differential + j=m/8; //adjust + j=j-1; //more adjustment + gotoxy(x,y);printf("É"); //Top left corner of drawframe + gotoxy(sx,y);printf("»"); //Top right corner of drawframe + gotoxy(x,sy);printf("È"); //Bottom left corner of drawframe + gotoxy(sx,sy);printf("¼"); //Bottom right corner of drawframe + + for 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 z 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