// Cloned by Cbum on 5 Dec 2022 from World "Pac-Man" by Cbum
// Please leave this clone trail here.
// Cloned by Cbum on 5 Dec 2022 from World "Zombie Death Baby" by Starter user
// Please leave this clone trail here.
// ==== Starter World =================================================================================================
// This code is designed for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.
// ====================================================================================================================
// Putting it all together - How to make a touch game for mobile
// Based on Complex World and Touch World
// touch drag of objects, touch hit of objects
// mouse drag of objects, mouse click on objects
// Splash screen (so click can auto-start audio)
// Behind the splash screen, it is loading resources while waiting for you to click
// Mobile:
// Touch drag to move agent
// Touch tap to "zap" baby
// Touch pinch camera (built in)
// Desktop:
// Mouse drag to move agent
// Mouse click to "zap" baby
// Mouse scroll camera (built in)
// =============================================================================================
// To do my own mouse/touch:
// Over-ride certain "ABHandler" functions.
// =============================================================================================
// Name inspired by "Sonic Death Monkey" in "High Fidelity"
// ===================================================================================================================
// === Start of tweaker's box ========================================================================================
// ===================================================================================================================
// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?
AB.clockTick = 100;
// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps = 1000000; // make it so run in practice never ends until user wants
// if do put in a maximum run, 1000 is a pretty long run
// Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep = 200;
// Take screenshot on this step. (All resources should have finished loading.) Default 50.
AB.drawRunControls = false;
// Scrap the Run/Step/Pause controls
//---- global constants: -------------------------------------------------------
// Switch between 3d and 2d view
const show3d = false;
const TEXTURE_WALL = '/uploads/kevin/maze.png' ;
const TEXTURE_MAZE = '/uploads/kevin/maze.png' ;
const TEXTURE_AGENT = '/uploads/kevin/pacman.png' ;
const TEXTURE_BABY = '/uploads/kevin/ghost.png' ;
const TEXTURE_DEAD = '/uploads/starter/ghost.3.png' ;
const SOUND_MAMA = '/uploads/kevin/ghostsiren.mp3';
const SOUND_SCREAM = '/uploads/kevin/eatghost.mp3';
const MUSIC_BACK = '/uploads/kevin/wakawaka.mp3' ;
const SOUND_ALARM = '/uploads/kevin/gamelose.mp3' ;
const gridsize = 36; // number of squares along side of world
const NOBOXES = Math.trunc ( (gridsize * gridsize) / 10 );
// density of maze - number of internal boxes
const squaresize = 100; // size of square in pixels
const MAXPOS = gridsize * squaresize; // length of one side in pixels
const SKYCOLOR = 'black'; // a number, not a string
const startRadiusConst = MAXPOS * 1 ; // distance from centre to start the camera at
const maxRadiusConst = MAXPOS * 10 ; // maximum distance from camera we will render things
// CSS style used for score output:
const scoreStyle = "font-weight: bold; font-family: Segoe Print, Comic Sans, Impact, Bolton;";
const NONEWBABIES = 2; // how many babies to make when I kill one
const SCORE_KILL = 500; // add this to score for every kill of a baby
const SCORE_CLOSE = -10; // add this to score for every step close to a baby
//--- change ABWorld defaults: -------------------------------
ABHandler.MAXCAMERAPOS = maxRadiusConst ;
ABHandler.GROUNDZERO = true; // "ground" exists at altitude zero
ABWorld.drawCameraControls = false;
//--- skybox: -------------------------------
const SKYBOX_ARRAY = [
"/uploads/kevin/arcade2.jpg",
"/uploads/kevin/arcade2.jpg",
"/uploads/kevin/arcade2.jpg",
"/uploads/kevin/arcade2.jpg",
"/uploads/kevin/arcade2.jpg",
"/uploads/kevin/arcade2.jpg"
];
// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================
// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.
// can write CSS to page
// document.write (of CSS at least) is good at the moment we include the JS, not later
document.write ( ` <style>
.mybutton
{
border-radius: 0px;
background-color: darkslategrey;
}
.mybutton:hover { background-color: darkcyan; }
</style> ` );
// we have a splash screen
// behind the splash screen, newRun is running, loading resources
// do not start the run loop until resources ready AND splash screen is dismissed
var resourcesLoaded = false;
var splashClicked = false;
const ACTION_LEFT = 0;
const ACTION_RIGHT = 1;
const ACTION_UP = 2;
const ACTION_DOWN = 3;
const ACTION_STAYSTILL = 4;
// in initial view, (smaller-larger) on i axis is aligned with (left-right)
// in initial view, (smaller-larger) on j axis is aligned with (away from you - towards you)
// contents of a grid square
const GRID_BLANK = 0;
const GRID_WALL = 1;
const GRID_MAZE = 2;
const GRID_AGENT = 3;
const GRID_BABY = 4;
const GRID_DEAD = 5;
var BOXHEIGHT; // 3d or 2d box height
var GRID = new Array ( gridsize ); // will in fact be a 2D array, see later
var theagent;
var BABIES = new Array ( 1 ); // start at 1 and increase using array.push()
var wall_texture, agent_texture, baby_texture, dead_texture, maze_texture;
// agent position
var ai, aj;
var score;
function loadResources() // asynchronous file loads - call initScene() when all finished
{
var loader1 = new THREE.TextureLoader();
var loader2 = new THREE.TextureLoader();
var loader3 = new THREE.TextureLoader();
var loader4 = new THREE.TextureLoader();
var loader5 = new THREE.TextureLoader();
loader1.load ( TEXTURE_WALL, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
wall_texture = thetexture;
if ( asynchFinished() ) initScene(); // if all file loads have returned
});
loader2.load ( TEXTURE_AGENT, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
agent_texture = thetexture;
if ( asynchFinished() ) initScene();
});
loader3.load ( TEXTURE_BABY, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
baby_texture = thetexture;
if ( asynchFinished() ) initScene();
});
loader4.load ( TEXTURE_MAZE, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
maze_texture = thetexture;
if ( asynchFinished() ) initScene();
});
loader5.load ( TEXTURE_DEAD, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
dead_texture = thetexture;
if ( asynchFinished() ) initScene();
});
}
function asynchFinished() // all file loads returned
{
if ( wall_texture && agent_texture && baby_texture && dead_texture && maze_texture ) return true;
else return false;
}
//--- grid system -------------------------------------------------------------------------------
// my numbering is 0 to gridsize-1
function occupied ( i, j ) // is this square occupied
{
return ( GRID[i][j] != GRID_BLANK );
}
// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
// logically, coordinates are: y=0, x and z all positive (no negative)
// logically my dimensions are all positive 0 to MAXPOS
// to centre everything on origin, subtract (MAXPOS/2) from all dimensions
function translate ( i, j )
{
var v = new THREE.Vector3();
v.y = 0;
v.x = ( i * squaresize ) - ( MAXPOS/2 );
v.z = ( j * squaresize ) - ( MAXPOS/2 );
return v;
}
function initScene() // all file loads have returned
{
var i,j, shape, thecube;
// set up GRID as 2D array
for ( i = 0; i < gridsize ; i++ )
GRID[i] = new Array(gridsize);
// set up walls
for ( i = 0; i < gridsize ; i++ )
for ( j = 0; j < gridsize ; j++ )
if ( ( i==0 ) || ( i==gridsize-1 ) || ( j==0 ) || ( j==gridsize-1 ) )
{
GRID[i][j] = GRID_WALL;
shape = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );
thecube = new THREE.Mesh( shape );
thecube.material = new THREE.MeshBasicMaterial( { map: wall_texture } );
thecube.position.copy ( translate(i,j) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
ABWorld.scene.add(thecube);
}
else
GRID[i][j] = GRID_BLANK;
// set up maze
for ( var c=1 ; c <= NOBOXES ; c++ )
{
i = AB.randomIntAtoB(1,gridsize-2); // inner squares are 1 to gridsize-2
j = AB.randomIntAtoB(1,gridsize-2);
GRID[i][j] = GRID_MAZE ;
shape = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );
thecube = new THREE.Mesh( shape );
thecube.material = new THREE.MeshBasicMaterial( { map: maze_texture } );
thecube.position.copy ( translate(i,j) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
ABWorld.scene.add(thecube);
}
// set up first baby
BABIES[0] = newBaby();
// set up agent
// start in random location
do
{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);
}
while ( occupied(i,j) ); // search for empty square
ai = i;
aj = j;
GRID[i][j] = GRID_AGENT;
shape = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );
theagent = new THREE.Mesh( shape );
theagent.material = new THREE.MeshBasicMaterial( { map: agent_texture } );
theagent.position.copy ( translate(ai,aj) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
ABWorld.scene.add(theagent);
// finally skybox
ABWorld.scene.background = new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY, function()
{
ABWorld.render();
console.log ( "Resources loaded." );
resourcesLoaded = true;
if ( resourcesLoaded && splashClicked )
AB.runReady = true; // start run loop
});
}
// --- babies -----------------------------------
function newBaby()
{
var i, j;
do
{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);
}
while ( occupied(i,j) ); // search for empty square
GRID[i][j] = GRID_BABY;
var shape = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
var thecube = new THREE.Mesh( shape );
thecube.material = new THREE.MeshBasicMaterial( { map: baby_texture } );
thecube.position.copy ( translate(i,j) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
ABWorld.scene.add(thecube);
return ( new Array ( thecube, i, j ) ); // save it for later, array, object plus i,j position
}
function newBabies() // make NONEWBABIES new babies
{
for ( var c=1 ; c <= NONEWBABIES ; c++ )
{
var baby = newBaby();
BABIES.push ( baby ); // add to array
}
}
function moveBabies()
{
for ( var c = 0; c < BABIES.length; c++ )
{
var thebaby = BABIES[c][0];
var ei = BABIES[c][1];
var ej = BABIES[c][2];
var i, j;
// move towards agent
// put some randomness in so it won't get stuck with barriers
if ( ei < ai ) i = AB.randomIntAtoB(ei, ei+1);
if ( ei == ai ) i = ei;
if ( ei > ai ) i = AB.randomIntAtoB(ei-1, ei);
if ( ej < aj ) j = AB.randomIntAtoB(ej, ej+1);
if ( ej == aj ) j = ej;
if ( ej > aj ) j = AB.randomIntAtoB(ej-1, ej);
if ( ! occupied(i,j) ) // if no obstacle then move, else just miss a turn for this baby
{
GRID[ei][ej] = GRID_BLANK;
GRID[i][j] = GRID_BABY;
thebaby.position.copy ( translate(i,j) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
BABIES[c] = new Array ( thebaby, i, j ); // update list of positions
}
}
}
// --- agent -----------------------------------
// movement caused by user actions
function moveAgent( a )
{
var i = ai;
var j = aj;
if ( a == ACTION_LEFT ) i--;
else if ( a == ACTION_RIGHT ) i++;
else if ( a == ACTION_UP ) j++;
else if ( a == ACTION_DOWN ) j--;
if ( ! occupied(i,j) ) // else just miss a turn
{
GRID[ai][aj] = GRID_BLANK;
GRID[i][j] = GRID_AGENT;
ai = i;
aj = j;
theagent.position.copy ( translate(ai,aj) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
}
}
// --- user actions - keyboard and touch handling functions: ----------------------------------------
var OURKEYS = [ 37, 38, 39, 40 ];
function ourKeys ( event ) { return ( OURKEYS.includes ( event.keyCode ) ); }
function keyHandler ( event )
{
if ( ! AB.runReady ) return true; // not ready yet
// if not handling this key, send it to default:
if ( ! ourKeys ( event ) ) return true;
// else handle it and prevent default:
if ( event.keyCode == 37 ) moveAgent ( ACTION_LEFT );
if ( event.keyCode == 38 ) moveAgent ( ACTION_DOWN );
if ( event.keyCode == 39 ) moveAgent ( ACTION_RIGHT );
if ( event.keyCode == 40 ) moveAgent ( ACTION_UP );
event.stopPropagation(); event.preventDefault(); return false;
}
var startX, startY;
var dragevents; // number of events in the current drag
function initDrag ( x, y ) // x,y position on screen
{
if ( ! AB.runReady ) return true; // not ready yet
startX = x;
startY = y;
dragevents = 0;
tryHitBabies ( x, y ); // check if this is a tap not a drag - and if it hits a baby
};
function drag ( x, y ) // compare with previous x,y position on screen to get direction of drag
{
if ( ! AB.runReady ) return true; // not ready yet
if ( ( dragevents % 4 ) == 0 ) // slow it down to respond to every nth event - too many events
{
if ( x > startX ) moveAgent ( ACTION_RIGHT );
else if ( x < startX ) moveAgent ( ACTION_LEFT );
if ( y > startY ) moveAgent ( ACTION_UP );
else if ( y < startY ) moveAgent ( ACTION_DOWN );
}
dragevents++;
startX = x;
startY = y;
};
// --- kill babies and make new ones -------------------------------------------------------------------
function tryHitBabies ( x, y ) // we did an x,y touch/click, did it hit a baby
{
for ( var c = 0; c < BABIES.length; c++ )
{
var thebaby = BABIES[c][0];
var ei = BABIES[c][1];
var ej = BABIES[c][2];
if ( ABWorld.hitsObject ( x, y, thebaby ) ) // detect hit object
{
soundScream(); // make a noise
score = score + SCORE_KILL ; // increase score
GRID[ei][ej] = GRID_DEAD ; // stationary obstacle - basically part of the maze now
thebaby.material = new THREE.MeshBasicMaterial( { map: dead_texture } );
// remove thebaby from array, so it will no longer move
BABIES.splice(c,1);
// make new baby/babies:
newBabies();
return; // assume only can hit one baby
}
}
}
// --- score and status: -----------------------------------
function babyClose()
// is a baby close to (within one square of) the agent
// note because of the wall, co-ordinates at +1 and -1 always exist
{
if ( GRID[ai-1][aj-1] == GRID_BABY ) return true;
if ( GRID[ai-1][aj] == GRID_BABY ) return true;
if ( GRID[ai-1][aj+1] == GRID_BABY ) return true;
if ( GRID[ai][aj-1] == GRID_BABY ) return true;
if ( GRID[ai][aj] == GRID_BABY ) return true;
if ( GRID[ai][aj+1] == GRID_BABY ) return true;
if ( GRID[ai+1][aj-1] == GRID_BABY ) return true;
if ( GRID[ai+1][aj] == GRID_BABY ) return true;
if ( GRID[ai+1][aj+1] == GRID_BABY ) return true;
return false;
}
function agentBlocked() // agent is blocked on the 4 compass sides (not diagonal)
{
return ( occupied (ai-1,aj) &&
occupied (ai+1,aj) &&
occupied ( ai,aj+1) &&
occupied ( ai,aj-1) );
}
AB.world.newRun = function()
{
score = 0;
if ( show3d )
{
BOXHEIGHT = squaresize;
ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
}
else
{
BOXHEIGHT = 1;
ABWorld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
}
// newRun can run behind splash screen
// do not start run loop until resources ready AND splash screen is dismissed
AB.runReady = false;
loadResources(); // aynch file loads
// calls initScene() when it returns
// redirect keyboard and touch event handling to my own functions:
document.onkeydown = keyHandler;
// override ABHandler default (which is camera control) to use my own functions:
myControls();
// Toggle between my mouse/touch controls and default mouse/touch controls
var buttons = " <p> <button onclick='myControls();' class='ab-normbutton mybutton' >My controls </button> " +
" <button onclick='ABHandler.defaultTouch(); ABHandler.defaultMouse();' class='ab-normbutton mybutton' >Default controls </button> </p> ";
AB.msg ( buttons, 3 );
};
function myControls()
{
ABHandler.initTouchDrag = initDrag;
ABHandler.touchDrag = drag
ABHandler.initMouseDrag = initDrag;
ABHandler.mouseDrag = drag
}
AB.world.nextStep = function ()
{
for ( var c = 0; c < BABIES.length; c++ )
if ( Math.random() < 0.05 )
soundMama(); // baby noises now and then, increasing as no. of babies increases
if ( ( AB.step % 2 ) == 0 ) moveBabies(); // slow babies down to every nth step
if ( babyClose() ) score = score + SCORE_CLOSE; // lose points every step you are close to a baby
AB.msg ( " Step: " + AB.step + " Score: <span style='" + scoreStyle + "'>" + score + "</span>" );
if ( agentBlocked() ) // if agent blocked in, run over
{
AB.abortRun = true;
musicPause();
soundAlarm();
}
};
B.world.endRun = function()
{
musicPause();
if ( AB.abortRun ) AB.msg ( " <br> <font color=red> <B> You are trapped. You lose! </B> </font> ", 2 );
else AB.msg ( " <br> <font color=green> <B> Run over. You survived! </B> </font> ", 2 );
};
// --- Background Music ----------------------------------------
var backmusic;
function initMusic() // called by user interaction
{
backmusic = AB.backgroundMusic ( MUSIC_BACK );
}
function musicPlay() { backmusic.play(); }
function musicPause() { backmusic.pause(); }
// --- Sound Effects ----------------------------------------
var thealarm = new Audio ( SOUND_ALARM );
var mama1 = new Audio( SOUND_MAMA );
var mama2 = new Audio( SOUND_MAMA );
var mama3 = new Audio( SOUND_MAMA );
var mama4 = new Audio( SOUND_MAMA );
var mama5 = new Audio( SOUND_MAMA );
var scream1 = new Audio( SOUND_SCREAM );
var scream2 = new Audio( SOUND_SCREAM );
var scream3 = new Audio( SOUND_SCREAM );
var scream4 = new Audio( SOUND_SCREAM );
var scream5 = new Audio( SOUND_SCREAM );
function soundAlarm()
{
thealarm.play();
}
function soundMama() // allow multiple sounds at same time
{
if ( ! AB.audioIsPlaying ( mama1 ) ) { mama1.play(); return; }
if ( ! AB.audioIsPlaying ( mama2 ) ) { mama2.play(); return; }
if ( ! AB.audioIsPlaying ( mama3 ) ) { mama3.play(); return; }
if ( ! AB.audioIsPlaying ( mama4 ) ) { mama4.play(); return; }
if ( ! AB.audioIsPlaying ( mama5 ) ) { mama5.play(); return; }
// else
var a = new Audio( SOUND_MAMA );
a.play();
}
function soundScream() // allow multiple sounds at same time
{
if ( ! AB.audioIsPlaying ( scream1 ) ) { scream1.play(); return; }
if ( ! AB.audioIsPlaying ( scream2 ) ) { scream2.play(); return; }
if ( ! AB.audioIsPlaying ( scream3 ) ) { scream3.play(); return; }
if ( ! AB.audioIsPlaying ( scream4 ) ) { scream4.play(); return; }
if ( ! AB.audioIsPlaying ( scream5 ) ) { scream5.play(); return; }
// else
var a = new Audio( SOUND_SCREAM );
a.play();
}
// --- Intro Screen --------------------------------------------------------------
function instructionsHtml() // HTML format string of instructions for splash screen
{
var s = "Pac-Man is an action maze chase video game. Run around the maze from the ghosts whilst clicking them to rack up your points! Game ends when you are overwhelmed and trapped by the ghosts!";
if ( AB.onDesktop() ) s = s + " Desktop instructions: Arrow keys or mouse drag to move. Click the ghosts to eat them. " ;
else s = s + " Mobile instructions: Touch drag to move. Touch the ghost to eat them. " ;
s = s + SCORE_KILL + " points for eating a ghost. " + SCORE_CLOSE + " points for every step you are close to a ghost.";
return ( s );
}
// display splash screen with instructions
AB.newSplash ( instructionsHtml() );
// touch on splash screen button will mark multiple audio objects as good for JS to call without further user interaction
AB.splashClick ( function ()
{
thealarm.play(); thealarm.pause();
mama1.play(); mama1.pause();
mama2.play(); mama2.pause();
mama3.play(); mama3.pause();
mama4.play(); mama4.pause();
mama5.play(); mama5.pause();
scream1.play(); scream1.pause();
scream2.play(); scream2.pause();
scream3.play(); scream3.pause();
scream4.play(); scream4.pause();
scream5.play(); scream5.pause();
initMusic();
AB.removeSplash(); // remove splash screen
// ready to start run loop?
splashClicked = true;
if ( resourcesLoaded && splashClicked )
AB.runReady = true; // start run loop
});