Sandbox
About the Game
Sandbox is quite simply and literally a sandbox. You can adjust the size of pixels and resize the window to any dimensions, then start dropping in sand. There are a few things to choose from with each having unique interactions either on its own or with other objects. Fire will burn away sand and plants, while plants will grow across the space and prevent sand from falling. Create, change, and destroy anything you'd like within this simple game of creativity and experimentation.
Developed from January 2020 to February 2020.
Play the game here!
Role in Development
This sandbox game is a simple project that demonstrates my skills and knowledge of JavaScript and creating games in the web browser. Though I don't plan on usually making games in the web browser, I wanted to make sure that I had the capability of creating one as such. I developed the entirety of the simulation myself, creating the physics system, collision detection, and unique properties of each object within the expandable grid space.
Script Examples
Index.jsdxlLIB.js'use strict'; let canvas; let ctx; let canvasWidth; let canvasHeight; let mouseDown = false; let mouseXPos; let mouseYPos; let updateTime = 10; //let walkerSpeed = 1000/12; let thread; let pixelWidth = 10; let block = { x:0, y:0, typeIndex: 1, color: '#2e2e2e', width: pixelWidth, gravity: 0, update() { //worldGraph[this.x][this.y] = this.typeIndex; } } let sand = { x:0, y:0, typeIndex: 2, color: "white", width: pixelWidth, gravity: 1, slideRadius: 3, update() { // Check if not at bottom if (this.y + (this.gravity * pixelWidth) < canvasHeight) { // Check if able to fall if (dxlLIB.checkValidPosition(this.x, this.y + (this.gravity * pixelWidth), [0])) { worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.y += this.gravity * pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update sand into new spot } // Check if should "slide" off to the left else if (dxlLIB.checkValidPosition(this.x - pixelWidth, this.y + (this.gravity * pixelWidth), [0]) && dxlLIB.checkValidPosition(this.x - pixelWidth, this.y, [0])) { worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.x -= pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update sand into new spot } // Check if should "slide" off to the right else if (dxlLIB.checkValidPosition(this.x + pixelWidth, this.y + (this.gravity * pixelWidth), [0]) && dxlLIB.checkValidPosition(this.x + pixelWidth, this.y, [0])) { worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.x += pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update sand into new spot } } } } let fire = { x:0, y:0, typeIndex: 3, color: "red", width: pixelWidth, gravity: -1, liveTime: 3, liveCounter: 0, update() { // Check if fire can spread // Left if (dxlLIB.checkValidPosition(this.x - pixelWidth, this.y, [2, 4, 5])) { let newObject = Object.assign({}, fire); newObject.x = this.x - pixelWidth; newObject.y = this.y; dxlLIB.replaceObject(newObject); } // Right if (dxlLIB.checkValidPosition(this.x + pixelWidth, this.y, [2, 4, 5])) { let newObject = Object.assign({}, fire); newObject.x = this.x + pixelWidth; newObject.y = this.y; dxlLIB.replaceObject(newObject); } // Up if (dxlLIB.checkValidPosition(this.x, this.y - pixelWidth, [2, 4, 5])) { let newObject = Object.assign({}, fire); newObject.x = this.x; newObject.y = this.y - pixelWidth; dxlLIB.replaceObject(newObject); } // Down if (dxlLIB.checkValidPosition(this.x, this.y + pixelWidth, [2, 4, 5])) { let newObject = Object.assign({}, fire); newObject.x = this.x; newObject.y = this.y + pixelWidth; dxlLIB.replaceObject(newObject); } // Check if not at top if (this.y + (this.gravity * pixelWidth) > 0) { // Check if able to rise if (dxlLIB.checkValidPosition(this.x, this.y + (this.gravity * pixelWidth), [0])) { worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.y += this.gravity * pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update fire into new spot this.liveCounter++; } else this.liveCounter = this.liveTime; } else this.liveCounter = this.liveTime; // Check if dead if (this.liveCounter >= this.liveTime) dxlLIB.removeObject(this); } } let plant = { x:0, y:0, typeIndex: 4, color: "green", width: pixelWidth, gravity: 0, growChance: 0.01, growDir: -1, update() { if (this.growDir == -1) { // First randomize direction this.growDir = Math.round(Math.random() * 3); } // Then random chance of growing // Left else if (this.growDir == 0) { if (dxlLIB.checkValidPosition(this.x - pixelWidth, this.y, [0]) && Math.random() < this.growChance) { let newObject = Object.assign({}, plant); newObject.x = this.x - pixelWidth; newObject.y = this.y; worldObjects.push(newObject); worldGraph[newObject.x][newObject.y] = newObject.typeIndex this.growDir = -1; } else if (Math.random() < this.growChance) { // Can't grow anymore this.growDir = -1; } } // Right else if (this.growDir == 1) { if (dxlLIB.checkValidPosition(this.x + pixelWidth, this.y, [0]) && Math.random() < this.growChance) { let newObject = Object.assign({}, plant); newObject.x = this.x + pixelWidth; newObject.y = this.y; worldObjects.push(newObject); worldGraph[newObject.x][newObject.y] = newObject.typeIndex; this.growDir = -1; } else if (Math.random() < this.growChance) { // Can't grow anymore this.growDir = -1; } } // Up else if (this.growDir == 2) { if (dxlLIB.checkValidPosition(this.x, this.y - pixelWidth, [0]) && Math.random() < this.growChance) { let newObject = Object.assign({}, plant); newObject.x = this.x; newObject.y = this.y - pixelWidth; worldObjects.push(newObject); worldGraph[newObject.x][newObject.y] = newObject.typeIndex; this.growDir = -1; } else if (Math.random() < this.growChance) { // Can't grow anymore this.growDir = -1; } } // Down else if (this.growDir == 3) { if (dxlLIB.checkValidPosition(this.x, this.y + pixelWidth, [0]) && Math.random() < this.growChance) { let newObject = Object.assign({}, plant); newObject.x = this.x; newObject.y = this.y + pixelWidth; worldObjects.push(newObject); worldGraph[newObject.x][newObject.y] = newObject.typeIndex; this.growDir = -1; } else if (Math.random() < this.growChance) { // Can't grow anymore this.growDir = -1; } } } } let ball = { x:0, y:0, typeIndex: 5, color: dxlLIB.getRandomColor(), width: pixelWidth, gravity: 0, speed: 3, speedCounter: 0, direction: Math.round(Math.random() * 7), update() { // Up if (this.direction == 0) { // Check if at the top of the screen if (this.y - pixelWidth > 0) { // Check if hitting something if (dxlLIB.checkValidPosition(this.x, this.y - pixelWidth, [1, 2, 3, 4, 5])) { dxlLIB.removeObject(worldGraph[this.x][this.y - pixelWidth]); // Reverse direction this.direction = Math.round(Math.random() * 2) + 3; } else { // Move up worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.y -= pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update ball into new spot } } else { // Reverse direction this.direction = Math.round(Math.random() * 2) + 3; } } // Up right else if (this.direction == 1) { // Check if at the top of the screen if (this.y - pixelWidth > 0 && this.x + pixelWidth < Math.round(canvasWidth / pixelWidth) * pixelWidth) { // Check if hitting something if (dxlLIB.checkValidPosition(this.x + pixelWidth, this.y - pixelWidth, [1, 2, 3, 4, 5])) { dxlLIB.removeObject(worldGraph[this.x + pixelWidth][this.y - pixelWidth]); // Reverse direction this.direction = Math.round(Math.random() * 2) + 4; } else { // Move up right worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.x += pixelWidth; this.y -= pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update ball into new spot } } else { // Reverse direction this.direction = Math.round(Math.random() * 2) + 4; } } // Right else if (this.direction == 2) { // Check if at the top of the screen if (this.x + pixelWidth < Math.round(canvasWidth / pixelWidth) * pixelWidth) { // Check if hitting something if (dxlLIB.checkValidPosition(this.x + pixelWidth, this.y, [1, 2, 3, 4, 5])) { dxlLIB.removeObject(worldGraph[this.x + pixelWidth][this.y]); // Reverse direction this.direction = Math.round(Math.random() * 2) + 5; } else { // Move up right worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.x += pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update ball into new spot } } else { // Reverse direction this.direction = Math.round(Math.random() * 2) + 5; } } // Down right else if (this.direction == 3) { // Check if at the top of the screen if (this.y + pixelWidth < Math.round(canvasHeight / pixelWidth) * pixelWidth && this.x + pixelWidth < Math.round(canvasWidth / pixelWidth) * pixelWidth) { // Check if hitting something if (dxlLIB.checkValidPosition(this.x + pixelWidth, this.y + pixelWidth, [1, 2, 3, 4, 5])) { dxlLIB.removeObject(worldGraph[this.x + pixelWidth][this.y + pixelWidth]); // Reverse direction this.direction = Math.round(Math.random() * 2) + 6; if (this.direction > 7) this.direction = this.direction - 8; } else { // Move up right worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.x += pixelWidth; this.y += pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update ball into new spot } } else { // Reverse direction this.direction = Math.round(Math.random() * 2) + 6; if (this.direction > 7) this.direction = this.direction - 8; } } // Down else if (this.direction == 4) { // Check if at the top of the screen if (this.y + pixelWidth < Math.round(canvasHeight / pixelWidth) * pixelWidth) { // Check if hitting something if (dxlLIB.checkValidPosition(this.x, this.y + pixelWidth, [1, 2, 3, 4, 5])) { dxlLIB.removeObject(worldGraph[this.x][this.y + pixelWidth]); // Reverse direction this.direction = Math.round(Math.random() * 2) + 7; if (this.direction > 7) this.direction = this.direction - 8; } else { // Move up right worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.y += pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update ball into new spot } } else { // Reverse direction this.direction = Math.round(Math.random() * 2) + 7; if (this.direction > 7) this.direction = this.direction - 8; } } // Down left else if (this.direction == 5) { // Check if at the top of the screen if (this.y + pixelWidth < Math.round(canvasHeight / pixelWidth) * pixelWidth && this.x - pixelWidth > 0) { // Check if hitting something if (dxlLIB.checkValidPosition(this.x - pixelWidth, this.y + pixelWidth, [1, 2, 3, 4, 5])) { dxlLIB.removeObject(worldGraph[this.x - pixelWidth][this.y + pixelWidth]); // Reverse direction this.direction = Math.round(Math.random() * 2); } else { // Move up right worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.x -= pixelWidth; this.y += pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update ball into new spot } } else { // Reverse direction this.direction = Math.round(Math.random() * 2); } } // Left else if (this.direction == 6) { // Check if at the top of the screen if (this.x - pixelWidth > 0) { // Check if hitting something if (dxlLIB.checkValidPosition(this.x - pixelWidth, this.y, [1, 2, 3, 4, 5])) { dxlLIB.removeObject(worldGraph[this.x - pixelWidth][this.y]); // Reverse direction this.direction = Math.round(Math.random() * 2) + 1; } else { // Move up right worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.x -= pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update ball into new spot } } else { // Reverse direction this.direction = Math.round(Math.random() * 2) + 1; } } // Up left else if (this.direction == 7) { // Check if at the top of the screen if (this.y - pixelWidth > 0 && this.x - pixelWidth > 0) { // Check if hitting something if (dxlLIB.checkValidPosition(this.x - pixelWidth, this.y - pixelWidth, [1, 2, 3, 4, 5])) { dxlLIB.removeObject(worldGraph[this.x - pixelWidth][this.y - pixelWidth]); // Reverse direction this.direction = Math.round(Math.random() * 2) + 2; } else { // Move up right worldGraph[this.x][this.y] = 0; // No longer occupying previous space this.x -= pixelWidth; this.y -= pixelWidth; worldGraph[this.x][this.y] = this.typeIndex; // Update ball into new spot } } else { // Reverse direction this.direction = Math.round(Math.random() * 2) + 2; } } } } let worldGraph = []; let worldObjects = []; let currentType; let types = []; // #0 - in this class we will always use ECMAScript 5's "strict" mode // See what 'use strict' does here: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode // #1 call the init function after the pages loads window.onload = init; function init() { console.log("page loaded!"); // Setup canvas canvas = document.querySelector('canvas'); ctx = canvas.getContext('2d'); window.addEventListener('resize', resizeCanvas, false); types.push(block); types.push(sand); types.push(fire); types.push(plant); types.push(ball); currentType = types[0]; resizeCanvas(); // Click event document.querySelector("canvas").onmousedown = function(e) { mouseDown = true; mouseXPos = e.pageX; mouseYPos = e.pageY; } document.querySelector("canvas").onmousemove = function(e) { // We only care when we need the mouse position if (mouseDown) { mouseXPos = e.pageX; mouseYPos = e.pageY; } } document.querySelector("canvas").onmouseup = function(e) { mouseDown = false; } document.querySelector("#clear").onclick = function(e) { worldObjects = []; worldGraph = dxlLIB.createWorldGraph(); } document.querySelector("#wall").onclick = function(e) { currentType = types[0]; document.querySelector("#wall").className = "active"; document.querySelector("#sand").className = ""; document.querySelector("#fire").className = ""; document.querySelector("#plant").className = ""; document.querySelector("#ball").className = ""; } document.querySelector("#sand").onclick = function(e) { currentType = types[1]; document.querySelector("#wall").className = ""; document.querySelector("#sand").className = "active"; document.querySelector("#fire").className = ""; document.querySelector("#plant").className = ""; document.querySelector("#ball").className = ""; } document.querySelector("#fire").onclick = function(e) { currentType = types[2]; document.querySelector("#wall").className = ""; document.querySelector("#sand").className = ""; document.querySelector("#fire").className = "active"; document.querySelector("#plant").className = ""; document.querySelector("#ball").className = ""; } document.querySelector("#plant").onclick = function(e) { currentType = types[3]; document.querySelector("#wall").className = ""; document.querySelector("#sand").className = ""; document.querySelector("#fire").className = ""; document.querySelector("#plant").className = "active"; document.querySelector("#ball").className = ""; } document.querySelector("#ball").onclick = function(e) { currentType = types[4]; document.querySelector("#wall").className = ""; document.querySelector("#sand").className = ""; document.querySelector("#fire").className = ""; document.querySelector("#plant").className = ""; document.querySelector("#ball").className = "active"; } let slider = document.querySelector("#pixelSlider"); let output = document.querySelector("#pixelWidth"); output.innerHTML = slider.value; // Display the default slider value // Update the current slider value (each time you drag the slider handle) slider.oninput = function() { output.innerHTML = this.value; pixelWidth = parseInt(slider.value); // Make sure it is an integer // Change sizes of objects for (let i = 0; i < types.length; i++) { types[i].width = pixelWidth; } // Clear the objects worldObjects = []; resizeCanvas(); } } // Resizing Source: https://stackoverflow.com/questions/4288253/html5-canvas-100-width-height-of-viewport function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; canvasWidth = canvas.width; canvasHeight = canvas.height; // Background ctx.fillStyle = 'black'; ctx.fillRect(0,0,canvasWidth,canvasHeight); // Reset the thread clearInterval(thread); // Make the world worldGraph = dxlLIB.createWorldGraph(); // Update again thread = setInterval(drawUpdate, updateTime); } function drawUpdate(){ // Clear every frame ctx.fillRect(0,0,canvasWidth,canvasHeight); // Draw in sand while mouse is held if (mouseDown) { // Check if valid spawning point dxlLIB.spawnObject(mouseXPos, mouseYPos); } ctx.save(); // Fire updates first for (let i = 0; i < worldObjects.length; i++) { if (worldObjects[i].typeIndex == 3) { ctx.fillStyle = worldObjects[i].color; ctx.fillRect(worldObjects[i].x-worldObjects[i].width/2,worldObjects[i].y-worldObjects[i].width/2,worldObjects[i].width/2,worldObjects[i].width/2); worldObjects[i].update(); } } // Rest of updates for (let i = 0; i < worldObjects.length; i++) { if (worldObjects[i].typeIndex != 3) { ctx.fillStyle = worldObjects[i].color; ctx.fillRect(worldObjects[i].x-worldObjects[i].width/2,worldObjects[i].y-worldObjects[i].width/2,worldObjects[i].width/2,worldObjects[i].width/2); worldObjects[i].update(); } } ctx.restore(); }