diff options
-rw-r--r-- | .github/workflows/c-cpp.yml | 25 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | build.zig | 93 | ||||
-rw-r--r-- | build.zig.zon | 72 | ||||
-rw-r--r-- | configure.ac | 16 | ||||
-rw-r--r-- | src/Makefile.am | 13 | ||||
-rw-r--r-- | src/actions.zig | 12 | ||||
-rw-r--r-- | src/cavegen.c | 103 | ||||
-rw-r--r-- | src/cavegen.h | 37 | ||||
-rw-r--r-- | src/common.h | 20 | ||||
-rw-r--r-- | src/display.c | 223 | ||||
-rw-r--r-- | src/display.h | 54 | ||||
-rw-r--r-- | src/ecs/entity.zig | 3 | ||||
-rw-r--r-- | src/entity.h | 24 | ||||
-rw-r--r-- | src/frontend/ncurses.zig | 186 | ||||
-rw-r--r-- | src/ht.c | 235 | ||||
-rw-r--r-- | src/ht.h | 40 | ||||
-rw-r--r-- | src/main.c | 192 | ||||
-rw-r--r-- | src/main.zig | 21 | ||||
-rw-r--r-- | src/root.zig | 10 |
21 files changed, 399 insertions, 983 deletions
diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml deleted file mode 100644 index 3342e3d..0000000 --- a/.github/workflows/c-cpp.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: C/C++ CI - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: autoreconf - run: autoreconf --install - - name: configure - run: ./configure - - name: make - run: make - - name: make check - run: make check - - name: make distcheck - run: make distcheck @@ -19,3 +19,5 @@ Makefile /stamp-h1 /*.gz /src/urlg +/.zig-cache/ +/zig-out/ diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index af437a6..0000000 --- a/Makefile.am +++ /dev/null @@ -1 +0,0 @@ -SUBDIRS = src diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..0f62653 --- /dev/null +++ b/build.zig @@ -0,0 +1,93 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const lib = b.addStaticLibrary(.{ + .name = "simple-lib-use", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + // This declares intent for the library to be installed into the standard + // location when the user invokes the "install" step (the default step when + // running `zig build`). + b.installArtifact(lib); + + const exe = b.addExecutable(.{ + .name = "simple-lib-use", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + exe.linkSystemLibrary("c"); + exe.linkSystemLibrary("ncurses"); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const lib_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const exe_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..cf22c40 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,72 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save <url>`, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = "simple-lib-use", + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save <url>` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 5ded62c..0000000 --- a/configure.ac +++ /dev/null @@ -1,16 +0,0 @@ -AC_PREREQ([2.65]) -AC_INIT([urlg], [0.0.1-alpha], [jacob.a.s.janzen@gmail.com]) -AC_CONFIG_SRCDIR([src/main.c]) -AC_CONFIG_AUX_DIR([build-aux]) - -AC_PROG_CC -AC_CHECK_LIB([ncurses], [initscr]) -AC_CHECK_LIB([tinfo], [stdscr]) -AC_CHECK_HEADERS([ncurses.h]) - -AM_INIT_AUTOMAKE([foreign -Wall -Werror]) - -AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([Makefile src/Makefile]) - -AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index f08ba63..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,13 +0,0 @@ -bin_PROGRAMS = urlg - -urlg_SOURCES = \ - cavegen.c \ - cavegen.h \ - display.c \ - display.h \ - ht.c \ - ht.h \ - common.h \ - entity.h \ - main.c - diff --git a/src/actions.zig b/src/actions.zig new file mode 100644 index 0000000..a801e28 --- /dev/null +++ b/src/actions.zig @@ -0,0 +1,12 @@ +const std = @import("std"); + +pub const Action = enum { + illegal, + exit, + move_up, + move_down, + move_left, + move_right, + down_stair, + up_stair, +}; diff --git a/src/cavegen.c b/src/cavegen.c deleted file mode 100644 index 8a74836..0000000 --- a/src/cavegen.c +++ /dev/null @@ -1,103 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#include "cavegen.h" - -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> - -#define MAX_STEPS 200 -#define NUM_WALKERS 100 - -enum direction { - UP, - LEFT, - RIGHT, - DOWN, - NUM_DIRS, -}; - -void create_cave(struct map *map) -{ - map->width = WIDTH; - map->height = HEIGHT; - - // create a map consisting entirely of walls - map->map = malloc(sizeof(enum tile_type) * HEIGHT * WIDTH); - for (int i = 0; i < HEIGHT; ++i) { - for (int j = 0; j < WIDTH; ++j) { - map->map[i * WIDTH + j] = WALL; - } - } - - // start in the middle of the screen - int start_x = WIDTH / 2; - int start_y = HEIGHT / 2; - - // make the starting point GROUND - map->map[start_y * WIDTH + start_x] = GROUND; - - // setup the open tiles - struct point *open_tiles = malloc(sizeof(struct point) * HEIGHT * WIDTH); - - int num_open_tiles = 1; - open_tiles[0].x = start_x; - open_tiles[0].y = start_y; - - for (int i = 0; i < NUM_WALKERS; ++i) { - // get a random open point - struct point curr_point = open_tiles[rand() % num_open_tiles]; - - int x_pos = curr_point.x; - int y_pos = curr_point.y; - - // iterate until the walk exits the array or MAX_STEPS is reached - for (int j = 1; j < MAX_STEPS - 1 && x_pos < WIDTH - 1 && x_pos >= 1 && - y_pos < HEIGHT - 1 && y_pos >= 1; - ++j) { - // add new open point if the current point is still a wall - if (map->map[y_pos * WIDTH + x_pos] == WALL) { - open_tiles[num_open_tiles].x = x_pos; - open_tiles[num_open_tiles].y = y_pos; - ++num_open_tiles; - } - - map->map[y_pos * WIDTH + x_pos] = GROUND; // assign ground - - // move in a random direction - enum direction dir = rand() % NUM_DIRS; - switch (dir) { - case UP : --y_pos; break; - case LEFT : --x_pos; break; - case RIGHT : ++x_pos; break; - case DOWN : ++y_pos; break; - default : exit(EXIT_FAILURE); // should not occur - } - } - } - - // assign the up stair and remove from open tiles - int in = rand() % num_open_tiles; - map->entry_point = open_tiles[in]; - open_tiles[in] = open_tiles[num_open_tiles - 1]; - --num_open_tiles; - map->map[map->entry_point.y * WIDTH + map->entry_point.x] = UP_STAIR; - - // assign the down stair and remove from open tiles - in = rand() % num_open_tiles; - struct point down = open_tiles[in]; - open_tiles[in] = open_tiles[num_open_tiles - 1]; - --num_open_tiles; - map->map[down.y * WIDTH + down.x] = DOWN_STAIR; - - free(open_tiles); -} diff --git a/src/cavegen.h b/src/cavegen.h deleted file mode 100644 index ae5f698..0000000 --- a/src/cavegen.h +++ /dev/null @@ -1,37 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#ifndef CAVEGEN_H_ -#define CAVEGEN_H_ - -#include "common.h" - -#define HEIGHT 100 -#define WIDTH 180 - -enum tile_type { - WALL, - GROUND, - UP_STAIR, - DOWN_STAIR, -}; - -struct map { - enum tile_type *map; - struct point entry_point; - - int width; - int height; -}; - -void create_cave(struct map *map); - -#endif // CAVEGEN_H_ diff --git a/src/common.h b/src/common.h deleted file mode 100644 index c36f0f2..0000000 --- a/src/common.h +++ /dev/null @@ -1,20 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#ifndef COMMON_H_ -#define COMMON_H_ - -struct point { - int x; - int y; -}; - -#endif // COMMON_H_ diff --git a/src/display.c b/src/display.c deleted file mode 100644 index e79ed2c..0000000 --- a/src/display.c +++ /dev/null @@ -1,223 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#include "display.h" - -#include <curses.h> -#include <locale.h> -#include <stdlib.h> -#include <string.h> - -#include "entity.h" - -struct windows { - WINDOW *main; - WINDOW *inst; - WINDOW *msgs; - WINDOW *stat; -}; - -static WINDOW *create_newwin(int height, int width, int y, int x) -{ - WINDOW *local_win = newwin(height, width, y, x); - box(local_win, 0, 0); - wrefresh(local_win); - - return local_win; -} - -static display_t *create_windows(void) -{ - display_t *wins = malloc(sizeof(display_t)); - - wins->inst = create_newwin( - INSTRUCTION_PANEL_HEIGHT, INSTRUCTION_PANEL_WIDTH, - (LINES - INSTRUCTION_PANEL_HEIGHT - STATUS_PANEL_HEIGHT) / 2 + 1, - (COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH) / 2 + - MAIN_PANEL_WIDTH - 1 - ); - wins->msgs = create_newwin( - MESSAGE_PANEL_HEIGHT, MESSAGE_PANEL_WIDTH, - (LINES - MAIN_PANEL_HEIGHT - MESSAGE_PANEL_HEIGHT) / 2 + - MAIN_PANEL_HEIGHT, - (COLS - MESSAGE_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH) / 2 - ); - wins->stat = create_newwin( - STATUS_PANEL_HEIGHT, STATUS_PANEL_WIDTH, - (LINES - INSTRUCTION_PANEL_HEIGHT - STATUS_PANEL_HEIGHT) / 2 + - INSTRUCTION_PANEL_HEIGHT, - (COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH) / 2 + - MAIN_PANEL_WIDTH - 1 - ); - wins->main = create_newwin( - MAIN_PANEL_HEIGHT, MAIN_PANEL_WIDTH, - (LINES - MAIN_PANEL_HEIGHT - MESSAGE_PANEL_HEIGHT) / 2 + 1, - (COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH) / 2 - ); - - return wins; -} - -display_t *display_init(void) -{ - setlocale(LC_ALL, ""); // allow extended ASCII - - initscr(); // initialize curses - - // exit on unsupported consoles - if (LINES < MAIN_PANEL_HEIGHT + MESSAGE_PANEL_HEIGHT || - COLS < MAIN_PANEL_WIDTH + INSTRUCTION_PANEL_WIDTH || !has_colors()) { - endwin(); - fprintf( - stderr, - "a color terminal is required with at least %dx%d characters\n", - INSTRUCTION_PANEL_WIDTH + MAIN_PANEL_WIDTH, - MAIN_PANEL_HEIGHT + MESSAGE_PANEL_HEIGHT - ); - return NULL; - } - - // configure curses if startup was successful - raw(); // disable line buffering - keypad(stdscr, TRUE); // enable reading function keys - noecho(); // don't print input - curs_set(0); // disable the cursor - start_color(); // enable colours - - // setup colours - init_pair(1, COLOR_WHITE, COLOR_BLACK); - init_pair(2, COLOR_BLACK, COLOR_RED); - wattron(stdscr, COLOR_PAIR(1)); - refresh(); - - return create_windows(); -} - -void display_destroy(display_t *disp) -{ - delwin(disp->main); - delwin(disp->inst); - delwin(disp->msgs); - delwin(disp->stat); - - free(disp); -} - -void display_map(display_t *disp, struct map *map, ht_t *entities) -{ - // print map - struct entity *camera = ht_find(entities, "camera"); - - for (int i = 1; i < MAIN_PANEL_HEIGHT - 1; ++i) { - for (int j = 1; j < MAIN_PANEL_WIDTH - 1; ++j) { - int map_i = i - 1 + camera->p.y; - int map_j = j - 1 + camera->p.x; - - if (map_i > map->height || map_j > map->width || map_i < 0 || - map_j < 0) { - mvwaddch(disp->main, i, j, ' '); - } else { - switch (map->map[map_i * map->width + map_j]) { - case GROUND : mvwaddch(disp->main, i, j, '.'); break; - case UP_STAIR : mvwaddch(disp->main, i, j, '<'); break; - case DOWN_STAIR : - wattron(disp->main, COLOR_PAIR(2)); - mvwaddch(disp->main, i, j, '>'); - wattroff(disp->main, COLOR_PAIR(2)); - break; - case WALL : - if (map_i > 0 && - map->map[(map_i - 1) * map->width + map_j] != WALL) { - mvwaddch(disp->main, i, j, ACS_BLOCK); - } else if (map_i < map->width - 1 && map->map[(map_i + 1) * map->width + map_j] != WALL) { - mvwaddch(disp->main, i, j, ACS_BLOCK); - } else if (map_j > 0 && map->map[map_i * map->width + map_j - 1] != WALL) { - mvwaddch(disp->main, i, j, ACS_BLOCK); - } else if (map_j < map->width - 1 && map->map[map_i * map->width + map_j + 1] != WALL) { - mvwaddch(disp->main, i, j, ACS_BLOCK); - } else { - mvwaddch(disp->main, i, j, ' '); - } - break; - default : mvwaddch(disp->main, i, j, ' '); - } - } - } - } - - // print entities - ht_iter_init(entities); - struct kvp kvp = ht_iter_next(entities); - while (kvp.key) { - struct entity *e = kvp.val; - if (e->visible) { - mvwaddch( - disp->main, e->p.y - camera->p.y + 1, e->p.x - camera->p.x + 1, - e->disp_ch[0] - ); - } - - kvp = ht_iter_next(entities); - } - - wrefresh(disp->main); -} - -void display_instructions(display_t *disp) -{ - mvwprintw(disp->inst, 1, 2, "h - move left"); - mvwprintw(disp->inst, 2, 2, "j - move down"); - mvwprintw(disp->inst, 3, 2, "k - move up"); - mvwprintw(disp->inst, 4, 2, "l - move right"); - mvwprintw(disp->inst, 5, 2, "> - move down staircase"); - mvwprintw(disp->inst, 6, 2, "< - exit via staircase"); - wrefresh(disp->inst); -} - -void display_message(display_t *disp, char *msg) -{ - for (int i = 1; i < MESSAGE_PANEL_WIDTH - 1; ++i) { - mvwaddch(disp->msgs, 1, i, ' '); - } - - mvwprintw(disp->msgs, 1, 1, msg); - wrefresh(disp->msgs); -} - -void display_status(display_t *disp, struct entity *entity) -{ - for (int i = 1; i < STATUS_PANEL_HEIGHT - 1; ++i) { - for (int j = 1; j < STATUS_PANEL_WIDTH - 1; ++j) { - mvwaddch(disp->stat, i, j, ' '); - } - } - - mvwprintw(disp->stat, 1, 2, "HP:"); - mvwprintw(disp->stat, 2, 2, "STAMINA:"); - mvwprintw(disp->stat, 3, 2, "MANA:"); - - wrefresh(disp->stat); -} - -enum action display_process_input(void) -{ - int ch = getch(); - switch (ch) { - case 'k' : return ACTION_UP; - case 'j' : return ACTION_DOWN; - case 'h' : return ACTION_LEFT; - case 'l' : return ACTION_RIGHT; - case '>' : return ACTION_STAIR_DOWN; - case '<' : return ACTION_STAIR_UP; - case KEY_F(1) : return ACTION_EXIT; - default : return ACTION_NONE; - } -} diff --git a/src/display.h b/src/display.h deleted file mode 100644 index b5d7d53..0000000 --- a/src/display.h +++ /dev/null @@ -1,54 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#ifndef DISPLAY_H_ -#define DISPLAY_H_ - -#include <stdbool.h> - -#include "cavegen.h" -#include "entity.h" -#include "ht.h" - -#define MAIN_PANEL_WIDTH 100 -#define MAIN_PANEL_HEIGHT 41 -#define INSTRUCTION_PANEL_WIDTH 32 -#define INSTRUCTION_PANEL_HEIGHT 39 -#define MESSAGE_PANEL_WIDTH 100 -#define MESSAGE_PANEL_HEIGHT 3 -#define STATUS_PANEL_WIDTH 32 -#define STATUS_PANEL_HEIGHT 5 - -typedef struct windows display_t; - -enum action { - ACTION_NONE, - ACTION_EXIT, - ACTION_DOWN, - ACTION_UP, - ACTION_LEFT, - ACTION_RIGHT, - ACTION_STAIR_DOWN, - ACTION_STAIR_UP, - NUM_ACTIONS, -}; - -display_t *display_init(void); -void display_destroy(display_t *disp); - -void display_map(display_t *disp, struct map *map, ht_t *entities); -void display_instructions(display_t *disp); -void display_message(display_t *disp, char *msg); -void display_status(display_t *disp, struct entity *entity); - -enum action display_process_input(void); - -#endif // DISPLAY_H_ diff --git a/src/ecs/entity.zig b/src/ecs/entity.zig new file mode 100644 index 0000000..6131cff --- /dev/null +++ b/src/ecs/entity.zig @@ -0,0 +1,3 @@ +const std = @import("std"); + +pub const Entity = struct {}; diff --git a/src/entity.h b/src/entity.h deleted file mode 100644 index f59d868..0000000 --- a/src/entity.h +++ /dev/null @@ -1,24 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#ifndef ENTITY_H_ -#define ENTITY_H_ - -#include "common.h" - -struct entity { - struct point p; - char *disp_ch; - bool solid; - bool visible; -}; - -#endif // ENTITY_H_ diff --git a/src/frontend/ncurses.zig b/src/frontend/ncurses.zig new file mode 100644 index 0000000..1586cf0 --- /dev/null +++ b/src/frontend/ncurses.zig @@ -0,0 +1,186 @@ +const std = @import("std"); +const Entity = @import("../ecs/entity.zig").Entity; +const Action = @import("../actions.zig").Action; +const ncurses = @cImport({ + @cInclude("ncurses.h"); +}); +const locale = @cImport({ + @cInclude("locale.h"); +}); + +const MAIN_PANEL_WIDTH = 100; +const MAIN_PANEL_HEIGHT = 41; +const INSTRUCTION_PANEL_WIDTH = 32; +const INSTRUCTION_PANEL_HEIGHT = 39; +const MESSAGE_PANEL_WIDTH = 100; +const MESSAGE_PANEL_HEIGHT = 3; +const STATUS_PANEL_WIDTH = 32; +const STATUS_PANEL_HEIGHT = 5; + +const KEY_MOVE_UP: i32 = 'k'; +const KEY_MOVE_DOWN: i32 = 'j'; +const KEY_MOVE_LEFT: i32 = 'h'; +const KEY_MOVE_RIGHT: i32 = 'l'; +const KEY_STAIR_UP: i32 = '<'; +const KEY_STAIR_DOWN: i32 = '>'; +const KEY_QUIT: i32 = ncurses.KEY_F(1); + +pub const Display = struct { + main: ?*ncurses.WINDOW, + inst: ?*ncurses.WINDOW, + msgs: ?*ncurses.WINDOW, + stat: ?*ncurses.WINDOW, + allocator: std.mem.Allocator, + + fn validTermSize() bool { + return ncurses.LINES >= MAIN_PANEL_HEIGHT + MESSAGE_PANEL_HEIGHT and + ncurses.COLS >= MAIN_PANEL_WIDTH + INSTRUCTION_PANEL_WIDTH; + } + + fn colorSupport() bool { + return ncurses.has_colors(); + } + + fn createNewWin(height: i32, width: i32, y: i32, x: i32) ?*ncurses.WINDOW { + const local_win = ncurses.newwin(height, width, y, x); + _ = ncurses.box(local_win, 0, 0); + _ = ncurses.wrefresh(local_win); + return local_win; + } + + fn formatInstruction(self: *Display, pos: i32, key: i32, description: [:0]const u8) void { + const c_description: ?[*:0]const u8 = description.ptr; + + if (ncurses.KEY_F0 <= key and key <= ncurses.KEY_F(64)) { + _ = ncurses.mvwprintw(self.inst, pos, 2, "F%d - %s", key - ncurses.KEY_F0, c_description); + } else if (key < 128) { + _ = ncurses.mvwprintw(self.inst, pos, 2, "%c - %s", key, c_description); + } else { + self.displayMessage("Invalid key name: {d}", .{key}); + } + } + + pub fn displayInstructions(self: *Display) void { + self.formatInstruction(1, KEY_MOVE_LEFT, "move left"); + self.formatInstruction(2, KEY_MOVE_DOWN, "move down"); + self.formatInstruction(3, KEY_MOVE_UP, "move up"); + self.formatInstruction(4, KEY_MOVE_RIGHT, "move right"); + self.formatInstruction(5, KEY_STAIR_DOWN, "move down staircase"); + self.formatInstruction(6, KEY_STAIR_UP, "move up staircase"); + self.formatInstruction(7, KEY_QUIT, "quit"); + _ = ncurses.wrefresh(self.inst); + } + + pub fn displayMessage(self: *Display, comptime fmt: []const u8, args: anytype) void { + for (1..MESSAGE_PANEL_WIDTH - 1) |i| { + const i_32 = @as(i32, @intCast(i)); + _ = ncurses.mvwaddch(self.msgs, 1, i_32, ' '); + } + + var buf: [MESSAGE_PANEL_WIDTH:0]u8 = undefined; + const data = std.fmt.bufPrint(&buf, fmt, args) catch { + self.displayMessage("Message too long", .{}); + return; + }; + buf[data.len] = 0; + + const msg: [:0]u8 = &buf; + + const c_msg: ?[*:0]const u8 = msg.ptr; + _ = ncurses.mvwprintw(self.msgs, 1, 1, c_msg); + _ = ncurses.wrefresh(self.msgs); + } + + pub fn displayStatus(self: *Display, entity: *const Entity) void { + for (1..STATUS_PANEL_HEIGHT - 1) |i| { + for (1..STATUS_PANEL_WIDTH - 1) |j| { + const i_32 = @as(i32, @intCast(i)); + const j_32 = @as(i32, @intCast(j)); + _ = ncurses.mvwaddch(self.stat, i_32, j_32, ' '); + } + } + + _ = entity; + _ = ncurses.wrefresh(self.stat); + } + + pub fn processInput(self: *Display) Action { + _ = self; + + const ch = ncurses.getch(); + return switch (ch) { + KEY_MOVE_UP => Action.move_up, + KEY_MOVE_DOWN => Action.move_down, + KEY_MOVE_LEFT => Action.move_left, + KEY_MOVE_RIGHT => Action.move_right, + KEY_STAIR_DOWN => Action.down_stair, + KEY_STAIR_UP => Action.up_stair, + KEY_QUIT => Action.exit, + else => Action.illegal, + }; + } + + pub fn init() !Display { + _ = locale.setlocale(locale.LC_ALL, ""); + if (ncurses.initscr() == null) { + return error.CursesInitFail; + } + + if (!validTermSize()) { + _ = ncurses.endwin(); + return error.InvalidTermSize; + } + + if (!colorSupport()) { + _ = ncurses.endwin(); + return error.NoColorSupport; + } + + _ = ncurses.raw(); + _ = ncurses.keypad(ncurses.stdscr, true); + _ = ncurses.noecho(); + _ = ncurses.curs_set(0); + _ = ncurses.start_color(); + + _ = ncurses.init_pair(1, ncurses.COLOR_WHITE, ncurses.COLOR_BLACK); + _ = ncurses.init_pair(2, ncurses.COLOR_BLACK, ncurses.COLOR_RED); + _ = ncurses.wattron(ncurses.stdscr, ncurses.COLOR_PAIR(1)); + _ = ncurses.refresh(); + + return Display{ + .inst = createNewWin( + INSTRUCTION_PANEL_HEIGHT, + INSTRUCTION_PANEL_WIDTH, + @divTrunc(ncurses.LINES - INSTRUCTION_PANEL_HEIGHT - STATUS_PANEL_HEIGHT, 2) + 1, + @divTrunc(ncurses.COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2) + MAIN_PANEL_WIDTH - 1, + ), + .msgs = createNewWin( + MESSAGE_PANEL_HEIGHT, + MESSAGE_PANEL_WIDTH, + @divTrunc(ncurses.LINES - MAIN_PANEL_HEIGHT - MESSAGE_PANEL_HEIGHT, 2) + MAIN_PANEL_HEIGHT, + @divTrunc(ncurses.COLS - MESSAGE_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2), + ), + .stat = createNewWin( + STATUS_PANEL_HEIGHT, + STATUS_PANEL_WIDTH, + @divTrunc(ncurses.LINES - INSTRUCTION_PANEL_HEIGHT - STATUS_PANEL_HEIGHT, 2) + INSTRUCTION_PANEL_HEIGHT, + @divTrunc(ncurses.COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2) + MAIN_PANEL_WIDTH - 1, + ), + .main = createNewWin( + MAIN_PANEL_HEIGHT, + MAIN_PANEL_WIDTH, + @divTrunc(ncurses.LINES - MAIN_PANEL_HEIGHT - MESSAGE_PANEL_HEIGHT, 2) + 1, + @divTrunc(ncurses.COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2), + ), + .allocator = undefined, + }; + } + + pub fn deinit(self: *Display) void { + _ = ncurses.delwin(self.main); + _ = ncurses.delwin(self.inst); + _ = ncurses.delwin(self.msgs); + _ = ncurses.delwin(self.stat); + _ = ncurses.endwin(); + } +}; diff --git a/src/ht.c b/src/ht.c deleted file mode 100644 index ff0a3a8..0000000 --- a/src/ht.c +++ /dev/null @@ -1,235 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#include "ht.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -struct node { - char *key; - void *val; - struct node *next; -}; - -struct hash_table { - struct node **vals; - int max_size; - int size; - - int curr_index; - struct node *curr_node; - bool iterating; -}; - -static void rehash(ht_t *h, int newsize) -{ - ht_t *new_h = ht_create(newsize); - - ht_iter_init(h); - - struct kvp kvp = ht_iter_next(h); - while (kvp.key) { - ht_insert(new_h, kvp.key, kvp.val); - kvp = ht_iter_next(h); - } - - // only destroy vals if it isn't NULL - if (h->vals) { - // iterate through the hash values - for (int i = 0; i < h->max_size; ++i) { - // iterate through the linked list and remove the value - struct node *curr = h->vals[i]; - struct node *prev; - while (curr) { - prev = curr; - curr = curr->next; - free(prev->key); - free(prev); - } - } - free(h->vals); - } - - h->max_size = newsize; - h->vals = new_h->vals; - h->curr_index = 0; - h->curr_node = NULL; - h->iterating = false; - - free(new_h); -} - -static unsigned long djb2_hash(char *str) -{ - unsigned long hash = 5381; - int c; - - // hash * 33 + c - while ((c = *str++)) hash = ((hash << 5) + hash) + c; - - return hash; -} - -ht_t *ht_create(int max_size) -{ - ht_t *h = malloc(sizeof(ht_t)); - h->max_size = max_size; - h->size = 0; - h->vals = malloc(sizeof(struct node) * max_size); - h->curr_index = 0; - h->curr_node = NULL; - h->iterating = false; - - return h; -} - -void ht_destroy(ht_t *h) -{ - // only destroy if h isn't NULL - if (h) { - // only destroy vals if it isn't NULL - if (h->vals) { - // iterate through the hash values - for (int i = 0; i < h->max_size; ++i) { - // iterate through the linked list and remove the value - struct node *curr = h->vals[i]; - struct node *prev; - while (curr) { - prev = curr; - curr = curr->next; - free(prev->key); - free(prev); - } - } - free(h->vals); - } - free(h); - } -} - -void *ht_find(ht_t *h, char *key) -{ - unsigned int hash = djb2_hash(key) % h->max_size; - - // exit early if hash isn't in table - if (!h->vals[hash]) - return NULL; - - // iterate through values stored at `hash`; exit early if found - struct node *curr = h->vals[hash]; - while (curr) { - if (strcmp(key, curr->key) == 0) - return curr->val; - curr = curr->next; - } - - return NULL; -} - -void ht_insert(ht_t *h, char *key, void *val) -{ - if (h->size > h->max_size * 0.75) - rehash(h, h->max_size * 2); - - unsigned int hash = djb2_hash(key) % h->max_size; - - // create a node - int len = strlen(key); - struct node *new = malloc(sizeof(struct node)); - new->key = malloc(sizeof(char) * (len + 1)); - for (int i = 0; i < len; ++i) { - new->key[i] = key[i]; - } - new->key[len] = 0; - new->val = val; - - // insert node at beginning of list - if (h->vals[hash]) { - new->next = h->vals[hash]; - } - h->vals[hash] = new; - - ++(h->size); -} - -void ht_delete(ht_t *h, char *key) -{ - if (h->size < h->max_size * 0.25) - rehash(h, h->max_size * 0.5); - - unsigned int hash = djb2_hash(key) % h->max_size; - - if (!h->vals[hash]) - return; // exit if the hash is not found - - // remove the key from the front of the list if it's there - if (strcmp(key, h->vals[hash]->key) == 0) { - struct node *temp = h->vals[hash]; - h->vals[hash] = h->vals[hash]->next; - free(temp->key); - free(temp); - } else { - // iterate through list and remove once a match is found - struct node *prev = h->vals[hash]; - struct node *curr = h->vals[hash]->next; - while (curr) { - if (strcmp(key, curr->key) == 0) { - prev->next = curr->next; - free(curr->key); - free(curr); - return; - } - - prev = curr; - curr = curr->next; - } - } -} - -int ht_size(ht_t *h) -{ - return h->size; -} - -void ht_iter_init(ht_t *h) -{ - h->curr_index = 0; - h->curr_node = h->vals[0]; -} - -struct kvp ht_iter_next(ht_t *h) -{ - struct kvp out = { - .key = NULL, - .val = NULL, - }; - - // return NULL if we've reached the end - if (!h->curr_node && h->curr_index >= h->max_size) - return out; - - // look for next index with node if current node is NULL - if (!h->curr_node) { - while (!h->curr_node) { - h->curr_node = h->vals[h->curr_index++]; - if (h->curr_index >= h->max_size) - return out; - } - } - - // get the value and move to the next node - out.key = h->curr_node->key; - out.val = h->curr_node->val; - h->curr_node = h->curr_node->next; - return out; -} diff --git a/src/ht.h b/src/ht.h deleted file mode 100644 index 236ae4e..0000000 --- a/src/ht.h +++ /dev/null @@ -1,40 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#ifndef HT_H_ -#define HT_H_ - -#include <stdbool.h> - -typedef struct hash_table ht_t; - -struct kvp { - char *key; - void *val; -}; - -// construct and destructor -ht_t *ht_create(int size); -void ht_destroy(ht_t *h); - -// accessors -void *ht_find(ht_t *h, char *key); -void ht_insert(ht_t *h, char *key, void *val); -void ht_delete(ht_t *h, char *key); - -// queries -int ht_size(ht_t *h); - -// iterator -void ht_iter_init(ht_t *h); -struct kvp ht_iter_next(ht_t *h); - -#endif // HT_H_ diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 5278d3e..0000000 --- a/src/main.c +++ /dev/null @@ -1,192 +0,0 @@ -/* -This file is part of urlg. -urlg is free software: you can redistribute it and/or modify it under the terms -of the GNU General Public License as published by the Free Software Foundation, -either version 3 of the License, or (at your option) any later version. urlg is -distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -PURPOSE. See the GNU General Public License for more details. You should have -received a copy of the GNU General Public License along with urlg. If not, see -<https://www.gnu.org/licenses/>. -*/ -#include <curses.h> -#include <getopt.h> -#include <locale.h> -#include <stdlib.h> -#include <time.h> - -#include "../config.h" -#include "cavegen.h" -#include "common.h" -#include "display.h" -#include "entity.h" -#include "ht.h" - -bool entity_set_pos(struct entity *e, struct point p, struct map *map) -{ - if (e->solid) { - if (p.y < 0 || p.x < 0 || p.y >= map->height || p.x >= map->width) { - return false; - } - if (map->map[p.y * map->width + p.x] == WALL) { - return false; - } - } - - e->p = p; - - return true; -} - -bool game_update( - display_t *disp, enum action action, ht_t *entities, struct map *map -) -{ - struct entity *player = ht_find(entities, "player"); - struct entity *camera = ht_find(entities, "camera"); - - struct point newp = player->p; - struct point newp_cam = camera->p; - switch (action) { - case ACTION_EXIT : return true; - case ACTION_UP : - --newp.y; - --newp_cam.y; - display_message(disp, "moving up"); - break; - case ACTION_DOWN : - ++newp.y; - ++newp_cam.y; - display_message(disp, "moving down"); - break; - case ACTION_LEFT : - --newp.x; - --newp_cam.x; - display_message(disp, "moving left"); - break; - case ACTION_RIGHT : - ++newp.x; - ++newp_cam.x; - display_message(disp, "moving right"); - break; - case ACTION_STAIR_DOWN : - if (map->map[player->p.y * map->width + player->p.x] == DOWN_STAIR) { - free(map->map); - create_cave(map); - - newp = map->entry_point; - newp_cam.x = map->entry_point.x - MAIN_PANEL_WIDTH / 2 + 1; - newp_cam.y = map->entry_point.y - MAIN_PANEL_HEIGHT / 2 + 1; - display_message(disp, "moving down stairs"); - } else { - display_message(disp, "no stairs to go down"); - } - break; - case ACTION_STAIR_UP : - if (map->map[player->p.y * WIDTH + player->p.x] == UP_STAIR) { - display_message(disp, "moving up stairs"); - return true; - } else { - display_message(disp, "no stairs to go up"); - } - break; - default : display_message(disp, "unrecognized command"); break; - } - - if (entity_set_pos(player, newp, map)) - entity_set_pos(camera, newp_cam, map); - - return false; -} - -void print_version(void) -{ - printf("%s\n", PACKAGE_STRING); - printf("Copyright (C) 2024 Jacob Janzen\n"); - printf("This is free software; see the source for copying conditions.\n"); - printf( - "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n" - ); - printf("PARTICULAR PURPOSE.\n"); -} - -int main(int argc, char **argv) -{ - int option_index = 0; - int ch; - int version_flag = 0; - struct option longopts[] = { - {"version", no_argument, &version_flag, 'v'}, - }; - while ((ch = getopt_long(argc, argv, ":v", longopts, &option_index)) != -1 - ) { - switch (ch) { - case 'v' : version_flag = 1; break; - case 0 : break; - default : break; - } - } - if (version_flag) { - print_version(); - return EXIT_SUCCESS; - } - - unsigned int seed = time(NULL); - srand(seed); - - display_t *disp = display_init(); - - if (!disp) { - return EXIT_FAILURE; - } - - // create the map - struct map map; - create_cave(&map); - - // create the entity map - ht_t *entities = ht_create(1); - - // create the camera - struct entity camera; - camera.disp_ch = " "; - camera.solid = false; - camera.visible = false; - ht_insert(entities, "camera", &camera); - - // create the player - struct entity player; - player.disp_ch = "@"; - player.solid = true; - player.visible = true; - ht_insert(entities, "player", &player); - - // set starting point - struct point cam_p = { - .x = map.entry_point.x - MAIN_PANEL_WIDTH / 2 + 1, - .y = map.entry_point.y - MAIN_PANEL_HEIGHT / 2 + 1, - }; - entity_set_pos(&player, map.entry_point, &map); - entity_set_pos(&camera, cam_p, &map); - - // start displaying things - display_map(disp, &map, entities); - display_instructions(disp); - display_status(disp, &player); - display_message(disp, ""); - - bool done = false; - while (!done) { - enum action action = display_process_input(); - done = game_update(disp, action, entities, &map); - display_map(disp, &map, entities); - } - - free(map.map); - ht_destroy(entities); - display_destroy(disp); - - endwin(); - - return 0; -} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..63cc2db --- /dev/null +++ b/src/main.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const Entity = @import("ecs/entity.zig").Entity; +const Display = @import("frontend/ncurses.zig").Display; +const Action = @import("actions.zig").Action; + +pub fn main() u8 { + var d = Display.init() catch |err| { + std.log.err("{}", .{err}); + return 1; + }; + d.displayMessage("Initialized", .{}); + d.displayInstructions(); + d.displayStatus(&Entity{}); + var action = Action.illegal; + while (action != Action.exit) { + action = d.processInput(); + } + d.deinit(); + + return 0; +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..ecfeade --- /dev/null +++ b/src/root.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const testing = std.testing; + +export fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "basic add functionality" { + try testing.expect(add(3, 7) == 10); +} |