Skip to content
Settlers Journal
Go back

Printing the FSM Definition at Startup and Discovering a Design Code Smell

Edit page

TLDR

(1) Printing full definition of FSM to console on application startup. (2) Working through additional test coverage of Action and Glide classes. A code smell (Glides having to duplicate information that could be found in their parent Action) motivates a refactoring of the FSM implementation.

alt text

Defining the transitions of a State.

public class StatePlaceFreeSettlement implements StateBase {

  private static final StateEnum stateEnum = StateEnum.PLACE_FREE_SETTLEMENT;

  private static final List<Transition> transitions = new ArrayList<>(List.of(
      Transition.Builder.builder()
          .setStartState(stateEnum)
          .setAction(new PlaceSettlementAction())
          .addGlide(new PlaceFreeSettlementToPlaceFreeRoadGlide())
          .build()
  ));

  .
  .
  .
}

Attempting to write a test for a particular Glide.

// NOTE: half-written test that pointed out weird design / code smell
@Test
public void givenValidCatanGame_whenValidateGlide_thenReturnTrue() {
  catanGame.setStateEnum(StateEnum.ROLL_FOR_ORDER);
  addPlayersWithPartialRolls(catanGame, 3, 2);

  // NOTE: Issue is here - we always know a Glide's concrete action.
  // NOTE: We shouldn't need to provide a mock.
  IAction mockAction = createMockAction();

  // TODO assert that game state is bumped to end state
  assertThat(glide.validateGlide(catanGame, mockAction)).isTrue();
}

Issue is that Glide implements a method that takes the IAction interface as a parameter. We know that any particular Glide will always be passed a specific action (PlaceSettlementAction in this case). Reorganizing the FSM avoids boilerplate initialization of Glides and reduces the lift of writing tests and providing mocked values for IAction objects we always know the concrete implementation of.

This redesign also reduces the chance of errors, since we no longer would need to ensure the same IAction logic was being passed to both the parent Transition and its related IGlides.

@Override
public CatanGame performGlide(CatanGame catanGame, IAction action, Map<String, String> parameters)

Proposed solution is to remove StateBase class. FSM will be defined entirely by a list of Transitions, each of which have a starting StateEnum, ending StateEnum, and ActionEnum. If ActionEnum is something like ActionEnum.FREE, the Transition can happen automatically (e.g., player could not have made any moves). Concrete Transitions will implement an interface that allows for checking if they are relevant and possible to execute.

Main work

Challenges

Learnings


Edit page
Share this post on:

Previous Post
Simplifying the FSM by Removing Glides and Adopting ActionEnum.NOOP
Next Post
Improving CatanGraph Test Coverage and Catching a HashMap Overwrite Bug