What is my JS Checkout About?

As a game dev, my JS checkout is a game, which I call dark hallways. After Mr. Mort’s statement on slack, I first, as a joke, and then actually thought about the duality of objects, such as hallways. How they can be ween in both an symbol of horror and a symbol of light. I decided to buy into this.

Breakdown of this Notebook

  • Start out with the HTML styling (split screen, buttons, etc.)
  • Talk about the logic (how button presses and functions are set, how they are managed)
  • Talk about use of javascript events and local storage for leaderboard purposes
  • Show leaderboard

CSS and Styling

%%html

<!-- escape.scss (menu, leaderboard, etc.)  -->
<style>
// This defines a 3x3 grid 
$grid-template-columns: repeat(3, 1fr);
$grid-template-rows: repeat(3, 1fr);

.container {
  display:grid;
  grid-template-rows:auto 1fr auto;
  grid-template-columns:100%;

  /* fallback height */
  min-height:100vh;
  /* new small viewport height for modern browsers */
  min-height:100svh;
}

.menu{
  background-color: white;
  text-align: center;
  top: 100%;
}

.image {
  display: block;
  margin-left: auto;
  margin-right: auto;
  width: 50%;
}

.button{
  border-width: 3px;
  font-family:'Times New Roman', Times, serif;
  width: 100px;
  height: 30px;

}

#footer{
  text-align: center;
  color: purple;
}

// ... define other grid item classes as needed


/* Split the screen in half */
.splitty {
  height: 100%;
  width: 50%;
  position: fixed;
  z-index: 1;
  top: 0;
  overflow-x: hidden;
  padding-top: 20px;
}

.left {
  left: 0;
  background-color: #111;
}

.right {
  right: 0;
  background-color: red;
}
</style>
%%html


<!-- center.scss (game)  -->
<style>
/* Split the screen in half */
.split {
    height: 100%;
    position: fixed;
    z-index: 1;
    top: 0;
    overflow-x: hidden;
    padding-top: 20px;
  }
  
  /* Control the left side */
  .left {
    left: 0;
    width: 60%;
    background-color: black;
  }
  
  /* Control the right side */
  .right {
    right: 0;
    width: 40%;
    background-color: rgba(243, 230, 230, 0.176);
  }
  
  /* If you want the content centered horizontally and vertically */
  .centered {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
  }
  
  /* Style the image inside the centered container, if needed */
  .centered img {
    width: 450px;
    height: 550px;
  }
  .menu {
    position: fixed;
    bottom: -300px;
    right: -280px;
    width: 500px; 
    align-items: center;
    }.button {
    background-color: rgba(255, 255, 255, 0.441);
    width: 100px;
    height: 75px;
    font-size: large;
    font-weight: bold;
    font-family: 'Times New Roman', Times, serif;
    color: white;
    }

    .grid-container {
        display: grid;
        position: fixed;
        grid-template-columns: auto auto auto;
        background-color: #2195f36a;
        padding: 10px;
        bottom: 25px;
        right: -80px;
      }
      
      .grid-item {
        background-color: rgba(255, 255, 255, 0.8);
        border: 1px solid rgba(255, 255, 255, 0.8);
        padding: 20px;
        font-size: 30px;
        flex: 25%;
        text-align: center;
      }
</style>

Input/Output and Logic and use of localStorage

%%html

<html>

    <head>
        <title> Dark Hallways </title>

        <meta charset="utf-8">
        <link rel="stylesheet" href="center.scss">
        </script>
    </head>

    <script>
        /*
        Player Variables
        */
       let armor = 0;
       let damage = 0;



        var local = "0";

        /*
        Logic for movement depending on locality (shorthand for where the player is)
        */
        var all = {
            "0": {
                "script": "You approach a dark hallway. <br> There appears to be something on the ground. <br> Pick it up?",
                "image": "hallway1.jpeg",
                "choices": ["Yes", "No"],
                "results" : {"Yes" : addToInv, "No":moveTo, "Yesfunc" : ["pistol", "1"], "Nofunc" : "1"}
            },
            "1": {
                "script": "There appears to be something in the distance. <br> The hallway spans further. <br> Whatever is there appears to be approaching.",
                "image": "hallway2.jpeg",
                "choices": ["Approach", "Hide"],
                "results" : {"Approach" : moveTo, "Hide":moveTo, "Approachfunc" : "3", "Hidefunc" : "2"}
            },
            "2": {
                "script": "You sit behind a couch, waiting for whatever is there to disappear <br> It is possible to sneak <br> Do we sneak or stay?",
                "image": "hallway2.jpeg",
                "choices": ["Sneak", "Stay"],
                "results" : {"Sneak" : moveTo, "Stay":moveTo, "Sneakfunc" : "6", "Stayfunc" : "2"}
            },
            "3": {
                "script": "You approach the thing... <br> And it attacks! <br> Do we fight or run?",
                "image": "hallway3.png",
                "choices": ["Fight", "Run"],
                "results" : {"Fight" : specialMove, "Run":addToInv, "Fightfunc" : [4, 5, 'damage', 0], "Runfunc" : ["Tendril", 1]}
            },
            "4": {
                "script": "You fight furiously... <br> But you are too weak <br> You fall to the maw of the monster",
                "image": "hallway3.png",
                "choices": ["End", "Game"],
                "results" : {"End" : leave, "Game":leave, "Endfunc" : "monster", "Gamefunc" : "monster"}
            },
            "5": {
                "script": "You fight furiously ... <br> and you destroy the monster! <br> You see a door beyond. Will you walk through?",
                "image": "hallway3.png",
                "choices": ["Go Through", "Stay"],
                "results" : {"Go Through" : moveTo, "Stay":moveTo, "Go Throughfunc" : "9", "Stayfunc" : "8"}
            },
            "6": {
                "script": "You sneak by the monster <br> and behind it you see a door. Do we stay or do we go approach the door?",
                "image": "hallway3.png",
                "choices": ["Go!", "Stay"],
                "results" : {"Go!" : moveTo, "Stay":moveTo, "Go!func" : "3", "Stayfunc" : "7"}
            },
            "7": {
                "script": "You stay inside <br> but you are lost within the chaos of the labyrinth <br> You fall in the winding labyrinth",
                "image": "hallway4.jpeg",
                "choices": ["End", "Game"],
                "results" : {"End" : leave, "Game":leave, "Endfunc" : "stay", "Gamefunc" : "stay"}
            },
            "8": {
                "script": "Your raucous fight opens a door in the damp floor <br> unable to keep up your balance, you fall into the ground <br> and fall into the infinite chasm",
                "image": "hallway5.jpg",
                "choices": ["End", "Game"],
                "results" : {"End" : leave, "Game":leave, "Endfunc" : "hole", "Gamefunc" : "hole"}
            },
            "9": {
                "script": "You escape through the door into the light. <br> The air feels good, and the darkness fades <br> You escape!",
                "image": "hallway6.jpg",
                "choices": ["End", "Game"],
                "results" : {"End" : victory, "Game":victory, "Endfunc" : "fighting", "Gamefunc" : "fighting"}
            }
            

        };

        //TODO: Finish the sneak and the stay/go through (completion page)
        // Then finish up backend and leaderboard
        // Completed: sneak, ending of staying
        
        var playerStats = {
            "armor" : 0,
            "damage" : 0,
            "health" : 0,
            "equipped" : {
                "damage": "",
                "armor" : ""
            }
        }
        var inventory = {
            
        }

        var assets = {
            "pistol" : ["damage", 1],
            "vest" : ["armor", 2],
            "tendril" : ["damage", 2]
        }

        function formatStuff(location) {
            local = location;
            console.log("Locality: " + local);
            let point = all[location];
            document.getElementById("hallwayIMG").src = "images/" + point["image"];
            document.getElementById("C1").innerHTML = point["choices"][0];
            document.getElementById("C2").innerHTML = point["choices"][1];
            document.getElementById("script").innerHTML = point["script"];
        }

        function equip(item){
            inventory[item] = !inventory[item];
            if (inventory[item]){
                document.getElementById("err").innerHTML = item + " equipped!"
                setTimeout(function(){document.getElementById("err").innerHTML = ""},1000);
                playerStats[assets[item][0]] += assets[item][1];
                playerStats["equipped"][assets[item][0]] = item;
            }
            else {
                document.getElementById("err").innerHTML = item + " unequipped."
                setTimeout(function(){document.getElementById("err").innerHTML = ""},1000);
                playerStats[assets[item][0]] -= assets[item][1];
            }
        }

        function addToInv(stuff) {
            let name = stuff[0];
            let location = stuff[1];
            let elems = document.getElementsByName("slot");
            let ocured = false;
            for(let i = 0; i < elems.length; i++){
                slot = elems[i];
                if(slot.innerHTML === ""){
                    var b = document.createElement("button");
                    b.innerHTML = name
                    b.id = name
                    b.addEventListener("click", function(event){
                        equip(name)
                    })

                    document.getElementById(slot.id).appendChild(b);
                    ocured = true;
                    document.getElementById("err").innerHTML = "You picked up a " + name
                    setTimeout(function(){document.getElementById("err").innerHTML = ""},1000);
                    break
                }
            }
            if (!ocured){
                document.getElementById("err").innerHTML = "Inventory Full!"
                setTimeout(function(){document.getElementById("err").innerHTML = ""},1000);
                
            }
           moveTo(location);
        }

        function moveTo(location){
            console.log("Location Passed in " + location)
            formatStuff(location);
        }

        function specialMove(information){//)loc1, loc2, condition, parameter){
            console.log(information)
            console.log(playerStats[information[2]] > information[3])
            if(playerStats[information[2]] > information[3]){
                moveTo(information[1])
            }
            else {
                moveTo(information[0])
            }
        }

        function manageFunctions(value){
            console.log(local)
            console.log("Stuff: " +  document.getElementById('C2').innerHTML);
            let func = all[local]["results"];
            console.log("value: " + value);
            console.log(all[local]["results"])
            func[value](func[value+"func"]);
        }

        function leave(cause){
            localStorage.setItem("causeOfDeath", cause);
            setTimeout(() => {
                window.location = 'https://nvarap.github.io/gameover.html'
            }, 500);
           
        }

        function victory(win){
            localStorage.setItem("causeOfVictory", win);
            setTimeout(() => {
                window.location = 'https://nvarap.github.io/victory.html'
            }, 500);
        }

        window.onload = (event) => {
            formatStuff(local);
        };


    </script>

    <body style="position: relative; background-color: black;">
        <div class="split left">
            <div class="centered">
                <img src="images/hallway1.jpeg" alt="Hallway1" id="hallwayIMG">
                <div style="color: white;">
                    <p id="script"> </p>
                </div>
            </div>
        </div>

        <div class="split right">
            <div class="centered">
                <h1 style="position: fixed; bottom: 270px; right: -80px; color: white;"> Inventory </h1>
                <div class="grid-container">
                    <div id="slot1" name="slot" class="grid-item"></div>
                    <div id="slot2" name="slot" class="grid-item"></div>
                    <div id="slot3" name="slot" class="grid-item"></div>
                    <div id="slot4" name="slot" class="grid-item"></div>
                    <div id="slot5"name="slot" class="grid-item"></div>
                    <div id="slot6" name="slot" class="grid-item"></div>
                    <div id="slot7" name="slot" class="grid-item"></div>
                    <div id="slot8" name="slot" class="grid-item"></div>
                    <div id="slot9" name="slot" class="grid-item"></div>
                    <div id="slot10" name="slot" class="grid-item"></div>
                    <div id="slot11" name="slot" class="grid-item"></div>
                    <div id="slot12" name="slot" class="grid-item"></div>
                    <div id="slot13" name="slot" class="grid-item"></div>
                    <div id="slot14" name="slot" class="grid-item"></div>
                    <div id="slot15" name="slot" class="grid-item"></div>
                    

                </div>
                <p id="err" style="position: fixed; top: 10px; right: -35px; color: white;"></p>
                <div class="menu">
                    <button onclick="manageFunctions(document.getElementById('C1').innerHTML)" id="C1" data-inline="true" class="button" style="margin: 0px"> </button>
                    <button onclick="manageFunctions(document.getElementById('C2').innerHTML)" id="C2" data-inline="true" class="button" style="margin: 50px"> </button>
                </div>
            </div>
        </div>
        </div>
    </body>

    </html>

Leaderboard

backend

from flask import Blueprint, request, jsonify  # jsonify creates an endpoint response object
from flask_restful import Api, Resource # used for REST API building

app_api = Blueprint('api', __name__,
                   url_prefix='/api/points')

# API generator https://flask-restful.readthedocs.io/en/latest/api.html#id1
api = Api(app_api)


class UsrAPI:
    local_dic = [] # Stores all the users and IDs

    def newScore(front, back, diction):
        user_id = str(len(diction))
        diction.append({'name':front, "points":back})

    class _Create(Resource):
        def post(self): # simply creates the endpoint, dne otherwise
            body = request.get_json()
            UsrAPI.newScore(body.get("name"), body.get("score"), UsrAPI.local_dic)
            return {"message" : "new person added"}
        

    class _Read(Resource):
        def get(self):
            return jsonify(sorted(UsrAPI.local_dic,key=lambda x: x["points"], reverse=True)) 
            # init wikipedia by default

    
    # getJoke(id)
   # class _ReadWithName(Resource): # read when url have name query satisfied
    #    def get(self, name):
     #       return jsonify()# otherwise check with name

    # getRandomJoke()
    #class _ReadRandom(Resource):
     #   def get(self):
      #      return jsonify() # this exists for some reason
    

    # building RESTapi resources/interfaces, these routes are added to Web Server
    api.add_resource(_Create, '/create')
    api.add_resource(_Read, '/')
   

Frontend

%%js
   // fetching

      fetch('http://127.0.0.1:8086/api/points/')
         .then(response => {
            if (!response.ok) {
               throw new Error('Network response was not ok');
            }
            return response.json();
         })
         .then(data => {
            let leaderboard = document.getElementById("leaderboard");
            // Process the response data
            console.log(data);
            let i = 1;
            data.forEach(element => {
               let d = document.createElement("div");
               d.style = "color: white; width: 500px";
               d.innerHTML = i + ". | " + element["name"] + " | " + element["points"];
               leaderboard.appendChild(d);
               i ++;
            });
         })
         .catch(error => {
            // Handle any errors that occurred during the request
            console.error('Error:', error);
         });

%%js

// posting

  var but = document.getElementById("add");
        but.addEventListener("click", () => {
            fetch('http://127.0.0.1:8086/api/points/create', {
                method: 'POST',
                mode: "cors",
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ "name" : localStorage.getItem("name"), "score":Math.floor(100-Math.random()*100)})
            })
                .then(response => {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then(data => {
                    // Process the response data
                    console.log(data);
                    localStorage.clear()
                    setTimeout(() => {
                        window.location = 'https://nvarap.github.io/index.html'
                    }, 500);
                })
                .catch(error => {
                    // Handle any errors that occurred during the request
                    console.error('Error:', error);
                });

        })