diff --git a/.gitea/workflows/demo.yaml b/.gitea/workflows/demo.yaml index 3c2941c..0514f8f 100644 --- a/.gitea/workflows/demo.yaml +++ b/.gitea/workflows/demo.yaml @@ -21,3 +21,30 @@ jobs: - name: Run e2e tests run: npm run e2e + + - name: ntfy-notifications-failure + uses: niniyas/ntfy-action@master + if: failure() + with: + url: ${{ secrets.NTFY_URL }} + topic: ${{ secrets.NTFY_TOPIC }} + priority: 5 + headers: '{"authorization": "${{ secrets.NTFY_HEADERS }}"}' + tags: -1,rotating_light,action,failed, + details: Workflow has failed! + icon: 'https://styles.redditmedia.com/t5_32uhe/styles/communityIcon_xnt6chtnr2j21.png' + image: true + + - name: ntfy-notifications-success + uses: niniyas/ntfy-action@master + if: success() + with: + url: ${{ secrets.NTFY_URL }} + topic: ${{ secrets.NTFY_TOPIC }} + priority: 4 + headers: '{"authorization": "${{ secrets.NTFY_HEADERS }}"}' + tags: +1,partying_face,action,successfully,completed + details: Workflow has been successfully completed! + icon: 'https://styles.redditmedia.com/t5_32uhe/styles/communityIcon_xnt6chtnr2j21.png' + image: true + diff --git a/e2e/game-model/gamesystems/productGamesystems/CreateProductStates.spec.ts b/e2e/game-model/gamesystems/productGamesystems/CreateProductStates.spec.ts new file mode 100644 index 0000000..3ee77ef --- /dev/null +++ b/e2e/game-model/gamesystems/productGamesystems/CreateProductStates.spec.ts @@ -0,0 +1,48 @@ +import { BrowserContext, ElectronApplication, Page, _electron as electron } from 'playwright'; +import { test, expect } from '@playwright/test'; +import * as PATH from 'path'; +import {GameModel} from "../../../src/app/game-model/GameModel"; +import {Gamesystem} from "../../src/app/game-model/gamesystems/Gamesystem"; +import {ScriptAccount} from "../../../src/app/game-model/scriptAccounts/ScriptAccount"; +import {ModelComponentType} from "../../../src/app/game-model/ModelComponentType"; +import {SimpleGamesystem} from "../../../src/app/game-model/gamesystems/SimpleGamesystem"; +import exp = require("node:constants"); +import {end} from "electron-debug"; +import {GamesystemTrainer} from "./GamesystemTrainer"; +import {ProductGamesystem} from "../../../src/app/game-model/gamesystems/ProductGamesystem"; +import {ProductStateTrainer} from "./ProductStateTrainer"; +import {SimpleState} from "../../../../src/app/game-model/gamesystems/SimpleState"; +test.describe('Test Create ProductStates', () => { + + test("Adding already existent ProductState", async () => { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + const result = gamesystem.createState(gamesystem.states[0].innerStates); + expect(result).toBeUndefined(); + expect(gamesystem.states.length).toEqual(4); + }) + + test("Test empty inputs for ProductState Creation", async ()=> { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.createState([])).toBeUndefined(); + expect(gamesystem.states.length).toEqual(4); + }) + + test("Test invalid inputs for ProductState Creation", async () => { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.createState([ProductStateTrainer.givenUnusedInnerStates(gamesystem)[0]])).toBeUndefined(); + expect(gamesystem.states.length).toEqual(4); + }) + + test("Test ProductState Creation with foreign State", async () => { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + const simpleState = new SimpleState(ProductStateTrainer.INNERSTATE_LETTER_3); + expect(gamesystem.createState([simpleState])).toBeUndefined(); + expect(gamesystem.states.length).toEqual(4); + }) + + test("Test valid ProductStateCreation", async () => { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.createState(ProductStateTrainer.givenUnusedInnerStates(gamesystem))).toBeDefined(); + expect(gamesystem.states.length).toEqual(5); + }) +}); diff --git a/e2e/game-model/gamesystems/productGamesystems/CreateProductTransitions.spec.ts b/e2e/game-model/gamesystems/productGamesystems/CreateProductTransitions.spec.ts new file mode 100644 index 0000000..fc7dcab --- /dev/null +++ b/e2e/game-model/gamesystems/productGamesystems/CreateProductTransitions.spec.ts @@ -0,0 +1,51 @@ +import { test, expect } from '@playwright/test'; +import {ProductStateTrainer} from "./ProductStateTrainer"; +import {ProductState} from "../../../../src/app/game-model/gamesystems/ProductState"; +import {SimpleGamesystem} from "../../../../src/app/game-model/gamesystems/SimpleGamesystem"; +test.describe('Test Create ProductTransitions', () => { + test("Test ProductTransition Creation with invalid inputs", async ()=> { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.createTransition(null, null)).toBeUndefined(); + expect(gamesystem.transitions.length).toEqual(3); + expect(gamesystem.createTransition(undefined, undefined)).toBeUndefined(); + expect(gamesystem.transitions.length).toEqual(3); + + expect(gamesystem.createTransition(undefined, gamesystem.states[0])).toBeUndefined(); + expect(gamesystem.transitions.length).toEqual(3); + expect(gamesystem.createTransition(null, gamesystem.states[0])).toBeUndefined(); + expect(gamesystem.transitions.length).toEqual(3); + + expect(gamesystem.createTransition(gamesystem.states[0], undefined)).toBeUndefined(); + expect(gamesystem.transitions.length).toEqual(3); + expect(gamesystem.createTransition(gamesystem.states[0], null)).toBeUndefined(); + expect(gamesystem.transitions.length).toEqual(3); + }) + + test("Test ProductTransitition Creation with duplicate Transition", async ()=> { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.createTransition(gamesystem.states[0], gamesystem.states[1])).toBeUndefined(); + }) + + test("Test ProductTransition Creation with foreign States", async () => { + const simpleGamesystems: SimpleGamesystem[] = [new SimpleGamesystem("Test", "Test"), new SimpleGamesystem("Test1", "Test2")]; + const simpleGamesystem2: SimpleGamesystem[] = [new SimpleGamesystem("Test3", "Test"), new SimpleGamesystem("Test4", "Test2")]; + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + const foreignProductState = new ProductState(simpleGamesystems); + const foreignEndProductState = new ProductState(simpleGamesystem2); + + expect(gamesystem.createTransition(foreignProductState, foreignEndProductState)).toBeUndefined(); + expect(gamesystem.transitions.length).toEqual(3); + }) + + test("Test creating SelfTransitions", async ()=> { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.createTransition(gamesystem.states[0], gamesystem.states[0])).toBeUndefined(); + expect(gamesystem.transitions.length).toEqual(3); + }) + + test("Test Valid Transition Creation", async () => { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.createTransition(gamesystem.states[1], gamesystem.states[2])).toBeDefined(); + expect(gamesystem.transitions.length).toEqual(4) + }) +}); diff --git a/e2e/game-model/gamesystems/productGamesystems/EqualInnerStates.spec.ts b/e2e/game-model/gamesystems/productGamesystems/EqualInnerStates.spec.ts new file mode 100644 index 0000000..1b6415e --- /dev/null +++ b/e2e/game-model/gamesystems/productGamesystems/EqualInnerStates.spec.ts @@ -0,0 +1,37 @@ +import { BrowserContext, ElectronApplication, Page, _electron as electron } from 'playwright'; +import { test, expect } from '@playwright/test'; +import * as PATH from 'path'; +import {GameModel} from "../../../src/app/game-model/GameModel"; +import {Gamesystem} from "../../src/app/game-model/gamesystems/Gamesystem"; +import {ScriptAccount} from "../../../src/app/game-model/scriptAccounts/ScriptAccount"; +import {ModelComponentType} from "../../../src/app/game-model/ModelComponentType"; +import {SimpleGamesystem} from "../../../src/app/game-model/gamesystems/SimpleGamesystem"; +import exp = require("node:constants"); +import {end} from "electron-debug"; +import {GamesystemTrainer} from "./GamesystemTrainer"; +import {ProductGamesystem} from "../../../src/app/game-model/gamesystems/ProductGamesystem"; +import {ProductStateTrainer} from "./ProductStateTrainer"; +import {SimpleState} from "../../../../src/app/game-model/gamesystems/SimpleState"; +test.describe('Test Check Equal of Innerstates', () => { + + test("Test invalid input for equal checking", async()=> { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.states[0].equalInnerStates(null)).toBeFalsy(); + expect(gamesystem.states[0].equalInnerStates(undefined)).toBeFalsy(); + }) + + test("Test empty input for equal checking", async ()=> { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.states[0].equalInnerStates([])).toBeFalsy(); + }) + + test("Test identical inner states for equal", async ()=> { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.states[0].equalInnerStates(gamesystem.states[0].innerStates)).toBeTruthy(); + }) + + test("Test slightly derivating inner states for Equal", async ()=> { + const gamesystem = ProductStateTrainer.givenFullProductGamesystemWithTwoStates(); + expect(gamesystem.states[0].equalInnerStates(gamesystem.states[1].innerStates)).toBeFalsy(); + }) +}); diff --git a/e2e/game-model/gamesystems/productGamesystems/ProductStateGeneration.spec.ts b/e2e/game-model/gamesystems/productGamesystems/ProductStateGeneration.spec.ts new file mode 100644 index 0000000..c9991f3 --- /dev/null +++ b/e2e/game-model/gamesystems/productGamesystems/ProductStateGeneration.spec.ts @@ -0,0 +1,34 @@ +import { BrowserContext, ElectronApplication, Page, _electron as electron } from 'playwright'; +import { test, expect } from '@playwright/test'; +import * as PATH from 'path'; +import {GameModel} from "../../../src/app/game-model/GameModel"; +import {Gamesystem} from "../../src/app/game-model/gamesystems/Gamesystem"; +import {ScriptAccount} from "../../../src/app/game-model/scriptAccounts/ScriptAccount"; +import {ModelComponentType} from "../../../src/app/game-model/ModelComponentType"; +import {SimpleGamesystem} from "../../../src/app/game-model/gamesystems/SimpleGamesystem"; +import exp = require("node:constants"); +import {end} from "electron-debug"; +import {GamesystemTrainer} from "./GamesystemTrainer"; +import {ProductGamesystem} from "../../../src/app/game-model/gamesystems/ProductGamesystem"; +import {ProductStateTrainer} from "./ProductStateTrainer"; +import {SimpleState} from "../../../../src/app/game-model/gamesystems/SimpleState"; +import {ProductSystemGenerationTrainer} from "./ProductSystemGenerationTrainer"; +test.describe('Test Create ProductStates', () => { + + test("Test simple Binary Product State", async () => { + const gamesystem = ProductSystemGenerationTrainer.givenSimplestProductSystem(); + gamesystem.generateFromChildsystems(); + + expect(gamesystem.states.length).toEqual(4); + expect(gamesystem.transitions.length).toEqual(5); + }) + + test("Test simple triple Product State", async () => { + const gamesystem = ProductSystemGenerationTrainer.givenTrippleProductSystem(); + gamesystem.generateFromChildsystems(); + + expect(gamesystem.states.length).toEqual(8); + expect(gamesystem.transitions.length).toEqual(19) + }) + +}); diff --git a/e2e/game-model/gamesystems/productGamesystems/ProductStateTrainer.ts b/e2e/game-model/gamesystems/productGamesystems/ProductStateTrainer.ts new file mode 100644 index 0000000..a0d09a4 --- /dev/null +++ b/e2e/game-model/gamesystems/productGamesystems/ProductStateTrainer.ts @@ -0,0 +1,53 @@ +import {ProductGamesystem} from "../../../../src/app/game-model/gamesystems/ProductGamesystem"; +import {SimpleGamesystem} from "../../../../src/app/game-model/gamesystems/SimpleGamesystem"; +import {ProductState} from "../../../../src/app/game-model/gamesystems/ProductState"; +import {ProductTransition} from "../../../../src/app/game-model/gamesystems/ProductTransition"; + +export class ProductStateTrainer { + static INNERSTATE_LETTER_1 = "A"; + static INNERSTATE_LETTER_2 = "B"; + static INNERSTATE_LETTER_3 = "C"; + static INNERSTATE_NUMBER_1 = "1"; + static INNERSTATE_NUMBER_2 = "2"; + static INNERSTATE_NUMBER_3 = "3"; + + static LETTERS_GAMESYSTEM_NAME = "Letters"; + static NUMBERS_GAMESYSTEM_NAME = "Numbers"; + static PRODUCT_GAMESYSTEM_NAME = "Product Gamesystem"; + + static givenFullProductGamesystemWithTwoStates() { + const letter_Gamesystem = new SimpleGamesystem(this.LETTERS_GAMESYSTEM_NAME); + const letter_1 = letter_Gamesystem.createState(this.INNERSTATE_LETTER_1, "")!; + const letter_2 = letter_Gamesystem.createState(this.INNERSTATE_LETTER_2, "")!; + const number_gamesystem = new SimpleGamesystem(this.NUMBERS_GAMESYSTEM_NAME); + const number_1 = number_gamesystem.createState(this.INNERSTATE_NUMBER_1, "")!; + const number_2 = number_gamesystem.createState(this.INNERSTATE_NUMBER_2, "")!; + const productGamesystem = new ProductGamesystem(this.PRODUCT_GAMESYSTEM_NAME); + + productGamesystem.states.push(new ProductState( [letter_1, number_1])); + productGamesystem.states.push(new ProductState( [letter_1, number_2])); + productGamesystem.states.push(new ProductState( [letter_2, number_1])); + productGamesystem.states.push(new ProductState( [letter_2, number_2])); + + productGamesystem.transitions.push(new ProductTransition(productGamesystem.states[0], productGamesystem.states[1])) + productGamesystem.transitions.push(new ProductTransition(productGamesystem.states[0], productGamesystem.states[2])) + productGamesystem.transitions.push(new ProductTransition(productGamesystem.states[0], productGamesystem.states[3])) + + productGamesystem.innerGamesystems.push(letter_Gamesystem); + productGamesystem.innerGamesystems.push(number_gamesystem); + + return productGamesystem; + + } + + static givenUnusedInnerStates(productGamesystem: ProductGamesystem) { + const letterGamesystem = productGamesystem.findChildSystemByName(ProductStateTrainer.LETTERS_GAMESYSTEM_NAME) as SimpleGamesystem; + const numberGamesystem = productGamesystem.findChildSystemByName(ProductStateTrainer.NUMBERS_GAMESYSTEM_NAME) as SimpleGamesystem; + + const letterGamesystemState = letterGamesystem.createState(ProductStateTrainer.INNERSTATE_LETTER_3, "")!; + const numberGamesystemState = numberGamesystem.createState(ProductStateTrainer.INNERSTATE_NUMBER_3, "")!; + + return [letterGamesystemState, numberGamesystemState]; + } + +} diff --git a/e2e/game-model/gamesystems/productGamesystems/ProductSystemGenerationTrainer.ts b/e2e/game-model/gamesystems/productGamesystems/ProductSystemGenerationTrainer.ts new file mode 100644 index 0000000..aa34557 --- /dev/null +++ b/e2e/game-model/gamesystems/productGamesystems/ProductSystemGenerationTrainer.ts @@ -0,0 +1,43 @@ +import {ProductGamesystem} from "../../../../src/app/game-model/gamesystems/ProductGamesystem"; +import {SimpleGamesystem} from "../../../../src/app/game-model/gamesystems/SimpleGamesystem"; + +export class ProductSystemGenerationTrainer { + + static PRODUCTGAMESYSTEMNAME = "ProductGamesystem"; + static CHILDGAMESYSTEMNAME_LEFT = "Left Child System"; + static CHILDGAMESYSTEMNAME_RIGHT = "Right Child System"; + static CHILDGAMESYSTEMNAME_MIDDLE = "Middle Child System"; + + static LEFTCHILDSYSTEM_A_NODE_NAME = "A"; + static LEFTCHILDSYSTEM_B_NODE_NAME = "B"; + + static RIGHTCHILDSYSTEM_A_NODE_NAME = "1"; + static RIGHTCHILDSYSTEM_B_NODE_NAME = "2"; + + static MIDDLECHILDSYSTEM_A_NODE_NAME = "Sun"; + static MIDDLECHILDSYSTEM_B_NODE_NAME = "Rain"; + static givenSimplestProductSystem() { + const productsystem = new ProductGamesystem(ProductSystemGenerationTrainer.PRODUCTGAMESYSTEMNAME); + const left_childsystem = new SimpleGamesystem(ProductSystemGenerationTrainer.CHILDGAMESYSTEMNAME_LEFT); + const right_childsystem = new SimpleGamesystem(ProductSystemGenerationTrainer.CHILDGAMESYSTEMNAME_RIGHT); + + left_childsystem.createTransition(left_childsystem.createState(ProductSystemGenerationTrainer.LEFTCHILDSYSTEM_A_NODE_NAME, "")!, + left_childsystem.createState(ProductSystemGenerationTrainer.LEFTCHILDSYSTEM_B_NODE_NAME, "")!); + right_childsystem.createTransition(right_childsystem.createState(ProductSystemGenerationTrainer.RIGHTCHILDSYSTEM_A_NODE_NAME, "")!, + right_childsystem.createState(ProductSystemGenerationTrainer.RIGHTCHILDSYSTEM_B_NODE_NAME, "")!); + productsystem.addChildGamesystem(left_childsystem); + productsystem.addChildGamesystem(right_childsystem); + + return productsystem; + } + + static givenTrippleProductSystem() { + const gamesystem = this.givenSimplestProductSystem(); + const middle_childsystem = new SimpleGamesystem(this.CHILDGAMESYSTEMNAME_MIDDLE); + + middle_childsystem.createTransition(middle_childsystem.createState(ProductSystemGenerationTrainer.MIDDLECHILDSYSTEM_A_NODE_NAME, "")!, + middle_childsystem.createState(ProductSystemGenerationTrainer.MIDDLECHILDSYSTEM_B_NODE_NAME, "")!); + gamesystem.addChildGamesystem(middle_childsystem); + return gamesystem; + } +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c23a46e..95cb454 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -15,6 +15,8 @@ import {DeleteConfirmationDialogComponent} from "./delete-confirmation-dialog/de import {ScriptAccount} from "./game-model/scriptAccounts/ScriptAccount"; import {GamescriptOverviewComponent} from "./side-overviews/gamescript-overview/gamescript-overview.component"; import {SimpleGamesystem} from "./game-model/gamesystems/SimpleGamesystem"; +import {ProductGamesystem} from "./game-model/gamesystems/ProductGamesystem"; +import {ProductState} from "./game-model/gamesystems/ProductState"; @Component({ selector: 'app-root', @@ -150,10 +152,19 @@ export class AppComponent implements OnInit{ const springState = season.createState("Spring", "Spring, also known as springtime, is one of the four temperate seasons, succeeding winter and preceding summer."); const summerState = season.createState("Summer", "Summer is the hottest and brightest of the four temperate seasons, occurring after spring and before autumn. "); - season.createTransition(springState!, summerState!); + const sunnyState = weather.createState("Sunny", "The sun is shining. No clouds, no rain, no storm."); + const rainingState = weather.createState("Raining", "It rains") - this.gameModel.addGamesystem(weather) - this.gameModel.addGamesystem(season); + season.createTransition(springState!, summerState!); + weather.createTransition(sunnyState!, rainingState!); + + const weather_season = new ProductGamesystem("Weather-Season"); + weather_season.addChildGamesystem(weather); + weather_season.addChildGamesystem(season); + + weather_season.createState([springState!, sunnyState!]); + + this.gameModel.addGamesystem(weather_season); } openScriptAccountsOverview() { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4461bfd..9a9cb35 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -51,6 +51,9 @@ import { SimpleTransitionEditorComponent } from "./editor/gamesystem-editor/transition-editor/simple-transition-editor/simple-transition-editor.component"; import {MatOption, MatSelect} from "@angular/material/select"; +import { + ProductGamesystemEditorComponent +} from "./editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component"; // AoT requires an exported function for factories const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json'); @@ -121,7 +124,8 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl MatCheckbox, MatSelect, MatOption, - MatHint + MatHint, + ProductGamesystemEditorComponent ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/editor/editor.component.html b/src/app/editor/editor.component.html index dc13cc1..59dcd6f 100644 --- a/src/app/editor/editor.component.html +++ b/src/app/editor/editor.component.html @@ -1,4 +1,4 @@ - + inventory_2 @@ -11,7 +11,8 @@ + [gamesystem]="convertModelComponentToGamesystem(modelComponent)" + (onOpenGamesystemEditor)="openGameModelComponent($event)"> diff --git a/src/app/editor/editor.component.ts b/src/app/editor/editor.component.ts index c042346..77a8433 100644 --- a/src/app/editor/editor.component.ts +++ b/src/app/editor/editor.component.ts @@ -15,10 +15,14 @@ import {Transition} from "../game-model/gamesystems/Transition"; export class EditorComponent { gameModelComponents: ModelComponent[] = []; @Output("onModelNameUpdate") onModelNameUpdateEmitter = new EventEmitter(); + activeTab: number = this.gameModelComponents.length; openGameModelComponent(gameModelComponent: ModelComponent) { if(!this.gameModelComponents.includes(gameModelComponent)) { this.gameModelComponents.push(gameModelComponent); + this.activeTab = this.gameModelComponents.length; + } else { + this.activeTab = this.gameModelComponents.findIndex(component => component.componentName === gameModelComponent.componentName); } } diff --git a/src/app/editor/gamesystem-editor/gamesystem-editor.component.html b/src/app/editor/gamesystem-editor/gamesystem-editor.component.html index 49bfe84..624b252 100644 --- a/src/app/editor/gamesystem-editor/gamesystem-editor.component.html +++ b/src/app/editor/gamesystem-editor/gamesystem-editor.component.html @@ -1 +1,3 @@ + diff --git a/src/app/editor/gamesystem-editor/gamesystem-editor.component.ts b/src/app/editor/gamesystem-editor/gamesystem-editor.component.ts index 666f6a6..e21373f 100644 --- a/src/app/editor/gamesystem-editor/gamesystem-editor.component.ts +++ b/src/app/editor/gamesystem-editor/gamesystem-editor.component.ts @@ -1,4 +1,4 @@ -import {Component, Input} from '@angular/core'; +import {Component, EventEmitter, Input, Output} from '@angular/core'; import {GameModel} from "../../game-model/GameModel"; import {Gamesystem} from "../../game-model/gamesystems/Gamesystem"; import {State} from "../../game-model/gamesystems/State"; @@ -14,6 +14,7 @@ import {ProductGamesystem} from "../../game-model/gamesystems/ProductGamesystem" export class GamesystemEditorComponent { @Input() gamesystem: Gamesystem, Transition> | undefined + @Output('onOpenGamesystemEditor') openGamesystemEmitter = new EventEmitter(); isSimpleGamesystem() { return this.gamesystem instanceof SimpleGamesystem; @@ -30,4 +31,8 @@ export class GamesystemEditorComponent { return this.gamesystem as ProductGamesystem; } } + + onOpenGamesystemEditor(gamesystem: SimpleGamesystem) { + this.openGamesystemEmitter.emit(gamesystem); + } } diff --git a/src/app/editor/gamesystem-editor/product-gamesystem-editor/LeafGamesystemCalculator.ts b/src/app/editor/gamesystem-editor/product-gamesystem-editor/LeafGamesystemCalculator.ts new file mode 100644 index 0000000..b505d93 --- /dev/null +++ b/src/app/editor/gamesystem-editor/product-gamesystem-editor/LeafGamesystemCalculator.ts @@ -0,0 +1,35 @@ +import {Gamesystem} from "../../../game-model/gamesystems/Gamesystem"; +import {SimpleGamesystem} from "../../../game-model/gamesystems/SimpleGamesystem"; +import {ProductGamesystem} from "../../../game-model/gamesystems/ProductGamesystem"; +import {State} from "../../../game-model/gamesystems/State"; +import {ProductState} from "../../../game-model/gamesystems/ProductState"; +import {SimpleState} from "../../../game-model/gamesystems/SimpleState"; + +export class LeafGamesystemCalculator { + + static calcLeafGeamesystems(gamesystem: Gamesystem) { + if(gamesystem instanceof SimpleGamesystem) { + return [gamesystem]; + } else { + const product_gamesystem = gamesystem as ProductGamesystem; + const leaf_gamesystems: SimpleGamesystem[] = []; + product_gamesystem.innerGamesystems.forEach(innerGamesystem => { + LeafGamesystemCalculator.calcLeafGeamesystems(innerGamesystem).forEach(leafGamesystem => leaf_gamesystems.push(leafGamesystem)) + }) + return leaf_gamesystems; + } + } + + static calcLeafStates(state: State) { + if(state instanceof SimpleState) { + return [state]; + } else { + const productState = state as ProductState; + const leafStates: SimpleState[] = []; + productState.innerStates.forEach(innerState => { + LeafGamesystemCalculator.calcLeafStates(innerState).forEach(leafState => leafStates.push(leafState)); + }) + return leafStates; + } + } +} diff --git a/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.html b/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.html new file mode 100644 index 0000000..038300d --- /dev/null +++ b/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.scss b/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.spec.ts b/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.spec.ts new file mode 100644 index 0000000..659bfd4 --- /dev/null +++ b/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductGamesystemEditorComponent } from './product-gamesystem-editor.component'; + +describe('ProductGamesystemEditorComponent', () => { + let component: ProductGamesystemEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductGamesystemEditorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProductGamesystemEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.ts b/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.ts new file mode 100644 index 0000000..1a90798 --- /dev/null +++ b/src/app/editor/gamesystem-editor/product-gamesystem-editor/product-gamesystem-editor.component.ts @@ -0,0 +1,29 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {ProductGamesystem} from "../../../game-model/gamesystems/ProductGamesystem"; +import {ProductStateEditorComponent} from "../state-editor/product-state-editor/product-state-editor.component"; +import {SimpleGamesystem} from "../../../game-model/gamesystems/SimpleGamesystem"; +import { + ProductTransitionEditorComponent +} from "../transition-editor/product-transition-editor/product-transition-editor.component"; + + + +@Component({ + selector: 'app-product-gamesystem-editor', + standalone: true, + imports: [ + ProductStateEditorComponent, + ProductTransitionEditorComponent + ], + templateUrl: './product-gamesystem-editor.component.html', + styleUrl: './product-gamesystem-editor.component.scss' +}) +export class ProductGamesystemEditorComponent { + + @Input() gamesystem: ProductGamesystem | undefined + @Output("onOpenGamesystemEditor") openGamesystemEditorEmitter = new EventEmitter(); + + onOpenGamesystemEditor(gamesystem: SimpleGamesystem) { + this.openGamesystemEditorEmitter.emit(gamesystem); + } +} diff --git a/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.html b/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.html new file mode 100644 index 0000000..2d7eda7 --- /dev/null +++ b/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.html @@ -0,0 +1,14 @@ + + + + + + + + +
{{col}} + {{getLeafState(state, i).stateLabel}} + + {{state.initial? 'done':'close'}} + +
diff --git a/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.scss b/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.scss new file mode 100644 index 0000000..d53103d --- /dev/null +++ b/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.scss @@ -0,0 +1,7 @@ +table { + width: 100%; +} + +.mat-column-Initial { + width: 32px; +} diff --git a/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.spec.ts b/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.spec.ts new file mode 100644 index 0000000..21f6882 --- /dev/null +++ b/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductStateEditorComponent } from './product-state-editor.component'; + +describe('ProductStateEditorComponent', () => { + let component: ProductStateEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductStateEditorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProductStateEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.ts b/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.ts new file mode 100644 index 0000000..8e4b02b --- /dev/null +++ b/src/app/editor/gamesystem-editor/state-editor/product-state-editor/product-state-editor.component.ts @@ -0,0 +1,82 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {ProductGamesystem} from "../../../../game-model/gamesystems/ProductGamesystem"; +import {SimpleGamesystem} from "../../../../game-model/gamesystems/SimpleGamesystem"; +import { + MatCell, MatCellDef, + MatColumnDef, MatHeaderCell, MatHeaderCellDef, + MatHeaderRow, + MatHeaderRowDef, + MatRow, + MatRowDef, + MatTable, + MatTableDataSource +} from "@angular/material/table"; +import {SimpleState} from "../../../../game-model/gamesystems/SimpleState"; +import {State} from "../../../../game-model/gamesystems/State"; +import {NgForOf, NgIf} from "@angular/common"; +import {ProductState} from "../../../../game-model/gamesystems/ProductState"; +import {MatIcon} from "@angular/material/icon"; +import {Gamesystem} from "../../../../game-model/gamesystems/Gamesystem"; +import {LeafGamesystemCalculator} from "../../product-gamesystem-editor/LeafGamesystemCalculator"; +import {MatTooltip} from "@angular/material/tooltip"; + +@Component({ + selector: 'app-product-state-editor', + standalone: true, + imports: [ + MatTable, + MatRow, + MatRowDef, + MatHeaderRow, + MatHeaderRowDef, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, + MatCell, + MatCellDef, + NgForOf, + NgIf, + MatIcon, + MatTooltip + ], + templateUrl: './product-state-editor.component.html', + styleUrl: './product-state-editor.component.scss' +}) +export class ProductStateEditorComponent implements OnInit{ + + @Input() gamesystem: ProductGamesystem | undefined + @Output('onOpenGamesystemEditor') openGamesystemEditorEmitter = new EventEmitter(); + displayedColumns: string[] = []; + datasource = new MatTableDataSource(); + + + ngOnInit() { + this.gamesystem!.generateFromChildsystems(); + this.generateColumnNamesRecursively(this.gamesystem!, ""); + this.displayedColumns.push('Initial'); + this.datasource.data = this.gamesystem!.states; + } + + generateColumnNamesRecursively(gamesystem: ProductGamesystem, nestedColumnName: string) { + gamesystem.innerGamesystems.forEach(innerGamesystem => { + if(innerGamesystem instanceof SimpleGamesystem) { + this.displayedColumns.push(nestedColumnName + innerGamesystem.componentName); + } else { + this.generateColumnNamesRecursively(innerGamesystem as ProductGamesystem, nestedColumnName + innerGamesystem.componentName + "."); + } + }) + } + + protected readonly SimpleState = SimpleState; + + getLeafState(state: State, i: number) { + return LeafGamesystemCalculator.calcLeafStates(state)[i]; + } + + clickOnInnerState(leafIndex: number) { + const leaf_gamesystems = LeafGamesystemCalculator.calcLeafGeamesystems(this.gamesystem!); + const clicked_gamesystem = leaf_gamesystems[leafIndex]; + this.openGamesystemEditorEmitter.emit(clicked_gamesystem); + } + +} diff --git a/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.html b/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.html new file mode 100644 index 0000000..cbe907a --- /dev/null +++ b/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + +
{{col.displayedName}} + {{getLeafStateByIndex(transition, i).stateLabel}} + + Starting State + + Ending State +
diff --git a/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.scss b/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.spec.ts b/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.spec.ts new file mode 100644 index 0000000..9026629 --- /dev/null +++ b/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductTransitionEditorComponent } from './product-transition-editor.component'; + +describe('ProductTransitionEditorComponent', () => { + let component: ProductTransitionEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductTransitionEditorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProductTransitionEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.ts b/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.ts new file mode 100644 index 0000000..c823260 --- /dev/null +++ b/src/app/editor/gamesystem-editor/transition-editor/product-transition-editor/product-transition-editor.component.ts @@ -0,0 +1,97 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {ProductGamesystem} from "../../../../game-model/gamesystems/ProductGamesystem"; +import {SimpleGamesystem} from "../../../../game-model/gamesystems/SimpleGamesystem"; +import { + MatCell, MatCellDef, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, + MatTable, + MatTableDataSource +} from "@angular/material/table"; +import {LeafGamesystemCalculator} from "../../product-gamesystem-editor/LeafGamesystemCalculator"; +import {NgForOf, NgIf} from "@angular/common"; +import {ProductTransition} from "../../../../game-model/gamesystems/ProductTransition"; +import {ProductState} from "../../../../game-model/gamesystems/ProductState"; +import {MatTooltip} from "@angular/material/tooltip"; + +class DisplayedColumnName { + displayedName: string + internalName: string + + + constructor(displayedName: string, internalName: string) { + this.displayedName = displayedName; + this.internalName = internalName; + } +} +@Component({ + selector: 'app-product-transition-editor', + standalone: true, + imports: [ + MatTable, + NgForOf, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, + MatCell, + MatCellDef, + NgIf, + MatHeaderRow, + MatRow, + MatHeaderRowDef, + MatRowDef, + MatTooltip + ], + templateUrl: './product-transition-editor.component.html', + styleUrl: './product-transition-editor.component.scss' +}) +export class ProductTransitionEditorComponent implements OnInit{ + + @Input() gamesystem: ProductGamesystem | undefined + @Output() onOpenGamesystem = new EventEmitter(); + + dataSource = new MatTableDataSource(); + displayedColumns: DisplayedColumnName[] = []; + columns: string[] = []; + numberLeafSystems: number = -1; + + ngOnInit() { + if(this.gamesystem != undefined) { + const leafGamesystems: SimpleGamesystem[] = LeafGamesystemCalculator.calcLeafGeamesystems(this.gamesystem); + this.displayedColumns = leafGamesystems.map(leafGamesystem => new DisplayedColumnName(leafGamesystem.componentName, leafGamesystem.componentName + "-start")); + this.displayedColumns = this.displayedColumns.concat( leafGamesystems.map(leafGamesystem => new DisplayedColumnName(leafGamesystem.componentName, leafGamesystem.componentName + "-end"))); + + this.numberLeafSystems = leafGamesystems.length; + this.columns = this.displayedColumns.map(column => column.internalName) + + this.dataSource.data = this.gamesystem.transitions; + } + } + + getLeafStateByIndex(transition: ProductTransition, leafIndex: number) { + let state: ProductState; + let index = leafIndex; + if(leafIndex < this.numberLeafSystems) { + state = transition.startingState + } else { + state = transition.endingState; + index = leafIndex - this.numberLeafSystems; + } + + + + const leafStates = LeafGamesystemCalculator.calcLeafStates(state); + console.log(leafStates) + return leafStates[index]; + } + + openGamesystemEditor(leafIndex: number) { + const leafGamesystems = LeafGamesystemCalculator.calcLeafGeamesystems(this.gamesystem!); + if(leafIndex < this.numberLeafSystems) { + this.onOpenGamesystem.emit(leafGamesystems[leafIndex]) + } else { + this.onOpenGamesystem.emit(leafGamesystems[leafIndex - this.numberLeafSystems]) + } + } +} diff --git a/src/app/game-model/gamesystems/Gamesystem.ts b/src/app/game-model/gamesystems/Gamesystem.ts index 0dea6ba..2625649 100644 --- a/src/app/game-model/gamesystems/Gamesystem.ts +++ b/src/app/game-model/gamesystems/Gamesystem.ts @@ -11,8 +11,6 @@ export abstract class Gamesystem extends ModelComponent{ super(gamesystemName, "", ModelComponentType.GAMESYTEM); } - abstract createState(label: string, description: string): S|undefined; - abstract createTransition(startingState: S, endingState: S): T|undefined; abstract removeState(state: S): boolean; diff --git a/src/app/game-model/gamesystems/ProductGamesystem.ts b/src/app/game-model/gamesystems/ProductGamesystem.ts index 84a135d..ecef1d3 100644 --- a/src/app/game-model/gamesystems/ProductGamesystem.ts +++ b/src/app/game-model/gamesystems/ProductGamesystem.ts @@ -6,6 +6,7 @@ import {Transition} from "./Transition"; import {SimpleState} from "./SimpleState"; import {SimpleGamesystem} from "./SimpleGamesystem"; import {GameModel} from "../GameModel"; +import {ProductStateTrainer} from "../../../../e2e/game-model/gamesystems/productGamesystems/ProductStateTrainer"; export class ProductGamesystem extends Gamesystem { @@ -33,12 +34,25 @@ export class ProductGamesystem extends Gamesystem[]): ProductState | undefined { + if(innerStates.length == this.innerGamesystems.length && this.findProductStateByInnerStates(innerStates)== undefined) { + const productState = new ProductState(innerStates); + this.states.push(productState); + return productState; + } else { + return undefined; + } } createTransition(startingState: ProductState, endingState: ProductState): ProductTransition | undefined { - return undefined; + if(startingState == undefined || endingState == undefined || this.existsTransition(startingState, endingState) + || !this.existsState(startingState) || !this.existsState(endingState) || startingState.equals(endingState)) { + return undefined; + } + + const productTransition = new ProductTransition(startingState, endingState); + this.transitions.push(productTransition); + return productTransition; } removeState(state: ProductState): boolean { @@ -49,6 +63,77 @@ export class ProductGamesystem extends Gamesystem[] = [this.innerGamesystems[0], this.innerGamesystems[1]]; + let gamesystem: ProductGamesystem = ProductGamesystem.generateFromChildsystems(this.innerGamesystems[0], this.innerGamesystems[1], false, integratedSystems); + + for(let i=2; i, rightSystem: Gamesystem, left_temp: boolean, integratedSystems: Gamesystem[]) { + const productGamesystem = new ProductGamesystem("Temporary Gamesystem"); + integratedSystems.forEach(integratedSystem => productGamesystem.addChildGamesystem(integratedSystem)); + + leftSystem.states.forEach(leftState => { + rightSystem.states.forEach(rightState => { + for(let i=0; i, rightInnerState: State, left_temp: boolean) { + let innerStates: State[] = []; + if(!left_temp) { + innerStates = [leftInnerState, rightInnerState]; + } else { + const left_inner_product_state = leftInnerState as ProductState; + left_inner_product_state.innerStates.forEach(state => innerStates.push(state)); + innerStates.push(rightInnerState); + } + + + let binary_productState = this.findProductStateByInnerStates(innerStates); + if(binary_productState == undefined) { + binary_productState = this.createState(innerStates)!; + } + + let productInitial = true; + binary_productState.innerStates.forEach(innerState => productInitial = productInitial && innerState.initial) + return binary_productState!; + } addChildGamesystem(gamesystem: Gamesystem, Transition>) { this.innerGamesystems.push(gamesystem); @@ -57,4 +142,30 @@ export class ProductGamesystem extends Gamesystem, Transition>) { this.innerGamesystems = this.innerGamesystems.filter(childSystem => childSystem != gamesystem); } + + findProductStateByInnerStates(innerStates: State[]) { + return this.states.find(productState => productState.equalInnerStates(innerStates)); + } + + findChildSystemByName(gamesystemName: string) { + const gamesystemQueue: Gamesystem, Transition>[] = []; + this.innerGamesystems.forEach(gamesystem => gamesystemQueue.push(gamesystem)); + + while(gamesystemName.length > 0 ){ + const currentGamesystem = gamesystemQueue.shift(); + if(currentGamesystem!.componentName === gamesystemName) { + return currentGamesystem; + } + } + return undefined; + } + + private existsTransition(startingState: ProductState, endingState: ProductState) { + return this.transitions.find(transition => + transition.startingState.equals(startingState) && transition.endingState.equals(endingState)) != undefined; + } + + private existsState(state: ProductState) { + return this.states.find(s => s.equals(state)) != undefined; + } } diff --git a/src/app/game-model/gamesystems/ProductState.ts b/src/app/game-model/gamesystems/ProductState.ts index 8a5cbaf..f499f35 100644 --- a/src/app/game-model/gamesystems/ProductState.ts +++ b/src/app/game-model/gamesystems/ProductState.ts @@ -1,7 +1,42 @@ import {ProductTransition} from "./ProductTransition"; import {State} from "./State"; import {SimpleState} from "./SimpleState"; +import {Transition} from "./Transition"; export class ProductState extends State { - innerStates: SimpleState[] = []; + innerStates: State[] = []; + + constructor(innerStates: State[]) { + super(); + this.innerStates = innerStates; + } + + equalInnerStates(innerStates: State[]) { + if(innerStates == undefined || this.innerStates.length != innerStates.length) { + return false; + } + + for(let i=0; i>): boolean { + if(state instanceof SimpleState || this.innerStates.length != (state as ProductState).innerStates.length) { + return false; + } + + for(let i=0; i { + stateLabel: string = ""; + stateDescription: string = ""; + + + constructor(stateLabel: string, stateDescription: string) { + super(); + this.stateLabel = stateLabel; + this.stateDescription = stateDescription; + } + + equals(state: State>): boolean { + if(!(state instanceof SimpleState)) { + return false; + } + return this.stateLabel === (state as SimpleState).stateLabel; + } + } diff --git a/src/app/game-model/gamesystems/State.ts b/src/app/game-model/gamesystems/State.ts index 5184811..d6b78de 100644 --- a/src/app/game-model/gamesystems/State.ts +++ b/src/app/game-model/gamesystems/State.ts @@ -1,19 +1,12 @@ import {Transition} from "./Transition"; export abstract class State> { - stateLabel: string = ""; - stateDescription: string = ""; + incomingTransitions: T[] =[]; outgoingTransitions: T[] =[]; initial: boolean = false; - - constructor(stateLabel: string, stateDescription: string) { - this.stateLabel = stateLabel; - this.stateDescription = stateDescription; - } - addIncomingTransition(transition: T) { this.incomingTransitions.push(transition); } @@ -29,4 +22,6 @@ export abstract class State> { removeOutgoingTransition(transition: T) { } + + abstract equals(state: State>): boolean; }