commit 63aac0e6b31a1405ba1688c98cc7a454303eb31b Author: Luxferre Date: Tue Jan 6 11:08:43 2026 +0200 initial upload diff --git a/README.md b/README.md new file mode 100644 index 0000000..058d626 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Scoundrel ports by Luxferre + +This is a collection of my own ports of the [Scoundrel](http://stfj.net/art/2011/Scoundrel.pdf) roguelike card game to as many platforms as possible. + +## Ports included in this repo + +* [ANSI C89 port](scoundrel.c) + +## Ports NOT included in this repo for various reasons + +* [(Circuit)Python port](https://codeberg.org/luxferre/t-deckard/src/branch/master/game/scoundrel.py) (as a part of the T-DeckARD suite) +* [HTML+CSS+JS port](https://ironlynx.neocities.org/scoundrel) (hosted as a single HTML file with readable source code) + +## Currently planned ports + +### Low-hanging fruits + +* POSIX AWK +* TI-74 BASIC +* VTL-2 +* Apple IIe (CC65, text mode) +* Commodore 64 (CC65, text mode) +* Free Pascal (CLI) + +### Some asset design required + +* Tcl/Tk GUI +* Free Pascal + Lazarus GUI + +### Ultimate goals + +* CHIP-8 (standard mode) +* NES (CC65, text/pseudo-graphics mode) +* Sega Mega Drive (SGDK) + +### Hypothetical ports + +Not sure if necessary: + +* POSIX sh without AWK +* Native Android + +Not sure if physically possible: + +* HP 12C +* Elektronika MK52/MK61 + +## Credits + +Original Scoundrel game concept and ruleset developed in 2011 by Zach Gage and Kurt Bieg. + +All Scoundrel ports in this repo were created by Luxferre and released into public domain with no warranties. diff --git a/scoundrel.c b/scoundrel.c new file mode 100644 index 0000000..ff4799e --- /dev/null +++ b/scoundrel.c @@ -0,0 +1,323 @@ +/** + Scoundrel.C: a C89 port of the Scoundrel roguelike card game + using the official ruleset only + Build: cc -std=c89 -O2 scoundrel.c -o scoundrel + Created by Luxferre in 2026, released into public domain +*/ + +#include +#include +#include +#include + +/* UI choices */ +typedef enum choices { CH1=0, CH2, CH3, CH4, CH_R, CH_Q, CH_INVAL } choice; + +/** +Deck/room representation: +0 is empty (this value cannot be in the deck, only in the room), +1 to 13 is a monster, +14 to 22 is a weapon, +23 to 31 is a potion + +Monster level: x + 1 +Weapon level: x - 12 +Potion level: x - 21 +*/ + +/* Game stats */ + +static char deck[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, /* monster pack 1 (clubs) */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, /* monster pack 2 (spades) */ + 14, 15, 16, 17, 18, 19, 20, 21, 22, /* weapon pack (diamonds) */ + 23, 24, 25, 26, 27, 28, 29, 30, 31 /* potion pack (hearts) */ + }, + room[] = {0, 0, 0, 0}, deck_idx = 0, + hp = 20, weapon = 0, durability = 0, can_run = 1, + engaged = 0, potion_drank = 0, last_potion_val = 0; + +/* Output method */ +void print_msg(char *s) { + puts(s); +} + +/* Input method */ +choice get_choice() { + char c; + choice res; + printf("Your choice: "); + scanf(" %c", &c); + switch(c) { + case 'a': + case 'A': + res = CH1; + break; + case 'b': + case 'B': + res = CH2; + break; + case 'c': + case 'C': + res = CH3; + break; + case 'd': + case 'D': + res = CH4; + break; + case 'r': + case 'R': + res = CH_R; + break; + case 'q': + case 'Q': + res = CH_Q; + break; + default: + res = CH_INVAL; + } + return res; +} + +/* Confirmation method */ +int confirm(char *prompt) { + char c, d; + printf("%s", prompt); + c = getchar(); + scanf(" %c", &c); + if(c == 'y' || c == 'Y') d = 1; + else d = 0; + return d; +} + +/* Stats display method */ +void display_stats() { + printf("---------------\nHP:%02d W:%02d D:%02d\n---------------\n", + hp, weapon, durability); +} + +/* Room display method */ +void display_room() { + unsigned int i, v; + for(i=0;i<4;i++) { + v = room[i]; + if(v == 0) printf(" "); /* just 4 spaces */ + else if(v < 14) printf("M%02d ", v + 1); + else if(v < 23) printf("W%02d ", v - 12); + else printf("P%02d ", v - 21); + } + printf("\n a b c d"); + if(can_run) printf(" r)un->"); + printf("\n"); +} + +/* Array shuffle method */ +void shuffle(char *array, size_t n) { + unsigned int i, j, t; + if(n > 1) { + for(i=n-1;i>0;i--) { + j = (unsigned int) (rand()%(i+1)); + t = array[j]; + array[j] = array[i]; + array[i] = t; + } + } +} + +/* Room item handling methods */ + +void handle_monster(int val) { + int healthlost = val; + printf("You fight a %d-level monster.\n", val); + if(weapon > 0 && val <= durability) { + int weapondiff = val - weapon; + if(weapondiff < 0) weapondiff = 0; + printf("You'll lose %d HP if fighting with bare hands.\n", val); + printf("You'll lose %d HP if using your weapon.\n", weapondiff); + if(confirm("Use weapon? (y/n) ")) { + healthlost = weapondiff; + durability = val - 1; + if(durability < 2) { + print_msg("You lose your weapon."); + durability = weapon = 0; + } + else printf("Your weapon's durability is now %d.\n", durability); + } + } + hp -= healthlost; + if(hp < 0) hp = 0; + printf("You lost %d HP. New HP: %d.\n", healthlost, hp); +} + +void handle_weapon(int val) { + weapon = val; + printf("You equip a %d-level weapon.\n", weapon); + durability = 14; /* reset durability to max limit */ +} + +void handle_potion(int val) { + if(potion_drank) { + print_msg("You already drank a potion in this room. Discarding."); + } else { + potion_drank = 1; + hp += val; + if(hp > 20) hp = 20; + printf("You drink a %d-level potion. New HP: %d.\n", val, hp); + if(deck_idx >= sizeof(deck) || deck[deck_idx] == 0) + last_potion_val = val; + } +} + +/* Main gameplay */ +int game() { + unsigned int i, j; + /* Shuffle the deck */ + shuffle(deck, sizeof(deck)); + deck_idx = 0; /* reset the deck index */ + /* prepare all other parameters */ + hp = 20; + can_run = 1; + weapon = durability = engaged = 0; + potion_drank = last_potion_val = 0; + choice ch; + int item, room_sum; /* true item value */ + int last_room = 0, last_room_clr = 0; /* last room markers */ + + while(hp > 0 && !last_room_clr) { + /* fill in the new room unless engaged */ + if(!engaged) { + for(i=0;i= sizeof(deck) || deck[deck_idx] == 0) { + last_room = 1; + can_run = 0; + } else { + room[i] = deck[deck_idx]; + deck[deck_idx] = 0; /* remove from deck */ + deck_idx++; + /* Check immediately if that was the last card */ + if(deck_idx >= sizeof(deck) || deck[deck_idx] == 0) { + last_room = 1; + can_run = 0; + } + } + } + } + } + + /* display the stats and room */ + display_stats(); + display_room(); + ch = get_choice(); + + /* Logic validation */ + if(ch == CH_R) { + if(!can_run || engaged) ch = CH_INVAL; /* Cannot run if disabled or engaged */ + } else if(ch < CH_R && !room[(int)ch]) { + ch = CH_INVAL; /* empty item */ + } + + if(ch == CH_Q) { + hp = 0; + print_msg("Quitting..."); + break; + } else if(ch == CH_INVAL) { + print_msg("Invalid input!"); + continue; + } else if(ch == CH_R) { /* run mechanics */ + /* Compact and rearrange the deck */ + char temp_deck[sizeof(deck)]; + int t_idx = 0; + + /* 1. Collect all valid remaining cards from deck */ + for(i=0; i 0) { + if(room_sum == 0) { + if(last_room) last_room_clr = 1; + else { + print_msg("Room clear, moving on..."); + engaged = 0; + potion_drank = 0; + can_run = 1; + } + } else if(!last_room && engaged == 3) { + print_msg("Room clear, moving on..."); + engaged = 0; + potion_drank = 0; + can_run = 1; + } + } + } + } + /* Score the result */ + int score = 0; + if(hp > 0) { /* win */ + score = hp; + if(hp == 20) score += last_potion_val; + } else { /* fail */ + for(i=0;i 0) { + printf("You win! Your score: %d\n", score); + } else { + printf("You lose! Your score: %d\n", score); + } + return 0; +}