Code viewer for World: Improved Pong
// Cloned by Liam Eguia on 13 Aug 2023 from World 'Enhanced Space Pong' by Enhanced

// Cloned by Enhanced on 18 Jun 2018 from World 'Revamped Space Pong' by SinfulSalad

// Cloned by SinfulSalad on 12 Jun 2018 from World 'Space Pong' by Igor Strelkov

//-------------------------- TWEAKER'S BOX ------------------------------------

const UNIT              = 1;
const MAP_WIDTH         = 100; 
const MAP_DEPTH         = 100;
const BALL_RADIUS       = 1.5;
const PADDLE_WIDTH      = 17.5;

const PLAYER_COLOUR     = 0x00bbff;
const OPPONENT_COLOUR   = 0xdd0022;

const START_SPEED       = 0.8;
const SPEED_BONUS       = 0.05;
const SCORE_NEEDED      = 5;

//----------------------------RENDERER SETUP-----------------------------------

AB.newDiv('app');
const container = document.getElementById('app');

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);

//-----------------------------CAMERA SETUP------------------------------------

const camera = new THREE.PerspectiveCamera(
  75, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.set(0, MAP_DEPTH*2/3, -MAP_DEPTH*2/3);
camera.lookAt(new THREE.Vector3(0, 0, -15))

//-----------------------------SCENE SETUP-------------------------------------

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x2b2b2d);

const ambientLight = new THREE.HemisphereLight(0xffffed, 0xb8b8a7, 0.25);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffed, 2.5);
directionalLight.position.set(-1, 1, -1);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.darkness = 1;
scene.add(directionalLight);

//------------------------------EDGE SETUP-------------------------------------

const edgeGeometry = new THREE.BoxGeometry(1, 5, MAP_DEPTH);
const edgeMaterial = new THREE.MeshPhongMaterial({
  color: 0x28282b
});

const leftEdge = new THREE.Mesh(edgeGeometry, edgeMaterial);
scene.add(leftEdge);

const rightEdge = new THREE.Mesh(edgeGeometry, edgeMaterial);
scene.add(rightEdge);

leftEdge.position.set(-MAP_WIDTH/2, 0, 0);
rightEdge.position.set(MAP_WIDTH/2, 0, 0);

const goalEdgeGeometry = new THREE.BoxGeometry(MAP_WIDTH, 5, 0.5);
const goalEdgeMaterial = new THREE.MeshBasicMaterial({
  color: 0xfcff54,
  transparent: true,
  opacity: 0.5
});

const playerGoalEdge = new THREE.Mesh(goalEdgeGeometry, goalEdgeMaterial);
scene.add(playerGoalEdge);

const opponentGoalEdge = new THREE.Mesh(goalEdgeGeometry, goalEdgeMaterial);
scene.add(opponentGoalEdge);

playerGoalEdge.position.set(0, 0, -MAP_DEPTH/2 - UNIT);
opponentGoalEdge.position.set(0, 0, MAP_DEPTH/2 + UNIT);

//------------------------------BALL SETUP-------------------------------------

const ballGeometry = new THREE.SphereGeometry(BALL_RADIUS);
const ballMaterial = new THREE.MeshPhongMaterial({
  color: 0xddf5ff
});

const ball = new THREE.Mesh(ballGeometry, ballMaterial);
scene.add(ball)

ball.tick = function() {
  const newX = this.position.x + this.velocity * Math.sin(this.direction);
  const newZ = this.position.z + this.velocity * Math.cos(this.direction);

  console.log('Ball newX:', newX)

  if (newX > MAP_WIDTH/2-UNIT || newX < UNIT-MAP_WIDTH/2) { // collision with side edge
    this.direction = -this.direction;
  }
  else if (newZ > MAP_DEPTH/2-UNIT*2) {
    this.velocity += SPEED_BONUS;
    this.direction = -Math.PI-this.direction;
    this.checkCollision(opponent);
  }
  else if (newZ < UNIT*2-MAP_DEPTH/2) {
    this.velocity += SPEED_BONUS;
    this.direction = Math.PI-this.direction;
    this.checkCollision(player);
  }

  this.position.x += this.velocity * Math.sin(this.direction);
  this.position.z += this.velocity * Math.cos(this.direction);
}

ball.checkCollision = function(paddle) {
  console.log('Checking collision with:', paddle.name)
  console.log('Paddle position:', paddle.position)
  console.log('Ball position:', this.position)

  if (this.position.x >= paddle.position.x + PADDLE_WIDTH*2/3 || this.position.x <= paddle.position.x - PADDLE_WIDTH*2/3) {
    paddle.score += -1;
    resetPoint();
  }
  else if (this.position.x > paddle.position.x + PADDLE_WIDTH/4) {
    this.direction += Math.PI/8;
  }
  else if (this.position.x < paddle.position.x - PADDLE_WIDTH/4) {
    this.direction -= Math.PI/8;
  }
  else if (this.position.x > paddle.position.x + PADDLE_WIDTH/8) {
    this.direction += Math.PI/12;
  }
  else if (this.position.x < paddle.position.x - PADDLE_WIDTH/8) {
    this.direction -= Math.PI/12;
  }
  else {
    this.direction += (Math.random() * 2 - 1) * Math.PI/12
  }
}

ball.reset = function() {
  this.position.set(0, 0, 0);
  this.direction = Math.PI;
  this.velocity = START_SPEED;
};

ball.reset()

//---------------------------BASE PADDLE SETUP---------------------------------

const	paddleGeometry = new THREE.BoxGeometry(PADDLE_WIDTH, 3, UNIT);

function tickPaddle(paddle) {
  paddle.position.x += paddle.movement * UNIT;
  if (paddle.position.x > MAP_WIDTH/2 - PADDLE_WIDTH/2) paddle.position.x = MAP_WIDTH/2 - PADDLE_WIDTH/2;
  else if (paddle.position.x < PADDLE_WIDTH/2 - MAP_WIDTH/2) paddle.position.x = PADDLE_WIDTH/2 - MAP_WIDTH/2;
}

//-----------------------------PLAYER SETUP------------------------------------

const playerMaterial = new THREE.MeshPhongMaterial({
  color: PLAYER_COLOUR
});

const player = new THREE.Mesh(paddleGeometry, playerMaterial);
player.name = 'player'
scene.add(player);

player.moveLeft = function() { player.movement = 1 }
player.moveRight = function() { player.movement = -1 }
player.stop = function() { player.movement = 0 }

player.tick = function() {
  tickPaddle(this)
}

player.reset = function() {
  this.position.set(0, 0, UNIT - (MAP_DEPTH/2));
  this.movement = 0;
}

player.score = 0;
player.speed = 1;
player.reset()

//----------------------------OPPONENT SETUP-----------------------------------

const opponentMaterial = new THREE.MeshPhongMaterial({
  color: OPPONENT_COLOUR
});

const opponent = new THREE.Mesh(paddleGeometry, opponentMaterial);
opponent.name = 'opponent'
scene.add(opponent);

opponent.moveLeft = function() { opponent.movement = 1 }
opponent.moveRight = function() { opponent.movement = -1 }
opponent.stop = function() { opponent.movement = 0 }

opponent.followBall = function() {
  if (this.position.x > ball.position.x + PADDLE_WIDTH/10) this.movement = -1;
  else if (this.position.x < ball.position.x - PADDLE_WIDTH/10) this.movement = 1;
  else this.movement = 0;
}

opponent.tick = function() {
  tickPaddle(this)
}

opponent.reset = function() {
  this.position.set(0, 0, (MAP_DEPTH/2) - UNIT);
  this.movement = 0;
}

opponent.score = 0;
opponent.speed = 1;
opponent.reset()

//------------------------------SETUP HUD--------------------------------------

var gameState = 'set';
var playerName;

if (AB.runloggedin) playerName = AB.myuserid;
else playerName = 'Player';

const score = document.createElement('div');
score.style.position = 'absolute';
score.style.top = '50px';
score.style.left = '50%';
score.style.transform = 'translateX(-50%)';
score.style.color = '#ffffff';
score.style.fontFamily = 'arial';
score.style.fontSize = '21px';
container.appendChild(score);

function updateScore(playerScore, opponentScore) {
  score.innerHTML = `${playerName} ${-opponentScore} - ${-playerScore} Opponent`;
}

//------------------------WINDOW EVENT LISTENERS-------------------------------

window.addEventListener('keydown', function(event) {
  console.log(event.key)

  if (event.key === 'ArrowLeft') {
    player.moveLeft();
  }
  if (event.key === 'ArrowRight') {
    player.moveRight();
  }
  if (event.key === ' ' && gameState !== 'start') {
    startGame()
  }
});

window.addEventListener('keyup', function(event) {
  if (event.key == 'ArrowLeft') {
    if (player.movement > 0) player.stop();
  }
  if (event.key == 'ArrowRight') {
    if (player.movement < 0) player.stop();
  }
})

window.onresize = function() {
  renderer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
}

//------------------------------GAME LOOP--------------------------------------

function resetPoint() {
  player.reset()
  opponent.reset();
  ball.reset();
  updateScore(player.score, opponent.score)
}

function startGame() {
  console.log('Game started!')
  player.score = 0;
  opponent.score = 0;

  resetPoint();
  gameState = 'start';
}

function checkGameEnd() {
  if (player.score == -SCORE_NEEDED || opponent.score == -SCORE_NEEDED) {
    console.log('Game over!');
    gameState = 'end';
    
    if (player.score == -SCORE_NEEDED) score.innerHTML = 'YOU LOSE...'
    else score.innerHTML = 'YOU WIN!'
  }
}

function tick() {
  requestAnimationFrame(tick)
  
  if (gameState === 'start') {
    ball.tick();
    opponent.followBall();
    opponent.tick();
    player.tick();
    checkGameEnd();
  }
    
  renderer.render(scene, camera)
}

tick();