Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33b640f9ea | ||
|
|
cc15e10652 | ||
|
|
4d7c6aeb2f | ||
|
|
3ef673cf21 | ||
|
|
b7ddaf3c17 | ||
|
|
587801bb59 | ||
|
|
9af0374c93 |
@@ -7,11 +7,15 @@ This is a collection of my own ports of the [Scoundrel](http://stfj.net/art/2011
|
|||||||
* [ANSI C89 port](c/scoundrel.c) - also compatible with CC65 and Z88DK (use the provided Makefile to build Scoundrel for Commodore 16/64/128/Plus-4/PET, Atari 400/800/XL/XE, ZX Spectrum, MSX and Apple II/IIe; a JRE installation on the host system is required to build Apple disk images)
|
* [ANSI C89 port](c/scoundrel.c) - also compatible with CC65 and Z88DK (use the provided Makefile to build Scoundrel for Commodore 16/64/128/Plus-4/PET, Atari 400/800/XL/XE, ZX Spectrum, MSX and Apple II/IIe; a JRE installation on the host system is required to build Apple disk images)
|
||||||
* [NES port](nes/nescoundrel.c) - gamepad-oriented, considered stable but some impovements may come
|
* [NES port](nes/nescoundrel.c) - gamepad-oriented, considered stable but some impovements may come
|
||||||
* [Game Boy port](gb/gbscoundrel.c) - gamepad-oriented, considered stable but some impovements may come
|
* [Game Boy port](gb/gbscoundrel.c) - gamepad-oriented, considered stable but some impovements may come
|
||||||
|
* [Janet port](janet/scoundrel.janet) - close to C89 port but not fully identical
|
||||||
|
* [Fennel port](fennel/scoundrel.fnl) - functionally identical to the Janet port
|
||||||
|
* [Jim Tcl port](scoundrel-jim.tcl) - functionally close to the Bash port
|
||||||
|
|
||||||
## Ports NOT included in this repo for various reasons
|
## Ports NOT included in this repo for various reasons
|
||||||
|
|
||||||
* [(Circuit)Python port](https://codeberg.org/luxferre/t-deckard/src/branch/master/game/scoundrel.py) (as a part of the T-DeckARD suite)
|
* [(Circuit)Python port](https://codeberg.org/luxferre/t-deckard/src/branch/master/game/scoundrel.py) (as a part of the T-DeckARD suite)
|
||||||
* [HTML+CSS+JS port](https://ironlynx.neocities.org/scoundrel) (hosted as a single HTML file with readable source code)
|
* [HTML+CSS+JS port](https://ironlynx.neocities.org/scoundrel) (hosted as a single HTML file with readable source code)
|
||||||
|
* [Bash shell port](https://codeberg.org/luxferre/bash-goodies/src/branch/master/scoundrel)
|
||||||
|
|
||||||
## Currently planned ports
|
## Currently planned ports
|
||||||
|
|
||||||
|
|||||||
@@ -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) 0) (= (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)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Makefile for Scoundrel Janet port JPM build
|
||||||
|
|
||||||
|
JC := jpm build --build-type=release --ldflags="-s"
|
||||||
|
|
||||||
|
all: clean
|
||||||
|
$(JC)
|
||||||
|
|
||||||
|
static: clean
|
||||||
|
$(JC) --janet-cflags="-static"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
jpm clean
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
(declare-project
|
||||||
|
:name "scoundrel"
|
||||||
|
:description "A Janet port of Scoundrel roguelike solitaire game")
|
||||||
|
|
||||||
|
(declare-source
|
||||||
|
:source ["scoundrel.janet"])
|
||||||
|
|
||||||
|
(declare-executable
|
||||||
|
:name "scoundrel"
|
||||||
|
:entry "scoundrel.janet"
|
||||||
|
:install true)
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
#!/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)))
|
||||||
Executable
+172
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env jimsh
|
||||||
|
# Scoundrel roguelike solitaire game port to Jim Tcl
|
||||||
|
# Created by Luxferre in 2026, released into the public domain
|
||||||
|
|
||||||
|
# helpers
|
||||||
|
|
||||||
|
proc shuffle list {
|
||||||
|
set n [llength $list]
|
||||||
|
loop i 1 $n {
|
||||||
|
set j $(int(rand() * $n))
|
||||||
|
set temp [lindex $list $i]
|
||||||
|
lset list $i [lindex $list $j]
|
||||||
|
lset list $j $temp
|
||||||
|
}
|
||||||
|
return $list
|
||||||
|
}
|
||||||
|
|
||||||
|
proc get_valid_choice {prompt pat} {
|
||||||
|
set c --
|
||||||
|
puts -nonewline [format "%s " $prompt]
|
||||||
|
stdout flush
|
||||||
|
stdin tty input raw
|
||||||
|
while {$c ni $pat} {
|
||||||
|
set c [string tolower [stdin read 1]]
|
||||||
|
}
|
||||||
|
stdin tty input cooked
|
||||||
|
return $c
|
||||||
|
}
|
||||||
|
|
||||||
|
# variables and states
|
||||||
|
set score -208
|
||||||
|
set hp 20
|
||||||
|
set can_run 1
|
||||||
|
set engaged 0
|
||||||
|
set drank 0
|
||||||
|
set weapon 0
|
||||||
|
set durability 0
|
||||||
|
set rooms_clrd 0
|
||||||
|
set last_potion_val 0
|
||||||
|
|
||||||
|
# deck and room
|
||||||
|
set deck [shuffle [concat [range 1 14] [range 1 32]]]
|
||||||
|
set room {}
|
||||||
|
|
||||||
|
# game logic functions
|
||||||
|
|
||||||
|
proc game_field_display {} {
|
||||||
|
global hp weapon durability room deck can_run engaged
|
||||||
|
puts "--------------------------"
|
||||||
|
puts [format "H%02d | W%02d | D%02d | Deck: %02d" $hp $weapon $durability [llength $deck]]
|
||||||
|
puts "--------------------------"
|
||||||
|
foreach x $room {
|
||||||
|
if $($x < 14) {
|
||||||
|
puts -nonewline [format "M%02d " $($x + 1)]
|
||||||
|
} elseif $($x < 23) {
|
||||||
|
puts -nonewline [format "W%02d " $($x - 12)]
|
||||||
|
} else {
|
||||||
|
puts -nonewline [format "H%02d " $($x - 21)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
puts -nonewline "\n 1 2 3 4"
|
||||||
|
if {$can_run == 1 && $engaged == 0} {puts -nonewline " r)un->"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# monster handling logic (adds monster value to the score)
|
||||||
|
proc handle_monster {val} {
|
||||||
|
global weapon durability hp score
|
||||||
|
set healthlost $val
|
||||||
|
puts [format "You encounter a monster of strength %d." $val]
|
||||||
|
if {$weapon > 0 && $durability >= $val} {
|
||||||
|
if {[get_valid_choice "Use weapon? (y/n)" "y n"] == "y"} {
|
||||||
|
set healthlost $($val - $weapon)
|
||||||
|
if {$healthlost < 0} {set healthlost 0}
|
||||||
|
puts [format "\nYou fight the monster with your %d-level weapon." $weapon]
|
||||||
|
set durability $($val - 1)
|
||||||
|
if {$durability < 2} {
|
||||||
|
set healthlost 0
|
||||||
|
set weapon 0
|
||||||
|
puts "Your weapon fully breaks!"
|
||||||
|
}
|
||||||
|
} else {puts "\nYou fight the monster barehanded."}
|
||||||
|
} else {puts "You fight the monster barehanded."}
|
||||||
|
set hp $($hp - $healthlost)
|
||||||
|
if {$hp < 0} {set hp 0}
|
||||||
|
puts [format "Lost %d HP. New HP: %d" $healthlost $hp]
|
||||||
|
incr score $val
|
||||||
|
}
|
||||||
|
|
||||||
|
# weapon handling logic
|
||||||
|
proc handle_weapon {val} {
|
||||||
|
global weapon durability
|
||||||
|
set weapon $val
|
||||||
|
set durability 14
|
||||||
|
puts [format "Equipped a %d-level weapon." $weapon]
|
||||||
|
}
|
||||||
|
|
||||||
|
# potion handling logic
|
||||||
|
proc handle_potion {val} {
|
||||||
|
global hp drank deck room
|
||||||
|
if {$drank > 0} {
|
||||||
|
puts "Already drank a potion here! Discarding..."
|
||||||
|
} else {
|
||||||
|
if {[llength $deck] == 0 && [llength $room] == 0} {set last_potion_val $val}
|
||||||
|
incr hp $val
|
||||||
|
if {$hp > 20} {set hp 20}
|
||||||
|
set drank 1
|
||||||
|
puts [format "Drank a %d-level potion. New HP: %d" $val $hp]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# game banner
|
||||||
|
puts "----- Scoundrel 2026 -----"
|
||||||
|
puts " Jim Tcl port by Luxferre"
|
||||||
|
puts " Good luck!"
|
||||||
|
|
||||||
|
# main game loop
|
||||||
|
while {([llength $deck] + [llength $room]) > 0 && $hp > 0} {
|
||||||
|
if {$engaged == 0} {
|
||||||
|
while {[llength $deck] > 0 && [llength $room] < 4} {
|
||||||
|
lappend room [lindex $deck 0]
|
||||||
|
set deck [lreplace $deck 0 0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
game_field_display
|
||||||
|
set chs {1 2 3 4 q}
|
||||||
|
if {$can_run == 1 && $engaged == 0} {set chs {1 2 3 4 q r}}
|
||||||
|
set ch [get_valid_choice {} $chs]
|
||||||
|
puts {}
|
||||||
|
if {$ch == "q"} {
|
||||||
|
if {[get_valid_choice "Really quit? (y/n)" "y n"] == "y"} {
|
||||||
|
puts "\nQuitting!"
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
} elseif {$ch == "r"} {
|
||||||
|
set can_run 0
|
||||||
|
while {[llength $room] > 0} {
|
||||||
|
set idx [rand 0 [llength $room]]
|
||||||
|
lappend deck [lindex $room $idx]
|
||||||
|
set room [lreplace $room $idx $idx]
|
||||||
|
}
|
||||||
|
puts "You run from the room."
|
||||||
|
} else {
|
||||||
|
incr ch -1
|
||||||
|
set item [lindex $room $ch]
|
||||||
|
if {$item > 0} {
|
||||||
|
set engaged 1
|
||||||
|
set room [lreplace $room $ch $ch]
|
||||||
|
if {$item < 14} {
|
||||||
|
handle_monster $($item + 1)
|
||||||
|
} elseif {$item < 23} {
|
||||||
|
handle_weapon $($item - 12)
|
||||||
|
} else {handle_potion $($item - 21)}
|
||||||
|
if {[llength $deck] > 0 && [llength $room] == 1} {
|
||||||
|
set can_run 1
|
||||||
|
set engaged 0
|
||||||
|
set drank 0
|
||||||
|
incr rooms_clrd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# now score and output the game result
|
||||||
|
if {$hp > 0} {
|
||||||
|
set score $hp
|
||||||
|
if {$hp == 20} {incr score $last_potion_val}
|
||||||
|
incr rooms_clrd
|
||||||
|
puts -nonewline "You win!"
|
||||||
|
} else {
|
||||||
|
puts -nonewline "You lose!"
|
||||||
|
}
|
||||||
|
puts [format " Your score: %d. Rooms cleared: %d" $score $rooms_clrd]
|
||||||
Reference in New Issue
Block a user