Files
scoundrel-ports/c/scoundrel.c
T

369 lines
8.7 KiB
C
Raw Normal View History

2026-01-06 11:08:43 +02:00
/**
2026-01-06 14:13:17 +02:00
scoundrel.c: a C89 port of the Scoundrel roguelike card game
2026-01-06 11:08:43 +02:00
using the official ruleset only
2026-01-06 14:13:17 +02:00
See the README.md file for platform-specific build instructions
2026-01-06 11:08:43 +02:00
Created by Luxferre in 2026, released into public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2026-01-07 00:46:52 +02:00
#if defined(__CC65__) || defined(__Z80__)
#define RETRO_IMPL
#endif
2026-01-07 09:07:35 +02:00
#ifdef RETRO_IMPL
#include <conio.h>
#else
2026-01-06 11:08:43 +02:00
#include <time.h>
2026-01-06 14:13:17 +02:00
#endif
2026-01-06 11:08:43 +02:00
2026-01-07 00:46:52 +02:00
char scanchar() {
char c;
scanf(" %c", &c);
return c;
}
void randomize() {
2026-01-07 09:07:35 +02:00
#ifdef RETRO_IMPL
unsigned int entropy;
#ifdef _randomize
_randomize();
2026-01-07 00:46:52 +02:00
#endif
2026-01-07 09:07:35 +02:00
entropy = rand();
clrscr();
puts("Press any key to start...");
while(!kbhit()) entropy++;
2026-01-07 10:45:25 +02:00
while(kbhit()) cgetc();
#ifdef SPECTRUM
while(!kbhit()) entropy++;
#endif
2026-01-07 09:07:35 +02:00
srand(entropy);
2026-01-07 09:11:41 +02:00
clrscr();
2026-01-07 09:07:35 +02:00
#else
srand(time(NULL));
#endif
}
2026-01-07 00:46:52 +02:00
2026-01-06 11:08:43 +02:00
/* UI choices */
2026-01-06 14:13:17 +02:00
#define CH1 0
#define CH2 1
#define CH3 2
#define CH4 3
#define CH_R 4
#define CH_Q 5
#define CH_INVAL 6
2026-01-06 11:08:43 +02:00
/**
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 */
2026-01-06 22:56:59 +02:00
/* main deck and room */
static char deck[44], room[] = {0, 0, 0, 0};
2026-01-06 14:13:17 +02:00
static short deck_idx = 0, hp = 20,
weapon = 0, durability = 0, can_run = 1,
2026-01-06 11:08:43 +02:00
engaged = 0, potion_drank = 0, last_potion_val = 0;
/* Output method */
void print_msg(char *s) {
puts(s);
}
/* Input method */
2026-01-06 14:13:17 +02:00
char get_choice() {
char c, res;
2026-01-06 11:08:43 +02:00
printf("Your choice: ");
2026-01-07 00:46:52 +02:00
c = scanchar();
2026-01-06 11:08:43 +02:00
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;
}
2026-01-06 16:57:08 +02:00
printf("\n");
2026-01-06 11:08:43 +02:00
return res;
}
/* Confirmation method */
int confirm(char *prompt) {
char c, d;
printf("%s", prompt);
2026-01-07 00:46:52 +02:00
c = scanchar();
2026-01-06 11:08:43 +02:00
if(c == 'y' || c == 'Y') d = 1;
else d = 0;
2026-01-06 16:57:08 +02:00
printf("\n");
2026-01-06 11:08:43 +02:00
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;
2026-01-06 14:13:17 +02:00
printf("Without weapon: lose %d HP.\n", val);
printf("With weapon: lose %d HP.\n", weapondiff);
2026-01-06 11:08:43 +02:00
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() {
2026-01-06 14:13:17 +02:00
unsigned int i;
int score = 0; /* score storage */
int item, room_sum; /* true item value */
int last_room = 0, last_room_clr = 0; /* last room markers */
char ch;
deck_idx = 0; /* reset the deck index */
2026-01-06 22:56:59 +02:00
/* Prefill the deck */
for(i=0;i<13;i++) deck[i] = deck[i+13] = i+1;
for(i=26;i<44;i++) deck[i] = i - 12;
2026-01-06 11:08:43 +02:00
/* Shuffle the deck */
shuffle(deck, sizeof(deck));
/* prepare all other parameters */
hp = 20;
can_run = 1;
weapon = durability = engaged = 0;
potion_drank = last_potion_val = 0;
2026-01-06 14:13:17 +02:00
for(i=0;i<sizeof(room);i++) room[i] = 0; /* reset the room */
2026-01-06 11:08:43 +02:00
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++) {
2026-01-06 22:56:59 +02:00
if(deck[i] != 0) temp_deck[t_idx++] = deck[i];
2026-01-06 11:08:43 +02:00
}
/* 2. Shuffle room cards and append to the end */
shuffle(room, sizeof(room));
for(i=0; i<sizeof(room); i++) {
2026-01-06 22:56:59 +02:00
if(room[i] != 0) {
temp_deck[t_idx++] = room[i];
room[i] = 0; /* Clear room */
}
2026-01-06 11:08:43 +02:00
}
/* 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 */
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() {
2026-01-06 14:13:17 +02:00
int score;
2026-01-07 00:46:52 +02:00
randomize();
2026-01-06 14:13:17 +02:00
while(1) {
print_msg("\n===============\nScoundrel--2026\n by Luxferre\n===============\n Good luck!");
2026-01-06 14:13:17 +02:00
score = game();
if(score > 0) {
printf("You win! Your score: %d\n", score);
} else {
printf("You lose! Your score: %d\n", score);
}
if(!confirm("Play again? (y/n) ")) break;
2026-01-06 11:08:43 +02:00
}
return 0;
}