💾 Archived View for gemini.circumlunar.space › users › wakyct › crt › 16-06-2020-1.gmi captured on 2020-10-31 at 00:59:30. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2020-09-24)
-=-=-=-=-=-=-
I remember what first turned me on to roguelikes was, counter-intuitively, the amazing visual demos jice was posting at the home of his RL The Chronicles of Doryen (hence the name of the library, tcod), and that led me to toying around with libtcod and Python here and there for a couple of years after that. But that, as they say, was long ago, and I hadn't done much game dev shaving until the recent Lisp Game Jam,
https://itch.io/jam/spring-lisp-game-jam-2020
where I learned about TIC-80 and Fennel, a lisp that compiles to Lua,
the combination of which I highly recommend. But as usual for a hobbyist like myself, once you start pulling on a thread it unravels with a will of its own, and a few late nights of compiling C in Lua libraries (why, really I don't know) strangely gave me the bug to visit more of that world in the key of C. From a safe distance of course. With full PPE. Somewhat by coincidence, at the same time I was exploring Gemini, and as an Emacs user I'm using Elpher, and Elpher's author runs a server written in Chicken Scheme. I had a vague notion of what Scheme was all about, kind of how you know you have relatives back in another country somewhere that you've never met, but the more I learned about Chicken the more interesting it sounded.
All of this is a long-winded way of saying that I have no logical basis for making the technology choices I do.
Installing Chicken on Windows was, somewhat to my surprise, really smooth with Mingw-64, so hats off to the Chicken team. The only gotcha was I didn't initially set my CHICKEN_REPOSITORY_PATH to include my CHICKEN_INSTALL_REPOSITORY (an optional configuration where third-party libraries go), but some nice help in IRC solved that for me. Adding the SDL2 egg was a little more complicated, but mainly because the installation script assumed a unix style environment. I have an open issue for that but it wasn't hard to work around.
http://wiki.call-cc.org/eggref/5/sdl2
https://gitlab.com/chicken-sdl2/chicken-sdl2/-/issues/48
I didn't realize at first I needed not only the SDL DLLs to compile Chicken sdl2 but the SDL development libraries as well, but that was straightforward to suss out when the Chicken sdl2 binding complained about a missing header file.
I'm using Emacs, and I've installed Geiser. Maybe I should get Paredit too, or one of its kin?
https://www.nongnu.org/geiser/
https://www.emacswiki.org/emacs/ParEdit
https://github.com/DogLooksGood/parinfer-mode
http://wikemacs.org/wiki/Lisp_editing
I've run the Chicken REPL, and the basic demo from SDL2.
And I grabbed the image file from the tutorial, it'll be a good starting point.
http://rogueliketutorials.com/tutorials/tcod/v2/part-1/
So I'm going to follow the steps of the Python tutorial, but more in the spirit than to the letter of the code. I'll likely make different choices at many points, and try to commit the code at each step so I have a clear history of what I did.
I've installed Magit in Emacs; I'll be learning Magit and Geiser as I go. A public repository is at
https://tildegit.org/wakyct/janus0x1
Instead of Python's `if __name__` syntax I've used Chicken's `cond-expand` to avoid running a `main` function when loading the code in the interpreter. The semantics here are not 1:1, but hopefully close enough. I'm not even sure if I'm doing this right to be honest though it does seem to work. I wonder if there's a portable Scheme version of feature expressions like `cond-expand`?
Because I'm not using a roguelike or console UI library (like libtcod or ncurses for example) I have a few more steps to take, compared to the reference tutorial, to create a window and display graphics on it. Why reinvent the wheel? Partly to learn, partly to include only the code I (think I) need. And hey, if I lose sleep over some bit fiddling, losing is fun right?
So the first steps I need to take are:
- initialize SDL ✓
- create a SDL window ✓
- create a SDL surface to draw on ✓
- create a main loop ✓
- handle a quit event so the user can close the window ✓
- handle exceptions that crash the program such that SDL can clean up properly ✓
- load a font and get the '@' out of it ✓
- draw the '@' on the surface and show it in the window ✓
Fortunately I'm not flying completely in the dark here, because the creator of the SDL2 bindings for Chicken has written several well-commented example games at:
https://gitlab.com/chicken-sdl2/chicken-sdl2-examples/-/tree/master
To accomplish the checked off items above I created a skeleton from the eggsweeper example game in that repo.
I got tripped up a little by a path/dependency problem. When I installed the sdl2-image and sdl2-ttf eggs, I specified the compiler and linker paths to my installs of SDL2_image and so on, and they installed successfully. But the eggs are dynamically linked with the SDL libraries, so if Chicken doesn't know where SDL and its associated libraries are at run-time, you'll get weird unbound variable errors when you try to import the eggs. To quote from the #chicken IRC:
12:32 AM <Bunny351> that unbound variable looks like the header of a windows binary, so dload of shared lib fails, the runtime attempts to load it as source and reads the first bytes of the binary file as a variable name (which is not bound) 12:32 AM <Bunny351> so this is a dloading issue 12:34 AM <Bunny351> sdl2-image can not be loaded, because it is not found, or because dependencies are missing
Simply putting the SDL2 dependency libraries on my path solved the issue.
In the process of getting a '@' displayed I started to figure out how I want to handle fonts in general. For non-terminal roguelikes like this one, though some use .ttf/.otf vector fonts, it seems most use a bitmap font tileset. You can see examples in the libtcod repo. There are different layouts; libtcod has a custom layout, there's the popular codepage 437 layout (that Dwarf Fortress uses) and other standard and custom layouts out there. I also found several good resources that explain much of what you need to know.
https://www.gridsagegames.com/blog/2014/09/font-creation/
https://www.reddit.com/r/roguelikedev/comments/3zyot8/faq_friday_29_fonts_and_styles/
https://python-tcod.readthedocs.io/en/latest/tcod/tileset.html#tcod.tileset.CHARMAP_TCOD
https://dwarffortresswiki.org/Tileset_repository
https://github.com/rsaarelm/sodna/blob/master/codepage_437.txt
https://www.reddit.com/r/roguelikedev/comments/7re9w2/faq_fridays_revisited_29_fonts_and_styles/
I don't have the knowledge to understand the technical issues of vector vs bitmap fonts but it does seem like each has practical advantages. With a .otf font file it's very easy to choose which glyph you want by just typing it in your source code, and then the sdl2-ttf library does the work of extracting the glyph from the font and returning a surface for it. However I like how you can just look at a bitmap font image and get a sense of the visual style of your game. You could accomplish the same thing with some code to render your vector glyphs to an example image, and of course I would need code to load and slice up the bitmap font to glyphs in the first place. But it also seems like bitmap font images are easier to tinker with if you want to add a few glyphs, combine fontsets, etc., compared to an .otf file which might require more specialized knowledge and tools.
Perhaps at first I could write a very simple custom layout of just a few glyphs (say, in just one row at one size), and then later a loader for codepage 437. I do know for sure I want different fonts for symbol display and text that's meant to be read.
Next steps:
- give the '@' thing a position x, y in the game ✓
- handle keyboard events (arrow keys) to change that position ✓
- create some kind of a movement action to separate concerns between user input and in-game effects, the libtcod tutorial uses an action type. TBD
- make sure I clear the screen in between moves ✓
- also handle the 'escape' key so that quits the game. ✓
For these last few steps I admit I chose the quickest and dirtiest solution, because I'm starting to face some architectural questions that I'm punting on until I give them some more thought. If I was following the libtcod tutorial I could just do that, but since I'm not, hey who doesn't enjoy some software star-chitect'ing?
My game loop is as basic as can be, but it'll need some work eventually. I found a good blog on the topic,
https://journal.stuffwithstuff.com/2014/07/15/a-turn-based-game-loop/
At the moment a Scheme record, 'actor', represents the player's position in-game. Chicken Scheme has many options for object systems but I'm inclined to pick a simple one for now. The game objects don't have any associated behavior yet and I'm mulling over different choices for how this should be expressed.
Input events map directly to output -- an 'up' key changes the on-screen image, and not (yet) the actor in-game 'thing'. I need to decide where to create the boundary between game objects and output. That relates to their visual symbols too. Of course you can put a game object's symbol right in their record and reference that when drawing, or you can create a mapping of visual 'assets' (glyphs and/or tiles) to in-game objects. I think I favor the second approach, if only because then the in-game objects are separate from the output representation.
This also ties into how input relates to world and non-world actions (e.g. hitting the escape key to quit, or a 'non-diegetic' action if you want to get fancy though I'm probably abusing the term). In the reference tutorial all actions are lumped together. In-game movement is alongside hitting the escape key to quit. I like the idea of actions but I'm wondering whether I should just create different types of actions (in-game and out-of-game) or some other solution.
I watched a talk recently that discussed event systems in RLs,
Danny Day - Dynamic Event-Listeners in Desktop Dungeon
I'm not sure though if I should weigh things down with a system like that, which at this point would probably be much bigger than any actual game code I have!
I've also not separated any code into different files, whereas the reference tutorial now has a file for actions and one for input handlers.
Finally I'm using SDL surfaces for rendering, but I have the option of working with the SDL Renderer, which enables 2D hardware acceleration insert buzzwords here. This doesn't seem like a priority at the moment but it's something to keep in mind.
Currently my process is edit -> compile -> play -> repeat, just because it's very easy and not much can go wrong and it's easy to restart when something goes wrong. I've poked around a little with Geiser but haven't settled into the kind of workflow I'd really like, which is to do most development with the Chicken Scheme process running. Before I go too far I should practice that workflow. I'll need to set up the game loop so it runs in its own Chicken thread, so as not to block the REPL.
...to be continued.