Gamedev in Lisp. Part 2: Dungeons and Interfaces

(gitlab.com)

333 points | by awkravchuk 257 days ago

15 comments

  • maxwelljoslyn 257 days ago
    This is what all technical tutorials should look like. Well-composed and generally free of grammatical errors, spends just the right amount of time explaining each new topic as it is introduced, comes with full code samples, and includes visual samples of what the code does. Also, lengthy enough to treat the material in depth, while still being sufficiently self-contained that I can follow along -- without having read part 1 and without more than a few months of Common Lisp under my belt from a couple years back (tho I've done a decent amount of Clojure and Emacs Lisp.)

    Bravo, awkravchuk/Andrew :^)

    (Crossposted from https://mxjn.me/2024/10/17/1)

    • dfxm12 256 days ago
      Importantly, it's written, as opposed to a video. That's a huge plus already. You can copy and paste things, read things legibly, follow along at your own pace, consume it in silence, easily save a copy for offline use/archival (where you can also annotate it), easily search for things, etc.
    • varjag 257 days ago
      Seconded! Top notch longform programming material.
  • fredrikholm 257 days ago
    Few (tech) things pull at the heart string more than great projects/articles about Common Lisp. Man what a treat!

    Read the first part when it came back, really excited to read this one. Kudos to the author!

    • awkravchuk 257 days ago
      Thanks mate, I appreciate it :)
  • mark_l_watson 257 days ago
    Wow! Your package.sh and in general managing builds for three operating systems is a master class in itself - reading through the GitHub repo was a good learning experience.

    I usually build command line Common Lisp apps in SBCL or LispWorks, but I might do the next one in ECL because having builds for both macOS and Linux would be cool, and it would be fun to try something new.

    • awkravchuk 257 days ago
      Oh thanks! I've been building that CI stuff on top of CL infrastructure for a few years now, and it constantly breaks :D
  • ertucetin 257 days ago
    This is a very good read. I’m developing a multiplayer, third-person, spell-based shooter game using Lisp (ClojureScript). It’s a 3D web-based game. I’ll also be writing a blog post about my journey, including the tools and abstractions I created for the project. If you’re interested, here’s a demo link: https://wizardmasters.io
    • fire_lake 257 days ago
      Jon Blow tried to make a game like this way back. It might be worth learning how/why it failed.
      • tines 257 days ago
        Link to any video or anything on the subject?
        • adamrezich 257 days ago
          Unless I'm mistaken, I think fire_lake might be referring to a wholly unrelated first-person RPG spellcasting game project wherein the player would draw glyphs with their mouse in order to cast spells, and then there would be a surprise later in the game based on this mechanic (which was later repurposed for The Witness).
  • xixixao 257 days ago
    This is super solid, but the setup in Part 1 (CL itself, Python, C, lots of steps) I think is indicative of why CL is not super popular, especially with young programmers. Which is a shame. Would be awesome if someone felt like putting in the work to make the language more approachable (installation wise).
    • wwfn 257 days ago
      This doesn't exactly get at it, but https://ciel-lang.org/ is at least attacking part of too-many-steps problem while focusing more on the too-many-choices and long in the tooth defaults (as I understand it).
  • Guthur 257 days ago
    The event loop is brilliant example for how much `loop` is a full blown iteration DSL... love it or hate it ;)
    • BoingBoomTschak 257 days ago
      Why loop when you can https://iterate.common-lisp.dev/ instead? No s-expr-less alien syntax, no need for `do` to switch to back to Lisp syntax, normal `if`/`when` without the ugly `else`/`end` and generally useful features added.
      • shawn_w 257 days ago
        If I used Common Lisp more I'd probably have a go at copying Racket's `for` forms[1]; they're really nice because you can usally tell at a glance what they're going to return - `for/list` returns a list for example. No having to scan the body for a `collect`.

        But in the meantime since discovering iterate I've barely used `loop`. It just feels so much more lispy and I find myself running to the documentation less often.

        [1]: https://docs.racket-lang.org/reference/for.html

        • BoingBoomTschak 257 days ago
          Interesting concept, but it visually has the same problem as loop IMO, using keywords to implement a new syntax instead of seamlessly blending with Lisp (at the cost of needing code walking, though).

          And it seems to lack all the iterations drivers (incl. builtin destructuring) that make half of loop/iterate's usefulness and "reads like English" comfy factor; especially liking

            (for (i j) on list [by #'cddr])
            (for i initially init-expr then then-expr)
            (for prev previous i [initially init-expr])
            (for i in-{file,stream} [using #'reader])
          
          The two lasts are iterate goodies and I often use the last with these custom readers: https://git.sr.ht/~q3cpma/cl-utils/tree/master/item/src/read...
          • shawn_w 257 days ago
            Racket splits up the iteration forms from what to iterate over (sequences[1]). You can compose different sequence constructors together, or make brand new ones, without introducing new syntax.

            It has limited destructuring - sequences can return multiple values, all of which can be bound. There's an adapter to convert one that does that into returning a single list, but not the other way around. If there was it could be used with `in-slice` to be equivalent to your first example.

            I could probably write a new sequence to get the `previous` behavior; don't think `initially ... then` is possible.

            Lots of sequences for reading from open ports (the Racket/Scheme name for CL streams)... `(for ([i (in-port)]) ...)` for example (with an optional reader argument defaulting to `read`).

            [1]: https://docs.racket-lang.org/reference/sequences.html

            • BoingBoomTschak 256 days ago
              Ah, I see, though I'd say it pollutes the function namespace a bit this way (as "in-x" semantically only makes sense in a loop) and missing on-list. Technically, you could do most of these in a few lines of CL too, but well, convenience is the point of these macros.

              Those seem to return sequences instead of streams/iterators, any idea why? Though it says "An in-list application can provide better performance for list iteration when it appears directly in a for clause", so I guess there's some macro magic at play.

              Anyway, thanks for exposing those, Racket does seem to be pretty practical (and with its Chez backend, I guess it's pretty fast); can't stand the square brackets used as syntax (as opposed to vector literals used as data), though ¯\_(ツ)_/¯.

              • rscho 256 days ago
                racket does not differentiate between () [] {}, so you don't have to use square brackets if you don't like them.
              • shawn_w 256 days ago
                Not so much macro magic, as not having to dispatch at runtime to the correct sequence constructor. (There is some magic if you use literals though).
      • Jtsummers 257 days ago
        Have they fixed the problem in Iterate yet where it breaks any uses of the built-in count function?
        • BoingBoomTschak 257 days ago
          Sadly no. Biggest bug in there, "fortunately". Easy to patch, though.
    • awkravchuk 257 days ago
      I used to scoff at it at first, but after a few years of CL programming loop is one of my favourite CL constructs :)
      • taeric 257 days ago
        I'm with you there. Is a bit of a mind bend, as I really disliked it the first few times I saw it.

        For an even sillier mind bend, I'm using tagbody to be able to directly transcribe some of Knuth's algorithms as I am learning them.

        • awkravchuk 257 days ago
          Cool! Using tagbody feels like writing supercharged C or even assembler to me (not that I've used it much, but still).
        • CyberDildonics 257 days ago
          I don't understand why turning a simple loop into a 'mindbend' is considered good. The downfall of programming is complexity, if you're getting your mind blown by a loop how are you going to do the rest of the program?
          • zelphirkalt 257 days ago
            Something can be mindbending in its implementation, but offer a very convenient interface at the same time.

            If mindbending isn't relating to its usage, but to its implementation, then I could see, how it could still be a good thing.

            • exe34 257 days ago
              mindbending can also refer to something being deceptively simple. you might think it would be a big complicated mess, but using this one weird trick makes it really obvious what's going on.
            • CyberDildonics 257 days ago
              How does that relate to a simple loop construct though? Why would you want that to be mind bending in interface or implementation? Every other language makes it as simple as possible.
              • SatvikBeri 257 days ago
                This isn't really true – you have languages like Odin that only have a for loop, no while loop, that only supports index-based iteration. Then you have languages like Python that let you loop over an arbitrary iterable, and define your own iterables. Some languages allow conditionals in loops, some don't. Some let you loop over multiple iterables, while some only take one at a time.

                Common Lisp happens to be on the upper end of what loop allows – you can use it as a standard for loop pretty easily, but the interface gives you many other options.

                • medo-bear 257 days ago
                  > Common Lisp happens to be on the upper end of what loop allows – you can use it as a standard for loop pretty easily, but the interface gives you many other options.

                  If you really wanna get freaky try 'do. It is the heroin addicted cousin of 'loop

                  https://www.lispworks.com/documentation/HyperSpec/Body/m_do_...

                  • shawn_w 257 days ago
                    `do` is very straightforward and basic compared to the things that `loop` allows.
                    • medo-bear 257 days ago
                      oh no. maybe you have in mind 'dolist or 'dotimes

                      'do is much more general and way more powerful. in some sense 'loop is the taming of 'do. see for example

                      https://www.lispworks.com/documentation/lcl50/loop/loop-7.ht...

                      • shawn_w 257 days ago
                        No, I mean do. It's basically just a C style for loop except with a return value. Nothing special.
                        • medo-bear 257 days ago
                          yes the syntax for 'do is simple, like that of lisp. however 'do allows you to make far more complex iteration constructs than 'loop. 'loop is just a DSL to make some of these constructs more concise. read up on it
                          • lispm 256 days ago
                            LOOP has the DO functionality included.

                            Example:

                                CL-USER 18 > (do ((a 1 (+ a 1))
                                                  (b 10 (* b 1.5))
                                                  (c nil))
                            
                                                 ((> a 5) (list a b (reverse c)))
                            
                                               (push (* a b) c))
                            
                                (6 75.9375 (10 30.0 67.5 135.0 253.125))
                            
                                CL-USER 19 > (loop for a = 1 then (+ a 1)
                                                   and b = 10 then (* b 1.5)
                                                   and c = NIL then c
                            
                                                   when (> a 5) do (return (list a b (reverse c)))
                            
                                                   do (push (* a b) c))
                            
                                (6 75.9375 (10 30.0 67.5 135.0 253.125))
                            • medo-bear 256 days ago
                              You can also express LOOP constructs in terms of DO. However if you were to construct a more exotic iterator that is not so straight forward in LOOP (beware of edge cases), I think it is more reasonable to pick DO. I think also that your example illustrates this.
                              • lispm 256 days ago
                                I would miss the in-order collects, actually collect/maximize/... features, destructuring of lists, direct type declarations, ...

                                I also find DO not easy to read and understand.

                                The code from above I would actually write in LOOP as

                                    (loop for a from 1 upto 5
                                          and b = 10 then (* b 1.5)
                                          collect (* a b) into c
                                          finally (return (list a b c)))
                                
                                I find that to be readable.
                                • medo-bear 256 days ago
                                  Of course to each their own. I like LOOP a lot actually when I need to do something familiar, however for something unfamiliar DO is often my choice. It also serves as a caution to tread and think carefully when I return to the code. Sometimes, after a while, I realise how to do the DO construct succintly with LOOP
                • shawn_w 257 days ago
                  And then there's Scheme, where there are no iterative loops; all looping is done with recursion. You can build pretty much everything other languages do with loops on top of that, though.
                  • groovy2shoes 257 days ago
                    Not true. Scheme has `do`. See R7RS section 4.2.4 "Iteration".
                    • shawn_w 256 days ago
                      Scheme's `do` is implemented using recursion. There's a sample macro for it in 7.3.
          • taeric 257 days ago
            The mindbend was more of my approach to the construct. It began with disdain before even really using it much. Looking back, I really couldn't articulate what I disliked about it.
          • 0xdeadbeefbabe 257 days ago
            He started with a bent mind though.
          • medo-bear 257 days ago
            Simple minds loop simply
            • CyberDildonics 256 days ago
              I don't think this comment means anything or contains any information.
              • medo-bear 256 days ago
                Colorless green ideas sleep furiously
                • CyberDildonics 256 days ago
                  This seems like you're replying with nonsense. Did you have anything coherent to say?
  • edem 257 days ago
    This reminds me of "Caves of Clojure": https://stevelosh.com/blog/2012/07/caves-of-clojure-01/
  • dunefox 257 days ago
    Nice, just this week I started developing a roguelike in Python, but Lisp might be cool as well.
  • 0xEF 257 days ago
    I feel tricked. I came to learn to make a simple game, ended up learning tons about computing.

    Love it!

  • rtpg 257 days ago
    Tiled is great. I really wish there was an SVG equivalent though. Inkscape is alright but custom data parameters are really annoying to deal with, and ultimately the tool is built around drawing things to paper.
  • sourcepluck 257 days ago
    I was only looking back over Part 1 yesterday! What timing!
  • davexunit 257 days ago
    I didn't know that bit of history about A* and Lisp! All roads lead to Lisp, it seems.

    As mentioned at the end of the article, the next Lisp Game Jam starts next week on the 25th. Join in here: https://itch.io/jam/autumn-lisp-game-jam-2024

    • awkravchuk 257 days ago
      I also learned it by chance while preparing the article :)
  • zelphirkalt 257 days ago
    I like the SICP references.
  • the_gorilla 257 days ago
    [dead]