I am building a slot machine game for a client (virtual money, not real-money gambling). They started out with a template game and hired me to reskin it and make "minor" changes for a "quick" release. Unfortunately, there are no brakes on the scope train.
The original template was very basic. Count the number of consecutive icons starting from the left, and check the value in the pay table. For example, if the player spun "7 7 Chery Cherry 7", that would count as two 7s, so we'd check the pay table for the value (if any) of getting two 7s. There were also some special cases in the evaluation code to handle special icons like Wild, Bonus, and Scatter.
Icon originally had three properties:
string name Image image IconType type (Normal, Wild, Bonus, or Scatter)
The pay table is a simple table with three columns
string name, int matches, int value
ProcessResult class handles the results for all machines; it determines what combination came up, and (based on the icon type) either looks up the value in the pay table, or applies a special effect if the combination consists of bonus or scatter icons.
However, each time they send me specifications for a new machine, the client adds new features that aren't compatible with the existing code. Now we have icons that don't need to be consecutive, icons that don't need to start on the left, icons that can combine with other types of icons, icons that modify the behavior of other icons, icons that modify the general behavior of the machine, etc etc.
For a while I was adding new properties to the Icon class (e.g.
consecutiveOnly), but now it has a bunch of properties that don't apply to most icons, and the ProcessResult class is full of messy edge cases to handle all these properties. It's getting to be unmaintainable, and I think it's time for a refactor, but I'm not sure how to approach it.
I've identified several special cases that are particularly problematic:
- Icons that don't need to be consecutive or start from the left greatly increase the complexity of determining which icon is considered the "result" for the spin
- Icons that can match with other icons besides themselves are difficult to represent in the current pay table. For example, imagine a 3-reel slot where three cherries pays 5x, three bananas pays 3x, but a combination of cherries and bananas pays out 4x. My current solution is to give them a secondary name with a separate entry in the pay table (so e.g. the "cherry" icon counts as "cherry" or "fruit", and the banana icon counts as "banana" or "fruit", with "cherry" "banana" and "fruit" all listed separately in the pay table).
- Some special icons usually have no inherent pay value (e.g. "Bonus", which triggers some type of minigame or alternate machine state) and thus we can't look up how many matches are required in the pay table. Right now the machine has generic properties such as "minBonusMatches", but this means that some machines have properties for icon types they don't actually use, and wouldn't work if the client introduced a second type of a special icon to some machine (e.g. "match 3 of the Bonus icons for a regular bonus or 5 of the Super Bonus icons for a super bonus")
I am not sure how to design the architecture in a way that will easily accommodate any crazy new rule that the client thinks up. I definitely don't want to keep building on the current tangle of nested
if-elses that handles all the edge cases. I'm pretty sure I don't want to write a separate "ProcessResult" class for each machine, because I think that will be confusing and is likely to encourage code duplication. I've considered extending the Icon class with child classes that define their own logic, but I think that will get messy, and it feels like loading responsibility into the wrong place (the icons would then need to know all about the game state, which seems backwards).
Is there a particular pattern or architecture that is well-suited here? The best I have right now is a vague notion of using something like the component pattern to assign properties to icons, and feeding these through a chain-of-responsibility, but I'm having trouble thinking of a concrete implementation - like the old saying goes, can't see the forest for the edge cases.
The client ignores all advice, so any answer that involves giving advice to the client about scope creep or project management is pointless