#!/usr/bin/env janet # Scoundrel roguelike game port to Janet # Created by Luxferre in 2026, released into public domain # helper method section # RNG helpers (def rng (math/rng (os/cryptorand 8))) (defn randint [max] (math/rng-int rng max)) # Shuffle helper (defn shuffle [vec] (def l (length vec)) (var j 0) (var t 0) (for i 1 l (set j (randint (+ i 1))) (set t (vec i)) (put vec i (vec j)) (put vec j t))) # Read a single-letter choice from the stdin (defn get-choice [prompt] (let [rawc (getline prompt)] (if (> (length rawc) 0) (string/ascii-lower (string/slice rawc 0 1)) nil))) # Ask user for y/n confirmation (with custom prompt) (defn get-confirm [prompt] (var resp nil) (var valid false) (while (not valid) (set resp (get-choice prompt)) (set valid (string/check-set "yn" resp))) (= resp "y")) # data section # The Deck (def deck (array/concat (range 1 14) (range 1 32))) # The Room (def room (array/new 4)) # The State (def 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 (defn game-field-display [] (print "--------------------------") (printf "H%02d | W%02d | D%02d | Deck: %02d" (state :hp) (state :weapon) (state :durability) (length deck)) (print "--------------------------") (each x room (cond (< 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 (defn get-valid-choice [] (var valid false) (var ch "") (while (not valid) (set ch (get-choice "Your choice: ")) (set valid (cond (= ch "r") (and (state :can-run) (not (state :engaged))) (string/check-set "1234" ch) (<= (scan-number ch) (length room)) false))) ch) # monster handling logic (adds monster value to the score) (defn 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) (put state :durability (- val 1)) (if (< (state :durability) 2) (do (put state :weapon 0) (put state :durability 0)))) (print "You fight the monster barehanded."))) ( # barehanded encounter print "You fight the monster barehanded." )) (-= (state :hp) healthlost) (if (< (state :hp) 0) (put state :hp 0)) (printf "Lost %d HP. New HP: %d" healthlost (state :hp)) val) # weapon handling logic (adds 0 score) (defn handle-weapon [val] (put state :weapon val) (put state :durability 14) (printf "Equipped a %d-level weapon." val) 0) # potion handling logic (adds 0 score) (defn handle-potion [val] (if (state :drank) (print "Already drank a potion here! Discarding...") (do (if (and (= (length room) 0) (= (length deck) 0)) (set last-potion-val val)) (+= (state :hp) val) (if (> (state :hp) 20) (put state :hp 20)) (put state :drank true) (printf "Drank a %d-level potion. New HP: %d" val (state :hp)))) 0) # entrypoint (defn main [& args] # print the banner (print "----- Scoundrel 2026 -----") (print " Janet 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)) (array/push room (array/pop deck)))) (game-field-display) (set choice (get-valid-choice)) (if (= choice "r") (do # running logic (put state :can-run false) # cannot run twice in a row (shuffle room) # shuffle this room (while (> (length room) 0) (array/insert deck 0 (array/pop room))) (print "You run from the room.") ) (do # engagement logic (put state :engaged true) # get the current item from the choice character (set itemidx (- (scan-number choice) 1)) (set item (get room itemidx)) # remove the element under itemidx from the room (array/remove room itemidx) # handling routines may or may not modify your score (+= (state :score) (cond (< 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 (put state :can-run true) (put state :engaged false) (put state :drank false) (++ (state :rooms-cleared))))))) # now score and output the game result (if (> (state :hp) 0) (do # win condition (put state :score (state :hp)) (if (= (state :hp) 20) (+= (state :score) last-potion-val)) (prin "You win!")) (prin "You lose!")) (printf " Your score: %d. Rooms cleared: %d" (state :score) (state :rooms-cleared)))