diff --git a/Assets/games/STICK HERO/hero.css b/Assets/games/STICK HERO/hero.css new file mode 100644 index 0000000..9820584 --- /dev/null +++ b/Assets/games/STICK HERO/hero.css @@ -0,0 +1,127 @@ +html, +body { + height: 100%; + margin: 0; +} + +body { + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + cursor: pointer; +} + +.container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +#score { + position: absolute; + top: 30px; + right: 30px; + font-size: 2em; + font-weight: 900; +} + +#introduction { + width: 200px; + height: 150px; + position: absolute; + font-weight: 600; + font-size: 0.8em; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + text-align: center; + transition: opacity 2s; +} + +#restart { + width: 120px; + height: 120px; + position: absolute; + border-radius: 50%; + color: white; + background-color: red; + border: none; + font-weight: 700; + font-size: 1.2em; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + display: none; + cursor: pointer; +} + +#perfect { + position: absolute; + opacity: 0; + transition: opacity 2s; +} + +#youtube, +#youtube-card { + display: none; +} + +@media (min-height: 425px) { + + #youtube { + z-index: 2; + display: block; + width: 100px; + height: 70px; + position: absolute; + bottom: 20px; + left: 20px; + background: red; + border-radius: 50% / 11%; + transform: scale(0.8); + transition: transform 0.5s; + } + + #youtube:hover, + #youtube:focus { + transform: scale(0.9); + } + + #youtube::before { + content: ""; + display: block; + position: absolute; + top: 7.5%; + left: -6%; + width: 112%; + height: 85%; + background: red; + border-radius: 9% / 50%; + } + + #youtube::after { + content: ""; + display: block; + position: absolute; + top: 20px; + left: 40px; + width: 45px; + height: 30px; + border: 15px solid transparent; + box-sizing: border-box; + border-left: 30px solid white; + } + + #youtube span { + font-size: 0; + position: absolute; + width: 0; + height: 0; + overflow: hidden; + } + + #youtube:hover + #youtube-card { + display: block; + position: absolute; + bottom: 12px; + left: 10px; + padding: 25px 25px 25px 130px; + width: 300px; + background-color: white; + } +} diff --git a/Assets/games/STICK HERO/index.html b/Assets/games/STICK HERO/index.html new file mode 100644 index 0000000..658eec2 --- /dev/null +++ b/Assets/games/STICK HERO/index.html @@ -0,0 +1,20 @@ + + + + + + STICK HERO + + + +
+
+ +
Hold down the mouse to stretch out a stick
+
DOUBLE SCORE
+ +
+ + + + \ No newline at end of file diff --git a/Assets/games/STICK HERO/logic.js b/Assets/games/STICK HERO/logic.js new file mode 100644 index 0000000..fbe9822 --- /dev/null +++ b/Assets/games/STICK HERO/logic.js @@ -0,0 +1,513 @@ +// Extend the base functionality of JavaScript +Array.prototype.last = function () { + return this[this.length - 1]; + }; + + // A sinus function that acceps degrees instead of radians + Math.sinus = function (degree) { + return Math.sin((degree / 180) * Math.PI); + }; + + // Game data + let phase = "waiting"; // waiting | stretching | turning | walking | transitioning | falling + let lastTimestamp; // The timestamp of the previous requestAnimationFrame cycle + + let heroX; // Changes when moving forward + let heroY; // Only changes when falling + let sceneOffset; // Moves the whole game + + let platforms = []; + let sticks = []; + let trees = []; + + // Todo: Save high score to localStorage (?) + + let score = 0; + + // Configuration + const canvasWidth = 375; + const canvasHeight = 375; + const platformHeight = 100; + const heroDistanceFromEdge = 10; // While waiting + const paddingX = 100; // The waiting position of the hero in from the original canvas size + const perfectAreaSize = 10; + + // The background moves slower than the hero + const backgroundSpeedMultiplier = 0.2; + + const hill1BaseHeight = 100; + const hill1Amplitude = 10; + const hill1Stretch = 1; + const hill2BaseHeight = 70; + const hill2Amplitude = 20; + const hill2Stretch = 0.5; + + const stretchingSpeed = 4; // Milliseconds it takes to draw a pixel + const turningSpeed = 4; // Milliseconds it takes to turn a degree + const walkingSpeed = 4; + const transitioningSpeed = 2; + const fallingSpeed = 2; + + const heroWidth = 17; // 24 + const heroHeight = 30; // 40 + + const canvas = document.getElementById("game"); + canvas.width = window.innerWidth; // Make the Canvas full screen + canvas.height = window.innerHeight; + + const ctx = canvas.getContext("2d"); + + const introductionElement = document.getElementById("introduction"); + const perfectElement = document.getElementById("perfect"); + const restartButton = document.getElementById("restart"); + const scoreElement = document.getElementById("score"); + + // Initialize layout + resetGame(); + + // Resets game variables and layouts but does not start the game (game starts on keypress) + function resetGame() { + // Reset game progress + phase = "waiting"; + lastTimestamp = undefined; + sceneOffset = 0; + score = 0; + + introductionElement.style.opacity = 1; + perfectElement.style.opacity = 0; + restartButton.style.display = "none"; + scoreElement.innerText = score; + + // The first platform is always the same + // x + w has to match paddingX + platforms = [{ x: 50, w: 50 }]; + generatePlatform(); + generatePlatform(); + generatePlatform(); + generatePlatform(); + + sticks = [{ x: platforms[0].x + platforms[0].w, length: 0, rotation: 0 }]; + + trees = []; + generateTree(); + generateTree(); + generateTree(); + generateTree(); + generateTree(); + generateTree(); + generateTree(); + generateTree(); + generateTree(); + generateTree(); + + heroX = platforms[0].x + platforms[0].w - heroDistanceFromEdge; + heroY = 0; + + draw(); + } + + function generateTree() { + const minimumGap = 30; + const maximumGap = 150; + + // X coordinate of the right edge of the furthest tree + const lastTree = trees[trees.length - 1]; + let furthestX = lastTree ? lastTree.x : 0; + + const x = + furthestX + + minimumGap + + Math.floor(Math.random() * (maximumGap - minimumGap)); + + const treeColors = ["#6D8821", "#8FAC34", "#98B333"]; + const color = treeColors[Math.floor(Math.random() * 3)]; + + trees.push({ x, color }); + } + + function generatePlatform() { + const minimumGap = 40; + const maximumGap = 200; + const minimumWidth = 20; + const maximumWidth = 100; + + // X coordinate of the right edge of the furthest platform + const lastPlatform = platforms[platforms.length - 1]; + let furthestX = lastPlatform.x + lastPlatform.w; + + const x = + furthestX + + minimumGap + + Math.floor(Math.random() * (maximumGap - minimumGap)); + const w = + minimumWidth + Math.floor(Math.random() * (maximumWidth - minimumWidth)); + + platforms.push({ x, w }); + } + + resetGame(); + + // If space was pressed restart the game + window.addEventListener("keydown", function (event) { + if (event.key == " ") { + event.preventDefault(); + resetGame(); + return; + } + }); + + window.addEventListener("mousedown", function (event) { + if (phase == "waiting") { + lastTimestamp = undefined; + introductionElement.style.opacity = 0; + phase = "stretching"; + window.requestAnimationFrame(animate); + } + }); + + window.addEventListener("mouseup", function (event) { + if (phase == "stretching") { + phase = "turning"; + } + }); + + window.addEventListener("resize", function (event) { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + draw(); + }); + + window.requestAnimationFrame(animate); + + // The main game loop + function animate(timestamp) { + if (!lastTimestamp) { + lastTimestamp = timestamp; + window.requestAnimationFrame(animate); + return; + } + + switch (phase) { + case "waiting": + return; // Stop the loop + case "stretching": { + sticks.last().length += (timestamp - lastTimestamp) / stretchingSpeed; + break; + } + case "turning": { + sticks.last().rotation += (timestamp - lastTimestamp) / turningSpeed; + + if (sticks.last().rotation > 90) { + sticks.last().rotation = 90; + + const [nextPlatform, perfectHit] = thePlatformTheStickHits(); + if (nextPlatform) { + // Increase score + score += perfectHit ? 2 : 1; + scoreElement.innerText = score; + + if (perfectHit) { + perfectElement.style.opacity = 1; + setTimeout(() => (perfectElement.style.opacity = 0), 1000); + } + + generatePlatform(); + generateTree(); + generateTree(); + } + + phase = "walking"; + } + break; + } + case "walking": { + heroX += (timestamp - lastTimestamp) / walkingSpeed; + + const [nextPlatform] = thePlatformTheStickHits(); + if (nextPlatform) { + // If hero will reach another platform then limit it's position at it's edge + const maxHeroX = nextPlatform.x + nextPlatform.w - heroDistanceFromEdge; + if (heroX > maxHeroX) { + heroX = maxHeroX; + phase = "transitioning"; + } + } else { + // If hero won't reach another platform then limit it's position at the end of the pole + const maxHeroX = sticks.last().x + sticks.last().length + heroWidth; + if (heroX > maxHeroX) { + heroX = maxHeroX; + phase = "falling"; + } + } + break; + } + case "transitioning": { + sceneOffset += (timestamp - lastTimestamp) / transitioningSpeed; + + const [nextPlatform] = thePlatformTheStickHits(); + if (sceneOffset > nextPlatform.x + nextPlatform.w - paddingX) { + // Add the next step + sticks.push({ + x: nextPlatform.x + nextPlatform.w, + length: 0, + rotation: 0 + }); + phase = "waiting"; + } + break; + } + case "falling": { + if (sticks.last().rotation < 180) + sticks.last().rotation += (timestamp - lastTimestamp) / turningSpeed; + + heroY += (timestamp - lastTimestamp) / fallingSpeed; + const maxHeroY = + platformHeight + 100 + (window.innerHeight - canvasHeight) / 2; + if (heroY > maxHeroY) { + restartButton.style.display = "block"; + return; + } + break; + } + default: + throw Error("Wrong phase"); + } + + draw(); + window.requestAnimationFrame(animate); + + lastTimestamp = timestamp; + } + + // Returns the platform the stick hit (if it didn't hit any stick then return undefined) + function thePlatformTheStickHits() { + if (sticks.last().rotation != 90) + throw Error(`Stick is ${sticks.last().rotation}°`); + const stickFarX = sticks.last().x + sticks.last().length; + + const platformTheStickHits = platforms.find( + (platform) => platform.x < stickFarX && stickFarX < platform.x + platform.w + ); + + // If the stick hits the perfect area + if ( + platformTheStickHits && + platformTheStickHits.x + platformTheStickHits.w / 2 - perfectAreaSize / 2 < + stickFarX && + stickFarX < + platformTheStickHits.x + platformTheStickHits.w / 2 + perfectAreaSize / 2 + ) + return [platformTheStickHits, true]; + + return [platformTheStickHits, false]; + } + + function draw() { + ctx.save(); + ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + + drawBackground(); + + // Center main canvas area to the middle of the screen + ctx.translate( + (window.innerWidth - canvasWidth) / 2 - sceneOffset, + (window.innerHeight - canvasHeight) / 2 + ); + + // Draw scene + drawPlatforms(); + drawHero(); + drawSticks(); + + // Restore transformation + ctx.restore(); + } + + restartButton.addEventListener("click", function (event) { + event.preventDefault(); + resetGame(); + restartButton.style.display = "none"; + }); + + function drawPlatforms() { + platforms.forEach(({ x, w }) => { + // Draw platform + ctx.fillStyle = "black"; + ctx.fillRect( + x, + canvasHeight - platformHeight, + w, + platformHeight + (window.innerHeight - canvasHeight) / 2 + ); + + // Draw perfect area only if hero did not yet reach the platform + if (sticks.last().x < x) { + ctx.fillStyle = "red"; + ctx.fillRect( + x + w / 2 - perfectAreaSize / 2, + canvasHeight - platformHeight, + perfectAreaSize, + perfectAreaSize + ); + } + }); + } + + function drawHero() { + ctx.save(); + ctx.fillStyle = "black"; + ctx.translate( + heroX - heroWidth / 2, + heroY + canvasHeight - platformHeight - heroHeight / 2 + ); + + // Body + drawRoundedRect( + -heroWidth / 2, + -heroHeight / 2, + heroWidth, + heroHeight - 4, + 5 + ); + + // Legs + const legDistance = 5; + ctx.beginPath(); + ctx.arc(legDistance, 11.5, 3, 0, Math.PI * 2, false); + ctx.fill(); + ctx.beginPath(); + ctx.arc(-legDistance, 11.5, 3, 0, Math.PI * 2, false); + ctx.fill(); + + // Eye + ctx.beginPath(); + ctx.fillStyle = "white"; + ctx.arc(5, -7, 3, 0, Math.PI * 2, false); + ctx.fill(); + + // Band + ctx.fillStyle = "red"; + ctx.fillRect(-heroWidth / 2 - 1, -12, heroWidth + 2, 4.5); + ctx.beginPath(); + ctx.moveTo(-9, -14.5); + ctx.lineTo(-17, -18.5); + ctx.lineTo(-14, -8.5); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo(-10, -10.5); + ctx.lineTo(-15, -3.5); + ctx.lineTo(-5, -7); + ctx.fill(); + + ctx.restore(); + } + + function drawRoundedRect(x, y, width, height, radius) { + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y + height - radius); + ctx.arcTo(x, y + height, x + radius, y + height, radius); + ctx.lineTo(x + width - radius, y + height); + ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius); + ctx.lineTo(x + width, y + radius); + ctx.arcTo(x + width, y, x + width - radius, y, radius); + ctx.lineTo(x + radius, y); + ctx.arcTo(x, y, x, y + radius, radius); + ctx.fill(); + } + + function drawSticks() { + sticks.forEach((stick) => { + ctx.save(); + + // Move the anchor point to the start of the stick and rotate + ctx.translate(stick.x, canvasHeight - platformHeight); + ctx.rotate((Math.PI / 180) * stick.rotation); + + // Draw stick + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(0, 0); + ctx.lineTo(0, -stick.length); + ctx.stroke(); + + // Restore transformations + ctx.restore(); + }); + } + + function drawBackground() { + // Draw sky + var gradient = ctx.createLinearGradient(0, 0, 0, window.innerHeight); + gradient.addColorStop(0, "#BBD691"); + gradient.addColorStop(1, "#FEF1E1"); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, window.innerWidth, window.innerHeight); + + // Draw hills + drawHill(hill1BaseHeight, hill1Amplitude, hill1Stretch, "#95C629"); + drawHill(hill2BaseHeight, hill2Amplitude, hill2Stretch, "#659F1C"); + + // Draw trees + trees.forEach((tree) => drawTree(tree.x, tree.color)); + } + + // A hill is a shape under a stretched out sinus wave + function drawHill(baseHeight, amplitude, stretch, color) { + ctx.beginPath(); + ctx.moveTo(0, window.innerHeight); + ctx.lineTo(0, getHillY(0, baseHeight, amplitude, stretch)); + for (let i = 0; i < window.innerWidth; i++) { + ctx.lineTo(i, getHillY(i, baseHeight, amplitude, stretch)); + } + ctx.lineTo(window.innerWidth, window.innerHeight); + ctx.fillStyle = color; + ctx.fill(); + } + + function drawTree(x, color) { + ctx.save(); + ctx.translate( + (-sceneOffset * backgroundSpeedMultiplier + x) * hill1Stretch, + getTreeY(x, hill1BaseHeight, hill1Amplitude) + ); + + const treeTrunkHeight = 5; + const treeTrunkWidth = 2; + const treeCrownHeight = 25; + const treeCrownWidth = 10; + + // Draw trunk + ctx.fillStyle = "#7D833C"; + ctx.fillRect( + -treeTrunkWidth / 2, + -treeTrunkHeight, + treeTrunkWidth, + treeTrunkHeight + ); + + // Draw crown + ctx.beginPath(); + ctx.moveTo(-treeCrownWidth / 2, -treeTrunkHeight); + ctx.lineTo(0, -(treeTrunkHeight + treeCrownHeight)); + ctx.lineTo(treeCrownWidth / 2, -treeTrunkHeight); + ctx.fillStyle = color; + ctx.fill(); + + ctx.restore(); + } + + function getHillY(windowX, baseHeight, amplitude, stretch) { + const sineBaseY = window.innerHeight - baseHeight; + return ( + Math.sinus((sceneOffset * backgroundSpeedMultiplier + windowX) * stretch) * + amplitude + + sineBaseY + ); + } + + function getTreeY(x, baseHeight, amplitude) { + const sineBaseY = window.innerHeight - baseHeight; + return Math.sinus(x) * amplitude + sineBaseY; + } + \ No newline at end of file diff --git a/Assets/games/STICK HERO/ss_stick_hero.png b/Assets/games/STICK HERO/ss_stick_hero.png new file mode 100644 index 0000000..e0e280b Binary files /dev/null and b/Assets/games/STICK HERO/ss_stick_hero.png differ diff --git a/index.html b/index.html index 3291d81..477094b 100644 --- a/index.html +++ b/index.html @@ -556,6 +556,14 @@

Random Name Generator


+ +
+ +

STICK HERO

+ +
+
+ @@ -694,6 +702,7 @@

Contact Us

+