// Timothy Liu
// 15-104, Section C
// tcliu@andrew.cmu.edu
// FINAL PROJECT: Crossy Chicken, the game!
var showStart = true; // start off the game showing the start screen
var myChar; // the player (the chicken)
var charSpeed = 2; // speed player moves at
var borderW = 40; // border used in all popup screens
var firstCars = []; // arrays of cars (from bottom of screen to top)
var secondCars = [];
var thirdCars = [];
var fourthCars = [];
// road characteristics
var roadWidth = 100;
var roadLine = 2;
var carSpacing;
// river and bridge characteristics
var riverStart = 243; // identifies the top of the river
var riverWidth = 100; // identifies the width of the river
var riverEnd = riverStart + riverWidth; // identifies the bottom of the river
var bridgeWidth = 30; // width of the bridge
var bridgeMiddle = riverStart + riverWidth / 2; // the top part of the middle bridge segment
var randomBridgeStart; // where the first segment of the bridge starts
var randomBridgeLength; // how long the middle segment of the bridge is
// variables that help draw/locate the egg and nest
var eggLocY = 50;
var eggW = 12;
var eggH = 16;
var nestLocY = 60;
var nestShadowY = 57;
var nestW = 30;
var nestH = 18;
// sound
var soundtrack;
var winSound;
var loseSound;
// preload all sounds
function preload() {
// background track
soundtrack = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/froggertrack.wav");
soundtrack.setVolume(0.5);
// losing jingle
loseSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/lose.wav");
loseSound.setVolume(0.6);
// winning jingle
winSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/12/win.wav");
winSound.setVolume(0.9);
}
function setup() {
createCanvas(600, 600); // create the canvas
soundtrack.play(); // play the frogger soundtrack that's been preloaded
myChar = makeCharacter(); // makes the character
carSpacing = random(30, 45); // determines spacing between cars; ensures they don't hit each other (but varies the spacing randomly)
randomBridgeStart = random(50, 500); // determines the random location of the bridge
randomBridgeLength = random(60, 250); // determines the random length of the bridge
firstCarSetup(); // defines properties for all the different rows of cars
secondCarSetup(); // second row of cars (2nd from bottom)
thirdCarSetup(); // third row of cars (3rd from bottom)
fourthCarSetup(); // top row of cars
}
function draw() {
background(87, 168, 84); // grass floor
noStroke();
drawFirstRoad(); // draw in the bottom road
drawRiver(); // draw in the river
drawSecondRoad(); // draw in the top road
drawNest(); // draw in the nest
// draw in and move the character!
myChar.draw();
myChar.move();
// draws in the cars on the road using drawCars()
showFirstCars();
showSecondCars();
showThirdCars();
showFourthCars();
characterWins(); // function that runs when the character reaches the egg and wins
characterLoses(); // function that identifies when the character loses
startScreen(); // starting screen shows right at the beginning
}
// the starting screen that shows rules and game info
function startScreen() {
// right at start of game, this statement is true UNTIL SPACE IS PRESSED
if (showStart === true) {
noLoop();
// the frame/popup screen
fill(235, 131, 131);
rect(width / 6, height / 6, width * 2 / 3, height * 2 / 3);
fill(255);
rect(width / 6 + borderW / 2, height / 6 + borderW / 2, width * 2 / 3 - borderW, height * 2 / 3 - borderW);
// Start screen
fill(0);
textSize(20);
textAlign(CENTER); // center the text
textFont('Avenir'); // text font
// starting screen text: the intro!
textStyle(NORMAL);
text("Welcome to Crossy Chicken!", width / 2, 225);
text("Watch out for cars, and", width / 2, 255);
text("don't let your feet get wet!", width / 2, 285);
text("Use the WASD keys to move, and", width / 2, 345);
textStyle(BOLD);
text("press SPACE to get started!", width / 2, 375);
}
}
// triggers to start and reset the game
function keyPressed() {
if (keyCode === 32) { // if SPACEBAR is pressed, let the game begin!
showStart = false;
loop();
}
if (keyCode === ENTER) { // if ENTER is pressed, the level restarts!
restart(); // restart function resets the stage
}
return false; // prevents the webpage from moving when arrows are pressed
}
// this function initializes all variables and then reruns the setup function, effectively restarting the game
function restart() {
firstCars = []; // initialize all of the car arrays so they can be redrawn/setup
secondCars = [];
thirdCars = [];
fourthCars = [];
setup(); // re-run setup, which resets the stage
loop(); // start running the code again!
}
// define all character properties (the chicken)
function makeCharacter() {
return {
x: 300,
y: 550,
c: 255,
w: 25,
h: 40,
r: 25,
hX: 300,
hY: 490,
hW: 15,
hH: 20,
eyeC: 0,
eyeW: 2,
beakC: color(252, 202, 3),
crownW: 5,
crownH: 10,
crownC: color(207, 58, 58),
footW: 7,
footH: 4,
draw: drawCharacter,
move: moveCharacter
}
}
// uses the obj characteristics and var myChar to draw the chicken
function drawCharacter() {
var headOffset = 18;
var eyeOffsetX = 3;
var eyeOffsetY = 21;
var beakOffsetX = 4;
var beakOffsetY = 19;
var beakOffsetY2 = 15;
var crownOffsetY = 27;
var footOffset = 5;
// red crown on head
fill(myChar.crownC);
arc(myChar.x, myChar.y - crownOffsetY, myChar.crownW, myChar.crownH, PI, TWO_PI);
// feet
fill(myChar.beakC);
arc(myChar.x - footOffset, myChar.y, myChar.footW, myChar.footH, 0, PI);
arc(myChar.x + footOffset, myChar.y, myChar.footW, myChar.footH, 0, PI);
// head and body
fill(myChar.c);
arc(myChar.x, myChar.y, myChar.w, myChar.h, PI, TWO_PI);
arc(myChar.x, myChar.y - headOffset, myChar.hW, myChar.hH, PI, TWO_PI);
// eyes
fill(myChar.eyeC);
ellipse(myChar.x - eyeOffsetX, myChar.y - eyeOffsetY, myChar.eyeW, myChar.eyeW);
ellipse(myChar.x + eyeOffsetX, myChar.y - eyeOffsetY, myChar.eyeW, myChar.eyeW);
// beak
fill(myChar.beakC);
triangle(myChar.x - beakOffsetX, myChar.y - beakOffsetY, myChar.x + beakOffsetX, myChar.y - beakOffsetY,
myChar.x, myChar.y - beakOffsetY2);
}
// this function allows the character to move
function moveCharacter() {
if (keyIsDown(65)) { // if pressing the A key
myChar.x -= charSpeed; // move the character's x position left
if (myChar.x + myChar.r < 0) { // if the character moves off the screen, have it wrap to the other side
myChar.x = width + myChar.r;
}
}
if (keyIsDown(68)) { // if pressing the D key
myChar.x += charSpeed; // move the character's x position right
if (myChar.x - myChar.r > width) { // if the character moves off the screen, have it wrap to the other side
myChar.x = -myChar.r;
}
}
if (keyIsDown(87)) { // if pressing the W key
myChar.y -= charSpeed; // move the character's y position up
}
if (keyIsDown(83)) { // if pressing the S key
myChar.y += charSpeed; // move the character's y position down
if (myChar.y + myChar.r > height) { // if it hits the bottom, don't let it go off the page
myChar.y = height - myChar.r;
}
}
}
// what happens if the character reaches the egg
function characterWins() {
// if the character reaches the egg successfully
if (myChar.y > eggLocY - eggH / 2 & myChar.y < nestLocY + nestH / 2) {
if (myChar.x > width / 2 - nestW / 2 && myChar.x < width / 2 + nestW / 2) {
noLoop(); // game stops
winningScreen(); // display winning screen
winSound.play(); // play winning jingle!
}
}
}
// this function determines if the character loses using two other functions: hit by car, or fall in river
function characterLoses() {
characterHitByCar(); // function if myChar is hit by a car
characterFallsInRiver(); // function if myChar falls into the river
}
// if the character is hit by a car
function characterHitByCar() {
// if char is hit by first row of cars
for (var i = 0; i < firstCars.length; i++) {
var carMiddleX = firstCars[i].x + (firstCars[i].w / 2);
var carMiddleY = firstCars[i].y + (firstCars[i].h / 2);
var charMiddleX = myChar.x;
var charMiddleY = myChar.y - (myChar.h / 2);
// if the edges of the character fall within the edges of the car, you lose!
if (charMiddleX - myChar.w / 4 < carMiddleX + firstCars[i].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - firstCars[i].w / 2
&& myChar.y > carMiddleY - firstCars[i].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + firstCars[i].h / 2) {
noLoop(); // game stops
losingScreenCar(); // show losing screen
loseSound.play(); // losing sound plays
}
}
// if char is hit by second row of cars
for (var j = 0; j < secondCars.length; j++) {
var carMiddleX = secondCars[j].x + (secondCars[j].w / 2);
var carMiddleY = secondCars[j].y + (secondCars[j].h / 2);
var charMiddleX = myChar.x;
var charMiddleY = myChar.y - (myChar.h / 2);
// if the edges of the character fall within the edges of the car, you lose!
if (charMiddleX - myChar.w / 4 < carMiddleX + secondCars[j].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - secondCars[j].w / 2
&& myChar.y > carMiddleY - secondCars[j].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + secondCars[j].h / 2) {
noLoop(); // game stops
losingScreenCar(); // show losing screen
loseSound.play(); // losing sound plays
}
}
// if char is hit by third row of cars
for (var k = 0; k < thirdCars.length; k++) {
var carMiddleX = thirdCars[k].x + (thirdCars[k].w / 2);
var carMiddleY = thirdCars[k].y + (thirdCars[k].h / 2);
var charMiddleX = myChar.x;
var charMiddleY = myChar.y - (myChar.h / 2);
// if the edges of the character fall within the edges of the car, you lose!
if (charMiddleX - myChar.w / 4 < carMiddleX + thirdCars[k].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - thirdCars[k].w / 2
&& myChar.y > carMiddleY - thirdCars[k].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + thirdCars[k].h / 2) {
noLoop(); // game stops
losingScreenCar(); // show losing screen
loseSound.play(); // losing sound plays
}
}
// if char is hit by fourth row of cars
for (var l = 0; l < fourthCars.length; l++) {
var carMiddleX = fourthCars[l].x + (fourthCars[l].w / 2);
var carMiddleY = fourthCars[l].y + (fourthCars[l].h / 2);
var charMiddleX = myChar.x;
var charMiddleY = myChar.y - (myChar.h / 2);
// if the edges of the character fall within the edges of the car, you lose!
if (charMiddleX - myChar.w / 4 < carMiddleX + fourthCars[l].w / 2 & charMiddleX + myChar.w / 4 > carMiddleX - fourthCars[l].w / 2
&& myChar.y > carMiddleY - fourthCars[l].h / 2 && charMiddleY - myChar.h / 5 < carMiddleY + fourthCars[l].h / 2) {
noLoop(); // game stops
losingScreenCar(); // show losing screen (car version)
loseSound.play(); // losing sound plays
}
}
}
// if the strays from the bridge and falls into the river
function characterFallsInRiver() {
// if the character is crossing the river...
if (myChar.y > riverStart & myChar.y < riverEnd) {
// first segment of the bridge (vertical)
if (myChar.y > bridgeMiddle + bridgeWidth && myChar.y < riverEnd) {
// if char is outside the bridge boundaries
if (myChar.x < randomBridgeStart || myChar.x > randomBridgeStart + bridgeWidth) {
noLoop(); // game stops
losingScreenRiver(); // show losing screen (splash version)
loseSound.play(); // play losing sound
}
}
// the bridge randomly goes left or right based on starting position...
// if the bridge goes rightward:
if (randomBridgeStart < width / 2) {
// second segment of the bridge (horizontal)
if (myChar.y > bridgeMiddle & myChar.y < bridgeMiddle + bridgeWidth) {
// if char is outside the boundaries of the horizontal bridge segment
if (myChar.x < randomBridgeStart || myChar.x > randomBridgeStart + randomBridgeLength + bridgeWidth) {
noLoop(); // game stops
losingScreenRiver(); // show losing screen (splash version)
loseSound.play(); // play losing sound
}
}
// last segment of the bridge (vertical)
if (myChar.y > riverStart & myChar.y < bridgeMiddle) {
if (myChar.x < randomBridgeStart + randomBridgeLength || myChar.x > randomBridgeStart + randomBridgeLength + bridgeWidth) {
noLoop(); // game stops
losingScreenRiver(); // show losing screen (splash version)
loseSound.play(); // play losing sound
}
}
// if the bridge goes leftward:
} else {
// second segment of the bridge (horizontal)
if (myChar.y > bridgeMiddle & myChar.y < bridgeMiddle + bridgeWidth) {
if (myChar.x < randomBridgeStart - randomBridgeLength || myChar.x > randomBridgeStart + bridgeWidth) {
noLoop(); // game stops
losingScreenRiver(); // show losing screen (splash version)
loseSound.play(); // play losing sound
}
}
// last segment of the bridge (vertical)
if (myChar.y > riverStart & myChar.y < bridgeMiddle) {
if (myChar.x < randomBridgeStart - randomBridgeLength || myChar.x > randomBridgeStart - randomBridgeLength + bridgeWidth) {
noLoop(); // game stops
losingScreenRiver(); // show losing screen (splash version)
loseSound.play(); // play losing sound
}
}
}
}
}
// the popup screen when you win
function winningScreen() {
soundtrack.stop(); // stop background sound
// border + screen
fill(255, 213, 0);
rect(width / 4, height / 4, width / 2, height / 2);
fill(255);
rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);
// winning screen text for when you make it to the egg
fill(0);
textSize(20);
textAlign(CENTER); // center the text
textFont('Avenir'); // text font
// text:
textStyle(NORMAL);
text("You protected your egg;", width / 2, 270);
textStyle(BOLD);
text("Congratulations!", width / 2, 300);
textStyle(NORMAL);
text("Hit ENTER to play again!", width / 2, 330);
}
// the popup screen when you're hit by a car
function losingScreenCar() {
soundtrack.stop();
fill(235, 131, 131);
rect(width / 4, height / 4, width / 2, height / 2);
fill(255);
rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);
// losing screen text for when a car runs into you
fill(0);
textSize(20);
textAlign(CENTER); // center the text
textFont('Avenir'); // text font
// text:
textStyle(NORMAL);
text("Oh no, a car got you!", width / 2, 285);
text("Hit ENTER to try again!", width / 2, 315);
}
// the popup screen when you fall into the river
function losingScreenRiver() {
soundtrack.stop();
fill(53, 99, 240);
rect(width / 4, height / 4, width / 2, height / 2);
fill(255);
rect(width / 4 + borderW / 2, height / 4 + borderW / 2, width / 2 - borderW, height / 2 - borderW);
// losing screen text for when you fall into the water
fill(0);
textSize(20);
textAlign(CENTER); // center the text
textFont('Avenir'); // text font
// text:
textStyle(BOLD); // bold the sound effect (splash)
text("Splash...", width / 2, 270);
textStyle(NORMAL);
text("You fell into the water!", width / 2, 300);
text("Hit ENTER to try again!", width / 2, 330);
}
// draw in the bottom (first) road
function drawFirstRoad() {
var roadLocX = 0;
var roadLocY = 385;
var lineLocY = 434;
fill(180);
rect(roadLocX, roadLocY, width, roadWidth); // draws the road
fill(245, 224, 66);
rect(roadLocX, lineLocY, width, roadLine); // the dividing yellow line
}
// draw in the top (second) road
function drawSecondRoad() {
var roadLocX = 0;
var roadLocY = 100;
var lineLocY = 149;
fill(180);
rect(roadLocX, roadLocY, width, roadWidth); // draws the road
fill(245, 224, 66);
rect(roadLocX, lineLocY, width, roadLine); // the dividing yellow line
}
// draw in the river
function drawRiver() {
var riverEdgeL = 0;
fill(100, 100, 255); // blue color
rect(riverEdgeL, riverStart, width, riverWidth);
drawBridge(); // draw in bridge using bridge function
}
// draws the bridge the crosses the river. the bridge is randomly generated each time the game is played!
// it will go rightward if the starting point is on the left of the screen, and leftward if the starting point is to the right
function drawBridge() {
fill(186, 145, 104); // brown bridge color
rect(randomBridgeStart, bridgeMiddle, bridgeWidth, riverWidth / 2); // first bridge segment
// this if-else statement determines which direction the bridge goes in
if (randomBridgeStart < width / 2) {
rect(randomBridgeStart, bridgeMiddle, randomBridgeLength, bridgeWidth); // second bridge segment
rect(randomBridgeStart + randomBridgeLength, riverStart, bridgeWidth, riverWidth / 2 + bridgeWidth); // final bridge segment
} else {
rect(randomBridgeStart - randomBridgeLength, bridgeMiddle, randomBridgeLength, bridgeWidth); // second bridge segment
rect(randomBridgeStart - randomBridgeLength, riverStart, bridgeWidth, riverWidth / 2 + bridgeWidth); // final bridge segment
}
}
// characteristics of the car objects
function makeCars(px, py) {
return {
x: px,
y: py,
c: color(random(255), random(255), random(255)),
w: 50,
h: 30,
lightsW: 2,
lightsH: 5,
lightsC: color(242, 235, 19),
tireW: 9,
tireH: 3,
tireC: color(0, 0, 0),
windshieldW: 4,
windshieldH: 25,
windshieldC: color(100, 100, 100),
draw: drawCars
}
}
// uses the obj characteristics to draw cars
function drawCars() {
var tireOffsetX = 6;
var tireOffsetY = 2;
var windshieldOffsetX = 10;
var windshieldOffsetY = 2.5;
// car body
fill(this.c);
rect(this.x, this.y, this.w, this.h);
// car headlights (x4)
fill(this.lightsC);
rect(this.x + this.w - this.lightsW, this.y, this.lightsW, this.lightsH);
rect(this.x + this.w - this.lightsW, this.y + this.h - this.lightsH, this.lightsW, this.lightsH);
rect(this.x, this.y, this.lightsW, this.lightsH);
rect(this.x, this.y + this.h - this.lightsH, this.lightsW, this.lightsH);
// car tires
fill(this.tireC);
rect(this.x + this.w - (2.5 * tireOffsetX), this.y - tireOffsetY, this.tireW, this.tireH);
rect(this.x + tireOffsetX, this.y - tireOffsetY, this.tireW, this.tireH);
rect(this.x + this.w - (2.5 * tireOffsetX), this.y + this.h - (tireOffsetY / 2), this.tireW, this.tireH);
rect(this.x + tireOffsetX, this.y + this.h - (tireOffsetY / 2), this.tireW, this.tireH);
// car windshields
fill(this.windshieldC);
rect(this.x + (3.5 * windshieldOffsetX), this.y + windshieldOffsetY, this.windshieldW, this.windshieldH);
rect(this.x + (1.2 * windshieldOffsetX), this.y + windshieldOffsetY, this.windshieldW, this.windshieldH);
if (this.x > width) {
this.x = -this.w;
}
if (this.x + this.w < 0) {
this.x = width;
}
}
// draws the nest using nest variables
function drawNest() {
// the nest
fill(219, 181, 75);
ellipse(width / 2, nestLocY, nestW * 1.3, nestH);
fill(163, 129, 34);
ellipse(width / 2, nestShadowY, nestW, nestH / 2);
// the egg
fill(250, 242, 220);
ellipse(width / 2, eggLocY, eggW, eggH);
}
// setting up the first row of cars and placing them (very bottom row of cars)
function firstCarSetup() {
var carPlacement; // car placement variable
var numCars = 5; // number of cars in the first row of cars
var carLocY = 445;
var newCar = makeCars();
firstCars.push(newCar); // add each new car to the empty array firstCars
// place each car in the first row of cars and add to the array firstCars
for (var i = 0; i < numCars; i++) {
carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
firstCars[i] = makeCars(carPlacement, carLocY); // placement of the first row of cars
}
}
// setting up the second row of cars and placing them
function secondCarSetup() {
var carPlacement; // car placement variable
var numCars = 5; // number of cars in the first row of cars
var carLocY = 395;
var newCar = makeCars();
secondCars.push(newCar); // add each new car to the empty array secondCars
// place each car in the second row of cars and add to the array secondCars
for (var i = 0; i < numCars; i++) {
carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
secondCars[i] = makeCars(carPlacement, carLocY);
}
}
// setting up the third row of cars and placing them
function thirdCarSetup() {
var carPlacement; // car placement variable
var numCars = 5; // number of cars in the first row of cars
var carLocY = 160;
var newCar = makeCars();
thirdCars.push(newCar); // add each new car to the empty array secondCars
// place each car in the third row of cars and add to the array secondCars
for (var i = 0; i < numCars; i++) {
carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
thirdCars[i] = makeCars(carPlacement, carLocY);
}
}
// setting up the fourth row of cars and placing them
function fourthCarSetup() {
var carPlacement; // car placement variable
var numCars = 5; // number of cars in the first row of cars
var carLocY = 110;
var newCar = makeCars();
fourthCars.push(newCar); // add each new car to the empty array secondCars
// place each car in the fourth row of cars and add to the array secondCars
for (var i = 0; i < numCars; i++) {
carPlacement = random((i * width / numCars) + carSpacing, ((i + 1) * width / numCars) - carSpacing);
fourthCars[i] = makeCars(carPlacement, carLocY);
}
}
// draws the first row of cars (closest to the bottom of the screen)
function showFirstCars() {
for (var i = 0; i < firstCars.length; i++) {
firstCars[i].draw(); // draws in cars
firstCars[i].x += 1; // makes the cars drive rightward on the first road (bottom)
}
}
// draws the second row of cars (next row up from bottom of the screen)
function showSecondCars() {
for (var i = 0; i < secondCars.length; i++) {
secondCars[i].draw();
secondCars[i].x -= 1; // makes the cars move in the opposite direction (leftward) on the first road
}
}
// draws the third row of cars (next row up from bottom of the screen)
function showThirdCars() {
for (var i = 0; i < thirdCars.length; i++) {
thirdCars[i].draw();
thirdCars[i].x += 1.8; // faster cars to increase the difficulty (rightward, top road)!
}
}
// draws the fourth row of cars (row of cars near top of screen)
function showFourthCars() {
for (var i = 0; i < fourthCars.length; i++) {
fourthCars[i].draw();
fourthCars[i].x -= 1.8; // faster cars in opposite direction to increase the difficulty (leftward, top road)!
}
}
Welcome to Crossy Chicken, my final project for 15-104! A tribute to the original Frogger, Crossy Chicken involves a chicken trying to reach her beloved egg. But to get there, the chicken must first cross two busy streets as well as a perilous bridge; can you get her back to her egg?
To play, use the WASD keys (W = Up, S = Down, A = Left, D = Right) to direct the chicken and follow the instructions on screen! Make sure you dodge all the cars and don’t let the chicken’s feet touch the water!
I had a fantastic time coding this project, and I’m proud of how I distributed the work. I broke my project down into phases, and I worked through them in the following order:
- Character movement
- Introducing cars
- Adding roads
- Creating the river and bridge
- Adding the next 3 rows of cars
- Losing screen
- Winning screen
- Reset button (ENTER)
- Start button (SPACE)
- Adding sounds
In the end, I wound up with what I felt was a very polished and complete game. I’m really happy that I was able to build a visually pleasing game with fun and retro graphics while using a multitude of elements from p5js. From objects, to loops, to sound, my game really has a bit of everything in it. The soundtrack (which is actually the original Frogger music!) adds an element of excitement and nostalgia, and the losing and winning sound effects complete the experience for the gamer. I hope you have a great time playing!
(*Note 2: as is typically the case with p5js, the sound is sometimes a bit glitchy on Chrome and certain other browsers. However, the implementation of sound is all correct, as can be seen in my code.)