initial upload

This commit is contained in:
Luxferre
2026-01-06 11:08:43 +02:00
commit 63aac0e6b3
2 changed files with 375 additions and 0 deletions
+52
View File
@@ -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.
+323
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* 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(room);i++) {
/* fill only if empty */
if(!room[i]) {
/* Check if deck is empty or exhausted */
if(deck_idx >= 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<sizeof(deck); i++) {
if(deck[i] != 0) temp_deck[t_idx++] = deck[i];
}
/* 2. Shuffle room cards and append to the end */
shuffle(room, sizeof(room));
for(i=0; i<sizeof(room); i++) {
if(room[i] != 0) {
temp_deck[t_idx++] = room[i];
room[i] = 0; /* Clear room */
}
}
/* 3. Rebuild deck */
memset(deck, 0, sizeof(deck));
for(i=0; i<t_idx; i++) deck[i] = temp_deck[i];
/* 4. Reset state */
deck_idx = 0; /* Reset index to start of compacted deck */
can_run = 0; /* can't run twice in a row */
engaged = 0;
last_room = 0; /* Since we added cards, it's not the last room anymore */
} else { /* engagement mechanics */
engaged++; /* increase the amount of engaged cards */
can_run = 0; /* Cannot run once engaged */
item = room[(int)ch]; /* get the item from the room */
room[(int)ch] = 0; /* mark the room slot as empty */
/* Note: We do NOT put the item back in deck. It is consumed. */
if(item < 14) { /* monster */
handle_monster(item + 1);
} else if(item < 23) { /* weapon */
handle_weapon(item - 12);
} else { /* potion */
handle_potion(item - 21);
}
/* condition to move to the next room */
room_sum = 0;
for(i=0;i<sizeof(room);i++) room_sum += room[i];
if(hp > 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<sizeof(room);i++)
if(room[i] && room[i] < 14)
score -= (room[i] + 1);
for(i=0;i<sizeof(deck);i++)
if(deck[i] && deck[i] < 14)
score -= (deck[i] + 1);
}
return score;
}
/* Entry point */
int main() {
print_msg("===============\nScoundrel--2026\n by Luxferre\n===============\n Good luck!");
srand(time(NULL));
int score = game();
if(score > 0) {
printf("You win! Your score: %d\n", score);
} else {
printf("You lose! Your score: %d\n", score);
}
return 0;
}