1./*
"driving.js" is a simple driving simulator written to introduce students to
the basics of game programming.
Author: Bob Jones & Raven Lord
Date: October 10th 2016
Usage: This script is invoked by loading "driving.html" in a browser.
*/
// Game Parameters
/* Note that ANIMDELTA should be a factor of BLOCKSIZE (1 for per-pixel
animation, higher for performance, BLOCKSIZE for charm) */
const BLOCKSIZE = 20; // Most dimensions are multiples of this
const ANIMDELTA = 20; // Should be a factor of BLOCKSIZE (read above)
const DIFFICULTYPERIOD = 1000; // Period between difficulty increases in ms
const INITTIMEDELTA = 60; // Initial delay between frames in ms
const MINTIMEDELTA = 5; // Browser is highly unlikely to go any faster
const TITLE = "Driving Game";
const PAGEMARGIN = 20;
const PAGECOLOUR = "White";
const MAINFONTCOLOUR = "Black";
const HEADINGBGCOLOUR = "White";
const INSBGCOLOUR = "White";
const PLAYAREAWIDTH = 35 * BLOCKSIZE;
const PLAYAREAHEIGHT = 25 * BLOCKSIZE;
const PLAYAREACOLOUR = "OliveDrab";
const PLAYAREAZINDEX = "-5";
const ROADWIDTH = 11 * BLOCKSIZE; // Best as odd number of blocks
const ROADCOLOUR = "Silver";
const ROADZINDEX = "-4";
const ROADINITPOS = Math.floor((PLAYAREAWIDTH - ROADWIDTH) / 2); // halfway
const NUMROADPIECES = Math.floor(PLAYAREAHEIGHT/ANIMDELTA);
const ROADGOESLEFT = 0.4; // roadDiceThrow < this value, road curves left
const ROADGOESRIGHT = 0.6; // roadDiceThrow > this value, road curves right
const CARCOLOUR = "Yellow";
const CARZINDEX = "-3";
const CARINITPOS = Math.floor((PLAYAREAWIDTH - BLOCKSIZE) / 2); // halfway
const FLASHCOLOUR = "Red"; // Colour used in death animation
const FLASHPERIOD = 50; // Controls rate of flashing in death animation
const NUMFLASHES = 20; // Control how long the death animation goes for
// Setup the page
document.body.style.margin = PAGEMARGIN + "px";
document.body.style.background = PAGECOLOUR;
document.body.style.textAlign = "center";
var pageTitle = document.createElement("title");
pageTitle.innerHTML = TITLE;
document.head.appendChild(pageTitle);
var pageHeading = document.createElement("div");
pageHeading.style.width = (PLAYAREAWIDTH - 20) + "px";
pageHeading.style.color = MAINFONTCOLOUR;
pageHeading.style.background = HEADINGBGCOLOUR;
pageHeading.style.textAlign = "center";
pageHeading.style.font = "bold 20px Verdana,Geneva,sans-serif";
pageHeading.style.margin = "0px auto 0px auto";
pageHeading.style.padding = "10px 10px 10px 10px";
pageHeading.innerHTML = TITLE;
document.body.appendChild(pageHeading);
var scoreBox = document.createElement("div");
scoreBox.style.width = (PLAYAREAWIDTH - 20) + "px";
scoreBox.style.color = MAINFONTCOLOUR;
scoreBox.style.background = HEADINGBGCOLOUR;
scoreBox.style.textAlign = "center";
scoreBox.style.font = "14px Verdana,Geneva,sans-serif";
scoreBox.style.margin = "0px auto 0px auto";
scoreBox.style.padding = "0px 10px 10px 10px";
scoreBox.innerHTML = "Score: 0";
document.body.appendChild(scoreBox);
// Create the playing area
var playArea = document.createElement("div");
playArea.style.width = PLAYAREAWIDTH + "px";
playArea.style.height = PLAYAREAHEIGHT + "px";
playArea.style.margin = "0px auto 0px auto";
playArea.style.background = PLAYAREACOLOUR;
playArea.style.position = "relative";
playArea.style.zIndex = PLAYAREAZINDEX;
document.body.appendChild(playArea);
// Display the instructions
var instructions = document.createElement("div");
instructions.style.width = (PLAYAREAWIDTH - 14) + "px";
instructions.style.color = MAINFONTCOLOUR;
instructions.style.background = INSBGCOLOUR;
instructions.style.textAlign = "center";
instructions.style.font = "14px Verdana,Geneva,sans-serif";
instructions.style.margin = "0px auto 0px auto";
instructions.style.padding = "7px";
instructions.innerHTML = "<B>S</B> Start ◀ Left ▶ Right";
document.body.appendChild(instructions);
// Create the road
var roadPieces = []; // an array of the rectangles that make up the road
var roadPiece = 0; // will serve as the index of the roadPieces array
for (roadPiece = 0; roadPiece < NUMROADPIECES ; roadPiece++)
{
roadPieces[roadPiece] = document.createElement("div");
roadPieces[roadPiece].style.width = ROADWIDTH + "px";
roadPieces[roadPiece].style.height = ANIMDELTA + "px";
roadPieces[roadPiece].style.background = ROADCOLOUR;
roadPieces[roadPiece].style.position = "absolute";
roadPieces[roadPiece].style.zIndex = ROADZINDEX;
roadPieces[roadPiece].style.left = ROADINITPOS + "px";
roadPieces[roadPiece].style.top = (roadPiece * ANIMDELTA) + "px";
playArea.appendChild(roadPieces[roadPiece]);
}
var carOnPiece = Math.floor(3/4*NUMROADPIECES); // constant in this version
// Create the car (the manipulation below results in a triangle)
var car = document.createElement("div");
car.style.width = BLOCKSIZE + "px";
car.style.height = BLOCKSIZE + "px";
car.style.background = CARCOLOUR;
car.style.position = "absolute";
car.style.zIndex = CARZINDEX;
car.style.left = CARINITPOS + "px";
car.style.top = roadPieces[carOnPiece].style.top;
playArea.appendChild(car);
// Listen for key presses
var gameIsRunning = false;
var changeInCarPos = 0; // used for steering
document.onkeydown = checkDown;
function checkDown(e)
{
if(e.keyCode === 37) changeInCarPos = -ANIMDELTA; // left cursor
else if(e.keyCode === 39) changeInCarPos = ANIMDELTA; // right cursor
else if(e.keyCode === 83 && !gameIsRunning) // S key
{
gameIsRunning = true;
runGame();
}
}
document.onkeyup = checkLift;
function checkLift(e)
{
if (
(e.keyCode === 37 && changeInCarPos < 0) || // left
(e.keyCode === 39 && changeInCarPos > 0) // right
)
changeInCarPos = 0;
}
// The game loop
function runGame()
{
var score = 0; // The score
var timeDelta = INITTIMEDELTA; // Initial delay between each frame
var flashNumber = NUMFLASHES; // Flashes left on death animation
var carLeftEdge = CARINITPOS;
var roadLeftEdges = [];
for (roadPiece = 0; roadPiece < NUMROADPIECES ; roadPiece++)
roadLeftEdges[roadPiece] = ROADINITPOS;
var changedLeftRoadEdge = ROADINITPOS;
var roadDiceThrow = 0;
var changeInRoadPos = 0; // note: changeInCarPos is defined above
// reset positions of the road and car
render();
// start difficulty timer
var difficultyTimer = setTimeout(increaseDifficulty, DIFFICULTYPERIOD);
function increaseDifficulty()
{
if (gameIsRunning)
{
if (timeDelta > MINTIMEDELTA) timeDelta--;
difficultyTimer = setTimeout(increaseDifficulty, DIFFICULTYPERIOD);
}
}
// start the game loop
var frame = setTimeout(gameLoop, timeDelta);
function gameLoop()
{
// calculate movement
carLeftEdge += changeInCarPos;
if (carLeftEdge < 0)
carLeftEdge = 0;
else if (carLeftEdge > (PLAYAREAWIDTH - BLOCKSIZE))
carLeftEdge = PLAYAREAWIDTH - BLOCKSIZE;
roadDiceThrow = Math.random();
if (roadDiceThrow < ROADGOESLEFT) changeInRoadPos = -ANIMDELTA;
else if (roadDiceThrow >= ROADGOESRIGHT) changeInRoadPos = ANIMDELTA;
else changeInRoadPos = 0;
changedLeftRoadEdge += changeInRoadPos;
if (changedLeftRoadEdge < 0)
changedLeftRoadEdge = 0;
else if (changedLeftRoadEdge > (PLAYAREAWIDTH - ROADWIDTH))
changedLeftRoadEdge = PLAYAREAWIDTH - ROADWIDTH;
for (roadPiece = NUMROADPIECES - 1; roadPiece >= 1; roadPiece --)
roadLeftEdges[roadPiece] = roadLeftEdges[roadPiece -1];
roadLeftEdges[0] = changedLeftRoadEdge;
// render new physical state
render();
// collision detection
if(
(carLeftEdge < roadLeftEdges [carOnPiece]) ||
(carLeftEdge + BLOCKSIZE > roadLeftEdges[carOnPiece] + ROADWIDTH)
)
{
gameIsRunning = false;
frame = setTimeout(deathAnimation, FLASHPERIOD);
}
// scoring
score++;
// loop again if the game is still going
if (gameIsRunning) frame = setTimeout (gameLoop, timeDelta);
}
function render()
{
for (roadPiece = 0; roadPiece < NUMROADPIECES ; roadPiece++)
roadPieces[roadPiece].style.left = roadLeftEdges[roadPiece] + "px";
car.style.left = carLeftEdge + "px";
//scoreBox.innerHTML = "Score: " + score + " (" + timeDelta + ")";
scoreBox.innerHTML = "Score: " + score;
}
function deathAnimation()
{
if (flashNumber % 2 == 0)
car.style.background = FLASHCOLOUR;
else
car.style.background = CARCOLOUR;
flashNumber--;
if (flashNumber > 0 && !gameIsRunning)
frame = setTimeout(deathAnimation, FLASHPERIOD);
else
car.style.background = CARCOLOUR;
}
}