/** scoundrel.c: a C89 port of the Scoundrel roguelike card game using the official ruleset only See the README.md file for platform-specific build instructions Created by Luxferre in 2026, released into public domain */ #include #include #include #if defined(__CC65__) || defined(__Z80__) #define RETRO_IMPL #endif #ifdef RETRO_IMPL #include #else #include #endif char scanchar() { char c; scanf(" %c", &c); return c; } void randomize() { #ifdef RETRO_IMPL unsigned int entropy; #ifdef _randomize _randomize(); #endif entropy = rand(); clrscr(); puts("Press any key to start..."); while(!kbhit()) entropy++; while(kbhit()) cgetc(); #ifdef SPECTRUM while(!kbhit()) entropy++; #endif srand(entropy); clrscr(); #else srand(time(NULL)); #endif } /* UI choices */ #define CH1 0 #define CH2 1 #define CH3 2 #define CH4 3 #define CH_R 4 #define CH_Q 5 #define CH_INVAL 6 /** 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 */ /* main deck and room */ static char deck[44], room[] = {0, 0, 0, 0}; static short 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 */ char get_choice() { char c, res; printf("Your choice: "); c = scanchar(); 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; } printf("\n"); return res; } /* Confirmation method */ int confirm(char *prompt) { char c, d; printf("%s", prompt); c = scanchar(); if(c == 'y' || c == 'Y') d = 1; else d = 0; printf("\n"); 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 */ int 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("Without weapon: lose %d HP.\n", val); printf("With weapon: lose %d HP.\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); return val; } 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; int score = -208; /* 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 */ /* 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; /* 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; for(i=0;i 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 */ if(hp > 0) { /* win */ score = hp; if(hp == 20) score += last_potion_val; } return score; } /* Entry point */ int main() { int score; randomize(); while(1) { print_msg("\n===============\nScoundrel--2026\n by Luxferre\n===============\n Good luck!"); 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; } return 0; }