bandit

oldschool thievery roguelike
git clone git://moonbender.net/bandit
Log | Files | Refs | README

commit 5d1e30631a73c80b06fc3f7eb42ee670d0e7cc5e
Author: calliope <me@calliope.sh>
Date:   Wed, 17 Sep 2025 01:12:04 -0500

aauuugghh refactor

Diffstat:
A.gitignore | 4++++
AMakefile | 19+++++++++++++++++++
Abandit.c | 343+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abandit_action.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abandit_actor.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommon.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainput.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; + } +}