Files
scoundrel-ports/janet/scoundrel.janet
T

194 lines
5.5 KiB
Janet
Raw Normal View History

2026-03-30 19:43:13 +03:00
#!/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)))
2026-03-30 19:43:13 +03:00
(= 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)
2026-03-30 19:43:13 +03:00
(<= (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)))