import { Exclude, Expose, Type, instanceToPlain, plainToClass, plainToInstance } from 'class-transformer'; function stringCompare(a: string, b: string) { return a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0; } export class BingoGame { id!: string; name!: string; short_description!: string; description!: string; generator: 'simple' | 'srl_v5' | 'srl_v8' = 'simple'; @Type(() => BingoGoal) goals: BingoGoal[] = []; @Type(() => BingoGroup) groups: BingoGroup[] = []; constructor(id: string, name: string, short_description: string, description: string) { this.id = id; this.name = name; this.short_description = short_description; this.description = description; } addGoal(goal: BingoGoal): void { if (this.goals.some(_goal => stringCompare(_goal.name, goal.name))) return; this.goals.push(goal); } removeGoal(goal: BingoGoal): void { this.goals = this.goals.filter(_goal => !stringCompare(_goal.name, goal.name)); } removeGoalByName(name: string): void { const goal = this.goals.find(goal => stringCompare(goal.name, name)); if (goal) this.removeGoal(goal); } addGroup(group: BingoGroup): void { // TODO: Right now we can do circular groups. // 1.parent = 2, 2.parent = 1 // This will loop the objects infinitely. // Make sure that going through the tree we don't have this problem. // // Option B: // Trash idea of groups-in-groups (parent system) // Just have groups and be done with them. // // Option C: // Bring back categories so we have categories + groups. // _____________________________________________________ if (this.groups.some(_group => stringCompare(_group.name, group.name))) return; this.groups.push(group); } removeGroup(group: BingoGroup): boolean { // Group has parent, don't delete const isParent = this.groups.find( _group => _group.parent && stringCompare(_group.parent.name, group.name) ); if (isParent) { console.error('This group is still a parent of:', isParent.name); return false; } // One or more goals still use this group, don't delete const isGoal = this.goals.find( goal => goal.group && stringCompare(goal.group.name, group.name) ); if (isGoal) { console.error('This group is still used by:', isGoal.name); return false; } this.groups = this.groups.filter(_group => !stringCompare(_group.name, group.name)); return true; } removeGroupByName(name: string): boolean { const group = this.groups.find(group => stringCompare(group.name, name)); return group ? this.removeGroup(group) : false; } getGroupByName(name: string): BingoGroup | undefined { return this.groups.find(group => stringCompare(group.name, name)); } getGoalsByTags(...tags: string[]): BingoGoal[] { return this.goals.filter( goal => goal.tags.some( _tag => tags.some( tag => stringCompare(_tag, tag) ) ) ); } getGoalsByGroup(includeParents: boolean, ...groups: BingoGroup[]): BingoGoal[] { const result: BingoGoal[] = []; this.goals.map(goal => { const goal_group = goal.group; if (includeParents) { let parent = goal_group.parent; while (parent) { if (groups.includes(parent)) { result.push(goal); return; } parent = parent.parent; } } if (groups.includes(goal_group)) { result.push(goal); } }); return result; } } function getRandomInt(max: number): number { return Math.floor(Math.random() * max); } export class BingoGoal { // id: string; // uuid-by-string maybe? https://www.npmjs.com/package/uuid-by-string name!: string; @Exclude({ toPlainOnly: true }) @Type(() => BingoGroup) group!: BingoGroup; tags: string[] = []; possible_spaces: number[] = []; constructor(name: string, group: BingoGroup) { this.name = name; this.group = group; for (let i = 0; i < 25; i++) { if (getRandomInt(2) == 0) continue; this.possible_spaces.push(i); } } @Expose() get group_id(): string { return this.group.name; } addTag(tag: string): BingoGoal { if (this.tags.includes(tag)) return this; this.tags.push(tag); return this; } removeTag(tag: string): BingoGoal { this.tags = this.tags.filter(_tag => !stringCompare(_tag, tag)); return this; } setGroup(group: BingoGroup) { this.group = group; } setPossibleSpaces(possible_spaces: number[]) { this.possible_spaces = possible_spaces; } } export class BingoGroup { name: string; // @Exclude({ // toPlainOnly: true // }) // @Type(() => BingoGroup) #parent?: BingoGroup; constructor(name: string, parent?: BingoGroup | undefined) { this.name = name; this.#parent = parent; } @Exclude() get parent(): BingoGroup | undefined { return this.#parent; } @Expose() get parent_id(): string | undefined { return this.#parent?.name; } } // eslint-disable-next-line @typescript-eslint/no-unused-vars function test(): void { const game = new BingoGame( 'yakuza_0', 'Yakuza 0', 'The funny game we used for all bingos.', 'Very long text here haha lmao' ); const group1 = new BingoGroup('1'); const group2 = new BingoGroup('2', group1); // group1.parent = group2; // group2.parent = group1; game.addGroup(group1); game.addGroup(group2); const jsonifiedInstance = instanceToPlain(game, { enableCircularCheck: true }); const fixed = JSON.parse(JSON.stringify(jsonifiedInstance)); console.log(JSON.stringify([ fixed ])); // const group1 = new BingoGroup('1'); // const group2 = new BingoGroup('2', group1); // const group3 = new BingoGroup('3', group2); // game.addGroup(group1); // game.addGroup(group2); // game.addGroup(group3); // const battle_group = new BingoGroup('Battle'); // const defeat_enemies = new BingoGroup('Defeat enemies on the street', battle_group); // game.addGroup(battle_group); // game.addGroup(defeat_enemies); // const easy = new BingoGoal('Defeat 50 enemies on the street', group1).addTag('Easy'); // const normal = new BingoGoal('Defeat 150 enemies on the street', group2).addTag('Normal'); // const hard = new BingoGoal('Defeat 300 enemies on the street', group3).addTag('Hard'); // game.addGoal(easy); // game.addGoal(normal); // game.addGoal(hard); // console.log(game.goals[0].group); // game.groups[0].name = 'not 1'; // game.groups[0].parent = game.groups[1]; // console.log(game.goals[0].group); // const jsonifiedInstance = instanceToPlain(game); // const fixed = JSON.parse(JSON.stringify(jsonifiedInstance)); // console.log(JSON.stringify([ fixed ])); // console.log(JSON.stringify(game)); // const got_group1 = game.getGroupByName('1'); // console.log(got_group1); // const got_group3 = game.getGroupByName('3'); // console.log(got_group3); // console.log(got_group1 === got_group3?.parent?.parent); // const jsonifiedString = JSON.stringify(jsonifiedInstance); // const parsedInstance: JSON = JSON.parse(jsonifiedString); // const parsedgame = plainToClass(BingoGame, parsedInstance); // console.log(parsedgame); // console.log(parsedgame === game); // const got_group1 = parsedgame.getGroupByName('1'); // console.log(got_group1); // const got_group3 = parsedgame.getGroupByName('3'); // console.log(got_group3); // console.log(got_group1 === got_group3?.parent?.parent); // const json_string = '{"id":"yakuza_0","name":"Yakuza 0","description":"The funny game we used for all bingos.","generator":"simple","goals":[{"name":"Defeat 50 enemies on the street","tags":["Easy"],"group":{"name":"Defeat enemies on the street","parent":{"name":"Battle"}},"possible_spaces":[]},{"name":"Defeat 150 enemies on the street","tags":["Normal"],"group":{"name":"Defeat enemies on the street","parent":{"name":"Battle"}},"possible_spaces":[]},{"name":"Defeat 300 enemies on the street","tags":["Hard"],"group":{"name":"Defeat enemies on the street","parent":{"name":"Battle"}},"possible_spaces":[]}],"groups":[{"name":"Battle"},{"name":"Defeat enemies on the street","parent":{"name":"Battle"}}]}'; // const parsed: JSON = JSON.parse(json_string); // const parsed_game = plainToInstance(BingoGame, parsed); // console.log(parsed_game); // console.log(JSON.stringify(other_game) === JSON.stringify(jsonifiedInstance)); // parsed_game.removeGroup(defeat_enemies); // console.log(parsed_game.goals); } test();