Simple movement demo

repo

Yea, this one happens to be in Czech, deal with it...

So I got kinda bored and decided to play a bit with scheme again. I've heard that CHICKEN has quite good C FFI, so that sounds like good enough excuse to mess with raylib once more.

After a bit of existing, I realised that my dorm room would make for good area in a simple point-and-click environment, so I made a very minimal engine that allows you to move through the dorm room and interact with it's objects.

Linux binary is provided, else you'll have to compile yourself.

FFI

CHICKEN provides the '(chicken foreign)' module, which allows you to handle most FFI basics, but for the full experience, I would however recommend to also use the 'foreigners' egg, which handles interacting with C structures.

The main functions used are 'foreign-lambda' for binding simple functions and 'foreign-lambda*' for more complex functions, which need wrapping. I wrote simple macros to make the code a bit cleaner:

(define-syntax dfl
  (syntax-rules ()
    [(dfl name arg ...)
     (define name (foreign-lambda arg ...))]))


(define-syntax dfl*
  (syntax-rules ()
    [(dfl* name arg ...)
     (define name (foreign-lambda* arg ...))]))

You also need to include raylib with:

(foreign-declare "#include <raylib.h>")

Now I can include, for example 'InitWindow' with:

; CHICKEN name      return value      C name        arg types
(dfl init-window        void        "InitWindow"   int int c-string)

Well, kinda. I still have to link to the raylib library at compilation. Since I'm using the csm build system, and I have raylib installed manually, that is in '/usr/local/', I will use the following flags

-program main -L -L/usr/local/lib -L -lraylib
or if I want to compile static executable:

-program main -static -L /usr/local/lib/libraylib.a -L -lraylib

I put these flags in 'all.options' file. (It, in fact, didn't work for me to just pass them to the compiler. Not sure why) Since I want both options, I use a make file, which generates 'all.options' as needed. (yes, a build system that controls another build system, I know...) I also added an option to build a 'tar.gz' with all the assets bundled in.

RAYLIB_DIR = /usr/local/lib
BASE_FLAGS = -program main -L -L$(RAYLIB_DIR) -L -lraylib

dynamic: $(wildcard *.scm)
	echo "$(BASE_FLAGS)" > all.options
	csm

static:
	echo "-static -L $(RAYLIB_DIR)/libraylib.a $(BASE_FLAGS)" > all.options
	csm

package: static
	mkdir -p movement-demo
	cp -R ../data movement-demo/
	cp main movement-demo/
	cp ../LICENSE movement-demo/
	tar -caf movement-demo.tar.gz movement-demo

clean:
	rm -fr main *.o *.so *.import.scm *.link movement-demo

Ok, back to binding raylib. Next step is to find a way to use structs, such as 'Color'. (quite useful in raylib)

First, I define a color type, both for using in FFI and as a CHICKEN structure:

(define-foreign-type color-p (c-pointer "Color"))
(define-foreign-record-type (<color> "Color")
  (constructor:  construct-color)
  (destructor:   destroy-color)
  (unsigned-byte r color-r set-color-r!)
  (unsigned-byte g color-g set-color-g!)
  (unsigned-byte b color-b set-color-b!)
  (unsigned-byte a color-a set-color-a!))

(define (make-color r g b a)
  (let ((color (construct-color)))
    (set-color-r! color r)
    (set-color-g! color g)
    (set-color-b! color b)
    (set-color-a! color a)
    color))

'construct-color' does not take arguments for the attributes, so I had to define my own constructor. Since scheme passes values by reference, I define the data type as a pointer. But raylib expects arguments passed by value, so I have to do a bit of wrapping:

(dfl* draw-rectangle void ((int x) (int y) (int w) (int h) (color-p c))
  "DrawRectangle(x, y, w, h, *c);")

I do not bind the entire raylib, but from the parts that I do bind, two required a bit of extra work:

(dfl* load-texture texture2d-p ((c-string str))
  "Texture2D* t = malloc(sizeof(Texture2D));"
  "*t = LoadTexture(str);"
  "C_return(t);")

First note, that these wrappings return values with the 'C_return' macro. Second, 'LoadTexture' returns a value, which gets out of scope once the wrapper function exits, so I need to allocate it on the heap. This also means that it is not garbage collected by CHICKEN and needs to be manually freed, but that is not needed in my case, as the texture exists for the entire lifetime of the program. Also the scheme predicate 'procedure?' thinks that any C pointer is a procedure, so that is something to consider.

Next one is more of a general raylib problem, but I encountered it here first:

(dfl* load-font-e font-p ((c-string str) (int size))
  "Font* f = malloc(sizeof(Font));"
  "*f = LoadFontEx(str, size, NULL, 999);" ; values that work for my needs
  "C_return(f);")

Raylib normally does not load unicode characters from the font, so I have to define which codepoints need to be loaded. These arguments work fine for my needs, but your experience might vary.

And that's it! Well, it might look long, but that's just all the code examples and entire make file mixed in. Now you're ready to use any C library you want in your CHICKEN project! (at least I think so...)

The game stuff...

Most of the game data is stored within a single data structure, located in the 'view-data.scm' file (as I internally call each screen a 'view'). Scheme has a built-in way to handle associative lists (maps) with the 'assoc' function. Well, that handles reading entries, modifying values under some key might have a function assigned to it, but I could not find it. It wasn't hard to implement with 'filter' tho.

Associative list in scheme looks something like this:

(define example-list '((key1 value1)
                       (key2 value2)))

(assoc 'key1 example-list) ; => ('key1 value1)

Once you realise you can nest associative lists, you basically have better JSON. (I say better, since you can evaluate parts of it and it can contain lambdas) Speaking of lambdas, I store textures in a separate associative list, and the main structure only contains lambdas, which return the needed value. This is there for two reasons:

1, The returned texture can change at any moment (I don't actually use this for anything...)

2, Textures need to be loaded after 'InitWindow' is called, and I didn't want to evaluate the entire list after that for some reason.

Either way, it works... And yes, my code could use some extra sanity checks, but since it's not meant as a general engine, but only as a part of my little demo, I omitted them for simplicity.

Each view has the 'background' key, which is the, already mentioned, texture. Next allowed keys are 'front', 'right', 'back' and 'left', which contain name of the view in that direction. Absence of one of those keys means, that the player cannot go in that direction from this view.

Then there are three more complex keys: 'positional-events', 'positional-views' and 'overlays'.

'overlays' is a list of smaller textures and where to draw them, in the format:

((x y) (width height) texture-structure)

These are used for parts of the view that can change based on user interaction, in this case collecting items. Speaking of user interaction...

'positional-events' is very similar, but instead of texture, it contains lambda, which will be executed if user clicks the area.

Last is 'positional-views', which contains a name of a view, to which one will go upon clicking the area.

This all ties together with one last associative list, which contains different world states and their values. This list is combined with a neat little trick called:

(define (to-val v) ; WARNING: may also catch C pointers, eg. textures
  (if (procedure? v) (v) v))

which allows me to replace any value with a lambda, which will return the value. This means, that I can have items, that only display if they are not yet collected and become collected, when clicked and an exit door, which only leads to the win screen (which is also just a view) only when all items are collected.

I like scheme.

Most of the rest of the code only handles displaying current view and managing user input, nothing that fancy. Reading it is left as an exercise for the reader...