Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33b640f9ea | ||
|
|
cc15e10652 | ||
|
|
4d7c6aeb2f | ||
|
|
3ef673cf21 | ||
|
|
b7ddaf3c17 | ||
|
|
587801bb59 | ||
|
|
9af0374c93 | ||
|
|
833c57631b | ||
|
|
69c3f46640 |
@@ -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)
|
||||
* [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
|
||||
* [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
|
||||
|
||||
* [(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)
|
||||
* [Bash shell port](https://codeberg.org/luxferre/bash-goodies/src/branch/master/scoundrel)
|
||||
|
||||
## 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)
|
||||
+16
-11
@@ -89,8 +89,14 @@ void game_startup() {
|
||||
}
|
||||
|
||||
/* Win screen display */
|
||||
static char* ranks[] = {"Survivor", "Master", "Epic", "Legend", "God"};
|
||||
void win_screen(s16 score) {
|
||||
u8 i, j;
|
||||
u8 i, j, r;
|
||||
if(score > 29) r = 4;
|
||||
else if(score > 24) r = 3;
|
||||
else if(score > 19) r = 2;
|
||||
else if(score > 9) r = 1;
|
||||
else r = 0;
|
||||
termreset();
|
||||
cputs( \
|
||||
" \x1d \x1c \x0e\x0e\x0e \x1c \x1c" NL
|
||||
@@ -102,9 +108,10 @@ void win_screen(s16 score) {
|
||||
cputsxy(0, 7, " You made it across");
|
||||
cputsxy(0, 8, " the dangerous");
|
||||
cputsxy(0, 9, " dungeon alive!");
|
||||
gotoxy(0, 11);
|
||||
cprintf(" Your score: ");
|
||||
printnum(score);
|
||||
gotoxy(1, 11);
|
||||
cprintf("Score: %d", score);
|
||||
gotoxy(SCREEN_WIDTH-3-strlen(ranks[r]), 11);
|
||||
cprintf("|%s|", ranks[r]);
|
||||
chlinexy(0, 12, SCREEN_WIDTH);
|
||||
cputs(" By: Luxferre, 2026");
|
||||
cputs(NL NL " Orig: Zach Gage," NL " Kurt Bieg, 2011");
|
||||
@@ -184,13 +191,9 @@ void render_room_item(u8 i, u8 v) {
|
||||
rect(topleft_x, topleft_y, cwidth, cheight);
|
||||
gotoxy(topleft_x + 1, topleft_y + 1);
|
||||
/* draw text */
|
||||
if(v < 14) {
|
||||
cputs("ENMY");
|
||||
} else if(v < 23) {
|
||||
cputs("WEAP");
|
||||
} else {
|
||||
cputs("POTI");
|
||||
}
|
||||
if(v < 14) cputs("ENMY");
|
||||
else if(v < 23) cputs("WEAP");
|
||||
else cputs("POTI");
|
||||
gotoxy(topleft_x + 2, topleft_y + 2);
|
||||
if(v < 14) printnum(v + 1);
|
||||
else if(v < 23) printnum(v - 12);
|
||||
@@ -368,8 +371,10 @@ s16 game() {
|
||||
}
|
||||
|
||||
if(ch == CH_Q) {
|
||||
if(confirm("End game?")) {
|
||||
hp = 0;
|
||||
break;
|
||||
} else print_msg("Go on!");
|
||||
} else if(ch == CH_INVAL) {
|
||||
print_msg("Invalid input!");
|
||||
beep_err();
|
||||
|
||||
@@ -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)))
|
||||
+18
@@ -37,6 +37,7 @@
|
||||
#define EZJOY_B 6
|
||||
#define EZJOY_SL 7
|
||||
#define EZJOY_ST 8
|
||||
#define EZJOY_COMBO 9
|
||||
|
||||
#define EZ_CORNER_UL (char) 20
|
||||
#define EZ_CORNER_UR (char) 18
|
||||
@@ -197,6 +198,23 @@ void wait_for_start_rand() {
|
||||
srand(_ez_entropy);
|
||||
}
|
||||
|
||||
/* wait for Start or a button combination */
|
||||
u8 wait_for_start_or_combo(u8 combo_mask) {
|
||||
u8 last_joy = 0, joy, pressed;
|
||||
/* wait until the previous key is released */
|
||||
while(joy_read(JOY_1));
|
||||
/* wait for a new press */
|
||||
while(1) {
|
||||
++_ez_entropy;
|
||||
joy = joy_read(JOY_1);
|
||||
pressed = joy & ~last_joy;
|
||||
if(pressed == combo_mask) return EZJOY_COMBO;
|
||||
if(pressed & JOY_BTN_4_MASK) return EZJOY_ST;
|
||||
last_joy = joy;
|
||||
waitvsync();
|
||||
}
|
||||
}
|
||||
|
||||
/* Generic helper methods */
|
||||
|
||||
/* Array shuffle method */
|
||||
|
||||
+27
-3
@@ -96,8 +96,18 @@ void game_startup() {
|
||||
}
|
||||
|
||||
/* Win screen display */
|
||||
static char* ranks[] = {"Survivor", "Master", "Epic", "Legend", "God"};
|
||||
static u8 sb1[] = { 6, 0x14, 3, 16, 0x75, 0, 0x1E, 7, 0x14, 0x1C, 0x1B, 0x1C, 0x14, 0x1B, 6, 0};
|
||||
static u8 sb2[] = { 0x13, 7, 0x1A, 0x18, 0x75, 15, 16, 0x16, 1, 0x14, 1, 0x1A, 7, 6, 0x1D, 0x1C, 5, 0 };
|
||||
static u8 sb3[] = { 0xF9, 0xFA, 0xF8, 0xEF, 0xEB, 0xEE, 0x8A, 0xFE, 0xE2, 0xEF, 0x8A, 0xFD,
|
||||
0xE5, 0xF8, 0xEE, 0};
|
||||
void win_screen(s16 score) {
|
||||
u8 i, j;
|
||||
u8 i, j, r;
|
||||
if(score > 29) r = 4;
|
||||
else if(score > 24) r = 3;
|
||||
else if(score > 19) r = 2;
|
||||
else if(score > 9) r = 1;
|
||||
else r = 0;
|
||||
clrscr();
|
||||
ppu_off();
|
||||
for(j=0;j<12;j+=4)
|
||||
@@ -111,7 +121,9 @@ void win_screen(s16 score) {
|
||||
" |::.|:. |::.|::.| |\r\n" \
|
||||
" `--- ---`---`--- ---'");
|
||||
cputsxy(0, 12, " Congratulations! You made it\r\n through the dangerous\r\n dungeon alive!\r\n\r\n");
|
||||
cprintf(" Your score: %d\r\n\r\n", score);
|
||||
cprintf(" Score: %d", score);
|
||||
gotoxy(SCREEN_WIDTH - 8 - strlen(ranks[r]), 16);
|
||||
cprintf("Rank: %s", ranks[r]);
|
||||
chlinexy(0, 17, SCREEN_WIDTH);
|
||||
cputs("Created by Luxferre in 2026\r\nReleased into public domain\r\n");
|
||||
cputs("\r\nOriginal concept by Zach Gage\r\nand Kurt Bieg, 2011");
|
||||
@@ -120,7 +132,17 @@ void win_screen(s16 score) {
|
||||
ppu_on();
|
||||
waitvsync();
|
||||
beep_win();
|
||||
wait_for_start();
|
||||
for(i=0;i<15;++i) sb1[i] ^= 0x55;
|
||||
for(i=0;i<17;++i) sb2[i] ^= 0x55;
|
||||
for(i=0;i<15;++i) sb3[i] ^= 0xaa;
|
||||
do {
|
||||
r = wait_for_start_or_combo(JOY_BTN_1_MASK | JOY_BTN_2_MASK);
|
||||
if(r == EZJOY_COMBO) {
|
||||
cputsxy(8, 1, sb1);
|
||||
cputsxy(7, 2, sb2);
|
||||
cputsxy(8, 10, sb3);
|
||||
}
|
||||
} while(r != EZJOY_ST);
|
||||
}
|
||||
|
||||
/* UI choices */
|
||||
@@ -393,8 +415,10 @@ s16 game() {
|
||||
}
|
||||
|
||||
if(ch == CH_Q) {
|
||||
if(confirm("End game?")) {
|
||||
hp = 0;
|
||||
break;
|
||||
} else print_msg("Go on!");
|
||||
} else if(ch == CH_INVAL) {
|
||||
print_msg("Invalid input!");
|
||||
beep_err();
|
||||
|
||||
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