194 lines
5.5 KiB
Janet
194 lines
5.5 KiB
Janet
#!/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 (case resp "y" true "n" true false)))
|
||
|
|
(= 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)))
|
||
|
|
(has-value? ["1" "2" "3" "4"] 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) 1) (= (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)))
|