# Practice: Hoppy Grace

I was surprise at first, then finally got this in my first try.
This is wonderful. I hope in the future Grasshopper will teach us how to make this full game, or maybe creating a little game to learn about the keywords and function in a better way.

Ya. I Was Also Wondering That in The Future, Grasshopper Will Teach Us This Game. Oh, By The Way. Here is The Hidden Code From The Hoppy Grace Explainer. Here is The Code.

Hidden Code
``````/**
* User Flags
*/

let changeColor = false;
let ceiling = true;
let floor = true;
let collisions = true;
let dizzyMode = false;
let binary = false;
let autopilot = false;
let laser = false;
let words = true;

/**
* Window Size Constants
*/

const WIW = window.innerWidth || 368;
const WIH = window.innerHeight || 231;

const CX = WIW / 2;
const CY = WIH / 2;

/**
* Initial Conditions
*/

// event type (click or touch)
let eventType;

// distance traveled
let x = 0;

// timestep in milliseconds
const dt = 1000 / 60;

// initial vertical position
let y = CY;

// initial vertical velocity
let dy = 0;

// vertical acceleration
const ddy = 0.0005;

// box size
const sideLength = 30;

// number of boxes in each column
let colSize = 5;

// number of box widths from top to bottom of each hole
let holeHeight = 3;

// number of box widths to separate each wall
let gap = 3;

// distance from the beginning of one wall to the next
const holeTohole = sideLength * (1 + gap);

// number of pixels to shrink bounding box of grasshopper
const tolerance = 5;

// number of taps
let taps = 0;

// record of where gaps were
let gaps = [];

// CS words - must end each string with a semicolon
const wordList = [
"STRING;",
"ARRAY;",
"INDEX;",
"VARIABLE;",
"OBJECT;",
"OPERATOR;",
"BOOLEAN;",
"FUNCTION;",
"FOR LOOP;",
"IF STATEMENT;",
"FOR...OF LOOP;",
"IF ELSE;",
"TERNARY;",
"CONSOLE;",
"RETURN;",
"RECURSION;",
"ARROW FUNCTION;",
"METHOD;",
].map(i => i.split(""));

/**
* Graphics Setup
*/

// setup svg environment
let _svg;
try {
_svg = setupD3();
} catch (e) {
_svg = new d3.select("body").append("svg").attr("width", WIW).attr("height", WIH);
}

// a group that will hold the grasshopper and boxes for dizzymode
let space = _svg.append("g");

// create a group to store boxes
const boxes = space.append("g");

// create a group to store floating letters
const letters = space.append("g").style("user-select", "none");

// timer for game
let gameClock;

// color of the grasshopper logo
const ghopGreen = d3.hsl(120, 1, 0.725); //d3.rgb(115, 255, 115);

// object to save the score and the scoreboard text element
let Score = {
board: _svg
.append("text")
.style("user-select", "none")
.attr("alignment-baseline", "hanging")
.attr("x", 0)
.attr("y", 5)
.attr("font-family", "impact")
.attr("font-size", 50)
.attr("fill", ghopGreen)
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("font-weight", 900),

value: 0,
};

// place to hold the current word being collected
let Word = {
board: _svg
.append("text")
.style("user-select", "none")
.attr("alignment-baseline", "baseline")
.attr("x", 0)
.attr("y", WIH - 10)
.attr("font-family", "monospace")
.attr("font-size", 30)
.attr("fill", "white")
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("font-weight", 900),

list: wordList,

goal: "JAVASCRIPT;".split(""),

current: [],
};

// object that controls the boxes' color phasing
let Glitter = {

glitterClock: null,

on: (interval = 100, increment = 10) => {
Glitter.off();
Glitter.glitterClock = setInterval(() => Glitter.shiftBoxColor(increment), interval);
},

off: () => clearInterval(Glitter.glitterClock),

shiftBoxColor: (increment = 10) => {
for (let box of boxes.selectAll("rect").nodes().map(node => d3.select(node))) {
// d3 attributes return strings, the + operator casts to a number
let boxHue = +d3.hsl(box.attr("fill")).h;
box.attr("fill", d3.hsl((boxHue + increment) % 360, 1, 0.5));
}
}
};

// create Grasshopper group = head + body
const grasshopper = space.append("g").attr("fill", ghopGreen);
grasshopper.append("path").attr("fill", ghopGreen).attr("d", "M 38.0575 7.865 H 36.5105 c 0 -1.7295 0.409 -3.416 1.1155 -4.9685 0.3255 -0.7155 -0.208 -1.5275 -0.994 -1.5275 a 1.093 1.093 0 0 0 -0.95 0.5545 A 11.988 11.988 0 0 0 34.0945 7.865 h 0 A 10.808 10.808 0 0 0 40.904 17.906 l 0.52 0.207 V 11.2315 A 3.3665 3.3665 0 0 0 38.0575 7.865 Z");
grasshopper.append("path").attr("fill", ghopGreen).attr("d", "M 25.06 4.625 16.95 1.369 c 0 2.6215 1.2145 5.5035 2.264 6.496 H 8.5555 c 1.025 5.6255 8.25 10.25 15.4075 10.25 h 0 L 20.53 21.55 c -0.7685 0.7685 -0.2245 2.0825 0.8625 2.0825 h 0 a 1.22 1.22 0 0 0 0.8625 -0.3575 L 27.4 18.131 l 0.0645 0.0265 v 4.2125 c 0 0.672 0.5255 1.25 1.197 1.2605 A 1.22 1.22 0 0 0 29.9 22.411 v -4.3 h 2.44 v 4.257 c 0 0.6715 0.5255 1.25 1.197 1.2605 a 1.22 1.22 0 0 0 1.243 -1.22 v -4.3 h 0 C 34.7825 11.9935 31.1 7.1 25.06 4.625 Z");

// bounding box of original grasshopper svg
const bb = grasshopper.node().getBBox();

// offset for grasshopper origin (top-left corner to center)
const offset = {
x: bb.x + bb.width / 2,
y: bb.y + bb.height / 2
};

// apply tolerance
const shrinkage = tolerance * 2;
bb.width -= shrinkage;
bb.height -= shrinkage;
bb.x += tolerance;
bb.y += tolerance;

// initial placement of grasshopper
grasshopper.attr("transform", `translate(\${CX - offset.x}, \${y - offset.y}) rotate(\${90 * Math.atan(dy)}, \${offset.x}, \${offset.y})`);

// distances from right side of screen
let grasshopperHead = (CX - (bb.width / 2));
let grasshopperTail = (CX + (bb.width / 2));

// laserGuideguide
let laserGuide = space
.append("line")
.lower()
.attr("x1", (WIW + bb.width + shrinkage) / 2)
.attr("y1", y);

/**
* Move grasshopper
*/

// update laserGuide
function updateLaser() {
laserGuide
.attr("y1", y)
.attr("x2", WIW - x + ((Score.value + 1) * holeTohole) + sideLength)
.attr("y2", gaps[Score.value]);
}

// give the velocity a boost and change color
function jump() {
dy = -0.20;
grasshopper.selectAll("path").attr("fill", d3.hsl((ghopGreen.h + 5 * ++taps) % 360, 1, 0.725));
}

// will jump if the grasshopper is below the next hole
function autoJump() {
let holeClearance = (gaps[Score.value] + sideLength * holeHeight / 2) - (y + bb.height / 2);
let heightAboveFloor = WIH - (y + bb.height / 2);
if (holeClearance < 10 || heightAboveFloor < 10) {
jump();
}
}

// calculate and draw the grasshopper's next orientation
function moveGrasshopper() {
// double integreate to get position (fake to make game easier)
dy = dy + ddy * dt;
y = y + dy * dt + ddy * dt / 2;

// move and rotate the graphics to the correct frame
grasshopper.attr("transform", `translate(\${CX - offset.x}, \${y - offset.y}) rotate(\${90 * Math.atan(dy)}, \${offset.x}, \${offset.y})`);
}

/**
* Move Boxes
*/

// generate a column of boxes with a hole centered at h, distance from the top of screen
function createBoxes(h) {
let hue = (x / 12) % 360;
for (let i = holeHeight / 2; i < holeHeight / 2 + colSize; ++i) {
let boxColor = d3.hsl((hue += 10) % 360, 1, 0.5);
boxes
.append("rect")
.lower()
.attr("fill", boxColor)
.attr("width", sideLength)
.attr("height", sideLength)
.attr("x", WIW)
.attr("y", h + sideLength * i);
boxes
.append("rect")
.lower()
.attr("fill", boxColor)
.attr("width", sideLength)
.attr("height", sideLength)
.attr("x", WIW)
.attr("y", h - sideLength * (i + 1));
}

// create letter
if (words) {
if (Word.goal.length === 0) {
Word.goal = wordList[0].slice();
wordList.push(wordList.shift());
}
let nextLetter = Word.goal.shift();
letters
.append("text")
.attr("font-family", "monospace")
.attr("alignment-baseline", "middle")
.attr("fill", "white")
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("font-weight", 900)
.text(nextLetter)
.attr("y", h)
.attr("x", WIW + sideLength)
.attr("font-size", 60);
}
}

// move all the boxes to the left
function moveBoxes() {
for (let box of boxes.selectAll("rect").nodes().map(node => d3.select(node))) {
// d3 attributes return strings, the + operator casts to a number
box.attr("x", +box.attr("x") - 1);
if (+box.attr("x") + sideLength < 0) {
box.remove();
}
}

// move letters to the left
if (words) {
for (let letter of letters.selectAll("text").nodes().map(node => d3.select(node))) {
// d3 attributes return strings, the + operator casts to a numbers
letter.attr("x", +letter.attr("x") - 1);
if (+letter.attr("x") < CX) {
Word.current.push(letter.text());
Word.board.text(Word.current.join(""));
if (letter.text() === ";") {
Word.current = [];
}
letter.remove();
}
}
}
}

/**
* Game Functions
*/

// resets the game
function reset() {
Glitter.off();
x = 0;
y = CY;
dy = 0;
taps = 0;
Score.value = 0;
gaps = [];
boxes.selectAll("*").remove();
Score.board
.text("")
.attr("x", 0)
.attr("font-size", 50)
.on(eventType, null);
init();
letters.selectAll("text").remove();
Word.current = [];
Word.board.text("");
Word.goal = "JAVASCRIPT;".split("");
}

// check if the grasshopper has collided with the ceiling, floor, or a box
function checkCollisions() {
// max height at ceiling
if (ceiling && y < 0) {
y = 0;
}

// if it hits the floor, stop movement and stop the game clock
if (floor && y > WIH) {
dy = 0;
y = WIH;
if (collisions) {
gameEnd();
}
}

// distances from right side of screen
let columnStart = (x - ((Score.value + 1) * holeTohole));
let columnEnd = (x - (Score.value + 1) * holeTohole - sideLength);

if (collisions) {
// nose entered column
let yToholeH = Math.abs(y - gaps[Score.value]);
// check if collided with a column
if ((yToholeH + bb.height / 2) > (sideLength * holeHeight / 2)) {
gameEnd();
}
}
}

// tail exited column
if (columnEnd > grasshopperTail) {
Score.value++;
Score.board.text(binary ? `0b\${Score.value.toString(2)}` : Score.value);
}
}

// triggered to run when the game ends
function gameEnd() {
cancelAnimationFrame(gameClock);

// grasshopper bounces down to the floor
grasshopper
.transition()
.duration(1000 + WIH - y)
.ease(d3.easeBounce)
.attr("transform", `translate(\${CX - offset.x}, \${WIH - (bb.height + shrinkage)})`);

// check if score is zero
if (Score.value === 0) {
Score.board
.text("Play Again")
.attr("x", 2)
.on(eventType, reset);
}

// expand score
let scoreBB = Score.board.node().getBBox();
Score.board
.transition()
.ease(d3.easeElastic)
.duration(500)
.attr("transform", `scale(\${Math.min(WIH / scoreBB.height, WIW / scoreBB.width) * 0.95})`);

// make boxes change color
Glitter.on();
}

// main game loop
function update() {
gameClock = requestAnimationFrame(update);

// increment distance traveled
x++;

// rotate boxes and grasshopper
if (dizzyMode) {
space.attr("transform", `rotate(\${x * 0.5}, \${CX}, \${CY})`);
}

// create new boxes
if (x % holeTohole === 0) {
let yHole = 2 * sideLength + Math.random() * (WIH - 4 * sideLength);
createBoxes(yHole);
gaps.push(yHole);
}

// move the grasshopper
moveGrasshopper();

// move boxes and remove any offscreen
moveBoxes();

// check for collisions
checkCollisions();

// autopilot
if (autopilot) {
autoJump();
}

// move laser
if (laser) {
updateLaser();
}
}

// initialize game
function init() {
if (changeColor) {

// reassign listener to jump()
_svg.on(eventType, autopilot ? null : jump);
jump();

// remove floor and ceiling, make boxes color pulse, increase the column height
if (dizzyMode) {
floor = false;
ceiling = false;
Glitter.on(120, -10);
colSize = 10;
}

// red line
if (laser) {
laserGuide
.attr("stroke", "red")
.attr("stroke-width", 2);
}

// armor
if (autopilot) {
grasshopper
.attr("stroke", "silver")
.attr("stroke-width", 2);
}

// make font smaller if binary mode
if (binary) {
Score.board.attr("font-size", 30);
}

requestAnimationFrame(update);
}
}

// wait for the first tap, disable other listener (needed for desktop vs. mobile. "click" doesn't work on iPhone)
_svg.on("touchstart", () => {
_svg.on("mousedown", null);
eventType = "touchstart";
init();
});

_svg.on("mousedown", () => {
_svg.on("touchstart", null);
eventType = "mousedown";
init();
});
``````

Hi, Leli. Thank you for sharing this. The code was beautiful and easy to understand. I learn a lot from it.

Actually, My name is Abraham.

This is My Mother’s Account. And Of Course, I am 11 Years old. That’s Why I Use My Mom’s Account, Because Google Didn’t Agree To Make My Account.

Toot ihouyfokuyffykukguffkuylfyuuylfluyflfuyylfufluyfyulylifyfilfyil