From f8aa90db08080cc2a7ca66a6d4b9df6064c19ac0 Mon Sep 17 00:00:00 2001
From: Jonas Zohren <jonas.zohren@adesso.de>
Date: Sat, 3 Apr 2021 22:56:34 +0200
Subject: [PATCH] Implement basic (untested) gameStateSave client

---
 src/App.svelte               |  6 ++--
 src/SavingComponent.svelte   | 57 ++++++++++++++++++++++++++++--------
 src/gameStateServerClient.ts | 48 ++++++++++++++++++++++++++++++
 src/types.ts                 | 13 ++++++++
 4 files changed, 110 insertions(+), 14 deletions(-)
 create mode 100644 src/gameStateServerClient.ts

diff --git a/src/App.svelte b/src/App.svelte
index 0b2c204..f326889 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -3,7 +3,9 @@
   import { fetchDialogSet } from "./utils";
   import Debugger from "./Debugger.svelte";
   import type { Dialog } from "./types";
-import SavingComponent from "./SavingComponent.svelte";
+  import SavingComponent from "./SavingComponent.svelte";
+
+  const gameStateServerBaseUrl = "http://localhost:4000/";
 
   const dialogSetPromise = fetchDialogSet();
   let currentDialog: Dialog;
@@ -15,7 +17,7 @@ import SavingComponent from "./SavingComponent.svelte";
 
     <br />
     <Debugger {dialogSet} bind:currentDialog />
-    <SavingComponent/>
+    <SavingComponent {gameStateServerBaseUrl} />
   {:catch _error}
     <h3>Oh no :(</h3>
     <p>
diff --git a/src/SavingComponent.svelte b/src/SavingComponent.svelte
index b1aee2c..0af4a6d 100644
--- a/src/SavingComponent.svelte
+++ b/src/SavingComponent.svelte
@@ -1,15 +1,48 @@
+<script lang="ts">
+  import { identity } from "svelte/internal";
+  import { gameFactsStore } from "./gameFacts";
+
+  import { GameStateServerClient } from "./gameStateServerClient";
+  import type { GameState } from "./types";
+
+  export let gameStateServerBaseUrl: string;
+  $: gameStateServerClient = new GameStateServerClient(gameStateServerBaseUrl);
+  let pretixOrderCode: string = "XXXYYY";
+
+  async function handleSaveGameState(): Promise<void> {
+    const gameState: GameState = {
+      gameFacts: $gameFactsStore,
+    };
+    gameStateServerClient.saveState(pretixOrderCode, gameState);
+  }
+
+  async function handleLoadGameState(): Promise<void> {
+    const newGameState = await gameStateServerClient.loadState(pretixOrderCode);
+    $gameFactsStore = newGameState.gameFacts;
+  }
+</script>
+
 <div>
-    <input type="text" placeholder="Your Pretix order code" maxlength="5" minlength="5" >
-    <button style="width: 25rem;"
-        >💾 Save game (not implemented yet)</button
-      >
+  <input
+    type="text"
+    placeholder="Your Pretix order code"
+    maxlength="5"
+    minlength="5"
+    bind:value={pretixOrderCode}
+  />
+  <button style="width: 10rem;" on:click|preventDefault={handleSaveGameState}>
+    💾 Save game
+  </button>
+  <button style="width: 10rem;" on:click|preventDefault={handleLoadGameState}>
+    ▶️ Load game
+  </button>
 </div>
+
 <style>
-    div {
-      border: 1px solid;
-      padding: 10px;
-      padding-bottom: 0px;
-      margin: 5px;
-    }
-  </style>
-  
\ No newline at end of file
+  div {
+    border: 1px solid;
+    padding: 10px;
+    padding-bottom: 0px;
+    margin: 5px;
+  }
+</style>
diff --git a/src/gameStateServerClient.ts b/src/gameStateServerClient.ts
new file mode 100644
index 0000000..cd93d70
--- /dev/null
+++ b/src/gameStateServerClient.ts
@@ -0,0 +1,48 @@
+import { GameState, isValidGameState } from "./types";
+
+export class GameStateServerClient {
+  _apiBaseUrl: string;
+  constructor(apiBaseUrl: string) {
+    this._apiBaseUrl = apiBaseUrl;
+  }
+
+  async saveState(id: string, gameState: GameState): Promise<boolean> {
+    try {
+      await fetch(this._apiBaseUrl + "/game/state/" + id, {
+        method: "POST",
+        mode: "cors",
+        cache: "no-cache",
+        credentials: "omit",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        referrerPolicy: "no-referrer",
+        body: JSON.stringify(gameState),
+      });
+      return true;
+    } catch (saveStateToServerError) {
+      console.error(
+        "Failed to save state to server. The following info may help to solve this problem:",
+        saveStateToServerError
+      );
+      return false;
+    }
+  }
+
+  async loadState(id: string): Promise<GameState> {
+    try {
+      const rawResponse = await fetch(this._apiBaseUrl + "/game/state/" + id, {
+        method: "GET",
+        mode: "cors",
+        cache: "no-cache",
+        credentials: "omit",
+        referrerPolicy: "no-referrer",
+      });
+      const decodedResponse = await rawResponse.json();
+      if (!isValidGameState(decodedResponse)) {
+        throw new TypeError("State from server is not a valid gameState");
+      }
+      return decodedResponse;
+    } catch (loadGameStateFromServerError) {}
+  }
+}
diff --git a/src/types.ts b/src/types.ts
index 3a82a88..fc40a9a 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -97,3 +97,16 @@ export interface DialogOption {
    */
   forbiddenFacts?: String[];
 }
+
+export interface GameState {
+  gameFacts: String[];
+}
+
+export function isValidGameState(input: unknown): input is GameState {
+  if (typeof input !== "object") return false;
+  return (
+    input["gameFacts"] !== undefined &&
+    Array.isArray(input["gameFacts"]) &&
+    input["gameFacts"].every((gameFact) => typeof gameFact === "string")
+  );
+}
-- 
GitLab