Tick Tock Toe - example game with immutable tables and undo mechanics

Simple Tic Tac Toe game with undo mechanics:

tick-tock-toe

This is an example of using immutable tables with my open source runtime immutability implementation presented here:

Game architecture

Game architecture and 3 different versions of the game are described in details in README.

Basically it shows the power of developing a game with immutable tables (though in release, runtime immutability can/should be discarded) and how easy is to implement undo mechanics when immutable game states are tracked in a game history stack.


It also shows an architecture, where game is divided into 3 separate parts (“streams”) that are responsible for input, game logic and visual representation and are connected through a common contract of immutable data (game_logic_input and game_state).

It’s kind of inspired by Flow-based Programming that was introduced to me by @dlannan, but I wouldn’t say it’s “pure” FBP.

Streams here can be presented on a graph, where each [ module ] connects with each other through immutable data:

The parts can be combined freely in game.script - there could be a version with mouse and/or keyboard inputs and visual representation in console outputs only or in graphical game window too.

For example:


  • console_game_example uses only numeric keys (1-9, as if numeric keyboard is a game board) inputs and console prints, e.g.:
    Current Player: O
    DEBUG:SCRIPT: 1 - - X
    DEBUG:SCRIPT: 2 - O -
    DEBUG:SCRIPT: 3 X - -

Graph:
[ input\numeric_keyboard.lua ]game_logic_input[ logic\tic_tac_toe.lua ]new_game_state[ visual\console_print.lua ]


  • visual_game_example uses only mouse inputs and visual representation is in game window (using label components), e.g.:

game_example

Graph:
[ input\mouse.lua ]game_logic_input[ logic\tic_tac_toe.lua ]new_game_state[ visual\label_print.lua ]


Source code:


License: MIT

Copyright: Paweł Jarosz 2024


This project includes immutable module as a Defold dependency library. It can be used in your Defold projects as Dependency library: Point release version in Dependency in your game.project, e.g.:

image

8 Likes

This sounds really attractive! I’m always in search of game architectures that make my life easier (ECS was the last one I embraced and that I still use in larger real-time games). While I’ve read a bit about Flow-based programming, do you know about more resources to read about why these immutable tables and why using them makes your life better? :smiley:

1 Like

One thing is Data Oriented Programming, I mentioned here:

https://forum.defold.com/t/shooting-circles-defold-ecs-game-example/77465/9?u=pawel

It’s literally a rule of DOP to have immutable data.

The FBP was mentioned in that post by David, I just got into this, can’t really share anything really good yet, just reading Internet :grin: maybe @dlannan, you can? I looked up your deity project, but it’s too complex for me yet :sweat_smile:

It’s literally a rule of DOP to have immutable data.

Interesting.
I’ve never heard such a rule before. And none of the code I’ve practiced (or seen presented) has had that constraint. E.g. I don’t recall seeing such a rule in any of Mike Acton’s presentations (I can really recommend those)

However, it may be beneficial of course, to not write to the same memory in certain cases, but that has more to do with ownership, and cache locality imho.

Come to think of it, I’m not sure the what the “immutable rule” is actually for?
In my mind the “data oriented” means “do what’s best for you with respect to data and the problem domain”, which of course will vary between problems.
In any case, interesting!

1 Like

It’s not uncommon to “lock” a table after the loading has been done, in order to make sure nothing tampers with the data.
It’s useful when there are several developers in the project, and they might accidentally alter the wrong table.

Mike Action talked about DOD and it is DOP =)
Data oriented design - all about data for hardware (make data layout cache friendly) e.g. ECS
vs
Data oriented programming - split data and functions and don’t mutate data (it’s more like not OOP arch)

3 Likes

Yeah, precisely this. DOP is term introduced and promoted by Yeonathan Sharvit, but it has roots of its ideas in many different other programming “ways” :wink:

It’s more of a solution to untangling complex architectures rather than DOD focus on performance. Per se a program using DOP isn’t automatically better performing, it just might be better to read, scale and maintain.

1 Like

Ah, my mistake confusing the two. Thx!

1 Like

For such small example this doesn’t matter, but DOP is positioned as complexity reducer for big projects, but at the same time doesn’t care about performance. So my question is “Isn’t the slowest parts of the lua, especially luajit, are allocating space for tables and constant garbage collection of this tables?”. Wouldn’t it impact performance of the game with a lot of entities?

1 Like

Yes, for sure. Note this module offers runtime immutability and nothing that is runtime and does not server the game / program itself is affecting the performance and should be optimised.

Yes, the project is just an example, too small to bother, but small enough to show the two most important features - immutability of data state and separation of streams.

Hence, I would recommend it as a module to “learn” writing code with immutable data and get rid of all conversion to immutable structues in release (this can be also achieved with Preprocessing).

If you are really need immutable data in your program, you should indeed most probably look for a native solution (or Lua FFI) :wink:

3 Likes

Not sure if it too much to ask, but because this is “learn by example” project, maybe including this preprocessinng that removes immutability would be good thing?

Personally I not really get it, by “removing” immutability in release, you mean just write data in one table instead of recreating multiple tables or removing “Immutable” module? If first, could this bite when all works fine in debug mode, and you would introduce some bug with the code on release mode because of such diversity? If second, isn’t garbage collecting would still affect game’s fps too much?

Sorry, if the questions sounds dumb, I’m relatively new to game developing.

FBP is a bit of an old paradigm. Like most things, invented in the early days of computing. The concept is simple enough but many differ on the processes, language (terminology) and implementation.
For me, I ‘discovered’ FBP while working with a friend at Ratbag. We were building a large scale open world game at the time and wanted to break down “how worlds work”. And there was a whole story… of “kicking the bin” that ensued… however we landed upon everything is transactional in nature, and that transacted information was data. Everything else was function :slight_smile:

This is the fundamentals of FBP (we didnt know it at the time :slight_smile: ). Essentially you can think of it like Matlab code blocks where you have IO ports and you pass data in and out those blocks, the blocks are just function components, maybe with their own memory, and maybe with some shared memory (much like GPU shaders btw). The “programming” is wiring the blocks together. Block are repeatable, configurable and managed - I built a kernel of sorts that ‘looked after’ all the modules and managed their routing as well as providing a runtime debug interface for the data management.

All the modules had IO ports, much like a socket on a computer. In fact in later iterations a friend made a version that was socket based and could be used across as many devices as needed. Unlike many Data driven design systems though, the data was most definitely mutable :slight_smile: (Sorry @Pawel ).
In fact that was kinda the point. Some of the greatest features of such a system was that you could switch renderers at runtime :slight_smile: I had a simple 3D renderer where you could switch between OGL and DX7 live (yes this was circa 2001).

I later found out about FBP by Paul Morrison, and bought his book and joined the FBP discord (sadly not been very active). He has different ideas about how its implemented, but its a fascinating space to learn quite good programming practices - one of the key problems of FBP is you just dont really code the way you are used to. Since the “programming” really is in the networking of modules, and the configuration of module states.

Paul has been a big proponent of FBP and some of his implementations in areas like banking speak highly of its viability as a development system. What I liked about it, was reusability. A module, that you know did X… would be used time and time again and since its a black box there is very little need to worry about changing it. There are of course many things to consider, like versioning, granularity and so on. But its a fascinating space… I have been meaning to go back there one day…

Oh and the link. I’ll add deity’s link here since Pawel mentioned it :slight_smile: (Its really not that complex)
https://github.com/dlannan/deity : Warning: Its old and I havent attempted to build it in years :slight_smile:

2 Likes