How to do functional game design?

230 views
Skip to first unread message

Evžen Wybitul

unread,
Jun 19, 2018, 2:00:23 AM6/19/18
to Racket Users
Hello, I'm building a simple roguelike and I want it to be as Racket-y as possible. Most of my code is structs (representing the screen, the world, the map etc.), but I managed to implement entities — monsters, player — only as classes. I don't feel hindered by this fact, but I'm not sure it's the best way to it like this; that's why I'm posting here.

Any entity has to have some basic properties (coords, colour), plus some types of entities might have their own special properties. And, some entities have to be able to move, i.e. have a (move dx dy) function which will move them by dx and dy in the respective directions. I don't know if this function can be implemented without the use of interfaces (and thus classes) — if I have a struct entity and a two child-structs player and monster, I'd have to implement the move function separately for both of them (because, AFAIK, I can't use struct-copy generically). Is there any other way to do it? Or am I okay with classes?

Thank you for you help!

HiPhish

unread,
Jun 19, 2018, 3:11:48 AM6/19/18
to Racket Users
I have been wondering about this for quite a while myself, and I think I have an idea. Keep in mind that this is just an idea, I have not actually tried putting it into action.

The gist of it is to think of the game world not as a collection of objects which get mutated, but as the result of "signals". Take for example time: the naive approach is to think of time as some variable which gets incremented or as an infinite loop. What if instead "time" is just some signal which gets generated by something? This something could be the real-world clock using certain intervals (if you want to run the simulation for a human), or it could be the CPU ticking as fast as possible (if you just want to run a simulation for its end result), or it could be a series of instructions read from a file. I have implemented a little toy example of a functional game loop myself:

Aside from time you can have other signals as well: user input, network or AI. To have a goblin act an AI generator would produce as signal like "go left" and send it to the goblin. The goblin stream processes the instruction, and the result is the "folded" goblin stream. If you are unfamiliar with streams look up chapter 3.5 of SICP.

I know the Sly game engine (written in GNU Guile) works based on signals, so maybe its internals could be useful in that direction.

Jay McCarthy

unread,
Jun 19, 2018, 6:57:17 AM6/19/18
to Evžen Wybitul, Racket Users
I've been building libraries for functional game design for a while.
Initially, I think you should start with big-bang as you learn Racket,
but then I would recommend using my lux library, because it is more
general (in particular, it has support for sound output, terminal
output, etc.) lux lets you set your update frequency for real-time
games or change the FPS to 0 so you are purely input-enabled.

When you structure your state, as your program gets larger, it can be
difficult to work with a monolithic "state" object (struct or class),
I made the `dos` package for this purpose as it is allows you to have
your own set of "threads" that externalize their state into a list of
continuations. (Typically, most entities are loops, so their memory is
in the arguments to the looping function, therefore the continuation
captures this information.) `dos` gives you the modularity of mutation
but makes it is easy to not worry about reasoning about effect order.
> --
> You received this message because you are subscribed to the Google Groups
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-users...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
-=[ Jay McCarthy http://jeapostrophe.github.io ]=-
-=[ Associate Professor PLT @ CS @ UMass Lowell ]=-
-=[ Moses 1:33: And worlds without number have I created; ]=-

David Vanderson

unread,
Jun 19, 2018, 7:03:49 AM6/19/18
to racket...@googlegroups.com
On 06/19/2018 02:00 AM, Evžen Wybitul wrote:
>
> Any entity has to have some basic properties (coords, colour), plus
> some types of entities might have their own special properties. And,
> some entities have to be able to move, i.e. have a (move dx dy)
> function which will move them by dx and dy in the respective
> directions. I don't know if this function can be implemented without
> the use of interfaces (and thus classes) — if I have a struct entity
> and a two child-structs player and monster, I'd have to implement the
> move function separately for both of them (because, AFAIK, I can't use
> struct-copy generically). Is there any other way to do it? Or am I
> okay with classes?
>
I've been through this.  There are many options, and it really comes
down to trying out different ones to see how you like them.  I've been
going back and forth on struct inheritence vs. composition.  If you are
starting with classes and understand how that works, I would move
forward with that until you have a reason to change.

In any case, here's how to do a generic struct-copy, but it only works
for prefab structs and a few containers.  That's what I use in my game,
but it doesn't have the nice functional update properties of struct-copy.

(define (copy s)
  (cond
    ((struct? s)
     (apply make-prefab-struct (prefab-struct-key s) (map copy
(struct->list s))))
    ((cons? s)
     (cons (copy (car s)) (copy (cdr s))))
    ((list? s)
     (map copy s))
    ((or (number? s)
         (boolean? s)
         (string? s)
         (symbol? s))
     s)
    (else
     (error "copy unknown datatype:\n" s))))

Thanks,
Dave

Christopher Lemmer Webber

unread,
Jun 23, 2018, 2:59:12 PM6/23/18
to Evžen Wybitul, Racket Users
I have a "taghash" library I've been sitting on that is dead simple
maybe would be helpful if I actually released it. It's basically a
struct with two fields, a tag (which can be super aritrary, usually a
symbol or list of symbols) and then the "fields" which are represented
as a mutable hashmap of sybol -> value. Thus functional updates are
very easy.

As for the methods, I have a generic method system used in conjunction
with it. It's currently fairly naive and uses mutation at the toplevel
of the program to add new methods (but only at the program toplevel) but
supports both predicate based dispatch (O(n) but very flexible) and tag
based dispatch (semi-rigid but O(1)).

If you think this would be useful I could prioritize packaging it. It
should be able to do everything you need, and flexibly, though it's very
loosely structured.

Anyway, that's a suggestion for the datastructures. As for the
engine, I do recommend you check out Lux and Raart; there were some
recent messages about how to use it on this list.

Matthias Felleisen

unread,
Jun 24, 2018, 4:52:37 PM6/24/18
to Evžen Wybitul, Racket Users


> On Jun 23, 2018, at 2:59 PM, Christopher Lemmer Webber <cwe...@dustycloud.org> wrote:
>
> Evžen Wybitul writes:
>
>> Hello, I'm building a simple roguelike and I want it to be as Racket-y as
>> possible. Most of my code is structs (representing the screen, the world,
>> the map etc.), but I managed to implement entities — monsters, player —
>> only as classes. I don't feel hindered by this fact, but I'm not sure it's
>> the best way to it like this; that's why I'm posting here.


Programming with classes in Racket is perfectly fine. Classes are first-class values at run-time even in Typed Racket (so they are not types). So we often develop functions on classes (dubbed mixins) and compose them as we need them. For example, DrRacket is a composition of some 30-40 mixins. Here is a short write up that I have been meaning to add to the Style Guide but it has grown a bit much and would overwhelm it:

http://felleisen.org/matthias/Thoughts/Programming_with_Class_in_Racket.html

— Matthias

Christopher Lemmer Webber

unread,
Jun 25, 2018, 5:16:41 PM6/25/18
to Matthias Felleisen, Evžen Wybitul, Racket Users
Racket's class system is nice, but is it really usable for functional
programs very easily? The reason I wrote my hacky taghash library is
because there's no convenient way to do a functional setter on Racket's
class system afaict. You can manually write such a method for a class,
but then it gets totally unwieldy IME once you include mixins.

Am I wrong? It would be *nice* to have a functional class system that
looks like Racket's class system.

Evžen Wybitul

unread,
Jun 26, 2018, 1:08:48 AM6/26/18
to Racket Users
I think Matthias based his question on the sentence "I don't feel hindered by this fact, but I'm not sure it's the best way to it like this" which I wrote in my original post. He (and rightly so) figured that I'm asking this question mainly because I don't know whether Racket classes are a) natural for the language and not a bad practice, and b) useable and feasible in the future without a risk of being a hindrance later on. 

He didn't necessarily recommend me to use classes in a functional game design, he only told me it is ok to use classes in general (at least I understood it like that). JSYK :-) But we should wait what is his response anyway.

Dne pondělí 25. června 2018 23:16:41 UTC+2 cwebber napsal(a):

Philip McGrath

unread,
Jun 26, 2018, 2:21:52 AM6/26/18
to Evžen Wybitul, Racket Users
I haven't been totally satisfied with functional update either for class-based objects or for structs with subtyping, since, as the original question notes, you can't use `struct-copy` generically to update the most specific instantiated struct type. (There are also some other well-known quirks to `struct-copy` surrounding name mangling.)

Using classes is definitely a reasonable approach. I do it myself, including in purely-functional contexts: when you want lots of OOP features, it's nice to avoid reinventing the wheel. In my experience, though, (and I don't consider myself a racket/class expert) supporting functional update with classes ends up with some sort of protocol you devise as the programmer, like a `copy` method, rather than built-in racket/class features. A particular consideration is that, in the context of functional update, you may want to handle object initialization differently that when creating an instance from scratch.

To provide some alternatives, though:

some entities have to be able to move, i.e. have a (move dx dy) function which will move them by dx and dy in the respective directions. I don't know if this function can be implemented without the use of interfaces (and thus classes) — if I have a struct entity and a two child-structs player and monster, I'd have to implement the move function separately for both of them (because, AFAIK, I can't use struct-copy generically). Is there any other way to do it?

Yes, you can implement a move function without racket/class. The high-level way to do it is with generic interfaces for structs from racket/generic. The way I would probably do it would be to define an interface like `movable` with a `move` method that would essentially wrap `struct-copy` with the specific struct type in question. (You could even write a short macro to further reduce boilerplate.) If there's a lot of logic involved in your `move` function, I would use the generic method for only the part that is struct type specific (in which case I would might call it `copy-for-move` or something). If you have several functions in additional to `move` that need functional update, you could define a `copyable` generic interface with either a big `copy` method, which would probably use optional keyword arguments, or a bunch of little methods like `copy-for-move` that rely on macros to reduce boilerplate.

There are some pitfalls to racket/generic. The biggest one, in my view, is that protecting yourself by contract from buggy implementations of the interface you define is subtle, and failing to do so can result in counter-intuitive blame. See https://github.com/racket/racket/issues/1710 and https://github.com/racket/racket/issues/1440 for ways main-distribution libraries fail to protect themselves from buggy clients; there is further discussion at https://groups.google.com/forum/#!msg/racket-users/3fTKqqcOnxs/hU9XqZpsAgAJ You can avoid this by using structure type properties directly, especially for simple, one-method interfaces. (Generic interfaces are implemented using struct type properties.) The `struct-type-property/c` contract can enforce requirements for the values attached to struct type properties; the racket/contract library uses these sorts of ad-hoc interfaces (see for example `build-contract-property`).

For something very different, but very "Racket-y," you could make a DSL to implement your entities. Matthew Flatt's paper "Creating Languages in Racket" (https://queue.acm.org/detail.cfm?id=2068896) uses a text-based adventure game as an example of how Racket's language-oriented programming capabilities work. This can seem a bit intimidating, especially if you're new to Racket, but I've had success at growing iteratively toward a DSL: for example, you could start with a `copyable` generic interface to create a hook for functional update, add a `define-copyable` macro implemented using `define-struct/derived` to add a `#:methods` section for the `gen:copyable` interface automatically, and gradually grow from there.

-Philip



Philip McGrath

unread,
Jun 26, 2018, 2:37:38 AM6/26/18
to Evžen Wybitul, Racket Users
Just for fun, I coded up a quick example of how `define-copyable` might work.

#lang racket

(require racket/generic
         rackunit
         (for-syntax syntax/parse))

(struct abstract-base-entity (x y)
  #:transparent)

(define-generics copyable
  (move copyable x y))

(define-syntax define-copyable
  (syntax-parser
    [(_ name:id field-spec struct-option ...)
     #`(define-struct/derived
         #,this-syntax
         (name abstract-base-entity) field-spec
         #:methods gen:copyable
         [(define (move this x y)
            (struct-copy
             name this
             [x #:parent abstract-base-entity x]
             [y #:parent abstract-base-entity y]))]
         struct-option ...)]))

(define-copyable monster (species health)
  #:transparent)

(check-equal? (move (monster 0 0 'goblin 100)
                    10 42)
              (monster 10 42 'goblin 100))



-Philip

Matthias Felleisen

unread,
Jun 26, 2018, 11:25:34 AM6/26/18
to Evžen Wybitul, Racket Users

> On Jun 26, 2018, at 1:08 AM, Evžen Wybitul <wybitu...@gmail.com> wrote:
>
> I think Matthias based his question on the sentence "I don't feel hindered by this fact, but I'm not sure it's the best way to it like this" which I wrote in my original post. He (and rightly so) figured that I'm asking this question mainly because I don't know whether Racket classes are a) natural for the language and not a bad practice, and b) useable and feasible in the future without a risk of being a hindrance later on.
>
> He didn't necessarily recommend me to use classes in a functional game design, he only told me it is ok to use classes in general (at least I understood it like that). JSYK :-) But we should wait what is his response anyway.


That’s precisely what my response meant.

I have coded in our class system in a purely functional style, using the ability to evaluate the class position in (new <class> ,,,) and retrieving the current class. But yes, this takes a protocol and disciplined adherence to it.

Which brings me to what Philip McGrath <phi...@philipmcgrath.com> wrote on Jun 26, 2018, at 2:21 AM:

> For something very different, but very "Racket-y," you could make a DSL to implement your entities.

When you see complex protocols and when you think others may wish to modify your program, Racket allows you to wrap a language around an implementation and a language exists to enforce protocols statically when an API can do it only dynamically (among other things). But you need a library first if you want to hide one.

— Matthias

Reply all
Reply to author
Forward
0 new messages