#!/usr/bin/env bash # SHoundrel: a Bash version of Scoundrel roguelike solitaire # Created by Luxferre in 2026, released into public domain # helpers get_choice() { local c read -r -p "$1" -n 1 c printf '%s' "${c@L}" } get_valid_choice() { local pr="$1" vcs="$2" c="_" until [[ "$vcs" == *$c* ]]; do c="$(get_choice "$pr")" done echo $c } # variables and states declare -i score=-208 hp=20 can_run=1 engaged=0 drank=0 \ weapon=0 durability=0 rooms_clrd=0 last_potion_val=0 declare -a deck room=() mapfile -t deck < <(shuf -e {1..13} {1..31}) # game logic functions game_field_display() { local -i x echo '--------------------------' printf 'H%02d | W%02d | D%02d | Deck: %02d\n' $hp $weapon $durability ${#deck[@]} echo '--------------------------' for x in "${room[@]}"; do if ((x < 14)); then printf 'M%02d ' $((x + 1)) elif ((x < 23)); then printf 'W%02d ' $((x - 12)) else printf 'H%02d ' $((x - 21)) fi done printf '\n %s' '1 2 3 4' if ((can_run && !engaged)); then printf '%s' ' r)un->' fi echo } # monster handling logic (adds monster value to the score) handle_monster() { local -i val="$1" healthlost="$1" printf 'You encounter a monster of strength %d.\n' $val if ((weapon > 0 && durability >= val)); then # can use weapon if [[ "$(get_valid_choice 'Use weapon? (y/n) ' 'yn')" = 'y' ]]; then healthlost=$((val - weapon)) ((healthlost < 0)) && healthlost=0 printf '\nYou fight the monster with your %d-level weapon.\n' $weapon durability=$((val - 1)) if ((durability < 2)); then durability=0 weapon=0 echo 'Your weapon fully breaks!' fi else # barehanded encounter printf '\nYou fight the monster barehanded.\n' fi else # barehanded encounter printf '\nYou fight the monster barehanded.\n' fi ((hp -= healthlost)) ((hp < 0)) && hp=0 printf 'Lost %d HP. New HP: %d\n' $healthlost $hp ((score += val)) } # weapon handling logic handle_weapon() { weapon="$1" durability=14 printf 'Equipped a %d-level weapon.\n' $weapon } # potion handling logic handle_potion() { if ((drank > 0)); then echo 'Already drank a potion here! Discarding...' else local -i val="$1" ((${#room[@]} == 1 && ${#deck[@]} == 0)) && last_potion_val=$val ((hp += val)) ((hp > 20)) && hp=20 drank=1 printf 'Drank a %d-level potion. New HP: %d\n' $val $hp fi } # main game loop declare choice declare -i item idx echo '----- Scoundrel 2026 -----' echo ' Bash port by Luxferre' echo ' Good luck!' while (( (${#deck[@]} + ${#room[@]}) > 0 && hp > 0 )); do if (( !engaged )); then # build the room while (( ${#deck[@]} > 0 && ${#room[@]} < 4 )); do room+=("${deck[0]}") unset 'deck[0]' deck=("${deck[@]}") done fi game_field_display if ((can_run == 1 && engaged == 0)); then choice=$(get_valid_choice 'Your choice: ' '1234r') else choice=$(get_valid_choice 'Your choice: ' '1234') fi echo if [[ "$choice" = 'r' ]] ; then # running logic can_run=0 # cannot run twice in a row while (( ${#room[@]} > 0 )); do idx=$((RANDOM % ${#room[@]})) deck+=("${room[$idx]}") unset 'room[idx]' room=("${room[@]}") done echo 'You run from the room.' else # engagement logic ((-- choice)) item=${room[$choice]} if ((item > 0)); then engaged=1 unset 'room[choice]' room=("${room[@]}") if ((item < 14)); then handle_monster $((item + 1)) elif ((item < 23)); then handle_weapon $((item - 12)) else handle_potion $((item - 21)) fi if (( ${#deck[@]} > 0 && ${#room[@]} == 1 )); then can_run=1 engaged=0 drank=0 ((++ rooms_clrd)) fi fi fi done # now score and output the game result if ((hp > 0)); then score=$hp ((hp == 20)) && ((score += last_potion_val)) ((++ rooms_clrd)) printf 'You win! ' else printf 'You lose! ' fi printf 'Your score: %d. Rooms cleared: %d\n' $score $rooms_clrd