leonitousconforti/tinyburg

View on GitHub
packages/bitprints/src/strategies/rebuild-every-x-floors.ts

Summary

Maintainability
A
1 hr
Test Coverage
/* eslint-disable @typescript-eslint/naming-convention */

import { buildFloorCost } from "@tinyburg/nucleus/data/floors";
import { FiniteStateMachine } from "../fsm.js";

/**
 * The states for a basic TinyTower strategy where we rebuild immediately once
 * we hit the desired number of floors. During our game-play loop, we will wait
 * for enough coins to build the next floor. Once we have enough coins, we will
 * transition to building the next floor. At the end of building the next floor,
 * we will attempt to transition to rebuilding which can fail if we do not have
 * the desired number of floors built yet. After attempting to transition to
 * rebuilding, we will transition back to waiting for enough coins to build the
 * next floor.
 */
export const enum TinyTowerRebuildAsapStrategyStates {
    WaitingForCoins = "WaitingForCoins",
    BuildingNewFloor = "BuildingNewFloor",
    Rebuilding = "Rebuilding",
}

/**
 * The event data we will need to know when transitioning from one state to
 * another, this will be provided by tinyburg doorman at runtime whenever an
 * event is emitted.
 */
export interface ITinyTowerRebuildEventData {
    coins: number;
    floors: number;
}

export const useStrategy = (): FiniteStateMachine<TinyTowerRebuildAsapStrategyStates, ITinyTowerRebuildEventData> => {
    /**
     * Rebuild after 50 floors so we get a golden ticket every time. There is no
     * reason that this can't be a function that takes in the above event data
     * and makes a decision using more data, but for this example this will do.
     */
    const rebuildAtFloor: number = 50 as const;

    // Construct a new FSM, starting in the waiting for coins state
    const fsm = new FiniteStateMachine<TinyTowerRebuildAsapStrategyStates, ITinyTowerRebuildEventData>(
        TinyTowerRebuildAsapStrategyStates.WaitingForCoins
    );

    // Defines the waiting for coins to building the next floor transition
    fsm.from(TinyTowerRebuildAsapStrategyStates.WaitingForCoins).to(
        TinyTowerRebuildAsapStrategyStates.BuildingNewFloor
    );

    // Defines the build next floor to rebuilding or back to waiting for coins transitions
    fsm.from(TinyTowerRebuildAsapStrategyStates.BuildingNewFloor).to(TinyTowerRebuildAsapStrategyStates.Rebuilding);
    fsm.from(TinyTowerRebuildAsapStrategyStates.BuildingNewFloor).to(
        TinyTowerRebuildAsapStrategyStates.WaitingForCoins
    );

    // Defines the rebuilding to waiting for coins transition
    fsm.from(TinyTowerRebuildAsapStrategyStates.Rebuilding).to(TinyTowerRebuildAsapStrategyStates.WaitingForCoins);

    // Listen for transitions to building the next floor. If the callback returns
    // false, because we don't have enough coins yet, then the transition is canceled
    fsm.onEnter(
        TinyTowerRebuildAsapStrategyStates.BuildingNewFloor,
        (_from, event) => event.coins >= buildFloorCost(event.floors + 1)
    );

    // After all other building new floor listeners have ran, we can attempt to
    // transition to rebuilding.
    fsm.on(
        TinyTowerRebuildAsapStrategyStates.BuildingNewFloor,
        async (_from, event) => {
            const transitionedToRebuilding = await fsm.go(TinyTowerRebuildAsapStrategyStates.Rebuilding, event);
            if (!transitionedToRebuilding) await fsm.go(TinyTowerRebuildAsapStrategyStates.WaitingForCoins, event);
        },
        "Later"
    );

    // Listen for transitions to rebuilding. If the callback returns false, because
    // we haven't built the desired number of floors yet, then transition is canceled
    fsm.onEnter(TinyTowerRebuildAsapStrategyStates.Rebuilding, (_from, event) => event.floors >= rebuildAtFloor);

    // After all other rebuilding listeners have ran, we can transition back to
    // waiting for coins
    fsm.on(
        TinyTowerRebuildAsapStrategyStates.Rebuilding,
        async (_from, event) => {
            await fsm.go(TinyTowerRebuildAsapStrategyStates.WaitingForCoins, { ...event, floors: 1 });
        },
        "Later"
    );

    return fsm;
};