Broke and Broker Inc.
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Dokumentation des Softwareprojekts **H.E.I.S.T** von *Gustav Weber* und *Jann Stute* entstanden im Rahmen des Softwareprojekts in der 12. Klasse zwischen der dritten und zwölften Kalenderwoche 2021 unter der Nutzung von Unity (+Mirror), JetBrains Rider, Visual Studio, Visual Studio Code, Paint, Paint.net, Photoshop, Audacity ## Inhaltsverzeichnis 1. [Inhaltsverzeichnis](/d7G4cStcS6GLeluEmkCpdQ#Plagiatszettel) 2. [Methodendokumentation](/d7G4cStcS6GLeluEmkCpdQ#Methodendokumentation) 2.1. [wichtigste Networking Grundlagen](/d7G4cStcS6GLeluEmkCpdQ#Methodendokumentation#wichtigste-Networking-Grundlagen) 2.2. [GUI Shaker](/d7G4cStcS6GLeluEmkCpdQ#Methodendokumentation#GUI-Shaker) 2.3. [Audio Player](/d7G4cStcS6GLeluEmkCpdQ#Audio-Player) 2.4. [Checkpointsystem](/d7G4cStcS6GLeluEmkCpdQ#Checkpointsystem) 2.5. [Alarmsystem](/d7G4cStcS6GLeluEmkCpdQ#Alarmsystem) 2.6. [Rätselsystem](/d7G4cStcS6GLeluEmkCpdQ#Rätselsystem) 3. [Benutzerdokumentation](/d7G4cStcS6GLeluEmkCpdQ#Benutzerdokumentation) 3.1. [Generelle Controlls](/d7G4cStcS6GLeluEmkCpdQ#Generelle-Controlls) 3.2. [Kabel Kappen](/d7G4cStcS6GLeluEmkCpdQ#Kabel-Kappen) 3.3. [Decode](/d7G4cStcS6GLeluEmkCpdQ#Decode1) 3.4. [Simon Says](/d7G4cStcS6GLeluEmkCpdQ#Simon-Says1) 3.5. [Maze](/d7G4cStcS6GLeluEmkCpdQ#Maze2) 3.6. [Zahlenschloss](/d7G4cStcS6GLeluEmkCpdQ#Zahlenschloss1) 3.7. [Schaltkreis](/d7G4cStcS6GLeluEmkCpdQ#Schaltkreis1) 3.8. [Tresorschloss](/d7G4cStcS6GLeluEmkCpdQ#Tresorschloss1) 3.9. [Checkpoints](/d7G4cStcS6GLeluEmkCpdQ#Checkpoints) 4. [Entwicklungsdokumentation](/d7G4cStcS6GLeluEmkCpdQ#Entwicklungsdokumentation) 5. [Plagiatszettel](/d7G4cStcS6GLeluEmkCpdQ#Plagiatszettel) ## Methodendokumentation ### wichtigste Networking Grundlagen Um das Networking zu realisieren haben wir das Mirror-Package aus dem Unity Asset-Store benutzt. Dieses basiert wiederum auf dem in Unity integrierten UNet System und funktioniert als Erweiterung dessen. Im Projekt wurden folgende Funktionalitäten von Mirror benutzt: #### SyncVars, SyncLists und SyncDictionarys Der `SyncVar`-Tag kann zu einer Variable hinzugefügt werden, um sie durch Mirror vom Server zu allen Clients zu synchronisieren. Das kann nur auf standardmäßigen Datentypen angewandt werden und nicht auf Listen und Dictionarys. Dazu gibt es die Mirror eigenen Datentypen SyncList und SyncDictionary. Diese werden ebenfalls automatisch vom Server zu den Clients synchronisiert. Wird also auf dem Server eine Veränderung an einer solchen Variable gemacht, wird diese auf alle Clients übertragen. Wird die Variable allerdings auf einem Client geändert, so wird sie nicht automatisch von Mirror synchronisiert. Man kann zusätzlich mit folgender Syntax eine hook definieren: `[SyncVar(hook="functionName")] variableName` Die Hook ist eine Funktion, die auf den Clients ausgeführt, wann immer die Variable verändert wird. Dabei ist aber darauf zu achten, dass das Definieren einer Hook das Synchronisieren überschreibt. Die Variable muss also in der Hook neu gesetzt werden. Die Hook nimmt dazu zwei Parameter mit dem Typ der Variable an, die für den alten und neuen Wert der Variable stehen. Es ist außerdem zu beachten, dass die Hook nicht auf dem Server ausgeführt wird. #### Commands Eine Funktion kann als Command markiert werden, damit sie vom Client aufgerufen und auf dem Server ausgeführt werden kann. Durch Commands kann also Kommunikation von den Clients zum Server gelangen. Ein Command kann standardmäßig nur von dem aktiven Spielerobjekt ausgeführt werden. Durch das boolean Attribut `requiresAuthority` kann man allerdings auch bestimmen, dass ein Command von jedem Objekt ausgeführt werden darf. In der Syntax sieht das dann so aus: `[Command(requiresAuthority = false)]` Im Projekt werden Commands vor allem zur Synchronisierung von SyncVars vom Client zum Server genutzt, da diese nicht automatisch durch die SyncVars funktioniert. Da diese Variablen nicht auf den Spielerobjekten sind, ist bei fast allen Commands im Projekt `requiresAuthority` auf `false` gesetzt. #### ClientRPC Eine Funktion kann als ClientRPC markiert werden, damit sie vom Server aufgerufen und auf allen Clients ausgeführt werden kann. ClientRPC sind also ein zweiter Weg, um Kommunikation vom Server auf die Clients zu übertragen. Ein ClientRPC kann nur vom Server aufgerufen werden und wird dann auf allen Clients ausgeführt. Wenn eine Funktion, die auf einem Client aufgerufen wird, nicht nur auf diesem Client ausgeführt werden soll, kann also ein ClientRPC genutzt werden. Diese dürfen aber nur vom Server ausgeführt werden. Daher ruft der Client einen Command auf, der dann auf dem Server einen ClientRPC aufruft, der auf allen Clients ausgeführt wird. ### GUI Shaker Der GUI Shaker wird benutzt, um dem Spieler Feedback zu geben, wenn dieser eine falsche Eingabe macht. Dazu wird die Funktion `ShakeScreen` genutzt. Diese nimmt die Länge des Schüttelns in Sekunden (meist 0.5), das Objekt, das geschüttelt werden soll (meist inputparentpanel, immer der root der GUI) und eine stärke zum schütteln. Je höher die Stärke, desto großer ist der Bereich, in dem das Objekt sich bewegt. Das eigentliche Schütteln passiert in `Update()`. Wenn die duration noch nicht überschritten wurde, wird die Position des Objekts auf einen zufälligen Punkt innerhalb eines Kreises mit Radius 1 * die Stärke des Schüttelns, relativ zur ursprünglichen Position gesetzt. Wird die Zeit überschritten, wird das Objekt wieder auf seinen Ursprungspunkt zurückgesetzt. Wenn das Objekt beginnt zu schütteln wird außerdem das "falsch" Geräusch über den audioPlayer abgespielt. ### Audio Player Der Audioplayer ist ein GameObject, das jederzeit in jeder Szene sein sollte. Es wird verwendet, um Geräusche Szenenübergreifend abzuspielen. Wird ein Geräusch auf normale Weise (mit `clip.Play()`) abgespielt, kurz bevor es einen Szenenübergang gibt, wird es von diesem unterbrochen und spielt in der neuen Szene nicht zu Ende. Der AudioPlayer hingegen wird von einer Szene, in die nächste übertragen. Dazu wird `DontDestroyOnLoad(this);` gesetzt. In diesem Fall wird der AudioPlayer von Szene zu Szene übertragen und der Sound übernommen. Deshalb sollten alle Geräusche, deren Auslöser auch einen Szenenübergang auslösen können (zum Beispiel der "Host Game" Button im Main Menu) den AudioPlayer benutzen, um ihre Geräusche abzuspielen. ### Checkpointsystem Der `CheckpointManager` hat eine Variable `lastCheckpoint`, in ihr wird der letzte aktivierte Checkpoint gespeichert. Wird nun ein Checkpoint über seine Methode `triggerCheckpoint()` ausgelöst, wird `lastCheckpoint` auf eben diesen Checkpoint gesetzt. Außerdem speichert der Checkpoint in seiner Variable `SolveStates` den aktuellen Zustand der gleichnamigen Variable des RätselManagers und speichert die Position des lokalen Spielers in `LocalPlayerPos` (Über einen Command-ClientRpc Setup wird dieser letzte Schritt bei allen Clients ausgelöst). Werden die Spieler zu einem Checkpoint zurückgesetzt, wird `CheckpointManager.resetToCheckpoint()` ausgeführt. Diese Methode sorgt dafür das die Variable `SolveStates` im RatselManager überschrieben wird, außerdem wird bei jedem Client ein Überschreiben der Position des lokalen Spielers durch die gespeicherte ausgelöst. Um den Rätseln, und auch jeder anderen Klasse / Objekt, die Möglichkeit zu geben, auf ein Zurücksetzen der Spieler zu einem Checkpoint zu reagieren, gibt es das `OnResetToCheckpoint` Event. Dieses Event wird ebenfalls in `CheckpointManager.resetToCheckpoint()` ausgelöst. Damit die Spieler auch zu einem Checkpoint zurückgesetzt werden können, bevor sie ein Rätsel gelöst haben, gibt es den `GameStartCheckpoint`. Dieser wird zu Beginn des Spiels ausgelöst, sodass die Spieler auch beim ersten Rätsel schon zurückgesetzt werden können. ### Alarmsystem Das Alarmsystem besteht aus der Klasse `AlarmManager`. Diese Klasse hat zwei Variablen, `AlarmCount` (zu Beginn 0) und `AlarmMax` (zu Beginn 3). Jedes Mal, wenn die Methode `triggerAlarm()` ausgeführt wird, wird `CheckpointManager.resetToCheckpoint()` ausgeführt. Außerdem wird `AlarmCount` um 1 erhöht und falls es dadurch `AlarmMax` entspricht, haben die Spieler verloren. Deshalb wird die Bewegung der Spieler deaktiviert und die Game Over GUI wird angezeigt. ### Rätselsystem Das Rätselsystem basiert auf 3 Klassen, dem `RatselManager`, dem `RatselController` und dem `Ratsel`. Der `RatselManger` existiert einmal, während jede Subklasse von `Ratsel` einmal pro RätselController am Objekt des RätselControllers existiert. #### RatselManager Der RätselManager hat eine Liste mit RätselControllern, jeder RätselController der funktionieren soll muss in dieser Liste sein. Der RätselManger hat außerdem eine Liste mit den Namen der Subklassen von `Ratsel`, den Rätseln. Bei Spielbeginn wird je ein Rätsel pro RätselController-ID (mehr zur RätselController-ID im Abschnitt RätselController) ausgewählt, welches aktiviert wird. Die Liste mit Namen der Rätsel wird genutzt, damit kein Rätsel mehrfach auftritt. Außerdem hat der RätselManager die Variable `SyncDictionary<int, bool> SolveStates`, in diese wird bei Spielbeginn für jede RätselController-ID ein Eintrag mit dem Wert `false` gemacht. Zusätzlich zu diesen Einträgen wird auch noch ein Eintrag für die RatselController-ID `-1` mit Wert `true` gemacht, dieser dient Türen, die von Beginn an offen sind. Das Wörterbuch `SolveStates` dient der Speicherung der Löse-Zustände aller Rätsel, ist also der Wert für eine RätselController-ID `true` wurde dieses Rätsel bereits gelöst. Weiterhin stellt der RätselManager Methoden zum Finden bestimmten RätselController bereit und sorgt für die Funktionalität der `Ratsel.fOnEnable()`-, `Ratsel.fAwake()`-, `Ratsel.fStart()`- und `RatselController.fStart()`-Methoden. #### Ratselcontroller Der RatselController sorgt für den Aufruf der GUI in seinem zugehörigen Rätsel. Hierfür nutzt er die, in der Superklasse der Rätsel `Ratsel`, definierten Methoden `showInputGUI()`, `showViewGUI()` und `hideGUI()`. Des Weiteren enthält er viele Referenzen zu anderen Objekten, die den Zugriff auf diese für die Rätsel einfacher machen. Die RätselController-ID wird in der Variable `id` gespeichert, sie ist ein Integer der nur für zwei RätselController gleich ist und so die Zusammengehörigkeit dieser RätselController und der entsprechenden Türen markiert. Die ID wird im RatselManager genutzt, um abzuspeichern, wenn Rätsel gelöst wurden und gibt den an den Tür-Objekten die Möglichkeit festzustellen, ob das Rätsel, zu dem sie gehören bereits gelöst wurde. Die Methode `triggerReward()` des RätselControllers, wird ausgeführt, wenn ein Rätsel gelöst wird. Unteranderem soll die Methode für die Anpassung der Variable `RatselManager.SolveStates` sorgen, da Variablen vom Typ `SyncDictionary` allerdings nur vom Server verändert werden können, gibt es eine gleichnamige Methode im RatselManager, welche als Command markiert ist und von `RatselController.triggerRewad()` aufgerufen wird. Sie sorgt ausschließlich für die Veränderung von `SolveStates`. #### Ratsel Die Klasse `Ratsel` dient der Verallgemeinerung aller Rätsel auf eine Superklasse. Sie enthält zum Beispiel die virtuellen Methoden `fOnEnable()`, `fAwake()` und `fStart()`. Für diese Methoden gilt (global, für aktivierte Rätsel) das folgende: Zuerst werden alle `fOnEnable()`-, dann alle `fAwake()`- und zuletzt alle `fStart`-Methoden ausgeführt. Danach werden die `fStart()` Methoden aller beim RätselManager eingetragenen RätselController ausgeführt. Die Klasse enthält außerdem die virtuellen Methoden `showViewGUI()`, `showInputGUI()` und `hideGUI()`, welche hauptsächlich von RätselControllern aufgerufen werden und die Methode `createOverworldSprite()`. Diese letzte Methode wird bei der Initialisierung des Rätsels durch den RätselManager aufgerufen und dient des Anzeigens der entsprechenden Sprite im Spiel. Sie wird von Rätseln gewöhnlicherweise überschrieben, es muss aber die Version der Superklasse `Ratsel` auch ausgeführt werden. ##### Maze ###### Labyrinthgenerierung Für die Generierung des Labyrinthes, musste zuerst ein Datentyp für das Labyrinth festgelegt werden. Hierfür haben wir ein 3-Dimensionales Boolean-Array gewählt. Hier stellt die erste Dimension die x-Koordinate im Labyrinth dar und die zweite Dimension die y-Koordinate. Die Menge der Einträge in erster und zweiter Dimension ist also abhängig von der Größe das Labyrinths, während die dritte Dimension immer genau 2 Elemente umfasst. Das erste Element der dritten Dimension repräsentiert die untere Wand einer Zelle, `true` bedeutet der Spieler kann passieren, `false` bedeutet eine Wand ist im Weg. Für das zweite Element der dritten Dimension gilt das gleiche, nur dass es die rechte Wand repräsentiert. Die linke und die obere Wand einer Zelle werden von der dritten Dimension der jeweils angrenzenden Zelle gespeichert. Für die eigentliche Generierung des Labyrinthes wird eine Klasse namens `MazeGeneration` genutzt, sie hat folgende Felder und Methoden: Name |Datentyp |Beschreibung ---------------------------------------|-----------------|------------ RnG |`System.Random` |Eine System.Random Instanz um geseedete Zufallsgenerierung zu ermöglichen size |`Tuple<int, int>`|Die Größe des Labyrinthes MazeGeneration(System.Random, int, int)|Konstruktor |Ein Konstruktor der eine `System.Random` Instanz für `MazeGeneration.RnG` und die Größe des Labyrinthes als zwei Integer für `MazeGeneration.size` annimmt. NextMaze() |`bool[,,]` |Die Methode zum Generieren eines Labyrinthes, sie ist nicht statisch, beruft sich also auf die im Konstruktor gesetzten Werte. Die Methode `MazeGeneration.NextMaze()` benutzt den Hunt-And-Kill-Algorithmus, um Labyrinthe zu generieren, dieser besteht aus 4 Schritten: 1. Auswählen einer zufälligen Startzelle 2. "Kill"-Modus Hier wird eine zufällige Nachbarzelle ausgewählt, welche noch nicht besucht wurde. Dann wird die Wand zwischen den zwei Zellen entfernt und die Nachbarzelle wird die aktuelle Zelle. Dieser Schritt wird wiederholt, bis die aktuelle Zelle keine unbesuchten Nachbarn mehr hat. 3. "Hunt"-Modus Alle Zellen werden nach unbesuchten Zellen durchsucht, wird eine unbesuchte Zelle gefunden wird sie die aktuelle Zelle. 4. Schritt 2 & 3 werden wiederholt, bis Schritt 3 keine unbesuchte Zelle mehr findet. Um zu verhindern, dass die Wege, die zum Ziel führen eine Tendenz in eine bestimmte Richtung haben, wird die Richtung der Suche im "Hunt"-Modus ebenfalls zufällig ausgesucht (bei jeder Ausführung des 3. Schritts). ###### Maze Bei der Initialisierung des Rätsels wird zuerst `fAwake()` aufgerufen und so die Variable `Tries` auf den Wert von `MaxTries` gesetzt, was sicherstellt, dass der Spieler das Rätsel so oft versuchen kann wie vorgesehen. Als nächstes wird, falls die Instanz von `Maze` an einen `RatselController` vom Typ `Input` gebunden ist, eine Instanz von `MazeGeneration` erstellt und ein 8x8 Zellen Labyrinth erstellt und in `Maze.GMaze` gespeichert. In `fStart()` wird Initialisierung fortgesetzt die Methode `Maze.OnResetToCheckpoint()` als Listener des Events `CheckpointManager.OnResetToCheckpoint` registriert wird, um auf ein Zurücksetzen der Spieler zu einem Checkpoint reagieren zu können. Darauffolgend wird, falls es sich bei zugehörigen `RatselController` um einen `RatselController` vom Typ `View` handelt, das schon generierte Labyrinth vom zweiten `Maze` abgerufen und ebenfalls in `Maze.GMaze` abgespeichert. Weiterhin wird die Variable der Superklasse `Ratsel`, `Ratsel.shaker`, auf die am selben Objekt vorhandene Instanz der Klasse `GUIShaker` gesetzt. Als letzten Schritt der Initialisierung werden alle notwendigen Variablen für die GUI von `Maze` gesetzt, das bedeutet abhängig vom Typ des `RatselController`s wird entweder `Maze.inputGUI` oder `Maze.viewGUI` auf eine neue Instanz des Prefabs `MazeInputGUI` bzw. `MazeViewGUI` gesetzt. Außerdem wird, falls es sich bei dem zugehörigen `RatselController` um den Typ `View`, die Methode `MazeViewGUIController.drawWalls(bool[,,])` aufgerufen, um die Wände des Labyrinthes in der GUI anzuzeigen. Die Methode `Maze.FixedUpdate()` überschreibt die von `UnityEngine.Monobehaviour` geerbte Methode (über die Superklasse von `Ratsel`, `Mirror.NetworkBehaviour`) und nutzt die sogenannte "FixedUpdate-Loop", das bedeutet die Methode `FixedUpdate` von `UnityEngine.Monobehaviour` und seinen Subklassen, wird von Unity regelmäßig aufgerufen. In der Methode `Maze.FixedUpdate()` wird der Input der durch den Spieler in der Input GUI gegeben wird verarbeitet, falls der Spieler eine der entsprechenden Tasten drückt, wird also die Variable `UnityEngine.Vector2Int Maze.PlayerPos` entsprechend verändert, zum Beispiel wird falls die Taste "Pfeil nach Unten" gedrückt wird, `Maze.PlayerPos.y` um 1 erhöht (Hier wird außerdem `MazeInputGUIController.setPlayerPos(UnityEngine.Vector2Int)` ausgeführt um die Spielerposition in GUI richtig anzuzeigen). Hier findet auch noch eine Menge Inputvalidierung statt, wie zum Beispiel, würde der Spieler versuchen eine Außenwand des Labyrinthes überschreiten passiert einfach nichts. Des Weiteren, falls ein Spieler gegen eine Wand läuft, wird die Variable `Tries` um 1 verringert und sollte diese 0 erreichen, wird durch das Ausführen der Methode `AlarmManager.triggerAlarm()` der Alarm ausgelöst. Außerdem werden in der FixedUpdate-Loop die Variablen `Maze.wasHInput` und `Maze.wasVInput` genutzt. Diese werden am Ende der Input Verarbeitung auf den aktuellen Zustand des Inputs gesetzt, d.h. `Maze.wasHInput` wird auf `true` gesetzt, falls Horizontaler Input gegeben wurde und `Maze.wasVInput` falls Vertikaler Input gegeben wurde. Dieser Variablen werden dann bei der Inputvalidierung genutzt, um sicherzustellen, dass ein einmaliges Drücken einer Taste auch nur zu einer einmaligen Bewegung des Spielers führt, ist `Maze.wasHInput` also `true`, wird sämtlicher Horizontaler Input abgelehnt, das gleiche gilt für `Maze.wasVInput`. Diese Variablen werden außerdem zurückgesetzt, wenn die Input GUI geschlossen wird. Eine letzte Sache, die ebenfalls noch in der FixedUpdate-Loop passiert, ist die Überprüfung, ob der Spieler am Ziel angekommen ist, hierfür wird anhand der `Maze.PlayerPos` Variable festgestellt, ob er angekommen ist und falls ja werden folgende Funktionen ausgeführt: 1. `RatselController.triggerReward(PlayerController)` Um den Fortschritt an den anderen Client und an den RatselManager zu übertragen 2. `Maze.hideGUI()` Um die GUI auszublenden 3. `Maze.solve()` Um bei beiden Instanzen `Maze.wasSolved` auf `true` zu setzen und so ein weiteres öffnen der `Maze`-GUI zu verhindern Die Methoden `Maze.showViewGUI()` und `Maze.showInputGUI()` dienen zum Anzeigen der GUI von Maze abhängig vom Typ des zugehörigen `RatselControllers`. Bei beiden wird die entsprechende GUI zuerst aktiviert und dann die zugehörige Variable aktualisiert (`Maze.InputGUIshowing` und `Maze.ViewGUIshowing`). Außerdem wird noch `PlayerController.canMove` für die Instanz `Maze.PC` auf `false` gesetzt, um zu verhindern das der Spielercharakter sich bewegt, wenn der Spieler versucht sich im Labyrinth zu bewegen. In `Maze.showInputGUI` wird als letztes außerdem `Maze.inputParentPanel` gesetzt (siehe GUI Shaker). Sollten die Spieler zu einem Checkpoint zurückgesetzt werden, wird außerdem die Methode `Maze.OnResetToCheckpoint()` wichtig, denn diese soll das Rätsel in seinen Ausgangszustand zurücksetzten, insofern es nicht schon gelöst wurde. Hierfür werden `Maze.Tries`, `Maze.wasHInput`, `Maze.wasVInput` und `Maze.PlayerPos` auf ihre Startwerte zurückgesetzt. ###### MazeInputGUIController Bei der Initialisierung wird die Größe und Position vom Hintergrundbild angepasst, sodass das Seitenverhältnis gleichbleibt, sich die Höhe aber an die Bildschirmgröße anpasst. Außerdem wird die Farbe des Spielers entsprechend der Variable `MazeInputGUIController.PlayerColor` gesetzt. Zu guter Letzt wird die Methode `MazeInputGUIController.setPlayerPos(UnityEngine.Vector2Int)` aufgerufen, um sicher zu stellen, dass die Spieler Position richtig ist (obere linke Ecke). Die Methode `MazeInputGUIController.setPlayerPos(UnityEngine.Vector2Int)` berechnet lediglich die neue Position der `UnityEngine.UI.Image`-Instanz und setzt diese. ###### MazeViewGUIController Die Initialisierung von `MazeViewGUIController` unterscheidet sich nicht von der von `MazeInputGUIController`, genau wie die Methode `MazeViewGUIController.setPlayerPos`. Der einzige Unterschied der Methode ist, dass sie bei `MazeViewGUIController` nur ein Mal (nämlich in der Initialisierung) aufgerufen wird. Die Methode `MazeViewGUIController.drawWalls(bool[,,])` iteriert über alle Zellen im Labyrinth und zeichnet die Wände ein, falls sie existieren (wichtige Ausnahme sind die Zellen der unteren Reihe und rechten Spalte, hier wird immer nur eine Wand gezeichnet). Der Prozess ist für beide Wände gleich. Hierfür werden zuerst Anfangs- und Endpunkt der Linie, welche die Wand darstellen soll, berechnet. Danach wird über alle Pixel zwischen diesen Punkten iteriert (mit Hilfe einer simplen for-Schleife, da sie alle in einer Spalte/Zeile liegen) und für jedes Pixel wird dann die Farbe in dem angezeigten Bild geändert. Als letztes folgen noch einige Anweisungen um die Änderungen am Bild anzuwenden. ##### Schaltkreis ###### Erstellen des Schaltkreis auf dem Server Um den Schaltkreis zu generieren, wird ein rekursiver Algorithmus genutzt. Wird zum ersten Mal `showViewGUI()` oder `showInputGUI()` aufgerufen, wird der Command `CmdCreateViewGUI()` ausgeführt. Dieser erstellt dann eine Liste `boolgates` mit den Prefabs der Gatter, die jeweils zweimal in der Liste vorkommen. Es wird außerdem das ViewGUI Prefab instanziiert, da dieses unterschiedlichen möglichen Positionen der Gatter beinhaltet. Diese werden dann nach Spalten sortiert und in die Liste `imagePositions` geschrieben. Dann wird die rekursive Funktion `Create_schaltkreis` aufgerufen. Sie nimmt als Argumente: - `int depth` - entspricht der Spalte, in der das neue Gatter entsteht - `int maxdepth` - die tiefste Spalte die besetzt werden kann - `LogicGate output` - das Gatter, das an den Output des neuen Gatters angeschlossen ist - `int outputrank` - ein Index, der angibt, welcher Input von output an den Output des neuen Gatters angeschlossen ist Es gibt auch noch folgende optionale Argumente: - `Vector2 newPosition = new Vector2()` - wird nur verwendet, falls ein IN-Gate erstellt wird und gibt die gewollte Position des Gates an - `Tranform parent = null` - wird ebenfalls nur für IN-Gates verwendet und gibt das Elterntransform des Gatters an Da diese Funktion nur von sich selbst und von `CmdCreateViewGUI()` aufgerufen wird, wird sie immer auf dem Server ausgeführt. Die Funktion überprüft dann, ob die maximale Tiefe erreicht ist. Falls das nicht der Fall ist, wird ein zufälliges Gatter aus der Liste `boolgates` ausgewählt, instanziiert und entfernt. Der Name des Gates wird außerdem als String der Liste `gates` hinzugefügt. Diese hält die Reihenfolge fest, in der die Gatter erstellt werden und wird später für die Synchronisierung auf dem Client benutzt. Es werden dann die Variablen des neuen Gatters aufgesetzt, es wird an die richtige Position auf dem Bildschirm bewegt und die Position wird aus der Liste `imagePositions` entfernt. Es wird dann für jeden Input, den das Gatter hat (1, falls es ein NOT Gate ist, andernfalls 2), ein neues Gatter erstellt, für das die gleiche Funktion ausgeführt wird. Danach werden die Outputs der neuen Gates mit den Inputs des momentanen Gates mit Linien verbunden. Dazu wird die Funktion `draw_line` verwendet, auf die später genauer eingegangen wird. Zum Schluss wird das momentane Gate zurückgegeben. Falls die maximale Tiefe erreicht ist, wird ein IN-Gate erstellt. Diese entsprechen den Inputs der InputGUI. Nachdem die variablen des Gatters ausgefüllt sind, wird es zur Liste `inputs` hinzugefügt, die von der InputGUI verwendet wird, um die Knöpfe mit den Gattern zu verbinden. ###### Synchronisierung auf Clients Nachdem alle Gatter erstellt wurden, wird in `CmdCreateViewGUI` `RpcStartSetSchaltkreis()` ausgeführt. Es wird also der Schaltkreis vom Server auf die Clients übertragen. Während `Create_schaltkreis()` wurden dazu die Namen der Gatter in der Reihenfolge ihrer Entstehung zur Liste `gates` hinzugefügt. Da ein ClientRPC keine eigenen Datentypen als Argumente nehmen kann, wurden die Namen als Strings gespeichert und dann erst auf den Clients benutzt, um die Gatter zu laden. `RpcstartSetSchaltkreis()` funktioniert ähnlich zu `CreateViewGUI()` und setzt zuerst die wichtigsten Listen auf und instanziiert die viewGUI. Danach wird aus der Liste `gates`, die die Namen der Gatter enthält die entsprechenden Prefabs gewonnen und schließlich `ClientSetSchaltkreis()` ausgeführt. `ClienSetSchaltkreis` nimmt folgende Argumente: - `int depth` - entspricht `Create_schaltkreis()` - `int maxpepth`- entspricht `Create_schaltkreis()` - `List<GameObject> logicGates` - die Liste der Gatter Objekte in der Reihenfolge, in der sie erstellt wurden - `LogicGate gateoutput` - entspricht `output` in `Create_schaltkreis()` - `int outputrank` - entspricht `Create_schaltkreis()` - `Transform parent` - entspricht `Create_schaltkreis()` - `Vector2 newPosition` - entspricht `Create_schaltkreis()` - `bool updateInputs` - true, falls die IN-Gates noch zu inputs hinzugefügt werden müssen Die Funktion funktioniert ähnlich wie Create_schaltkreis, aber anstatt die Gates zufällig auszuwählen, werden sie aus `logicGates` gezogen. ###### draw_line() Um die Linien zwischen den einzelnen Gattern zu zeigen, wird Unitys Linerenderer Komponente benutzt. Da diese im Worldspace gerendert werden, nicht in der GUI, wird die ViewGUI ebenfalls nicht in der UI Layer gerendert. `draw_line()` nimmt folgende Argumente: - `GameObject parent` - das Objekt, dem der LineRenderer hinzugefügt werden soll - `Vector3 p1` - die Position des ersten Punkt der Linie - `Vector3 p2` - die Position des zweiten Punkt der Linie Die Funktion erstellt dann eine schwarze Linie, die die beiden Punkte verbindet und über allem anderen gerendert wird. ###### LogicGate Die LogicGate klasse wird benutzt, um die Logik der Gatter zu speichern. Mit der Funktion `UpdateOutput()` wird aus den beiden Inputs ein output gemacht und an output weitergegeben. `updateWire` wird ausgeführt, wenn `wireActiv` geändert wird und ändert die Farbe des LineRenderers, der in `wire` gespeichert wird. Die LogicGate-Komponente wird nie durch Skript zu einem Objekt hinzugefügt, sondern ist bereits an den Prefabs der einzelnen Gatter. Dort sind auch alle Variablen, die unabhängig vom Schaltkreis sind gesetzt. ##### Decode Die Buchstabe-Symbol-Paare werden in einem Dictionary gespeichert und von der View-Komponente festgelegt. In `showViewGUI()` werden dann sämtliche Image Komponenten in den Kindern der ViewGUI gesucht. Das erste wird entfernt, da es sich um das Hintergrundbild ändert. Danach werden die Platzhalter Symbole durch die richtigen ersetzt. In `showInputGUI()` werden die keyValuePairs von der View-Komponente geholt. Es werden auch hier die Platzhalter Symbole im GUI-Prefab gesammelt. Dann werden die ersten beiden entfernt, da es sich um das Hintergrundbild und das Bild des Eingabefelds handelt. Es wird außerdem das letzte entfernt, da es sich um das Bild des Buttons handelt. Es wird dann ein zufälliges, 5-stelliges Passwort generiert und die Platzhalter werden durch die entsprechenden Symbole ersetzt. Dem Button wird außerdem `onButtonClick()` als Listener zugewiesen. In `OnButtonClick()` wird überprüft, ob der Text im inputfield dem Passwort entspricht. Da man seine Eingabe nicht direkt sehen kann (nur \*, wie es bei einem echten Passwort währe) wird die Groß- und Kleinschreibung ignoriert. Falls das Passwort falsch ist, werden die Versuche um eins verringert und die GUI wackelt ein wenig, um dem Spieler Feedback über seine falsche Eingabe zu geben. ##### Simon Says Die Sequence von Simon Says wird erst festgelegt, wenn eine der beiden GUIs angezeigt wird. In diesem Fall wird sie allerdings immer von der Input-Komponente erstellt. Das wird über den Command `CmdCreateSequence` erledigt, was sicherstellt, dass sie auf dem Server und den Clients gleich ist. Es werden fünf Integer zwischen 0 und 8 festgelegt, die jeweils für einen der neun Knöpfe stehen. In `showInputGUI()` wird außerdem jedem Knopf `OnButtonClick` mit der entsprechenden Nummer als Listener hinzugefügt. In `showViewGUI()` werden die alle Image-Komponenten in den Kindern der viewGUI gefunden und in Lampen abgespeichert. Auch hier wird die erste Stelle entfernt, da es sich dabei um das Hintergrundbild handelt. In `Update()` wird das Blinken der Lampen durchgeführt. `Update()` wird in jedem Frame des Spiels einmal ausgeführt. Es wird zuerst überprüft, ob die Lampen überhaupt blinken sollten. Das ist der Fall, wenn es sich bei der Komponente um die View-Komponente handelt, die ViewGUI aktiv ist und sie bereits gezeigt wurde. Wenn sie noch nicht gezeigt wurde, ist sie zwar aktiv, ist aber nur ein Prefab. Dann wird lamp_pos benutzt, um zu speichern, in welchem Teil des Blinkzyklus die Lampen gerade sind. Dabei entspricht... - 0 - eine Lampe ist gerade an - 1 - alle Lampen sind gerade aus (man ist zwischen zwei blinkern) - ohne diesen Teil des Zyklus könnte man nicht erkennen, wenn die selbe Lampe zwei Mal direkt hintereinander dran ist. - 2 - alle Lampen sind aus und man ist am Ende der Sequenz - hier müssen die Lampen länger aus sein, damit man erkennt, wo die Sequenz anfängt und aufhört Für jeden Teil dieser Zyklen gibt es eine entsprechende Zeit, für die dieser Teil aktiv sein sollte. - `blink_length` ist die Zeit in Sekunden, die die Lampen an sein sollen - `gap_length` ist die Zeit zwischen zwei blinkern - `extra_gap_length` ist die Zeit zwischen zwei Durchgängen Um diese Zeiten zu überprüfen wird `timeElapsed` benutzt. Am Ende von `Update()` wird `timeElapsed += Time.deltaTime;` ausgeführt. Time.deltaTime ist die Zeit, die vergangen ist, seitdem das letzte Mal `Update()` ausgeführt wurde. Wenn ein Teil des Zyklus vorbei ist, wird `timeElapsed` auf 0 zurückgesetzt. Die Farbe einer aktivierten Lampe wird in `lamp_color` gespeichert und am Anfang auf einen zufälligen RGB-Wert gesetzt. Eine deaktivierte Lampe ist immer weiß. In `OnButtonClick()` wird die entsprechende Nummer des gedrückten Knopfes zur Liste `InputSequence` hinzugefügt. Falls diese die gleiche Länge wie die richtige Sequenz hat, wird sie auf Richtigkeit überprüft. Dazu wird über die beiden Listen iteriert und wenn eine der Stellen ungleich ist, aus der Schleife ausgebrochen. In diesem Fall wackelt die GUI ein wenig (siehe GUIShaker). ##### Zahlenschloss Die PIN vom Zahlenschloss wird in dem Command `CmdGetPin()` erstellt. Hierbei ist nicht sichergestellt, welche der Komponenten die PIN erstellt. Stattdessen überprüft die Funktion, ob der Partner (also die jeweils andere Komponente) bereits eine PIN erstellt hat. In diesem Fall wird sie vom Partner geholt. Andernfalls werden fünf zufällige Ziffern in eine Liste geschrieben. Da es sich bei der Funktion um einen Command handelt, wird die PIN immer auf dem Server erstellt und automatisch auf die Clients übertragen. In `showInputGUI()` wird außerdem dem Knopf die Funktion `confirmPIN()` zugewiesen und das Inputfield eingespeichert. In `showViewGUI()` wird außerdem der entsprechende Text in der GUI auf die PIN gesetzt. Da diese zu dem entsprechenden Zeitpunkt manchmal noch nicht vom Server auf den Client synchronisiert wurde, wird in `Update()` außerdem sichergestellt, dass die PIN richtig gesetzt ist. `confirmPIN()` wird ausgeführt, wenn der Knopf in der InputGUI gedrückt wird und wenn die enter-Taste gedrückt wird, während die InputGUI offen ist. Darin wird einfach die Liste `PIN` in einen String umgewandelt, damit sie mit der Eingabe verglichen werden kann. Wenn etwas Falsches eingegeben wird, wackelt die UI und die versuche werden um eins verringert. ##### Kabel Die Seriennummer für die Input GUI wird als erstes generiert, hierfür wird die Methode `generateProductID()` aufgerufen, welche 6 verschiedene Ziffern von 0-9 aneinanderreiht und zurückgibt. Als nächstes werden für die View GUI die 2 Ziffernpaare für den Anfang und das Ende festgelegt. Hierfür wird jeweils mit einer 50% Chance entschieden, ob diese mit der Seriennummer übereinstimmen sollen oder nicht, falls sie nicht übereinstimmen sollen, werden, wie bei `generateProductID()`, zwei zufällige Ziffern von 0-9 aneinandergehängt. Sollten die Ziffern übereinstimmen wird der Schritt wiederholt bis sie das nicht mehr tun. Wenn das geschehen ist, wird anhand der vorherigen Entscheidungen, ob Anfang und Ende übereinstimmen sollen, die entsprechende Reihenfolge, in der die Ziffern der Seriennummer neu zusammengesetzt werden müssen aus dem Array `digitGroups` ausgewählt. Anhand dieser ausgewählten Reihenfolge werden dann die 3 zweistelligen Zahlen erstellt und synchron mit dem Array `correctSequence`, welches hier den Wert `{0, 1, 2}` hat, sortiert, sodass dieses Array hinterher die richtige Reihenfolge, in der die Kabel laut der Anleitung durchgeschnitten werden müssen, enthält. Nachdem die Notwendigen, der eben generierten Werte, der Instanz an dem `RatselController` mit dem Typ View zugewiesen wurden, beginnt diese mit der Vorbereitung der GUI. Hier wird die Methode `KabelViewGUI.updateTextboxes(string, string)` aufgerufen, welche in den Textboxen der GUI die entsprechenden Werte für Anfang und Ende festlegt. Für die Vorbereitung der anderen GUI wird als erstes die Methode `generateKabels()` aufgerufen, welche eine zufällige Positionierung der Kabel generiert, nach dem diese Positionierung in `kabelSetup` gespeichert wurde, wird `KabelInputGUI.drawWires(int[])` aufgerufen mit `kabelSetup` als einziges Argument. Nachdem die GUI jetzt die richtigen Kabel anzeigt, wird mit Hilfe von `KabelInputGUI.setProductID(string)` die Seriennummer in der GUI gesetzt. Wenn ein Kabel vom Spieler angeklickt wird, wird die Methode `KabelInputGUI.cutWire(int)` mit der Nummer des Kabels als Argument aufgerufen, welche an die `ObservableCollection<int> Maze.sequence`, die Nummer des durch geschnittenen Kabels anhängt. Die wichtige Eigenschaft des Datentyps `ObservableCollection` ist, dass es Event Listener unterstützt, denn bei der Initialisierung wird die Methode `Kabel.OnSequenceChanged` als Event Listener bei `Kabel.sequence` hinzugefügt, sodass sie bei einer Änderung der `ObservableCollection` ausgeführt wird. Die Methode `OnSequenceChanged` überprüft jedes Mal, wenn etwas zu der `ObservableCollection` hinzugefügt wurde, ob sie die Länge 3 hat, und falls ja wird außerdem überprüft, ob die Reihenfolge stimmt. Ist die Reihenfolge Falsch, werden die Spieler zum letzten Checkpoint zurückgesetzt, ist sie richtig wird das an den RatselManager weitergeleitet und es wird sichergestellt, dass die GUI nicht mehr geöffnet werden kann. ##### Tresorschloss Das Tresorschloss ist kein gewöhnliches Rätsel. Es wird nicht vom RatselManager gesetzt und befindet sich in jedem Durchlauf an der gleichen Stelle. Es hat auch keinen Ratselcontroller, weshalb sämtliche Interaktionen im Skript selbst überprüft werden müssen. In `showViewGUI()` werden, nachdem die viewGUI instanziiert wurde, sämtliche Image-Komponenten in den Kinder der UI gefunden und in die Liste `lamps` geschrieben. Es wird die erste entfernt, da es sich dabei um den Hintergrund handelt. Falls noch keine sequence existiert, wird eine neue erstellt. Dazu wird der Command `CmdAddToSequence()` benutzt. Dieser stellt sicher, dass die Elemente auf dem Server hinzugefügt werden, sodass sie auf den Clients synchronisiert werden. In `showInputGUI()` wird die inputGUI instanziiert und das vaultLock GameObject gefunden. Es wird außerdem der Sound geladen, der gespielt werden soll, wenn das Schloss sich dreht. Dieser wird zur Master Gruppe des AudioMixer hinzugefügt, damit er der Lautstärke aus den Einstellungen angepasst wird. Falls noch keine Sequence existiert, wird eine erstellt. Der Command wird aber bei dem Partner ausgeführt, da dieser die Eingaben auf Richtigkeit prüft. Es wird außerdem dem Knopf `onButtonClick()` als Listener hinzugefügt. In `onButtonClick()` wird überprüft, ob die Funktion auf der View-Komponente oder auf der Input-Komponente ausgeführt wird. Auf der Input-Komponente wird das click-Geräusch über den AudioPlayer abgespielt und dann onButtonClick() vom Partner ausgelöst. Das Click-Geräusch wird über den AudioPlayer abgespielt, da es sein kann, dass durch diesen Knopfdruck eine neue Scene geladen wird. Ohne den Audioplayer wäre das Geräusch in diesem Fall nicht zu hören. Auf der View-Komponente wird überprüft, ob die ausgewählte Nummer gleich der Nummer in der Sequenz an der aktiven Stelle ist. Wenn das der Fall ist, wird die aktive Stelle (dargestellt durch `stage`) mit `increaseStage()` um eins erhöht. Andernfalls werden die Versuche um eins verringert und ggf. wird der Alarm ausgelöst. Es wird außerdem das Geräusch für "falsch" abgespielt. `increaseStage` wurde in eine eigene Funktion ausgelagert, da es sich dabei um einen Command handeln muss. Andernfalls wird stage nicht auf den Clients synchronisiert. In `increaseStage` wird außerdem überprüft, ob es drei richtige Eingaben gab. In diesem Fall ist das Spiel gewonnen und die entsprechende Szene wird geladen. Um sicherzugehen, dass die Clients nicht ins Hauptmenü zurückkehren, wird außerdem `gameWon` im EscapeMenuController auf wahr gesetzt. In `Update()` wird die Eingabe im Rätsel kontrolliert. Zuerst wird überprüft, ob der Spieler bereits aktiv ist. Da die variable `playerController` vom aktiven Spieler gesetzt wird, ist sie null solange der Spieler nicht bereit ist. Als nächstes wird überprüft, ob die UI gerade angezeigt wird, also ob das Rätsel gerade aktiv ist. Ist das nicht der Fall, wird überprüft, ob der Spieler versucht mit dem Rätsel zu interagieren. Ist das der Fall, wird es aktiviert. Ist es bereits aktiv, wird überprüft, um welche Komponente es sich handelt. Wenn es sich um die Input-Komponente handelt, wird überprüft, ob es einen Input nach links oder rechts gibt, den es im letzten Frame noch nicht gegeben hat. In diesem Fall wird das vaultLock entsprechend gedreht und die Currentnumber wird erhöht oder verkleinert. Falls die Nummer danach außerhalb der Skala liegt (unter 1 oder über 9) wird sie auf 1 oder 9 zurückgesetzt. Handelt es sich stattdessen um die View-Komponente, wird die momentan Aktive Lampe auf ihre entsprechende Farbe gesetzt. Da die Farbe auch angepasst wird, wenn die UI nicht aktiv ist, wird die Farbe in der Liste `lampColors` gespeichert. Schließlich wird in beiden Fällen überprüft, ob die "escape"-Taste gedrückt wird und die UI wird ggf. geschlossen. Auch wenn die UI nicht aktiv ist, es sich aber um die view-Komponente handelt und diese bereits einmal angezeigt wurde, wird die Farbe der aktiven Lampe in `lampColors` angepasst. Je nachdem, wie weit die `currentNumber` von der gewollten Nummer abweicht, ist die Lampe rot, gelb oder grün. ## Benutzerdokumentation ### Generelle Controlls Im Hauptemenü gibt es eine Auswahl "Tutorial" dort sind die wichtigsten Controlls ebenfalls erklärt. Ansonsten gilt: - Vorwärts: "W" oder Pfeil nach oben - Links: "A" oder Pfeil nach links - Rechts: "D" oder Pfeil nach rechts - Rückwärts: "S" oder Pfeil nach unten - Sprinten: "lshift" - Tür/Rätsel öffnen/interagieren: "E" - Pausenmenü öffnen/Rätsel schließen: "esc" Falls es bei den einzelnen Rätseln Unklarheiten gibt, hier eine kleine Erklärung: ### Kabel Kappen Einer von euch sieht eine Reihe von Kabeln vor sich, der andere einen Entscheidungsbaum. Bei den Kabeln ist außerdem eine Seriennummer abgebildet. Mit der Seriennummer und dem Entscheidungsbaum kann eine bunte Reihe von kreuzen erhalten werden. Dies Farben der Kreuze korrespondieren mit den Farben der Kabel. Mit den Kreuzen und der Seriennummer kann dann jedem Kabel eine zweistellige Zahl zugeordnet werden. Es müssen dann die Kabel mit aufsteigender Zahl durchgeschnitten (angeklickt) werden. Es ist auch eine kleine Erklärung auf der Seite mit dem Entscheidungsbaum zu sehen ### Decode Einer von euch sieht eine Reihe aus fünf Symbolen und ein Eingabefeld, der andere sieht eine Tabelle, die jedem Buchstaben ein Symbol zuordnet. Mithilfe dieser Tabelle könnt ihr die Symbole entschlüsseln und ein Passwort erhalten, dass in das Eingabefeld eingegeben werden muss. ### Simon Says Einer von euch sieht ein 3x3 Feld von Lampen, der andere sieht ein 3x3 Feld von Knöpfen. Die Lampen leuchten in einer bestimmten Reihenfolge auf. In dieser Reihenfolge müssen die entsprechenden Knöpfe gedrückt werden. ### Maze Einer von euch sieht ein Quadrat aus kleinen Feldern und einen Roten Punkt. Der andere sieht ein Labyrinth und einen roten Punkt. Der Spieler, der nicht das Labyrinth sieht, kann seinen Punkt mit "WASD" und den Pfeiltasten bewegen und muss ihn in die rechte untere Ecke bringen, ohne eine der Linien zu überschreiten, die dem anderen Spieler markiert sind. ### Zahlenschloss Einer von euch sieht ein Eingabefeld für den Personal Employee Code von Jon Kelly. Der andere sieht den Text: "Your Personal Employee Code is:" und eine Nummer. Außerdem gibt es eine Notiz "Don't share this Code, Jon!". Gebt den Code in das Eingabefeld ein und drückt enter. ### Schaltkreis Einer von euch sieht eine Reihe aus Knöpfen und Abbildungen der Logischen Gatter: AND, OR, XOR und NOT. Der andere sieht einen Schaltung, die aus diesen Gattern besteht. Drückt die Knöpfe so, dass die Ausgabe am rechten Ende der Schaltung Wahr ist. Die Schalttabellen der Gatter sind folgende: AND | Eingabe A | Eingabe B | Ausgabe | | -------- | -------- | -------- | |False|False|False| |False|True|False| |True|False|False| |True|True|True| OR |Eingabe A|Eingabe B|Ausgabe| | ------- | ------- | ----- | |False|False|False| |False|True|True| |True|False|True| |True|True|True| XOR |Eingabe A|Eingabe B|Ausgabe| | ------- | ------- | ----- | |False|False|False| |False|True|True| |True|False|True| |True|True|False| NOT |Eingabe|Ausgabe| | ----- | ----- | |False|True| |True|False| ### Tresorschloss Einer von euch sieht drei Lampen vor sich. Der zweite sieht einen Drehregler mit den Zahlen 1-9. Der Drehregler kann mit A und D bzw. mit den Pfeiltasten nach links und rechts bedient werden. Die Lampen zeigen an, ob die ausgewählte Zahl richtig ist. Wenn die Lampe grün leuchtet, ist die Zahl richtig, wenn die Zahl eine Stelle von der richtigen Zahl entfernt ist, leuchtet die Lampe gelb. Andernfalls leuchtet sie rot. Findet die richtige Zahl und drückt dann auf "Confirm". Das wiederholt ihr, bis alle drei Zahlen grün leuchten. ### Checkpoints Jedes Mal, wenn ihr ein Rätsel löst, erreicht ihr einen Checkpoint, das bedeutet solltet ihr bei einem Rätsel jetzt zu viele Fehler machen werdet ihr zu diesem Checkpoint zurückgesetzt. So müsst ihr kein Rätsel nochmal machen. Seit aber vorsichtig! Wenn ihr es zu oft falsch macht habt ihr verloren und müsst von vorne anfangen. ## Entwicklungsdokumentation Auf diesem [Trello-Board](https://trello.com/invite/b/rFnEgwwd/58d3f4b52000d41eccd6462a4bc1e3b4/tolles-softwareprojekt) sind Mitschriften aus den Scrum Meetings, verworfene und neu entstandene Ideen, sowie interne Notizen zu finden. Da wir das Board nicht öffentlich stellen wollten, müssen sie sich leider mit einem beliebigem Trello-Account anmelden, um es einzusehen. Falls sie noch keinen Account haben können sie folgende Anmeldedaten benutzen: ``` Email: dha22092@cuoly.com Nutzername: Anon Ymus Passwort: 1nf0rm@t1k ``` Ein Archiv mit sämtlichen Versionen des Projektes ist [hier](https://1drv.ms/u/s!AraPKJeXmISdo3j7JvVQWttMZCv4?e=Mz1oP5) zu finden. In dem ZIP Ordner befindet sich außerdem eine Tabelle mit allen Versionen des Projektes. Diese Dateien waren ursprüngliche nicht zum Teilen gedacht und sind deshalb nicht in formeller Sprache gehalten. ## Plagiatszettel Hiermit bestätigen wir, dass wir sämtliche Inhalte in diesem Projekt selbst erstellt oder (im Falle der meisten Grafiken) die Nutzungsrechte erworben haben. Grafiken wurden von [LimeZu](https://limezu.itch.io/moderninteriors) erstellt. Die Lizenz zur Nutzung haben wir erworben.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully