Fork me on GitHub
#architecture
<
2022-08-12
>
Noah Bogart20:08:28

Code layout/namespace/architecture question: I maintain a collectible card game written in clojure, and I've done an okay job of splitting the core functionality into various namespaces and keeping things relatively separated. However, my predecessors left large parts of the rules handling to the players, so as I'm fleshing out those parts of the rules, I'm finding that I have to move more and more of the "engine" logic from these discrete namespaces into a single namespace because they all end up touching each other: where I used to have game.core.ability and game.core.moving and game.core.checkpoints, I now have a 2k line file that holds all of the "resolve an ability` logic and the "move cards" logic and the "process checkpoints" logic, because they nest and interact with each other in a tightly coupled fashion (as one might expect from a complex board/card game). This is due to one of the primary rules parts of the game is the "checkpoint", where abilities are triggered, reactions are set up, illegal game states are resolved, etc. Before my work, that was left to be handled by hand by the players, but as I'm working on enforcing it, I find myself creating a big kitchen sink namespace to account for all of the parts of the rules that the "checkpoint" touches. Does anyone have good recommendations or advice about how to handle complex interlocking functions like this?

hiredman21:08:45

I would consider just taking advantage of the fact that clojure requires definitions before usages and just split game.core.checkpoints into game.core.checkpoints-top game.core.checkpoints-bottom

hiredman21:08:03

you also might try and remove direct language level dependencies between things by moving to something that is more about data dependencies. where each bit of logic doesn't directly call other logic, but instead returns the data passed to it enriched with more data that subsequent logic might use down the road, and you have some overarching "scheduler" that determines how and when to run each bit of logic (not unlike something like a rules engine)

👍 1
Max01:08:57

Seconding hiredman’s suggestion to decouple the direct calls. Are there any patterns in how abilities interact that you could factor out into general dispatchers or multimethods or something? I don’t know your game obviously, but for example with MTG I could imagine cards registering themselves with “when x” dispatchers such that when an ability triggers one you just say “x occurred” and the dispatcher handles calling all the subscribers

👍 1
Noah Bogart15:08:24

Thanks for the ideas! I’ve thought of similar things but never so concretely. If you have any articles or books or code examples, I’d love to see them to get a better idea of how to do it

Max15:08:25

I don’t know any examples off the top of my head, but when you get right down to it, it’s just a collection of fns, a way to add fns to the collection, and a way to run all the fns in the collection (often with some arguments)