Skip to content

Commit

Permalink
add controller page
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxMuehlbauer committed May 5, 2024
1 parent b321de2 commit 3652cf0
Show file tree
Hide file tree
Showing 10 changed files with 535 additions and 22 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"vituum": "^1.1.0"
},
"dependencies": {
"@picocss/pico": "^2.0.6",
"alpinejs": "^3.13.10",
"normalize.css": "^8.0.1"
}
Expand Down
339 changes: 332 additions & 7 deletions src/pages/controller.html

Large diffs are not rendered by default.

98 changes: 98 additions & 0 deletions src/scripts/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { storage } from "./storage";
import { DynamicGameState, DynamicQuestionState } from "./types";
import Alpine from "alpinejs";

export function initControllerData() {
Alpine.data("controller", (state: DynamicGameState) => {
return {
get view() {
return state.currentView;
},
set view(view: DynamicGameState["currentView"]) {
state.changeView(view);
},
progress: {
questions: {
get max() {
return state.questions.length;
},
get value() {
return state.activeQuestion + 1;
}
},
points: {
total: {
get max() {
return state.questions.reduce((accumulator, { maximumPoints }) => accumulator + maximumPoints, 0);
},
get value() {
return state.teams.reduce((accumulator, { points }) => accumulator + points, 0);
}
}
}
},
get currentQuestion() {
return state.questions[state.activeQuestion];
},
get plan() {
return state.questions.map((question: DynamicQuestionState) => {
return {
get teamA() {
return {
get id() {
return question._teamA;
},
set id(value) {
question._teamA = value;
},
get name() {
return question.teamA?.name;
}
}
},
get teamB() {
return {
get id() {
return question._teamB;
},
set id(value) {
question._teamB = value;
},
get name() {
return question.teamB?.name;
}
}
}
}
})
},
get teams() {
return state.teams.map(({ id, name, points }) => {
return {
id,
name,
points
}
});
},
get rankedTeams() {
return this.teams.toSorted((a, b) => b.points - a.points);
},
deleteGame() {
return () => {
const shouldDelete = confirm("Do you really wish to delete the entire game state, including teams, questions and given answers and points?");
if (shouldDelete) {
storage.delete("game");
location.reload();
}
}
},
nextQuestion() {
state.nextQuestion();
},
prevQuestion() {
state.prevQuestion();
}
}
});
}
38 changes: 29 additions & 9 deletions src/scripts/gameState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ function buildFailsCount(failsCount: StorableFailState): DynamicFailState {
return {
...failsCount,
async increase() {
this.failCount = (this.failCount + 1) > 3 ? 0 : this.failCount + 1;
this.failCount = Math.min((this.failCount + 1), 3);
await playAudio("fail.mp3");
},
decrease() {
this.failCount = Math.max((this.failCount - 1), 0);
}
}
}
Expand Down Expand Up @@ -77,13 +80,30 @@ function buildQuestion(question: StorableQuestionState): DynamicQuestionState {
get teamB() {
return state.getById?.<DynamicTeamState>(this._teamB);
},
get winnerTeam() {
if (this._winnerTeam) {
return state.getById?.<DynamicTeamState>(this._winnerTeam) || null;
}

return null;
},
get closed() {
return !!this.winnerTeam;
},
clear() {
this.answers.forEach(answer => {
answer.reset();
});
Object.values(this.fails).forEach(fail => {
fail.failCount = 0;
});
this.winnerTeam?.addPoints(this.pointsWon * -1);
this._winnerTeam = null;
},
win(teamId: WithID["id"], points: number) {
this._winnerTeam = teamId;
this.pointsWon = points;
this.winnerTeam?.addPoints(points);
}
}
}
Expand Down Expand Up @@ -116,7 +136,7 @@ function buildTeam(team: StorableTeamState): DynamicTeamState {
return trimmedName;
},
addPoints(amount: number) {
this.points = this.points + amount;
this.points = Math.max(this.points + amount, 0);
}
}
}
Expand Down Expand Up @@ -167,14 +187,12 @@ function buildGameStateFromJSON(inputState: StorableGameState): DynamicGameState
teams: inputState.teams.map(team => buildTeam(team)),
questions: inputState.questions.map(question => buildQuestion(question)),
get ranking() {
return this.teams.sort((a, b) => b.points - a.points);
return this.teams.toSorted((a, b) => b.points - a.points);
},
prevQuestion() {
(this.questions[this.activeQuestion] as DynamicQuestionState).clear();
this.activeQuestion = this.activeQuestion <= 0 ? 0 : this.activeQuestion - 1;
},
nextQuestion() {
(this.questions[this.activeQuestion] as DynamicQuestionState).clear();
this.activeQuestion = this.activeQuestion >= this.questions.length - 1 ? this.questions.length - 1 : this.activeQuestion + 1;
},
getById<T extends WithID>(id: T["id"]): T | undefined {
Expand Down Expand Up @@ -221,7 +239,9 @@ function buildDefaultGameState(): DynamicGameState {
{ id: getStateId("answer"), solution: "Schlitten", points: 79, open: false },
{ id: getStateId("answer"), solution: "Pferd", points: 69, open: true },
{ id: getStateId("answer"), solution: "Jetpack mit Festbrennstoffraketen-Antrieb", points: 59, open: false }
]
],
_winnerTeam: null,
pointsWon: 0,
},
{
id: getStateId("question"),
Expand All @@ -244,7 +264,9 @@ function buildDefaultGameState(): DynamicGameState {
{ id: getStateId("answer"), solution: "Ohne Hose rumlaufen", points: 42, open: false },
{ id: getStateId("answer"), solution: "Pferd", points: 33, open: false },
{ id: getStateId("answer"), solution: "Wäsche machen / Putzen", points: 24, open: false }
]
],
_winnerTeam: null,
pointsWon: 0,
}
]
});
Expand Down Expand Up @@ -350,6 +372,4 @@ export function initGameState(id: string = "game") {
const newState = deepMergeGameState(state, JSON.parse(newValue));
console.log(newState);
});

window["state"] = state;
}
2 changes: 2 additions & 0 deletions src/scripts/main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Alpine from "alpinejs";
import { revealDirective } from "./reveal-directive";
import { initGameState } from "./gameState";
import { initControllerData } from "./controller";

// @ts-ignore
window["Alpine"] = Alpine;

Alpine.directive("reveal", revealDirective);

initGameState();
initControllerData();

Alpine.start();
4 changes: 4 additions & 0 deletions src/scripts/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class StorageAPI {
callback({ key, oldValue, newValue });
});
}

delete(key: string) {
localStorage.removeItem(key);
}
}

export const storage = new StorageAPI();
8 changes: 7 additions & 1 deletion src/scripts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export interface StorableFailState extends WithID {
}

export interface DynamicFailState extends StorableFailState {
increase(): Promise<void>
increase(): Promise<void>;
decrease(): void;
}

export interface StorableQuestionState extends WithID {
Expand All @@ -45,6 +46,8 @@ export interface StorableQuestionState extends WithID {
};
_teamA: WithID["id"];
_teamB: WithID["id"];
_winnerTeam: WithID["id"] | null;
pointsWon: number;
}

export interface DynamicQuestionState extends StorableQuestionState {
Expand All @@ -57,7 +60,10 @@ export interface DynamicQuestionState extends StorableQuestionState {
readonly maximumPoints: number;
readonly teamA?: DynamicTeamState;
readonly teamB?: DynamicTeamState;
readonly winnerTeam: DynamicTeamState | null;
readonly closed: boolean;
clear(): void;
win(teamId: WithID["id"], points: number): void;
}

export interface StorableGameState extends WithID {
Expand Down
43 changes: 43 additions & 0 deletions src/styles/pico-edit.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
article hgroup {
--pico-typography-spacing-vertical: 0;
}

article :is(h1, h2, h3, h4, h5, h6) {
--pico-font-size: 1.1rem;
}

td>select:last-child {
--pico-spacing: 0;
}

details>summary {
position: relative;
padding-inline-end: calc(1rem + var(--pico-spacing));
}

details>summary::after {
position: absolute;
float: none;
margin: 0;
inset-inline-end: 0;
inset-block-start: calc(50% - 0.5rem);
}

article>details {
margin: 0;
}

fieldset:last-child {
margin-bottom: 0;
}

table:last-child {
margin-bottom: 0;
}

tfoot [type="button"] {
--pico-font-weight: 400;
--pico-form-element-spacing-vertical: 0.25rem;
--pico-form-element-spacing-horizontal: 0.5rem;
font-size: 0.7rem;
}
15 changes: 10 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": [
"ES2023.Array",
"ES2020",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
"include": [
"src"
]
}

0 comments on commit 3652cf0

Please sign in to comment.