TLDR
Had several hours to work today. Made significant changes to both the frontend and backend. Frontend - added the ability to place roads on the board. This required cleaning up API call logic and reusing / refactoring the existing place settlement code. Backend - reworked the data structures and logic for Finite State Machine now that it is relatively stable. This involved a lot of boilerplate Java code changes.
Builder Pattern
// internal class implementing the steps
private static class BuilderSteps implements StepStartState, StepAction, StepGlide {
private final Builder builder = new Builder();
@Override
public StepAction setStartState(StateEnum startState) {
builder.startStateEnum = startState;
return this;
}
@Override
public StepGlide setAction(IAction action) {
builder.action = action;
return this;
}
@Override
public StepGlide addGlide(IGlide glide) {
builder.glides.add(glide);
return this;
}
// TODO this should have a test that checks for a thrown exception
@SneakyThrows
@Override
public Transition build() {
// throw exception if any glides don't have correct start state
for(IGlide glide : builder.glides) {
if (glide.getStartStateEnum() != builder.startStateEnum) {
throw new Exception("Invalid glide definition in Transition. Starting StateEnum must match parent StateEnum.");
}
}
return new Transition(
builder.action,
builder.glides
);
}
}
Observable Reuse
// common logic for handling player action success / failure
private handlePlayerAction(playerAction: PlayerAction): {
next: (value: GameState) => void;
error?: (error: any) => void;
complete?: () => void;
} {
return {
next: (gameState: GameState) => {
console.log(`Performed ${playerAction} action.`);
this.actionState.clearAll();
this.gameState = gameState;
},
error: (e: any) => {
// Handle errors, such as logging or notifying the user
console.error(
`Could not perform ${playerAction} action: ${JSON.stringify(e)}`,
);
this.actionState.clearAll();
},
};
}
Main work
- Reworking how
Observablesare passed around the frontend when performing player actions. - Pulling spaghetti code FSM definition into individual data structures.
- Creating
Builderpattern logic for FSM data structures. - Regression testing all of these changes via the testing scripts previously developed.
- Manually trigger the scripts then visually validate board state in UI.
Challenges
- Refactoring Java code boilerplate is a bear. The language is just so verbose.
- As always, balancing getting results vs risking long-term design decisions that could save time later or be completely scrapped.
Learnings
- If you use ChatGPT in very pointed ways, you can get huge benefits. For example…
- When breaking anonymous functions out into named functions with a type signature, just ask ChatGPT to resolve what the parameter and result types should be.
- When creating builder pattern for a specific data structure, provide the existing data structure and the order of the fields you want to built.
- I’m very happy I have not yet introduced any persistance / DB. The data models are changing as I need. Once things seem “stable”, I will break out the entire
GameStateobject into a relational schema. Builderpattern can let you collect MORE parameters than necessary, then you can run validation logic during.build()and later discard the additional parameters the object does not need to maintain.