Updated the SnakeDCI game!

106 views
Skip to first unread message

Andreas Söderlund

unread,
Sep 18, 2018, 4:01:34 PM9/18/18
to object-composition
Hello everyone, long time no see. I've spent some time modernizing the Snake game I wrote a long time ago, now it runs in the browser and maps to the use case format shown in the Trygve documentation.

Check it out and let me know what you think about the code! https://github.com/ciscoheat/SnakeDCI

/Andreas

Andreas Söderlund

unread,
Sep 23, 2018, 5:07:23 AM9/23/18
to object-composition
I know it can be tedious to look a whole project, so I'm gonna expand my quick notice with some question formulations.

Since I'm using a framework for the game, there are some useful base classes that can play roles, "Sprite" for a snake segment and "Group" for the snake itself. What I've been struggling with here is if I should extend these to base classes related to the game, or keep them as simple as possible, letting the Contexts do the heavy lifting.

Right now I'm extending a few classes, the Snake for example, which is simple enough to show the whole source here:

class Snake extends Group {
   
var _textures : SnakeGame.Textures;

   
public function new(game, textures) {
       
super(game);
       
this._textures = textures;
   
}

   
public function addSegment(x, y) {
       
this.create(x, y, this.length == 0 ? _textures.head : _textures.segment);
   
}
}

It's so simple that I'm considering removing the addSegment method, putting it in the Movement Context. This will keep the Role players and the RoleObjectContract small, at the expense of increased Context logic and state. I must move "_textures" into the Context, for example. And if some other Context wants to add a segment, it must implement its own logic. 

Another example is if I want to save the Hi-score to the browser. Since the "Text" class has no knowledge of the browser, naturally, should I extend it to a Highscore class that handles its own save/load (which makes it start to look like a Context), or put it in the collision Context, that handles the Game Over part?

I notice that by creating methods like "addSegment" on a Snake class, eventually, that class will be so specific that the RoleObjectContracts will be tailor-made to that class, getting closer to the "Tower of Hanoi" issue with genericity, as mentioned in the trygve documentation.

I think you see the general problem here. When to create Domain-like classes as opposed to extending a Context?

Rune Funch Søltoft

unread,
Sep 23, 2018, 5:31:52 AM9/23/18
to object-co...@googlegroups.com
What prohibits you from a snake context?

Mvh
Rune
--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at https://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

Andreas Söderlund

unread,
Sep 23, 2018, 5:40:42 AM9/23/18
to object-composition
As I see it, it's far too simple right now. There are no roles in it, just a reference to graphical textures. As long as there are only a few private properties inside it, and very little interaction between those, I'm hesitating to make it a Context.

But I know you may have a different (stricter?) view on this Rune, so would you say that as soon as you add something relevant to the mental model to an object, that object should be considered a context?
To unsubscribe from this group and stop receiving emails from it, send an email to object-composition+unsub...@googlegroups.com.

Rune Funch Søltoft

unread,
Sep 23, 2018, 7:01:20 AM9/23/18
to object-co...@googlegroups.com
Why would you say there’s no interaction? That would imply that it’s just a struct kind of object. 
My point in general is that quite often people miss the interaction that happens because it’s labelled a class

Mvh
Rune
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.

Andreas Söderlund

unread,
Sep 23, 2018, 7:27:54 AM9/23/18
to object-composition
The framework classes are huge, but that I can do nothing about, I'm just using them in Contexts, and treating them struct-like (state containers or MVC models) currently. Where is the Role interaction in the Snake class code that I posted? It only calls its own method.

Matthew Browne

unread,
Sep 23, 2018, 7:56:13 AM9/23/18
to object-co...@googlegroups.com
Snake seems more like a role to me than a Context, but at a higher level of abstraction - interaction between the game player and the snake - not something that necessarily needs to be reflected in the use case or code that you have. I started reading through the code and the use case and overall I find it quite readable - great work!

If changing from a class to a context would simply mean changing the keyword “class” to “context” and using composition instead of inheritance but still basically doing nothing other than using the base class functionality without modification, then I don’t see what would be gained by that.

On Sun, Sep 23, 2018 at 7:27 AM Andreas Söderlund <gaz...@gmail.com> wrote:
The framework classes are huge, but that I can do nothing about, I'm just using them in Contexts, and treating them struct-like (state containers or MVC models) currently. Where is the Role interaction in the Snake class code that I posted? It only calls its own method.

--

Matthew Browne

unread,
Sep 23, 2018, 8:00:39 AM9/23/18
to object-co...@googlegroups.com
P.S. I realize of course that Contexts can and often do play roles. So to be more precise, I see Snake as a role *not* played by a Context - but at a higher level of abstraction as I said. So I think the current implementation of Snake is good.

Matthew Browne

unread,
Sep 23, 2018, 8:05:34 AM9/23/18
to object-co...@googlegroups.com
...at least, not unless you were to ditch classes entirely for anything other than immutable data structs (that seems to be Rune’s style). Which would be a viable option that I like (at least in theory), but I gather that that you’re aiming for traditional DCI here.

Rune Funch Søltoft

unread,
Sep 23, 2018, 8:48:06 AM9/23/18
to object-co...@googlegroups.com
When you extend a class you have at least one roleplayer namely base/super. For each instance field/property you add in the case and use in conjunction with anything derived from the base class, you also have an interaction. So when extending a class you either have interactions or you probably shouldn’t derive your class in the first place (nothing of what you’ve added have any interactions with the base)

Mvh
Rune

Rune Funch Søltoft

unread,
Sep 23, 2018, 8:56:56 AM9/23/18
to object-co...@googlegroups.com
Well I would argue that there was an (implicit) change from context to class, and not using a class would simply simplify the mental model for the developer. Not the other way around :)

Why is class a more natural choice in DCI than a context. A historical argument is not valid. Ie something that can be reduced to “we’ve always done it like that”. I’ve seen one argument on this list that doesn’t fall in that category. Namely when using classes for classification purposes

Mvh
Rune

> Den 23. sep. 2018 kl. 13.56 skrev Matthew Browne <mbro...@gmail.com>:
>

Matthew Browne

unread,
Sep 23, 2018, 9:43:26 PM9/23/18
to object-co...@googlegroups.com
I am in favor of whatever solution is most readable, and you’re right - that shouldn’t be based on just history and familiarity but what would be most readable to someone without a class-oriented background. I’m not familiar with all of Haxe’s capabilities when it comes to forwarding method calls to a nested object. If there’s an elegant way to do that, great. Otherwise I think a bunch of manually written forwarding methods would just add noise to the code rather than helping readability. On the other hand, maybe the principle that it’s conceptually a Context because of the little interactions going on inside it is more important. So basically I’m not that convinced of my own argument, but I’m also not convinced that making it a Context would really help to make it more understandable.

Egon Elbre

unread,
Sep 24, 2018, 2:50:35 AM9/24/18
to object-composition
Notes in no particular order:

Organization by category (contexts, data, models, views and similar) tend to harm locality of ideas. At this point there isn't much code so a single folder would probably be sufficient.

Looking at code for snake, it's difficult to understand it. It's unclear whether it's grid based or smooth. It's not clear where the snake length is represented.

In Movement context, Keyboard is hardcoded. The role is "Controller", rather than Keyboard. Keyboard is the object playing the role.

Having segments logic as part of Movement context, seems weird. To me it looks like the "Movement context" is actually the "Snake".

For some reason Collisions context is also tracking and updating points. Similarly it contains FRUIT placement logic, text updating and other things. It seems very confused in what ideas it represents.

"Movement" system having own timer will probably be problematic eventually. I.e. imagine having to add bullet-time.

"Movement" system is also triggering the collision system (imagine what happens when  multiple snakes are on the board). Collision, Movement, Time are all usually orthogonal.

But, it should be going without saying, It's awesome to see new content. :)

+ Egon

Matthew Browne

unread,
Sep 24, 2018, 7:59:25 AM9/24/18
to object-co...@googlegroups.com

Some feedback on the use cases...

I found them a little confusing to read at first, but that might just be because of the format (i.e. "Deviation" as the first column). Overall I think you did a great job of mapping the use cases directly to contexts and roles, down to each individual step.

One thing that didn't match though was the Keyboard role in the Movement context. The use case says the Head "Asks Keyboard for its direction" and the Keyboard "Looks up the currently pressed down key (if any)", but that's not how it happens in the code. I wonder if the use case is just for an earlier version of the code and you overlooked this?

I think Egon made some good points about the mental model. So although the code generally maps very closely to the use cases, I think it would be worth backing up and thinking about / discussing the use cases themselves. This is an interesting case because a game is different than a business app...I'm not sure it would work to write the use cases almost entirely from the end user's perspective, which I assume is why the use cases say "Author goal" and not "Player goal" or "User goal". While you could certainly write use cases that primarily describe the system from the player's perspective, a lot of important details that need to go in the code would be missing. Still, perhaps the use cases as they are are leaning too far in the direction of the programmer's mental model and implementation.

Andreas Söderlund

unread,
Sep 24, 2018, 11:55:26 AM9/24/18
to object-co...@googlegroups.com
I think you're right Rune, there are interactions going on behind the scenes that would certainly qualify the Snake as a Context. I'll try that approach in the next iteration.

What bothers me a bit is that the same name turns up both in what the system is and what it does.

What the system is: A Snake composed of a Group (of Sprites)
What the system does: Collision testing, containing a Snake role.

Does this hint of a lack of genericity? Egon was also into that Movement looked more like the Snake, which would also strengthen that case, but isn't this blurring the border between is and does? Can the snake move by itself? How? In what Context? Etc.

(I will reply in detail to Egons comments in another mail)

Andreas Söderlund

unread,
Sep 24, 2018, 1:44:48 PM9/24/18
to object-co...@googlegroups.com
Thanks for all your feedback Matt. I agree that the deviations are a little bit messy, I didn't want to write them on the bottom in a single sentence, because there will probably be Role interactions in a deviation too. But I wanted to try the format, in comparison with the other (Library borrowing machine). And you're right, the Keyboard being asked for its direction is an oversight, I will fix that.

I wanted to experiment with the use case level, putting it as how a game creator (an experienced player, but not a professional programmer) would describe the game. Then see if a programmer could "fill in the blanks" in the RoleMethods, doing some computations with help of the RoleObjectContract, fulfilling the use case that way. But the details, yes, looks a bit spread out, and I'm not happy with the Context state, which makes the Context almost like a mix of standard OOP and DCI. It's hard to avoid though, if you only want Roles as specified in the use case... Well, the search goes on, thanks for helping out!

Andreas Söderlund

unread,
Sep 24, 2018, 2:11:34 PM9/24/18
to object-co...@googlegroups.com
Hello Egon, thank you, I don't think I will be able to satisfy you on this one, though. ;) Here are my comments, just remember that this is meant to be a simple game. Expanding it to multiplayer etc, would completely change the whole picture.


Notes in no particular order:

Organization by category (contexts, data, models, views and similar) tend to harm locality of ideas. At this point there isn't much code so a single folder would probably be sufficient.

I'm doing an is/does organization here, using the DCI terms, and that will probably be the only namespacing I will do.

Looking at code for snake, it's difficult to understand it. It's unclear whether it's grid based or smooth. It's not clear where the snake length is represented.

True, there is no focus on a grid system. I guess it gets a bit more clear with more knowledge about the game framework, the Roles are referring to the pixel x/y of the playfield, and I let the Movement context handle the grid to view mapping, simply because that's what I have access to in the RoleObjectContract.

In Movement context, Keyboard is hardcoded. The role is "Controller", rather than Keyboard. Keyboard is the object playing the role.

Good point, it should definitely be Controller. Will change it.

Having segments logic as part of Movement context, seems weird. To me it looks like the "Movement context" is actually the "Snake".

Talked about this earlier in response to Rune.

For some reason Collisions context is also tracking and updating points. Similarly it contains FRUIT placement logic, text updating and other things. It seems very confused in what ideas it represents.

It follows the mental model as specified in the use case. The Game Over could be its own Context, and will probably in a larger game, but where to draw the line...

"Movement" system having own timer will probably be problematic eventually. I.e. imagine having to add bullet-time.

Not a feature on the roadmap. :) This is a simple example, no other featured are planned, which reflects the system of course. Designing the system for "infinite flexibility", where every whimsical management idea must quickly be realized, could quickly lead into overengineering, the bane of many projects. 

"Movement" system is also triggering the collision system (imagine what happens when  multiple snakes are on the board). Collision, Movement, Time are all usually orthogonal.

Movement and Collisions are closely related, I almost decided to join them together since there are interactions happening between them, like "when colliding with the fruit, the snake should grow on next move". This inter-Context communication is something I haven't seen much talk about here. I think it could imply an encompassing Context, but for now I just put it in a System Operation, to at least separate it from the interactions, so if multiple snakes is required, at least its easy to separate.

Egon Elbre

unread,
Sep 24, 2018, 4:22:46 PM9/24/18
to object-composition
On Monday, 24 September 2018 21:11:34 UTC+3, Andreas Söderlund wrote:
Hello Egon, thank you, I don't think I will be able to satisfy you on this one, though. ;) Here are my comments, just remember that this is meant to be a simple game. Expanding it to multiplayer etc, would completely change the whole picture.

Notes in no particular order:

Organization by category (contexts, data, models, views and similar) tend to harm locality of ideas. At this point there isn't much code so a single folder would probably be sufficient.

I'm doing an is/does organization here, using the DCI terms, and that will probably be the only namespacing I will do.

Looking at code for snake, it's difficult to understand it. It's unclear whether it's grid based or smooth. It's not clear where the snake length is represented.

True, there is no focus on a grid system. I guess it gets a bit more clear with more knowledge about the game framework, the Roles are referring to the pixel x/y of the playfield, and I let the Movement context handle the grid to view mapping, simply because that's what I have access to in the RoleObjectContract.

In Movement context, Keyboard is hardcoded. The role is "Controller", rather than Keyboard. Keyboard is the object playing the role.

Good point, it should definitely be Controller. Will change it.

Having segments logic as part of Movement context, seems weird. To me it looks like the "Movement context" is actually the "Snake".

Talked about this earlier in response to Rune.

For some reason Collisions context is also tracking and updating points. Similarly it contains FRUIT placement logic, text updating and other things. It seems very confused in what ideas it represents.

It follows the mental model as specified in the use case. The Game Over could be its own Context, and will probably in a larger game, but where to draw the line... 

Generally I use magic number 4+-1 as the ideal number of things interacting.

But it's more about "Collision context" not having a clear responsibility in what it does.


"Movement" system having own timer will probably be problematic eventually. I.e. imagine having to add bullet-time.

Not a feature on the roadmap. :) This is a simple example, no other featured are planned, which reflects the system of course. Designing the system for "infinite flexibility", where every whimsical management idea must quickly be realized, could quickly lead into overengineering, the bane of many projects.

I agree, it's not necessary for the implementation, but rather an example how having separate timer inside a system is problematic.

Just lessons learned from implementing games. Usually there's some top-level World that invokes the `Update` or `Tick` for all the systems in the game.


"Movement" system is also triggering the collision system (imagine what happens when  multiple snakes are on the board). Collision, Movement, Time are all usually orthogonal.

Movement and Collisions are closely related, I almost decided to join them together since there are interactions happening between them, like "when colliding with the fruit, the snake should grow on next move". This inter-Context communication is something I haven't seen much talk about here. I think it could imply an encompassing Context, but for now I just put it in a System Operation, to at least separate it from the interactions, so if multiple snakes is required, at least its easy to separate.


However, due to missing multi-dispatch / func maps, I had to resort to some ugliness.

Egon Elbre

unread,
Sep 24, 2018, 4:27:54 PM9/24/18
to object-composition


On Monday, 24 September 2018 20:44:48 UTC+3, Andreas Söderlund wrote:
Thanks for all your feedback Matt. I agree that the deviations are a little bit messy, I didn't want to write them on the bottom in a single sentence, because there will probably be Role interactions in a deviation too. But I wanted to try the format, in comparison with the other (Library borrowing machine). And you're right, the Keyboard being asked for its direction is an oversight, I will fix that.

I wanted to experiment with the use case level, putting it as how a game creator (an experienced player, but not a professional programmer) would describe the game. Then see if a programmer could "fill in the blanks" in the RoleMethods, doing some computations with help of the RoleObjectContract, fulfilling the use case that way. But the details, yes, looks a bit spread out, and I'm not happy with the Context state, which makes the Context almost like a mix of standard OOP and DCI. It's hard to avoid though, if you only want Roles as specified in the use case... Well, the search goes on, thanks for helping out!

I also did an experiment a while a ago, by letting my sister explain Space Invaders.


However, I didn't do an actual implementation.

Ritchie Paul Buitre

unread,
Sep 25, 2018, 8:15:16 AM9/25/18
to object-composition
Keyboard is too concrete, I suggest renaming the role to steering. With it you can have a demo mode where an A.I. plays the role instead of an actual keyboard.

Andreas Söderlund

unread,
Oct 28, 2018, 7:57:44 AM10/28/18
to object-composition
Thanks to the feedback from you guys, I'm finally back with a new version of SnakeDCI. 

One thing led to the other, so after thinking about Rune's immutable Data vs. Contexts idea, I decided to develop a library for immutable data, similar to Redux. It was a perfect timing for that, since the latest version of Haxe just got a "final" field in its arsenal. I'm calling it deepstate, and it turned out to be very useful.

Here is the complete game state:

typedef Coordinate = {
   
final x : Int;
   
final y : Int;
}

typedef State = {
   
final snake : {
       
final segments : ImmutableArray<Coordinate>;
       
final nextMoveTime : Float;
       
final currentDirection : Float;
       
final wantedDirection : Float;
   
};
   
final fruit : Coordinate;
   
final score : Int;
   
final hiScore : Int;
   
final playfield : {
       
final width : Int;
       
final height : Int;
       
final squareSize : Int;
   
}
   
final active : Bool;
}

Armed with this game state as immutable data, I set out trying to realize my vision that "There is no Snake" - it only lives in the players mind - translated to a transient Context, executing at the right moment.

I still had the game framework Phaser to deal with, so I took Egon's advice and used the game timer in a different way, factorizing it out of the Contexts. Then I really wanted to used our old friend MVC, so I created a GameView object, containing and reflecting the game state as the Model in that View object. But now, combining MVC and DCI, I made all the relevant objects in the view (fruit, playfield, snake, score and hiscore) into Roles, and it was a great fit.

The GameView object also contains the main game loop, and from there I started to rewrite the Contexts, now operating on the immutable data, bound as Roles during instantiation. This turned out to be a really nice way of doing things, since the data is final (similar to const in trygve) I even removed the warning when accessing a Role-object-contract field from outside its own Role. This could be the way to avoid accessors for Roles, as discussed previously.

In the latest commits, I'm making the Contexts transient, which came to my mind after adding a method as System Operation on every Context. "Movement.move", "Collisions.checkCollisions", etc. But since the data is now immutable, what could happen if you execute a Movement Context twice? Most likely, it will move the Snake based on the data bound that time. So you need either a check at a public method (System Operation) that it can only be called once, but that requires some additional state. So the way I'm doing it now is just starting the System Operation directly in the constructor. Of course some class-oriented people will go bananas about this, but that's why we're here, isn't it? ;) My aim is to protect the programmer from himself, since there is no way to call such an object. It will execute for a short period of time where the array of coordinates will be a Snake, execution will end, and Neo will disconnect from the Matrix.

As a sidenote, this only works for synchronous code. For async code, there will probably be a public method starting the Context, which could then return a Promise that manages how many times it has been called.

Enough theory now, I invite you to review the code and give some critique: https://github.com/ciscoheat/SnakeDCI/tree/master/src

The thing I'm least satisfied with is the way to change the state, which is done by calling the game state object in a RoleMethod, after that Role has gathered what is needed from the other Roles. Here's an example in the Movement Context. There could be a mental model mismatch here. I think the snake should move itself, but instead the game state will update itself. This makes me wonder, maybe there should be a Snake Context with methods for updating its state (since the game state object can play a role in that Context). What do you think?

Many of the state update methods operate on more than one object though, for example "fruitEaten", modifying the score, fruit and segments of the snake, so where to call this state update method? Should I introduce a "state" Role in all Contexts that wants to modify state? It's not part of the use cases, however, that's why it's currently a private var in the Contexts.

The question is whether the RoleMethods should be able to do anything inside themselves, making the name of the RoleMethods the only tangible link to the use case, or if they should be more restricted, for example only interacting with Roles.

role SNAKE {
 
var x : Int;
 
var y : Int;
 
function move(newPos);

 
public function moveTo(x, y) { // RoleMethod
   
...
    move
(pos);
 
}
}

compared to

role SNAKE {
 
var x : Int;
 
var y : Int;

 
public function moveTo(x, y) { // RoleMethod
   
...
    _gameState
.moveSnake(pos); // Context variable, not a Role
    STATE
.moveSnake(pos); // Alt. when using Roles
 
}
}


Matthew Browne

unread,
Oct 29, 2018, 5:29:35 PM10/29/18
to object-co...@googlegroups.com
Hi Andreas,
Very cool! This new approach to managing the state seems very promising.

A couple initial reactions...

On 10/28/18 7:57 AM, Andreas Söderlund wrote:

> Then I really wanted to used our old friend MVC, so I created a
> GameView object, containing and reflecting the game state as the Model
> in that View object. But now, combining MVC and DCI, I made all the
> relevant objects in the view (fruit, playfield, snake, score and
> hiscore) into Roles, and it was a great fit.
I think it might be a stretch to call GameView a "View" at this point,
given that it's the whole UI. Maybe just call it GameUI? I suppose it's
still mostly a View and you're still following the general idea of MVC,
but in a more traditional MVC app wouldn't there be multiple Views for
this game? Then again I don't have experience with game development so
maybe I'm mistaken here.

> So the way I'm doing it now is just starting the System Operation
> directly in the constructor. Of course some class-oriented people will
> go bananas about this, but that's why we're here, isn't it? ;) My aim
> is to protect the programmer from himself, since there is no way to
> call such an object. It will execute for a short period of time where
> the array of coordinates will be a Snake, execution will end, and Neo
> will disconnect from the Matrix.
This seems like a clear example of a case where the Context is really
just a function. And that's why in my JS implementation, top-level
functions can be Contexts (I should also credit Egon since I originally
got this idea from him). 'context' declarations (similar to using
'class' declarations for Contexts in other languages) are only needed
for stateful Context objects that you want to keep around after the
first time you call a method on them.

I'm not sure how difficult it would be to add that feature to your Haxe
implementation, and of course you can accomplish the same thing in the
end using classes, but I do think just having your contexts be functions
would be an elegant solution.

Andreas Söderlund

unread,
Oct 30, 2018, 7:42:29 PM10/30/18
to object-co...@googlegroups.com
I think it might be a stretch to call GameView a "View" at this point,
given that it's the whole UI. Maybe just call it GameUI? I suppose it's
still mostly a View and you're still following the general idea of MVC,
but in a more traditional MVC app wouldn't there be multiple Views for
this game? Then again I don't have experience with game development so
maybe I'm mistaken here.

According to MVC the GameView it's a View object, and it's no problem in general to let a single View be the whole view for the game. I elaborate a bit on that here: https://github.com/ciscoheat/haxedci-example#together-with-mvc (also, a general rule is that a given view canonically has a single Model.)

This seems like a clear example of a case where the Context is really
just a function. And that's why in my JS implementation, top-level
functions can be Contexts (I should also credit Egon since I originally
got this idea from him). 'context' declarations (similar to using
'class' declarations for Contexts in other languages) are only needed
for stateful Context objects that you want to keep around after the
first time you call a method on them.

Unfortunately Haxe has no top-level functional constructs, but it's a good idea.

Reply all
Reply to author
Forward
0 new messages