From 15f90eac1ab32838dd613e10cd2dadc52f327340 Mon Sep 17 00:00:00 2001 From: Luxferre Date: Sat, 30 May 2026 10:12:15 +0300 Subject: [PATCH] added Scoundrel port --- README.md | 3 +- scoundrel | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100755 scoundrel diff --git a/README.md b/README.md index 2c3dd45..8a86485 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ For POSIX shell compatible scripts, see the [sh-goodies](https://codeberg.org/lu ## List +* [bmux](./bmux): a simple, dmux-like session manager in Bash on top of just dtach * [YouStore](./ys): a way to securely store any set of files/directories inside a video * [1DLife](./1dlife): Jon Millen's 1D Game of Life in pure Bash * [Elem](./elem): Stephen Wolfram's elementary cellular automatons in pure Bash -* [bmux](./bmux): a simple, dmux-like session manager in Bash on top of just dtach +* [Scoundrel](./scoundrel): A Bash port of the Scoundrel roguelike solitaire game diff --git a/scoundrel b/scoundrel new file mode 100755 index 0000000..845dfc1 --- /dev/null +++ b/scoundrel @@ -0,0 +1,164 @@ +#!/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