Skip to content
Snippets Groups Projects
game.js 20.7 KiB
Newer Older
function Game() {
    // Current Sprite Selection from User -- might not be needed
    let currUserSprite;

    // Four numbers that define the transform, i hat and j hat
    const i_x = 1;
    const i_y = 0.5;
    const j_x = -1;
    const j_y = 0.5;

    // Symmetric 2D array to store instances of TileClass
    const tile_arr_size = 12;
    let tileArr = new Array(tile_arr_size).fill(null).map(() => new Array(tile_arr_size).fill(null));
    let playerSprite;
    let playerSprite_curTgc; // the current tgc coordinates that the player is at
    let playerSprite_curTileType; // The tile type that the player is currently sitting on

    // Sprite size
    const w = 64;
    const h = 64;

    // what the current cursor calculation is based off
    let maxWinWidth = 1920;
    let maxWinHeight = 1080;

    // TODO: replace
    let tools = ["Fix Sidewalk", "Add Curb Ramp"];
    let currentToolIndex = 0; // keyPressed() has logic to make sure that index doens't go outisde of tools array bounds

    // Variables for Pose Classifier
    let video;
    let poseNet;
    let pose;
    let skeleton;

    let brain;
    let poseLabel = "";

    let state = 'waiting';
    let targetLabel;

    /*
        0 --> grass block (use for where houses go for now)
        1 --> road tile
        2 --> sidewalk tile
        3 --> sidewalk rotated 180 degrees
        4 --> broken sidewalk
        5 --> broken sidewalk rotated 180 degrees
        6 --> sidewalk corner topLeft (no curb ramp)
        7 --> sidewalk corner botLeft (no curb ramp)
        8 --> sidewalk corner botRight (no curb ramp)
        9 --> sidewalk corner topRight (no curb ramp)
    */

    // Hardcoding tile placement for the first level
    // x and y here are just the grid coordinates
    const startingTileIndex = {
        x: 3,
        y: 0
    };
    const endingTileIndex = {
        x: 3,
        y: 11
    };
    const levelOne = [
        [0, 0, 0, 2, 1, 1, 1, 1, 2, 0, 0, 0],
        [0, 0, 0, 2, 1, 1, 1, 1, 2, 0, 0, 0],
        [0, 0, 0, 2, 1, 1, 1, 1, 2, 0, 0, 0],
        [3, 3, 5, 6, 1, 1, 1, 1, 9, 3, 5, 3],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [3, 5, 3, 7, 1, 1, 1, 1, 8, 3, 3, 3],
        [0, 0, 0, 2, 1, 1, 1, 1, 4, 0, 0, 0],
        [0, 0, 0, 2, 1, 1, 1, 1, 4, 0, 0, 0],
        [0, 0, 0, 2, 1, 1, 1, 1, 2, 0, 0, 0],
    ]; // Currently 10 by 10


    this.setup = function() {
        // createCanvas(maxWinWidth, maxWinHeight);
        new Canvas(maxWinWidth, maxWinHeight);
        imageMode(CENTER);

        // Create our player sprite
        playerSprite = new Sprite();
        playerSprite.width = w;
        playerSprite.height = h;
        playerSprite.image = 'assets/tile_pngs/grass.png';

        playerSprite_curTgc = startingTileIndex; // An object: {x: _, y: _}
        playerSprite.x = toScreenCoords(playerSprite_curTgc).x;
        playerSprite.y = toScreenCoords(playerSprite_curTgc).y;

        /****** CODE FOR THE POSE CLASSIFER! UNCOMMENT TO HAVE THE MODEL RUNNING */
        // video = createCapture(VIDEO);
        // video.hide();
        // poseNet = ml5.poseNet(video, modelLoaded);
        // poseNet.on('pose', gotPoses);

        // let options = {
        // inputs: 34,
        // outputs: 4,
        // task: 'classification',
        // debug: true
        // }
        // brain = ml5.neuralNetwork(options);
        // LOAD PRETRAINED MODEL: Uncomment below to train your own model!
        // const modelInfo = {
        // model: 'model1/model.json',
        // metadata: 'model1/model_meta.json',
        // weights: 'model1/model.weights.bin',
        // };
        // brain.load(modelInfo, brainLoaded);

        // Now loop through our tile map and draw
        // each tile accoridng to our levelOne matrix
        // mapping
        for (let i = 0; i < tile_arr_size; i++) {
            for (let j = 0; j < tile_arr_size; j++) {
                let tile_type = levelOne[i][j];
                let tsc = toScreenCoords({
                    x: i,
                    y: j
                });

                switch (tile_type) {
                    case 0: // grass block (use for where houses go for now)
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "city", "none");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, grassTile, "decoration", "normal");
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 1: // road tile
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "vertical");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, roadTile, "road", "normal");
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 2: // sidewalk tile
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "horizontal");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, sidewalk, "sidewalk", "normal");
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 3: // sidewalk rotated 180 degrees
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "4way");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, sidewalkRotated180, "sidewalk", "rotated180");
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 4: // broken sidewalk
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "4way");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, sidewalkBroken, "broken_sidewalk", "normal");
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 5: // broken sidewalk rotated 180 degrees
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "4way");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, sidewalkBrokenRotated180, "broken_sidewalk", "rotated180");
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 6: // sidewalk corner topLeft (no curb ramp)
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "4way");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, sidewalkCornerTopLeft, "sidewalk_corner", "normal");
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 7: // sidewalk corner botLeft (no curb ramp)
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "4way");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, sidewalkCornerBotLeft, "sidewalk_corner", "rotated90"); // TODO: Replace with rotated versions
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 8: // sidewalk corner botRight (no curb ramp)
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "4way");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, sidewalkCornerBotRight, "sidewalk_corner", "rotated180"); // TODO: Replace with rotated versions
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    case 9: // sidewalk corner topRight (no curb ramp)
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "road", "4way");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, sidewalkCornerTopRight, "sidewalk_corner", "rotated270"); // TODO: Replace with rotated versions
                        tileArr[i][j].display(tileArr[i][j].sprite);
                        break;
                    default:
                        // Otherwise, default to road
                        // grid[i][j] = new GridTile(j * gridSize, i * gridSize, gridSize, "city", "none");
                        tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, roadTile, "road", "normal");
                        tileArr[i][j].display(tileArr[i][j].sprite);
                }
                // tileArr[i][j] = new TileClass(i, j, tsc.x, tsc.y, roadTile);
                // tileArr[i][j].display(tileArr[i][j].sprite);
            }
        }
    }

    this.draw = function() {
        currUserSprite = sidewalk;
        background('#588dbe');

        translate(width / 2, height / 3);

        // For now, use simple text to show which
        // tool you have selected
        // Top-left corner
        // textAlign(LEFT, TOP);
        textSize(16);
        // fill(255);
        // stroke(0);
        // strokeWeight(4);
        text(`Current Tool: ${tools[currentToolIndex]}`, -300, -10);

        // Draw the player in their current pos

        let curPlayerScreenCords = toScreenCoords(playerSprite_curTgc);
        playerSprite.x = curPlayerScreenCords.x;
        playerSprite.y = curPlayerScreenCords.y;

        let tgc = screenToGridCoords({
            x: mouseX,
            y: mouseY
        });

        for (let i = 0; i < tile_arr_size; i++) {
            for (let j = 0; j < tile_arr_size; j++) {
                tileArr[i][j].display(tileArr[i][j].sprite);

                // hover highlighting
                if (tgc.x == i && tgc.y == j && tileArr[i][j].sprite == roadTile) {
                    tileArr[i][j].display(roadHighlightedTile);

                } else {
                    tileArr[i][j].display(tileArr[i][j].sprite);
                }

            }
        }

        /**** For debugging the pose classifier *****/
        // Uncomment for seeing each prediction drawn on the screen
        // push();
        // translate(video.width, 0);
        // scale(-1, 1);

        // // For debugging the pose classifier:
        // if (pose) {
        //   for (let i = 0; i < skeleton.length; i++) {
        //     let a = skeleton[i][0];
        //     let b = skeleton[i][1];
        //     strokeWeight(2);
        //     stroke(0);
        //     line(a.position.x, a.position.y, b.position.x, b.position.y);
        //   }
        //   for (let i = 0; i < pose.keypoints.length; i++) {
        //     let x = pose.keypoints[i].position.x;
        //     let y = pose.keypoints[i].position.y;
        //     fill(0);
        //     stroke(255);
        //     ellipse(x, y, 16, 16);
        //   }
        // }
        // pop();
    }

    this.mouseClicked = function() {
        let {
            x: tgc_x,
            y: tgc_y
        } = screenToGridCoords({
            x: mouseX,
            y: mouseY
        });
        if (!isValidGridCoords(tgc_x, tgc_y)) {
            return;
        }

        /* Type can either be
            - decoration (grass, house, etc)
            - road
            - sidewalk
            - broken_sidewalk
            - sidewalk_corner
            - sidewalk_corner_curbRamp
        */
        /* Orientation can either be
            - normal (rotated 0 degrees)
            - rotated90
            - rotated180
            - rotated270
        */
        // TODO: Make a fixed_sidewalk sprite
        tile_type = tileArr[tgc_x][tgc_y].type;
        tile_orientation = tileArr[tgc_x][tgc_y].orientation;

        // Depending on what type of tile you clicked on,
        // update the sprite accordingly
        if (tile_type === "broken_sidewalk" && tools[currentToolIndex] === "Fix Sidewalk") {
            // If broken sidewalk is clicked, then
            // repair to make fixed sidewalk
            updateTileType(tgc_x, tgc_y, "sidewalk");

            // Check to see this tile's orientation to make sure we
            // place down the right sprite
            if (tile_orientation === "normal") {
                updateTileSprite(tgc_x, tgc_y, sidewalk);
            } else if (tile_orientation === "rotated180") {
                updateTileSprite(tgc_x, tgc_y, sidewalkRotated180);
            }
        } else if (tile_type === "sidewalk_corner" && tools[currentToolIndex] === "Add Curb Ramp") {
            // If sidewalk corner is clicked, then
            // replace with curb ramp
            updateTileType(tgc_x, tgc_y, "sidewalk_corner_curbRamp");

            // Check to see this tile's orientation to make sure we
            // place down the right sprite
            if (tile_orientation === "normal") {
                updateTileSprite(tgc_x, tgc_y, sidewalkCurbRampTopLeft);
            } else if (tile_orientation === "rotated90") {
                updateTileSprite(tgc_x, tgc_y, sidewalkCurbRampBotLeft);
            } else if (tile_orientation === "rotated180") {
                updateTileSprite(tgc_x, tgc_y, sidewalkCurbRampBotRight);
            } else {
                updateTileSprite(tgc_x, tgc_y, sidewalkCurbRampTopRight);
            }
        }
    }



    this.keyPressed = function() {
        if (key === 'r') {
            tileArr = rotateMatrixClockwise(tileArr);
        }

        if (key === 't') {
            // Cycle to the next tool in our tools array (and wrap back around if
            // we have reached the end of our tools array)
            currentToolIndex = (currentToolIndex + 1) % tools.length;

            // TODO: Change the cursor to an image that matches the current tool
            // type can either be predefined image or path to image
            // cursor(type, x, y)
        }
    }

    this.keyReleased = function() {

        // why is the player not showing up?

        // it seems to be a reference issue
        let playerSprite_newTcgCords_x = playerSprite_curTgc.x;
        let playerSprite_newTcgCords_y = playerSprite_curTgc.y;
        if (key === 'w') {
            playerSprite_newTcgCords_y -= 1;
        } else if (key === 's') {
            playerSprite_newTcgCords_y += 1;
        } else if (key === 'a') {
            playerSprite_newTcgCords_x -= 1;
        } else if (key === 'd') {
            playerSprite_newTcgCords_x += 1;
        }

        // Check to make sure that the new coords are valid, if not, then don't
        // allow the player to move to that tile
        let tileType_playerIsOn = tileArr[playerSprite_curTgc.x][playerSprite_curTgc.y].type;
        let tileType_nextTile = tileArr[playerSprite_newTcgCords_x][playerSprite_newTcgCords_y].type;

        // console.log(`\nCurrent tile: ${tileType_playerIsOn}`);
        // console.log(`Next tile: ${tileType_nextTile}`);

        // console.log(`tileType_nextTile !== "broken_sidewalk": ${tileType_nextTile !== "broken_sidewalk"}`);
        // console.log(`tileType_nextTile !== "grass": ${tileType_nextTile !== "grass"}`);
        // console.log(`(tileType_nextTile !== "road" && tileType_playerIsOn !== "sidewalk_corner") : ${(tileType_nextTile !== "road" && tileType_playerIsOn !== "sidewalk_corner") }`);
        // console.log(`(tileType_nextTile !== "sidewalk_corner" && tileType_playerIsOn !== "road"): ${(tileType_nextTile !== "sidewalk_corner" && tileType_playerIsOn !== "road")}`);
        // console.log(`(tileType_nextTile !== "sidewalk" && tileType_playerIsOn !== "road"): ${(tileType_nextTile !== "sidewalk" && tileType_playerIsOn !== "road")}`);


        // check redundancy later here
        if (tileType_nextTile == "road" && tileType_playerIsOn == "sidewalk_corner_curbRamp") {
            playerSprite_curTgc.x = playerSprite_newTcgCords_x;
            playerSprite_curTgc.y = playerSprite_newTcgCords_y;
            return;
        }

        if (tileType_nextTile == "sidewalk_corner_curbRamp" && tileType_playerIsOn == "road") {
            playerSprite_curTgc.x = playerSprite_newTcgCords_x;
            playerSprite_curTgc.y = playerSprite_newTcgCords_y;
            return;
        }

        if (tileType_nextTile == "road" && tileType_playerIsOn == "road") {
            playerSprite_curTgc.x = playerSprite_newTcgCords_x;
            playerSprite_curTgc.y = playerSprite_newTcgCords_y;
            return;
        }

        if (tileType_nextTile == "sidewalk_corner_curbRamp") {
            playerSprite_curTgc.x = playerSprite_newTcgCords_x;
            playerSprite_curTgc.y = playerSprite_newTcgCords_y;
            return;
        }

        // I think we only want to walk on sidwalk no?
        if (tileType_playerIsOn != "road" &&
            (tileType_nextTile == "sidewalk" || tileType_nextTile == "sidewalk_corner")) {
            console.log("The movement has been allowed...");
            playerSprite_curTgc.x = playerSprite_newTcgCords_x;
            playerSprite_curTgc.y = playerSprite_newTcgCords_y;
        }
    }

    function rotateMatrixClockwise(matrix) {
        let rotatedMatrix = new Array(tile_arr_size).fill(null).map(() => new Array(tile_arr_size).fill(null));

        for (let i = 0; i < tile_arr_size; i++) {
            for (let j = 0; j < tile_arr_size; j++) {
                let newX = j;
                let newY = tile_arr_size - 1 - i;
                let tile = matrix[i][j];
                let screenCoord = toScreenCoords({
                    x: newX,
                    y: newY
                });
                rotatedMatrix[newX][newY] = new TileClass(newX, newY, screenCoord.x, screenCoord.y, tile.sprite);
            }
        }
        return rotatedMatrix;
    }

    // Helper function to check if grid coordinates are within bounds
    function isValidGridCoords(tgc_x, tgc_y) {
        return tgc_x >= 0 && tgc_x < tile_arr_size && tgc_y >= 0 && tgc_y < tile_arr_size;
    }

    // Helper function to update tile sprite
    function updateTileSprite(tgc_x, tgc_y, sprite) {
        tileArr[tgc_x][tgc_y].sprite = sprite;
    }

    // Helper function to update tile type
    function updateTileType(tgc_x, tgc_y, type) {
        tileArr[tgc_x][tgc_y].type = type;
    }

    function invertMatrix(a, b, c, d) {
        /*
          Invert a 2d matrix, with a, b, c, and d being
          [ a  b ]
          [ c  d ]
        */
        const det = (1 / (a * d - b * c));

        return {
            a: det * d,
            b: det * -b,
            c: det * -c,
            d: det * a,
        }
    }

    function toGridCoords(screen) {
        const a = i_x * 0.5 * w;
        const b = j_x * 0.5 * w;
        const c = i_y * 0.5 * h;
        const d = j_y * 0.5 * h;

        const inv = invertMatrix(a, b, c, d);

        return {
            // offset mouse cursor to align with block
            x: screen.x * inv.a + screen.y * inv.b - 25.75,
            y: screen.x * inv.c + screen.y * inv.d + 4.15,
        }
    }

    function toScreenCoords(tile) {
        return {
            x: tile.x * i_x * 0.5 * w + tile.y * j_x * 0.5 * w,
            y: tile.x * i_y * 0.5 * h + tile.y * j_y * 0.5 * h,
        }
    }

    // Helper function to convert screen coordinates to grid coordinates
    function screenToGridCoords(screen) {
        let tgc = toGridCoords(screen);
        return {
            x: Math.floor(tgc.x),
            y: Math.floor(tgc.y)
        };
    }

    /****** POSE CLASSIFIER HELPER FUNCTIONS ******/
    function brainLoaded() {
        console.log('pose classification ready!');
        classifyPose();
    }
    function classifyPose() {
        // console.log("We are in classifyPose");
        if (pose) {
        // console.log("We got a pose to classify!");
        // console.log(pose);
        let inputs = [];
        for (let i = 0; i < pose.keypoints.length; i++) {
            let x = pose.keypoints[i].position.x;
            let y = pose.keypoints[i].position.y;
            inputs.push(x);
            inputs.push(y);
        }
        brain.classify(inputs, gotResult);
        } else {
        setTimeout(classifyPose, 100);
        }
    }
    function gotResult(error, results) {
        if (results[0].confidence > 0.75) {
        poseLabel = results[0].label.toUpperCase();
        }
        // console.log("Do we ever call gotResult?");
        console.log(results);
        classifyPose();
    }
    function gotPoses(poses) {
        // console.log("Did we get any poses??");
        // console.log(poses);
        if (poses.length > 0) {
        pose = poses[0].pose;
        skeleton = poses[0].skeleton;
        if (state == 'collecting') {
            let inputs = [];
            for (let i = 0; i < pose.keypoints.length; i++) {
            let x = pose.keypoints[i].position.x;
            let y = pose.keypoints[i].position.y;
            inputs.push(x);
            inputs.push(y);
            }
            let target = [targetLabel];
            brain.addData(inputs, target);
        }
        }
    }
    function modelLoaded() {
        console.log('poseNet ready');
    }