#!/usr/bin/env fennel ;; Scoundrel roguelike game port to Fennel ;; Created by Luxferre in 2026, released into public domain ; helper method section ; RNG helpers (fn randint [m] (math.random 0 (- m 1))) ; Shuffle helper (fn shuffle [vec] (local l (- (length vec) 1)) ; because for is inclusive (var j 0) (var t 0) (for [i 1 l] (set j (+ 1 (randint (+ 1 i)))) (set t (. vec (+ 1 i))) (tset vec (+ 1 i) (. vec j)) (tset vec j t))) ; Read a single-letter choice from the stdin (fn get-choice [prompt] (io.write prompt) (let [rawc (io.read)] (if (> (length rawc) 0) (string.lower (rawc:sub 1 1)) nil))) ; prin/prinf/printf emulation (fn prin [...] (io.write ...)) (fn prinf [fmt ...] (io.write (fmt:format ...))) (fn printf [fmt ...] (io.write (fmt:format ...)) (print)) ; Ask user for y/n confirmation (with custom prompt) (fn get-confirm [prompt] (var resp nil) (var valid false) (while (not valid) (set resp (get-choice prompt)) (set valid (case resp "y" true "n" true _ false))) (= resp "y")) ; data section ; The Deck (local deck (table.move (fcollect [i 1 13] i) 1 13 32 (fcollect [i 1 31] i))) ; The Room (local room {}) ; The State (local state { :score -208 :hp 20 :can-run true :engaged false :drank false :weapon 0 :durability 0 :rooms-cleared 0 }) ; last potion value (for special scoring) (var last-potion-val 0) ; game logic section ; display the current stats and room (fn game-field-display [] (print "--------------------------") (printf "H%02d | W%02d | D%02d | Deck: %02d" (. state :hp) (. state :weapon) (. state :durability) (length deck)) (print "--------------------------") (each [_ x (ipairs room)] (if (< x 14) (prinf "M%02d " (+ x 1)) (< x 23) (prinf "W%02d " (- x 12)) (prinf "P%02d " (- x 21)))) (prin "\n 1 2 3 4") (if (and (. state :can-run) (not (. state :engaged))) (prin " r)un->")) (print)) ; get valid user's choice (fn get-valid-choice [] (var valid false) (var ch "") (while (not valid) (set ch (get-choice "Your choice: ")) (set valid (if (= ch "r") (and (. state :can-run) (not (. state :engaged))) (or (= ch "1") (= ch "2") (= ch "3") (= ch "4")) (<= (tonumber ch) (length room)) false))) ch) ; monster handling logic (adds monster value to the score) (fn handle-monster [val] (var healthlost val) (printf "You encounter a monster of strength %d." val) (if (and (> (. state :weapon) 0) (>= (. state :durability) val)) (do ; can use weapon (if (get-confirm "Use weapon? (y/n) ") (do (var weapondiff (- val (. state :weapon))) (if (< weapondiff 0) (set weapondiff 0)) (printf "You fight the monster with your %d-level weapon." (. state :weapon)) (set healthlost weapondiff) (tset state :durability (- val 1)) (if (< (. state :durability) 2) (do (tset state :weapon 0) (tset state :durability 0)))) (print "You fight the monster barehanded."))) ( ; barehanded encounter print "You fight the monster barehanded." )) (tset state :hp (- (. state :hp) healthlost)) (if (< (. state :hp) 0) (tset state :hp 0)) (printf "Lost %d HP. New HP: %d" healthlost (. state :hp)) val) ; weapon handling logic (adds 0 score) (fn handle-weapon [val] (tset state :weapon val) (tset state :durability 14) (printf "Equipped a %d-level weapon." val) 0) ; potion handling logic (adds 0 score) (fn handle-potion [val] (if (. state :drank) (print "Already drank a potion here! Discarding...") (do (if (and (= (length room) 1) (= (length deck) 0)) (set last-potion-val val)) (tset state :hp (+ (. state :hp) val)) (if (> (. state :hp) 20) (tset state :hp 20)) (tset state :drank true) (printf "Drank a %d-level potion. New HP: %d" val (. state :hp)))) 0) ; entrypoint (fn main [& args] ; print the banner (print "----- Scoundrel 2026 -----") (print " Fennel port by Luxferre") (print " Good luck!") ; shuffle the deck (shuffle deck) ; start the main game loop (var choice nil) (var item nil) (var itemidx nil) (while (and (> (+ (length deck) (length room)) 0) (> (. state :hp) 0)) (if (not (. state :engaged)) ; build the room (while (and (< (length room) 4) (> (length deck) 0)) (table.insert room (table.remove deck)))) (game-field-display) (set choice (get-valid-choice)) (if (= choice "r") (do ; running logic (tset state :can-run false) ; cannot run twice in a row (shuffle room) ; shuffle this room (while (> (length room) 0) (table.insert deck 1 (table.remove room))) (print "You run from the room.") ) (do ; engagement logic (tset state :engaged true) ; get the current item from the choice character (set itemidx (tonumber choice)) ; in Fennel, they are 1-based (set item (. room itemidx)) ; remove the element under itemidx from the room (table.remove room itemidx) ; handling routines may or may not modify your score (tset state :score (+ (. state :score) (if (< item 14) (handle-monster (+ item 1)) (< item 23) (handle-weapon (- item 12)) (handle-potion (- item 21))))) ; now, evaluate whether we can move to the next room (if (and (= (length room) 1) (> (length deck) 0)) (do (tset state :can-run true) (tset state :engaged false) (tset state :drank false) (tset state :rooms-cleared (+ (. state :rooms-cleared) 1))))))) ; now score and output the game result (if (> (. state :hp) 0) (do ; win condition (tset state :score (. state :hp)) (if (= (. state :hp) 20) (tset state :score (+ (. state :score) last-potion-val))) (prin "You win!")) (prin "You lose!")) (printf " Your score: %d. Rooms cleared: %d" (. state :score) (. state :rooms-cleared))) (main)