diff --git a/Games/Alphabet-and-Vowels/README.md b/Games/Alphabet-and-Vowels/README.md
new file mode 100644
index 0000000000..7e19c63d8d
--- /dev/null
+++ b/Games/Alphabet-and-Vowels/README.md
@@ -0,0 +1,38 @@
+# **Alphabet and Vowel Game**
+
+---
+
+
+
+## **Description 📃**
+
+- The Alphabet and Vowel Game is an interactive web-based game designed to help players identify vowels and consonants. This game is ideal for educational purposes, providing a fun way to enhance letter recognition and differentiate between vowels and consonants. It offers a seamless and enjoyable experience for players of all ages.
+
+## **Functionalities 🎮**
+
+- Displays a grid of alphabet letters.
+- Prompts players to click on vowels first, followed by consonants.
+- Provides immediate feedback on the correctness of the selected letter.
+- Tracks player progress and updates instructions dynamically.
+- Features a reset button to start a new game session.
+- Boasts a modern, user-friendly interface for an engaging experience.
+
+
+
+## **How to play? 🕹️**
+
+1. Launch the Alphabet and Vowel Game in your browser.
+2. Read the instructions to know whether to click on vowels or consonants.
+3. Click on the letters displayed in the grid according to the instructions.
+4. Receive instant feedback indicating if your choice was correct or incorrect.
+5. Complete the first task (clicking all vowels), then move on to the next task (clicking all consonants).
+6. Use the "Reset Game" button to restart the game at any time.
+
+
+
+## **Screenshots 📸**
+
+![image](https://github.com/kunjgit/GameZone/assets/97523900/03b0a814-d6f5-4aea-8ca9-7995947677e0)
+
+
+
diff --git a/Games/Alphabet-and-Vowels/app.js b/Games/Alphabet-and-Vowels/app.js
new file mode 100644
index 0000000000..4b7164453c
--- /dev/null
+++ b/Games/Alphabet-and-Vowels/app.js
@@ -0,0 +1,56 @@
+const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
+const vowels = 'AEIOU';
+let score = 0;
+let target = 'vowel';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const alphabetGrid = document.getElementById('alphabetGrid');
+ const feedback = document.getElementById('feedback');
+ const instruction = document.getElementById('instruction');
+ const resetButton = document.getElementById('resetButton');
+
+ const shuffleArray = array => array.sort(() => Math.random() - 0.5);
+
+ const generateButtons = () => {
+ shuffleArray(alphabet).forEach(letter => {
+ const button = document.createElement('button');
+ button.textContent = letter;
+ button.addEventListener('click', () => checkLetter(letter, button));
+ alphabetGrid.appendChild(button);
+ });
+ };
+
+ const checkLetter = (letter, button) => {
+ if ((target === 'vowel' && vowels.includes(letter)) ||
+ (target === 'consonant' && !vowels.includes(letter))) {
+ button.classList.add('correct');
+ score++;
+ feedback.textContent = 'Correct!';
+ } else {
+ button.classList.add('wrong');
+ feedback.textContent = 'Try again!';
+ }
+
+ button.disabled = true;
+
+ if (score === 5) {
+ target = 'consonant';
+ instruction.textContent = 'Now click on all the consonants!';
+ score = 0;
+ feedback.textContent = '';
+ }
+ };
+
+ const resetGame = () => {
+ alphabetGrid.innerHTML = '';
+ feedback.textContent = '';
+ instruction.textContent = 'Click on all the vowels!';
+ score = 0;
+ target = 'vowel';
+ generateButtons();
+ };
+
+ resetButton.addEventListener('click', resetGame);
+
+ generateButtons();
+});
diff --git a/Games/Alphabet-and-Vowels/index.html b/Games/Alphabet-and-Vowels/index.html
new file mode 100644
index 0000000000..c8867851b4
--- /dev/null
+++ b/Games/Alphabet-and-Vowels/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Alphabet and Vowel Game
+
+
+
+
+
Alphabet and Vowel Game
+
Click on all the vowels!
+
+
+
+
+
+
+
diff --git a/Games/Alphabet-and-Vowels/style.css b/Games/Alphabet-and-Vowels/style.css
new file mode 100644
index 0000000000..ae806620eb
--- /dev/null
+++ b/Games/Alphabet-and-Vowels/style.css
@@ -0,0 +1,69 @@
+body {
+ font-family: 'Arial', sans-serif;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ margin: 0;
+ background-color: #16d1e2;
+}
+
+.container {
+ text-align: center;
+ background: #fff;
+ padding: 20px;
+ border-radius: 10px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+}
+
+h1 {
+ color: #333;
+}
+
+.alphabet-grid {
+ display: grid;
+ grid-template-columns: repeat(6, 50px);
+ gap: 10px;
+ justify-content: center;
+ margin: 20px 0;
+}
+
+.alphabet-grid button {
+ background-color: #008cba;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ width: 50px;
+ height: 50px;
+ font-size: 18px;
+ cursor: pointer;
+ transition: background-color 0.3s;
+}
+
+.alphabet-grid button.correct {
+ background-color: #4caf50;
+}
+
+.alphabet-grid button.wrong {
+ background-color: #f44336;
+}
+
+#feedback {
+ margin-top: 20px;
+ font-size: 20px;
+}
+
+#resetButton {
+ background-color: #ff9800;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ padding: 10px 20px;
+ font-size: 18px;
+ cursor: pointer;
+ transition: background-color 0.3s;
+}
+
+#resetButton:hover {
+ background-color: #e68900;
+}
diff --git a/Games/Block_Ninja/README.md b/Games/Block_Ninja/README.md
new file mode 100644
index 0000000000..cee18fdbb6
--- /dev/null
+++ b/Games/Block_Ninja/README.md
@@ -0,0 +1,59 @@
+# **Block Ninja**
+
+---
+
+## **Description 📃**
+Block Ninja is an engaging and addictive game where players must slash through blocks that appear on the screen while avoiding dangerous obstacles. The game tests your reflexes and precision as you aim to score the highest points by slicing through as many blocks as possible. The challenge increases with each level, making it an exciting experience for players of all ages.
+
+## **Functionalities 🎮**
+- **Start Game:** Begin a new game session.
+- **Slash Blocks:** Use swipe gestures or mouse movements to slash through blocks.
+- **Avoid Obstacles:** Dodge dangerous obstacles to prevent losing points or ending the game.
+- **Score Tracking:** Keep track of your highest scores and aim to beat them.
+- **Multiple Levels:** Progress through various levels of increasing difficulty.
+- **Sound Effects:** Enjoy immersive sound effects that enhance the gaming experience.
+- **Pause/Resume:** Pause the game anytime and resume from where you left off.
+
+## **How to Play? 🕹️**
+1. **Launch the Game:**
+ - Open the Block Ninja game application on your device.
+
+2. **Start a New Game:**
+ - Click on the "Start Game" button to begin.
+
+3. **Slash Blocks:**
+ - Use your finger (on touch screens) or mouse (on computers) to swipe across the screen.
+ - Slash through the blocks that appear to score points.
+
+4. **Avoid Obstacles:**
+ - Be cautious of obstacles that appear among the blocks.
+ - Avoid slashing through these obstacles as they can end the game or reduce your points.
+
+5. **Score Points:**
+ - Successfully slashing blocks will earn you points.
+ - Try to slash multiple blocks in one swipe for combo points.
+
+6. **Advance Through Levels:**
+ - As you score points, you'll advance through levels of increasing difficulty.
+ - Each level presents new challenges and faster-moving blocks.
+
+7. **Pause/Resume:**
+ - You can pause the game at any time by clicking the "Pause" button.
+ - Resume the game from the same point by clicking "Resume."
+
+8. **End of Game:**
+ - The game ends when you hit a dangerous obstacle or miss too many blocks.
+ - Your final score will be displayed, and you can choose to start a new game.
+
+---
+
+Enjoy slicing through blocks and mastering the art of the Block Ninja! Can you achieve the highest score and become the ultimate ninja?
+
+
+## **Screenshots 📸**
+
+
+![image](../../assets/images/Block_Ninja.png)
+
+
+
diff --git a/Games/Block_Ninja/index.html b/Games/Block_Ninja/index.html
new file mode 100644
index 0000000000..0c93eb7494
--- /dev/null
+++ b/Games/Block_Ninja/index.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Block Ninja!
+
+
+
+
+
Paused
+
+
+
+
+
Game Over
+
Your Score:
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Games/Block_Ninja/script.js b/Games/Block_Ninja/script.js
new file mode 100644
index 0000000000..60fa9b177c
--- /dev/null
+++ b/Games/Block_Ninja/script.js
@@ -0,0 +1,2185 @@
+// globalConfig.js
+// ============================================================================
+// ============================================================================
+
+// Provides global variables used by the entire program.
+// Most of this should be configuration.
+
+// Timing multiplier for entire game engine.
+let gameSpeed = 1;
+
+// Colors
+const BLUE = { r: 0x67, g: 0xd7, b: 0xf0 };
+const GREEN = { r: 0xa6, g: 0xe0, b: 0x2c };
+const PINK = { r: 0xfa, g: 0x24, b: 0x73 };
+const ORANGE = { r: 0xfe, g: 0x95, b: 0x22 };
+const allColors = [BLUE, GREEN, PINK, ORANGE];
+
+// Gameplay
+const getSpawnDelay = () => {
+ const spawnDelayMax = 1400;
+ const spawnDelayMin = 550;
+ const spawnDelay = spawnDelayMax - state.game.cubeCount * 3.1;
+ return Math.max(spawnDelay, spawnDelayMin);
+}
+const doubleStrongEnableScore = 2000;
+// Number of cubes that must be smashed before activating a feature.
+const slowmoThreshold = 10;
+const strongThreshold = 25;
+const spinnerThreshold = 25;
+
+// Interaction state
+let pointerIsDown = false;
+// The last known position of the primary pointer in screen coordinates.`
+let pointerScreen = { x: 0, y: 0 };
+// Same as `pointerScreen`, but converted to scene coordinates in rAF.
+let pointerScene = { x: 0, y: 0 };
+// Minimum speed of pointer before "hits" are counted.
+const minPointerSpeed = 50;
+// The hit speed affects the direction the target post-hit. This number dampens that force.
+const hitDampening = 0.1;
+// Backboard receives shadows and is the farthest negative Z position of entities.
+const backboardZ = -400;
+const shadowColor = '#262e36';
+// How much air drag is applied to standard objects
+const airDrag = 0.022;
+const gravity = 0.3;
+// Spark config
+const sparkColor = 'rgba(170,221,255,.9)';
+const sparkThickness = 2.2;
+const airDragSpark = 0.1;
+// Track pointer positions to show trail
+const touchTrailColor = 'rgba(170,221,255,.62)';
+const touchTrailThickness = 7;
+const touchPointLife = 120;
+const touchPoints = [];
+// Size of in-game targets. This affects rendered size and hit area.
+const targetRadius = 40;
+const targetHitRadius = 50;
+const makeTargetGlueColor = target => {
+ // const alpha = (target.health - 1) / (target.maxHealth - 1);
+ // return `rgba(170,221,255,${alpha.toFixed(3)})`;
+ return 'rgb(170,221,255)';
+};
+// Size of target fragments
+const fragRadius = targetRadius / 3;
+
+
+
+// Game canvas element needed in setup.js and interaction.js
+const canvas = document.querySelector('#c');
+
+// 3D camera config
+// Affects perspective
+const cameraDistance = 900;
+// Does not affect perspective
+const sceneScale = 1;
+// Objects that get too close to the camera will be faded out to transparent over this range.
+// const cameraFadeStartZ = 0.8*cameraDistance - 6*targetRadius;
+const cameraFadeStartZ = 0.45*cameraDistance;
+const cameraFadeEndZ = 0.65*cameraDistance;
+const cameraFadeRange = cameraFadeEndZ - cameraFadeStartZ;
+
+// Globals used to accumlate all vertices/polygons in each frame
+const allVertices = [];
+const allPolys = [];
+const allShadowVertices = [];
+const allShadowPolys = [];
+
+
+
+
+// state.js
+// ============================================================================
+// ============================================================================
+
+///////////
+// Enums //
+///////////
+
+// Game Modes
+const GAME_MODE_RANKED = Symbol('GAME_MODE_RANKED');
+const GAME_MODE_CASUAL = Symbol('GAME_MODE_CASUAL');
+
+// Available Menus
+const MENU_MAIN = Symbol('MENU_MAIN');
+const MENU_PAUSE = Symbol('MENU_PAUSE');
+const MENU_SCORE = Symbol('MENU_SCORE');
+
+
+
+//////////////////
+// Global State //
+//////////////////
+
+const state = {
+ game: {
+ mode: GAME_MODE_RANKED,
+ // Run time of current game.
+ time: 0,
+ // Player score.
+ score: 0,
+ // Total number of cubes smashed in game.
+ cubeCount: 0
+ },
+ menus: {
+ // Set to `null` to hide all menus
+ active: MENU_MAIN
+ }
+};
+
+
+////////////////////////////
+// Global State Selectors //
+////////////////////////////
+
+const isInGame = () => !state.menus.active;
+const isMenuVisible = () => !!state.menus.active;
+const isCasualGame = () => state.game.mode === GAME_MODE_CASUAL;
+const isPaused = () => state.menus.active === MENU_PAUSE;
+
+
+///////////////////
+// Local Storage //
+///////////////////
+
+const highScoreKey = '__menja__highScore';
+const getHighScore = () => {
+ const raw = localStorage.getItem(highScoreKey);
+ return raw ? parseInt(raw, 10) : 0;
+};
+
+let _lastHighscore = getHighScore();
+const setHighScore = score => {
+ _lastHighscore = getHighScore();
+ localStorage.setItem(highScoreKey, String(score));
+};
+
+const isNewHighScore = () => state.game.score > _lastHighscore;
+
+
+
+
+// utils.js
+// ============================================================================
+// ============================================================================
+
+
+const invariant = (condition, message) => {
+ if (!condition) throw new Error(message);
+};
+
+
+/////////
+// DOM //
+/////////
+
+const $ = selector => document.querySelector(selector);
+const handleClick = (element, handler) => element.addEventListener('click', handler);
+const handlePointerDown = (element, handler) => {
+ element.addEventListener('touchstart', handler);
+ element.addEventListener('mousedown', handler);
+};
+
+
+
+////////////////////////
+// Formatting Helpers //
+////////////////////////
+
+// Converts a number into a formatted string with thousand separators.
+const formatNumber = num => num.toLocaleString();
+
+
+
+////////////////////
+// Math Constants //
+////////////////////
+
+const PI = Math.PI;
+const TAU = Math.PI * 2;
+const ETA = Math.PI * 0.5;
+
+
+
+//////////////////
+// Math Helpers //
+//////////////////
+
+// Clamps a number between min and max values (inclusive)
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+
+// Linearly interpolate between numbers a and b by a specific amount.
+// mix >= 0 && mix <= 1
+const lerp = (a, b, mix) => (b - a) * mix + a;
+
+
+
+
+////////////////////
+// Random Helpers //
+////////////////////
+
+// Generates a random number between min (inclusive) and max (exclusive)
+const random = (min, max) => Math.random() * (max - min) + min;
+
+// Generates a random integer between and possibly including min and max values
+const randomInt = (min, max) => ((Math.random() * (max - min + 1)) | 0) + min;
+
+// Returns a random element from an array
+const pickOne = arr => arr[Math.random() * arr.length | 0];
+
+
+
+
+///////////////////
+// Color Helpers //
+///////////////////
+
+// Converts an { r, g, b } color object to a 6-digit hex code.
+const colorToHex = color => {
+ return '#' +
+ (color.r | 0).toString(16).padStart(2, '0') +
+ (color.g | 0).toString(16).padStart(2, '0') +
+ (color.b | 0).toString(16).padStart(2, '0');
+};
+
+// Operates on an { r, g, b } color object.
+// Returns string hex code.
+// `lightness` must range from 0 to 1. 0 is pure black, 1 is pure white.
+const shadeColor = (color, lightness) => {
+ let other, mix;
+ if (lightness < 0.5) {
+ other = 0;
+ mix = 1 - (lightness * 2);
+ } else {
+ other = 255;
+ mix = lightness * 2 - 1;
+ }
+ return '#' +
+ (lerp(color.r, other, mix) | 0).toString(16).padStart(2, '0') +
+ (lerp(color.g, other, mix) | 0).toString(16).padStart(2, '0') +
+ (lerp(color.b, other, mix) | 0).toString(16).padStart(2, '0');
+};
+
+
+
+
+
+////////////////////
+// Timing Helpers //
+////////////////////
+
+const _allCooldowns = [];
+
+const makeCooldown = (rechargeTime, units=1) => {
+ let timeRemaining = 0;
+ let lastTime = 0;
+
+ const initialOptions = { rechargeTime, units };
+
+ const updateTime = () => {
+ const now = state.game.time;
+ // Reset time remaining if time goes backwards.
+ if (now < lastTime) {
+ timeRemaining = 0;
+ } else {
+ // update...
+ timeRemaining -= now-lastTime;
+ if (timeRemaining < 0) timeRemaining = 0;
+ }
+ lastTime = now;
+ };
+
+ const canUse = () => {
+ updateTime();
+ return timeRemaining <= (rechargeTime * (units-1));
+ };
+
+ const cooldown = {
+ canUse,
+ useIfAble() {
+ const usable = canUse();
+ if (usable) timeRemaining += rechargeTime;
+ return usable;
+ },
+ mutate(options) {
+ if (options.rechargeTime) {
+ // Apply recharge time delta so change takes effect immediately.
+ timeRemaining -= rechargeTime-options.rechargeTime;
+ if (timeRemaining < 0) timeRemaining = 0;
+ rechargeTime = options.rechargeTime;
+ }
+ if (options.units) units = options.units;
+ },
+ reset() {
+ timeRemaining = 0;
+ lastTime = 0;
+ this.mutate(initialOptions);
+ }
+ };
+
+ _allCooldowns.push(cooldown);
+
+ return cooldown;
+};
+
+const resetAllCooldowns = () => _allCooldowns.forEach(cooldown => cooldown.reset());
+
+const makeSpawner = ({ chance, cooldownPerSpawn, maxSpawns }) => {
+ const cooldown = makeCooldown(cooldownPerSpawn, maxSpawns);
+ return {
+ shouldSpawn() {
+ return Math.random() <= chance && cooldown.useIfAble();
+ },
+ mutate(options) {
+ if (options.chance) chance = options.chance;
+ cooldown.mutate({
+ rechargeTime: options.cooldownPerSpawn,
+ units: options.maxSpawns
+ });
+ }
+ };
+};
+
+
+
+
+////////////////////
+// Vector Helpers //
+////////////////////
+
+const normalize = v => {
+ const mag = Math.hypot(v.x, v.y, v.z);
+ return {
+ x: v.x / mag,
+ y: v.y / mag,
+ z: v.z / mag
+ };
+}
+
+// Curried math helpers
+const add = a => b => a + b;
+// Curried vector helpers
+const scaleVector = scale => vector => {
+ vector.x *= scale;
+ vector.y *= scale;
+ vector.z *= scale;
+};
+
+
+
+
+
+
+
+
+////////////////
+// 3D Helpers //
+////////////////
+
+// Clone array and all vertices.
+function cloneVertices(vertices) {
+ return vertices.map(v => ({ x: v.x, y: v.y, z: v.z }));
+}
+
+// Copy vertex data from one array into another.
+// Arrays must be the same length.
+function copyVerticesTo(arr1, arr2) {
+ const len = arr1.length;
+ for (let i=0; i {
+ const targetVertex = target[i];
+ // X axis rotation
+ const x1 = v.x;
+ const y1 = v.z*sinX + v.y*cosX;
+ const z1 = v.z*cosX - v.y*sinX;
+ // Y axis rotation
+ const x2 = x1*cosY - z1*sinY;
+ const y2 = y1;
+ const z2 = x1*sinY + z1*cosY;
+ // Z axis rotation
+ const x3 = x2*cosZ - y2*sinZ;
+ const y3 = x2*sinZ + y2*cosZ;
+ const z3 = z2;
+
+ // Scale, Translate, and set the transform.
+ targetVertex.x = x3 * sX + tX;
+ targetVertex.y = y3 * sY + tY;
+ targetVertex.z = z3 * sZ + tZ;
+ });
+}
+
+// 3D projection on a single vertex.
+// Directly mutates the vertex.
+const projectVertex = v => {
+ const focalLength = cameraDistance * sceneScale;
+ const depth = focalLength / (cameraDistance - v.z);
+ v.x = v.x * depth;
+ v.y = v.y * depth;
+};
+
+// 3D projection on a single vertex.
+// Mutates a secondary target vertex.
+const projectVertexTo = (v, target) => {
+ const focalLength = cameraDistance * sceneScale;
+ const depth = focalLength / (cameraDistance - v.z);
+ target.x = v.x * depth;
+ target.y = v.y * depth;
+};
+
+
+
+
+
+// PERF.js
+// ============================================================================
+// ============================================================================
+
+// Dummy no-op functions.
+// I use these in a special build for custom performance profiling.
+const PERF_START = () => {};
+const PERF_END = () => {};
+const PERF_UPDATE = () => {};
+
+
+
+
+// 3dModels.js
+// ============================================================================
+// ============================================================================
+
+// Define models once. The origin is the center of the model.
+
+// A simple cube, 8 vertices, 6 quads.
+// Defaults to an edge length of 2 units, can be influenced with `scale`.
+function makeCubeModel({ scale=1 }) {
+ return {
+ vertices: [
+ // top
+ { x: -scale, y: -scale, z: scale },
+ { x: scale, y: -scale, z: scale },
+ { x: scale, y: scale, z: scale },
+ { x: -scale, y: scale, z: scale },
+ // bottom
+ { x: -scale, y: -scale, z: -scale },
+ { x: scale, y: -scale, z: -scale },
+ { x: scale, y: scale, z: -scale },
+ { x: -scale, y: scale, z: -scale }
+ ],
+ polys: [
+ // z = 1
+ { vIndexes: [0, 1, 2, 3] },
+ // z = -1
+ { vIndexes: [7, 6, 5, 4] },
+ // y = 1
+ { vIndexes: [3, 2, 6, 7] },
+ // y = -1
+ { vIndexes: [4, 5, 1, 0] },
+ // x = 1
+ { vIndexes: [5, 6, 2, 1] },
+ // x = -1
+ { vIndexes: [0, 3, 7, 4] }
+ ]
+ };
+}
+
+// Not very optimized - lots of duplicate vertices are generated.
+function makeRecursiveCubeModel({ recursionLevel, splitFn, color, scale=1 }) {
+ const getScaleAtLevel = level => 1 / (3 ** level);
+
+ // We can model level 0 manually. It's just a single, centered, cube.
+ let cubeOrigins = [{ x: 0, y: 0, z: 0 }];
+
+ // Recursively replace cubes with smaller cubes.
+ for (let i=1; i<=recursionLevel; i++) {
+ const scale = getScaleAtLevel(i) * 2;
+ const cubeOrigins2 = [];
+ cubeOrigins.forEach(origin => {
+ cubeOrigins2.push(...splitFn(origin, scale));
+ });
+ cubeOrigins = cubeOrigins2;
+ }
+
+ const finalModel = { vertices: [], polys: [] };
+
+ // Generate single cube model and scale it.
+ const cubeModel = makeCubeModel({ scale: 1 });
+ cubeModel.vertices.forEach(scaleVector(getScaleAtLevel(recursionLevel)));
+
+ // Compute the max distance x, y, or z origin values will be.
+ // Same result as `Math.max(...cubeOrigins.map(o => o.x))`, but much faster.
+ const maxComponent = getScaleAtLevel(recursionLevel) * (3 ** recursionLevel - 1);
+
+ // Place cube geometry at each origin.
+ cubeOrigins.forEach((origin, cubeIndex) => {
+ // To compute occlusion (shading), find origin component with greatest
+ // magnitude and normalize it relative to `maxComponent`.
+ const occlusion = Math.max(
+ Math.abs(origin.x),
+ Math.abs(origin.y),
+ Math.abs(origin.z)
+ ) / maxComponent;
+ // At lower iterations, occlusion looks better lightened up a bit.
+ const occlusionLighter = recursionLevel > 2
+ ? occlusion
+ : (occlusion + 0.8) / 1.8;
+ // Clone, translate vertices to origin, and apply scale
+ finalModel.vertices.push(
+ ...cubeModel.vertices.map(v => ({
+ x: (v.x + origin.x) * scale,
+ y: (v.y + origin.y) * scale,
+ z: (v.z + origin.z) * scale
+ }))
+ );
+ // Clone polys, shift referenced vertex indexes, and compute color.
+ finalModel.polys.push(
+ ...cubeModel.polys.map(poly => ({
+ vIndexes: poly.vIndexes.map(add(cubeIndex * 8))
+ }))
+ );
+ });
+
+ return finalModel;
+}
+
+
+// o: Vector3D - Position of cube's origin (center).
+// s: Vector3D - Determines size of menger sponge.
+function mengerSpongeSplit(o, s) {
+ return [
+ // Top
+ { x: o.x + s, y: o.y - s, z: o.z + s },
+ { x: o.x + s, y: o.y - s, z: o.z + 0 },
+ { x: o.x + s, y: o.y - s, z: o.z - s },
+ { x: o.x + 0, y: o.y - s, z: o.z + s },
+ { x: o.x + 0, y: o.y - s, z: o.z - s },
+ { x: o.x - s, y: o.y - s, z: o.z + s },
+ { x: o.x - s, y: o.y - s, z: o.z + 0 },
+ { x: o.x - s, y: o.y - s, z: o.z - s },
+ // Bottom
+ { x: o.x + s, y: o.y + s, z: o.z + s },
+ { x: o.x + s, y: o.y + s, z: o.z + 0 },
+ { x: o.x + s, y: o.y + s, z: o.z - s },
+ { x: o.x + 0, y: o.y + s, z: o.z + s },
+ { x: o.x + 0, y: o.y + s, z: o.z - s },
+ { x: o.x - s, y: o.y + s, z: o.z + s },
+ { x: o.x - s, y: o.y + s, z: o.z + 0 },
+ { x: o.x - s, y: o.y + s, z: o.z - s },
+ // Middle
+ { x: o.x + s, y: o.y + 0, z: o.z + s },
+ { x: o.x + s, y: o.y + 0, z: o.z - s },
+ { x: o.x - s, y: o.y + 0, z: o.z + s },
+ { x: o.x - s, y: o.y + 0, z: o.z - s }
+ ];
+}
+
+
+
+// Helper to optimize models by merging duplicate vertices within a threshold,
+// and removing all polys that share the same vertices.
+// Directly mutates the model.
+function optimizeModel(model, threshold=0.0001) {
+ const { vertices, polys } = model;
+
+ const compareVertices = (v1, v2) => (
+ Math.abs(v1.x - v2.x) < threshold &&
+ Math.abs(v1.y - v2.y) < threshold &&
+ Math.abs(v1.z - v2.z) < threshold
+ );
+
+ const comparePolys = (p1, p2) => {
+ const v1 = p1.vIndexes;
+ const v2 = p2.vIndexes;
+ return (
+ (
+ v1[0] === v2[0] ||
+ v1[0] === v2[1] ||
+ v1[0] === v2[2] ||
+ v1[0] === v2[3]
+ ) && (
+ v1[1] === v2[0] ||
+ v1[1] === v2[1] ||
+ v1[1] === v2[2] ||
+ v1[1] === v2[3]
+ ) && (
+ v1[2] === v2[0] ||
+ v1[2] === v2[1] ||
+ v1[2] === v2[2] ||
+ v1[2] === v2[3]
+ ) && (
+ v1[3] === v2[0] ||
+ v1[3] === v2[1] ||
+ v1[3] === v2[2] ||
+ v1[3] === v2[3]
+ )
+ );
+ };
+
+
+ vertices.forEach((v, i) => {
+ v.originalIndexes = [i];
+ });
+
+ for (let i=vertices.length-1; i>=0; i--) {
+ for (let ii=i-1; ii>=0; ii--) {
+ const v1 = vertices[i];
+ const v2 = vertices[ii];
+ if (compareVertices(v1, v2)) {
+ vertices.splice(i, 1);
+ v2.originalIndexes.push(...v1.originalIndexes);
+ break;
+ }
+ }
+ }
+
+ vertices.forEach((v, i) => {
+ polys.forEach(p => {
+ p.vIndexes.forEach((vi, ii, arr) => {
+ const vo = v.originalIndexes;
+ if (vo.includes(vi)) {
+ arr[ii] = i;
+ }
+ });
+ });
+ });
+
+ polys.forEach(p => {
+ const vi = p.vIndexes;
+ p.sum = vi[0] + vi[1] + vi[2] + vi[3];
+ });
+ polys.sort((a, b) => b.sum - a.sum);
+
+ // Assumptions:
+ // 1. Each poly will either have no duplicates or 1 duplicate.
+ // 2. If two polys are equal, they are both hidden (two cubes touching),
+ // therefore both can be removed.
+ for (let i=polys.length-1; i>=0; i--) {
+ for (let ii=i-1; ii>=0; ii--) {
+ const p1 = polys[i];
+ const p2 = polys[ii];
+ if (p1.sum !== p2.sum) break;
+ if (comparePolys(p1, p2)) {
+ polys.splice(i, 1);
+ polys.splice(ii, 1);
+ i--;
+ break;
+ }
+ }
+ }
+
+ return model;
+}
+
+
+
+
+
+// Entity.js
+// ============================================================================
+// ============================================================================
+
+class Entity {
+ constructor({ model, color, wireframe=false }) {
+ const vertices = cloneVertices(model.vertices);
+ const shadowVertices = cloneVertices(model.vertices);
+ const colorHex = colorToHex(color);
+ const darkColorHex = shadeColor(color, 0.4);
+
+ const polys = model.polys.map(p => ({
+ vertices: p.vIndexes.map(vIndex => vertices[vIndex]),
+ color: color, // custom rgb color object
+ wireframe: wireframe,
+ strokeWidth: wireframe ? 2 : 0, // Set to non-zero value to draw stroke
+ strokeColor: colorHex, // must be a CSS color string
+ strokeColorDark: darkColorHex, // must be a CSS color string
+ depth: 0,
+ middle: { x: 0, y: 0, z: 0 },
+ normalWorld: { x: 0, y: 0, z: 0 },
+ normalCamera: { x: 0, y: 0, z: 0 }
+ }));
+
+ const shadowPolys = model.polys.map(p => ({
+ vertices: p.vIndexes.map(vIndex => shadowVertices[vIndex]),
+ wireframe: wireframe,
+ normalWorld: { x: 0, y: 0, z: 0 }
+ }));
+
+ this.projected = {}; // Will store 2D projected data
+ this.model = model;
+ this.vertices = vertices;
+ this.polys = polys;
+ this.shadowVertices = shadowVertices;
+ this.shadowPolys = shadowPolys;
+ this.reset();
+ }
+
+ // Better names: resetEntity, resetTransform, resetEntityTransform
+ reset() {
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+ this.xD = 0;
+ this.yD = 0;
+ this.zD = 0;
+
+ this.rotateX = 0;
+ this.rotateY = 0;
+ this.rotateZ = 0;
+ this.rotateXD = 0;
+ this.rotateYD = 0;
+ this.rotateZD = 0;
+
+ this.scaleX = 1;
+ this.scaleY = 1;
+ this.scaleZ = 1;
+
+ this.projected.x = 0;
+ this.projected.y = 0;
+ }
+
+ transform() {
+ transformVertices(
+ this.model.vertices,
+ this.vertices,
+ this.x,
+ this.y,
+ this.z,
+ this.rotateX,
+ this.rotateY,
+ this.rotateZ,
+ this.scaleX,
+ this.scaleY,
+ this.scaleZ
+ );
+
+ copyVerticesTo(this.vertices, this.shadowVertices);
+ }
+
+ // Projects origin point, stored as `projected` property.
+ project() {
+ projectVertexTo(this, this.projected);
+ }
+}
+
+
+
+
+
+// getTarget.js
+// ============================================================================
+// ============================================================================
+
+// All active targets
+const targets = [];
+
+// Pool target instances by color, using a Map.
+// keys are color objects, and values are arrays of targets.
+// Also pool wireframe instances separately.
+const targetPool = new Map(allColors.map(c=>([c, []])));
+const targetWireframePool = new Map(allColors.map(c=>([c, []])));
+
+
+
+const getTarget = (() => {
+
+ const slowmoSpawner = makeSpawner({
+ chance: 0.5,
+ cooldownPerSpawn: 10000,
+ maxSpawns: 1
+ });
+
+ let doubleStrong = false;
+ const strongSpawner = makeSpawner({
+ chance: 0.3,
+ cooldownPerSpawn: 12000,
+ maxSpawns: 1
+ });
+
+ const spinnerSpawner = makeSpawner({
+ chance: 0.1,
+ cooldownPerSpawn: 10000,
+ maxSpawns: 1
+ });
+
+ // Cached array instances, no need to allocate every time.
+ const axisOptions = [
+ ['x', 'y'],
+ ['y', 'z'],
+ ['z', 'x']
+ ];
+
+ function getTargetOfStyle(color, wireframe) {
+ const pool = wireframe ? targetWireframePool : targetPool;
+ let target = pool.get(color).pop();
+ if (!target) {
+ target = new Entity({
+ model: optimizeModel(makeRecursiveCubeModel({
+ recursionLevel: 1,
+ splitFn: mengerSpongeSplit,
+ scale: targetRadius
+ })),
+ color: color,
+ wireframe: wireframe
+ });
+
+ // Init any properties that will be used.
+ // These will not be automatically reset when recycled.
+ target.color = color;
+ target.wireframe = wireframe;
+ // Some properties don't have their final value yet.
+ // Initialize with any value of the right type.
+ target.hit = false;
+ target.maxHealth = 0;
+ target.health = 0;
+ }
+ return target;
+ }
+
+ return function getTarget() {
+ if (doubleStrong && state.game.score <= doubleStrongEnableScore) {
+ doubleStrong = false;
+ // Spawner is reset automatically when game resets.
+ } else if (!doubleStrong && state.game.score > doubleStrongEnableScore) {
+ doubleStrong = true;
+ strongSpawner.mutate({ maxSpawns: 2 });
+ }
+
+ // Target Parameters
+ // --------------------------------
+ let color = pickOne([BLUE, GREEN, ORANGE]);
+ let wireframe = false;
+ let health = 1;
+ let maxHealth = 3;
+ const spinner = state.game.cubeCount >= spinnerThreshold && isInGame() && spinnerSpawner.shouldSpawn();
+
+ // Target Parameter Overrides
+ // --------------------------------
+ if (state.game.cubeCount >= slowmoThreshold && slowmoSpawner.shouldSpawn()) {
+ color = BLUE;
+ wireframe = true;
+ }
+ else if (state.game.cubeCount >= strongThreshold && strongSpawner.shouldSpawn()) {
+ color = PINK;
+ health = 3;
+ }
+
+ // Target Creation
+ // --------------------------------
+ const target = getTargetOfStyle(color, wireframe);
+ target.hit = false;
+ target.maxHealth = maxHealth;
+ target.health = health;
+ updateTargetHealth(target, 0);
+
+ const spinSpeeds = [
+ Math.random() * 0.1 - 0.05,
+ Math.random() * 0.1 - 0.05
+ ];
+
+ if (spinner) {
+ // Ends up spinning a random axis
+ spinSpeeds[0] = -0.25;
+ spinSpeeds[1] = 0;
+ target.rotateZ = random(0, TAU);
+ }
+
+ const axes = pickOne(axisOptions);
+
+ spinSpeeds.forEach((spinSpeed, i) => {
+ switch (axes[i]) {
+ case 'x':
+ target.rotateXD = spinSpeed;
+ break;
+ case 'y':
+ target.rotateYD = spinSpeed;
+ break;
+ case 'z':
+ target.rotateZD = spinSpeed;
+ break;
+ }
+ });
+
+ return target;
+ }
+})();
+
+
+const updateTargetHealth = (target, healthDelta) => {
+ target.health += healthDelta;
+ // Only update stroke on non-wireframe targets.
+ // Showing "glue" is a temporary attempt to display health. For now, there's
+ // no reason to have wireframe targets with high health, so we're fine.
+ if (!target.wireframe) {
+ const strokeWidth = target.health - 1;
+ const strokeColor = makeTargetGlueColor(target);
+ for (let p of target.polys) {
+ p.strokeWidth = strokeWidth;
+ p.strokeColor = strokeColor;
+ }
+ }
+};
+
+
+const returnTarget = target => {
+ target.reset();
+ const pool = target.wireframe ? targetWireframePool : targetPool;
+ pool.get(target.color).push(target);
+};
+
+
+function resetAllTargets() {
+ while(targets.length) {
+ returnTarget(targets.pop());
+ }
+}
+
+
+
+
+
+// createBurst.js
+// ============================================================================
+// ============================================================================
+
+// Track all active fragments
+const frags = [];
+// Pool inactive fragments by color, using a Map.
+// keys are color objects, and values are arrays of fragments.
+// // Also pool wireframe instances separately.
+const fragPool = new Map(allColors.map(c=>([c, []])));
+const fragWireframePool = new Map(allColors.map(c=>([c, []])));
+
+
+const createBurst = (() => {
+ // Precompute some private data to be reused for all bursts.
+ const basePositions = mengerSpongeSplit({ x:0, y:0, z:0 }, fragRadius*2);
+ const positions = cloneVertices(basePositions);
+ const prevPositions = cloneVertices(basePositions);
+ const velocities = cloneVertices(basePositions);
+
+ const basePositionNormals = basePositions.map(normalize);
+ const positionNormals = cloneVertices(basePositionNormals);
+
+
+ const fragCount = basePositions.length;
+
+ function getFragForTarget(target) {
+ const pool = target.wireframe ? fragWireframePool : fragPool;
+ let frag = pool.get(target.color).pop();
+ if (!frag) {
+ frag = new Entity({
+ model: makeCubeModel({ scale: fragRadius }),
+ color: target.color,
+ wireframe: target.wireframe
+ });
+ frag.color = target.color;
+ frag.wireframe = target.wireframe;
+ }
+ return frag;
+ }
+
+ return (target, force=1) => {
+ // Calculate fragment positions, and what would have been the previous positions
+ // when still a part of the larger target.
+ transformVertices(
+ basePositions, positions,
+ target.x, target.y, target.z,
+ target.rotateX, target.rotateY, target.rotateZ,
+ 1, 1, 1
+ );
+ transformVertices(
+ basePositions, prevPositions,
+ target.x - target.xD, target.y - target.yD, target.z - target.zD,
+ target.rotateX - target.rotateXD, target.rotateY - target.rotateYD, target.rotateZ - target.rotateZD,
+ 1, 1, 1
+ );
+
+ // Compute velocity of each fragment, based on previous positions.
+ // Will write to `velocities` array.
+ for (let i=0; i {
+ frag.reset();
+ const pool = frag.wireframe ? fragWireframePool : fragPool;
+ pool.get(frag.color).push(frag);
+};
+
+
+
+
+
+// sparks.js
+// ============================================================================
+// ============================================================================
+
+const sparks = [];
+const sparkPool = [];
+
+
+function addSpark(x, y, xD, yD) {
+ const spark = sparkPool.pop() || {};
+
+ spark.x = x + xD * 0.5;
+ spark.y = y + yD * 0.5;
+ spark.xD = xD;
+ spark.yD = yD;
+ spark.life = random(200, 300);
+ spark.maxLife = spark.life;
+
+ sparks.push(spark);
+
+ return spark;
+}
+
+
+// Spherical spark burst
+function sparkBurst(x, y, count, maxSpeed) {
+ const angleInc = TAU / count;
+ for (let i=0; i {
+ if (Math.random() < 0.4) {
+ projectVertex(v);
+ addSpark(
+ v.x,
+ v.y,
+ random(-12, 12),
+ random(-12, 12)
+ );
+ }
+ });
+}
+
+function returnSpark(spark) {
+ sparkPool.push(spark);
+}
+
+
+
+
+
+// hud.js
+// ============================================================================
+// ============================================================================
+
+const hudContainerNode = $('.hud');
+
+function setHudVisibility(visible) {
+ if (visible) {
+ hudContainerNode.style.display = 'block';
+ } else {
+ hudContainerNode.style.display = 'none';
+ }
+}
+
+
+///////////
+// Score //
+///////////
+const scoreNode = $('.score-lbl');
+const cubeCountNode = $('.cube-count-lbl');
+
+function renderScoreHud() {
+ if (isCasualGame()) {
+ scoreNode.style.display = 'none';
+ cubeCountNode.style.opacity = 1;
+ } else {
+ scoreNode.innerText = `SCORE: ${state.game.score}`;
+ scoreNode.style.display = 'block';
+ cubeCountNode.style.opacity = 0.65 ;
+ }
+ cubeCountNode.innerText = `CUBES SMASHED: ${state.game.cubeCount}`;
+}
+
+renderScoreHud();
+
+
+//////////////////
+// Pause Button //
+//////////////////
+
+handlePointerDown($('.pause-btn'), () => pauseGame());
+
+
+////////////////////
+// Slow-Mo Status //
+////////////////////
+
+const slowmoNode = $('.slowmo');
+const slowmoBarNode = $('.slowmo__bar');
+
+function renderSlowmoStatus(percentRemaining) {
+ slowmoNode.style.opacity = percentRemaining === 0 ? 0 : 1;
+ slowmoBarNode.style.transform = `scaleX(${percentRemaining.toFixed(3)})`;
+}
+
+
+
+
+
+// menus.js
+// ============================================================================
+// ============================================================================
+
+// Top-level menu containers
+const menuContainerNode = $('.menus');
+const menuMainNode = $('.menu--main');
+const menuPauseNode = $('.menu--pause');
+const menuScoreNode = $('.menu--score');
+
+const finalScoreLblNode = $('.final-score-lbl');
+const highScoreLblNode = $('.high-score-lbl');
+
+
+
+function showMenu(node) {
+ node.classList.add('active');
+}
+
+function hideMenu(node) {
+ node.classList.remove('active');
+}
+
+function renderMenus() {
+ hideMenu(menuMainNode);
+ hideMenu(menuPauseNode);
+ hideMenu(menuScoreNode);
+
+ switch (state.menus.active) {
+ case MENU_MAIN:
+ showMenu(menuMainNode);
+ break;
+ case MENU_PAUSE:
+ showMenu(menuPauseNode);
+ break;
+ case MENU_SCORE:
+ finalScoreLblNode.textContent = formatNumber(state.game.score);
+ if (isNewHighScore()) {
+ highScoreLblNode.textContent = 'New High Score!';
+ } else {
+ highScoreLblNode.textContent = `High Score: ${formatNumber(getHighScore())}`;
+ }
+ showMenu(menuScoreNode);
+ break;
+ }
+
+ setHudVisibility(!isMenuVisible());
+ menuContainerNode.classList.toggle('has-active', isMenuVisible());
+ menuContainerNode.classList.toggle('interactive-mode', isMenuVisible() && pointerIsDown);
+}
+
+renderMenus();
+
+
+
+////////////////////
+// Button Actions //
+////////////////////
+
+// Main Menu
+handleClick($('.play-normal-btn'), () => {
+ setGameMode(GAME_MODE_RANKED);
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.play-casual-btn'), () => {
+ setGameMode(GAME_MODE_CASUAL);
+ setActiveMenu(null);
+ resetGame();
+});
+
+// Pause Menu
+handleClick($('.resume-btn'), () => resumeGame());
+handleClick($('.menu-btn--pause'), () => setActiveMenu(MENU_MAIN));
+
+// Score Menu
+handleClick($('.play-again-btn'), () => {
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.menu-btn--score'), () => setActiveMenu(MENU_MAIN));
+
+
+
+
+////////////////////
+// Button Actions //
+////////////////////
+
+// Main Menu
+handleClick($('.play-normal-btn'), () => {
+ setGameMode(GAME_MODE_RANKED);
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.play-casual-btn'), () => {
+ setGameMode(GAME_MODE_CASUAL);
+ setActiveMenu(null);
+ resetGame();
+});
+
+// Pause Menu
+handleClick($('.resume-btn'), () => resumeGame());
+handleClick($('.menu-btn--pause'), () => setActiveMenu(MENU_MAIN));
+
+// Score Menu
+handleClick($('.play-again-btn'), () => {
+ setActiveMenu(null);
+ resetGame();
+});
+
+handleClick($('.menu-btn--score'), () => setActiveMenu(MENU_MAIN));
+
+
+
+
+
+// actions.js
+// ============================================================================
+// ============================================================================
+
+//////////////////
+// MENU ACTIONS //
+//////////////////
+
+function setActiveMenu(menu) {
+ state.menus.active = menu;
+ renderMenus();
+}
+
+
+/////////////////
+// HUD ACTIONS //
+/////////////////
+
+function setScore(score) {
+ state.game.score = score;
+ renderScoreHud();
+}
+
+function incrementScore(inc) {
+ if (isInGame()) {
+ state.game.score += inc;
+ if (state.game.score < 0) {
+ state.game.score = 0;
+ }
+ renderScoreHud();
+ }
+}
+
+function setCubeCount(count) {
+ state.game.cubeCount = count;
+ renderScoreHud();
+}
+
+function incrementCubeCount(inc) {
+ if (isInGame()) {
+ state.game.cubeCount += inc;
+ renderScoreHud();
+ }
+}
+
+
+//////////////////
+// GAME ACTIONS //
+//////////////////
+
+function setGameMode(mode) {
+ state.game.mode = mode;
+}
+
+function resetGame() {
+ resetAllTargets();
+ state.game.time = 0;
+ resetAllCooldowns();
+ setScore(0);
+ setCubeCount(0);
+ spawnTime = getSpawnDelay();
+}
+
+function pauseGame() {
+ isInGame() && setActiveMenu(MENU_PAUSE);
+}
+
+function resumeGame() {
+ isPaused() && setActiveMenu(null);
+}
+
+function endGame() {
+ handleCanvasPointerUp();
+ if (isNewHighScore()) {
+ setHighScore(state.game.score);
+ }
+ setActiveMenu(MENU_SCORE);
+}
+
+
+
+////////////////////////
+// KEYBOARD SHORTCUTS //
+////////////////////////
+
+window.addEventListener('keydown', event => {
+ if (event.key === 'p') {
+ isPaused() ? resumeGame() : pauseGame();
+ }
+});
+
+
+
+
+
+
+// tick.js
+// ============================================================================
+// ============================================================================
+
+
+let spawnTime = 0;
+const maxSpawnX = 450;
+const pointerDelta = { x: 0, y: 0 };
+const pointerDeltaScaled = { x: 0, y: 0 };
+
+// Temp slowmo state. Should be relocated once this stabilizes.
+const slowmoDuration = 1500;
+let slowmoRemaining = 0;
+let spawnExtra = 0;
+const spawnExtraDelay = 300;
+let targetSpeed = 1;
+
+
+function tick(width, height, simTime, simSpeed, lag) {
+ PERF_START('frame');
+ PERF_START('tick');
+
+ state.game.time += simTime;
+
+ if (slowmoRemaining > 0) {
+ slowmoRemaining -= simTime;
+ if (slowmoRemaining < 0) {
+ slowmoRemaining = 0;
+ }
+ targetSpeed = pointerIsDown ? 0.075 : 0.3;
+ } else {
+ const menuPointerDown = isMenuVisible() && pointerIsDown;
+ targetSpeed = menuPointerDown ? 0.025 : 1;
+ }
+
+ renderSlowmoStatus(slowmoRemaining / slowmoDuration);
+
+ gameSpeed += (targetSpeed - gameSpeed) / 22 * lag;
+ gameSpeed = clamp(gameSpeed, 0, 1);
+
+ const centerX = width / 2;
+ const centerY = height / 2;
+
+ const simAirDrag = 1 - (airDrag * simSpeed);
+ const simAirDragSpark = 1 - (airDragSpark * simSpeed);
+
+ // Pointer Tracking
+ // -------------------
+
+ // Compute speed and x/y deltas.
+ // There is also a "scaled" variant taking game speed into account. This serves two purposes:
+ // - Lag won't create large spikes in speed/deltas
+ // - In slow mo, speed is increased proportionately to match "reality". Without this boost,
+ // it feels like your actions are dampened in slow mo.
+ const forceMultiplier = 1 / (simSpeed * 0.75 + 0.25);
+ pointerDelta.x = 0;
+ pointerDelta.y = 0;
+ pointerDeltaScaled.x = 0;
+ pointerDeltaScaled.y = 0;
+ const lastPointer = touchPoints[touchPoints.length - 1];
+
+ if (pointerIsDown && lastPointer && !lastPointer.touchBreak) {
+ pointerDelta.x = (pointerScene.x - lastPointer.x);
+ pointerDelta.y = (pointerScene.y - lastPointer.y);
+ pointerDeltaScaled.x = pointerDelta.x * forceMultiplier;
+ pointerDeltaScaled.y = pointerDelta.y * forceMultiplier;
+ }
+ const pointerSpeed = Math.hypot(pointerDelta.x, pointerDelta.y);
+ const pointerSpeedScaled = pointerSpeed * forceMultiplier;
+
+ // Track points for later calculations, including drawing trail.
+ touchPoints.forEach(p => p.life -= simTime);
+
+ if (pointerIsDown) {
+ touchPoints.push({
+ x: pointerScene.x,
+ y: pointerScene.y,
+ life: touchPointLife
+ });
+ }
+
+ while (touchPoints[0] && touchPoints[0].life <= 0) {
+ touchPoints.shift();
+ }
+
+
+ // Entity Manipulation
+ // --------------------
+ PERF_START('entities');
+
+ // Spawn targets
+ spawnTime -= simTime;
+ if (spawnTime <= 0) {
+ if (spawnExtra > 0) {
+ spawnExtra--;
+ spawnTime = spawnExtraDelay;
+ } else {
+ spawnTime = getSpawnDelay();
+ }
+ const target = getTarget();
+ const spawnRadius = Math.min(centerX * 0.8, maxSpawnX);
+ target.x = (Math.random() * spawnRadius * 2 - spawnRadius);
+ target.y = centerY + targetHitRadius * 2;
+ target.z = (Math.random() * targetRadius*2 - targetRadius);
+ target.xD = Math.random() * (target.x * -2 / 120);
+ target.yD = -20;
+ targets.push(target);
+ }
+
+ // Animate targets and remove when offscreen
+ const leftBound = -centerX + targetRadius;
+ const rightBound = centerX - targetRadius;
+ const ceiling = -centerY - 120;
+ const boundDamping = 0.4;
+
+ targetLoop:
+ for (let i = targets.length - 1; i >= 0; i--) {
+ const target = targets[i];
+ target.x += target.xD * simSpeed;
+ target.y += target.yD * simSpeed;
+
+ if (target.y < ceiling) {
+ target.y = ceiling;
+ target.yD = 0;
+ }
+
+ if (target.x < leftBound) {
+ target.x = leftBound;
+ target.xD *= -boundDamping;
+ } else if (target.x > rightBound) {
+ target.x = rightBound;
+ target.xD *= -boundDamping;
+ }
+
+ if (target.z < backboardZ) {
+ target.z = backboardZ;
+ target.zD *= -boundDamping;
+ }
+
+ target.yD += gravity * simSpeed;
+ target.rotateX += target.rotateXD * simSpeed;
+ target.rotateY += target.rotateYD * simSpeed;
+ target.rotateZ += target.rotateZD * simSpeed;
+ target.transform();
+ target.project();
+
+ // Remove if offscreen
+ if (target.y > centerY + targetHitRadius * 2) {
+ targets.splice(i, 1);
+ returnTarget(target);
+ if (isInGame()) {
+ if (isCasualGame()) {
+ incrementScore(-25);
+ } else {
+ endGame();
+ }
+ }
+ continue;
+ }
+
+
+ // If pointer is moving really fast, we want to hittest multiple points along the path.
+ // We can't use scaled pointer speed to determine this, since we care about actual screen
+ // distance covered.
+ const hitTestCount = Math.ceil(pointerSpeed / targetRadius * 2);
+ // Start loop at `1` and use `<=` check, so we skip 0% and end up at 100%.
+ // This omits the previous point position, and includes the most recent.
+ for (let ii=1; ii<=hitTestCount; ii++) {
+ const percent = 1 - (ii / hitTestCount);
+ const hitX = pointerScene.x - pointerDelta.x * percent;
+ const hitY = pointerScene.y - pointerDelta.y * percent;
+ const distance = Math.hypot(
+ hitX - target.projected.x,
+ hitY - target.projected.y
+ );
+
+ if (distance <= targetHitRadius) {
+ // Hit! (though we don't want to allow hits on multiple sequential frames)
+ if (!target.hit) {
+ target.hit = true;
+
+ target.xD += pointerDeltaScaled.x * hitDampening;
+ target.yD += pointerDeltaScaled.y * hitDampening;
+ target.rotateXD += pointerDeltaScaled.y * 0.001;
+ target.rotateYD += pointerDeltaScaled.x * 0.001;
+
+ const sparkSpeed = 7 + pointerSpeedScaled * 0.125;
+
+ if (pointerSpeedScaled > minPointerSpeed) {
+ target.health--;
+ incrementScore(10);
+
+ if (target.health <= 0) {
+ incrementCubeCount(1);
+ createBurst(target, forceMultiplier);
+ sparkBurst(hitX, hitY, 8, sparkSpeed);
+ if (target.wireframe) {
+ slowmoRemaining = slowmoDuration;
+ spawnTime = 0;
+ spawnExtra = 2;
+ }
+ targets.splice(i, 1);
+ returnTarget(target);
+ } else {
+ sparkBurst(hitX, hitY, 8, sparkSpeed);
+ glueShedSparks(target);
+ updateTargetHealth(target, 0);
+ }
+ } else {
+ incrementScore(5);
+ sparkBurst(hitX, hitY, 3, sparkSpeed);
+ }
+ }
+ // Break the current loop and continue the outer loop.
+ // This skips to processing the next target.
+ continue targetLoop;
+ }
+ }
+
+ // This code will only run if target hasn't been "hit".
+ target.hit = false;
+ }
+
+ // Animate fragments and remove when offscreen.
+ const fragBackboardZ = backboardZ + fragRadius;
+ // Allow fragments to move off-screen to sides for a while, since shadows are still visible.
+ const fragLeftBound = -width;
+ const fragRightBound = width;
+
+ for (let i = frags.length - 1; i >= 0; i--) {
+ const frag = frags[i];
+ frag.x += frag.xD * simSpeed;
+ frag.y += frag.yD * simSpeed;
+ frag.z += frag.zD * simSpeed;
+
+ frag.xD *= simAirDrag;
+ frag.yD *= simAirDrag;
+ frag.zD *= simAirDrag;
+
+ if (frag.y < ceiling) {
+ frag.y = ceiling;
+ frag.yD = 0;
+ }
+
+ if (frag.z < fragBackboardZ) {
+ frag.z = fragBackboardZ;
+ frag.zD *= -boundDamping;
+ }
+
+ frag.yD += gravity * simSpeed;
+ frag.rotateX += frag.rotateXD * simSpeed;
+ frag.rotateY += frag.rotateYD * simSpeed;
+ frag.rotateZ += frag.rotateZD * simSpeed;
+ frag.transform();
+ frag.project();
+
+ // Removal conditions
+ if (
+ // Bottom of screen
+ frag.projected.y > centerY + targetHitRadius ||
+ // Sides of screen
+ frag.projected.x < fragLeftBound ||
+ frag.projected.x > fragRightBound ||
+ // Too close to camera
+ frag.z > cameraFadeEndZ
+ ) {
+ frags.splice(i, 1);
+ returnFrag(frag);
+ continue;
+ }
+ }
+
+ // 2D sparks
+ for (let i = sparks.length - 1; i >= 0; i--) {
+ const spark = sparks[i];
+ spark.life -= simTime;
+ if (spark.life <= 0) {
+ sparks.splice(i, 1);
+ returnSpark(spark);
+ continue;
+ }
+ spark.x += spark.xD * simSpeed;
+ spark.y += spark.yD * simSpeed;
+ spark.xD *= simAirDragSpark;
+ spark.yD *= simAirDragSpark;
+ spark.yD += gravity * simSpeed;
+ }
+
+ PERF_END('entities');
+
+ // 3D transforms
+ // -------------------
+
+ PERF_START('3D');
+
+ // Aggregate all scene vertices/polys
+ allVertices.length = 0;
+ allPolys.length = 0;
+ allShadowVertices.length = 0;
+ allShadowPolys.length = 0;
+ targets.forEach(entity => {
+ allVertices.push(...entity.vertices);
+ allPolys.push(...entity.polys);
+ allShadowVertices.push(...entity.shadowVertices);
+ allShadowPolys.push(...entity.shadowPolys);
+ });
+
+ frags.forEach(entity => {
+ allVertices.push(...entity.vertices);
+ allPolys.push(...entity.polys);
+ allShadowVertices.push(...entity.shadowVertices);
+ allShadowPolys.push(...entity.shadowPolys);
+ });
+
+ // Scene calculations/transformations
+ allPolys.forEach(p => computePolyNormal(p, 'normalWorld'));
+ allPolys.forEach(computePolyDepth);
+ allPolys.sort((a, b) => b.depth - a.depth);
+
+ // Perspective projection
+ allVertices.forEach(projectVertex);
+
+ allPolys.forEach(p => computePolyNormal(p, 'normalCamera'));
+
+ PERF_END('3D');
+
+ PERF_START('shadows');
+
+ // Rotate shadow vertices to light source perspective
+ transformVertices(
+ allShadowVertices,
+ allShadowVertices,
+ 0, 0, 0,
+ TAU/8, 0, 0,
+ 1, 1, 1
+ );
+
+ allShadowPolys.forEach(p => computePolyNormal(p, 'normalWorld'));
+
+ const shadowDistanceMult = Math.hypot(1, 1);
+ const shadowVerticesLength = allShadowVertices.length;
+ for (let i=0; i {
+ if (p.wireframe) {
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ const { vertices } = p;
+ const vCount = vertices.length;
+ const firstV = vertices[0];
+ ctx.moveTo(firstV.x, firstV.y);
+ for (let i=1; i {
+ if (!p.wireframe && p.normalCamera.z < 0) return;
+
+ if (p.strokeWidth !== 0) {
+ ctx.lineWidth = p.normalCamera.z < 0 ? p.strokeWidth * 0.5 : p.strokeWidth;
+ ctx.strokeStyle = p.normalCamera.z < 0 ? p.strokeColorDark : p.strokeColor;
+ }
+
+ const { vertices } = p;
+ const lastV = vertices[vertices.length - 1];
+ const fadeOut = p.middle.z > cameraFadeStartZ;
+
+ if (!p.wireframe) {
+ const normalLight = p.normalWorld.y * 0.5 + p.normalWorld.z * -0.5;
+ const lightness = normalLight > 0
+ ? 0.1
+ : ((normalLight ** 32 - normalLight) / 2) * 0.9 + 0.1;
+ ctx.fillStyle = shadeColor(p.color, lightness);
+ }
+
+ // Fade out polys close to camera. `globalAlpha` must be reset later.
+ if (fadeOut) {
+ // If polygon gets really close to camera (outside `cameraFadeRange`) the alpha
+ // can go negative, which has the appearance of alpha = 1. So, we'll clamp it at 0.
+ ctx.globalAlpha = Math.max(0, 1 - (p.middle.z - cameraFadeStartZ) / cameraFadeRange);
+ }
+
+ ctx.beginPath();
+ ctx.moveTo(lastV.x, lastV.y);
+ for (let v of vertices) {
+ ctx.lineTo(v.x, v.y);
+ }
+
+ if (!p.wireframe) {
+ ctx.fill();
+ }
+ if (p.strokeWidth !== 0) {
+ ctx.stroke();
+ }
+
+ if (fadeOut) {
+ ctx.globalAlpha = 1;
+ }
+ });
+ PERF_END('drawPolys');
+
+
+ PERF_START('draw2D');
+
+ // 2D Sparks
+ // ---------------
+ ctx.strokeStyle = sparkColor;
+ ctx.lineWidth = sparkThickness;
+ ctx.beginPath();
+ sparks.forEach(spark => {
+ ctx.moveTo(spark.x, spark.y);
+ // Shrink sparks to zero length as they die.
+ // Speed up shrinking as life approaches 0 (root curve).
+ // Note that sparks already get smaller over time as their speed slows
+ // down from damping. So this is like a double scale down. To counter this
+ // a bit and keep the sparks larger for longer, we'll also increase the scale
+ // a bit after applying the root curve.
+ const scale = (spark.life / spark.maxLife) ** 0.5 * 1.5;
+ ctx.lineTo(spark.x - spark.xD*scale, spark.y - spark.yD*scale);
+
+ });
+ ctx.stroke();
+
+
+ // Touch Strokes
+ // ---------------
+
+ ctx.strokeStyle = touchTrailColor;
+ const touchPointCount = touchPoints.length;
+ for (let i=1; i 68) {
+ frameTime = 68;
+ }
+
+ const halfW = width / 2;
+ const halfH = height / 2;
+
+ // Convert pointer position from screen to scene coords.
+ pointerScene.x = pointerScreen.x / viewScale - halfW;
+ pointerScene.y = pointerScreen.y / viewScale - halfH;
+
+ const lag = frameTime / 16.6667;
+ const simTime = gameSpeed * frameTime;
+ const simSpeed = gameSpeed * lag;
+ tick(width, height, simTime, simSpeed, lag);
+
+ // Auto clear canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ // Auto scale drawing for high res displays, and incorporate `viewScale`.
+ // Also shift canvas so (0, 0) is the middle of the screen.
+ // This just works with 3D perspective projection.
+ const drawScale = dpr * viewScale;
+ ctx.scale(drawScale, drawScale);
+ ctx.translate(halfW, halfH);
+ draw(ctx, width, height, viewScale);
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ }
+ const raf = () => requestAnimationFrame(frameHandler);
+ // Start loop
+ raf();
+}
+
+
+
+
+
+// interaction.js
+// ============================================================================
+// ============================================================================
+
+// Interaction
+// -----------------------------
+
+function handleCanvasPointerDown(x, y) {
+ if (!pointerIsDown) {
+ pointerIsDown = true;
+ pointerScreen.x = x;
+ pointerScreen.y = y;
+ // On when menus are open, point down/up toggles an interactive mode.
+ // We just need to rerender the menu system for it to respond.
+ if (isMenuVisible()) renderMenus();
+ }
+}
+
+function handleCanvasPointerUp() {
+ if (pointerIsDown) {
+ pointerIsDown = false;
+ touchPoints.push({
+ touchBreak: true,
+ life: touchPointLife
+ });
+ // On when menus are open, point down/up toggles an interactive mode.
+ // We just need to rerender the menu system for it to respond.
+ if (isMenuVisible()) renderMenus();
+ }
+}
+
+function handleCanvasPointerMove(x, y) {
+ if (pointerIsDown) {
+ pointerScreen.x = x;
+ pointerScreen.y = y;
+ }
+}
+
+
+// Use pointer events if available, otherwise fallback to touch events (for iOS).
+if ('PointerEvent' in window) {
+ canvas.addEventListener('pointerdown', event => {
+ event.isPrimary && handleCanvasPointerDown(event.clientX, event.clientY);
+ });
+
+ canvas.addEventListener('pointerup', event => {
+ event.isPrimary && handleCanvasPointerUp();
+ });
+
+ canvas.addEventListener('pointermove', event => {
+ event.isPrimary && handleCanvasPointerMove(event.clientX, event.clientY);
+ });
+ // We also need to know if the mouse leaves the page. For this game, it's best if that
+ // cancels a swipe, so essentially acts as a "mouseup" event.
+ document.body.addEventListener('mouseleave', handleCanvasPointerUp);
+} else {
+ let activeTouchId = null;
+ canvas.addEventListener('touchstart', event => {
+ if (!pointerIsDown) {
+ const touch = event.changedTouches[0];
+ activeTouchId = touch.identifier;
+ handleCanvasPointerDown(touch.clientX, touch.clientY);
+
+
+ }
+ });
+ canvas.addEventListener('touchend', event => {
+ for (let touch of event.changedTouches) {
+ if (touch.identifier === activeTouchId) {
+ handleCanvasPointerUp();
+ break;
+ }
+ }
+ });
+ canvas.addEventListener('touchmove', event => {
+ for (let touch of event.changedTouches) {
+ if (touch.identifier === activeTouchId) {
+ handleCanvasPointerMove(touch.clientX, touch.clientY);
+ event.preventDefault();
+ break;
+ }
+ }
+ }, { passive: false });
+}
+
+
+
+
+
+// index.js
+// ============================================================================
+// ============================================================================
+
+setupCanvases();
\ No newline at end of file
diff --git a/Games/Block_Ninja/style.css b/Games/Block_Ninja/style.css
new file mode 100644
index 0000000000..c762c2b1d4
--- /dev/null
+++ b/Games/Block_Ninja/style.css
@@ -0,0 +1,267 @@
+body {
+ margin: 0;
+ background-color: #000;
+ background-image: radial-gradient(ellipse at top, #335476 0.0%, #31506e 11.1%, #304b67 22.2%, #2f4760 33.3%, #2d4359 44.4%, #2c3f51 55.6%, #2a3a4a 66.7%, #293643 77.8%, #28323d 88.9%, #262e36 100.0%);
+ height: 100vh;
+ overflow: hidden;
+
+ font-family: monospace;
+ font-weight: bold;
+ letter-spacing: 0.06em;
+ color: rgba(255, 255, 255, 0.75);
+}
+
+#c {
+ display: block;
+ touch-action: none;
+ transform: translateZ(0);
+}
+
+
+/*/////////////////////
+// HUD //
+/////////////////////*/
+
+
+.hud__score,
+.pause-btn {
+ position: fixed;
+ font-size: calc(14px + 2vw + 1vh);
+}
+
+.hud__score {
+ top: 0.65em;
+ left: 0.65em;
+ pointer-events: none;
+ user-select: none;
+}
+
+.cube-count-lbl {
+ font-size: 0.46em;
+}
+
+.pause-btn {
+ position: fixed;
+ top: 0;
+ right: 0;
+ padding: 0.8em 0.65em;
+}
+
+.pause-btn > div {
+ position: relative;
+ width: 0.8em;
+ height: 0.8em;
+ opacity: 0.75;
+}
+
+.pause-btn > div::before,
+.pause-btn > div::after {
+ content: '';
+ display: block;
+ width: 34%;
+ height: 100%;
+ position: absolute;
+ background-color: #fff;
+}
+
+.pause-btn > div::after {
+ right: 0;
+}
+
+.slowmo {
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.4s;
+ will-change: opacity;
+}
+
+.slowmo::before {
+ content: 'SLOW-MO';
+ display: block;
+ font-size: calc(8px + 1vw + 0.5vh);
+ margin-left: 0.5em;
+ margin-bottom: 8px;
+}
+
+.slowmo::after {
+ content: '';
+ display: block;
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ height: 1.5vh;
+ background-color: rgba(0, 0, 0, 0.25);
+ z-index: -1;
+}
+
+.slowmo__bar {
+ height: 1.5vh;
+ background-color: rgba(255, 255, 255, 0.75);
+ transform-origin: 0 0;
+}
+
+
+
+/*/////////////////////
+// MENUS //
+/////////////////////*/
+
+.menus::before {
+ content: '';
+ pointer-events: none;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: #000;
+ opacity: 0;
+ transition: opacity 0.2s;
+ transition-timing-function: ease-in;
+}
+
+.menus.has-active::before {
+ opacity: 0.08;
+ transition-duration: 0.4s;
+ transition-timing-function: ease-out;
+}
+
+.menus.interactive-mode::before {
+ opacity: 0.02;
+}
+
+
+
+/* Menu containers */
+.menu {
+ pointer-events: none;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ user-select: none;
+ text-align: center;
+ color: rgba(255, 255, 255, 0.9);
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(30px);
+ transition-property: opacity, visibility, transform;
+ transition-duration: 0.2s;
+ transition-timing-function: ease-in;
+}
+
+.menu.active {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+ transition-duration: 0.4s;
+ transition-timing-function: ease-out;
+}
+
+.menus.interactive-mode .menu.active {
+ opacity: 0.6;
+}
+
+.menus:not(.interactive-mode) .menu.active > * {
+ pointer-events: auto;
+}
+
+
+/* Common menu elements */
+
+h1 {
+ font-size: 4rem;
+ line-height: 0.95;
+ text-align: center;
+ font-weight: bold;
+ margin: 0 0.65em 1em;
+}
+
+h2 {
+ font-size: 1.2rem;
+ line-height: 1;
+ text-align: center;
+ font-weight: bold;
+ margin: -1em 0.65em 1em;
+}
+
+.final-score-lbl {
+ font-size: 5rem;
+ margin: -0.2em 0 0;
+}
+
+.high-score-lbl {
+ font-size: 1.2rem;
+ margin: 0 0 2.5em;
+}
+
+button {
+ display: block;
+ position: relative;
+ width: 200px;
+ padding: 12px 20px;
+ background: transparent;
+ border: none;
+ outline: none;
+ user-select: none;
+ font-family: monospace;
+ font-weight: bold;
+ font-size: 1.4rem;
+ color: #fff;
+ opacity: 0.75;
+ transition: opacity 0.3s;
+}
+
+button::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: rgba(255, 255, 255, 0.15);
+ transform: scale(0, 0);
+ opacity: 0;
+ transition: opacity 0.3s, transform 0.3s;
+}
+
+/* No `:focus` styles because this is a mouse/touch game! */
+button:active {
+ opacity: 1;
+}
+
+button:active::before {
+ transform: scale(1, 1);
+ opacity: 1;
+}
+
+.credits {
+ position: fixed;
+ width: 100%;
+ left: 0;
+ bottom: 20px;
+}
+
+a {
+ color: white;
+}
+
+/* Only enable hover state on large screens */
+@media (min-width: 1025px) {
+ button:hover {
+ opacity: 1;
+ }
+
+ button:hover::before {
+ transform: scale(1, 1);
+ opacity: 1;
+ }
+}
\ No newline at end of file
diff --git a/Games/Brick_Breaker/README.md b/Games/Brick_Breaker/README.md
index d815790085..9058a3e049 100644
--- a/Games/Brick_Breaker/README.md
+++ b/Games/Brick_Breaker/README.md
@@ -17,6 +17,10 @@
+## **Scores:**
+- +1 for each brick broken till number of bricks is greater than or equal to 60.
+- +2 for each brick broken till number of bricks is less than 60 and greater than or equal to 50.
+- +5 for each brick broken if number of bricks is less than 50;
## **Screenshots 📸**
diff --git a/Games/Brick_Breaker/index.html b/Games/Brick_Breaker/index.html
index 024a1d3f52..7a8bfbed97 100644
--- a/Games/Brick_Breaker/index.html
+++ b/Games/Brick_Breaker/index.html
@@ -1,25 +1,34 @@
-
-
-
-
- Brick Breaker
-
-
-
-
-
3.click on another block and try to remember on which block have you seen the same image
-
4.Your Number of moves are counted
+
1. Click on each block to turn it
+
2. Remember the position of each block image
+
3. Click on another block and try to remember on which block have you seen the same image
+
4. Your Number of moves are counted
diff --git a/Games/Memory_Game/mind.jpeg b/Games/Memory_Game/mind.jpeg
new file mode 100644
index 0000000000..479ca8962c
Binary files /dev/null and b/Games/Memory_Game/mind.jpeg differ
diff --git a/Games/Memory_Game/mind.png b/Games/Memory_Game/mind.png
deleted file mode 100644
index cdb2396d78..0000000000
Binary files a/Games/Memory_Game/mind.png and /dev/null differ
diff --git a/Games/Memory_Game/mind1.jpg b/Games/Memory_Game/mind1.jpg
deleted file mode 100644
index a5c87ab806..0000000000
Binary files a/Games/Memory_Game/mind1.jpg and /dev/null differ
diff --git a/Games/Memory_Game/script.js b/Games/Memory_Game/script.js
index 346e06c589..32c76941a6 100644
--- a/Games/Memory_Game/script.js
+++ b/Games/Memory_Game/script.js
@@ -124,8 +124,14 @@ const matrixGenerator = (cardValues, size = 4) => {
winCount += 1;
//check if winCount ==half of cardValues
if (winCount == Math.floor(cardValues.length / 2)) {
- result.innerHTML = `
You Won
-
Moves: ${movesCount}
`;
+ result.innerHTML = `
You Won!!
`;
+ result.style.fontFamily = 'Arial, sans-serif'; // Change the font family
+ result.style.fontSize = '24px'; // Change the font size
+ result.style.fontWeight = 'bold'; // Change the font weight
+ //result.style.fontStyle = 'italic'; // Change the font style
+ result.style.color = 'white'; // Change the text color
+
+ `
Moves: ${movesCount}
`;
stopGame();
}
} else {
diff --git a/Games/Memory_Game/style.css b/Games/Memory_Game/style.css
index 81687cb580..63bb5d2260 100644
--- a/Games/Memory_Game/style.css
+++ b/Games/Memory_Game/style.css
@@ -1,7 +1,6 @@
:root {
- /* --color-green--light: #00c46a; */
- /* background-image: linear-gradient(135deg, #71FA32, #009DE0) ; */
-
+ --color-green--light: #00c46a;
+ background-color: linear-gradient(135deg, #71FA32, #009DE0);
}
* {
@@ -9,18 +8,22 @@
margin: 0;
box-sizing: border-box;
font-family: "Poppins", sans-serif;
+ overflow: hidden;
}
body {
- background-image: url("./mind1.jpg");
+ background-image:url(img1.jpg); /*changing bg image*/
+ background-size: cover;
}
.wrapper {
box-sizing: content-box;
width: 26.87em;
padding: 2.5em 3em;
-
- background-color: #0d614b;
+ /* margin: 0; */
+ height: 90vh;
+ background-color: transparent;
+ /* background- */
position: absolute;
transform: translate(-50%, -50%);
left: 50%;
@@ -34,7 +37,7 @@ body {
width: 100%;
display: grid;
gap: 0.6em;
-
+
}
.stats-container {
@@ -56,7 +59,7 @@ body {
.card-before,
.card-after {
position: absolute;
- border-radius: 5px;
+ border-radius: 2px;
width: 100%;
height: 100%;
display: flex;
@@ -68,8 +71,11 @@ body {
}
.card-before {
- background-color: var(--color-green--light);
-
+ background: rgba(203, 250, 255, 0.24);
+ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
+ backdrop-filter: blur(2px);
+ -webkit-backdrop-filter: blur(2px);
+ border: 1px solid rgba(203, 250, 255, 0.26);
font-size: 2.8em;
font-weight: 600;
}
@@ -86,7 +92,7 @@ body {
.card-container.flipped .card-after {
transform: rotateY(0deg);
-}
+}
.controls-container {
position: absolute;
@@ -96,7 +102,8 @@ body {
flex-direction: column;
width: 100%;
height: 100%;
- background-image: url("./mind.png");
+ background-image: url("./mind.jpeg");
+ background-size: cover;
/* background-color: var(--color-green--light); */
top: 0;
@@ -122,7 +129,7 @@ button {
font-size: 1.3em;
font-family: cursive;
box-shadow: 0 0.6em 2em rgba(86, 66, 0, 0.2);
- background-color: rgb(162, 162, 93);
+ background-color: transparent;
}
@@ -143,18 +150,71 @@ button {
font-size: 1.8em;
margin: 0.6em 0 1em 0;
}
-.menu{
+.menu {
justify-content: center;
}
.instructions{
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
- font-size: 10px;
- background-color: aquamarine;
- border-radius: 30px;
- padding:45px;
+ font-size: 12px;
+ /* background-color: #ffffff; */
+ box-shadow: 0 0.9em 2.8em rgba(86, 66, 0, 0.2);
+ /* color:#1479EA; */
+ border: 1px solid white;
+ border-radius: 10px;
+ padding: 10px;
+ margin-bottom: 10px;
}
.instructions p{
- padding:25 25 25 25;
+ /* padding: 10px; */
+ font-size: 12px;
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
+}
+
+/*adding button animation to start game and stop game buttons*/
+button {
+ width: 10em;
+ position: relative;
+ height: 3.5em;
+ border: 4px ridge #ffffff;
+ outline: none;
+ background-color: transparent;
+ color: rgb(190, 238, 255);
+ transition: 1s;
+ border-radius: 0.3em;
+ font-size: 16px;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+button::after {
+ content: "";
+ position: absolute;
+ top: -10px;
+ left: 3%;
+ width: 95%;
+ height: 40%;
+ /* background-color: #212121; */
+ transition: tranform ease-in .5s;
+ transform-origin: center;
+}
+
+button::before {
+ content: "";
+ transform-origin: center;
+ position: absolute;
+ top: 80%;
+ left: 3%;
+ width: 95%;
+ height: 40%;
+ /* background-color: #212121; */
+ transition: transform ease-in 0.5s;
+}
+
+button:hover::before, button:hover::after {
+ transform: scale(1.1);
+}
+
+button:hover {
+ box-shadow: inset 0px 0px 25px #ffffff;
}
\ No newline at end of file
diff --git a/Games/Tic_tac_toe_responsive/README.md b/Games/Tic_tac_toe_responsive/README.md
new file mode 100644
index 0000000000..cee5495f23
--- /dev/null
+++ b/Games/Tic_tac_toe_responsive/README.md
@@ -0,0 +1,28 @@
+# **Game_Name**
+
+Tic Tac Toe
+
+
+
+## **Description 📃**
+- Tic Tac Toe is a traditional 2 player game. Where players take turn one by one to mark a cell out of 9
+
+
+## **functionalities 🎮**
+- If a player is able to mark 3 consecutive cells (horizontally/vertically/diagonally) then that player wins the game.
+- If no player is able to mark 3 consecutive cells, then the game is a draw
+
+
+
+## **How to play? 🕹️**
+- Firstly player X takes turn and clicks on a cell of their choice
+- The cell is filled with an X
+- Now player O will mark a cell and fills it by a O
+
+
+
+## **Screenshots 📸**
+
+
+[image](/Games/Tic_tac_toe_responsive/assets/images/Tic_tac_toe_responsive.png)
+
diff --git a/Games/Tic_tac_toe_responsive/assets/images/PointerBody.png b/Games/Tic_tac_toe_responsive/assets/images/PointerBody.png
new file mode 100644
index 0000000000..f1ae84d5d5
Binary files /dev/null and b/Games/Tic_tac_toe_responsive/assets/images/PointerBody.png differ
diff --git a/Games/Tic_tac_toe_responsive/assets/images/PointerHover.png b/Games/Tic_tac_toe_responsive/assets/images/PointerHover.png
new file mode 100644
index 0000000000..0f63672839
Binary files /dev/null and b/Games/Tic_tac_toe_responsive/assets/images/PointerHover.png differ
diff --git a/Games/Tic_tac_toe_responsive/assets/images/PointerText.png b/Games/Tic_tac_toe_responsive/assets/images/PointerText.png
new file mode 100644
index 0000000000..b6545bf4f0
Binary files /dev/null and b/Games/Tic_tac_toe_responsive/assets/images/PointerText.png differ
diff --git a/Games/Tic_tac_toe_responsive/assets/images/Tic_tac_toe_responsive.png b/Games/Tic_tac_toe_responsive/assets/images/Tic_tac_toe_responsive.png
new file mode 100644
index 0000000000..c002f0e872
Binary files /dev/null and b/Games/Tic_tac_toe_responsive/assets/images/Tic_tac_toe_responsive.png differ
diff --git a/Games/Tic_tac_toe_responsive/index.html b/Games/Tic_tac_toe_responsive/index.html
new file mode 100644
index 0000000000..a610682d2d
--- /dev/null
+++ b/Games/Tic_tac_toe_responsive/index.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+ Tic Tac Toe
+
+
+
+
+
+
+
+