commit 5d1e30631a73c80b06fc3f7eb42ee670d0e7cc5e
Author: calliope <me@calliope.sh>
Date: Wed, 17 Sep 2025 01:12:04 -0500
aauuugghh refactor
Diffstat:
| A | .gitignore | | | 4 | ++++ |
| A | Makefile | | | 19 | +++++++++++++++++++ |
| A | bandit.c | | | 343 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | bandit_action.c | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | bandit_actor.c | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | common.h | | | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | input.c | | | 84 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 652 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+*.o
+*.sw*
+*core*
+banditrl
diff --git a/Makefile b/Makefile
@@ -0,0 +1,19 @@
+CC = cc
+OBJ = bandit.o bandit_actor.o bandit_action.o input.o
+CFLAGS = -std=c89 -pedantic -Wall `pkgconf --cflags ncurses`
+LFLAGS = `pkgconf --libs ncurses`
+
+banditrl: $(OBJ)
+ $(CC) -o $@ $(OBJ) $(LFLAGS)
+
+bandit.o : bandit.c common.h
+ $(CC) $(CFLAGS) -c $<
+bandit_actor.o : bandit_actor.c common.h
+ $(CC) $(CFLAGS) -c $<
+bandit_action.o : bandit_action.c common.h
+ $(CC) $(CFLAGS) -c $<
+input.o : input.c common.h
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ rm -f $(OBJ)
diff --git a/bandit.c b/bandit.c
@@ -0,0 +1,343 @@
+#include <stdio.h>
+#include <curses.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+extern u32 icons[MAXACTORS];
+extern Actor actors[MAXACTORS];
+
+u8 app_state = 1;
+u8 menu_state = 1;
+u8 menu_selection = 1;
+
+enum {
+ ABILITY_STRONG = 1 << 0,
+ ABILITY_ECHO = 1 << 1
+};
+
+Thief pc;
+
+unsigned int ltiles[MAXLEVELWIDTH * MAXLEVELHEIGHT] = {
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2,
+ 2, 1, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 1, 2,
+ 2, 3, 2, 2, 2, 2, 0, 0, 0, 2, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+};
+
+char *banner =
+ "\n\n"
+ "#### # #### # \n"
+ "# # ### ### # # # # # # \n"
+ "#### # # # # ### #### # \n"
+ "# # #### # # #### # # # # # \n"
+ "# # # # # # # # # # # # # \n"
+ "#### #### # # #### # ## # # ####\n"
+ "\n\n"
+ "$ to roll a new scoundrel\n"
+ "@ to load your existing thief\n"
+ "q to exit\n\0";
+
+u32 tile_flags[MAXLEVELWIDTH * MAXLEVELHEIGHT];
+
+char* messages[3] = {" \n", " \n", " \n"};
+
+void draw_init_colors(void) {
+ start_color();
+ init_pair(1, COLOR_RED, COLOR_BLACK);
+ init_pair(2, COLOR_WHITE, COLOR_GREEN);
+ init_pair(3, COLOR_BLUE, COLOR_BLACK);
+ init_pair(4, COLOR_MAGENTA, COLOR_BLACK);
+}
+
+void draw_messagebox(void) {
+ u32 cursx, cursy;
+ u32 i;
+ char* msg;
+
+ cursy = LINES - 1;
+
+ for (i = 0; i < 3; i++) {
+ msg = messages[i];
+ for (cursx = 0; cursx < 60; cursx++) {
+ if (*msg == '\0') {
+ mvaddch(cursy, cursx, ' ');
+ } else {
+ mvaddch(cursy, cursx, *msg);
+ msg++;
+ }
+ }
+ cursy -= 1;
+ }
+ cursy = LINES - 4;
+ for (cursx = 0; cursx < COLS; cursx++) {
+ mvaddch(cursy, cursx, '-');
+ }
+}
+
+void message_push(char* str) {
+ messages[2] = messages[1];
+ messages[1] = messages[0];
+ messages[0] = str;
+}
+
+void draw_screen_clear(void) {
+ u32 i, j;
+
+ for (j = 0; j < COLS; j++) {
+ for (i = 0; i < LINES; i++) {
+ mvaddch(i, j, ' ');
+ }
+ }
+}
+
+void menu_title(void) {
+ u32 i;
+ int input;
+
+ mvaddstr(LINES/4, COLS/4,banner);
+ for (i = 0; i < COLS; i++) {
+ mvaddch(0, i, '#');
+ mvaddch(LINES - 1, i, '#');
+ }
+
+ input = getch();
+ if (input == 'q') { app_state = 0; }
+ else if (input == '$') { app_state = 2; }
+ else if (input == '@') { }
+}
+
+void thief_init_bat(Thief *in) {
+ in->grace = 2;
+ in->perception = 3;
+ in->charisma = 0;
+ in->lore = 1;
+ in->abilities = ABILITY_ECHO;
+}
+
+void thief_init_snake(Thief *in) {
+ in->grace = 0;
+ in->perception = 1;
+ in->charisma = 3;
+ in->lore = 2;
+ in->abilities = ABILITY_STRONG;
+}
+
+void thief_init_rat(Thief *in) {
+ in->grace = 3;
+ in->perception = 2;
+ in->charisma = 1;
+ in->lore = 0;
+ in->abilities = 0;
+}
+
+void thief_init_raven(Thief *in) {
+ in->grace = 1;
+ in->perception = 0;
+ in->charisma = 2;
+ in->lore = 3;
+ in->abilities = 0;
+}
+
+void menu_character_gen(void) {
+ int input;
+
+ if (menu_state == 2) {
+ echo();
+ mvprintw(10, 0, "Type your name...");
+ mvprintw(11, 0, "");
+ getstr((char*) pc.name);
+ actors[0].x = 3;
+ actors[0].y = 3;
+ app_state = 3;
+ noecho();
+ } else {
+ mvprintw(10, 0, "Choose what creature you are... (c to confirm)");
+ mvprintw(12, 0, "Bat");
+ mvprintw(13, 0, "Snake");
+ mvprintw(14, 0, "Raven");
+ mvprintw(15, 0, "Rat");
+ if (menu_selection == 1) {
+ attron(COLOR_PAIR(2));
+ mvprintw(12, 0, "Bat");
+ attroff(COLOR_PAIR(2));
+ attron(COLOR_PAIR(3));
+ mvprintw(20, 0, "Grace 2");
+ mvprintw(21, 0, "Perception 3");
+ mvprintw(22, 0, "Lore 1");
+ mvprintw(23, 0, "Charisma 0");
+ attroff(COLOR_PAIR(3));
+ attron(COLOR_PAIR(4));
+ mvprintw(24, 0, "\n");
+ mvprintw(25, 0, "Walk Speed : 10");
+ mvprintw(26, 0, "Fly Speed : 90");
+ mvprintw(27, 0, "\n");
+ mvprintw(28, 0, "Echolocation");
+ attroff(COLOR_PAIR(4));
+ } else if (menu_selection == 2) {
+ attron(COLOR_PAIR(2));
+ mvprintw(13, 0, "Snake");
+ attroff(COLOR_PAIR(2));
+ attron(COLOR_PAIR(3));
+ mvprintw(20, 0, "Grace 0");
+ mvprintw(21, 0, "Perception 1");
+ mvprintw(22, 0, "Lore 2");
+ mvprintw(23, 0, "Charisma 3");
+ attroff(COLOR_PAIR(3));
+ attron(COLOR_PAIR(4));
+ mvprintw(24, 0, "\n");
+ mvprintw(25, 0, "Slither Speed: 40");
+ mvprintw(26, 0, "\n");
+ mvprintw(27, 0, "Strong");
+ attroff(COLOR_PAIR(4));
+ } else if (menu_selection == 3) {
+ attron(COLOR_PAIR(2));
+ mvprintw(14, 0, "Raven");
+ attroff(COLOR_PAIR(2));
+ attron(COLOR_PAIR(3));
+ mvprintw(20, 0, "Grace 1");
+ mvprintw(21, 0, "Perception 0");
+ mvprintw(22, 0, "Lore 3");
+ mvprintw(23, 0, "Charisma 2");
+ attroff(COLOR_PAIR(3));
+ mvprintw(24, 0, "\n");
+ attron(COLOR_PAIR(4));
+ mvprintw(25, 0, "Walk Speed: 30");
+ mvprintw(26, 0, "Fly Speed: 90");
+ attroff(COLOR_PAIR(4));
+ } else if (menu_selection == 4) {
+ attron(COLOR_PAIR(2));
+ mvprintw(15, 0, "Rat");
+ attroff(COLOR_PAIR(2));
+ attron(COLOR_PAIR(3));
+ mvprintw(20, 0, "Grace 3");
+ mvprintw(21, 0, "Perception 2");
+ mvprintw(22, 0, "Lore 0");
+ mvprintw(23, 0, "Charisma 1");
+ mvprintw(24, 0, "\n");
+ attroff(COLOR_PAIR(3));
+ attron(COLOR_PAIR(4));
+ mvprintw(25, 0, "Walk Speed: 30");
+ mvprintw(26, 0, "Scurry Speed: 50");
+ attroff(COLOR_PAIR(4));
+ }
+
+ input = getch();
+ if (input == 'q') { app_state = 0; }
+ else if (input == KEY_DOWN) {
+ menu_selection++;
+ if (menu_selection > 4) { menu_selection = 4; }
+ } else if (input == KEY_UP) {
+ menu_selection--;
+ if (menu_selection < 1) { menu_selection = 1; }
+ } else if (input == 'c') {
+ switch (menu_selection) {
+ case 1: thief_init_bat(&pc); break;
+ case 2: thief_init_snake(&pc); break;
+ case 3: thief_init_raven(&pc); break;
+ case 4: thief_init_rat(&pc);
+ }
+ menu_state = 2;
+ }
+ }
+}
+
+void draw_level(void) {
+ u32 i;
+
+ attron(COLOR_PAIR(1));
+
+ for(i = 0; i < MAXLEVELWIDTH * MAXLEVELHEIGHT; i++) {
+ u32 curs_y = i / MAXLEVELWIDTH;
+ u32 curs_x = i % MAXLEVELWIDTH;
+ u32 local_x = curs_x/* - pov_x*/;
+ u32 local_y = curs_y/* - pov_y*/;
+
+ if ( local_y > LINES || local_x > COLS ) {
+ continue;
+ }
+
+ if(ltiles[i] == TILE_NULL) {
+ continue;
+ } else if(ltiles[i] == TILE_FLOOR) {
+ mvaddch(local_y, local_x, '.');
+ } else if(ltiles[i] == TILE_WALL) {
+ mvaddch(local_y, local_x, '#');
+ } else if(ltiles[i] == TILE_DOOR_CLOSED) {
+ mvaddch(local_y, local_x, '+');
+ } else if(ltiles[i] == TILE_DOOR_OPEN) {
+ mvaddch(local_y, local_x, '*');
+ }
+ }
+
+ attroff(COLOR_PAIR(1));
+
+ /*
+ for (i = 0; i < MAXACTORS; i++) {
+ if (icons[i] != '?') {
+ mvaddch(actors[i].x - pov_y, actors[i].y - pov_x, icons[i]);
+ }
+ }
+ */
+
+ mvaddch(actors[0].y, actors[0].x, '@');
+}
+
+u32 tile_index(u32 x, u32 y) {
+ if (x > MAXLEVELWIDTH || y > MAXLEVELHEIGHT) {
+ return 1; /* outside of level bounds */
+ }
+ return x + y * MAXLEVELWIDTH;
+}
+
+u8 bandit_time_advance(void) {
+ bandit_actor_think();
+ return 0;
+}
+
+int main(void) {
+ u32 doortile = MAXLEVELWIDTH * 7 + 7;
+ tile_flags[doortile] = DOOR_HAS_LOCK | DOOR_LOCK_ENGAGED;
+
+ initscr(); /* init curses mode */
+ noecho();
+
+ /*
+ * input characters are immediately available,
+ * instead of waiting for the user to hit enter
+ */
+ raw();
+
+ /* allows getch() to read arrow key inputs and such */
+ keypad(stdscr, TRUE);
+
+ draw_init_colors();
+
+ while(0 != app_state) {
+ draw_screen_clear();
+ if (app_state == 1) {
+ menu_title();
+ } else if (app_state == 2) {
+ menu_character_gen();
+ } else if (app_state == 3) {
+ draw_level();
+ draw_messagebox();
+ curses_input_handle();
+ }
+ refresh();
+ }
+
+ endwin();
+}
diff --git a/bandit_action.c b/bandit_action.c
@@ -0,0 +1,60 @@
+#include "common.h"
+
+extern u32 ltiles[];
+extern u32 tile_flags[];
+
+u32 target = 0;
+
+void door(u32 index) {
+ u32 * tile = (ltiles + index);
+ if (*tile == TILE_DOOR_OPEN) {
+ *tile = TILE_DOOR_CLOSED;
+ message_push("You close the door.");
+ return;
+ }
+ if (*tile == TILE_DOOR_OPEN)
+ {
+ message_push("1. Quick open");
+ message_push("2. Turn knob");
+ message_push("3. Kick");
+ }
+}
+
+void bandit_action_interact(u32 x, u32 y) {
+ u32 target_index;
+
+ target_index = tile_index(x, y);
+ if (ltiles[target_index] == TILE_DOOR_OPEN) {
+ door(target_index);
+ }
+
+ target = target_index;
+}
+
+void bandit_action(u32 choice) {
+ if (ltiles[target] == TILE_DOOR_OPEN) {
+ if (choice == 1) {
+ if (tile_flags[target] & (DOOR_LOCK_ENGAGED | DOOR_HAS_LOCK)) {
+ message_push("It's locked.");
+ return;
+ }
+ ltiles[target] = TILE_DOOR_OPEN;
+ message_push("You open the door.");
+ return;
+ } else if (choice == 2) {
+ if (tile_flags[target] & (DOOR_LOCK_ENGAGED | DOOR_HAS_LOCK)) {
+ message_push("You twist the doorknob. It's locked.");
+ } else if (tile_flags[target] & DOOR_LOCK_ENGAGED && tile_flags[target] ^ DOOR_HAS_LOCK) {
+ message_push("You twist the doorknob. The lock is broken.");
+ } else {
+ message_push("You twist the doorknob. It's unlocked.");
+ }
+ return;
+ } else if (choice == 3) {
+ message_push("You kick the door open!");
+ ltiles[target] = TILE_DOOR_OPEN;
+ tile_flags[target] ^= DOOR_HAS_LOCK;
+ return;
+ }
+ }
+}
diff --git a/bandit_actor.c b/bandit_actor.c
@@ -0,0 +1,89 @@
+#include "common.h"
+
+extern u32 ltiles[MAXLEVELWIDTH * MAXLEVELHEIGHT];
+
+Actor actors[MAXACTORS];
+/*
+unsigned int ActorX[MAXACTORS] = {3, 12, 12, 0, 0};
+unsigned int ActorY[MAXACTORS] = {3, 12, 8, 0, 0};
+*/
+u32 icons[MAXACTORS] = {'@', 'G', 'T', '?', '?'};
+
+u8 bandit_actor_place(u32 in, u32 x, u32 y) {
+ if (x > MAXLEVELWIDTH || y > MAXLEVELHEIGHT)
+ return 1;
+
+ if (ltiles[tile_index(x, y)] == TILE_FLOOR || ltiles[tile_index(x, y)] == TILE_DOOR_OPEN ) {
+ actors[in].x = x;
+ actors[in].y = y;
+ return 0;
+ }
+
+ return 1;
+}
+
+u8 los(u32 viewer, u32 target) {
+ u32 i;
+
+ for ( i = 0;
+ i < (MAXLEVELWIDTH * MAXLEVELHEIGHT);
+ i++ ) {
+ u32 x = i % MAXLEVELWIDTH;
+ u32 y = i / MAXLEVELWIDTH;
+
+ /* skip tiles that don't block LOS */
+ if (ltiles[i] != TILE_WALL && ltiles[i] != TILE_DOOR_OPEN) { continue; }
+
+ if ((x > actors[target].x) && (x > actors[viewer].x)) { continue; }
+ if((x < actors[target].x) && (x < actors[viewer].x)) { continue; }
+
+ if((y < actors[target].y) && (y < actors[viewer].y)) { continue; }
+ if((y > actors[target].y) && (y > actors[viewer].y)) { continue; }
+
+ return 0;
+ }
+ return 1;
+}
+
+u8 bandit_actor_think(void)
+{
+ /* glorg this sucks rewrite
+ u32 destx = ActorX[1];
+ u32 desty = ActorY[1];
+
+ if (!CheckLOS(1, 0))
+ {
+ return;
+ }
+
+ if (ActorY[1] > ActorY[0])
+ {
+ desty -= 1;
+ }
+ else if (ActorY[1] < ActorY[0])
+ {
+ desty += 1;
+ }
+ else
+ {
+ if (ActorX[1] > ActorX[0])
+ {
+ destx -= 1;
+ }
+ else if (ActorX[1] < ActorX[0])
+ {
+ destx += 1;
+ }
+ }
+
+ if (destx == ActorX[0] && desty == ActorY[0])
+ {
+ //if player is in destination tile, attack
+ AddMessage("You're DEAD\n");
+ return;
+ }
+
+ actor_place(1, destx, desty);
+ */
+ return 0;
+}
diff --git a/common.h b/common.h
@@ -0,0 +1,53 @@
+#include <stdint.h>
+#include <stddef.h>
+
+#define MAXLEVELWIDTH 15
+#define MAXLEVELHEIGHT 15
+#define MAXACTORS 128
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+enum {
+ TILE_NULL,
+ TILE_FLOOR,
+ TILE_WALL,
+ TILE_DOOR_CLOSED,
+ TILE_DOOR_OPEN
+};
+
+typedef struct {
+ u32 x, y;
+} Actor;
+
+/*
+ * NOTE: A door can be locked and open at the same time!
+ * The effect would be that the door must be unlocked before
+ * it can be closed. Yes, this is the type of granularity
+ * that I want to implement.
+ */
+enum {
+ DOOR_HAS_LOCK,
+ DOOR_LOCK_ENGAGED
+};
+
+void input_handle(void);
+
+typedef struct {
+ u8 grace, lore, perception, charisma;
+ u32 abilities;
+ u8 name[64];
+} Thief;
+
+u8 bandit_actor_think(void);
+u32 tile_index(u32 x, u32 y);
+
+void message_push(char *str);
+u8 bandit_time_advance(void);
+u8 bandit_actor_place(u32 in, u32 x, u32 y);
+void bandit_action_interact(u32 x, u32 y);
+void bandit_action(u32 choice);
+
+void curses_input_handle(void);
diff --git a/input.c b/input.c
@@ -0,0 +1,84 @@
+#include <curses.h>
+
+#include "common.h"
+
+extern Thief pc;
+extern u32 app_state;
+extern Actor actors[MAXACTORS];
+
+enum {
+ INPUT_STATE_MOVE,
+ INPUT_STATE_TARGET,
+ INPUT_STATE_INTERACT
+};
+
+enum {
+ LEFT,
+ RIGHT,
+ UP,
+ DOWN
+};
+
+u8 input_state = INPUT_STATE_MOVE;
+
+void keypress_direction(u8 dir)
+{
+ u32 xgo = actors[0].x;
+ u32 ygo = actors[0].y;
+ switch(dir) {
+ case LEFT: xgo--; break;
+ case RIGHT: xgo++; break;
+ case UP: ygo--; break;
+ case DOWN: ygo++; break;
+ }
+
+ if (input_state == INPUT_STATE_TARGET) {
+ bandit_action_interact(xgo, ygo);
+ input_state = INPUT_STATE_INTERACT;
+ return;
+ }
+ input_state = INPUT_STATE_MOVE;
+ bandit_actor_place(0, xgo, ygo);
+ bandit_time_advance();
+}
+
+void curses_input_handle(void) {
+ int input;
+
+ input = getch();
+ switch (input) {
+ case KEY_LEFT:
+ keypress_direction(LEFT);
+ break;
+ case KEY_RIGHT:
+ keypress_direction(RIGHT);
+ break;
+ case KEY_UP:
+ keypress_direction(UP);
+ break;
+ case KEY_DOWN:
+ keypress_direction(DOWN);
+ break;
+ case 'q': /* there are no macros for alphabetical keys. Just treat it like a char and it SHOULD work ?? */
+ app_state = 0;
+ break;
+ case 'i':
+ input_state = INPUT_STATE_TARGET;
+ break;
+ case '1':
+ if (input_state == INPUT_STATE_INTERACT) {
+ bandit_action(1);
+ }
+ break;
+ case '2':
+ if (input_state == INPUT_STATE_INTERACT) {
+ bandit_action(2);
+ }
+ break;
+ case '3':
+ if (input_state == INPUT_STATE_INTERACT) {
+ bandit_action(3);
+ }
+ break;
+ }
+}