Let's discuss ways to render a grid-based game

Soon I’ll lead a typed FP high-school course based on Purescript. The goals of the course are to showcase the functional programming paradigm and benefits of strong type system, and also to teach them things they might not encounter in the usual Programming 101 in college. And because it is aimed at 17 year olds, all of this will be properly illustrated on small real-world projects, games and simulations.

The problem

Let’s say we’re implementing Conway’s game of life, where you can click the cells to toggle them, and press space to advance the simulation by one step. What would be the recommended way to go about rendering the grid of cells and listening for the user input?

Possibilities

Considering my goals (showcasing proper FP, showing new interesting things), which one would you choose? Which one would feel the best? And what are the different tradeoffs?

Non-FRP-based libraries:

  • game, the game engine from @artemisSystem
  • emo8, the emoji game engine
  • canvas, could lead to foreign-feeling code, depending on the API (sort of like using bindings to a C lib in Haskell)

Now, I’m not sure whether FRP is suitable for this type of problem, but if it is, you can use:

And of course, there’s also the “traditional” UI libraries, although I’m not sure whether they would be good for this, either:

  • any of the react libraries
  • halogen

Conclusion

I’m sure I’m not the only Purescript beginner wondering how to do this. I think it would help others greatly to have an opinionated list of “you need X? use Y!” aimed at beginners and the common use cases; currently there’s things like awesome-purescript, but they’re not granular enough.

I tag @milesfrain, because he seemed to have been interested in helping getting beginners up-to-speed. There’s also an old thread with a similar question.

7 Likes

What I’d usually say to people thinking of writing a game is that in my experience, the canvas API is perfectly adequate for most use cases, and in game programming the impulse to do things in a “proper”, declarative, purely functional way can end up being rather counterproductive (especially if you’re new to it). My advice would be to ignore that impulse and write a game using the canvas bindings directly anyway, and only worry about different ways to do it afterwards.

However, in your case, there’s probably a stronger argument for avoiding the canvas API in favour of something more functional. I’d recommend looking into https://github.com/paf31/purescript-drawing, which is a more declarative layer on top of canvas.

3 Likes

This doesn’t really answer your question, but I thought you might like to see my attempt at implementing game of life for a coding interview, using cofree to store the grid

I believe the main branch is deployed at

Here’s a tris example (live demo) that uses signal + canvas and should be pretty adaptable to GoL.

There are a few more examples in the cookboook’s recipe table. Search for “behaviors”, “signal”, and “tictactoe”. The behaviors library seems more appropriate for modeling continuous-time stuff (e.g. physics simulation), rather than something with discrete time steps like GoL.

Another option is using svg elements (example with halogen) which can be convenient if you want to add a unique clickable event to oddly-shaped elements. But for grids, calculating the cell based on canvas click coordinates is simple enough. I think there can also be situations where modifying individual SVG elements and relying on DOM diffs can be faster than redrawing the canvas, but the overhead is probably not worth it for game of life. You can also investigate optimizing the canvas approach by only redrawing over what’s changed, instead of completely redrawing everything from scratch after each update.

As a side note, there’s an excellent Rust + Wasm tutorial which covers building GoL (live demo). Curious to see how performance compares to a reasonable PS implementation and a JS-only version.

5 Likes

Yeah, in general I agree, bit in my specific case I think it makes sense to do it the functional/declarative way. However, the project you linked hasn’t seen an update in two years; maybe I’ll use canvas-actions, then, which seems to provide a declarative layer over canvas as well, but seems to be alive.

1 Like

Awesome, thanks a lot. Halogen + svg might be better in cases in which I’d like to provide some kind of UI as well (say, some buttons to control the speed of the simulation and to change the rules), because I could use Halogen quite easily to implement that.

1 Like

My first instinct would be to use basic web buttons for those UI controls in that case, and still use canvas. So you’d have Halogen + canvas.

1 Like

I don’t think canvas-action is really any less imperative than the basic canvas API is, it just provides a few tools that make it a bit more convenient to use.

1 Like

Ok. And just to be sure, signal and Halogen are two alternatives that strive to achieve the same goal, correct? Or should/can they work together?

1 Like

I wrapped everything I needed from thi.ng/geom (for geometry) and thi.ng/hiccup-canvas (for rendering) in a declarative api for the project I’m currently working on. This approach works great on top of concur!

(I’ve also ffied js code using those libraries on top of halogen in the past, so halogen works just as well).

I think this would probably be overkill for GOL, but thought about posting it here nonethereless, in case someone finds it interesting :sweat_smile:

Note: I’ve just noticed some of the comments in my code are there from an older version using recursion schemes, gotta update them some day…

1 Like

I don’t know if one could or should blend signal and Halogen.

I’m also not sure what the best strategy is for this in Halogen. Is creating a custom hook for Window.requestAnimationFrame() appropriate? @thomashoneyman for insight.

Concur is also a great option for this project. I’d suggest rewriting this app in a few different frameworks and then deciding which version you think will be best-received in a highschool class. It would also be very illuminating to see the various GoL implementations for comparison in the cookbook.

1 Like

I’d suggest rewriting this app in a few different frameworks and then deciding which version you think will be best-received in a highschool class.

Yeah, that’s an option, too. I’m quite time-constrained atm, but if I come around to do this, I’ll definitely add it to the cookbook.

Concur is also a great option for this project.

I’ll check it out. The PS version is based on React components, right? That would fit into my skillset well. What implementation do you have in mind? Does it make sense to model the grid cells divs with different background colors (the naive way to do it in React)? Or do you have something else in mind?

Yep. See concur-react. Here’s an example of rerendering on a time interval.

Yet another option is react-basic-hooks. Here’s an example of rerendering on intervals. The same approach can also be applied to halogen-hooks.

I was thinking a single canvas element would be more efficient than a grid of cell elements if you have lots of cells, but can’t say for sure without trying both versions. Grid of cells would be easier to build though, so I guess start with that, then see if you encounter performance issues.

Oh, so still a canvas-based solution (with one of the canvas libraries), but with the buttons handled by concur rather then Halogen.

In all versions, the high-level buttons (speed, size, etc.) should just be basic buttons that you define in the framework’s idomatic way. Framework options proposed so far are:

  • halogen / halogen-hooks
  • react-basic-hooks
  • concur

For the grid of cells, you could either make that a grid of elements (each element representing a cell), or a single canvas element that contains all of the cells. Even with the single canvas element, you can still let the cells be clickable/toggleable, but it requires some more work to translate the canvas click coordinates to a cell index.

1 Like

Yeah, now I understand. For a minute I though that when you said that “Concur is also a great option for this project” you meant the whole project, i.e. the ‘buttons’ together with the grid itself.

How exactly I do the buttons isn’t really the main problem anyway; I presume PS has good support for building traditional (i.e. html-based) interfaces.

Thanks for the assistance, all is clear now. Not that it matters for this exact project, but is there any place I can read up on the pros/cons of Halogen and Concur? Both seem really interesting.

1 Like

Also, to clarify, I believe that for any framework you’d want the grid (canvas or not) to be encapsulated in a component. So the choice of framework somewhat takes over the whole project.

A comprehensive framework comparison guide would be a wonderful community resource. I started putting a few notes together about that in an unofficial FAQ. Still lacking details though. We’re trying to do more side-by-side framework comparisons in the cookbook. There’s also the “realworld” apps for halogen (non-hooks) and react-basic-hooks, but no concur version yet.

2 Likes

Thanks again. Seems like we (me, the teacher, and the whole class) are up for a much rougher ride than I originally anticipated. Hopefully I’ll be able to provide some cushioning so that my students don’t feel too lost.

1 Like

I think the PS community is pretty self-aware that the ecosystem and onboarding experience are not quite ready for mainstream adoption yet. So what about starting the class out with Elm, then if that goes really smoothly, you can introduce PS? That way, there’s less risk of students having a frustrating time and swearing-off typed FP forever. If Elm proves challenging enough, then you can stick with that for the remainder of the course, while still meeting the core goals you outlined in your original post. I think many folks who have a good initial experience with Elm will eventually give PS a try. As long as you’re not forcing students to hand-write massive JSON decoders or do a lot of FFI via ports, I don’t see any time wasted by taking a detour though Elm. Using Elm as a stepping stone might even be an overall faster path to success in PS.

When’s the first day of class?

1 Like

@Eugleo I’m a bit biased but I feel Concur is a great way to teach FP to newcomers.

Because Concur turns asynchronous interactions into synchronous seeming code, it is very easy for newcomers to grasp. Infact, I’ve found that people who have existing experience with FP and other GUI frameworks struggle whereas people new to FP (or even new to programming altogether) pick it up extremely quickly.

I answered a similar question here - https://ajnsit.github.io/concur-documentation/ch05-01-why-concur.html.

Secondly, Concur has React bindings which make it easy to integrate with real world widgets and code.

Also, writing canvas bindings for Concur should be easy because canvas’ immediate mode rendering model is a perfect fit for Concur. I will try to work on creating bindings for the browser canvas.

EDIT: I will put the code here - https://github.com/purescript-concur/purescript-concur-canvas

1 Like