What I'm Playing: Clubhouse Games: 51 Worldwide Classics

What I'm Playing: Clubhouse Games: 51 Worldwide Classics

I loved the version on the DS, so I got this the moment it was released on Switch.

You start by picking from a set of little human pieces, which you can recolor their skin & hair, but not their appalling clothes. I almost went with Dad there, but in the end Cool Bro looks better. Looking at the random other players later, I see a lot of them chose that or Suit Guy. As I've noted before, Nintendo has Mii avatars, and then doesn't use them in games even where it'd make sense. You see a little face photo of your Mii in some games, but it should use your Mii in the world! Nintendo is so frustrating and anti-social.

Then you go to a globe UI, with figures representing "guides" that give you a menu of a few games. Or you can just pick any game from a preposterously long line menu, or you can hit X (up button) to switch to a grid which is more reasonable. UX is very confused, always a couple extra button presses or spinning the cursor around a too-large area to get to anywhere you want. You unlock more guides by playing games, earning trophies.

Your piece has up to 5 "recommendations", but you can't set them from in the game, you have to go all the way back out to the globe, find your piece, and add them from a list. And these don't help you jump back to a game fast, you have to find it in the grid every time.

Like almost all Nintendo software now, there's no settings for audio, and the "music" is driving me insane, but I need the sound to play some of these, so I'm constantly muting and unmuting. At least in the old days, Nintendo's music had complete scores, but they've apparently fired all their musicians, this is just beep-doo-beep-de-beep, over and over until I stab someone.

Each game starts with couple figures playing the game with often amusing commentary—the kids narrating Connect Four as a samurai duel is fantastic—often enough tutorial for anyone, but it immediately comes up to a menu with "How to Play" and Play, and hitting + in game usually gets a help menu. They're trying to teach you games you may be unfamiliar with. However, showing the tutorial EVERY time you start a game until you hit X (up) is insipid.

There are medals for winning against the AI and playing at least 2-4 times depending on the game, so there's a little grind possible if you're into that.

Nintendo History guide gives you: Hanafuda, Gomoku, President, Shogi, and Riichi Mahjong, which Nintendo made for a century before going into the videogames business.

Many of the games have local and Internet multiplayer, which I haven't yet tried. I expect the usual Nintendo® Quality™ networking, which is to say everything will drop out constantly. I'd rather play against AIs.

Current playlist of 11 good, 16 bad, 25 unplayed doesn't seem all that positive, but the good games are usually very good, and you can just ignore the stupid ones. The constant terrible music is the only strong negative.

I'll keep updating this post as I play more of them.

★★★★☆

bold is good, italic is bad, plain is I haven't bothered to play it yet.

  1. Mancala: aka Awari. Anyone who's typed in games from Basic Computer Games is intimately familiar with Awari. It's a weird little game, but fast and fun, and there's just enough strategy against a smart player (not the AI) to make it hard to win.
  2. Dots and Boxes: "Boxing" is also very familiar from school. The first player (default to you) is at a severe disadvantage, but it's possible to only give up a few boxes to the second player, and then clean up the rest.
  3. Yacht Dice: aka Yahtzee, Poker Dice. Nice enough, but I found the controls a little finnicky, it should not use the "do stuff" button for both pick and reroll. Slaughtered the AI, as one would expect.
  4. Four in a Row: aka Connect Four. Pretty dull, aside from the tutorial.
  5. Hit and Blow: aka Mastermind, Bagels, etc. with an unfortunate translation name. But I dislike the color-matching version, I'm a numbers person.
  6. Nine Men's Morris: I don't understand this game. You start playing while setting up, and it just screws anyone who loses one piece. Also obviously should have been #9.
  7. Hex: Again, should've been #6. It's a road-building game, dumb low-challenge game.
  8. Checkers
  9. Hare and Hounds
  10. Gomoku
  11. Dominoes
  12. Chinese Checkers: aka Pegboard. Not Chinese, sort of checkers except nothing is captured.
  13. Ludo: aka Parcheesi, Sorry!, Trouble, etc. I switch to the 3 dice to come out rule, rather than automatic/on 6, otherwise it's Parcheesi (not quite Indian Pachesi), a good being-dicks-to-each-other race game.
  14. Backgammon: An ancient dice game, a good fun game. I dislike the joycon controls, cursor-moving by spike around the track instead of selecting individual pieces left/right.
  15. Renegade: aka Othello, Reversi, etc., pretty standard. I lost really badly the first round, and then eked out a win, I've always been bad at this game, or anything that requires me to do deep analysis of simple positions (go, checkers, etc.), I'm a broad strategy for complex positions (wargames) thinker.
  16. Chess: There's a sort of lesson program, but the starter AI is incredibly suicidal, so it's not even interesting. Probably it gets harder, but I'm not that interested yet. I dislike the set design, it's very hard to tell the pawns apart from bishops, queen from king, and there's no alternate set option. Still, it's Chess.
  17. Shogi
  18. Mini Shogi
  19. Hanafuda: Very pretty cards, but I've never learned the sets, and visual association like this is harder for me. AI let me win 3/3 on this, which is crazy since I was just clearing chaff, I never saw more than 2 cards of a good set. However, after winning the guide "gave me a gift" of Mario-themed Hanafuda cards, so that might be easier for me. I think this might be worth practicing at.
  20. Riichi Mahjong
  21. Last Card: aka Uno, Crazy Eights. The card branding is almost but not quite infringing on Uno, so it hits that uncanny valley effect, and I kinda hate looking at it. AI didn't stand a chance, I don't know what they were even doing, picking cards at random? There isn't much strategy to Uno, but no strategy means you lose.
  22. Blackjack: Gives a limited number of rounds, and chips but you can go into debt. Does not have Split, which is kind of amateurish, and it doesn't have the dealer check their down card on A or 10 up, so you might play a round and find out they have Blackjack. It's bizarre beyond belief that they didn't make Blackjack be game #21, but #22. But I can always play a few hands of Blackjack.
  23. Texas Hold 'em: aka Poker.
  24. President: aka Asshole, Daifugo, etc. Kind of an annoying party game, giant hand of cards to manage at start. I hate the rich-get-richer mechanic, which is why it's sometimes called Capitalism, but it's more like Monarchy.
  25. Sevens
  26. Speed
  27. Matching
  28. War:

    War, huh!
    What is it good for? Absolutely nothin'
    Say it again, war, huh!
    What is it good for? Absolutely nothin', come on!
    —Bruce Springsteen, "War"

    I timed this, and it takes 5 seconds and one button-press for each card, and it always resets the cursor to the rightmost card, so it takes a minimum of 2.5 minutes. Was this included as a prank?

  29. Takoyaki: Ten octopus. Almost as random as War, but you get a choice when Joker is drawn, and it's much faster. Winning this nonsense unlocked a Mario-themed card deck!

  30. Pig's Tail: aka Buta no shippo. Instead of a little action game of throwing drawn cards into a pile but avoiding matches, it's a completely random War-like, with a slow "penalty cards" deck.
  31. Golf: Cute little putting game, only has 3 clubs: Driver, Iron, Putter. It's not quite a wacky golf or mini-golf, but it's not any kind of realistic golf simulator.
  32. Billiards
  33. Bowling: Has touch controls or joy-cons, but I have a Switch Lite, so I just went with touch. A little rocky start, but then I can get a strike most throws. IRL, my aim is a little too erratic, but I've played hundreds of hours of Ramp Champ and other touch-stroke games on iPhone, so this isn't hard for me.
  34. Darts
  35. Carrom: Like marbles or pogs, but without the freedom of motion, and a strange "queen" you have to take another coin after or you put it back. I don't know that I like this game, it takes too long and the controls are stupid (stick to move up/down only, L/R to aim?!), but it's competent and kind of interesting.
  36. Toy Tennis
  37. Toy Soccer
  38. Toy Curling
  39. Toy Boxing: Lightly based on Rock'em Sock'em Robots, but without the pop-up heads or movement forward/back, just button-mashing. Controls are A/B to wobble your guy's arms out to hit or up to block, which is implausibly hard to switch between, they should've used L & R shoulder buttons. Normal AI is easy, Hard AI is brutal, I assume the others are unwinnable?
  40. Toy Baseball: Accurately simulates a cheap mechanical baseball game from the '60s, with maybe the worst pitching stick control I've ever seen. Once I got the hang of it, I recovered from 0 runs to 3, while the machine that doesn't fumble with sticks got 6. Not likely to play more. There's no Toy Football, as that's not "worldwide".
  41. Air Hockey
  42. Slot Cars
  43. Fishing
  44. Battle Tanks
  45. Team Tanks
  46. Shooting Gallery
  47. 6-Ball Puzzle: A weird collapsing ball Tetris variant, not as interesting as Bejewelled or Tetris.
  48. Sliding Puzzle
  49. Mahjong Solitaire: 20 layouts each for Beginner, Standard, Advanced difficulty. Has a nice color-assist, which is good if you can't easily make out the stack depth. I can see this being a big time-killer for me.
  50. Klondike Solitaire
  51. Spider Solitaire
  52. Bonus: Piano: The piano has a single octave, the help says the buttons or shaking joycons does something, but it does nothing on the Switch Lite at least. Turning the device upside down gets you a synth with 4 octaves selectable by button, but the keys don't rotate into normal position, so it's pretty unusable. I would prefer a real "toy piano" simulator, but then you may as well buy a teaching piano toy or a proper cheap synth, they're $30 or less on the 'zon.

LISP Machines

I really wish we had a front-end to software even half as useful as this in the 21st C, but technology has regressed massively since the '80s and '90s. Some of the old IDEs, before they became bloated "enterprise" software (because giant mega-corporations paid for them, not individual programmers, so the IDE makers serve their paymasters), started to slouch towards this kind of usefulness but fast and small. CodeWarrior, Project Builder/Interface Builder, Borland's Turbo Whatever.

emacs isn't the answer, it's an abandonment of the question; "modern" emacs turned away from the LM zmacs model, it's now just a terrible LISP interpreter with a bad text-only-editor front end; you can make tools in it, but nobody sane will want to use them. I'm perfectly comfortable with the emacs editing keys (well, obviously, Macs use emacs keys for all text areas), but the machinery in it is just broken.

I get excited about Chez Scheme having a nice REPL, with history and can edit multi-line blocks in the REPL, unlike the crappy readline almost every other Scheme uses. But it doesn't do hypertext, it doesn't do graphics, it barely has tab completion (procedure names only), it doesn't have any inline documentation & source inspector; all of those were in the LISP Machine.

When I'm working, I have my text editor (BBEdit or Atom mostly), a terminal with the REPL running and I copy-paste to it, 3-4 PDFs open (R6RS, R6RS-lib, CSUG, sometimes TSPL), and a web browser pointed at the SRFIs. If the Internet connection went down, I'd have to search the SRFI sources to figure out what's in there. I really need a better tool for this.

DrRacket can do some graphics inline, and the tab completion shows documentation hints in the top right corner, but as I note every time, it doesn't really have a REPL because it destroys the interactive environment every time you edit code; utterly useless for code exploration. And in practice, Racket is really horrifyingly slow; it does fine in focused benchmarks but real-world use it just falls over drooling.

The graphics part's sort of irrelevant, and sort of not; the way the LISP Machine worked was getting a "presentation" form for an object, which would render as text or drawings or images, and interacting with it sent messages back to that object. That's probably out of scope for anything except a complete new OS and terminal. A simplistic number-tagged hypertext would be good enough and orders of magnitude easier.

So. I'm not sure what to do here, I don't want to just complain about tools and not do anything about them. I could try to extract the docs to make a hypertext doc system; a lot of text processing on TeX source sounds painful, and a one-off job, I want a more universal solution. It may be possible to hook into Chez's completion to call a help system. Or it could be a standalone program that you feed several doc sources into, and it lets you search against them. Dash does that, but it's Mac and C/Objective-C primarily, and does poorly at other docsets.

Retrospective: PortalWorlds

What worked, what sucked, lessons learned, The More You Know, and knowing is half the battle, go Cobra, etc.

JS: Test Your Libraries -1

Especially the hard-to-test parts. I had one "obviously correct" array shuffle function I've been using for maybe 10 years:

WRONG RIGHT
function arrayShuffle(arr) {
    for (let i = arr.length-1; i >>= 1; --i) {
        const j = Math.floor(Math.random() * i); // WRONG. NO.
        const tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    return arr;
}
/** Fisher-Yates */
function arrayShuffle(arr) {
    for (let i = arr.length-1; i >= 1; --i) {
        const j = Math.floor(Math.random() * (i+1)); // RIGHT. YES.
        const tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    return arr;
}

And because of that, I got a distorted shuffle. I only noticed because no mob using my dumb-as-rocks not-even-AI would ever choose to move north… everything ended up piled up at the bottom of the map.

It's hard to test randomness, but you can check that values are in a sane range. For a decade I've had wrong code just copy-pasted into new projects because I never checked my assumptions.

JS: UI Libraries are Great +1

I mean, I knew that. But how easy it is to throw something up is a bit of a surprise every time I do it. It's a fast, dynamic, functional/OOP language that also has good graphics and sound libraries (I didn't do sound in PortalWorlds, but I will before 1.0), and tolerable event handling these days.

I'm not releasing the full source, but in a week or two I'll add a few more things and release my common library under BSD license. This is mostly stuff from my Learn2JS project, and some from TTMS-76, but those have a giant framework for running scripts, this is just a few functions for a pure JS application, and greatly condensed.

Pulling in common code makes this stuff so much easier.

JS: Application Structure -1

There's no proper "run loop" in PortalWorlds, and late in the process that bit me in the ass.

What you should do is, collect events, put them in a queue. Every X milliseconds (I usually use 30 FPS, so 1000/30=33.333ms) have setInterval do an event-update-redraw loop. If something takes a long time or requires animation updates, it must be done in chunks or in a background thread (using Web Workers ). This is true of any application, not just games.

What I did wrong is having events immediately perform actions, and the setInterval just does update-redraw. This is much easier, but it's wrong. I have no control currently over blocking user actions, and animation has to all happen at once.

The trick I used is to keep a "frame" counter, and that chooses animation frames, moves floating text and the tracers of missiles and fireballs. Next turn invoked by user event just wipes those out. An amusing side-effect is missiles vaporize corpses as they fly past.

Switching to a correct run loop isn't super hard, but does require changes to all my timing and animation hacks, so at this point it's not worth it.

JS: Fonts -1

I used Oryx's "simplex" font, which looks fine and bitmappy in web pages, but in Canvas it gets antialiased, and I wasn't able to make it stop. So I have to make all fonts slightly larger than I'd like, and they're still kind of blurry.

The smarter way would be to use a bitmap font, and a very simple bitmap text renderer. That's what I do in Perilar: Dark Weaver, with an ATASCII-inspired fantasy font. But in 7 days I didn't have time to write and test that.

That's on a "maybe later" list for 1.0. Alternately, I could figure out how to get them antialiased in Canvas?

JS: Minimizer -1

I used to use yuicompressor, which renames variables and aggressively minimizes your code into unreadable but very compact line noise. But with the long-drawn-out death of Yahoo!, that hasn't been updated in a decade, and it doesn't handle ES2020.

I could probably have run everything thru Babel and then yuicompressor, but it's time to move on? So I just used Crockford's jsmin which only removes whitespace. It might be a good time to look into compiling JS to WASM binaries.

My build.zsh script:

#!/bin/zsh
rm -rf portalworlds*.zip
rm -rf build
mkdir -p build
mkdir -p build/js

# *.js -> .min.js
for f in js/*; do
    n=`basename $f .js`
    perl -ne 's/const DEBUG = true;/const DEBUG = false;/; s/^\s*DLOG.*$//; print;' $f >build/$f
    jsmin-crockford <build/$f >build/js/$n.min.js "Copyright (c) 2020 by Mark Damon Hughes. All Rights Reserved."
    rm build/$f
done

# index.html
perl -ne "s/\.js'/.min.js'/; print;" index.html >build/index.html

cp -R favicon.* style.css i ttf build
cd build
zname=portalworlds-$(date "+%Y%m%d_%H%M%S").zip
zip -r9Xq ../$zname * -x "*.DS_Store"
cd ..
echo $zname

Game: Combat Design +1

So I knew I wanted combat to be very swingy (allowing anything from instant death to instant kill), but not have levels or many stats; I didn't have time or inclination to make another detailed RPG!

Instead, combat works by adding your current strength and enemy's current strength, rolling 2 dice in half that range, so central results are more likely, but it's easy to get results at either far end. Then apply the difference from your strength as damage to you or the monster, depending on which side of the line it's on:

const roll = Math.floor(dice(2, this.strength + mob.strength)/2);
if (roll <= this.strength) {
    const dmg = Math.floor( (this.strength - roll) * (100 + this.getDamage()) / 100 );
    mob.takeDamage(dmg, true);
} else {
    const dmg = Math.floor( (roll - this.strength) * (100 - this.getDefense()) / 100 );
    message(mob.toString()+" hits you for "+dmg+" damage");
    this.takeDamage(dmg);
}

Kind of ridiculously simple, but it makes for symmetric combat, so player-attacks-monster and monster-attacks-player have the same results. It's not practical for a tabletop game, but this is a really fun mechanic in a computer game.

Damage and Defense from gear just modify the result by percentiles, they don't affect attack roll at all. If your strength + defense % is less than the monster's strength, you can be one-hit-killed, and vice versa.

Experience adds to current strength, and increases base strength if you're near max; you need to finish fights against slightly superior foes unharmed to grind up strength.

Game: Spells +1/-1

I knew I didn't have time for a lot of magic, and I never feel like I'm finished with magic systems anyway. So here I just picked 4 types: AOE damage, AOE control, escape, and heal; or as the game knows them, Fireball, Sleep, Invisible, and Heal. Then rather than have MP and worry about regeneration, I just give you "base" spells in each based on class, and you can pick up scrolls to add new points to current spell total. When you heal with mana potions or going thru a portal, you get back your base spells.

Last three were trivial: Sleep just searches a radius and has a die(100) > strength chance to give humanoid or animal targets a sleep condition for 2d4 turns; if they're sleeping, they skip the turn. Invis just sets an invis condition for 2d4 turns; mobs ignore you if you have that condition. Couple places like melee and taking damage I manually clear the Invis and Sleep conditions. Heal just heals 50% of base strength, woo.

Even with just 4, doing Fireball turned out to be quite challenging (the Application Structure problem); how do I show it go across the board, maybe have a turn or two delay, then explode? Well, it does it by moving 4 steps every turn, and only having the fake animation for a tracer of where it's been.

Game: Permadeath +0

Permadeath happened more by lack of save state than any intention. I much prefer to have save slots, and then you can choose to save your game and reload last or earlier, or you can play hardcore and never reload. It puts the moral burden of choosing permadeath on the player, not the developer.

But JS localStorage doesn't really have room to stash a huge amount of data. In Reaper's Crypt I had a very aggressive compression for the map (which looks like a giant grid of tiles but it's really about 64 rooms per level), and it's still problematic, sometimes maps just can't be saved.

I could save the character as of last portal you entered, and regenerate maps. But that encourages "stair-scumming", which I do have a problem with. So instead this is very arcade-like, you just play until you die, then insert another quarter.

I do plan to add a scoreboard, both local and maybe server-based for a copy hosted on mysticdungeon.club.

Personal: Shipping is Awesome +1

Actually finishing something and making it public without a decade-long process is amazing. It's not perfect, and I know nothing is perfect, I should just ship things, but I can't normally do that. Having a hard deadline and meeting it was the best feeling.

Personal: Drone-Slack Balance -1

I was desperately exhausted afterwards. Bone tired all day Sunday, and I'm still feeling it on Monday. 7 days in a row, even with a couple shorter days, is about 3 or 4 days too many.

I'm nocturnal. I like to wake up ideally just before midnight (but sometimes backslide to evening if Real Life interferes), eat and coffee, then work until dawn, walk the dog at sunrise, maybe get my own walk in (tiny dog cannot keep up for a mile+), then finish up and goof off until early afternoon when I can sleep. The schedule weirds out some daywalkers, but it's quieter and more compatible with "morning people" (ugh) than waking in afternoon and sleeping at dawn, which I did for decades.

But normally that work is 3 days of code, 1-2 days of writing or art, 2-3 days of just playing videogames or going Outside, doing Real Life AFK stuff.

Drones who can work all the time frighten me, they're basically Terminators. People who Slack all the time frighten me, they're a waste of precious oxygen & water, and I may foolishly try to rely on these people and get nothing. You need to be in the middle area.

Scheme-Test-Unit

Moving my library from Chicken to Chez (when I moved it from Chez to Chicken originally, it was much smaller and I only used a few asserts to test), I discovered that:

  1. Chicken's test egg is pretty nice but non-standard.
  2. SRFI-64 (from Thunderchez ) is OK as far as it goes, but has an inadequate test runner (the default just lists PASS/FAIL with no explanation for each test, and has one total). Ridiculous when you have dozens or hundreds of tests.
  3. There's no good alternative. There's a SchemeUnit which is for PLT Scheme née Racket, and a couple others which aren't SRFI-64 and aren't for Chez.

So I ended up writing my own:

Here's how it works:

#!/usr/bin/env scheme-script
;; example-test.ss

(import (chezscheme)
    (srfi s64 testing) ;; thunderchez
    (scheme-test-unit)
)

(define (all-tests)
(test-group "All Tests"

(test-group "Math"
    (test-equal "add" 4 (+ 2 3))
)

(test-group "Strings"
    (test-equal "append" "foobar" (string-append "foo" "bar"))
)

) ;; test group "All Tests"
) ;; all-tests

(define (main argv)
    (scheme-test-configure argv)
    (all-tests)
)

(main (command-line-arguments))

----
% chmod 755 example-test.ss
% ./example-test.ss --help
Usage: scheme-test-configure [-v|--verbose|-q|--quiet|-o FILENAME|--output FILENAME]
% ./example-test.ss
*** All Tests START
*** Math START
- add [(+ 2 3)] expected <<<5>>> but got <<<4>>>
*** Math END, PASS: 0 / FAIL: 1
*** Strings START
+ append [(string-append foo bar)]
*** Strings END, PASS: 1 / FAIL: 0
*** All Tests END, PASS: 0 / FAIL: 0
FINAL PASS: 1 / FAIL: 1

My own test cases come out:

% ./marklib-test.ss -q
*** Control END, PASS: 4 / FAIL: 0
*** Logic END, PASS: 12 / FAIL: 0
*** Math END, PASS: 18 / FAIL: 0
*** Strings END, PASS: 29 / FAIL: 0
*** Any END, PASS: 31 / FAIL: 0
*** Hashtable END, PASS: 9 / FAIL: 0
*** List END, PASS: 6 / FAIL: 0
*** Maybe END, PASS: 6 / FAIL: 0
*** Stack END, PASS: 10 / FAIL: 0
*** Vector END, PASS: 8 / FAIL: 0
*** Data Structures END, PASS: 0 / FAIL: 0
*** Dice END, PASS: 7 / FAIL: 0
*** All Tests END, PASS: 0 / FAIL: 0
FINAL PASS: 140 / FAIL: 0

Green Window Means Thunderchez is Working

This weekend, I wanted to have dynamic access to SDL for some interactive graphing, and again ran into the problem from More Fun and Swearing with Scheme. With more experienced eyes, I read the Chez Scheme User's Guide and discovered everything I'd done wrong a year+ ago.

First, install Thunderchez somewhere, I just put it in ~/Code/CodeChezScheme

In particular, read ffi-utils.sls which provides somewhat easier wrapping of binary flags; thunderchez is a very low-level port.

Second, install SDL2, I used port install libsdl2 and that puts it in /opt/local/lib, but in production I'll use a local dir. Read the SDL2 API for reference.

Took me a few hours to translate the basic functions I needed, and now I have:

sdltest.ss:

#!/usr/bin/env scheme-script
;; sdltest.ss
;; Copyright © 2020 by Mark Damon Hughes. All Rights Reserved.

(import (chezscheme)
    (sdl2)  ;; thunderchez
)

;; Set this to your location. I used `port install libsdl2`, YMMV.
;; In production, I'll use a local dir with libs.
(load-shared-object "/opt/local/lib/libSDL2.dylib")

(define kWindowWidth 320)
(define kWindowHeight 240)

(define gWindow )
(define gRender )
(define gState 'quit)

(define (gr-init title x y w h)
    (sdl-set-main-ready)
    (sdl-init sdl-initialization-everything)
    (set! gWindow (sdl-create-window title x y w h (sdl-window-flags 'shown 'allow-highdpi)) )
    (set! gRender (sdl-create-renderer gWindow -1 (sdl-renderer-flags 'accelerated)) )
    (sdl-show-window gWindow)
    (sdl-raise-window gWindow)
)

(define (gr-event)
    (make-ftype-pointer sdl-event-t (foreign-alloc (ftype-sizeof sdl-event-t)))
)

(define (gr-rect x y w h)
    (let [ (rect (make-ftype-pointer sdl-rect-t (foreign-alloc (ftype-sizeof sdl-rect-t)))) ]
        (ftype-set! sdl-rect-t (x) rect x)
        (ftype-set! sdl-rect-t (y) rect y)
        (ftype-set! sdl-rect-t (w) rect w)
        (ftype-set! sdl-rect-t (h) rect h)
        rect
))

(define (gr-mainloop)
    (let [ (event (gr-event)) ]
        (let mainloop []
            ;; event
            (sdl-poll-event event)
            (let [ (evt-type (ftype-ref sdl-event-t (type) event)) ]
                (cond
                    [(eqv? evt-type (sdl-event-type 'quit))  (set! gState 'quit)]
                    [(eq? gState 'main)  (main-event evt-type event) ]
                ))
            ;; TODO: loop polling until no event left
            ;; update
            (case gState
                [(main)  (main-update) ]
            )
            ;; render
            (sdl-render-clear gRender)
            (case gState
                [(main)  (main-render) ]
            )
            (sdl-render-present gRender)
            ;; loop
            ;; TODO: wait timer, currently burns CPU
            (unless (eq? gState 'quit) (mainloop))
    ))
    (gr-shutdown)
)

(define (gr-shutdown)
    (sdl-quit)
)

(define (main-event evt-type event)
     ;; no event handling yet
)

(define (main-update)
     ;; no updates yet
)

(define (main-render)
    (sdl-set-render-draw-color gRender 0 255 0 255) ;; green
    (sdl-render-fill-rect gRender (gr-rect 0 0 kWindowWidth kWindowHeight))
)

(define (main argv)
    (gr-init "Hello, Thunderchez!" 100 100 kWindowWidth kWindowHeight)
    (set! gState 'main)
    (gr-mainloop)
)

(main (command-line-arguments))

To start it I just need:

% CHEZSCHEMELIBDIRS=thunderchez-trunk: ./sdltest.ss

Green window means it's working! And it can be closed with the window close button or Cmd-Q, but menu or other events don't work yet.

So… to port my graphics library from Chicken to Chez is probably a week's work, maybe less—I only fill rects, blit images, play sounds, and read a few events. My sound support on Chicken is minimal because the maintainer didn't bother to port the audio API, I had to do that myself.

Rehosting my game on Chez is an unknowably difficult debugging problem, but I code in a way that's usually portable. Large chunks of my large marklib library exist only to work around the non-portable parts and missing features of R5RS.

But the advantages are:

  1. Chez's R6RS is a better language than Chicken's R5RS++/R7RS-not-really.
  2. Chez is much faster, sometimes just 10-25%, sometimes hundreds of times, even compiled.
  3. Chez's JIT compiler is fast and invisible. Working on Chez is instant gratification. Chicken has a 30-second compile before I see anything, which has been impossibly frustrating.

At the very least, if I get my graphics library ported I can do the graphing I wanted last weekend.

Project Status

Made a lot of progress on Perilar Dark Weaver map generation. Hopefully this week I'll get ruins finished.

Spent a little too much time on the new Mystic Dungeon, the TTMS-76 virtual retro-console started on tilde.town but is now replacing the BBS. It now has scoreboards for the games! I still need to finish Heist, which turned out to be bigger than I first thought. In a bit I'll get yet another damned login system done, and from there a forum. I wonder if anyone else would be interested in making games for it? I've got a trivially easy framework, so if you know any Javascript at all it's fun to work with. All BSD licensed.

Updated StupidComments.css to block some more inline "affiliate" blocks and Youtube spam segments.

I started making a console Pomodoro timer, and it works, but needs persistence and a teeny bit of task management before I can release it. Very soon.

RPG-wise, I wrote a bit more of my "survival D&D" game Delvers in Darkness (aka Dungeon Hell), which is looking to come in well under 16 pages for a full Holmes-type dungeon game; maybe 32 if I write more on the setting, which since I complain about that in everyone else's games, I should. Haven't looked at my light game in a bit, and don't know when I'll get back to that.

Perilar: Dark Weaver

Perilar was my favorite of my games, an original iPhone RPG (loosely inspired by Rogue, Ultima, & Zelda). Alas, the App Store is a pain in the ass to stay current in, and Perilar needed updates I wasn't willing to jump thru Apple's hoops to deliver, so it's been gone from the store for a few years. Does anyone even remember me?

Then I wrote & released Brigand, a stripped-down realtime roguelike, which apparently everyone got mad at either because it was too hard (it was, but I liked that), or cost $9.99 which is SO MUCH MONEY on the goddamned App Store. And again the App Store made it obsolete and it wasn't reasonable to update it.

So I got back to a new desktop version of Perilar. I wrote a JS prototype (Fallen Kingdom) that wasn't fast enough to be usable, but let me rapidly test a bunch of new things. Now in Scheme, after a very long time, I have a nice, fast, nearing-complete sequel to Perilar: Dark Weaver.

Where I am right now:

  • World has both hand-designed and random sectors, tho the latter need some smoothing out to look like real terrain. I can walk across the world, at least until I hit impassable sectors. Porting my complex map and dungeon generators in is the next task. I have spent the last 6 months building features in the Town of Amity, and I'm ready to move on!
  • Most of the tile art is from the DawnLike set, with a lot of customization and new art where needed, and I've stuck to the DawnLike palette, it really has a nice old-timey look, a little less garish than the Atari 800, brighter than C64.
  • Player art isn't going to be these sprites, but the paperdolls I have are 2-facing (East/West), and I'd prefer 4- or 8-facing (you can move diagonally!); so I still need to find or draw (oh no) those.
  • NPCs have dialogue trees, stores, and special abilities (like the Innkeeper healing you; they're not super-powered).
  • Combat, with multiple attack/defense options, works in my test area. I haven't spread monsters around the sectors yet, but they've been developed and tested in the JS prototype.
  • Loot is extensive, magical weapons and armor have all the complex options you'd expect. I'm being a hardass on encumbrance in this one, because you can drop loot anywhere and come back for it. (Not quite the hardest possible ass; gold doesn't count towards weight, which it does in tabletop RPGs!)
  • Spells beyond Magic Missile are not implemented at all yet; will probably ship with only the dozen basic spells from the original release, and advanced spells added in an update. You won't find anyone to teach those for a long time anyway. Despite that, Wizards are still useful with magic wands.
  • New bosses, boss arenas, deeper dungeons, main quest, and sidequests.
  • At least one sector will be user-modifiable, tho I don't know if it'll be in the first release. You can buy furniture and walls, and fix up your own town. There's useful things you'll be able to get from that. (The building mechanic half works now; gathering doesn't).
  • Currently tested on Mac, should be buildable with no or very few changes to Windows, Linux, BSD, etc., but I need to get proper test environments for all of those.
  • Will be for sale on itch.io sometime this year. Price TBD.

I feel super awkward about self-promotion, but I do have a Patreon, and for Gold level you'll get betas; I haven't explained this, but at any level, when you've paid up whatever the cover price of the game ends up being, you'll get a full release license for it, too.

darkweaver-2020-01-08-2
darkweaver-2020-01-08-3

Software Principles for 2020

This is both for myself, and to decide what software I'll tolerate in my presence in the future.

  1. No lag. All UI must respond and be responsive again within 100ms. Most everyone has many cores in their CPUs and a massively parallel GPU not doing that much, you can spare ONE to run your work thread. Stop with the long animation shit. 100ms is plenty to see a shadow moved from one place to another, where there is now an interactive UI.
  2. No load screens. If you can't preload "instantly", be functional, show a usable menu while background loading. Media streaming needs to buffer, but you can show a poster frame instead of empty space.
  3. No ads or spyware. If you can't subsidize your software some other way, don't ship software. Or as the late very lamented Bill Hicks said, "If anyone here is in advertising or marketing, kill yourself!" (and of course there's ads on youtube; so maybe I need to find a better video hosting system? I know there's a fediverse-based video thing)
  4. No custom binary formats. Save your data in JSON or some other common system (plist on Mac, etc), so users can export & manipulate it from their own tools.
  5. No sites without syndication. If you have a web site or blog, you MUST support RSS or Atom, or both. Failure to do so should have you removed from the Internet.
  6. No unsecure connections. I know it's hard to add https the first time, and some older services can't be easily wrapped, but every http connection is a chance for false information to be fed to you, your computer compromised, your information to be stolen.

Atheism Reading Assignment

The things that deconverted me as a child:

  • National Geographic Concise History of Religions: Either one of these is right, and it's not yours, or none of them are right. I'm particularly fond of the Aztecs, since they believed at a depth no rational person can comprehend… and were just totally wrong. Huitzilopochtli didn't end the world when the sacrifices stopped.
  • Carl Sagan's Cosmos, book (most importantly) and TV series (often streaming online, or get the boxed set). Explains the scientific method, and how we have learned what we know. I don't recommend Neil Degrasse Tyson's version, which is much more pop-culture.
  • Isaac Asimov's Guide to the Bible: Explains where the Bible came from and how it was written, and why.

Additional:

Adult Engineer Over-Optimization as the Motie Problem

Looking at my Scheme code and the way I customize it, I'm starting to see the real reason evil megacorps (and wannabe evil startups) won't hire even middle-aged programmers or use your favorite weirdo language, they just want young idiots who code Java or Go.

If you think about a standard software career, there's maybe 10 years of a submissive fool badly coding crap languages ^1 like Java, Go ^3, PHP, JavaScript ^4. They just got out of college or self-trained, and can barely copy existing algorithms, let alone think of one for themselves. This is why FizzBuzzTest ^5 is such a good novice coder test: It requires following directions exactly, and slightly competent logic skills, but not much more.

Then maybe 10 years of them being project managers and "architects", running waterfall and GANTT charts; they'll say they're "agile" but then have a giant JIRA repo of "backlog" features which have to be implemented before shipping, weekly 4-hour planning "backlog grooming" meetings, and unrealistic estimates. This is sufficient to build all kinds of horrible vertical prisons of the mind like Azkaban Facebook.

Then they either retire, or are "downsized", and now what? So they work on their own code, do maintenance on old systems, or leave the industry entirely.

If they work on their own, freed of evil megacorp constraints, they're going to end up in something idiosyncratic and expressive, like Scheme, LISP, Forth, or a custom language. Make their own weirdo environment that's perfectly fit to themself, and unusable/unreadable by anyone else.

Case in point, I needed an object model. There's one I like in Gerbil, and Gerbil's blazing fast, but I can't make a full SDL2 library for it yet (Gambit's FFI is hard, I've hit some bugs, and there's a LOT of library to interface to), and I'm using a bunch of other Chickenisms anyway, so I can't really move to it yet. Instead I just made my own simple object libary, with a couple macros to hide the ugly reality behind it:

(test-group "Object"
    (test "Object" 'Object (class-name Object))
    (let [ (obj (@new Object))  (bug )  (cow )  (duck ) ]
        (test "Object-to-string" "[Object]" (@call obj 'to-string))

        (define-class Animal Object)
        (define-field Animal 'legs 0)
        (define-field Animal 'color )
        (define-method Animal 'init (self legs color)
            (set! (@field self 'legs) legs)
            (set! (@field self 'color) color) )
        (define-method Animal 'speak (self)
            (sprintf "The ~A ~A with ~A legs says " (@field self 'color) (class-name (@class self)) (@field self 'legs)) )

        (set! bug (@new Animal 6 "green"))
        (test "bug-legs" 6 (@field bug 'legs))
        (test "bug-color" "green" (@field bug 'color))
        (test "Bug speak" "The green Animal with 6 legs says " (@call bug 'speak))

        (define-class Cow Animal)
        (define-method Cow 'init (self color)
            (@super self 'init 4 color) )
        (define-method Cow 'speak (self)
            (string-append (@super self 'speak) "MOO!") )
        (set! cow (@new Cow "brown"))

        ;; second class to make sure classes don't corrupt shared superclass
        (define-class Duck Animal)
        (define-method Duck 'init (self color)
            (@super self 'init 2 color) )
        (define-method Duck 'speak (self)
            (string-append (@super self 'speak) "QUACK!") )
        (set! duck (@new Duck "black"))

        (test "Cow speak" "The brown Cow with 4 legs says MOO!" (@call cow 'speak))
        (test "Cow to string" "[Cow color:brown;legs:4]" (@call cow 'to-string))
        (test "Duck speak" "The black Duck with 2 legs says QUACK!" (@call duck 'speak))
        (test "Duck to string" "[Duck color:black;legs:2]" (@call duck 'to-string))

        (test "instance-of?"  (instance-of? cow Cow))
        (test "instance-of? parent"  (instance-of? cow Animal))
        (test "instance-of? grandparent"  (instance-of? cow Object))
        (test "instance-of? cousin-false"  (instance-of? cow Duck))
        (test "instance-of? not an obj-false"  (instance-of? "wtf" Cow))
    )
)

The implementation code's not much longer than the tests, but it's not quite done for me to show off; I need to switch my macros into non-hygeinic forms so I can get rid of the (self) in define-method, and introduce an Objective-C-like _cmd field for self-reflection, and message-not-understood handling. There's always more tinkering to do.

Which is great for me, but makes my code an undocumented (mostly) new language, unusable by anyone normal. A giant pile of crap Java program, no matter how old, can be "worked on" (more crap piled on top) by any teenage Bro Coder.

All of which brought to mind The Mote in God's Eye, where the Motie Engineers over-optimize everything into a tangled mess, and the Watchmaker vermin are even worse, wiring up everything to everything to make new devices. The threat posed by and solution to Scheme programmers, in your usual authoritarian megacorp scenario, is similar to Watchmakers.


^1 Swift is intended to fit this niche much more than weirdo expressive Smalltalk+C Objective-C was, BDSM ^2 to prevent one from writing "bad" code, but it's not there yet; the reality of low-level software dev can't be simplified as much as Apple wants, and their C++ developers weren't up to the task anyway.

^2 Bondage-Domination-Sado-Masochism; aka strict type systems and code flow analysis, that prevent one from writing "bad" code at the cost of annotating everything with types instead of doing useful work. I'm not kink-shaming people who do that for sex, only those who do it to their own software.

^3 Rob Pike has openly said they can't give a powerful language to newbie Googlers, they mostly just know Java, C, C++, which is why Go is so limited and generic.

^4 Oddly, JS is basically a LISP with really shitty syntax. It's easy to make trivial, broken junk in it, but it's also powerful and expressive if you're an old maniac who understands the Self-based object system.

^5 Oh, fine, but only so I can demonstrate something:

(define (fizzbuzz-test i n s)  (if (zero? (modulo i n))  (begin (display s) )  ) )
(define (fizzbuzz i)
    (unless (any identity (list (fizzbuzz-test i 3 'Fizz) (fizzbuzz-test i 5 'Buzz)))  (display i))
    (newline) )
(for (i 1 100) (fizzbuzz i))

Totally different structure from the usual loop-if-else repetition and hardcoding of everything, because Scheme encourages coding in small pieces. Of course I wrote my own for macro which expands to a named let loop; there's many like it but this one is mine. More Motie engineering.