// Cloned by tuitef2 on 26 Jan 2019 from World "Simple Pacman World" by Cillian Mc Neill
// Please leave this clone trail here.
const SCREENSHOT_STEP = 1;
/*
#####SUBMISSION FOR AI CA#####
DISCLAIMER!!!
Built on top of a creation by butchler on Github (https://github.com/butchler/Pacman-3D)
//WHY DID I DO THIS?
I wanted to have a look at different pathfinding algorithms and how they would look on a simple map.
Pacman was one of the more interesting maps to work with and i had 4 unique ghosts to add to so this is
what i went with. Below i've outlined what i've changed along with how the ghosts will track you.
NOTE: I didn't use the mind API as it was easier for me to make the pathfinding for spercifically my map
and not a generic algorithm for all cases.
//WHATS THE SAME
Basic world creation has stayed the same (i would have implemented this in a similar fashion so i saw
no reason to change this).
Pacman and ghost movements and deltas have not changed as i saw no reason to alter movement
Shapes and appearance are practically the same
//WHATS CHANGED
Accoring to (https://www.diffchecker.com/diff) there have been 87 removals and 556 additions with 468 additional lines
of code to allow for the changes outlined below
Rather than 4 copies of a singualar ghost i have 4 seperate ghosts identified with a character in their
constructor to differentiate when using them in the code.
Ghost pathfinding is completely different as outlined below:
Blinky: Will move in such a way that the stright line distance is the shortest
Inky: Will move similar to blinky but when it gets closer it Will send out probes in the directions it has to choose
from, will do this recursively up to 3 times and will return the option with the lowest value and go that way,
if not option exists it will stop in place until pacman is far enough away that it will go back to straight line
distances again
Pinky: will try and move a few spaces ahead of pacman
Clyde: Will randomly pick a turn to take
Camera is changed to give a first person perspective
Some HUD elements have been removed to simplify the game for use on the world wide mind server
//KNOWN ISSUES
Inky will sometimes decide going straight through a wall is the quickest way and while sometimes that may be true
it's not exactly fair and can lead to him just jumping to the other end of the map as a result.
NOTE: You may notice that there are only 2-3 ghosts moving around initially but this is due to ghosts going the same way
and hiding within one another. They will split off over time as they make different choices.
*/
(function () {
// Constants
// =========
var PACMAN_SPEED = 2, PACMAN_RADIUS = 0.25;
var GHOST_SPEED = 1.75, GHOST_RADIUS = PACMAN_RADIUS * 1.25;
var DOT_RADIUS = 0.05, PELLET_RADIUS = DOT_RADIUS * 2;
var UP = new THREE.Vector3(0, 0, 1);
var LEFT = new THREE.Vector3(-1, 0, 0);
var TOP = new THREE.Vector3(0, 1, 0);
var RIGHT = new THREE.Vector3(1, 0, 0);
var BOTTOM = new THREE.Vector3(0, -1, 0);
var LEVEL = [
'# # # # # # # # # # # # # # # # # # # # # # # # # # # #',
'# . . . . . . . . . . . . # # . . . . . . . . . . . . #',
'# . # # # # . # # # # # . # # . # # # # # . # # # # . #',
'# o # # # # . # # # # # . # # . # # # # # . # # # # o #',
'# . # # # # . # # # # # . # # . # # # # # . # # # # . #',
'# . . . . . . . . . . . . . . . . . . . . . . . . . . #',
'# . # # # # . # # . # # # # # # # # . # # . # # # # . #',
'# . # # # # . # # . # # # # # # # # . # # . # # # # . #',
'# . . . . . . # # . . . . # # . . . . # # . . . . . . #',
'# # # # # # . # # # # # # # # # # # # . # # # # # #',
' # . # # # # # # # # # # # # . # ',
' # . # # G # # . # ',
' # . # # # # # # # # # # # # . # ',
'# # # # # # . # # # # # # . # # # # # #',
' . # # . ',
'# # # # # # . # # # # # # . # # # # # #',
' # . # # # # # # # # # # # # . # ',
' # . # # # # . # ',
' # . # # # # # # # # # # # # . # ',
'# # # # # # . # # # # # # # # # # # # . # # # # # #',
'# . . . . . . . . . . . . # # . . . . . . . . . . . . #',
'# . # # # # . # # # # # . # # . # # # # # . # # # # . #',
'# . # # # # . # # # # # . # # . # # # # # . # # # # . #',
'# o . . # # . . . . . . . P . . . . . . . # # . . o #',
'# # # . # # . # # . # # # # # # # # . # # . # # . # # #',
'# # # . # # . # # . # # # # # # # # . # # . # # . # # #',
'# . . . . . . # # . . . . # # . . . . # # . . . . . . #',
'# . # # # # # # # # # # . # # . # # # # # # # # # # . #',
'# . # # # # # # # # # # . # # . # # # # # # # # # # . #',
'# . . . . . . . . . . . . . . . . . . . . . . . . . . #',
'# # # # # # # # # # # # # # # # # # # # # # # # # # # #'
];
// Ghost tracking variables
var blinkyPosition = new THREE.Vector3();
var inkyPosition = new THREE.Vector3();
var pinkyPostition = new THREE.Vector3();
var clydePostition = new THREE.Vector3();
var numBlinky = 0;
var numPinky = 0;
var numInky = 0;
var numClyde = 0;
// Game-specific functions
// =======================
var createMap = function (scene, levelDefinition) {
var map = {};
map.bottom = -(levelDefinition.length - 1);
map.top = 0;
map.left = 0;
map.right = 0;
map.numDots = 0;
map.pacmanSpawn = null;
map.ghostSpawn = null;
var x, y;
for (var row = 0; row < levelDefinition.length; row++) {
// Set the coordinates of the map so that they match the
// coordinate system for objects.
y = -row;
map[y] = {};
// Get the length of the longest row in the level definition.
var length = Math.floor(levelDefinition[row].length / 2);
//map.right = Math.max(map.right, length - 1);
map.right = Math.max(map.right, length);
// Skip every second element, which is just a space for readability.
for (var column = 0; column < levelDefinition[row].length; column += 2) {
x = Math.floor(column / 2);
var cell = levelDefinition[row][column];
var object = null;
if (cell === '#') {
object = createWall();
} else if (cell === '.') {
object = createDot();
map.numDots += 1;
} else if (cell === 'o') {
object = createPowerPellet();
} else if (cell === 'P') {
map.pacmanSpawn = new THREE.Vector3(x, y, 0);
} else if (cell === 'G') {
map.ghostSpawn = new THREE.Vector3(x, y, 0);
}
if (object !== null) {
object.position.set(x, y, 0);
map[y][x] = object;
scene.add(object);
}
}
}
map.centerX = (map.left + map.right) / 2;
map.centerY = (map.bottom + map.top) / 2;
return map;
};
var getAt = function (map, position) {
var x = Math.round(position.x), y = Math.round(position.y);
return map[y] && map[y][x];
}
var isWall = function (map, position) {
var cell = getAt(map, position);
return cell && cell.isWall === true;
};
var removeAt = function (map, scene, position) {
var x = Math.round(position.x), y = Math.round(position.y);
if (map[y] && map[y][x]) {
// Don't actually remove, just make invisible.
map[y][x].visible = false;
}
}
var createWall = function () {
var wallGeometry = new THREE.BoxGeometry(1, 1, 1);
var wallMaterial = new THREE.MeshLambertMaterial({ color: 'blue' });
return function () {
var wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.isWall = true;
return wall;
};
}();
var createDot = function () {
var dotGeometry = new THREE.SphereGeometry(DOT_RADIUS);
var dotMaterial = new THREE.MeshPhongMaterial({ color: 0xFFFFFF });
return function () {
var dot = new THREE.Mesh(dotGeometry, dotMaterial);
dot.isDot = true;
return dot;
};
}();
var alertUser = function()
{
alert("You've Lost!")
}
var createPowerPellet = function() {
var pelletGeometry = new THREE.SphereGeometry(PELLET_RADIUS, 12, 8);
var pelletMaterial = new THREE.MeshPhongMaterial({ color: 0xFFFFFF });
return function () {
var pellet = new THREE.Mesh(pelletGeometry, pelletMaterial);
pellet.isPowerPellet = true;
return pellet;
};
}();
var createRenderer = function () {
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor('black', 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
return renderer;
}
var createScene = function () {
var scene = new THREE.Scene();
// Add lighting
scene.add(new THREE.AmbientLight(0x888888));
var light = new THREE.SpotLight('white', 0.5);
light.position.set(0, 0, 50);
scene.add(light);
return scene;
};
var createHudCamera = function (map) {
var halfWidth = (map.right - map.left) / 2, halfHeight = (map.top - map.bottom) / 2;
var hudCamera = new THREE.OrthographicCamera(-halfWidth, halfWidth, halfHeight, -halfHeight, 1, 100);
hudCamera.position.copy(new THREE.Vector3(map.centerX, map.centerY, 10));
hudCamera.lookAt(new THREE.Vector3(map.centerX, map.centerY, 0));
return hudCamera;
};
var renderHud = function (renderer, hudCamera, scene) {
// Increase size of pacman and dots in HUD to make them easier to see.
scene.children.forEach(function (object) {
if (object.isWall !== true)
object.scale.set(2.5, 2.5, 2.5);
});
// Only render in the bottom left 200x200 square of the screen.
renderer.enableScissorTest(true);
renderer.setScissor(10, 10, 200, 200);
renderer.setViewport(10, 10, 200, 200);
renderer.render(scene, hudCamera);
renderer.enableScissorTest(false);
// Reset scales after rendering HUD.
scene.children.forEach(function (object) {
object.scale.set(1, 1, 1);
});
};
var createPacman = function () {
// Create spheres with decreasingly small horizontal sweeps, in order
// to create pacman "death" animation.
var pacmanGeometries = [];
var numFrames = 40;
var offset;
for (var i = 0; i < numFrames; i++) {
offset = (i / (numFrames - 1)) * Math.PI;
pacmanGeometries.push(new THREE.SphereGeometry(PACMAN_RADIUS, 16, 16, offset, Math.PI * 2 - offset * 2));
pacmanGeometries[i].rotateX(Math.PI / 2);
}
var pacmanMaterial = new THREE.MeshPhongMaterial({ color: 'yellow', side: THREE.DoubleSide });
return function (scene, position) {
var pacman = new THREE.Mesh(pacmanGeometries[0], pacmanMaterial);
pacman.frames = pacmanGeometries;
pacman.currentFrame = 0;
pacman.isPacman = true;
pacman.isWrapper = true;
pacman.atePellet = false;
pacman.distanceMoved = 0;
// Initialize pacman facing to the left.
pacman.position.copy(position);
pacman.direction = new THREE.Vector3(-1, 0, 0);
scene.add(pacman);
return pacman;
};
}();
var createBlinky = function () {
var ghostGeometry = new THREE.SphereGeometry(GHOST_RADIUS, 16, 16);
return function (scene, position) {
// Give each ghost it's own material so we can change the colors of individual ghosts.
var ghostMaterial = new THREE.MeshPhongMaterial({ color: 'red' });
var ghost = new THREE.Mesh(ghostGeometry, ghostMaterial);
ghost.name = "b";
ghost.isGhost = true;
ghost.isWrapper = true;
ghost.isAfraid = false;
// Ghosts start moving left.
ghost.position.copy(position);
blinkyPosition.copy(position);
numBlinky = 1;
ghost.direction = new THREE.Vector3(-1, 0, 0);
scene.add(ghost);
};
}();
var createPinky = function () {
var ghostGeometry = new THREE.SphereGeometry(GHOST_RADIUS, 16, 16);
return function (scene, position) {
// Give each ghost it's own material so we can change the colors of individual ghosts.
var ghostMaterial = new THREE.MeshPhongMaterial({ color: 'pink' });
var ghost = new THREE.Mesh(ghostGeometry, ghostMaterial);
ghost.name = "p";
ghost.isGhost = true;
ghost.isWrapper = true;
ghost.isAfraid = false;
// Ghosts start moving left.
ghost.position.copy(position);
pinkyPostition.copy(position);
numPinky = 1;
ghost.direction = new THREE.Vector3(-1, 0, 0);
scene.add(ghost);
};
}();
var createInky = function () {
var ghostGeometry = new THREE.SphereGeometry(GHOST_RADIUS, 16, 16);
return function (scene, position) {
// Give each ghost it's own material so we can change the colors of individual ghosts.
var ghostMaterial = new THREE.MeshPhongMaterial({ color: 'green' });
var ghost = new THREE.Mesh(ghostGeometry, ghostMaterial);
ghost.name = "i";
ghost.isGhost = true;
ghost.isWrapper = true;
ghost.isAfraid = false;
// Ghosts start moving left.
ghost.position.copy(position);
inkyPosition.copy(position);
numInky = 1;
ghost.direction = new THREE.Vector3(-1, 0, 0);
scene.add(ghost);
};
}();
var createClyde = function () {
var ghostGeometry = new THREE.SphereGeometry(GHOST_RADIUS, 16, 16);
return function (scene, position) {
// Give each ghost it's own material so we can change the colors of individual ghosts.
var ghostMaterial = new THREE.MeshPhongMaterial({ color: 'orange' });
var ghost = new THREE.Mesh(ghostGeometry, ghostMaterial);
ghost.name = "c";
ghost.isGhost = true;
ghost.isWrapper = true;
ghost.isAfraid = false;
// Ghosts start moving left.
ghost.position.copy(position);
clydePostition.copy(position);
numClyde = 1;
ghost.direction = new THREE.Vector3(-1, 0, 0);
scene.add(ghost);
};
}();
// Make object wrap to other side of map if it goes out of bounds.
var wrapObject = function (object, map) {
if (object.position.x < map.left)
object.position.x = map.right;
else if (object.position.x > map.right)
object.position.x = map.left;
if (object.position.y > map.top)
object.position.y = map.bottom;
else if (object.position.y < map.bottom)
object.position.y = map.top;
};
// Generic functions
// =================
var distance = function () {
var difference = new THREE.Vector3();
return function (object1, object2) {
// Calculate difference between objects' positions.
difference.copy(object1.position).sub(object2.position);
return difference.length();
};
}();
// Returns an object that contains the current state of keys being pressed.
var createKeyState = function () {
// Keep track of current keys being pressed.
var keyState = {};
document.body.addEventListener('keydown', function (event) {
keyState[event.keyCode] = true;
keyState[String.fromCharCode(event.keyCode)] = true;
});
document.body.addEventListener('keyup', function (event) {
keyState[event.keyCode] = false;
keyState[String.fromCharCode(event.keyCode)] = false;
});
document.body.addEventListener('blur', function (event) {
// Make it so that all keys are unpressed when the browser loses focus.
for (var key in keyState) {
if (keyState.hasOwnProperty(key))
keyState[key] = false;
}
});
return keyState;
};
var animationLoop = function (callback, requestFrameFunction) {
requestFrameFunction = requestFrameFunction || requestAnimationFrame;
var previousFrameTime = window.performance.now();
// How many seconds the animation has progressed in total.
var animationSeconds = 0;
var render = function () {
var now = window.performance.now();
var animationDelta = (now - previousFrameTime) / 1000;
previousFrameTime = now;
animationDelta = Math.min(animationDelta, 1/30);
// Keep track of how many seconds of animation has passed.
animationSeconds += animationDelta;
callback(animationDelta, animationSeconds);
requestFrameFunction(render);
};
requestFrameFunction(render);
};
// Main function
// =============
var main = function () {
// Game state variables
var keys = createKeyState();
var renderer = createRenderer();
var scene = createScene();
var map = createMap(scene, LEVEL);
var numDotsEaten = 0;
var camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.up.copy(UP);
camera.targetPosition = new THREE.Vector3();
camera.targetLookAt = new THREE.Vector3();
camera.lookAtPosition = new THREE.Vector3();
var hudCamera = createHudCamera(map);
var pacman = createPacman(scene, map.pacmanSpawn);
var ghostSpawnTime = -8;
var numGhosts = 0;
var won = lost = false;
var lostTime, wonTime;
var chompSound = new Audio('/uploads/cillianm/pacman_chomp.mp3');
chompSound.volume = 0.5;
chompSound.loop = true;
chompSound.preload = 'auto';
var levelStartSound = new Audio('/uploads/cillianm/pacman_beginning.mp3');
levelStartSound.preload = 'auto';
// Play the level start sound as soon as the game starts.
levelStartSound.autoplay = true;
var deathSound = new Audio('/uploads/cillianm/pacman_death.mp3');
deathSound.preload = 'auto';
var killSound = new Audio('/uploads/cillianm/pacman_eatghost.mp3');
killSound.preload = 'auto';
var remove = [];
// Create life images
var lives = 3;
// Main game logic
var update = function (delta, now) {
updatePacman(delta, now);
updateCamera(delta, now);
scene.children.forEach(function (object) {
if (object.isGhost === true)
updateGhost(object, delta, now);
if (object.isWrapper === true)
wrapObject(object, map);
if (object.isTemporary === true && now > object.removeAfter)
remove.push(object);
});
// Cannot remove items from scene.children while iterating
// through it, so remove them after the forEach loop.
remove.forEach(scene.remove, scene);
for (item in remove) {
if (remove.hasOwnProperty(item)) {
scene.remove(remove[item]);
delete remove[item];
}
}
// Spawn a ghost every 8 seconds, up to 4 ghosts.
if (numGhosts < 4 && now - ghostSpawnTime > 8) {
if(numBlinky == 0)
{
createBlinky(scene, map.ghostSpawn);
numGhosts += 1;
}
if(numInky == 0)
{
createInky(scene, map.ghostSpawn);
numGhosts += 1;
}
if(numPinky ==0)
{
createPinky(scene, map.ghostSpawn);
numGhosts += 1;
}
if(numClyde == 0)
{
createClyde(scene, map.ghostSpawn);
numGhosts += 1;
}
ghostSpawnTime = now;
}
};
var _diff = new THREE.Vector3();
var updatePacman = function (delta, now) {
// Play chomp sound if player is moving.
if (!won && !lost && (keys['W'] || keys['S'])) {
chompSound.play();
} else {
chompSound.pause();
}
// Move if we haven't died or won.
if (!won && !lost) {
movePacman(delta);
}
// Check for win.
if (!won && numDotsEaten === map.numDots) {
won = true;
wonTime = now;
levelStartSound.play();
}
// Go to next level 4 seconds after winning.
if (won && now - wonTime > 3) {
// Reset pacman position and direction.
pacman.position.copy(map.pacmanSpawn);
pacman.direction.copy(LEFT);
pacman.distanceMoved = 0;
// Reset dots, power pellets, and ghosts.
scene.children.forEach(function (object) {
if (object.isDot === true || object.isPowerPellet === true)
object.visible = true;
if (object.isGhost === true)
remove.push(object);
});
// Increase speed.
PACMAN_SPEED += 1;
GHOST_SPEED += 1;
won = false;
numDotsEaten = 0;
numGhosts = 0;
numBlinky = 0;
numClyde = 0;
numInky = 0;
numPinky = 0;
}
// Reset pacman 4 seconds after dying.
if (lives > 0 && lost && now - lostTime > 4) {
lost = false;
pacman.position.copy(map.pacmanSpawn);
pacman.direction.copy(LEFT);
pacman.distanceMoved = 0;
}
// Animate model
if (lost) {
// If pacman got eaten, show dying animation.
var angle = (now - lostTime) * Math.PI / 2;
var frame = Math.min(pacman.frames.length - 1, Math.floor(angle / Math.PI * pacman.frames.length));
pacman.geometry = pacman.frames[frame];
} else {
// Otherwise, show eating animation based on how much pacman has moved.
var maxAngle = Math.PI / 4;
var angle = (pacman.distanceMoved * 2) % (maxAngle * 2);
if (angle > maxAngle)
angle = maxAngle * 2 - angle;
var frame = Math.floor(angle / Math.PI * pacman.frames.length);
pacman.geometry = pacman.frames[frame];
}
};
var _lookAt = new THREE.Vector3();
var movePacman = function (delta) {
// Update rotation based on direction so that mouth is always facing forward.
// The "mouth" part is on the side of the sphere, make it "look" up but
// set the up direction so that it points forward.
pacman.up.copy(pacman.direction).applyAxisAngle(UP, -Math.PI / 2);
pacman.lookAt(_lookAt.copy(pacman.position).add(UP));
// Move based on current keys being pressed.
if (keys['W']) {
// W - move forward
//pacman.translateOnAxis(pacman.direction, PACMAN_SPEED * delta);
// Because we are rotating the object above using lookAt, "forward" is to the left.
pacman.translateOnAxis(LEFT, PACMAN_SPEED * delta);
pacman.distanceMoved += PACMAN_SPEED * delta;
}
if (keys['A']) {
// A - rotate left
pacman.direction.applyAxisAngle(UP, Math.PI / 2 * delta);
}
if (keys['D']) {
// D - rotate right
pacman.direction.applyAxisAngle(UP, -Math.PI / 2 * delta);
}
if (keys['S']) {
// S - move backward
//pacman.translateOnAxis(pacman.direction, -PACMAN_SPEED * delta);
pacman.translateOnAxis(LEFT, -PACMAN_SPEED * delta);
pacman.distanceMoved += PACMAN_SPEED * delta;
}
// Check for collision with walls.
var leftSide = pacman.position.clone().addScaledVector(LEFT, PACMAN_RADIUS).round();
var topSide = pacman.position.clone().addScaledVector(TOP, PACMAN_RADIUS).round();
var rightSide = pacman.position.clone().addScaledVector(RIGHT, PACMAN_RADIUS).round();
var bottomSide = pacman.position.clone().addScaledVector(BOTTOM, PACMAN_RADIUS).round();
if (isWall(map, leftSide)) {
pacman.position.x = leftSide.x + 0.5 + PACMAN_RADIUS;
}
if (isWall(map, rightSide)) {
pacman.position.x = rightSide.x - 0.5 - PACMAN_RADIUS;
}
if (isWall(map, topSide)) {
pacman.position.y = topSide.y - 0.5 - PACMAN_RADIUS;
}
if (isWall(map, bottomSide)) {
pacman.position.y = bottomSide.y + 0.5 + PACMAN_RADIUS;
}
var cell = getAt(map, pacman.position);
// Make pacman eat dots.
if (cell && cell.isDot === true && cell.visible === true) {
removeAt(map, scene, pacman.position);
numDotsEaten += 1;
}
// Make pacman eat power pellets.
pacman.atePellet = false;
if (cell && cell.isPowerPellet === true && cell.visible === true) {
removeAt(map, scene, pacman.position);
pacman.atePellet = true;
killSound.play();
}
};
var updateCamera = function (delta, now) {
if (won) {
// After winning, pan camera out to show whole level.
camera.targetPosition.set(map.centerX, map.centerY, 30);
camera.targetLookAt.set(map.centerX, map.centerY, 0);
} else if (lost) {
// After losing, move camera to look down at pacman's body from above.
camera.targetPosition = pacman.position.clone().addScaledVector(UP, 4);
camera.targetLookAt = pacman.position.clone().addScaledVector(pacman.direction, 0.01);
} else {
// Place camera above and behind pacman, looking towards direction of pacman.
camera.targetPosition.copy(pacman.position).addScaledVector(pacman.direction, 0.5);
camera.targetLookAt.copy(pacman.position).add(pacman.direction);
}
// Move camera slowly during win/lose animations.
var cameraSpeed = (lost || won) ? 1 : 10;
camera.position.lerp(camera.targetPosition, delta * cameraSpeed);
camera.lookAtPosition.lerp(camera.targetLookAt, delta * cameraSpeed);
camera.lookAt(camera.lookAtPosition);
};
var updateGhost = function (ghost, delta, now) {
// Make all ghosts afraid if Pacman just ate a pellet.
if (pacman.atePellet === true) {
ghost.isAfraid = true;
ghost.becameAfraidTime = now;
ghost.material.color.setStyle('purple');
}
// Make ghosts not afraid anymore after 10 seconds.
if (ghost.isAfraid && now - ghost.becameAfraidTime > 10) {
ghost.isAfraid = false;
//revert back to original colors
if(ghost.name == "i"){
ghost.material.color.setStyle('green');
}
if(ghost.name == "b"){
ghost.material.color.setStyle('red');
}
if(ghost.name == "p"){
ghost.material.color.setStyle('pink');
}
if(ghost.name == "c"){
ghost.material.color.setStyle('orange');
}
}
moveGhost(ghost, delta);
// Check for collision between Pacman and ghost.
if (!lost && !won && distance(pacman, ghost) < PACMAN_RADIUS + GHOST_RADIUS) {
if (ghost.isAfraid === true) {
remove.push(ghost);
numGhosts -= 1;
if(ghost.name == "b"){numBlinky = 0;}
if(ghost.name == "i"){numInky = 0;}
if(ghost.name == "p"){numPinky = 0;}
if(ghost.name == "c"){numClyde = 0;}
killSound.play();
} else {
lives -= 1;
lost = true;
lostTime = now;
deathSound.play();
if (lives == 0)
alertUser();
}
}
}
/*
Sends out a probe in all available directions, returning a sum of the minimum values
up to the TTL so it doesn't go on forever and loop, we only want a general idea of
which direction is better so this is just fine for our purposes
*/
var sendProbe = function(currentPostioning,currentDirection,goal,TTL,delta)
{
//See where i am
//if the ttl = 0
//if there is walls then move forward and call again
if(TTL == 0)
{
return 1;
}
var currentPosition = new THREE.Vector3();
var distance = currentPostioning.distanceTo(goal);
var minus = new THREE.Vector3();
TTL -= 1;
//Take all of the probes current postionings
var previousPosition = new THREE.Vector3();
var currentPosition = new THREE.Vector3();
var leftTurn = new THREE.Vector3();
var rightTurn = new THREE.Vector3();
previousPosition.copy(currentPostioning).addScaledVector(currentDirection, 0.5).round();
currentPosition.copy(currentPostioning).addScaledVector(currentDirection, 0.5).round();
leftTurn.copy(currentDirection).applyAxisAngle(UP, Math.PI / 2);
rightTurn.copy(currentDirection).applyAxisAngle(UP, -Math.PI / 2);
var forwardWall = isWall(map, currentPosition);
var leftWall = isWall(map, currentPosition.copy(currentPostioning).add(leftTurn));
var rightWall = isWall(map, currentPosition.copy(currentPostioning).add(rightTurn));
var tmpPosition = new THREE.Vector3();
var current = new THREE.Vector3();
tmpPosition.copy(currentPostioning.round().addScaledVector(currentDirection, delta));
var forwardMin = 0;
var rightMin = 0;
var leftMin = 0;
if(!forwardWall)
{
tmpPosition.copy(currentPostioning.round().addScaledVector(currentDirection, delta));
tmpPosition.round().addScaledVector(currentDirection, delta);
var forward = sendProbe(tmpPosition,currentDirection,goal,TTL,delta);
forwardMin = forwardMin + forward;
}
if (!leftWall)
{
tmpPosition.copy(currentPostioning.round().addScaledVector(leftTurn, delta));
tmpPosition.round().addScaledVector(leftTurn, delta);
var left = sendProbe(tmpPosition,leftTurn,goal,TTL,delta);
leftMin = leftMin + left;
}
if (!rightWall)
{
tmpPosition.copy(currentPostioning.round().addScaledVector(rightTurn, delta));
tmpPosition.round().addScaledVector(rightTurn, delta);
var right = sendProbe(tmpPosition,rightTurn,goal,TTL,delta);
rightMin = rightMin + right;
}
if(forwardMin < rightMin && forwardMin < leftMin)
return forwardMin;
else if(rightMin < forwardMin && rightMin < leftMin)
return rightMin;
else
return leftMin;
}
var moveGhost = function () {
var previousPosition = new THREE.Vector3();
var currentPosition = new THREE.Vector3();
var leftTurn = new THREE.Vector3();
var rightTurn = new THREE.Vector3();
return function (ghost, delta) {
previousPosition.copy(ghost.position).addScaledVector(ghost.direction, 0.5).round();
ghost.translateOnAxis(ghost.direction, delta * GHOST_SPEED);
currentPosition.copy(ghost.position).addScaledVector(ghost.direction, 0.5).round();
// If the ghost is transitioning from one cell to the next, see if they can turn.
if (!currentPosition.equals(previousPosition))
{
var currentDistance = currentPosition.distanceTo(pacman.position);
//Inky will get more accurate when it's closes otherwise it follows clydes rules
if(ghost.name == "i")
{
leftTurn.copy(ghost.direction).applyAxisAngle(UP, Math.PI / 2);
rightTurn.copy(ghost.direction).applyAxisAngle(UP, -Math.PI / 2);
var forwardWall = isWall(map, currentPosition);
var leftWall = isWall(map, currentPosition.copy(ghost.position).add(leftTurn));
var rightWall = isWall(map, currentPosition.copy(ghost.position).add(rightTurn));
//Nothing to check if you have no options
if(leftWall && rightWall)
{
//If you can't turn then just keep going no need to do the rest
ghost.position.round().addScaledVector(ghost.direction, delta);
}
//if the ghosts distance is too far away get in range of pacman
else if(currentDistance > 3)
{
var possibleTurns = [];
var ghostDistances = [];
if (!forwardWall)
{
ghostDistances.push(currentPosition.distanceTo(pacman.position));
possibleTurns.push(ghost.direction);
}
if (!leftWall)
{
var leftTurnPosition = currentPosition.copy(ghost.position).add(leftTurn);
ghostDistances.push(leftTurnPosition.distanceTo(pacman.position));
possibleTurns.push(leftTurn);
}
if (!rightWall)
{
var rightTurnPosition = currentPosition.copy(ghost.position).add(rightTurn);
ghostDistances.push(rightTurnPosition.distanceTo(pacman.position));
possibleTurns.push(rightTurn);
}
if (possibleTurns.length === 0)
throw new Error('A ghost got stuck!');
var min = ghostDistances[0];
var minIndex = 0;
for (var i = 0; i <= ghostDistances.length; i++)
{
if(ghostDistances[i] < min)
{
min = ghostDistances[i];
minIndex = i;
}
}
var newDirection = possibleTurns[minIndex];
ghost.direction.copy(newDirection);
// Snap ghost to center of current cell and start moving in new direction.
ghost.position.round().addScaledVector(ghost.direction, delta);
}
//If we're in range we can send out probes to make a more accurate guess to where pacman is
else
{
var TTL = 3;
// If the ghsot can turn, randomly choose one of the possible turns.
var tmpPosition = new THREE.Vector3();
var current = new THREE.Vector3();
var pacmanPosition = new THREE.Vector3();
pacmanPosition.copy(pacman.position);
tmpPosition.copy(ghost.position.round().addScaledVector(ghost.direction, delta));
current.copy(ghost.position);
var min = 100;
if(!forwardWall)
{
tmpPosition.copy(current.round().addScaledVector(ghost.direction, delta));
var forward = sendProbe(tmpPosition,ghost.direction,pacmanPosition,TTL,delta);
}
if (!leftWall)
{
tmpPosition.copy(current.round().addScaledVector(leftTurn, delta));
var left = sendProbe(tmpPosition,leftTurn,pacmanPosition,TTL,delta);
}
if (!rightWall)
{
tmpPosition.copy(current.round().addScaledVector(rightTurn, delta));
var right = sendProbe(tmpPosition,rightTurn,pacmanPosition,TTL,delta);
}
if(left <= forward && left <= right && !leftWall)
{
min = left;
ghost.position.copy(tmpPosition);
ghost.position.round().addScaledVector(leftTurn, delta);
}
else if(forward <= left && forward <= right && !forwardWall)
{
min = forward;
ghost.position.copy(tmpPosition);
ghost.position.round().addScaledVector(ghost.direction, delta);
}
else if(!rightWall)
{
min = right;
ghost.position.copy(tmpPosition);
ghost.position.round().addScaledVector(rightTurn, delta);
}
else
{
var stop = new THREE.Vector3(0,0,0);
ghost.position.copy(tmpPosition);
ghost.position.round().addScaledVector(stop, delta);
}
}
}
//Blinky will hunt the player down
if(ghost.name == "b")
{
leftTurn.copy(ghost.direction).applyAxisAngle(UP, Math.PI / 2);
rightTurn.copy(ghost.direction).applyAxisAngle(UP, -Math.PI / 2);
var forwardWall = isWall(map, currentPosition);
var leftWall = isWall(map, currentPosition.copy(ghost.position).add(leftTurn));
var rightWall = isWall(map, currentPosition.copy(ghost.position).add(rightTurn));
var possibleTurns = [];
var ghostDistances = [];
if(leftWall && rightWall)
{
//If you can't turn then just keep going no need to do the rest
ghost.position.round().addScaledVector(ghost.direction, delta);
}
if (!forwardWall)
{
ghostDistances.push(currentPosition.distanceTo(pacman.position));
possibleTurns.push(ghost.direction);
}
if (!leftWall)
{
var leftTurnPosition = currentPosition.copy(ghost.position).add(leftTurn);
ghostDistances.push(leftTurnPosition.distanceTo(pacman.position));
possibleTurns.push(leftTurn);
}
if (!rightWall)
{
var rightTurnPosition = currentPosition.copy(ghost.position).add(rightTurn);
ghostDistances.push(rightTurnPosition.distanceTo(pacman.position));
possibleTurns.push(rightTurn);
}
if (possibleTurns.length === 0)
throw new Error('A ghost got stuck!');
var min = ghostDistances[0];
var minIndex = 0;
for (var i = 0; i <= ghostDistances.length; i++)
{
if(ghostDistances[i] < min)
{
min = ghostDistances[i];
minIndex = i;
}
}
var newDirection = possibleTurns[minIndex];
ghost.direction.copy(newDirection);
// Snap ghost to center of current cell and start moving in new direction.
ghost.position.round().addScaledVector(ghost.direction, delta);
}
//Pinky will try to move 4 spaces ahead of where pacman is
if(ghost.name == "p")
{
leftTurn.copy(ghost.direction).applyAxisAngle(UP, Math.PI / 2);
rightTurn.copy(ghost.direction).applyAxisAngle(UP, -Math.PI / 2);
var forwardWall = isWall(map, currentPosition);
var leftWall = isWall(map, currentPosition.copy(ghost.position).add(leftTurn));
var rightWall = isWall(map, currentPosition.copy(ghost.position).add(rightTurn));
var possibleTurns = [];
var ghostDistances = [];
var goalPosition = new THREE.Vector3(1,1,0);
goalPosition.add(goalPosition,pacman.position);
if(leftWall && rightWall)
{
//If you can't turn then just keep going no need to do the rest
ghost.position.round().addScaledVector(ghost.direction, delta);
}
if (!forwardWall)
{
ghostDistances.push(currentPosition.distanceTo(goalPosition));
possibleTurns.push(ghost.direction);
}
if (!leftWall)
{
var leftTurnPosition = currentPosition.copy(ghost.position).add(leftTurn);
ghostDistances.push(leftTurnPosition.distanceTo(goalPosition));
possibleTurns.push(leftTurn);
}
if (!rightWall)
{
var rightTurnPosition = currentPosition.copy(ghost.position).add(rightTurn);
ghostDistances.push(rightTurnPosition.distanceTo(goalPosition));
possibleTurns.push(rightTurn);
}
if (possibleTurns.length === 0)
throw new Error('A ghost got stuck!');
var min = ghostDistances[0];
var minIndex = 0;
for (var i = 0; i <= ghostDistances.length; i++)
{
if(ghostDistances[i] < min)
{
min = ghostDistances[i];
minIndex = i;
}
}
var newDirection = possibleTurns[minIndex];
ghost.direction.copy(newDirection);
// Snap ghost to center of current cell and start moving in new direction.
ghost.position.round().addScaledVector(ghost.direction, delta);
}
//at a distance of 8 it hunts the player, otherwise it will move to the bottom left of the map
if(ghost.name == "c")
{
leftTurn.copy(ghost.direction).applyAxisAngle(UP, Math.PI / 2);
rightTurn.copy(ghost.direction).applyAxisAngle(UP, -Math.PI / 2);
var forwardWall = isWall(map, currentPosition);
var leftWall = isWall(map, currentPosition.copy(ghost.position).add(leftTurn));
var rightWall = isWall(map, currentPosition.copy(ghost.position).add(rightTurn));
if (!leftWall || !rightWall) {
// If the ghsot can turn, randomly choose one of the possible turns.
var possibleTurns = [];
if (!forwardWall) possibleTurns.push(ghost.direction);
if (!leftWall) possibleTurns.push(leftTurn);
if (!rightWall) possibleTurns.push(rightTurn);
if (possibleTurns.length === 0)
throw new Error('A ghost got stuck!');
var newDirection = possibleTurns[Math.floor(Math.random() * possibleTurns.length)];
ghost.direction.copy(newDirection);
// Snap ghost to center of current cell and start moving in new direction.
ghost.position.round().addScaledVector(ghost.direction, delta);
}
}
}
}
}();
var moveBlinky = function () {
}();
var moveInky = function () {
}();
var movePinky = function () {
}();
var moveClyde = function () {
}();
// Main game loop
animationLoop(function (delta, now) {
update(delta, now);
// Render main view
renderer.setViewport(0, 0, renderer.domElement.width, renderer.domElement.height);
renderer.render(scene, camera);
// Render HUD
renderHud(renderer, hudCamera, scene);
});
};
main();
}());