From 9af0374c93b343876388a779983bd021d645c2e3 Mon Sep 17 00:00:00 2001 From: Luxferre Date: Mon, 30 Mar 2026 19:43:13 +0300 Subject: [PATCH] added Janet port --- README.md | 1 + janet/scoundrel.janet | 193 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 janet/scoundrel.janet diff --git a/README.md b/README.md index 8766949..2a3aa2f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ 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 ## Ports NOT included in this repo for various reasons diff --git a/janet/scoundrel.janet b/janet/scoundrel.janet new file mode 100644 index 0000000..ba06208 --- /dev/null +++ b/janet/scoundrel.janet @@ -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 (case resp "y" true "n" true false))) + (= 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))) + (has-value? ["1" "2" "3" "4"] 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) 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)))