From e0b05b3aa6c2ddd2248cc936534cc18fd1c0a1f1 Mon Sep 17 00:00:00 2001 From: Lordmau5 Date: Wed, 15 Nov 2023 12:13:01 +0100 Subject: [PATCH] More changes --- src/js/Bingo.ts | 324 ---------------------------------- src/js/ParseGamesJSON.ts | 28 +-- src/js/lib/BingoGame.ts | 167 ++++++++++++++++++ src/js/lib/BingoGoal.ts | 61 +++++++ src/js/lib/BingoGroup.ts | 47 +++++ src/js/lib/SuccessResponse.ts | 21 +++ src/js/lib/Util.ts | 13 ++ tsconfig.app.json | 3 +- 8 files changed, 329 insertions(+), 335 deletions(-) delete mode 100644 src/js/Bingo.ts create mode 100644 src/js/lib/BingoGame.ts create mode 100644 src/js/lib/BingoGoal.ts create mode 100644 src/js/lib/BingoGroup.ts create mode 100644 src/js/lib/SuccessResponse.ts create mode 100644 src/js/lib/Util.ts diff --git a/src/js/Bingo.ts b/src/js/Bingo.ts deleted file mode 100644 index 1224337..0000000 --- a/src/js/Bingo.ts +++ /dev/null @@ -1,324 +0,0 @@ -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(); diff --git a/src/js/ParseGamesJSON.ts b/src/js/ParseGamesJSON.ts index 9390f9e..ed2d814 100644 --- a/src/js/ParseGamesJSON.ts +++ b/src/js/ParseGamesJSON.ts @@ -1,8 +1,5 @@ // Example code to convert the old format to the new one -import { - BingoGame, BingoGoal, BingoGroup -} from '@/js/Bingo.js'; import json from './games.json'; import new_json from './new_games.json'; import { @@ -10,6 +7,9 @@ import { } from 'class-transformer'; import markdown_text from '@/js/testmarkdown.js'; +import BingoGame from '@/js/lib/BingoGame.ts'; +import BingoGroup from '@/js/lib/BingoGroup.ts'; +import BingoGoal from '@/js/lib/BingoGoal.ts'; export const games: Map = new Map; @@ -68,19 +68,27 @@ function run_new_format() { ); const group_map: Map = new Map; + const group_to_group_map: Map = new Map; for (const j_group of j_game.groups) { - let group: BingoGroup; + const group = new BingoGroup(j_group.name); if (j_group.parent_id) - group = new BingoGroup(j_group.name, group_map.get( - j_group.parent_id - )); - else - group = new BingoGroup(j_group.name); + group_to_group_map.set(group, j_group.parent_id); group_map.set(j_group.name, group); game.addGroup(group); } + for (const [ + group, + parent_id + ] of group_to_group_map) { + const parent = group_map.get(parent_id); + if (!parent) + continue; + + group.parent = parent; + } + for (const j_goal of j_game.goals) { const group = group_map.get(j_goal.group_id); if (!group) @@ -97,4 +105,4 @@ function run_new_format() { } } -// run_new_format(); +run_new_format(); diff --git a/src/js/lib/BingoGame.ts b/src/js/lib/BingoGame.ts new file mode 100644 index 0000000..8750d4d --- /dev/null +++ b/src/js/lib/BingoGame.ts @@ -0,0 +1,167 @@ + +import BingoGoal from '@/js/lib/BingoGoal.ts'; +import BingoGroup from '@/js/lib/BingoGroup.ts'; +import type SuccessResponse from '@/js/lib/SuccessResponse.ts'; +import Util from '@/js/lib/Util.ts'; +import { + Type, + instanceToPlain +} from 'class-transformer'; + +export default 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): SuccessResponse { + const existingGoal = this.goals.find(g => g === goal); + if (existingGoal) { + return Util.returnSuccess(false, 'A goal with this name already exists'); + } + else { + this.goals.push(goal); + + return Util.returnSuccess(true); + } + } + + removeGoal(goal: BingoGoal): SuccessResponse { + const filteredGoals = this.goals.filter(g => g !== goal); + if (filteredGoals.length === this.goals.length) { + return Util.returnSuccess(false, 'Goal not found'); + } + else { + this.goals = filteredGoals; + + return Util.returnSuccess(true); + } + } + + removeGoalByName(name: string): SuccessResponse { + const goal = this.goals.find(g => g.name === name); + if (!goal) { + return Util.returnSuccess(false, 'Goal not found'); + } + + return this.removeGoal(goal); + } + + addGroup(group: BingoGroup): SuccessResponse { + const existingGroup = this.groups.find(g => g === group); + if (existingGroup) { + return Util.returnSuccess(false, 'A group with this name already exists'); + } + else { + this.groups.push(group); + + return Util.returnSuccess(true); + } + } + + removeGroup(group: BingoGroup): SuccessResponse { + const filteredGroups = this.groups.filter(g => g !== group); + if (filteredGroups.length === this.groups.length) { + return Util.returnSuccess(false, 'Group not found'); + } + else { + // Check if any group has the given group as its parent + const hasParent = this.groups.some(g => g.parent === group); + if (hasParent) { + // Add the group back to the list of groups + this.groups.push(group); + + return Util.returnSuccess(false, 'Cannot delete group as it is a parent of another group'); + } + else { + this.groups = filteredGroups; + + return Util.returnSuccess(true); + } + } + } + + removeGroupByName(name: string): SuccessResponse { + const group = this.groups.find(g => g.name === name); + if (!group) { + return Util.returnSuccess(false, 'Group not found'); + } + + return this.removeGroup(group); + } + + getGoalsByTags(...tags: string[]): BingoGoal[] { + return this.goals.filter(goal => + tags.some( + tag => goal.tags.some( + goalTag => Util.stringCompare(goalTag, 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; + } + + toJSON(): Record { + this.groups.sort((a, b) => { + if (!a.parent && b.parent) { + return -1; + } + + if (a.parent && !b.parent) { + return 1; + } + + return 0; + }); + + return instanceToPlain(this); + } +} diff --git a/src/js/lib/BingoGoal.ts b/src/js/lib/BingoGoal.ts new file mode 100644 index 0000000..2860c94 --- /dev/null +++ b/src/js/lib/BingoGoal.ts @@ -0,0 +1,61 @@ + +import type SuccessResponse from '@/js/lib/SuccessResponse.js'; +import BingoGroup from './BingoGroup'; +import Util from './Util'; +import { + Exclude, Type, Expose +} from 'class-transformer'; + +export default class BingoGoal { + 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; + this.tags = []; + this.possible_spaces = []; + } + + @Expose() + get group_id(): string { + return this.group.name; + } + + addTag(tag: string): SuccessResponse { + if (this.tags.some( + existingTag => Util.stringCompare(existingTag, tag) + )) { + return Util.returnSuccess(false, 'Tag already exists'); + } + else { + this.tags.push(tag); + + return Util.returnSuccess(true); + } + } + + removeTag(tag: string): SuccessResponse { + if (!this.tags.some( + existingTag => Util.stringCompare(existingTag, tag) + )) { + return Util.returnSuccess(false, 'Tag doesn\'t exist'); + } + else { + this.tags = this.tags.filter( + existingTag => !Util.stringCompare(existingTag, tag) + ); + + return Util.returnSuccess(true); + } + } +} diff --git a/src/js/lib/BingoGroup.ts b/src/js/lib/BingoGroup.ts new file mode 100644 index 0000000..e6320a9 --- /dev/null +++ b/src/js/lib/BingoGroup.ts @@ -0,0 +1,47 @@ +import { + Expose, Exclude +} from 'class-transformer'; + +export default class BingoGroup { + name: string; + + #parent?: BingoGroup; + + constructor(name: string, parent?: BingoGroup) { + this.name = name; + this.parent = parent; + } + + @Exclude() + get parent(): BingoGroup | undefined { + return this.#parent; + } + + set parent(newParent: BingoGroup | undefined) { + if (newParent === this) { + throw new Error('A BingoGroup cannot be a parent of itself.'); + } + + // if (!newParent) { + // this.#parent = undefined; + + // return; + // } + + let temp = newParent; + while (temp) { + if (temp === this) { + throw new Error('Circular reference detected.'); + } + temp = temp.parent; + } + + this.#parent = newParent; + } + + @Expose() + get parent_id(): string | undefined { + return this.#parent?.name; + } +} + diff --git a/src/js/lib/SuccessResponse.ts b/src/js/lib/SuccessResponse.ts new file mode 100644 index 0000000..017e1a0 --- /dev/null +++ b/src/js/lib/SuccessResponse.ts @@ -0,0 +1,21 @@ +export default class SuccessResponse { + #success: boolean; + + #message: string | undefined; + + constructor( + success: boolean, + message?: string + ) { + this.#success = success; + this.#message = message; + } + + get success(): boolean { + return this.#success; + } + + get message(): string | undefined { + return this.#message; + } +} diff --git a/src/js/lib/Util.ts b/src/js/lib/Util.ts new file mode 100644 index 0000000..5d5a400 --- /dev/null +++ b/src/js/lib/Util.ts @@ -0,0 +1,13 @@ +import SuccessResponse from './SuccessResponse'; + +export default class Util { + static stringCompare(str1: string, str2: string): boolean { + return str1.localeCompare(str2, undefined, { + sensitivity: 'accent' + }) === 0; + } + + static returnSuccess(success: boolean, message?: string): SuccessResponse { + return new SuccessResponse(success, message); + } +} diff --git a/tsconfig.app.json b/tsconfig.app.json index e00e234..f066f55 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -22,6 +22,7 @@ }, "target": "ESNext", "emitDecoratorMetadata": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "allowImportingTsExtensions": true }, } \ No newline at end of file