#!/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)
