
Unofficial Chicka Chicka Boom Game in p5.js | by uxBrad | Medium
How did I spend my Memorial Day? - Creating a game based on a kids book with p5.js:
I’ll try and make this short and sweet. I’m not trying to turn this into a recipe where you need to scroll through 20 screens of backstory just to figure out how much flour you need.
In fact. If you know what bookmarklets are, are using obsidian, and have some things stuck in Trello because it’s a pain to get stuff out, then just drag this link (Trello –> Obsidian) to your browser bookmarks and go to town. You’ll probably figure it out. If that’s not enough, jump to how to use it and ignore all the beautiful time I lost to padding out the rest of this post.
Otherwise, here’s 20 screens of backstory.
I’ve been an avid user of Obsidian (A second brain, for you, forever. As their tagline says.) for a couple years. There are few apps that have ever affected my productivity as much as obsidian has. I’m not going to go into detail about how it helps me to better organize and connect everything that I’ve ever written, or how it has created a better system for maintaining and helping me act on tasks and projects because there’s a lot of coverage on the internet for those sorts of things. In fact, here are a few:
Remember the time before Chrome Extensions existed? If you just asked me what a bookmarklet is, then you clearly don’t. I get it, I might be old.
Before browser extensions, the best we had were bookmarklets - bookmarks that contained a bunch of JavaScript code that could run on any page. They didn’t need to be installed, they didn’t ask for crazy permissions, and they didn’t run all the time collecting data on every page you visited.
They still don’t. And I think they still have a place in the modern web.
I vented about this a little bit recently when I wrote a bookmarklet for copying YouTube links with specific timecodes in markdown so I could use them in Obsidian.
The following chart shows Google search trends for “Bookmarklet” over the last 5 years and their down-trend following the launch of Chrome Extensions.
What’s that bit of uptick at the end? Maybe we are on the verge of a resurgence and that people are starting to realize they don’t need a Pinterest Extension or a collection of useless tools mucking up their browser UI just to quickly add quirky DIY projects to your future dream home projects - especially with many extensions overstepping their usefulness.
I’m glad you’re asking these questions. You should be. Again, smarter people something something:
As for what exactly this bookmarklet is doing, you’ll just have to look at the code to find out. Spoiler alert, its doesn’t do anything other than pull stuff off of the page and copy it into your clipboard. So yeah, it’s safe. The bookmarklet’s code is actually minified, so here’s the full source…
I don’t feel like I need to explain code as long as it’s well commented. Contrary to what this article implies, I don’t write code - which you’ll clearly see if you actually look through this.
// The variable that will store all the text, starts with the kanban board settings
var obsidianText = "---\n\nkanban-plugin: basic\n\n---\n\n";
// Something to keep track of just to display when it's copied.
var numberOfLists;
var numberOfCards;
// Get all the lists in trello
var trelloLists = document.getElementsByClassName("js-list");
numberOfLists = trelloLists.length;
// Cycle through all the lists
for (l = 0; l < numberOfLists; l++){
// Get the list name
var trelloListHeader = trelloLists[l].getElementsByTagName("h2")[0].innerText;
// Append the header to obsidianText in markdown
obsidianText += "\n## " + trelloListHeader + "\n";
// Get all the cards in each list
var trelloItems = trelloLists[l].getElementsByClassName("list-card");
numberOfCards = numberOfCards + trelloItems.length;
// Cycle through all the cards in each list
for (i = 0; i < trelloItems.length; i++){
// Get the card title
var trelloItemTitle = trelloItems[i].getElementsByClassName("list-card-title")[0].innerText;
// Grab all of the labels if they have text specified. This does not get labels that are just colors.
var trelloLabels = trelloItems[i].getElementsByTagName("button");
// cycle though all of labels
for (b=0; b<trelloLabels.length; b++){
var ariaLabel = trelloLabels[b].getAttribute("aria-label"); // aria label is formatted as aria-label = "Color:red title: "Label_Text""
var splitAriaValue = ariaLabel.split('title: ')[1]; // Split the string to just get the title, so 0 is trash 1 is the value of 'title' “Label_Text”"
splitAriaValue = splitAriaValue.replace("“", "") // get rid of the smart quotes
splitAriaValue = splitAriaValue.replace("”", "") // both of them
// Append the label to item
if (splitAriaValue != "none"){
trelloItemTitle = trelloItemTitle + " #" + splitAriaValue;
}
}
// Get the date. We can't get the time because trello doesn't display it in the list.
var trelloDate = trelloItems[i].getElementsByClassName("js-due-date-text");
if (trelloDate.length > 0){ // Only work on the ones that have a date.
trelloDate = trelloDate[0].innerText;
var trelloConvertedDate = new Date(trelloDate).toISOString().substring(0, 10);; // Convert it to an actual date so we can change the format
// Append the date to item
trelloItemTitle = trelloItemTitle + " @{" + trelloConvertedDate + "}";
}
// Append the item (with labels and date) to obsidianText in markdown
obsidianText += "- [ ] " + trelloItemTitle + "\n";
}
}
// Append the final expected kanban settins
obsidianText += '\n\n%% kanban:settings\n```\n{"kanban-plugin":"basic"}\n```\n%%';
//Copy text to the clipboard
navigator.clipboard.writeText(obsidianText);
// Flash the time on the page in an overlay so that I know I clicked it.
var elemDiv = document.createElement('div');
elemDiv.innerHTML = "<h1 style='font-size:40px; color:white; text-align:center; margin-top:2em;'>" + "Copied " + numberOfLists + " Lists and " + numberOfCards + " cards. </h1>";
elemDiv.style.cssText = 'position:absolute;width:100%;height:100%;opacity:0.8;z-index:1000;background:#000;top:0';
document.body.appendChild(elemDiv);
// Have it fade out after a bit
setTimeout(function(){ elemDiv.style.display = "none"; }, 2000);
var numberOfLists,numberOfCards,obsidianText="---\n\nkanban-plugin: basic\n\n---\n\n",trelloLists=document.getElementsByClassName("js-list");for(numberOfLists=trelloLists.length,l=0;l<numberOfLists;l++){var trelloListHeader=trelloLists[l].getElementsByTagName("h2")[0].innerText;obsidianText+="\n## "+trelloListHeader+"\n";var trelloItems=trelloLists[l].getElementsByClassName("list-card");for(numberOfCards+=trelloItems.length,i=0;i<trelloItems.length;i++){var trelloItemTitle=trelloItems[i].getElementsByClassName("list-card-title")[0].innerText,trelloLabels=trelloItems[i].getElementsByTagName("button");for(b=0;b<trelloLabels.length;b++){var ariaLabel=trelloLabels[b].getAttribute("aria-label"),splitAriaValue=ariaLabel.split("title: ")[1];"none"!=(splitAriaValue=(splitAriaValue=splitAriaValue.replace("“","")).replace("”",""))&&(trelloItemTitle=trelloItemTitle+" #"+splitAriaValue)}var trelloDate=trelloItems[i].getElementsByClassName("js-due-date-text");if(trelloDate.length>0){trelloDate=trelloDate[0].innerText;var trelloConvertedDate=new Date(trelloDate).toISOString().substring(0,10);trelloItemTitle=trelloItemTitle+" @{"+trelloConvertedDate+"}"}obsidianText+="- [ ] "+trelloItemTitle+"\n"}}obsidianText+='\n\n%% kanban:settings\n```\n{"kanban-plugin":"basic"}\n```\n%%',navigator.clipboard.writeText(obsidianText);var elemDiv=document.createElement("div");elemDiv.innerHTML="<h1 style='font-size:40px; color:white; text-align:center; margin-top:2em;'>Copied "+numberOfLists+" Lists and "+numberOfCards+" cards. </h1>",elemDiv.style.cssText="position:absolute;width:100%;height:100%;opacity:0.8;z-index:1000;background:#000;top:0",document.body.appendChild(elemDiv),setTimeout((function(){elemDiv.style.display="none"}),2e3);
Finally, we made it through all the backstory. As promised here’s what to do with this bookmarklet, if you couldn’t figure it out.
If you made it here, as always, thanks for stopping by.
I came across a post the other day where someone was surprised that you could put JavaScript into a bookmark and run it from on any page. They even asked what to call that type of thing. I was surprised. They’re called bookmarklets and they’ve saved me on numerous occasions.
For all you folks that don’t know, before the invention of chrome extensions and browser plugins, we used to cram JavaScript into browser bookmarks so that we could do things with the pages we were using. Years ago, I made a bookmarklet for copying tweets so that they could be embedded into my own blog — long before Twitter came up with embeddable tweets.
We don’t always need a massive permission grabbing chrome extension to add some extensibility to a page. Today I’m going to show you how I made one just last weekend to scratch an itch I’d been having. If you know a bit of JavaScript, you can come up with some pretty clever ways of interacting with the code on other people’s pages.
I played around with Memetic for taking notes on YouTube videos but I always just ended up exporting those notes into Obsidian (my second brain) and I figured I could streamline the process. The most useful feature in Memetic was adding the timestamp (and link) to the notes I was taking.
Going from Memetic and into something more useful, like Obsidian, seemed like an unnecessary step when I would normally just take all my notes in Obsidian in the first place.
I needed to fix my process.
I figured that link would exist somewhere in the page on the video I was watching — I just needed to grab it and get it into obsidian. YouTube has a bit of UI that exposed that link, but I’m not opening that up, clicking a button, and copying it out every time. That does mean that information is in the code and I can work with that.
What did I need this thing to do?
I don’t feel like I need to explain code as long as it’s well commented:
// Function to convert number of seconds into Hours::Minutes::Seconds
function minutesSeconds(s){
return(s-(s%=60))/60+(9<s?':':':0')+s;
}
// The Main Function
function start(){
// Current Page
var currentURL = window.location.href;
// Where the data is in youtube
var bar = document.getElementsByClassName("ytp-progress-bar")[0];
//Converting that to data
var seconds = bar.getAttribute("aria-valuenow");
var secondsInMS = minutesSeconds(seconds);
//Adding the time to the url
var URL = currentURL + "?t=" + seconds;
//Setting up the markdown format : [This is the linkname](link.html)
var obsidianFormat = "[" + secondsInMS + "]" + "(" + URL + ")";
// Put the obsidian formatted link into the clipboard
navigator.clipboard.writeText(obsidianFormat);
null;
// Flash the time on the page in an overlay so that I know I clicked it.
var elemDiv = document.createElement('div');
elemDiv.innerHTML = "<h1 style='font-size:100px; color:white; text-align:center; margin-top:2em;'>" + secondsInMS + "</h1>";
elemDiv.style.cssText = 'position:absolute;width:100%;height:100%;opacity:0.8;z-index:1000;background:#000;';
document.body.appendChild(elemDiv);
// Have it fade out after a bit
setTimeout(function(){ elemDiv.style.display = "none"; }, 600);
}
// Start
start();
javascript:function minutesSeconds(s){return(s-(s%=60))/60+(9<s?':':':0')+s}function start(){var currentURL=window.location.href;var bar=document.getElementsByClassName("ytp-progress-bar")[0];var seconds=bar.getAttribute("aria-valuenow");var secondsInMS=minutesSeconds(seconds);var URL=currentURL+"?t="+seconds;var obsidianFormat="["+secondsInMS+"]("+URL+")";navigator.clipboard.writeText(obsidianFormat);null;var elemDiv=document.createElement('div');elemDiv.innerHTML="<h1 style='font-size:100px; color:white; text-align:center; margin-top:2em;'>"+secondsInMS+"</h1>";elemDiv.style.cssText='position:absolute;width:100%;height:100%;opacity:0.8;z-index:1000;background:#000;';document.body.appendChild(elemDiv);setTimeout(function(){elemDiv.style.display="none"},600)}start();
Things I could do to improve this:
When I was tasked with creating an interactive story based on a children’s book that had to include some form of randomness and build on some of the things we’ve learned in the first 2 weeks of my class on p5.js — Chicka Chicka Boom Boom was the first thing that came to mind — so I ran with it.
tldr; Made a game that you or your kids should play: Chicka Chicka Boom Boom Game Demo
Chicka Chicka Boom Boom is a children’s book that helps kids to learn the alphabet by turning the letters into characters and telling a story of how they go to a party and fall out of a tree — something like that. I only read it a handful of times when my kids were little and I was a bit too old to grow up on it (it was published in 1989).
The design of the book is bright, the typeface is bold, and the rhyming wants to make you get loud and excited when reading it.
My idea seems simple — Create an interactive game based on the story — letters will be piled up on each other after they’ve fallen out of the tree and the player will need to find each letter in ABC order.
The design should mimic the bold lettering and bright visuals from the book.
After placing the letters back into the tree, the player should be given the opportunity to play it again.
Each letter will be assigned a color and added onto the canvas in the scene so that that are spread out, skewed and in no particular order.
At the top of the screen there will be an indicator on which letter the player should be looking for and direction on how to play.
If a player selects the wrong character, it will make a sound indicating an error.
If the player selects the correct character, a sound will indicate that they were correct, it will be removed from the pile, placed on top of the tree and the indicator will change to the next letter.
After the user finds the last letter, a sound will indicate they’ve completed it and will be prompted to play it again.
The code in this morphed quite a bit as I added in features and effed up some things so I’m only going to share some chunks of code, but you can check out the working demo and the final code at the end. Let’s make this happen.
I started out by creating the alphabet and storing it into an array, then cycling through each letter and assigning a random position on the canvas:
The typeface in the book used for the letters is very blocky. A quick google search and of course the Chicka Chicka Boom Boom font is a thing and easily imported.
The color palette used in the book is bright and very limited — maybe 10 colors in all, so I stored those in a color palette array and assigned the letters a random color as I placed them on the canvas.
There is one page in the book when all the letters fall out of a tree that I used as inspiration for the game page — I made the tree in SVG and created the border around the whole canvas by looping through the dots one by one and assigning a small bit of randomness. I could have been more clever with the measurements but, for the sake of sanity, I used a bunch of magic numbers to get it done.
function drawCirclePattern() {
// Creates the dots that surround the canvas
var numberOfRows = 19
var numberOfColumns = 38
var radius = 14
var marginX = 34
var marginY = 38
var xPosition = 20
var yPosition = 16
var rotation = 0
fill ( 215, 21, 101 )
// Create each row
for (j = 0; j < numberOfRows; j++ ){
// Create each column's circle
for (i = 0; i < numberOfColumns; i++ ){
// Add the circle with a little bit of randomness for the Y position
ellipse( xPosition, yPosition + ( random( -3, 3 ) ), radius, radius )
xPosition += marginX
}
// Offset the even numbered rows
if ( j % 2 == 0 ){
xPosition = -41
}
else{
xPosition = 20
}
yPosition += marginY
}
}
and got it looking pretty darn close to the actual look of the page:
I couldn’t get away with random positions on the canvas — the letters needed to be in their own spot and not overlapping the other characters.
I chose to actually define 26 different positions on the canvas that each letter could go into so that I could get more control of their placement. I stored those in an array and assigned each letter randomly to one of those positions.
I also created a small bar at the bottom of the screen to indicate what letters had been found.
At this point I had forgotten the fact that I wasn’t storing the random positions, rotation and color for each of the letters so when I ran it, it produced a seizure inducing affect of creating and randomizing the alphabet on top of the existing one:
Whoops. 10 minutes of trying to figure out if I could get away with noLoop()
later and ended up extending my alphabet array to include the positions, color and rotations — only calling that once. Whew:
At the start of the program, I set which letter is the first to be found and use that as the alphabet is being drawn onto the page so that I can color it gray and also to display, at the bottom, which ones the player has found.
The coloring was simply done like this during the loop of placing the letters on the page:
if (alphabet.indexOf(currentLetterToFind) > i){
fill( 220 , 220, 220 )
}
else{
// Assign the stored color value
fill( alphabet[i][4] )
}
A mouse looks to see if the click happens close to the coordinates of the current letter to be found:
var clickDistance = dist ( mouseX, mouseY, currentLetterToFind[1], currentLetterToFind[2] )
if (clickDistance < 50 ){
var currentLetterIndex = alphabet.indexOf(currentLetterToFind)
var nextLetterIndex = currentLetterIndex + 1
}
And the game ends up looking this this as it progresses:
It’s not much of a game without a great intro screen and a screen for indicating you’ve won! So I set out to create 2 additional scenes. A new vector that looks like the book cover and some more color choices and buttons later and I had this:
At this point, the game was working smoothly, so I volunteered my kids to do a little quality assurance. They thought it was cute and were kinda surprised I had it in me — but it wasn’t enticing enough for them to play it more than once.
They definitely came up with some far out ideas on how to improve it, but not anything that I’d want to try to make happen for this assignment.
But, I decided that there’s nothing better than a little healthy competition to get them on each other’s nerves — so I decided I’d add in a timer so that we could race each other’s best times.
Time is complicated — mainly because of math and the fact that noon rolls into 1 and 59 rolls into 1 and their aren’t any good built in functions for calculating how much time has passed.
So I decided I would just keep track of each drawn frame and assuming there’s about 60 frames running per second, I would just estimate the number in seconds.
Ending up with this:
I did my best to make the code as clear and commented as I could, but before you delve into it, check out the working demo of the Chicka Chicka Boom Boom Game.
// Set the starting scene
var scene = 0
// Setup some global variables
var alphabet = []
var currentLetterToFind
var positions
// Store the frames as the game is played to be used as a timer
var numberOfFrames = 0
// Set a default winning text to be randomized later
var finalWinningText = "Wow!"
// Color of the letters
var colorPalette = [
[ 240, 0, 100 ],
[ 247, 90, 29 ],
[ 8, 170, 143 ],
[ 242, 24, 67 ],
[ 243, 162, 32 ],
[ 1, 170, 254 ],
[ 86, 72, 157 ],
[ 0, 120, 194 ],
[ 120, 100, 170 ],
[ 251, 196, 14 ]
]
// Different congradulations text
var congratsText = [
"Great Job!",
"Wow!",
"You Rock!",
"Fantastic!"
]
function setupPositions(){
// Positions of the letters on the canvas
positions = [
[ 497,362 ],
[ 613, 362 ],
[ 181, 437 ],
[ 352, 437 ],
[ 468, 437 ],
[ 584, 437 ],
[ 700, 437 ],
[ 816, 437 ],
[ 932, 437 ],
[ 164, 525 ],
[ 336, 525 ],
[ 452, 525 ],
[ 568, 525 ],
[ 684, 525 ],
[ 800, 525 ],
[ 916, 525 ],
[ 1032, 525 ],
[ 134, 623 ],
[ 245, 623 ],
[ 365, 623 ],
[ 481, 623 ],
[ 597, 623 ],
[ 713, 623 ],
[ 829, 623 ],
[ 956, 623 ],
[ 1058, 623 ]
]
}
function preload(){
// Import Font
font = loadFont( "ccbb.ttf" )
// Import Images
treeImage = loadImage( "tree.svg" )
treeIntroImage = loadImage( "tree-intro.svg" )
// Import Sounds
soundFormats( "mp3" )
yeahSound = loadSound( "yeah.mp3" )
waSound = loadSound( "wa.mp3" )
}
function sceneIntro() {
// Start Screen
// Create the white canvas over the background border
var canvasWidth = width - 156
var canvasHeight = height - 80
fill( 254, 207, 65 )
rect( 78, 80, canvasWidth, canvasHeight )
// Title
textSize( 120 )
textAlign( RIGHT, RIGHT );
fill( 120, 71, 179 )
text( "Chicka", 1050, 200 )
text( "Chicka", 1050, 300 )
fill( 88, 203, 181 )
text( "Boom Boom", 1050, 400 )
text( "Game", 1050, 500 )
// Image
image( treeIntroImage, -10, 70 )
// Start Button
fill( 204, 59, 76)
rect( 750, 600, 300, 100)
textSize( 60 )
fill( 255, 255, 255)
text( "START", 1000, 670 )
}
function sceneCongrats(){
// Ending Screen
// Create the white canvas over the background border
var canvasWidth = width - 156
var canvasHeight = height - 80
fill( 255,255,255 )
rect( 78, 80, canvasWidth, canvasHeight )
// Congradulations Text
textSize( 120 )
textAlign( RIGHT, RIGHT );
fill( 120, 71, 179 )
text( finalWinningText, 1050, 300 )
fill( 88, 203, 181 )
text( "You Won", 1050, 400 )
// Show how long it took based on number of frames accumulated
var calulatedSeconds = Math.floor( numberOfFrames / 60 )
textSize( 50 )
fill( 253, 155, 30 )
text( "In " + calulatedSeconds + " seconds", 1050, 500 )
// Tree Image
image(treeIntroImage, -10,70)
// Play Again Button
fill(204,59,76)
rect(650, 600, 400, 100)
textSize( 60 )
fill(255,255,255)
text("Play Again", 1000, 670)
}
function randomizeAlphabet(){
// Assign the position on the canvas, rotation and color values for each letter
for ( i = 0; i < 26; i++ ){
var randomColor = Math.floor( random( 0, 9 ) )
// Pick a random position on the canvas to go into
var randomPosition = Math.floor( random( 0, positions.length ) )
// Take the positions stored in the position array and store them
// as the character's position
var characterPositionX = positions[ randomPosition ][ 0 ]
var characterPositionY = positions[ randomPosition][ 1 ]
// Push that posision into the alphabet array
alphabet[i][1] = characterPositionX
alphabet[i][2] = characterPositionY
// Randomly rotate the letter and store that in the array
var randomRotation
var positiveOrNegative = random( 0, 1 )
if (positiveOrNegative > .5 ){
randomRotation = random( 0, 120 )
}
else{
randomRotation = -random( 0, 120 )
}
alphabet[ i ][ 3 ] = randomRotation
// Randomly assign a color from the stored color palette
alphabet[ i ][ 4 ] = colorPalette[ randomColor ]
//remove the used position from the array
positions.splice(randomPosition, 1)
}
}
function setup() {
createCanvas( 1200, 750 )
noStroke()
angleMode( DEGREES )
background( 253, 155, 30 )
// Setup the array of characters with placeholder values for x, y, color, and rotation
// using the ascii character code, in case I want to do lowercase or other characters
for ( var z = 65; z < 91; z ++ ){
var currentCharacter = String.fromCharCode( z )
alphabet.push( [currentCharacter, 0, 0, 0, 0 ] )
}
// Set the first letter to be found in the game
currentLetterToFind = alphabet[ 0 ]
// Create the dots that surround the canvas
drawCirclePattern()
// Get the list of all the positions available on the canvas
setupPositions()
// Randomize the order of the alphabet and attach x, y, rotation, and color
randomizeAlphabet()
}
function drawCirclePattern() {
// Creates the dots that surround the canvas
var numberOfRows = 19
var numberOfColumns = 38
var radius = 14
var marginX = 34
var marginY = 38
var xPosition = 20
var yPosition = 16
var rotation = 0
fill ( 215, 21, 101 )
// Create each row
for (j = 0; j < numberOfRows; j++ ){
// Create each column's circle
for (i = 0; i < numberOfColumns; i++ ){
// Add the circle with a little bit of randomness for the Y position
ellipse( xPosition, yPosition + ( random( -3, 3 ) ), radius, radius )
xPosition += marginX
}
// Offset the even numbered rows
if ( j % 2 == 0 ){
xPosition = -41
}
else{
xPosition = 20
}
yPosition += marginY
}
}
function collectedArea(){
// Create the area that indicates what letter needs to be found
// Position on the canvas for the text to sit
var abcPositionX = 150
// The current letter that needs to be found
var currentLetterIndex = alphabet.indexOf( currentLetterToFind )
// The "Letters Found " text
fill( 204, 59, 76 )
rect( 0, 680, width, 70 )
fill( 255, 255, 255 )
textSize( 30 )
text( "Letters Found: ", abcPositionX, 715 )
// Position for the letters on the canvas
abcPositionX = 280
for (k = 0; k < currentLetterIndex; k++ ){
text( alphabet[k][0], abcPositionX, 715 )
abcPositionX +=35
}
}
function draw() {
textFont(font)
// Determine which scence should be shown
if (scene == 0) { sceneIntro() }
else if(scene == 3){ sceneCongrats() }
else{
// The game screen
// Create the white canvas over the border
numberOfFrames++
var canvasWidth = width - 156
var canvasHeight = height - 80
fill( 255, 255, 255 )
rect( 78, 80, canvasWidth, canvasHeight )
setupPositions()
// Add in the tree
image(treeImage, 160,-10)
// Draw the letters on the page
for ( i = 0; i < 26; i++ ){
// Keep track of what the current letter to find is
var currentLetterIndex = alphabet.indexOf(currentLetterToFind)
// If the letter has already been found, make it gray
if (alphabet.indexOf(currentLetterToFind) > i){
fill( 220 , 220, 220 )
}
else{
// Assign the stored color value
fill( alphabet[i][4] )
}
// Text settings
textSize( 120 )
textAlign( CENTER, CENTER );
// This is where we rotate the letters
// Move the origin of the canvas to the center of the character
translate(alphabet[i][1], alphabet[i][2]);
rotate(alphabet[i][3])
// Set the letter, position is 0,0 becuase the orgin shifted to its stored
// coordinates
text( alphabet[i][0], 0, 0)
// Reset the translation and rotation, because those are accumulated
rotate(-alphabet[i][3])
translate(-alphabet[i][1], -alphabet[i][2])
// Remove the used position from the array, can't have more than one letter
// in the same spot
positions.splice(alphabet[i][3], 1)
}
// Add in the "Letters Found" area
collectedArea()
}
}
function mouseClicked(){
// Check if it's the intro splash scene
if (scene == 0){
// Only allow clicking on the button
if (mouseX > 750 && mouseX < 1050 && mouseY > 600 && mouseY < 700){
setup()
scene = 1
}
}
// Check if it's the congrats scene
else if (scene == 3){
// Only allow clicking on the button
if (mouseX > 750 && mouseX < 1050 && mouseY > 600 && mouseY < 700){
setup()
scene = 1
// Reset the timer
numberOfFrames = 0
}
}
else{
// During the game, check if the user has clicked the current letter
var clickDistance = dist ( mouseX, mouseY, currentLetterToFind[1], currentLetterToFind[2] )
if (clickDistance < 50 ){
// Play a sound that you found the right letter
yeahSound.setVolume(1);
yeahSound.play();
// Increment the next letter to be found
var currentLetterIndex = alphabet.indexOf(currentLetterToFind)
var nextLetterIndex = currentLetterIndex + 1
if (nextLetterIndex == 26){
// All the letters were found!
// Pick a random congrats text
finalWinningText = congratsText[Math.floor(random(0,3))]
// Go to the congrats scene
scene = 3
setup()
}
else{
// User is still finding the letters, go find the next letter
scene = 1
currentLetterToFind = alphabet[nextLetterIndex]
}
}
else{
// Didn't find the right one
waSound.setVolume(1)
waSound.play()
}
}
}
How did I spend my Memorial Day? - Creating a game based on a kids book with p5.js:
So I happened to be writing some p5.js this weekend to make some art with code. I think it turned out alright and I even managed to write up a blog about how I did it: #processing #javascript #generativeart
After going through a bit of history on using computers to make art, my second class on p5.js had us attempt to be inspired by geometric artwork and write code to make something cool.
In about an hour and a half of coding, I ended up with this — randomly generated triangles in all kinds of colors.
We were tasked to continue working on it until we were happy with it — play around with shape, size and color.
I spun some things around in my head during a long commute — I’m determined to get all these triangles to be all connected and not overlap. It was something we discussed before starting the project but we deemed it too difficult to do consider we’re only 3 hours into learning p5. We looked at a library that accomplished it, but I needed to come up with my own solution.
I have a difficult time drawing while I’m driving so as soon as I managed to stop I sketched this out:
What if, instead of Random coordinates to create the triangles, I could
In my head it seems like it could work — getting rid of the randomness of the triangle generation.
Let’s see how far I get into this code before I realize how dumb I was to think this would work.
Just so I can see what my brain is thinking, I’m physically putting the grid onto the canvas:
function setup() {
createCanvas( windowWidth, windowHeight )
colorMode( HSB )
noLoop()
noStroke()
}
function draw() {
background( 20, 20, 20)
// Setup a grid so i can figure out the uppper and lower bounds
// of where the points should go
var columns = 5
var rows = 5
var columnWidth = ( windowWidth / columns )
var columnHeight = ( windowHeight / rows )
fill( 30, 30, 30)
stroke( 90, 90, 90 )
rect( 0, 0, columnWidth, columnHeight )
// Create a row
for ( i = 0; i < rows; i++ ){
// Create a column
for ( j = 0; j < columns; j++ ){
rect( j * columnWidth, i * columnHeight, columnWidth, columnHeight )
}
}
}
Which ended up looking like this. I’m off to an okay start.
I’m going to generate random points inside each grid using the upper and lower bounds based on the column width and height and use small circles to visualize where the points are being created.
So just in side the column loop:
for ( j = 0; j < columns + 1 ; j++ ){
rect( j * columnWidth, i * columnHeight, columnWidth, columnHeight )
//create my point
var pointPositionX = random( j * columnWidth - columnWidth, j * columnWidth )
var pointPositionY = random( i * columnHeight - columnHeight, i * columnHeight )
circle( pointPositionX, pointPositionY, 10 )
}
and I get some points:
I need to keep all these coordinates so that I can go back and create the triangles.
for ( i = 0; i < rows + 1; i++ ){
var columnData = []
// Create a column
for ( j = 0; j < columns + 1 ; j++ ){
rect( j * columnWidth, i * columnHeight, columnWidth, columnHeight )
//create my point
var pointPositionX = random( j * columnWidth - columnWidth, j * columnWidth )
var pointPositionY = random( i * columnHeight - columnHeight, i * columnHeight )
columnData.push( [pointPositionX, pointPositionY] )
console.log( columnData )
//circle( pointPositionX, pointPositionY, 10 )
}
allCoordinates.push( columnData )
console.log( allCoordinates )
}
It has been a few years since I’ve written some JavaScript but it’s seeming to fit like a glove. This is what was logged to the console. I think I somehow managed to do this nearly right* … except those negative numbers …
Yeah, so those negative numbers weren’t great. Math is weird sometimes for me — why would I be subtracting from 0? Who knows. I think I thought that i and j were 1’s? Whatever. Fixed it.
Also don’t know why I needed two arrays…I should just push all the coordinates to allCoordinates[]
.
So once all that was out the way, I wanted to make sure those first points were stored as I expected them and that I could make a triangle from them.
This is what the code ended up being:
var allCoordinates = [];
// Create a row
for ( i = 0; i < rows + 1; i++ ){
// Create a column
for ( j = 0; j < columns + 1 ; j++ ){
rect( j * columnWidth, i * columnHeight, columnWidth, columnHeight )
//create my point
var pointPositionX = random( j * columnWidth + columnWidth, j * columnWidth )
var pointPositionY = random( i * columnHeight + columnHeight, i * columnHeight )
allCoordinates.push( [pointPositionX, pointPositionY] )
circle( pointPositionX, pointPositionY, 10 )
console.log(allCoordinates)
}
}
// Create the first triangle
fill( 90, 90, 90 )
var currentPoint = 0
var firstPointX = allCoordinates[0][0]
var firstPointY = allCoordinates[0][1]
var secondPointX = allCoordinates[1][0]
var secondPointY = allCoordinates[1][1]
var thirdPointX = allCoordinates[6][0]
var thirdPointY = allCoordinates[6][1]
triangle( firstPointX,
firstPointY,
secondPointX,
secondPointY,
thirdPointX,
thirdPointY )
and the output:
Fantastic! Now I gotta loop through and do them all!
I spent a good bit of time working out how to cycle through each of the points in the array and creating 2 triangles for every point. I messed it up a lot given that I also had to figure out if it was in the first or last row or the last column — it was weird.
I finally got to a point where I was okay with the output and knew that if I just spent another hour on it I could have gotten in nearly perfect…but I was ready to just move on — I’m not trying to overachieve this.
I won’t share that code with you, but here’s how it was shaping up:
With some adjustments of the number of columns and rows, some creative cropping and some actual color choices I came up with a couple iterations before finally enjoying where it was headed. Getting closer…
Finally, the renders started coming together to a better point — enough where I could say that I was done for now.
This is the final render:
And the p5.js code. Sorry, I was ready to call it done today so there’s no cleaning it up. Enjoy it.
function setup() {
var canvas = createCanvas( 1600, 1600 )
canvas.parent("canvasArea");
colorMode( HSB )
noLoop()
noStroke()
}
function draw() {
background( 197, 31, 65 )
// Setup a grid so i can figure out the uppper and lower bounds
// of where the points should go
var columns = 20
var rows = 20
var columnWidth = 1600 / columns
var columnHeight = 1600 / rows
// Show the grid
fill( 18, 11, 18 )
//stroke( 90, 90, 90 )
//rect( 0, 0, columnWidth, columnHeight )
var allCoordinates = [];
// Create a row
for ( i = 0; i < rows; i++ ){
// Create a column
for ( j = 0; j < columns ; j++ ){
rect( j * columnWidth, i * columnHeight, columnWidth, columnHeight )
//create my point
var pointPositionX = random( j * columnWidth + columnWidth, j * columnWidth )
var pointPositionY = random( i * columnHeight + columnHeight, i * columnHeight )
allCoordinates.push( [pointPositionX, pointPositionY] )
circle( pointPositionX, pointPositionY, 10 )
}
}
var whichRow = 1
var l = 1
while ( l < allCoordinates.length ){
//firstTriangle
if (whichRow == 1 || whichRow == rows){
//don't do anything on the first and last row (thse need just one triangle)
}
else{
if ( (l + 1) % rows == 0 || l == 0){
console.log("it's the last item, do nothing")
}
else{
//Triangle 1
var colorChoices = [
[45, 15, 75],
[22, 42, 95],
[5, 55, 75],
[0, 61, 95]
]
var randomColorChoice = Math.floor(random(0,4))
fill( colorChoices[randomColorChoice][0], colorChoices[randomColorChoice][1], Math.floor(random(50,100)) )
//stroke( 200, 90, 90 )
var currentPoint = l
var nextPoint = l + 1
var bottomPoint = l + rows
var firstPointX = allCoordinates[currentPoint][0]
var firstPointY = allCoordinates[currentPoint][1]
var secondPointX = allCoordinates[nextPoint][0]
var secondPointY = allCoordinates[nextPoint][1]
var thirdPointX = allCoordinates[bottomPoint][0]
var thirdPointY = allCoordinates[bottomPoint][1]
triangle(firstPointX, firstPointY, secondPointX, secondPointY, thirdPointX, thirdPointY)
//Triangle 2
randomColorChoice = Math.floor(random(0,4))
fill( colorChoices[randomColorChoice][0], colorChoices[randomColorChoice][1], Math.floor(random(50,100)) )
//stroke( 200, 90, 90 )
var currentPoint = l
var nextPoint = l - rows
var bottomPoint = l + 1
var firstPointX = allCoordinates[currentPoint][0]
var firstPointY = allCoordinates[currentPoint][1]
var secondPointX = allCoordinates[nextPoint][0]
var secondPointY = allCoordinates[nextPoint][1]
var thirdPointX = allCoordinates[bottomPoint][0]
var thirdPointY = allCoordinates[bottomPoint][1]
triangle( firstPointX, firstPointY, secondPointX, secondPointY, thirdPointX, thirdPointY )
}
}
l++
if ( l % rows == 0 ){ whichRow++ }
}
}
Check out the working demo.
Video tracking with #JavaScript:
Crazy simple #javascript form validation with some lovely ternary’ness: