I am attempting to create a 'movable entity' that returns a new state whenever it is 'moved'. I am running into two errors I am unsure how to solve. The first is when I create a Movable from an Entity, I need to populate the Entity with a 'move' object. This object relies on the fact that the parent object will now have a 'move' object as a child. You can see the specific error at 1)
(this still runs, I'd just rather have it be typed correctly).
For 2)
, I cannot call entity.move.nextState()
and am unsure why. This is the main blocker. Any ideas?
Here is the code in a Typescript playground
(Skip down to testMove
to get an idea of what the function is doing)
type Nullable<T> = T | null
interface IEntityState {
id: string
}
interface IPosition {
x: number, y: number, z: number
}
interface IComponent extends IEntityState { }
interface Actionable {
nextState: (...args: any) => IEntityState
}
interface Undoable {
initialState: Nullable<any>
undo: () => IEntityState | Error // TODO Make an Either
commit: () => IEntityState
}
/** Movable */
interface IMoveAction extends Actionable, Undoable {
initialState: Nullable<any>
nextState: (position: IPosition) => IMovable
undo: () => IMovable | Error
commit: () => IMovable
}
interface IMovable extends IComponent {
position: IPosition
move: IMoveAction
}
const MoveAction = <O extends IMovable>(object: O): IMoveAction =>
({
initialState: null,
// Return with an initial move state if it does not exist already
nextState: (position: IPosition): IMovable =>
initialState
? { ...object, position }
: { ...object, position, move: { ...object.move, initialState: position } }
,
undo: () =>
initialState
? { ...object, position: initialState, move: { ...object.move, initialState: null } }
: new Error('Cannot undo a move with no initialState')
,
commit: () =>
({ ...object, position: initialState, move: { ...object.move, initialState: null } })
,
})
type MovableInput = IEntityState & { position: IPosition }
const Movable = <O extends MovableInput>(object: O): O & IMovable =>
({
...object,
move: MoveAction(object), // 1) Argument of type 'O' is not assignable to parameter of type 'IMovable'. Property 'move' is missing in type 'MovableInput' but required in type 'IMovable'
})
function testMove() {
console.log('Running move component tests')
const id = 'test'
const initialPosition: IPosition = { x: 3, y: 2, z: 3 }
const newPosition: IPosition = { x: 3, y: 2, z: 0 }
const entity: MovableInput = { id, position: initialPosition }
const initialState = Movable(entity)
// 2) Throws error 'Cannot access 'initialState' before initialization'
const nextState = initialState.move.nextState(newPosition)
const undoneState = nextState.move.undo() as IMovable
// Initial state is preserved
console.assert(initialPosition === nextState.move.initialState)
// State transitions correctly
console.assert(initialState.position !== nextState.position)
// We can undo actions
console.assert(nextState.position !== undoneState.position)
console.assert(initialState.position === undoneState.position)
// We cannot undo commited changes
const committedState = nextState.move.commit()
const error = committedState.move.undo() as Error
console.assert(error.message === 'Cannot undo a move with no initialState')
} testMove()