Code viewer for World: Project (clone by Cbum)

// Cloned by Cbum on 5 Dec 2022 from World "Project" by Duarte Martinho 
// Please leave this clone trail here.
 
var scene = new THREE.Scene();

// Audio
const SPEED1  = "/uploads/duarte/swingx1.mp3";
const SPEED2  = "/uploads/duarte/swingx1.25.mp3";
const SPEED3  = "/uploads/duarte/swingx1.5.mp3";
const HONK    = "/uploads/duarte/car-honk.mp3";
const SPEEDUP = "/uploads/duarte/speed-up.mp3";
var speed1  = new Audio(SPEED1);
var speed2  = new Audio(SPEED2);
var speed3  = new Audio(SPEED3);
var honk    = new Audio(HONK);
var speedUp = new Audio(SPEEDUP);

speed1.volume = 0.1;
speed2.volume = 0.1;
speed3.volume = 0.1;
honk.volume = 0.1;
speedUp.volume = 0.1;

const distance = 500;

// Creates a camera
const camera = new THREE.OrthographicCamera( window.innerWidth/-2, window.innerWidth/2, window.innerHeight / 2, window.innerHeight / -2, 0.1, 10000 );
camera.rotation.x = 50*Math.PI/180;
camera.rotation.y = 20*Math.PI/180;
camera.rotation.z = 10*Math.PI/180;

const initialCameraPositionY = -Math.tan(camera.rotation.x)*distance;
const initialCameraPositionX = Math.tan(camera.rotation.y)*Math.sqrt(distance**2 + initialCameraPositionY**2);
camera.position.y = initialCameraPositionY;
camera.position.x = initialCameraPositionX;
camera.position.z = distance;


var score1 = 0;
var hscore1 = 0;
var score2 = 0;
var hscore2 = 0;

const zoom = 2;

const chickenSize = 15;

const positionWidth = 42;
const columns = 17;
const boardWidth = positionWidth*columns;

const stepTime = 50; // Miliseconds it takes for the chicken to take a step forward, backward, left or right

let lanes;
let currentLane;
let currentColumn;
let password = "753";
let previousTimestamp;
let startMoving;
let moves;
let stepStartTimestamp;

const generateLanes = () => [-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9].map((index) => {
  const lane = new Lane(index);
  lane.mesh.position.y = index*positionWidth*zoom;
  scene.add( lane.mesh );
  return lane;
}).filter((lane) => lane.index >= 0);

const addLane = () => {
  const index = lanes.length;
  const lane = new Lane(index);
  lane.mesh.position.y = index*positionWidth*zoom;
  scene.add(lane.mesh);
  lanes.push(lane);
}

var chicken;

const initialDirLightPositionX = -100;
const initialDirLightPositionY = -100;
var d = 500;

const laneTypes = ['car', 'forest'];
const vechicleColors = [0xFFFFFF, 0x000000, 0xFF0000, 0xFFFF00, 0x0000FF, 0x9400D3, 0xFFA500];
const laneSpeeds = [2, 3, 5, 6, 7];
const bushHeights = [15,25,35];
const rocksHeights = [5,7,12];

const initaliseValues = () => {
    
    chicken = new Chicken();
    scene.add( chicken );
    
    // else we have a password, start the socket run with this password 
    hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
    scene.add(hemiLight);
    dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
    dirLight.position.set(initialDirLightPositionX, initialDirLightPositionY, 200);
    dirLight.target = chicken;
    scene.add(dirLight);
    
    
    backLight = new THREE.DirectionalLight(0x000000, .4);
    backLight.position.set(200, 200, 50);
    scene.add(backLight);
    
    lanes = generateLanes()

    currentLane = 0;
    currentColumn = Math.floor(columns/2);

    previousTimestamp = null;

    startMoving = false;
    moves = [];
    stepStartTimestamp;

    chicken.position.x = 0;
    chicken.position.y = 0;

    camera.position.y = initialCameraPositionY;
    camera.position.x = initialCameraPositionX;

    dirLight.position.x = initialDirLightPositionX;
    dirLight.position.y = initialDirLightPositionY;
    
    requestAnimationFrame( animate );
    updateHtml();
    
    speed1.play();
}

var host;
var playerJoined= false; 
var playerLeft = false;
var lockControls = false;
var clock;

function loadResources() {
    AB.runReady = true;
    AB.socketStart (  );
    
    AB.removeSplash();
    if (playerJoined) {
        initaliseValues();
    } else {
        waitingForPlayers();    
    }
}


AB.socketUserlist = function(e) {
    console.log(e);
    if (e.length % 2 === 0 && !playerJoined && host === undefined) {
        // Join game
        host = Math.floor(Math.random() * 999999999);
        console.log(host);
        playerJoined = true;
       
        initaliseValues();
        AB.removeSplash();
        clock = new Clock();
        clock.start();
        
        socketOut("triggerJoin", true, false);
        console.log("Join game");
    } else if (e.length % 2 === 1 && !playerJoined && host === undefined) {
        // Create game
        host = AB.socket.id;
        console.log("Create game");
    }
    
    switch(e.length) {
        default:
            if (void 0 !== host) {
                socketOut("start", false, true);
            }
        }
    
    
    AB.msg("<p>Current number of players = " + e.length + "\n</p>", 2);
}

window.onbeforeunload = function() {
    socketOut("playerLeft", false, true);
}

AB.socketIn = function(element) {
    console.log("TRIGGER SOCKET IN ", element.name, element.data);
    
    if (element.name === "playerLeft" && element.data.host === host && playerJoined) {
        lockControls = true;
        playerLeft = true;
        AB.socket.destroy();
        endGame();
        resetPlayer();
    } else if (element.name === "update" && element.data.host === host && playerJoined) {
        score2 = element.data.score2;
        hscore2 = element.data.hscore2;
        updateHtml();
    } else if (element.name === "triggerJoin" && element.data.joined === true && !playerJoined) {
        playerJoined = true;
        clock = element.data.clock;
        host = element.data.host;
        initaliseValues();
        AB.removeSplash();
    } else if (element.name === "clockUpdate" && element.data.host === host && playerJoined) {
        clock = element.data.clock;
        AB.msg("<p>Time left: "+ clock.currTime +" seconds</p>", 5);
    } else if (element.name === "endGame" && element.data.host === host && playerJoined) {
        score2 = element.data.score2;
        hscore2 = element.data.hscore2;
        lockControls = true;
        AB.socket.destroy();
        endGame();
        resetPlayer();
    }
};


function updateHtml() {
    AB.msg(`<h3> Welcome to our game!</h3>`, 1);
    AB.msg(`<p>Player 1 Score: `+ score1+` | Highest: ` + hscore1 + ` </p>`, 3);
    AB.msg(`<p>Player 2 Score: `+ score2 +` | Highest: ` + hscore2 + ` </p>`, 4); 
}


const renderer = new THREE.WebGLRenderer({
    alpha: true,
    antialias: true
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

function Texture(width, height, rects) {
  const canvas = document.createElement( "canvas" );
  canvas.width = width;
  canvas.height = height;
  const context = canvas.getContext( "2d" );
  context.fillStyle = "#ffffff";
  context.fillRect( 0, 0, width, height );
  context.fillStyle = "rgba(0,0,0,0.6)";  
  rects.forEach(rect => {
    context.fillRect(rect.x, rect.y, rect.w, rect.h);
  });
  return new THREE.CanvasTexture(canvas);
}

function socketOut(_name, _joined, _closed) {
    AB.socketOut({
        name: _name,
        data : {
            joined: _joined,
            closed: _closed,
            host: host,
            clock: clock,
            score2: score1,
            hscore2: hscore1,
        }
    });
}

class Clock {
    constructor () {
        this.currTime = 60
    }
    
    start() {
        var clock = this;
        function _time() {
            clock.currTime--;
            if (clock.currTime < 0) {
                socketOut("endGame", false, false);
                lockControls = true;
                AB.socket.destroy();
                endGame();
                resetPlayer();
                return;
            }
            AB.msg("<p>Time left: "+ clock.currTime +" seconds</p>", 5);

            socketOut("clockUpdate", false, false);
        }
        setInterval(_time, 1000);
    }
}


// =============================================================================================
// =============================================================================================
//                                              PERSON 
// =============================================================================================
// =============================================================================================
function Chicken() {
    const chicken = new THREE.Group();

      const body = new THREE.Mesh(
        new THREE.BoxBufferGeometry( (chickenSize - 1)*zoom, (chickenSize - 1)*zoom, (chickenSize - 1)*zoom ), 
        new THREE.MeshPhongMaterial( { color: 0xF0B8A0 } )
      );
      body.position.z = 10*zoom;
      body.position.z += 15;
      chicken.add(body);
    
      const hair = new THREE.Mesh(
        new THREE.BoxBufferGeometry( (chickenSize - 0.8)*zoom , (chickenSize - 0.8)*zoom, 2*zoom ), 
        new THREE.MeshLambertMaterial( { color: 0x000000 } )
      );
      hair.position.z = 21*zoom;
      hair.position.z += 6;
      chicken.add(hair);
      
      const pants = new THREE.Mesh(
        new THREE.BoxBufferGeometry( chickenSize*zoom , chickenSize*zoom, 20*zoom ), 
        new THREE.MeshLambertMaterial( { color: 0x435F9A } )
      );
      pants.position.z = 10*zoom / 2;
      chicken.add(pants);
      
      return chicken;
}
// =============================================================================================
// =============================================================================================
//                                          END PERSON 
// =============================================================================================
// =============================================================================================



// =============================================================================================
// =============================================================================================
//                                              CAR 
// =============================================================================================
// =============================================================================================
function Wheel() {
  const wheel = new THREE.Mesh( 
    new THREE.BoxBufferGeometry( 12*zoom, 33*zoom, 12*zoom ), 
    new THREE.MeshLambertMaterial( { color: 0x333333 } ) 
  );
  wheel.position.z = 6*zoom;
  return wheel;
}

function Car() {
    const car = new THREE.Group();
    const carFrontTexture = new Texture(40,80,[{x: 0, y: 10, w: 30, h: 60 }]);
    const carBackTexture = new Texture(40,80,[{x: 10, y: 10, w: 30, h: 60 }]);
    const carRightSideTexture = new Texture(110,40,[{x: 10, y: 0, w: 50, h: 30 }, {x: 70, y: 0, w: 30, h: 30 }]);
    const carLeftSideTexture = new Texture(110,40,[{x: 10, y: 10, w: 50, h: 30 }, {x: 70, y: 10, w: 30, h: 30 }]);
    const color = vechicleColors[Math.floor(Math.random() * vechicleColors.length)];
  
    const main = new THREE.Mesh(
        new THREE.BoxBufferGeometry( 60*zoom, 30*zoom, 15*zoom ), 
        new THREE.MeshPhongMaterial( { color} )
    );
      main.position.z = 12*zoom;
      car.add(main)
  
    const cabin = new THREE.Mesh(
        new THREE.BoxBufferGeometry( 33*zoom, 24*zoom, 12*zoom ), 
        [
            new THREE.MeshPhongMaterial( { color: 0xcccccc, map: carBackTexture } ),
            new THREE.MeshPhongMaterial( { color: 0xcccccc, map: carFrontTexture } ),
            new THREE.MeshPhongMaterial( { color: 0xcccccc, map: carRightSideTexture } ),
            new THREE.MeshPhongMaterial( { color: 0xcccccc, map: carLeftSideTexture } ),
            new THREE.MeshPhongMaterial( { color: 0xcccccc, } ), // top
            new THREE.MeshPhongMaterial( { color: 0xcccccc, } ) // bottom
        ]
    );
    cabin.position.x = 6*zoom;
    cabin.position.z = 25.5*zoom;
    car.add( cabin );
  
  const frontWheel = new Wheel();
  frontWheel.position.x = -18*zoom;
  car.add( frontWheel );

  const backWheel = new Wheel();
  backWheel.position.x = 18*zoom;
  car.add( backWheel );

  return car;  
}
// =============================================================================================
// =============================================================================================
//                                              END CAR 
// =============================================================================================
// =============================================================================================



// =============================================================================================
// =============================================================================================
//                                           BUSH
// =============================================================================================
// =============================================================================================
function Bush() {
  const bush = new THREE.Group();

  height = bushHeights[Math.floor(Math.random()*bushHeights.length)];

  const leaf = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 30*zoom, 30*zoom, height*zoom ), 
    new THREE.MeshLambertMaterial( { color: 0x37AE0F,  } )
  );
  leaf.position.z = (height/2)*zoom;
  bush.add(leaf);

  return bush;  
}
// =============================================================================================
// =============================================================================================
//                                          END BUSH
// =============================================================================================
// =============================================================================================



// =============================================================================================
// =============================================================================================
//                                            ROCK
// =============================================================================================
// =============================================================================================
function Rock() {
  const rocks = new THREE.Group();

  height = rocksHeights[Math.floor(Math.random()*bushHeights.length)];

  const rock = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 12*zoom, 12*zoom, height*zoom ), 
    new THREE.MeshLambertMaterial( { color: 0x666a6c,  } )
  );
  rock.position.z = (height/2)*zoom;
  rocks.add(rock);

  return rocks;  
}
// =============================================================================================
// =============================================================================================
//                                           END ROCK
// =============================================================================================
// =============================================================================================



// =============================================================================================
// =============================================================================================
//                                            ROAD
// =============================================================================================
// =============================================================================================
function Road() {
  const road = new THREE.Group();

  const createSection = color => new THREE.Mesh(
    new THREE.PlaneBufferGeometry( boardWidth*zoom, positionWidth*zoom ), 
    new THREE.MeshPhongMaterial( { color } )
  );

  const middle = createSection(0x454A59);
  road.add(middle);

  const left = createSection(0x393D49);
  left.position.x = - boardWidth*zoom;
  road.add(left);

  const right = createSection(0x393D49);
  right.position.x = boardWidth*zoom;
  road.add(right);

  return road;
}
// =============================================================================================
// =============================================================================================
//                                        END ROAD
// =============================================================================================
// =============================================================================================



// =============================================================================================
// =============================================================================================
//                                         GRASS
// =============================================================================================
// =============================================================================================
function Grass() {
  const grass = new THREE.Group();

  const createSection = color => new THREE.Mesh(
    new THREE.BoxBufferGeometry( boardWidth*zoom, positionWidth*zoom, 3*zoom ), 
    new THREE.MeshPhongMaterial( { color } )
  );

  const middle = createSection(0xbaf455);
  middle.receiveShadow = true;
  grass.add(middle);

  const left = createSection(0x99C846);
  left.position.x = - boardWidth*zoom;
  grass.add(left);

  const right = createSection(0x99C846);
  right.position.x = boardWidth*zoom;
  grass.add(right);

  grass.position.z = 1.5*zoom;
  return grass;
}
// =============================================================================================
// =============================================================================================
//                                         END GRASS
// =============================================================================================
// =============================================================================================



// =============================================================================================
// =============================================================================================
//                                          LANE
// =============================================================================================
// =============================================================================================

var onSpeedUp1 = true;
var onSpeedUp2 = true;
function Lane(index) {
  this.index = index;
  this.type = index <= 0 ? 'field' : laneTypes[Math.floor(Math.random()*laneTypes.length)];
  this.hasHit = false;

  switch(this.type) {
    case 'field': {
      this.type = 'field';
      this.mesh = new Grass();
      break;
    }
    case 'forest': {
        this.mesh = new Grass();

        this.occupiedPositions = new Set();
        this.threes = [1,2,3,4].map(() => {
        var obstacle;
        if (Math.random() > 0.7) {
            obstacle = new Bush();
        } else {
            obstacle = new Rock();
        }
        let position;
        do {
          position = Math.floor(Math.random()*columns);
        }while(this.occupiedPositions.has(position))
          this.occupiedPositions.add(position);
        obstacle.position.x = (position*positionWidth+positionWidth/2)*zoom-boardWidth*zoom/2;
        this.mesh.add( obstacle );
        return obstacle;
      })
      break;
    }
    case 'car' : {
        this.mesh = new Road();
        this.direction = Math.random() >= 0.5;

        const occupiedPositions = new Set();
        this.vechicles = [1,2,3].map(() => {
        const vechicle = new Car();
        let position;
        do {
            position = Math.floor(Math.random()*columns/2);
        } while(occupiedPositions.has(position))
            occupiedPositions.add(position);
            vechicle.position.x = (position*positionWidth*2+positionWidth/2)*zoom-boardWidth*zoom/2;
            if(!this.direction) vechicle.rotation.z = Math.PI;
            this.mesh.add( vechicle );
            return vechicle;
        })

        this.speed = laneSpeeds[Math.floor(Math.random()*laneSpeeds.length)];
        if (score1 >= 29) {
            this.speed *= 1.7;
        } else if (score1 >= 9) {
            this.speed *= 1.3;
        }
      
        break;
    }
  }
}


window.addEventListener("keyup", event => {
    if (lockControls) return;
  if (event.keyCode == '38' || event.keyCode == '87' ) {
    // up arrow
    move('forward');
  }
  else if (event.keyCode == '40' || event.keyCode == '83' ) {
    // down arrow
    move('backward');
  }
  else if (event.keyCode == '37' || event.keyCode == '65' ) {
    // left arrow
    move('left');
  }
  else if (event.keyCode == '39' || event.keyCode == '68' ) {
    // right arrow
    move('right');
  }
});

function move(direction) {
  const finalPositions = moves.reduce((position,move) => {
    if(move === 'forward') return {lane: position.lane+1, column: position.column};
    if(move === 'backward') return {lane: position.lane-1, column: position.column};
    if(move === 'left') return {lane: position.lane, column: position.column-1};
    if(move === 'right') return {lane: position.lane, column: position.column+1};
  }, {lane: currentLane, column: currentColumn})
    
    
  if (direction === 'forward') {
    if(lanes[finalPositions.lane+1].type === 'forest' && lanes[finalPositions.lane+1].occupiedPositions.has(finalPositions.column)) return;
    if(!stepStartTimestamp) {
        startMoving = true;
        
    }
    addLane();
  }
  else if (direction === 'backward') {
    if(finalPositions.lane === 0) return;
    if(lanes[finalPositions.lane-1].type === 'forest' && lanes[finalPositions.lane-1].occupiedPositions.has(finalPositions.column)) return;
        if(!stepStartTimestamp) {
            startMoving = true;
        }
    }
    else if (direction === 'left') {
        if(finalPositions.column === 0) return;
        if(lanes[finalPositions.lane].type === 'forest' && lanes[finalPositions.lane].occupiedPositions.has(finalPositions.column-1)) return;
        if(!stepStartTimestamp) startMoving = true;
    }
    else if (direction === 'right') {
        if(finalPositions.column === columns - 1 ) return;
        if(lanes[finalPositions.lane].type === 'forest' && lanes[finalPositions.lane].occupiedPositions.has(finalPositions.column+1)) return;
        if(!stepStartTimestamp) startMoving = true;
    }
    moves.push(direction);
}

function animate(timestamp) {
  requestAnimationFrame( animate );

  if(!previousTimestamp) previousTimestamp = timestamp;
  const delta = timestamp - previousTimestamp;
  previousTimestamp = timestamp;

  // Animate cars and trucks moving on the lane
  lanes.forEach(lane => {
    if(lane.type === 'car') {
      const aBitBeforeTheBeginingOfLane = -boardWidth*zoom/2 - positionWidth*2*zoom;
      const aBitAfterTheEndOFLane = boardWidth*zoom/2 + positionWidth*2*zoom;
      lane.vechicles.forEach(vechicle => {
        if(lane.direction) {
          vechicle.position.x = vechicle.position.x < aBitBeforeTheBeginingOfLane ? aBitAfterTheEndOFLane : vechicle.position.x -= lane.speed/16*delta;
        }else{
          vechicle.position.x = vechicle.position.x > aBitAfterTheEndOFLane ? aBitBeforeTheBeginingOfLane : vechicle.position.x += lane.speed/16*delta;
        }
      });
    }
  });

  if(startMoving) {
    stepStartTimestamp = timestamp;
    startMoving = false;
  }

  if(stepStartTimestamp) {
    const moveDeltaTime = timestamp - stepStartTimestamp;
    const moveDeltaDistance = Math.min(moveDeltaTime/stepTime,1)*positionWidth*zoom;
    switch(moves[0]) {
      case 'forward': {
        const positionY = currentLane*positionWidth*zoom + moveDeltaDistance;
        camera.position.y = initialCameraPositionY + positionY; 
        dirLight.position.y = initialDirLightPositionY + positionY; 
        chicken.position.y = positionY;
        break;
      }
      case 'backward': {
        positionY = currentLane*positionWidth*zoom - moveDeltaDistance
        camera.position.y = initialCameraPositionY + positionY;
        dirLight.position.y = initialDirLightPositionY + positionY; 
        chicken.position.y = positionY;
        break;
      }
      case 'left': {
        const positionX = (currentColumn*positionWidth+positionWidth/2)*zoom -boardWidth*zoom/2 - moveDeltaDistance;
        camera.position.x = initialCameraPositionX + positionX;     
        dirLight.position.x = initialDirLightPositionX + positionX; 
        chicken.position.x = positionX; // initial chicken position is 0
        break;
      }
      case 'right': {
        const positionX = (currentColumn*positionWidth+positionWidth/2)*zoom -boardWidth*zoom/2 + moveDeltaDistance;
        camera.position.x = initialCameraPositionX + positionX;       
        dirLight.position.x = initialDirLightPositionX + positionX;
        chicken.position.x = positionX; 
        break;
      }
    }
    // Once a step has ended
    if(moveDeltaTime > stepTime) {
        switch(moves[0]) {
            case 'forward': {
                if (lanes[currentLane].type === 'car') {
                    score1++;
                    // console.log("FOWARDs in car lane");
                }
                currentLane++;
                break;
            }
            case 'backward': {
                currentLane--;
                if (lanes[currentLane].type === 'car') {
                    score1--;
                    // console.log("backwards in car lane");
                }
                break;
            }
            case 'left': {
                currentColumn--;
                break;
            }
            case 'right': {
                currentColumn++;
                break;
            }
        }
        if (hscore1 < score1) hscore1 = score1;
        if (score1 > 9 ) {
            // AB.socket.destroy();
            // endGame();
        }
        
        if (score1 >= 30) {
            speed2.pause();
            if (onSpeedUp2) {
                speedUp.play();
                onSpeedUp2 = false;
            }
            speed3.play();
        } else if (score1 >= 10) {
            speed1.pause();
            if (onSpeedUp1) {
                speedUp.play();
                onSpeedUp1 = false;
            }
            speed2.play();
        }
        socketOut("update", false, false);
        updateHtml();
        
      moves.shift();
      // If more steps are to be taken then restart counter otherwise stop stepping
      stepStartTimestamp = moves.length === 0 ? null : timestamp;
    }
  }

  // Hit test for lanes
  if(lanes[currentLane].type === 'car') {
    const chickenMinX = chicken.position.x - chickenSize*zoom/2;
    const chickenMaxX = chicken.position.x + chickenSize*zoom/2;
    const vechicleLength = { car: 60, truck: 105}[lanes[currentLane].type]; 
    lanes[currentLane].vechicles.forEach(vechicle => {
      const carMinX = vechicle.position.x - vechicleLength*zoom/2;
      const carMaxX = vechicle.position.x + vechicleLength*zoom/2;
      if(chickenMaxX > carMinX && chickenMinX < carMaxX) {
        if (hscore1 < score1) hscore1 = score1;
            speed1.pause();
            speed2.pause();
            speed3.pause();
            onSpeedUp1 = true;
            onSpeedUp2 = true;
            score1 = 0;
            resetPlayer();
            socketOut("update", false, false);
        }
    });
  }
  renderer.render( scene, camera );	
}

function resetPlayer() {
    currentLane = 0;
    currentColumn = Math.floor(columns/2);

    previousTimestamp = null;

    startMoving = false;
    moves = [];
    stepStartTimestamp;

    chicken.position.x = 0;
    chicken.position.y = 0;

    camera.position.y = initialCameraPositionY;
    camera.position.x = initialCameraPositionX;

    dirLight.position.x = initialDirLightPositionX;
    dirLight.position.y = initialDirLightPositionY;
    
    updateHtml();
    speed1.play();
}

function endGame() {
    AB.removeSplash();
    AB.newSplash();

    var txt;
    var other = "";
    if (playerLeft) {
        // Other player left
        txt = "Other Player has left.<br><br><span style='color: rgb(0, 255, 0);'>You Won!</span>";
    } else if (hscore1 > hscore2) {
        // You Win
        txt = "<span style='color: rgb(0, 255, 0);'>You Won!</span>";
        other = `Your opponent's highscore was: `+ hscore2 +` <br>`;
    } else if (hscore2 > hscore1) {
        // Other Player Wins
        txt = "<span style='color: rgb(255, 0, 0);'>You Lost!</span>";
        other = `Your opponent's highscore was: `+ hscore2 +` <br>`;
    } else {
        // Draw
        txt = "It's a draw!";
        other = `Your opponent's highscore was: `+ hscore2 +` <br>`;
    }
    
      
    AB.splashHtml (  ` 
    <h1> GAME ENDED </h1> <br>  
    <h3>` + txt +`<br></h3>
    Your highest score was: ` + hscore1 + `<br>
    `+ other +`<br>
    <button onclick='location.reload();'  class=ab-largenormbutton >Play Again</button>` );  
    
    AB.runReady = false

}

function waitingForPlayers() {
    AB.removeSplash();
    AB.newSplash();                   
      
    AB.splashHtml (  ` 
    <h1> WAITING FOR THE OTHER PLAYER </h1>  
    A version of Websocket boxes that only certain users can join.  <br>
    You enter a password. You only join with users who enter the same password (not with all users of the World).  <br>
    The first user to run the World can enter any password! But other users must know what password they will use.
    ` );  
    
    AB.runReady = false

}

function welcome() {
    var  pwd =  jQuery("input#p").val();
	pwd = pwd.trim();
	
	if ( pwd != password )
	{
	    $("#errordiv").html( "<font color=red> <B> LEAVE NOW!! </b></font> " );
	    return;
	}
    AB.removeSplash();
    AB.newSplash();                   
      
    AB.splashHtml (  ` 
    <h1> Password only Websocket boxes </h1>  
    A version of Websocket boxes that only certain users can join.  <br>
    You enter a password. You only join with users who enter the same password (not with all users of the World).  <br>
    The first user to run the World can enter any password! But other users must know what password they will use.
    <p>
    <button onclick='loadResources();'  class=ab-largenormbutton >Start</button>
    <p>  
    <div id=errordiv name=errordiv> </div> ` );  
    
    AB.runReady = false
}


function splashPass() {
    AB.removeSplash();
    AB.newSplash();                   
      
    AB.splashHtml (  ` 
    <input    style='width:25vw;'    maxlength='2000'   NAME="p"    id="p"       VALUE='' >
    <button onclick='welcome();'  class=ab-largenormbutton >Start</button>
    <p>  
    <div id=errordiv name=errordiv> </div> ` );  
    
    AB.runReady = false
}

splashPass();