So I made this other game... (Nonaga)

Hello dear people,

I learned a lot from you last time when I submitted code for a Tic-Tac-Toe, so I present you an implementation of Nonaga. The code is bit more involved (500 LOC), but the game is much more fun.
I would really appreciate some feedback on the implementation, so thanks in advance :slight_smile:

The rules : https://steffen-spiele.de/fileadmin/media/Spiele/Nonaga/Nonaga_EN.pdf
The code : https://ellie-app.com/gLNNFbBtdk3a1

Specifically, a few interrogations that I had while writing this :

  • I found domain modelling a bit awkward: I tried modelling my tokens as type alias Tokens = Dict Player Set but was surprised to find that Union Types weren’t comparable. How would you have modeled the tokens?

  • Defining type alias Tokens = Dict Platform Player and using Dict-specific functions on data of type Tokens felt wrong somehow, like I am breaking encapsulation or something. And indeed, my experiments changing the underlying data structure to Tokens = List Token involved quite a few modifications (although the compiler helped a lot, of course). How do you cope with this?

  • I still ended up duplicating code (a little) when dealing with “If I have this condition, I will pipe into this function, otherwise I don’t”, see line 380-388 for an example. My guess is the GraphicsSVG API makes it a bit awkward by relying so much on piping and not on functions arguments. Do you face similar “problems” while using e.g elm-html?

  • Because of the duplication problem, I considered sending Message at inappropriate times (e.g during the wrong phase of the turn, or after the game is over), but ignored in the update. Instead, I chose to not have event handlers in the view when the Message would be appropriate, but it sometimes felt like “putting business logic in presentation layer”. What approach do you favor?

  • Surprisingly, a single file project of 500 LOC felt ok, what is your personal limit before breaking it into modules?

Wow, so many questions about 500 LOC! Hopefully you don’t dread too much the first time I hit 1k :grin:

Cheers

6 Likes

First, this is really awesome and I really enjoyed playing it!

As for your questions:

  • I sometimes use alternatives to Dict that allow for custom keys. Other times I just accept changing my data structures around. I personally haven’t found a singular solution that I prefer yet.
  • I don’t mind code duplication, unless it’s causing issues somewhere. Clarity is always greater than code duplication/de-duplication for me. However you also don’t have to always be piping everything, unless you want to. E.g. line 384-385 doesn’t need to be a pipe.
  • I tend to not worry too much about messages being sent at the wrong time unless it’s a performance concern.
  • My limit is around 2k - 3k loc. In my current side project I have space-sim/Playing.elm at main · wolfadex/space-sim · GitHub which I think was close to 3k loc at one point before I broke it up. Not sure if I’ve hit 4k loc in a single file yet but I wouldn’t be surprised.
1 Like

Thanks for the kind words, and for your answers as well :slight_smile:

I’ve thought a bit about having event handlers at the wrong time and actually expect the approach to be cleaner after all: it would gather the logic in the update function and allow it to be checked by the compiler to some extent (e.g exhaustive pattern matching). I think removing the event handlers makes the view code a bit hairy and might create an “implicit state machine” problem.

I’ll try it out!

In language I’d describe the game as two players having 3 pieces of their (red or black) color. So model like

Player = { color: Color, pieces: List Platform }

feels closer to this description and thus more natural and easy to use.

Thanks for the feedback!

I implemented your suggestion as follows:

type alias Player =
    { player : Color, tokens : Set Platform }


type alias Model =
    { currentPlayer : Color
    , turnPhase : TurnPhase
    , board : Board
    , players : List Player
    , lastMovedPlatform : Platform
    , selectedToken : Maybe Token
    , selectedPlatform : Maybe Platform
    }

In practice I have the following problems with this model:

  • I still can’t directly access a player’s tokens (like the Dict Color (Set Platform) would have).
  • It is more involved to can’t check if a platform is empty and I loose the constraint of “one token max on a platform”.
  • This introduces some additional nesting that makes the update a bit more complex.

The pros being:

  • This does seem to model the game better, with improved names and straightforward types.
  • It avoids the Dict wrangling which is quite nice, though.
  • Domain functions dealing with Player type are nice.

I am considering going back to a Dict Platform Color, but with nice helper functions to get a player’s set of tokens, move the token, etc… And see which I like better

This is clearly overthinking it, but well, I have time and the goal is to improve :slight_smile:

The updated code is here : https://ellie-app.com/gNx2FTFYR3za1

2 Likes

Well you don’t have to stop here. Replacing players with

, currentPlayer : Player
, otherPlayer : Player

will give direct access to data necessary and simplify winning condition check.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.