So, I'm building this Mafia/Werewolf kind of game, I created this registry of the available roles, and then I extracted the type RoleId to be exactly a union of the ids that are present in the object
export const ROLE_REGISTRY = {
[VillagerRole.id]: {
roleClass: VillagerRole,
name: 'Villager',
iconName: 'villager',
team: VillagerRole.team.name,
category: 'base',
tags: ['idle'],
description:
"You have no special power. At day you may discuss with the other villagers to vote out who you think it's the werewolf.",
max: Infinity,
},
...
} as const satisfies Record<string, RoleMetadata>
export type RoleId = keyof typeof ROLE_REGISTRY;
Now yes, defining the ids directly in the classes and then using them like that to index the registry was definitely a questionable choice, but besides that, why am I able then to use that exact definition of RoleId in the Role class itself? (which is the parent class for each of the roles)
export abstract class Role {
static id: RoleId;
static team: Team;
abstract seenAsGood: boolean;
canBeKilledByWolves = true;
get id(): RoleId {
return (this.constructor as typeof Role).id;
}
...
}
Typescript sees no problem with that, no circular dependency, which I found weird. So I tried do this once more, this time with the actions that the roles can perform
export const ACTION_REGISTRY = {
[AttackAction.id]: { actionClass: AttackAction },
[CheckAction.id]: { actionClass: CheckAction },
...
} as const satisfies Record<string, ActionMetadata>;
export type ActionId = keyof typeof ACTION_REGISTRY;
export interface Action {
readonly id: ActionId;
...
}
export abstract class InstantAction<T extends ActionResult> implements Action {
static id: ActionId;
get id() {
return (this.constructor as typeof InstantAction).id;
}
...
}
export abstract class ScheduledAction implements Action {
static id: ActionId;
get id() {
return (this.constructor as typeof ScheduledAction).id;
}
...
}
Here TypeScript tells me (as expected) that ActionId is circularly referencing itself. I really don't understand the difference between these two cases, and/or if there is something else entirely that I'm missing that it's the root cause of the problem. I will paste this other piece of code which MAY be relevant.
export abstract class ActiveRole extends Role {
abstract actions: ActionGroup[];
canCreateAction(actionId: ActionId): boolean {
const action = this.findAction(actionId);
return action.amount > 0;
}
protected consumeAction(actionId: ActionId) {
const action = this.findAction(actionId);
action.amount--;
}
private findAction(actionId: ActionId) {
const actionGroup = this.actions.find(
(actionGroup) => actionGroup.options[actionId] !== undefined
);
if (!actionGroup) {
throw new Error('Impossible to find action ' + actionId);
}
return actionGroup.options[actionId];
}
createAction(
state: GameState,
actionId: ActionId,
source: Player,
targets: Player[],
payload?: any
): Action {
const action = this.findAction(actionId);
this.consumeAction(actionId);
return action.create(state, source, targets, payload);
}
}
Thanks in advance