added Fennel port

This commit is contained in:
Luxferre
2026-03-31 16:47:55 +03:00
parent 9af0374c93
commit 587801bb59
2 changed files with 204 additions and 0 deletions
+203
View File
@@ -0,0 +1,203 @@
#!/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)