Each student will build Chapters of their GitHub Pages College Articulation Blog. It is expected that you have Chapters on JavaScript Objects, Finite State Machines using Objects, and Single Requirements Principles in Coding Methods.
•
11 min read
Javascript Objects
Collection of key value pairs.
Example Object (Car)
constcar={year:2006,type:"Honda Accord LX Special Edition",liscensePlate:"123ABC",VIN:"12HG56789MXN45678",weightInPounds:3200,color:"Navy Blue",passengers:["bob","john","steve"],drive(){console.log("VROOM")},stop(){console.log("SCREEECH")},honk(){console.log("BEEP")}}
Javascript objects can have both properties and methods. Properties in this example include: color, year, liscensePlate, etc. Properties can be stored as strings, numbers, objects, arrays, etc. Methods in this example are drive, stop, and honk. These methods can utilize properties inside of the object. Methods act like functions when called.
Finite State Machines (FSM)
FSM helps control the behavior of a system by defining its different states.
This machine has an initial state of ‘pending’. When you successfully attempt to send a message, it will update the status to ‘sent’. If you successfully attempt to unsend a message, it will update the status to ‘unsent’. If you want to see the machines current message status, you could call the method, ‘getMessageStatus’, which returns the current status of the state machine.
Single Responsibility Principle
The concept that any single object in object-oriented programing (OOP) should be made for one specific function.
Coin.js
collisionAction(){// check player collisionif(this.collisionData.touchPoints.other.id==="player"){if(this.id){GameEnv.claimedCoinIds.push(this.id)}this.destroy();GameControl.gainCoin(5)GameEnv.playSound("coin");}}
When a player gets a coin, it will add the coin’s id to the array, claimedCoinIds.
this.id=this.initiateId()initiateId(){constcurrentCoins=GameEnv.gameObjectsreturncurrentCoins.length//assign id to the coin's position in the gameObject Array (is unique to the coin)}
The coin’s id is determined when it is created. So if it were the 5th Game Object created, the coin’s id would be 5. This makes it so you don’t have to go into each coin and give it a unique id.
GameEnv.js
staticclaimedCoinIds=[]
This claimedCoinIds array is reset after every new level.
Game Control Code:
Starting with GameSetup you will describe the JavaScript Objects and how they are collected:
This is a list of all the objects our game level utilizes to operate. Each object has properties such as a name, id, class, and data. The name and id helps us identify each object in the level. The class helps us draw and configure the game objects, and the data gives our game object more properties used to create the image and dimensions.
GameControl.js
gameLoop(){// Turn game loop off during transitionsif(!this.inTransition){// Get current levelGameEnv.update();constcurrentLevel=GameEnv.currentLevel;// currentLevel is definedif(currentLevel){// run the isComplete callback functionif(currentLevel.isComplete&¤tLevel.isComplete()){constcurrentIndex=GameEnv.levels.indexOf(currentLevel);// next index is in boundsif(currentIndex!==-1&¤tIndex+1<GameEnv.levels.length){// transition to the next levelthis.transitionToLevel(GameEnv.levels[currentIndex+1]);}}// currentLevel is null, (ie start or restart game)}else{// transition to beginning of gamethis.transitionToLevel(GameEnv.levels[0]);}}// recycle gameLoop, aka recursionrequestAnimationFrame(this.gameLoop.bind(this));}
In GameControl, the gameloop function controls the transistioning between different levels. It will wait until the currentLevel is complete, and then use the transitionToLevel function to switch to the next level.
GameControl.js
asynctransitionToLevel(newLevel){this.inTransition=true;// Destroy existing game objectsGameEnv.destroy();// Load GameLevel objectsif(GameEnv.currentLevel!==newLevel){GameEnv.claimedCoinIds=[];}awaitnewLevel.load();GameEnv.currentLevel=newLevel;// Update invert propertyGameEnv.setInvert();// Trigger a resize to redraw canvas elementswindow.dispatchEvent(newEvent('resize'));this.inTransition=false;},
This is the transitionToLevel function. It starts by puting the state of the the game to inTransition. Next, it destroys all existing game objects. Then, if the current game level is not the same as the level it is transitioning to, it will clear the claimedCoinIds. After this, it will set the current level to the new one, and get out of transition mode.
Drawing Game Objects
GameEnv.js
staticupdate(){// Update game state, including all game objects// if statement prevents game from updating upon player deathif(GameEnv.player===null||GameEnv.player.state.isDying===false){for(constgameObjectofGameEnv.gameObjects){gameObject.update();gameObject.serialize();gameObject.draw();}}}
This is the update function of GameEnv, it is called during the loop. It will loop through each game object and update, serialize, and draw it.
SkibidiToilet.js
update(){super.update();// Check for boundariesif(this.x<=this.minPosition||(this.x+this.canvasWidth>=this.maxPosition)){this.speed=-this.speed;};//Random Event 2: Time Stop All Goombasif(GameControl.randomEventId===2&&GameControl.randomEventState===1){this.speed=0;if(this.name==="goombaSpecial"){GameControl.endRandomEvent();};};//Random Event 3: Kill a Random Goomba//Whichever Goomba recieves this message first will die, then end the event so the other Goombas don't dieif(GameControl.randomEventId===3&&GameControl.randomEventState===1){this.destroy();GameControl.endRandomEvent();};// Every so often change directionswitch(GameEnv.difficulty){case"normal":if(Math.random()<0.005)this.speed=-this.speed;break;case"hard":if(Math.random()<0.01)this.speed=-this.speed;break;case"impossible":if(Math.random()<0.02)this.speed=-this.speed;break;}//Immunize Goomba & Texture Itif(GameEnv.difficulty==="hard"){this.canvas.style.filter="invert(100%)";this.canvas.style.scale=1.25;this.immune=1;}elseif(GameEnv.difficulty==="impossible"){this.canvas.style.filter='brightness(1000%)';this.immune=1;}// Move the enemythis.x-=this.speed;this.playerBottomCollision=false;}
This is the update function of our SkibidiToilet enemy character. Every iteration of the game loop, the update function will be called. This fuction updates the enemy’s position and moves it to a new location.
Examine the GameObjects (canvas items) and see changes in their properties DEMO
How we determine the end of level and where we transition between GameLevels
GameSetup.js
// Game Over screen added to the GameEnv ...newGameLevel({tag:"end",callback:this.gameOverCallBack,objects:endGameObjects});
This appears at the bottom of our GameSetup file, and creates a new level for the end of the game. It inlcudes endGameObjects (Which is just a single background for the end of the game), and has a callback function that handles the game ending.
GameSetup.js
gameOverCallBack:asyncfunction(){constid=document.getElementById("gameOver");id.hidden=false;GameControl.stopTimer()// Wait for the restart button to be clickedawaitthis.waitForButtonRestart('restartGame');id.hidden=true;// Change currentLevel to start/restart value of nullGameEnv.currentLevel=false;returntrue;}
This is the callback for the end of a game. It will stop the timer, show the restart button at the top of the screen (with the id of “gameOver”). It will asynchronously wait (await) for the button to be pressed. After it is pressed, it will hide the button and restart the current level (set it to false).
Class Design using DrawIO - / 3, /1
Illustrate the design of GameObjects. Consider how Player, Enemy, and other Obstacles are all GameObjects.
My Favorite Feature
Sorting the leaderboard
getTimeSortedLeaderboardData(slowestFirst){constlocalData=JSON.parse(localStorage.getItem(this.currentKey))if(!localData){console.log("NO DATA")return[]}localData.sort((a,b)=>a.time-b.time);if(slowestFirst){localData.reverse()}returnlocalData},getCoinScoreSortedLeaderboardData(highestFirst){constlocalData=JSON.parse(localStorage.getItem(this.currentKey))if(!localData){console.log("NO DATA")return[]}localData.sort((a,b)=>a.coinScore-b.coinScore);if(highestFirst){localData.reverse()}returnlocalData},getDateSortedLeaderboardData(newestFirst){constlocalData=JSON.parse(localStorage.getItem(this.currentKey))if(!localData){console.log("NO DATA")return[]}localData.sort((a,b)=>{constdateA=newDate(a.date)constdateB=newDate(b.date)returndateA-dateB})//defaults to oldest firstif(newestFirst){localData.reverse()}console.log(localData)returnlocalData}
Array.prototype.sort()
array.sort(function(a,b){returna-b})
If the callback returns…
- Less than 0: “a” is sorted to be a lower index than “b”.
- Zero: “a” and “b” are considered equal, and no sorting is performed.
- Greater than 0: “b” is sorted to be a lower index than “a”.
Date object
“A JavaScript date is fundamentally specified as the time in milliseconds that has elapsed since the epoch, which is defined as the midnight at the beginning of January 1, 1970, UTC”
“The reverse() method transposes the elements of the calling array object in place, mutating the array, and returning a reference to the array.”
Working with a large team in Github
As we progressed in the process of making our game together, we used a main Github repository to store the most up-to-date version of the game. Each group had a single “leader” or scrum master who created a Fork from the main repository. On these forks, we used branches to create different features and additons to our game. After creating a large feature or mulitple features, we merged our code with the main repository to update the game.
As simple as it sounds, we still ran into many difficulties while collaborating and sharing code. The biggest issue our team faced was version control. We tended to fall behind in our production and create changes off of the older versions of the game. To combat this issue we need to ensure that our repository and branches are all up-to-date with the main branch. To do this when you are already far behind and have already added multiple commits and changes is very difficult.
The most helpful git command we found while fixing our version was git pull --rebase. Since our code became over 200 commits behind the main repository, I had decided that we would add our features manually as rebasing would prove to be very time consuming. I was able to manually add all of our features, and bring them onto the main repository, however this meant that our repository was still far behind production. I researched and ran multiple git commands to update the remote branch to match upstream, and after many our our team members faced difficulties pulling the updated repository to their local repository. I realized that using git pull --rebase rather than git pull would work by rebasing all of their commits and forcing their repository to be up to date.
After finally fixing our repository and getting our team up-to-date, we were able to work on and add many features to the game.