diff --git "a/.github/ISSUE_TEMPLATE/bug-report-\360\237\220\233.md" "b/.github/ISSUE_TEMPLATE/bug-report-\360\237\220\233.md" deleted file mode 100644 index 4cf2af144..000000000 --- "a/.github/ISSUE_TEMPLATE/bug-report-\360\237\220\233.md" +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: "Bug Report \U0001F41B" -about: Reporting for any bug in the project -title: "\U0001F41E : " -labels: '' -assignees: '' - ---- - -:red_circle: **Title** : -:red_circle: **Bug** : -:red_circle: **Changes** : - - -### Screenshots 📷 - - - -************************************************************ - -✅ Details to Include When Taking the Issue: -Name : -Participant Role (Specify the Open Source Program name, e.g., GSSOC, Hacktoberfest, etc.): - -*********************************************************************** - -Happy Contributing! 🚀 - -Wishing you all the best on your open source journey. Enjoy! 😎 diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 000000000..fb9114ae8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,51 @@ +name: ​🐞 Bug +description: Report an issue to help us improve the project. +title: '[BUG] ' +labels: ["bug"] +body: + - type: textarea + attributes: + label: Description + id: description + description: A brief description of the issue or bug you are facing, also include what you tried and what didn't work. + validations: + required: false + - type: textarea + attributes: + label: Screenshots + id: screenshots + description: Please add screenshots if applicable + validations: + required: false + - type: textarea + attributes: + label: Any additional information? + id: extrainfo + description: Any additional information or Is there anything we should know about this bug? + validations: + required: false + - type: dropdown + id: browsers + attributes: + label: What browser are you seeing the problem on? + multiple: true + options: + - Firefox + - Chrome + - Safari + - Microsoft Edge + - type: checkboxes + id: no-duplicate-issues + attributes: + label: 'Checklist' + options: + - label: 'I have checked the existing issues' + required: true + + - label: 'I have read the [Contributing Guidelines](https://github.com/alo7lika/master-web-development/blob/dev/CONTRIBUTING.md)' + required: true + - label: "I'm a GSSoC'24-Extd contributor" + - label: "I'm a Hacktoberfest'24 contributor" + + - label: 'I am willing to work on this issue (optional)' + required: false diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 000000000..554caab74 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,71 @@ +name: 📝 Documentation Update +description: Improve Documentation +title: "[Doc]: " +labels: [documentation] +body: + - type: checkboxes + id: existing-issue + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the updates you want to make. + options: + - label: I have searched the existing issues + required: true + + - type: textarea + id: issue-description + attributes: + label: Issue Description + description: Please provide a clear description of the documentation update you are suggesting. + placeholder: Describe the improvement or correction you'd like to see in the documentation. + validations: + required: true + + - type: textarea + id: suggested-change + attributes: + label: Suggested Change + description: Provide details of the proposed change to the documentation. + placeholder: Explain how the documentation should be updated or corrected. + validations: + required: true + + - type: textarea + id: rationale + attributes: + label: Rationale + description: Why is this documentation update necessary or beneficial? + placeholder: Explain the importance or reasoning behind the suggested change. + validations: + required: false + + - type: dropdown + id: urgency + attributes: + label: Urgency + description: How urgently do you believe this documentation update is needed? + options: + - High + - Medium + - Low + default: 0 + validations: + required: true + + - type: checkboxes + id: terms + attributes: + label: Acknowledgements + description: Ensure you have read and agree to the project's guidelines. + options: + - label: I have read the [Contributing Guidelines](https://github.com/alo7lika/master-web-development/blob/test/CONTRIBUTING.md)* + required: true + - label: I'm a GSSOC'24-Extd contributor + - label: I'm a Hacktoberfest contributor + - label: I have starred the repository + required: true + - label: 'I am willing to work on this issue (optional)' + required: false + + + diff --git a/.htaccess b/.htaccess new file mode 100644 index 000000000..9fded1ed8 --- /dev/null +++ b/.htaccess @@ -0,0 +1 @@ +ErrorDocument 404 /404.html \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 000000000..fffc865f6 --- /dev/null +++ b/404.html @@ -0,0 +1,74 @@ + + + + + + 404 PAGE + + + + + + + + + +
+
+
+
+
+
+

404

+ + +
+ +
+

+ Look like you're lost +

+ +

The page you are looking for not available!

+ + Go to Home +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/ATS-SCORE-RECOZNIZER/templates/index.html b/ATS-SCORE-RECOZNIZER/templates/index.html index 9ece9bf2d..dff76dedd 100644 --- a/ATS-SCORE-RECOZNIZER/templates/index.html +++ b/ATS-SCORE-RECOZNIZER/templates/index.html @@ -4,6 +4,7 @@ ATS Score Recognizer + + + + + +
+
+

[Key Commands]
Load Fully Evolved Archive: [CTRL]
Speed Up: [E]
Slow Down: [D]
Toggle AI: [A]
Move Shape: [Arrow Keys]
Rotate Shape: [Up Arrow]
Drop Shape: [Down Arrow]
Save State: [Q]
Load State: [W]
Get Archive: [G]
Load Archive: [R]
Pick Shape: [I,O,T,S,Z,J,L]
+
Created By Idrees Hassan
Questions? Just ask!
idrees@idreesinc.com
+ + + + + diff --git a/Backbone Tetris Game/script.js b/Backbone Tetris Game/script.js new file mode 100644 index 000000000..36575f306 --- /dev/null +++ b/Backbone Tetris Game/script.js @@ -0,0 +1,2211 @@ +//Define 10x20 grid as the board +var grid = [ +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +[0,0,0,0,0,0,0,0,0,0], +]; + +//Block shapes +var shapes = { + I: [[0,0,0,0], [1,1,1,1], [0,0,0,0], [0,0,0,0]], + J: [[2,0,0], [2,2,2], [0,0,0]], + L: [[0,0,3], [3,3,3], [0,0,0]], + O: [[4,4], [4,4]], + S: [[0,5,5], [5,5,0], [0,0,0]], + T: [[0,6,0], [6,6,6], [0,0,0]], + Z: [[7,7,0], [0,7,7], [0,0,0]] +}; + +//Block colors +var colors = ["F92338", "C973FF", "1C76BC", "FEE356", "53D504", "36E0FF", "F8931D"]; + +//Used to help create a seeded generated random number for choosing shapes. makes results deterministic (reproducible) for debugging +var rndSeed = 1; + +//BLOCK SHAPES +//coordinates and shape parameter of current block we can update +var currentShape = {x: 0, y: 0, shape: undefined}; +//store shape of upcoming block +var upcomingShape; +//stores shapes +var bag = []; +//index for shapes in the bag +var bagIndex = 0; + +//GAME VALUES +//Game score +var score = 0; +// game speed +var speed = 500; +// boolean for changing game speed +var changeSpeed = false; +//for storing current state, we can load later +var saveState; +//stores current game state +var roundState; +//list of available game speeds +var speeds = [500,100,1,0]; +//inded in game speed array +var speedIndex = 0; +//turn ai on or off +var ai = true; +//drawing game vs updating algorithms +var draw = true; +//how many so far? +var movesTaken = 0; +//max number of moves allowed in a generation +var moveLimit = 500; +//consists of move the 7 move parameters +var moveAlgorithm = {}; +//set to highest rate move +var inspectMoveSelection = false; + + +//GENETIC ALGORITHM VALUES +//stores number of genomes, init at 50 +var populationSize = 50; +//stores genomes +var genomes = []; +//index of current genome in genomes array +var currentGenome = -1; +//generation number +var generation = 0; +//stores values for a generation +var archive = { + populationSize: 0, + currentGeneration: 0, + elites: [], + genomes: [] +}; +//rate of mutation +var mutationRate = 0.05; +//helps calculate mutation +var mutationStep = 0.2; + + +//main function, called on load +function initialize() { + //init pop size + archive.populationSize = populationSize; + //get the next available shape from the bag + nextShape(); + //applies the shape to the grid + applyShape(); + //set both save state and current state from the game + saveState = getState(); + roundState = getState(); + //create an initial population of genomes + createInitialPopulation(); + //the game loop + var loop = function(){ + //boolean for changing game speed + if (changeSpeed) { + //restart the clock + //stop time + clearInterval(interval); + //set time, like a digital watch + interval = setInterval(loop, speed); + //and don't change it + changeInterval = false; + } + if (speed === 0) { + //no need to draw on screen elements + draw = false; + //updates the game (update fitness, make a move, evaluate next move) + update(); + update(); + update(); + } else { + //draw the elements + draw = true; + } + //update regardless + update(); + if (speed === 0) { + //now draw elements + draw = true; + //now update the score + updateScore(); + } + }; + //timer interval + var interval = setInterval(loop, speed); +} +document.onLoad = initialize(); + + +//key options +window.onkeydown = function (event) { + + var characterPressed = String.fromCharCode(event.keyCode); + if (event.keyCode == 38) { + rotateShape(); + } else if (event.keyCode == 40) { + moveDown(); + } else if (event.keyCode == 37) { + moveLeft(); + } else if (event.keyCode == 39) { + moveRight(); + } else if (shapes[characterPressed.toUpperCase()] !== undefined) { + removeShape(); + currentShape.shape = shapes[characterPressed.toUpperCase()]; + applyShape(); + } else if (characterPressed.toUpperCase() == "Q") { + saveState = getState(); + } else if (characterPressed.toUpperCase() == "W") { + loadState(saveState); + } else if (characterPressed.toUpperCase() == "D") { + //slow down + speedIndex--; + if (speedIndex < 0) { + speedIndex = speeds.length - 1; + } + speed = speeds[speedIndex]; + changeSpeed = true; + } else if (characterPressed.toUpperCase() == "E") { + //speed up + speedIndex++; + if (speedIndex >= speeds.length) { + speedIndex = 0; + } + //adjust speed index + speed = speeds[speedIndex]; + changeSpeed = true; + //Turn on/off AI + } else if (characterPressed.toUpperCase() == "A") { + ai = !ai; + } else if (characterPressed.toUpperCase() == "R") { + //load saved generation values + loadArchive(prompt("Insert archive:")); + } else if (characterPressed.toUpperCase() == "G") { + if (localStorage.getItem("archive") === null) { + alert("No archive saved. Archives are saved after a generation has passed, and remain across sessions. Try again once a generation has passed"); + } else { + prompt("Archive from last generation (including from last session):", localStorage.getItem("archive")); + } + } else if (characterPressed.toUpperCase() == "F") { + //? + inspectMoveSelection = !inspectMoveSelection; + } else { + return true; + } + //outputs game state to the screen (post key press) + output(); + return false; +}; + +/** + * Creates the initial population of genomes, each with random genes. + */ + function createInitialPopulation() { + //inits the array + genomes = []; + //for a given population size + for (var i = 0; i < populationSize; i++) { + //randomly initialize the 7 values that make up a genome + //these are all weight values that are updated through evolution + var genome = { + //unique identifier for a genome + id: Math.random(), + //The weight of each row cleared by the given move. the more rows that are cleared, the more this weight increases + rowsCleared: Math.random() - 0.5, + //the absolute height of the highest column to the power of 1.5 + //added so that the algorithm can be able to detect if the blocks are stacking too high + weightedHeight: Math.random() - 0.5, + //The sum of all the column’s heights + cumulativeHeight: Math.random() - 0.5, + //the highest column minus the lowest column + relativeHeight: Math.random() - 0.5, + //the sum of all the empty cells that have a block above them (basically, cells that are unable to be filled) + holes: Math.random() * 0.5, + // the sum of absolute differences between the height of each column + //(for example, if all the shapes on the grid lie completely flat, then the roughness would equal 0). + roughness: Math.random() - 0.5, + }; + //add them to the array + genomes.push(genome); + } + evaluateNextGenome(); + } + +/** + * Evaluates the next genome in the population. If there is none, evolves the population. + */ + function evaluateNextGenome() { + //increment index in genome array + currentGenome++; + //If there is none, evolves the population. + if (currentGenome == genomes.length) { + evolve(); + } + //load current gamestate + loadState(roundState); + //reset moves taken + movesTaken = 0; + //and make the next move + makeNextMove(); + } + +/** + * Evolves the entire population and goes to the next generation. + */ + function evolve() { + + console.log("Generation " + generation + " evaluated."); + //reset current genome for new generation + currentGenome = 0; + //increment generation + generation++; + //resets the game + reset(); + //gets the current game state + roundState = getState(); + //sorts genomes in decreasing order of fitness values + genomes.sort(function(a, b) { + return b.fitness - a.fitness; + }); + //add a copy of the fittest genome to the elites list + archive.elites.push(clone(genomes[0])); + console.log("Elite's fitness: " + genomes[0].fitness); + + //remove the tail end of genomes, focus on the fittest + while(genomes.length > populationSize / 2) { + genomes.pop(); + } + //sum of the fitness for each genome + var totalFitness = 0; + for (var i = 0; i < genomes.length; i++) { + totalFitness += genomes[i].fitness; + } + + //get a random index from genome array + function getRandomGenome() { + return genomes[randomWeightedNumBetween(0, genomes.length - 1)]; + } + //create children array + var children = []; + //add the fittest genome to array + children.push(clone(genomes[0])); + //add population sized amount of children + while (children.length < populationSize) { + //crossover between two random genomes to make a child + children.push(makeChild(getRandomGenome(), getRandomGenome())); + } + //create new genome array + genomes = []; + //to store all the children in + genomes = genomes.concat(children); + //store this in our archive + archive.genomes = clone(genomes); + //and set current gen + archive.currentGeneration = clone(generation); + console.log(JSON.stringify(archive)); + //store archive, thanks JS localstorage! (short term memory) + localStorage.setItem("archive", JSON.stringify(archive)); +} + +/** + * Creates a child genome from the given parent genomes, and then attempts to mutate the child genome. + * @param {Genome} mum The first parent genome. + * @param {Genome} dad The second parent genome. + * @return {Genome} The child genome. + */ + function makeChild(mum, dad) { + //init the child given two genomes (its 7 parameters + initial fitness value) + var child = { + //unique id + id : Math.random(), + //all these params are randomly selected between the mom and dad genome + rowsCleared: randomChoice(mum.rowsCleared, dad.rowsCleared), + weightedHeight: randomChoice(mum.weightedHeight, dad.weightedHeight), + cumulativeHeight: randomChoice(mum.cumulativeHeight, dad.cumulativeHeight), + relativeHeight: randomChoice(mum.relativeHeight, dad.relativeHeight), + holes: randomChoice(mum.holes, dad.holes), + roughness: randomChoice(mum.roughness, dad.roughness), + //no fitness. yet. + fitness: -1 + }; + //mutation time! + + //we mutate each parameter using our mutationstep + if (Math.random() < mutationRate) { + child.rowsCleared = child.rowsCleared + Math.random() * mutationStep * 2 - mutationStep; + } + if (Math.random() < mutationRate) { + child.weightedHeight = child.weightedHeight + Math.random() * mutationStep * 2 - mutationStep; + } + if (Math.random() < mutationRate) { + child.cumulativeHeight = child.cumulativeHeight + Math.random() * mutationStep * 2 - mutationStep; + } + if (Math.random() < mutationRate) { + child.relativeHeight = child.relativeHeight + Math.random() * mutationStep * 2 - mutationStep; + } + if (Math.random() < mutationRate) { + child.holes = child.holes + Math.random() * mutationStep * 2 - mutationStep; + } + if (Math.random() < mutationRate) { + child.roughness = child.roughness + Math.random() * mutationStep * 2 - mutationStep; + } + return child; + } + +/** + * Returns an array of all the possible moves that could occur in the current state, rated by the parameters of the current genome. + * @return {Array} An array of all the possible moves that could occur. + */ + function getAllPossibleMoves() { + var lastState = getState(); + var possibleMoves = []; + var possibleMoveRatings = []; + var iterations = 0; + //for each possible rotation + for (var rots = 0; rots < 4; rots++) { + + var oldX = []; + //for each iteration + for (var t = -5; t <= 5; t++) { + iterations++; + loadState(lastState); + //rotate shape + for (var j = 0; j < rots; j++) { + rotateShape(); + } + //move left + if (t < 0) { + for (var l = 0; l < Math.abs(t); l++) { + moveLeft(); + } + //move right + } else if (t > 0) { + for (var r = 0; r < t; r++) { + moveRight(); + } + } + //if the shape has moved at all + if (!contains(oldX, currentShape.x)) { + //move it down + var moveDownResults = moveDown(); + while (moveDownResults.moved) { + moveDownResults = moveDown(); + } + //set the 7 parameters of a genome + var algorithm = { + rowsCleared: moveDownResults.rowsCleared, + weightedHeight: Math.pow(getHeight(), 1.5), + cumulativeHeight: getCumulativeHeight(), + relativeHeight: getRelativeHeight(), + holes: getHoles(), + roughness: getRoughness() + }; + //rate each move + var rating = 0; + rating += algorithm.rowsCleared * genomes[currentGenome].rowsCleared; + rating += algorithm.weightedHeight * genomes[currentGenome].weightedHeight; + rating += algorithm.cumulativeHeight * genomes[currentGenome].cumulativeHeight; + rating += algorithm.relativeHeight * genomes[currentGenome].relativeHeight; + rating += algorithm.holes * genomes[currentGenome].holes; + rating += algorithm.roughness * genomes[currentGenome].roughness; + //if the move loses the game, lower its rating + if (moveDownResults.lose) { + rating -= 500; + } + //push all possible moves, with their associated ratings and parameter values to an array + possibleMoves.push({rotations: rots, translation: t, rating: rating, algorithm: algorithm}); + //update the position of old X value + oldX.push(currentShape.x); + } + } + } + //get last state + loadState(lastState); + //return array of all possible moves + return possibleMoves; + } + +/** + * Returns the highest rated move in the given array of moves. + * @param {Array} moves An array of possible moves to choose from. + * @return {Move} The highest rated move from the moveset. + */ + function getHighestRatedMove(moves) { + //start these values off small + var maxRating = -10000000000000; + var maxMove = -1; + var ties = []; + //iterate through the list of moves + for (var index = 0; index < moves.length; index++) { + //if the current moves rating is higher than our maxrating + if (moves[index].rating > maxRating) { + //update our max values to include this moves values + maxRating = moves[index].rating; + maxMove = index; + //store index of this move + ties = [index]; + } else if (moves[index].rating == maxRating) { + //if it ties with the max rating + //add the index to the ties array + ties.push(index); + } + } + //eventually we'll set the highest move value to this move var + var move = moves[ties[0]]; + //and set the number of ties + move.algorithm.ties = ties.length; + return move; +} + +/** + * Makes a move, which is decided upon using the parameters in the current genome. + */ + function makeNextMove() { + //increment number of moves taken + movesTaken++; + //if its over the limit of moves + if (movesTaken > moveLimit) { + //update this genomes fitness value using the game score + genomes[currentGenome].fitness = clone(score); + //and evaluates the next genome + evaluateNextGenome(); + } else { + //time to make a move + + //we're going to re-draw, so lets store the old drawing + var oldDraw = clone(draw); + draw = false; + //get all the possible moves + var possibleMoves = getAllPossibleMoves(); + //lets store the current state since we will update it + var lastState = getState(); + //whats the next shape to play + nextShape(); + //for each possible move + for (var i = 0; i < possibleMoves.length; i++) { + //get the best move. so were checking all the possible moves, for each possible move. moveception. + var nextMove = getHighestRatedMove(getAllPossibleMoves()); + //add that rating to an array of highest rates moves + possibleMoves[i].rating += nextMove.rating; + } + //load current state + loadState(lastState); + //get the highest rated move ever + var move = getHighestRatedMove(possibleMoves); + //then rotate the shape as it says too + for (var rotations = 0; rotations < move.rotations; rotations++) { + rotateShape(); + } + //and move left as it says + if (move.translation < 0) { + for (var lefts = 0; lefts < Math.abs(move.translation); lefts++) { + moveLeft(); + } + //and right as it says + } else if (move.translation > 0) { + for (var rights = 0; rights < move.translation; rights++) { + moveRight(); + } + } + //update our move algorithm + if (inspectMoveSelection) { + moveAlgorithm = move.algorithm; + } + //and set the old drawing to the current + draw = oldDraw; + //output the state to the screen + output(); + //and update the score + updateScore(); + } + } + +/** + * Updates the game. + */ + function update() { + //if we have our AI turned on and the current genome is nonzero + //make a move + if (ai && currentGenome != -1) { + //move the shape down + var results = moveDown(); + //if that didn't do anything + if (!results.moved) { + //if we lost + if (results.lose) { + //update the fitness + genomes[currentGenome].fitness = clone(score); + //move on to the next genome + evaluateNextGenome(); + } else { + //if we didnt lose, make the next move + makeNextMove(); + } + } + } else { + //else just move down + moveDown(); + } + //output the state to the screen + output(); + //and update the score + updateScore(); + } + +/** + * Moves the current shape down if possible. + * @return {Object} The results of the movement of the piece. + */ + function moveDown() { + //array of possibilities + var result = {lose: false, moved: true, rowsCleared: 0}; + //remove the shape, because we will draw a new one + removeShape(); + //move it down the y axis + currentShape.y++; + //if it collides with the grid + if (collides(grid, currentShape)) { + //update its position + currentShape.y--; + //apply (stick) it to the grid + applyShape(); + //move on to the next shape in the bag + nextShape(); + //clear rows and get number of rows cleared + result.rowsCleared = clearRows(); + //check again if this shape collides with our grid + if (collides(grid, currentShape)) { + //reset + result.lose = true; + if (ai) { + } else { + reset(); + } + } + result.moved = false; + } + //apply shape, update the score and output the state to the screen + applyShape(); + score++; + updateScore(); + output(); + return result; + } + +/** + * Moves the current shape to the left if possible. + */ + function moveLeft() { + //remove current shape, slide it over, if it collides though, slide it back + removeShape(); + currentShape.x--; + if (collides(grid, currentShape)) { + currentShape.x++; + } + //apply the new shape + applyShape(); + } + +/** + * Moves the current shape to the right if possible. + */ + //same deal + function moveRight() { + removeShape(); + currentShape.x++; + if (collides(grid, currentShape)) { + currentShape.x--; + } + applyShape(); + } + +/** + * Rotates the current shape clockwise if possible. + */ + //slide it if we can, else return to original rotation + function rotateShape() { + removeShape(); + currentShape.shape = rotate(currentShape.shape, 1); + if (collides(grid, currentShape)) { + currentShape.shape = rotate(currentShape.shape, 3); + } + applyShape(); + } + +/** + * Clears any rows that are completely filled. + */ + function clearRows() { + //empty array for rows to clear + var rowsToClear = []; + //for each row in the grid + for (var row = 0; row < grid.length; row++) { + var containsEmptySpace = false; + //for each column + for (var col = 0; col < grid[row].length; col++) { + //if its empty + if (grid[row][col] === 0) { + //set this value to true + containsEmptySpace = true; + } + } + //if none of the columns in the row were empty + if (!containsEmptySpace) { + //add the row to our list, it's completely filled! + rowsToClear.push(row); + } + } + //increase score for up to 4 rows. it maxes out at 12000 + if (rowsToClear.length == 1) { + score += 400; + } else if (rowsToClear.length == 2) { + score += 1000; + } else if (rowsToClear.length == 3) { + score += 3000; + } else if (rowsToClear.length >= 4) { + score += 12000; + } + //new array for cleared rows + var rowsCleared = clone(rowsToClear.length); + //for each value + for (var toClear = rowsToClear.length - 1; toClear >= 0; toClear--) { + //remove the row from the grid + grid.splice(rowsToClear[toClear], 1); + } + //shift the other rows + while (grid.length < 20) { + grid.unshift([0,0,0,0,0,0,0,0,0,0]); + } + //return the rows cleared + return rowsCleared; + } + +/** + * Applies the current shape to the grid. + */ + function applyShape() { + //for each value in the current shape (row x column) + for (var row = 0; row < currentShape.shape.length; row++) { + for (var col = 0; col < currentShape.shape[row].length; col++) { + //if its non-empty + if (currentShape.shape[row][col] !== 0) { + //set the value in the grid to its value. Stick the shape in the grid! + grid[currentShape.y + row][currentShape.x + col] = currentShape.shape[row][col]; + } + } + } + } + +/** + * Removes the current shape from the grid. + */ + //same deal but reverse + function removeShape() { + for (var row = 0; row < currentShape.shape.length; row++) { + for (var col = 0; col < currentShape.shape[row].length; col++) { + if (currentShape.shape[row][col] !== 0) { + grid[currentShape.y + row][currentShape.x + col] = 0; + } + } + } + } + +/** + * Cycles to the next shape in the bag. + */ + function nextShape() { + //increment the bag index + bagIndex += 1; + //if we're at the start or end of the bag + if (bag.length === 0 || bagIndex == bag.length) { + //generate a new bag of genomes + generateBag(); + } + //if almost at end of bag + if (bagIndex == bag.length - 1) { + //store previous seed + var prevSeed = rndSeed; + //generate upcoming shape + upcomingShape = randomProperty(shapes); + //set random seed + rndSeed = prevSeed; + } else { + //get the next shape from our bag + upcomingShape = shapes[bag[bagIndex + 1]]; + } + //get our current shape from the bag + currentShape.shape = shapes[bag[bagIndex]]; + //define its position + currentShape.x = Math.floor(grid[0].length / 2) - Math.ceil(currentShape.shape[0].length / 2); + currentShape.y = 0; + } + +/** + * Generates the bag of shapes. + */ + function generateBag() { + bag = []; + var contents = ""; + //7 shapes + for (var i = 0; i < 7; i++) { + //generate shape randomly + var shape = randomKey(shapes); + while(contents.indexOf(shape) != -1) { + shape = randomKey(shapes); + } + //update bag with generated shape + bag[i] = shape; + contents += shape; + } + //reset bag index + bagIndex = 0; + } + +/** + * Resets the game. + */ + function reset() { + score = 0; + grid = [[0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0,0], + ]; + moves = 0; + generateBag(); + nextShape(); + } + +/** + * Determines if the given grid and shape collide with one another. + * @param {Grid} scene The grid to check. + * @param {Shape} object The shape to check. + * @return {Boolean} Whether the shape and grid collide. + */ + function collides(scene, object) { + //for the size of the shape (row x column) + for (var row = 0; row < object.shape.length; row++) { + for (var col = 0; col < object.shape[row].length; col++) { + //if its not empty + if (object.shape[row][col] !== 0) { + //if it collides, return true + if (scene[object.y + row] === undefined || scene[object.y + row][object.x + col] === undefined || scene[object.y + row][object.x + col] !== 0) { + return true; + } + } + } + } + return false; + } + +//for rotating a shape, how many times should we rotate + function rotate(matrix, times) { + //for each time + for (var t = 0; t < times; t++) { + //flip the shape matrix + matrix = transpose(matrix); + //and for the length of the matrix, reverse each column + for (var i = 0; i < matrix.length; i++) { + matrix[i].reverse(); + } + } + return matrix; + } +//flip row x column to column x row + function transpose(array) { + return array[0].map(function(col, i) { + return array.map(function(row) { + return row[i]; + }); + }); + } + +/** + * Outputs the state to the screen. + */ + function output() { + if (draw) { + var output = document.getElementById("output"); + var html = "

TetNet

Evolutionary approach to Tetris AI
var grid = ["; + var space = "            "; + for (var i = 0; i < grid.length; i++) { + if (i === 0) { + html += "[" + grid[i] + "]"; + } else { + html += "
" + space + "[" + grid[i] + "]"; + } + } + html += "];"; + for (var c = 0; c < colors.length; c++) { + html = replaceAll(html, "," + (c + 1), "," + (c + 1) + ""); + html = replaceAll(html, (c + 1) + ",", "" + (c + 1) + ","); + } + output.innerHTML = html; + } + } + +/** + * Updates the side information. + */ + function updateScore() { + if (draw) { + var scoreDetails = document.getElementById("score"); + var html = "

 

Score: " + score + "

"; + html += "
--Upcoming--"; + for (var i = 0; i < upcomingShape.length; i++) { + var next =replaceAll((upcomingShape[i] + ""), "0", " "); + html += "
    " + next; + } + for (var l = 0; l < 4 - upcomingShape.length; l++) { + html += "
"; + } + for (var c = 0; c < colors.length; c++) { + html = replaceAll(html, "," + (c + 1), "," + (c + 1) + ""); + html = replaceAll(html, (c + 1) + ",", "" + (c + 1) + ","); + } + html += "
Speed: " + speed; + if (ai) { + html += "
Moves: " + movesTaken + "/" + moveLimit; + html += "
Generation: " + generation; + html += "
Individual: " + (currentGenome + 1) + "/" + populationSize; + html += "
" + JSON.stringify(genomes[currentGenome], null, 2) + "
"; + if (inspectMoveSelection) { + html += "
" + JSON.stringify(moveAlgorithm, null, 2) + "
"; + } + } + html = replaceAll(replaceAll(replaceAll(html, " ,", "  "), ", ", "  "), ",", " "); + scoreDetails.innerHTML = html; + } + } + +/** + * Returns the current game state in an object. + * @return {State} The current game state. + */ + function getState() { + var state = { + grid: clone(grid), + currentShape: clone(currentShape), + upcomingShape: clone(upcomingShape), + bag: clone(bag), + bagIndex: clone(bagIndex), + rndSeed: clone(rndSeed), + score: clone(score) + }; + return state; + } + +/** + * Loads the game state from the given state object. + * @param {State} state The state to load. + */ + function loadState(state) { + grid = clone(state.grid); + currentShape = clone(state.currentShape); + upcomingShape = clone(state.upcomingShape); + bag = clone(state.bag); + bagIndex = clone(state.bagIndex); + rndSeed = clone(state.rndSeed); + score = clone(state.score); + output(); + updateScore(); + } + +/** + * Returns the cumulative height of all the columns. + * @return {Number} The cumulative height. + */ + function getCumulativeHeight() { + removeShape(); + var peaks = [20,20,20,20,20,20,20,20,20,20]; + for (var row = 0; row < grid.length; row++) { + for (var col = 0; col < grid[row].length; col++) { + if (grid[row][col] !== 0 && peaks[col] === 20) { + peaks[col] = row; + } + } + } + var totalHeight = 0; + for (var i = 0; i < peaks.length; i++) { + totalHeight += 20 - peaks[i]; + } + applyShape(); + return totalHeight; + } + +/** + * Returns the number of holes in the grid. + * @return {Number} The number of holes. + */ + function getHoles() { + removeShape(); + var peaks = [20,20,20,20,20,20,20,20,20,20]; + for (var row = 0; row < grid.length; row++) { + for (var col = 0; col < grid[row].length; col++) { + if (grid[row][col] !== 0 && peaks[col] === 20) { + peaks[col] = row; + } + } + } + var holes = 0; + for (var x = 0; x < peaks.length; x++) { + for (var y = peaks[x]; y < grid.length; y++) { + if (grid[y][x] === 0) { + holes++; + } + } + } + applyShape(); + return holes; + } + +/** + * Returns an array that replaces all the holes in the grid with -1. + * @return {Array} The modified grid array. + */ + function getHolesArray() { + var array = clone(grid); + removeShape(); + var peaks = [20,20,20,20,20,20,20,20,20,20]; + for (var row = 0; row < grid.length; row++) { + for (var col = 0; col < grid[row].length; col++) { + if (grid[row][col] !== 0 && peaks[col] === 20) { + peaks[col] = row; + } + } + } + for (var x = 0; x < peaks.length; x++) { + for (var y = peaks[x]; y < grid.length; y++) { + if (grid[y][x] === 0) { + array[y][x] = -1; + } + } + } + applyShape(); + return array; + } + +/** + * Returns the roughness of the grid. + * @return {Number} The roughness of the grid. + */ + function getRoughness() { + removeShape(); + var peaks = [20,20,20,20,20,20,20,20,20,20]; + for (var row = 0; row < grid.length; row++) { + for (var col = 0; col < grid[row].length; col++) { + if (grid[row][col] !== 0 && peaks[col] === 20) { + peaks[col] = row; + } + } + } + var roughness = 0; + var differences = []; + for (var i = 0; i < peaks.length - 1; i++) { + roughness += Math.abs(peaks[i] - peaks[i + 1]); + differences[i] = Math.abs(peaks[i] - peaks[i + 1]); + } + applyShape(); + return roughness; + } + +/** + * Returns the range of heights of the columns on the grid. + * @return {Number} The relative height. + */ + function getRelativeHeight() { + removeShape(); + var peaks = [20,20,20,20,20,20,20,20,20,20]; + for (var row = 0; row < grid.length; row++) { + for (var col = 0; col < grid[row].length; col++) { + if (grid[row][col] !== 0 && peaks[col] === 20) { + peaks[col] = row; + } + } + } + applyShape(); + return Math.max.apply(Math, peaks) - Math.min.apply(Math, peaks); + } + +/** + * Returns the height of the biggest column on the grid. + * @return {Number} The absolute height. + */ + function getHeight() { + removeShape(); + var peaks = [20,20,20,20,20,20,20,20,20,20]; + for (var row = 0; row < grid.length; row++) { + for (var col = 0; col < grid[row].length; col++) { + if (grid[row][col] !== 0 && peaks[col] === 20) { + peaks[col] = row; + } + } + } + applyShape(); + return 20 - Math.min.apply(Math, peaks); + } + +/** + * Loads the archive given. + * @param {String} archiveString The stringified archive. + */ + function loadArchive(archiveString) { + archive = JSON.parse(archiveString); + genomes = clone(archive.genomes); + populationSize = archive.populationSize; + generation = archive.currentGeneration; + currentGenome = 0; + reset(); + roundState = getState(); + console.log("Archive loaded!"); + } + +/** + * Clones an object. + * @param {Object} obj The object to clone. + * @return {Object} The cloned object. + */ + function clone(obj) { + return JSON.parse(JSON.stringify(obj)); + } + +/** + * Returns a random property from the given object. + * @param {Object} obj The object to select a property from. + * @return {Property} A random property. + */ + function randomProperty(obj) { + return(obj[randomKey(obj)]); + } + +/** + * Returns a random property key from the given object. + * @param {Object} obj The object to select a property key from. + * @return {Property} A random property key. + */ + function randomKey(obj) { + var keys = Object.keys(obj); + var i = seededRandom(0, keys.length); + return keys[i]; + } + + function replaceAll(target, search, replacement) { + return target.replace(new RegExp(search, 'g'), replacement); + } + +/** + * Returns a random number that is determined from a seeded random number generator. + * @param {Number} min The minimum number, inclusive. + * @param {Number} max The maximum number, exclusive. + * @return {Number} The generated random number. + */ + function seededRandom(min, max) { + max = max || 1; + min = min || 0; + + rndSeed = (rndSeed * 9301 + 49297) % 233280; + var rnd = rndSeed / 233280; + + return Math.floor(min + rnd * (max - min)); + } + + function randomNumBetween(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); + } + + function randomWeightedNumBetween(min, max) { + return Math.floor(Math.pow(Math.random(), 2) * (max - min + 1) + min); + } + + function randomChoice(propOne, propTwo) { + if (Math.round(Math.random()) === 0) { + return clone(propOne); + } else { + return clone(propTwo); + } + } + + function contains(a, obj) { + var i = a.length; + while (i--) { + if (a[i] === obj) { + return true; + } + } + return false; + } + +/** + * A node, representing a biological neuron. + * @param {Number} ID The ID of the node. + * @param {Number} val The value of the node. + */ + function Node(ID, val) { + this.id = ID; + this.incomingConnections = []; + this.outgoingConnections = []; + if (val === undefined) { + val = 0; + } + this.value = val; + this.bias = 0; + } + +/** + * A connection, representing a biological synapse. + * @param {String} inID The ID of the incoming node. + * @param {String} outID The ID of the outgoing node. + * @param {Number} weight The weight of the connection. + */ + function Connection(inID, outID, weight) { + this.in = inID; + this.out = outID; + if (weight === undefined) { + weight = 1; + } + this.id = inID + ":" + outID; + this.weight = weight; + } + +/** + * The neural network, containing nodes and connections. + * @param {Object} config The configuration to use. + */ + function Network(config) { + this.nodes = {}; + this.inputs = []; + this.hidden = []; + this.outputs = []; + this.connections = {}; + this.nodes.BIAS = new Node("BIAS", 1); + + if (config !== undefined) { + var inputNum = config.inputNodes || 0; + var hiddenNum = config.hiddenNodes || 0; + var outputNum = config.outputNodes || 0; + this.createNodes(inputNum, hiddenNum, outputNum); + + if (config.createAllConnections) { + this.createAllConnections(true); + } + } + } + +/** + * Populates the network with the given number of nodes of each type. + * @param {Number} inputNum The number of input nodes to create. + * @param {Number} hiddenNum The number of hidden nodes to create. + * @param {Number} outputNum The number of output nodes to create. + */ + Network.prototype.createNodes = function(inputNum, hiddenNum, outputNum) { + for (var i = 0; i < inputNum; i++) { + this.addInput(); + } + for (var j = 0; j < hiddenNum; j++) { + this.addHidden(); + } + for (var k = 0; k < outputNum; k++) { + this.addOutput(); + } + }; + +/** + * @param {Number} [value] The value to set the node to [Default is 0]. + */ + Network.prototype.addInput = function(value) { + var nodeID = "INPUT:" + this.inputs.length; + if (value === undefined) { + value = 0; + } + this.nodes[nodeID] = new Node(nodeID, value); + this.inputs.push(nodeID); + }; + +/** + * Creates a hidden node. + */ + Network.prototype.addHidden = function() { + var nodeID = "HIDDEN:" + this.hidden.length; + this.nodes[nodeID] = new Node(nodeID); + this.hidden.push(nodeID); + }; + +/** + * Creates an output node. + */ + Network.prototype.addOutput = function() { + var nodeID = "OUTPUT:" + this.outputs.length; + this.nodes[nodeID] = new Node(nodeID); + this.outputs.push(nodeID); + }; + +/** + * Returns the node with the given ID. + * @param {String} nodeID The ID of the node to return. + * @return {Node} The node with the given ID. + */ + Network.prototype.getNodeByID = function(nodeID) { + return this.nodes[nodeID]; + }; + +/** + * Returns the node of the given type at the given index. + * @param {String} type The type of node requested [Accepted arguments: "INPUT", "HIDDEN", "OUTPUT"]. + * @param {Number} index The index of the node (from the array containing nodes of the requested type). + * @return {Node} The node requested. Will return null if no node is found. + */ + Network.prototype.getNode = function(type, index) { + if (type.toUpperCase() == "INPUT") { + return this.nodes[this.inputs[index]]; + } else if (type.toUpperCase() == "HIDDEN") { + return this.nodes[this.hidden[index]]; + } else if (type.toUpperCase() == "OUTPUT") { + return this.nodes[this.outputs[index]]; + } + return null; + }; + +/** + * Returns the connection with the given ID. + * @param {String} connectionID The ID of the connection to return. + * @return {Connection} The connection with the given ID. + */ + Network.prototype.getConnection = function(connectionID) { + return this.connections[connectionID]; + }; + +/** + * Calculates the values of the nodes in the neural network. + */ + Network.prototype.calculate = function calculate() { + this.updateNodeConnections(); + for (var i = 0; i < this.hidden.length; i++) { + this.calculateNodeValue(this.hidden[i]); + } + for (var j = 0; j < this.outputs.length; j++) { + this.calculateNodeValue(this.outputs[j]); + } + }; + +/** + * Updates the node's to reference the current connections. + */ + Network.prototype.updateNodeConnections = function() { + for (var nodeKey in this.nodes) { + this.nodes[nodeKey].incomingConnections = []; + this.nodes[nodeKey].outgoingConnections = []; + } + for (var connectionKey in this.connections) { + this.nodes[this.connections[connectionKey].in].outgoingConnections.push(connectionKey); + this.nodes[this.connections[connectionKey].out].incomingConnections.push(connectionKey); + } + }; + +/** + * Calculates and updates the value of the node with the given ID. Node values are computed using a sigmoid function. + * @param {String} nodeId The ID of the node to update. + */ + Network.prototype.calculateNodeValue = function(nodeID) { + var sum = 0; + for (var incomingIndex = 0; incomingIndex < this.nodes[nodeID].incomingConnections.length; incomingIndex++) { + var connection = this.connections[this.nodes[nodeID].incomingConnections[incomingIndex]]; + sum += this.nodes[connection.in].value * connection.weight; + } + this.nodes[nodeID].value = sigmoid(sum); + }; + +/** + * Creates a connection with the given values. + * @param {String} inID The ID of the node that the connection comes from. + * @param {String} outID The ID of the node that the connection enters. + * @param {Number} [weight] The weight of the connection [Default is 1]. + */ + Network.prototype.addConnection = function(inID, outID, weight) { + if (weight === undefined) { + weight = 1; + } + this.connections[inID + ":" + outID] = new Connection(inID, outID, weight); + }; + + /** + * Creates all possible connections between nodes, not including connections to the bias node. + * @param {Boolean} randomWeights Whether to choose a random weight between -1 and 1, or to default to 1. + */ + Network.prototype.createAllConnections = function(randomWeights) { + if (randomWeights === undefined) { + randomWeights = false; + } + var weight = 1; + for (var i = 0; i < this.inputs.length; i++) { + for (var j = 0; j < this.hidden.length; j++) { + if (randomWeights) { + weight = Math.random() * 4 - 2; + } + this.addConnection(this.inputs[i], this.hidden[j], weight); + } + if (randomWeights) { + weight = Math.random() * 4 - 2; + } + this.addConnection("BIAS", this.inputs[i], weight); + } + for (var k = 0; k < this.hidden.length; k++) { + for (var l = 0; l < this.outputs.length; l++) { + if (randomWeights) { + weight = Math.random() * 4 - 2; + } + this.addConnection(this.hidden[k], this.outputs[l], weight); + } + if (randomWeights) { + weight = Math.random() * 4 - 2; + } + this.addConnection("BIAS", this.hidden[k], weight); + } + }; + +/** + * Sets the value of the node with the given ID to the given value. + * @param {String} nodeID The ID of the node to modify. + * @param {Number} value The value to set the node to. + */ + Network.prototype.setNodeValue = function(nodeID, value) { + this.nodes[nodeID].value = value; + }; + +/** + * Sets the values of the input neurons to the given values. + * @param {Array} array An array of values to set the input node values to. + */ + Network.prototype.setInputs = function(array) { + for (var i = 0; i < array.length; i++) { + this.nodes[this.inputs[i]].value = array[i]; + } + }; + +/** + * Sets the value of multiple nodes, given an object with node ID's as parameters and node values as values. + * @param {Object} valuesByID The values to set the nodes to. + */ + Network.prototype.setMultipleNodeValues = function(valuesByID) { + for (var key in valuesByID) { + this.nodes[key].value = valuesByID[key]; + } + }; + + +/** + * A visualization of the neural network, showing all connections and nodes. + * @param {Object} config The configuration to use. + */ + function NetworkVisualizer(config) { + this.canvas = "NetworkVisualizer"; + this.backgroundColor = "#FFFFFF"; + this.nodeRadius = -1; + this.nodeColor = "grey"; + this.positiveConnectionColor = "green"; + this.negativeConnectionColor = "red"; + this.connectionStrokeModifier = 1; + if (config !== undefined) { + if (config.canvas !== undefined) { + this.canvas = config.canvas; + } + if (config.backgroundColor !== undefined) { + this.backgroundColor = config.backgroundColor; + } + if (config.nodeRadius !== undefined) { + this.nodeRadius = config.nodeRadius; + } + if (config.nodeColor !== undefined) { + this.nodeColor = config.nodeColor; + } + if (config.positiveConnectionColor !== undefined) { + this.positiveConnectionColor = config.positiveConnectionColor; + } + if (config.negativeConnectionColor !== undefined) { + this.negativeConnectionColor = config.negativeConnectionColor; + } + if (config.connectionStrokeModifier !== undefined) { + this.connectionStrokeModifier = config.connectionStrokeModifier; + } + } + } + +/** + * Draws the visualized network upon the canvas. + * @param {Network} network The network to visualize. + */ + NetworkVisualizer.prototype.drawNetwork = function(network) { + var canv = document.getElementById(this.canvas); + var ctx = canv.getContext("2d"); + var radius; + ctx.fillStyle = this.backgroundColor; + ctx.fillRect(0, 0, canv.width, canv.height); + if (this.nodeRadius != -1) { + radius = this.nodeRadius; + } else { + radius = Math.min(canv.width, canv.height) / (Math.max(network.inputs.length, network.hidden.length, network.outputs.length, 3)) / 2.5; + } + var nodeLocations = {}; + var inputX = canv.width / 5; + for (var inputIndex = 0; inputIndex < network.inputs.length; inputIndex++) { + nodeLocations[network.inputs[inputIndex]] = {x: inputX, y: canv.height / (network.inputs.length) * (inputIndex + 0.5)}; + } + var hiddenX = canv.width / 2; + for (var hiddenIndex = 0; hiddenIndex < network.hidden.length; hiddenIndex++) { + nodeLocations[network.hidden[hiddenIndex]] = {x: hiddenX, y: canv.height / (network.hidden.length) * (hiddenIndex + 0.5)}; + } + var outputX = canv.width / 5 * 4; + for (var outputIndex = 0; outputIndex < network.outputs.length; outputIndex++) { + nodeLocations[network.outputs[outputIndex]] = {x: outputX, y: canv.height / (network.outputs.length) * (outputIndex + 0.5)}; + } + nodeLocations.BIAS = {x: canv.width / 3, y: radius / 2}; + for (var connectionKey in network.connections) { + var connection = network.connections[connectionKey]; + //if (connection.in != "BIAS" && connection.out != "BIAS") { + ctx.beginPath(); + ctx.moveTo(nodeLocations[connection.in].x, nodeLocations[connection.in].y); + ctx.lineTo(nodeLocations[connection.out].x, nodeLocations[connection.out].y); + if (connection.weight > 0) { + ctx.strokeStyle = this.positiveConnectionColor; + } else { + ctx.strokeStyle = this.negativeConnectionColor; + } + ctx.lineWidth = connection.weight * this.connectionStrokeModifier; + ctx.lineCap = "round"; + ctx.stroke(); + //} + } + for (var nodeKey in nodeLocations) { + var node = network.getNodeByID(nodeKey); + ctx.beginPath(); + if (nodeKey == "BIAS") { + ctx.arc(nodeLocations[nodeKey].x, nodeLocations[nodeKey].y, radius / 2.2, 0, 2 * Math.PI); + } else { + ctx.arc(nodeLocations[nodeKey].x, nodeLocations[nodeKey].y, radius, 0, 2 * Math.PI); + } + ctx.fillStyle = this.backgroundColor; + ctx.fill(); + ctx.strokeStyle = this.nodeColor; + ctx.lineWidth = 3; + ctx.stroke(); + ctx.globalAlpha = node.value; + ctx.fillStyle = this.nodeColor; + ctx.fill(); + ctx.globalAlpha = 1; + } + }; + + + BackpropNetwork.prototype = new Network(); + BackpropNetwork.prototype.constructor = BackpropNetwork; + +/** + * Neural network that is optimized via backpropagation. + * @param {Object} config The configuration to use. + */ + function BackpropNetwork(config) { + Network.call(this, config); + this.inputData = {}; + this.targetData = {}; + this.learningRate = 0.5; + this.step = 0; + this.totalErrorSum = 0; + this.averageError = []; + + if (config !== undefined) { + if (config.learningRate !== undefined) { + this.learningRate = config.learningRate; + } + if (config.inputData !== undefined) { + this.setInputData(config.inputData); + } + if (config.targetData !== undefined) { + this.setTargetData(config.targetData); + } + } + } + +/** + * Backpropagates the neural network, using the input and training data given. Currently does not affect connections to the bias node. + */ + BackpropNetwork.prototype.backpropagate = function() { + this.step++; + if (this.inputData[this.step] === undefined) { + this.averageError.push(this.totalErrorSum / this.step); + this.totalErrorSum = 0; + this.step = 0; + } + for (var inputKey in this.inputData[this.step]) { + this.nodes[inputKey].value = this.inputData[this.step][inputKey]; + } + this.calculate(); + var currentTargetData = this.targetData[this.step]; + var totalError = this.getTotalError(); + this.totalErrorSum += totalError; + var newWeights = {}; + for (var i = 0; i < this.outputs.length; i++) { + var outputNode = this.nodes[this.outputs[i]]; + for (var j = 0; j < outputNode.incomingConnections.length; j++) { + var hiddenToOutput = this.connections[outputNode.incomingConnections[j]]; + var deltaRuleResult = -(currentTargetData[this.outputs[i]] - outputNode.value) * outputNode.value * (1 - outputNode.value) * this.nodes[hiddenToOutput.in].value; + newWeights[hiddenToOutput.id] = hiddenToOutput.weight - this.learningRate * deltaRuleResult; + } + } + for (var k = 0; k < this.hidden.length; k++) { + var hiddenNode = this.nodes[this.hidden[k]]; + for (var l = 0; l < hiddenNode.incomingConnections.length; l++) { + var inputToHidden = this.connections[hiddenNode.incomingConnections[l]]; + var total = 0; + for (var m = 0; m < hiddenNode.outgoingConnections.length; m++) { + var outgoing = this.connections[hiddenNode.outgoingConnections[m]]; + var outgoingNode = this.nodes[outgoing.out]; + total += ((-(currentTargetData[outgoing.out] - outgoingNode.value)) * (outgoingNode.value * (1 - outgoingNode.value))) * outgoing.weight; + } + var outOverNet = hiddenNode.value * (1 - hiddenNode.value); + var netOverWeight = this.nodes[inputToHidden.in].value; + var result = total * outOverNet * netOverWeight; + newWeights[inputToHidden.id] = inputToHidden.weight - this.learningRate * result; + } + } + for (var key in newWeights) { + this.connections[key].weight = newWeights[key]; + } + }; + +/** + * Adds a target result to the target data. This will be compared to the output in order to determine error. + * @param {String} outputNodeID The ID of the output node whose value will be compared to the target. + * @param {Number} target The value to compare against the output when checking for errors. + */ + BackpropNetwork.prototype.addTarget = function(outputNodeID, target) { + this.targetData[outputNodeID] = target; + }; + +/** + * Sets the input data that will be compared to the target data. + * @param {Array} array An array containing the data to be inputted (ex. [0, 1] will set the first input node + * to have a value of 0 and the second to have a value of 1). Each array argument represents a single + * step, and will be compared against the parallel set in the target data. + */ + BackpropNetwork.prototype.setInputData = function() { + var all = arguments; + if (arguments.length == 1 && arguments[0].constructor == Array) { + all = arguments[0]; + } + this.inputData = {}; + for (var i = 0; i < all.length; i++) { + var data = all[i]; + var instance = {}; + for (var j = 0; j < data.length; j++) { + instance["INPUT:" + j] = data[j]; + } + this.inputData[i] = instance; + } + }; + +/** + * Sets the target data that will be used to check for total error. + * @param {Array} array An array containing the data to be compared against (ex. [0, 1] will compare the first + * output node against 0 and the second against 1). Each array argument represents a single step. + */ + BackpropNetwork.prototype.setTargetData = function() { + var all = arguments; + if (arguments.length == 1 && arguments[0].constructor == Array) { + all = arguments[0]; + } + this.targetData = {}; + for (var i = 0; i < all.length; i++) { + var data = all[i]; + var instance = {}; + for (var j = 0; j < data.length; j++) { + instance["OUTPUT:" + j] = data[j]; + } + this.targetData[i] = instance; + } + }; + +/** + * Calculates the total error of all the outputs' values compared to the target data. + * @return {Number} The total error. + */ + BackpropNetwork.prototype.getTotalError = function() { + var sum = 0; + for (var i = 0; i < this.outputs.length; i++) { + sum += Math.pow(this.targetData[this.step][this.outputs[i]] - this.nodes[this.outputs[i]].value, 2) / 2; + } + return sum; + }; + +/** + * A gene containing the data for a single connection in the neural network. + * @param {String} inID The ID of the incoming node. + * @param {String} outID The ID of the outgoing node. + * @param {Number} weight The weight of the connection to create. + * @param {Number} innovation The innovation number of the gene. + * @param {Boolean} enabled Whether the gene is expressed or not. + */ + function Gene(inID, outID, weight, innovation, enabled) { + if (innovation === undefined) { + innovation = 0; + } + this.innovation = innovation; + this.in = inID; + this.out = outID; + if (weight === undefined) { + weight = 1; + } + this.weight = weight; + if (enabled === undefined) { + enabled = true; + } + this.enabled = enabled; + } + +/** + * Returns the connection that the gene represents. + * @return {Connection} The generated connection. + */ + Gene.prototype.getConnection = function() { + return new Connection(this.in, this.out, this.weight); + }; + +/** + * A genome containing genes that will make up the neural network. + * @param {Number} inputNodes The number of input nodes to create. + * @param {Number} outputNodes The number of output nodes to create. + */ + function Genome(inputNodes, outputNodes) { + this.inputNodes = inputNodes; + this.outputNodes = outputNodes; + this.genes = []; + this.fitness = -Number.MAX_VALUE; + this.globalRank = 0; + this.randomIdentifier = Math.random(); + } + + Genome.prototype.containsGene = function(inID, outID) { + for (var i = 0; i < this.genes.length; i++) { + if (this.genes[i].inID == inID && this.genes[i].outID == outID) { + return true; + } + } + return false; + }; + +/** + * A species of genomes that contains genomes which closely resemble one another, enough so that they are able to breed. + */ + function Species() { + this.genomes = []; + this.averageFitness = 0; + } + +/** + * Culls the genomes to the given amount by removing less fit genomes. + * @param {Number} [remaining] The number of genomes to cull to [Default is half the size of the species (rounded up)]. + */ + Species.prototype.cull = function(remaining) { + this.genomes.sort(compareGenomesDescending); + if (remaining === undefined) { + remaining = Math.ceil(this.genomes.length / 2); + } + while (this.genomes.length > remaining) { + this.genomes.pop(); + } + }; + +/** + * Calculates the average fitness of the species. + */ + Species.prototype.calculateAverageFitness = function() { + var sum = 0; + for (var j = 0; j < this.genomes.length; j++) { + sum += this.genomes[j].fitness; + } + this.averageFitness = sum / this.genomes.length; + }; + +/** + * Returns the network that the genome represents. + * @return {Network} The generated network. + */ + Genome.prototype.getNetwork = function() { + var network = new Network(); + network.createNodes(this.inputNodes, 0, this.outputNodes); + for (var i = 0; i < this.genes.length; i++) { + var gene = this.genes[i]; + if (gene.enabled) { + if (network.nodes[gene.in] === undefined && gene.in.indexOf("HIDDEN") != -1) { + network.nodes[gene.in] = new Node(gene.in); + network.hidden.push(gene.in); + } + if (network.nodes[gene.out] === undefined && gene.out.indexOf("HIDDEN") != -1) { + network.nodes[gene.out] = new Node(gene.out); + network.hidden.push(gene.out); + } + network.addConnection(gene.in, gene.out, gene.weight); + } + } + return network; + }; + +/** + * Creates and optimizes neural networks via neuroevolution, using the Neuroevolution of Augmenting Topologies method. + * @param {Object} config The configuration to use. + */ + function Neuroevolution(config) { + this.genomes = []; + this.populationSize = 100; + this.mutationRates = { + createConnection: 0.05, + createNode: 0.02, + modifyWeight: 0.15, + enableGene: 0.05, + disableGene: 0.1, + createBias: 0.1, + weightMutationStep: 2 + }; + this.inputNodes = 0; + this.outputNodes = 0; + this.elitism = true; + this.deltaDisjoint = 2; + this.deltaWeights = 0.4; + this.deltaThreshold = 2; + this.hiddenNodeCap = 10; + this.fitnessFunction = function (network) {log("ERROR: Fitness function not set"); return -1;}; + this.globalInnovationCounter = 1; + this.currentGeneration = 0; + this.species = []; + this.newInnovations = {}; + if (config !== undefined) { + if (config.populationSize !== undefined) { + this.populationSize = config.populationSize; + } + if (config.inputNodes !== undefined) { + this.inputNodes = config.inputNodes; + } + if (config.outputNodes !== undefined) { + this.outputNodes = config.outputNodes; + } + if (config.mutationRates !== undefined) { + var configRates = config.mutationRates; + if (configRates.createConnection !== undefined) { + this.mutationRates.createConnection = configRates.createConnection; + } + if (configRates.createNode !== undefined) { + this.mutationRates.createNode = configRates.createNode; + } + if (configRates.modifyWeight !== undefined) { + this.mutationRates.modifyWeight = configRates.modifyWeight; + } + if (configRates.enableGene !== undefined) { + this.mutationRates.enableGene = configRates.enableGene; + } + if (configRates.disableGene !== undefined) { + this.mutationRates.disableGene = configRates.disableGene; + } + if (configRates.createBias !== undefined) { + this.mutationRates.createBias = configRates.createBias; + } + if (configRates.weightMutationStep !== undefined) { + this.mutationRates.weightMutationStep = configRates.weightMutationStep; + } + } + if (config.elitism !== undefined) { + this.elitism = config.elitism; + } + if (config.deltaDisjoint !== undefined) { + this.deltaDisjoint = config.deltaDisjoint; + } + if (config.deltaWeights !== undefined) { + this.deltaWeights = config.deltaWeights; + } + if (config.deltaThreshold !== undefined) { + this.deltaThreshold = config.deltaThreshold; + } + if (config.hiddenNodeCap !== undefined) { + this.hiddenNodeCap = config.hiddenNodeCap; + } + } + } + +/** + * Populates the population with empty genomes, and then mutates the genomes. + */ + Neuroevolution.prototype.createInitialPopulation = function() { + this.genomes = []; + for (var i = 0; i < this.populationSize; i++) { + var genome = this.linkMutate(new Genome(this.inputNodes, this.outputNodes)); + this.genomes.push(genome); + } + this.mutate(); + }; + +/** + * Mutates the entire population based on the mutation rates. + */ + Neuroevolution.prototype.mutate = function() { + for (var i = 0; i < this.genomes.length; i++) { + var network = this.genomes[i].getNetwork(); + if (Math.random() < this.mutationRates.createConnection) { + this.genomes[i] = this.linkMutate(this.genomes[i]); + } + if (Math.random() < this.mutationRates.createNode && this.genomes[i].genes.length > 0 && network.hidden.length < this.hiddenNodeCap) { + var geneIndex = randomNumBetween(0, this.genomes[i].genes.length - 1); + var gene = this.genomes[i].genes[geneIndex]; + if (gene.enabled && gene.in.indexOf("INPUT") != -1 && gene.out.indexOf("OUTPUT") != -1) { + var newNum = -1; + var found = true; + while (found) { + newNum++; + found = false; + for (var j = 0; j < this.genomes[i].genes.length; j++) { + if (this.genomes[i].genes[j].in.indexOf("HIDDEN:" + newNum) != -1 || this.genomes[i].genes[j].out.indexOf("HIDDEN:" + newNum) != -1) { + found = true; + } + } + } + if (newNum < this.hiddenNodeCap) { + var nodeName = "HIDDEN:" + newNum; + this.genomes[i].genes[geneIndex].enabled = false; + this.genomes[i].genes.push(new Gene(gene.in, nodeName, 1, this.globalInnovationCounter)); + this.globalInnovationCounter++; + this.genomes[i].genes.push(new Gene(nodeName, gene.out, gene.weight, this.globalInnovationCounter)); + this.globalInnovationCounter++; + network = this.genomes[i].getNetwork(); + } + } + } + if (Math.random() < this.mutationRates.createBias) { + if (Math.random() > 0.5 && network.inputs.length > 0) { + var inputIndex = randomNumBetween(0, network.inputs.length - 1); + if (network.getConnection("BIAS:" + network.inputs[inputIndex]) === undefined) { + this.genomes[i].genes.push(new Gene("BIAS", network.inputs[inputIndex])); + } + } else if (network.hidden.length > 0) { + var hiddenIndex = randomNumBetween(0, network.hidden.length - 1); + if (network.getConnection("BIAS:" + network.hidden[hiddenIndex]) === undefined) { + this.genomes[i].genes.push(new Gene("BIAS", network.hidden[hiddenIndex])); + } + } + } + for (var k = 0; k < this.genomes[i].genes.length; k++) { + this.genomes[i].genes[k] = this.pointMutate(this.genomes[i].genes[k]); + } + + } + }; + +/** + * Attempts to create a new connection gene in the given genome. + * @param {Genome} genome The genome to mutate. + * @return {Genome} The mutated genome. + */ + Neuroevolution.prototype.linkMutate = function(genome) { + var network = genome.getNetwork(); + var inNode = ""; + var outNode = ""; + if (Math.random() < 1/3 || network.hidden.length <= 0) { + inNode = network.inputs[randomNumBetween(0, this.inputNodes - 1)]; + outNode = network.outputs[randomNumBetween(0, this.outputNodes - 1)]; + } else if (Math.random() < 2/3) { + inNode = network.inputs[randomNumBetween(0, this.inputNodes - 1)]; + outNode = network.hidden[randomNumBetween(0, network.hidden.length - 1)]; + } else { + inNode = network.hidden[randomNumBetween(0, network.hidden.length - 1)]; + outNode = network.outputs[randomNumBetween(0, this.outputNodes - 1)]; + } + if (!genome.containsGene(inNode, outNode)) { + var newGene = new Gene(inNode, outNode, Math.random() * 2 - 1); + if (this.newInnovations[newGene.in + ":" + newGene.out] === undefined) { + this.newInnovations[newGene.in + ":" + newGene.out] = this.globalInnovationCounter; + newGene.innovation = this.globalInnovationCounter; + this.globalInnovationCounter++; + } else { + newGene.innovation = this.newInnovations[newGene.in + ":" + newGene.out]; + } + genome.genes.push(newGene); + } + return genome; + }; + + /** + * Mutates the given gene based on the mutation rates. + * @param {Gene} gene The gene to mutate. + * @return {Gene} The mutated gene. + */ + Neuroevolution.prototype.pointMutate = function(gene) { + if (Math.random() < this.mutationRates.modifyWeight) { + gene.weight = gene.weight + Math.random() * this.mutationRates.weightMutationStep * 2 - this.mutationRates.weightMutationStep; + } + if (Math.random() < this.mutationRates.enableGene) { + gene.enabled = true; + } + if (Math.random() < this.mutationRates.disableGene) { + gene.enabled = false; + } + return gene; + }; + +/** + * Crosses two parent genomes with one another, forming a child genome. + * @param {Genome} firstGenome The first genome to mate. + * @param {Genome} secondGenome The second genome to mate. + * @return {Genome} The resultant child genome. + */ + Neuroevolution.prototype.crossover = function(firstGenome, secondGenome) { + var child = new Genome(firstGenome.inputNodes, firstGenome.outputNodes); + var firstInnovationNumbers = {}; + for (var h = 0; h < firstGenome.genes.length; h++) { + firstInnovationNumbers[firstGenome.genes[h].innovation] = h; + } + var secondInnovationNumbers = {}; + for (var j = 0; j < secondGenome.genes.length; j++) { + secondInnovationNumbers[secondGenome.genes[j].innovation] = j; + } + for (var i = 0; i < firstGenome.genes.length; i++) { + var geneToClone; + if (secondInnovationNumbers[firstGenome.genes[i].innovation] !== undefined) { + if (Math.random() < 0.5) { + geneToClone = firstGenome.genes[i]; + } else { + geneToClone = secondGenome.genes[secondInnovationNumbers[firstGenome.genes[i].innovation]]; + } + } else { + geneToClone = firstGenome.genes[i]; + } + child.genes.push(new Gene(geneToClone.in, geneToClone.out, geneToClone.weight, geneToClone.innovation, geneToClone.enabled)); + } + for (var k = 0; k < secondGenome.genes.length; k++) { + if (firstInnovationNumbers[secondGenome.genes[k].innovation] === undefined) { + var secondDisjoint = secondGenome.genes[k]; + child.genes.push(new Gene(secondDisjoint.in, secondDisjoint.out, secondDisjoint.weight, secondDisjoint.innovation, secondDisjoint.enabled)); + } + } + return child; + }; + +/** + * Evolves the population by creating a new generation and mutating the children. + */ + Neuroevolution.prototype.evolve = function() { + this.currentGeneration++; + this.newInnovations = {}; + this.genomes.sort(compareGenomesDescending); + var children = []; + this.speciate(); + this.cullSpecies(); + this.calculateSpeciesAvgFitness(); + + var totalAvgFitness = 0; + var avgFitnesses = []; + for (var s = 0; s < this.species.length; s++) { + totalAvgFitness += this.species[s].averageFitness; + avgFitnesses.push(this.species[s].averageFitness); + } + var arr = []; + for (var j = 0; j < this.species.length; j++) { + var childrenToMake = Math.floor(this.species[j].averageFitness / totalAvgFitness * this.populationSize); + arr.push(childrenToMake); + if (childrenToMake > 0) { + children.push(this.species[j].genomes[0]); + } + for (var c = 0; c < childrenToMake - 1; c++) { + children.push(this.makeBaby(this.species[j])); + } + } + while (children.length < this.populationSize) { + children.push(this.makeBaby(this.species[randomNumBetween(0, this.species.length - 1)])); + } + this.genomes = []; + this.genomes = this.genomes.concat(children); + this.mutate(); + this.speciate(); + log(this.species.length); + }; + +/** + * Sorts the genomes into different species. + */ + Neuroevolution.prototype.speciate = function() { + this.species = []; + for (var i = 0; i < this.genomes.length; i++) { + var placed = false; + for (var j = 0; j < this.species.length; j++) { + if (!placed && this.species[j].genomes.length > 0 && this.isSameSpecies(this.genomes[i], this.species[j].genomes[0])) { + this.species[j].genomes.push(this.genomes[i]); + placed = true; + } + } + if (!placed) { + var newSpecies = new Species(); + newSpecies.genomes.push(this.genomes[i]); + this.species.push(newSpecies); + } + } + }; + +/** + * Culls all the species to the given amount by removing less fit members of each species. + * @param {Number} [remaining] The number of genomes to cull all the species to [Default is half the size of the species]. + */ + Neuroevolution.prototype.cullSpecies = function(remaining) { + var toRemove = []; + for (var i = 0; i < this.species.length; i++) { + this.species[i].cull(remaining); + if (this.species[i].genomes.length < 1) { + toRemove.push(this.species[i]); + } + } + for (var r = 0; r < toRemove.length; r++) { + this.species.remove(toRemove[r]); + } + }; + +/** + * Calculates the average fitness of all the species. + */ + Neuroevolution.prototype.calculateSpeciesAvgFitness = function() { + for (var i = 0; i < this.species.length; i++) { + this.species[i].calculateAverageFitness(); + } + }; + +/** + * Creates a baby in the given species, with fitter genomes having a higher chance to reproduce. + * @param {Species} species The species to create a baby in. + * @return {Genome} The resultant baby. + */ + Neuroevolution.prototype.makeBaby = function(species) { + var mum = species.genomes[randomWeightedNumBetween(0, species.genomes.length - 1)]; + var dad = species.genomes[randomWeightedNumBetween(0, species.genomes.length - 1)]; + return this.crossover(mum, dad); + }; + +/** + * Calculates the fitness of all the genomes in the population. + */ + Neuroevolution.prototype.calculateFitnesses = function() { + for (var i = 0; i < this.genomes.length; i++) { + this.genomes[i].fitness = this.fitnessFunction(this.genomes[i].getNetwork()); + } + }; + +/** + * Returns the relative compatibility metric for the given genomes. + * @param {Genome} genomeA The first genome to compare. + * @param {Genome} genomeB The second genome to compare. + * @return {Number} The relative compatibility metric. + */ + Neuroevolution.prototype.getCompatibility = function(genomeA, genomeB) { + var disjoint = 0; + var totalWeight = 0; + var aInnovationNums = {}; + for (var i = 0; i < genomeA.genes.length; i++) { + aInnovationNums[genomeA.genes[i].innovation] = i; + } + var bInnovationNums = []; + for (var j = 0; j < genomeB.genes.length; j++) { + bInnovationNums[genomeB.genes[j].innovation] = j; + } + for (var k = 0; k < genomeA.genes.length; k++) { + if (bInnovationNums[genomeA.genes[k].innovation] === undefined) { + disjoint++; + } else { + totalWeight += Math.abs(genomeA.genes[k].weight - genomeB.genes[bInnovationNums[genomeA.genes[k].innovation]].weight); + } + } + for (var l = 0; l < genomeB.genes.length; l++) { + if (aInnovationNums[genomeB.genes[l].innovation] === undefined) { + disjoint++; + } + } + var n = Math.max(genomeA.genes.length, genomeB.genes.length); + return this.deltaDisjoint * (disjoint / n) + this.deltaWeights * (totalWeight / n); + }; + +/** + * Determines whether the given genomes are from the same species. + * @param {Genome} genomeA The first genome to compare. + * @param {Genome} genomeB The second genome to compare. + * @return {Boolean} Whether the given genomes are from the same species. + */ + Neuroevolution.prototype.isSameSpecies = function(genomeA, genomeB) { + return this.getCompatibility(genomeA, genomeB) < this.deltaThreshold; + }; + +/** + * Returns the genome with the highest fitness in the population. + * @return {Genome} The elite genome. + */ + Neuroevolution.prototype.getElite = function() { + this.genomes.sort(compareGenomesDescending); + return this.genomes[0]; + }; + + +//Private static functions +function sigmoid(t) { + return 1 / (1 + Math.exp(-t)); +} + +function randomNumBetween(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); +} + +function randomWeightedNumBetween(min, max) { + return Math.floor(Math.pow(Math.random(), 2) * (max - min + 1) + min); +} + +function compareGenomesAscending(genomeA, genomeB) { + return genomeA.fitness - genomeB.fitness; +} + +function compareGenomesDescending(genomeA, genomeB) { + return genomeB.fitness - genomeA.fitness; +} + +Array.prototype.remove = function() { + var what, a = arguments, L = a.length, ax; + while (L && this.length) { + what = a[--L]; + while ((ax = this.indexOf(what)) !== -1) { + this.splice(ax, 1); + } + } + return this; +}; + + +function log(text) { + console.log(text); +} diff --git a/Backbone Tetris Game/style.css b/Backbone Tetris Game/style.css new file mode 100644 index 000000000..e56fbd63b --- /dev/null +++ b/Backbone Tetris Game/style.css @@ -0,0 +1,34 @@ +body { + background-color: #272821; + } + .text { + color: #706C5A; + font-family: Inconsolata, Courier, monospace; + font-size: 20px; + } + #output { + float: left; + padding-left: 20%; + } + #score { + padding-left: 55%; + } + #instructions { + float: left; + position: absolute; + left: 1.5%; + bottom: 3%; + font-size: small; + line-height: 110%; + } + #signature { + float: right; + position: absolute; + right: 1.5%; + bottom: 3%; + font-size: small; + line-height: 110%; + } + a:link { + color: inherit; + } diff --git a/Beer Filling Game/index.html b/Beer Filling Game/index.html index 07da72e96..eacd88827 100644 --- a/Beer Filling Game/index.html +++ b/Beer Filling Game/index.html @@ -5,6 +5,7 @@ + Shoot Easter Bunny diff --git a/Bloom filter visualizer/templates/index.html b/Bloom filter visualizer/templates/index.html index e8b7c86ae..2a27b044c 100644 --- a/Bloom filter visualizer/templates/index.html +++ b/Bloom filter visualizer/templates/index.html @@ -4,6 +4,7 @@ Bloom Filter Animation + + Flames Calculator + + +
+
+

FLAMES

+
+ + + + + + + +
+
+
+ +
+
+

Results

+

Your Relationship:

+

+ +

F - Friend

+

L - Love

+

A - Affection

+

M - Marriage

+

E - Enemy

+

S - Sister

+
+
+ + + \ No newline at end of file diff --git a/Flames/flames_calculation.js b/Flames/flames_calculation.js new file mode 100644 index 000000000..65373e565 --- /dev/null +++ b/Flames/flames_calculation.js @@ -0,0 +1,42 @@ +var name1; +var name2; +const button = document.getElementById('check-button'); +if(button){ + button.addEventListener('click',function(event){ + event.preventDefault(); + name1 = document.getElementById("first-name").value; + name2 = document.getElementById("second-name").value; + name1 = name1.trim(); + name1 = name1.toLowerCase(); + name2 = name2.trim(); + name2 = name2.toLowerCase(); + name1 = name1.replace(' ',''); + name2 = name2.replace(' ',''); + name1 = Array.from(new Set(name1)).join(''); + name2 = Array.from(new Set(name2)).join(''); + console.log(name1); + console.log(name2); + const flames = ['Sister','Friend','Love','Affection','Marriage','Enemy']; + if (name1 == '' || name2 == '') { + alert("Please enter both names."); + return; + } + var counter=0; + for(let i=0;i + + + + + Flappy Bird + + + + +

Flappy Bird

+ + + + + + \ No newline at end of file diff --git a/Flappy-Birds-Game/script.js b/Flappy-Birds-Game/script.js new file mode 100644 index 000000000..0bdeb0362 --- /dev/null +++ b/Flappy-Birds-Game/script.js @@ -0,0 +1,167 @@ +let board; +let boardWidth = 360; +let boardHeight = 640; +let context; + +let birdWidth = 34; +let birdHeight = 24; +let birdX = (boardWidth - birdWidth) / 8; +let birdY = boardHeight / 2; +let birdImage; + +let bird = { + x: birdX, + y: birdY, + width: birdWidth, + height: birdHeight +} + +let pipeArray = []; +let pipeWidth = 64 ; +let pipeHeight = 512; +let pipeX = boardWidth; +let pipeY = 0; + +let topPipeImg; +let bottomPipeImg; + +let velocityX = -2; +let velocityY = 0; +let gravity = 0.4; + +let gameOver = false; +let score = 0; + +window.onload = function () { + board = document.getElementById("board"); + board.height = boardHeight; + board.width = boardWidth; + context = board.getContext("2d"); + + birdImage = new Image(); + birdImage.src = "./img/flappybird.png"; + birdImage.onload = function () { + context.drawImage(birdImage, bird.x, bird.y, bird.width, bird.height); + } + + topPipeImg = new Image(); + topPipeImg.src = "./img/toppipe.png"; + + bottomPipeImg = new Image(); + bottomPipeImg.src = "./img/bottompipe.png"; + + requestAnimationFrame(update); + setInterval(placePipes, 3000); + + document.addEventListener("keydown", moveBird); + document.addEventListener("touchstart", moveBird); + document.addEventListener("click", moveBird); +} + +function update() { + requestAnimationFrame(update); + if (gameOver) { + return; + } + context.clearRect(0, 0, board.width, board.height); + + velocityY += gravity; + bird.y = Math.max(bird.y + velocityY, 0); + context.drawImage(birdImage, bird.x, bird.y, bird.width, bird.height); + + if (bird.y > board.height) { + triggerGameOver(); + } + + for (let i = 0; i < pipeArray.length; i++) { + let pipe = pipeArray[i]; + pipe.x += velocityX; + context.drawImage(pipe.img, pipe.x, pipe.y, pipe.width, pipe.height); + + if (!pipe.passed && bird.x > pipe.x + pipe.width) { + score += 0.5; + pipe.passed = true; + } + + if (detectCollision(bird, pipeArray[i])) { + triggerGameOver(); + } + } + + while (pipeArray.length > 0 && pipeArray[0].x < -pipeWidth) { + pipeArray.shift(); + } + + context.fillStyle = "white"; + context.font = "45px sans-serif"; + context.fillText(score, 5, 45); +} + +function resetGame() { + bird.y = birdY; + pipeArray = []; + score = 0; + velocityY = 0; + gameOver = false; +} + +function placePipes() { + + if (gameOver) { + return; + } + + let randomPipeY = pipeY - pipeHeight / 4 - Math.random() * (pipeHeight / 2); + let openingSpace = board.height / 4; + + let topPipe = { + img: topPipeImg, + x: pipeX, + y: randomPipeY, + width: pipeWidth, + height: pipeHeight, + passed: false + } + pipeArray.push(topPipe); + + let bottomPipe = { + img: bottomPipeImg, + x: pipeX, + y: randomPipeY + pipeHeight + openingSpace, + width: pipeWidth, + height: pipeHeight, + passed: false + } + pipeArray.push(bottomPipe); +} + +function triggerGameOver() { + gameOver = true; + Swal.fire({ + title: 'Game Over!', + text: `Your Score: ${score}`, + icon: 'error', + confirmButtonText: 'Play Again' + }).then((result) => { + if (result.isConfirmed) { + resetGame(); + } + }); +} + +function moveBird(e) { + if (e.type === "click" || e.type === "touchstart" || e.code === "Space" || e.code === "ArrowUp" || e.code === "KeyX") { + velocityY = -6; + + if (gameOver) { + resetGame(); + } + } +} + +function detectCollision(a, b) { + return a.x < b.x + b.width && + a.x + a.width > b.x && + a.y < b.y + b.height && + a.y + a.height > b.y; +} \ No newline at end of file diff --git a/Flappy-Birds-Game/style.css b/Flappy-Birds-Game/style.css new file mode 100644 index 000000000..1a578e605 --- /dev/null +++ b/Flappy-Birds-Game/style.css @@ -0,0 +1,21 @@ +*{ + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body{ + font-family: 'Courier New', Courier, monospace; + text-align: center; + background: #71C5CF; + padding: 1rem 0; +} + +h1{ + font-size: 3rem; +} + +#board{ + background: url("./img/flappybirdbg.png"); + box-shadow: 10px 10px 100px 10px rgb(255, 255, 255); +} \ No newline at end of file diff --git a/Goal game/index.html b/Goal game/index.html new file mode 100644 index 000000000..2deb0135a --- /dev/null +++ b/Goal game/index.html @@ -0,0 +1,39 @@ + + + + + + + + Document + + +
+
+
+
+
+
+
+
+
+ + + + + + + + +

GOAL!

+

FAIL

+
+

Click inside the goal to kick, then move cursor to curve shot.

+ + \ No newline at end of file diff --git a/Goal game/main.js b/Goal game/main.js new file mode 100644 index 000000000..e4d1c1d16 --- /dev/null +++ b/Goal game/main.js @@ -0,0 +1,94 @@ +let offset = 20, + gf = game_field.getBoundingClientRect(), + b = ball.getBoundingClientRect(), + ooo = gf.x - offset, + gfcx = gf.x + (gf.width*.5) - ooo, + goalie = ['https://contentservice.mc.reyrey.net/image_v1.0.0/?id=4d2c276a-53d1-5d61-973a-43e438e4d242&638600308304985475','https://contentservice.mc.reyrey.net/image_v1.0.0/?id=50c68e89-78aa-5583-b9fb-a6b7088a2795&638600308421269575','https://contentservice.mc.reyrey.net/image_v1.0.0/?id=c82054e5-0c08-5936-8b3a-da9c3803a430&638600308520821680','https://contentservice.mc.reyrey.net/image_v1.0.0/?id=72578b00-ecdb-5e3d-824e-cdd981720b04&638600308646914584','https://contentservice.mc.reyrey.net/image_v1.0.0/?id=9effa62d-7e3d-5638-9ea2-a02ab3508dfc&638600308768788828'] + +svg.setAttribute('viewBox','0 0 ' + gf.width + ' ' + gf.height) + +function grabClick(e) { + let x = e.clientX, + y = e.clientY, + el = document.elementFromPoint(x,y), + el_num = el.getAttribute('box'), + g = Math.floor(Math.random()*5), + o = game_field.getBoundingClientRect(), + hit = true + + // console.log(g,el,hit) + if(hit){ + if(el.getAttribute('box') == 0) { + g = 'https://contentservice.mc.reyrey.net/image_v1.0.0/?id=4d2c276a-53d1-5d61-973a-43e438e4d242&638600308304985475' + } + if(el.getAttribute('box') == 1) { + g = 'https://contentservice.mc.reyrey.net/image_v1.0.0/?id=50c68e89-78aa-5583-b9fb-a6b7088a2795&638600308421269575' + } + if(el.getAttribute('box') == 2) { + g = 'https://contentservice.mc.reyrey.net/image_v1.0.0/?id=c82054e5-0c08-5936-8b3a-da9c3803a430&638600308520821680' + } + if(el.getAttribute('box') == 3) { + g = 'https://contentservice.mc.reyrey.net/image_v1.0.0/?id=72578b00-ecdb-5e3d-824e-cdd981720b04&638600308646914584' + } + if(el.getAttribute('box') == 4) { + g = 'https://contentservice.mc.reyrey.net/image_v1.0.0/?id=9effa62d-7e3d-5638-9ea2-a02ab3508dfc&638600308768788828' + } + if(el.id == 'goal') { + g = 'https://contentservice.mc.reyrey.net/image_v1.0.0/?id=f4278541-20df-568f-baea-e387ee22937c&638600307412335028' + } + } else { + g = goalie[g] + } + + ball.className = 'kicked' + let kick = setInterval(function(){ + let c = circle.getBoundingClientRect(), + cx = c.x, + cy = c.y + ball.style.left = cx - ooo - offset + 'px' + ball.style.top = (cy - offset) + 'px' + setTimeout(function(){ + clearInterval(kick) + + let c = circle.getBoundingClientRect(), + cx = c.x, + cy = c.y + + let score = document.elementFromPoint(cx, cy) + // console.log(score,el_num) + if(score.getAttribute('box') != el_num){ + ball.className = 'goal' + success.className = 'show_result' + } else { + ball.className = 'fall' + failure.className = 'show_result' + } + }, 1000) + },1000/30) + + setTimeout(function(){ + goal.style.background = 'url('+g+')' + goal.style.animation = 'none' + }, 200) + + setTimeout(function(){ + goal.style.background = '' + ball.style.left = '' + ball.style.top = '' + ball.style.setProperty('--ani', '') + ball.className = '' + success.className = '' + failure.className = '' + goal.style.animation = 'shake .25s linear infinite' + }, 4000) +} + +goal.addEventListener('click', grabClick) + +goal.addEventListener('mousemove', function(e){ + let x = e.clientX - ooo - offset, + y = e.clientY + + path.setAttribute('d', 'M '+(b.x - ooo)+' '+(b.y+ offset)+' C '+gfcx+' 250, '+gfcx+' 150, ' + x + ' ' + y) + bally.setAttribute('path', 'M '+(b.x - ooo)+' '+(b.y+ offset)+' C '+gfcx+' 250, '+gfcx+' 150, ' + x + ' ' + y) +}) diff --git a/Goal game/style.css b/Goal game/style.css new file mode 100644 index 000000000..598d24ac0 --- /dev/null +++ b/Goal game/style.css @@ -0,0 +1,191 @@ +/* author: https://codepen.io/kitjenson/pen/MWMGEaE +Kit Jenson + */ + + +:root { + --green: #1fb24c; + --red: #be1e2d; + } + html { + min-height: 100vh; + display: grid; + background: black; + font-family: system-ui; + text-align: center; + font-size: .75rem; + } + + body { + max-width: 1200px; + background: + linear-gradient(to right, black 57px, transparent 57px calc(100% - 57px), black calc(100% - 57px)), + linear-gradient(to bottom, black 450px, var(--green) 450px); + margin: 0 auto; + } + + h1 { + font-size: 10rem; + margin: 0; + position: absolute; + font-weight: 900; + color: gold; + text-shadow: 5px 5px black; + left: 50%; + top: 30%; + transform: translate(-50%,-50%); + opacity: 0; + pointer-events: none; + user-select: none; + } + #failure { + color: var(--red) !important; + } + + .show_result { + transition: .75s; + transition-delay: 1s; + opacity: 1; + } + + #game_field { + width: 100vw; + max-width: 1200px; + height: 450px; + background: url('https://contentservice.mc.reyrey.net/image_v1.0.0/?id=26107e19-02ae-5a04-bc21-88b57bc22ced&638600304193604811'); + position: relative; + background-position: 50% 0%; + background-repeat: no-repeat; + margin: 0 auto; + } + #game_field:before { + content: ''; + width: 100%; + height: 400px; + /* background: var(--red); */ + position: absolute; + left: 0; + top: -28px; + mix-blend-mode: multiply; + } + + #goal { + height: 250px; + aspect-ratio: 2/1; + margin: 0 auto; + background: url('https://contentservice.mc.reyrey.net/image_v1.0.0/?id=f4278541-20df-568f-baea-e387ee22937c&638600307412335028'); + background-size: 100% 100% !important; + box-sizing: border-box; + cursor: crosshair; + position: absolute; + left: calc(50% - 250px); + top: 30px; + animation: shake .25s linear infinite; + transform-origin: 50% 100%; + /* filter: brightness(15); */ + } + @keyframes shake { + 25% { transform: rotate(1deg); } + 75% { transform: rotate(-1deg); } + } + + .hitbox { + /* outline: 1px solid red; */ + } + .hitbox[box="0"] { + width:175px; + height: 125px; + position: absolute; + left: 0; + bottom: 0; + } + .hitbox[box="1"] { + width:175px; + height: 125px; + position: absolute; + left: 0; + top: 0; + } + .hitbox[box="2"] { + width:140px; + height: 175px; + position: absolute; + left: calc(50% - 70px); + top: 0; + } + .hitbox[box="3"] { + width:175px; + height: 125px; + position: absolute; + right: 0; + top: 0; + } + .hitbox[box="4"] { + width:175px; + height: 125px; + position: absolute; + right: 0; + bottom: 0; + } + + #ball { + --ani: roll 1s linear infinite; + width: 40px; + aspect-ratio: 1/1; + background-size: 100% 100%; + border-radius: 50%; + position: absolute; + top: calc(100% - 50px); + left: calc(50% - 20px); + pointer-events: none; + } + #ball:before{ + content: ''; + width: 40px; + aspect-ratio: 1/1; + /* background: var(--red); */ + background-size: 100% 100%; + border-radius: 50%; + position: absolute; + left: 0%; + top: 50%; + mix-blend-mode: multiply; + } + #ball:after{ + content: ''; + width: 40px; + aspect-ratio: 1/1; + background: + url(http://upload.wikimedia.org/wikipedia/en/e/ec/Soccer_ball.svg); + background-size: 100% 100%; + border-radius: 50%; + position: absolute; + left: 0; + top: 0; + } + @keyframes roll { + 100% { + transform: rotate(1080deg); + } + } + + .kicked:after { + animation: var(--ani); + } + + .fall { + animation: fall .5s linear .5s forwards; + } + @keyframes fall { + 100% { + top: 250px; + } + } + .goal { + animation: score .5s linear .5s forwards + } + @keyframes score { + 100% { + top: 200px; + } + } \ No newline at end of file diff --git a/Habit Tracker App/index.html b/Habit Tracker App/index.html index a25e5b03d..03bec121f 100644 --- a/Habit Tracker App/index.html +++ b/Habit Tracker App/index.html @@ -1,20 +1,29 @@ + + + + Habit Tracker + + Habit Tracker + + -
-

Habit Tracker

- - - - -
+
+

Habit Tracker

+ + + +

+ +
- + diff --git a/Habit Tracker App/script.js b/Habit Tracker App/script.js index 37499d608..e425e34a6 100644 --- a/Habit Tracker App/script.js +++ b/Habit Tracker App/script.js @@ -1,69 +1,92 @@ // Select elements const habitInput = document.getElementById('habitInput'); +const timeInput = document.getElementById('timeInput'); const addHabitBtn = document.getElementById('addHabitBtn'); const habitList = document.getElementById('habitList'); +const quoteDisplay = document.getElementById('quote'); + +// Motivational quotes +const quotes = [ + "Consistency is key to success.", + "Small habits make a big difference.", + "Success is the sum of small efforts repeated day in and day out.", + "The journey of a thousand miles begins with one step.", + "It's not about having time, it's about making time." +]; // Load habits from local storage on page load window.onload = function() { - const savedHabits = JSON.parse(localStorage.getItem('habits')) || []; - savedHabits.forEach((habit) => { - addHabitToDOM(habit.text, habit.completed); - }); + const savedHabits = JSON.parse(localStorage.getItem('habits')) || []; + savedHabits.forEach((habit) => { + addHabitToDOM(habit.text, habit.time, habit.completed); + }); + displayRandomQuote(); }; // Add habit event listener addHabitBtn.addEventListener('click', () => { - const habitText = habitInput.value.trim(); - if (habitText) { - addHabitToDOM(habitText); - saveHabit(habitText, false); - habitInput.value = ''; - } + const habitText = habitInput.value.trim(); + const habitTime = timeInput.value.trim(); + + if (habitText && habitTime) { + addHabitToDOM(habitText, habitTime); + saveHabit(habitText, habitTime, false); + habitInput.value = ''; + timeInput.value = ''; + } }); // Function to add a habit to the DOM -function addHabitToDOM(habitText, isCompleted = false) { - const li = document.createElement('li'); - li.innerHTML = ` - ${habitText} - - `; +function addHabitToDOM(habitText, habitTime, isCompleted = false) { + const li = document.createElement('li'); + li.innerHTML = ` + ${habitText} (${habitTime} min) + + `; - // Toggle completion on habit click - li.querySelector('span').addEventListener('click', () => { - li.querySelector('span').classList.toggle('completed'); - updateHabitStatus(habitText); - }); + // Toggle completion on habit click + li.querySelector('span').addEventListener('click', () => { + li.querySelector('span').classList.toggle('completed'); + updateHabitStatus(habitText); + }); - // Delete habit on button click - li.querySelector('.delete-btn').addEventListener('click', () => { - li.remove(); - deleteHabit(habitText); - }); + // Delete habit on button click with confirmation + li.querySelector('.delete-btn').addEventListener('click', () => { + if (confirm("Are you sure you want to delete this habit?")) { + li.remove(); + deleteHabit(habitText); + } + }); - habitList.appendChild(li); + habitList.appendChild(li); } // Save habit to local storage -function saveHabit(text, completed) { - const habits = JSON.parse(localStorage.getItem('habits')) || []; - habits.push({ text, completed }); - localStorage.setItem('habits', JSON.stringify(habits)); +function saveHabit(text, time, completed) { + const habits = JSON.parse(localStorage.getItem('habits')) || []; + habits.push({ text, time, completed }); + localStorage.setItem('habits', JSON.stringify(habits)); } // Update habit status in local storage function updateHabitStatus(habitText) { - const habits = JSON.parse(localStorage.getItem('habits')) || []; - const habit = habits.find((h) => h.text === habitText); - if (habit) { - habit.completed = !habit.completed; - localStorage.setItem('habits', JSON.stringify(habits)); - } + const habits = JSON.parse(localStorage.getItem('habits')) || []; + const habit = habits.find((h) => h.text === habitText); + if (habit) { + habit.completed = !habit.completed; + localStorage.setItem('habits', JSON.stringify(habits)); + } } // Delete habit from local storage function deleteHabit(habitText) { - let habits = JSON.parse(localStorage.getItem('habits')) || []; - habits = habits.filter((h) => h.text !== habitText); - localStorage.setItem('habits', JSON.stringify(habits)); + let habits = JSON.parse(localStorage.getItem('habits')) || []; + habits = habits.filter((h) => h.text !== habitText); + localStorage.setItem('habits', JSON.stringify(habits)); +} + +// Display a random quote +function displayRandomQuote() { + const randomIndex = Math.floor(Math.random() * quotes.length); + quoteDisplay.textContent = quotes[randomIndex]; } diff --git a/Habit Tracker App/styles.css b/Habit Tracker App/styles.css index d496ee709..f95fecda2 100644 --- a/Habit Tracker App/styles.css +++ b/Habit Tracker App/styles.css @@ -1,79 +1,106 @@ * { - margin: 0; - padding: 0; - box-sizing: border-box; - font-family: Arial, sans-serif; - } - - body { - background-color: #f4f4f4; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - } - - .container { - background-color: white; - padding: 20px; - border-radius: 8px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - width: 300px; - text-align: center; - } - - input { - width: 80%; - padding: 8px; - margin-top: 10px; - border: 1px solid #ccc; - border-radius: 4px; - } - - button { - padding: 8px 12px; - margin-top: 10px; - border: none; - background-color: #5cb85c; - color: white; - border-radius: 4px; - cursor: pointer; - } - - button:hover { - background-color: #4cae4c; - } - - ul { - list-style-type: none; - margin-top: 15px; - } - - li { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px; - background-color: #fafafa; - margin-bottom: 5px; - border-radius: 4px; - } - - .completed { - text-decoration: line-through; - color: #888; - } - - .delete-btn { - background-color: #d9534f; - border: none; - color: white; - padding: 5px; - border-radius: 4px; - cursor: pointer; - } - - .delete-btn:hover { - background-color: #c9302c; - } - + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: Arial, sans-serif; +} + +body { + background-color: #f4f4f4; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + transition: background-color 0.5s ease; +} + +.container { + background-color: white; + padding: 20px; + border-radius: 10px; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); + width: 350px; + text-align: center; + animation: fadeIn 0.5s ease; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +input { + width: 80%; + padding: 10px; + margin-top: 10px; + border: 1px solid #ccc; + border-radius: 5px; + transition: border 0.3s; +} + +input:focus { + border: 1px solid #5cb85c; + outline: none; +} + +button { + padding: 10px 15px; + margin-top: 10px; + border: none; + background-color: #5cb85c; + color: white; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s, transform 0.3s; +} + +button:hover { + background-color: #4cae4c; + transform: scale(1.05); +} + +.quote { + margin: 15px 0; + font-style: italic; + color: #666; +} + +ul { + list-style-type: none; + margin-top: 15px; + padding: 0; +} + +li { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background-color: #fafafa; + margin-bottom: 10px; + border-radius: 5px; + transition: background-color 0.3s; +} + +li:hover { + background-color: #f0f0f0; +} + +.completed { + text-decoration: line-through; + color: #888; +} + +.delete-btn { + background-color: #d9534f; + border: none; + color: white; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s; +} + +.delete-btn:hover { + background-color: #c9302c; +} diff --git a/Mario-game/README.md b/Mario-game/README.md new file mode 100644 index 000000000..764d13bd8 --- /dev/null +++ b/Mario-game/README.md @@ -0,0 +1,21 @@ +### Game-Mario-Jump + +This Mario-inspired game requires you to press the spacebar to make Mario jump and avoid colliding with passing pipes. + +### Technologies Used: +
+ JavaScript + HTML + CSS +
+ +### Project Images: +
+ Game image + Game image +
+ +
+ +### Project Link: +[https://niraj1608.github.io/Game-Mario/](https://niraj1608.github.io/Game-Mario/) diff --git a/Mario-game/css/style.css b/Mario-game/css/style.css new file mode 100644 index 000000000..7f39c2c2b --- /dev/null +++ b/Mario-game/css/style.css @@ -0,0 +1,87 @@ +*{ + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.game-board{ + width: 100%; + height: 500px; + border-bottom: 15px solid #049c18; + margin: 0 auto; + position: relative; + overflow: hidden; + background: linear-gradient(#87ceeb, #e0f6ff); +} + +.pipe{ + position: absolute; + bottom: 0; + width: 80px; + animation: pipe-animation 2s infinite linear; +} + +.mario{ + width: 150px; + position: absolute; + bottom: 0; + +} + +.jump{ + animation: jump 500ms ease-out; +} + +.clouds{ + position: absolute; + width: 550px; + animation: clouds-animation 20s infinite linear; +} + +@keyframes clouds-animation { + from{ + right: -550px; + } + + to{ + right: 100%; + } +} + +@keyframes pipe-animation { + from{ + right: -80px; + } + + to{ + right: 100%; + } +} + +@keyframes jump { + + 0%{ + bottom: 0; + } + + 40%{ + bottom: 180px; + } + + 50%{ + bottom: 180px; + } + + 60%{ + bottom: 180px; + } + + 100%{ + bottom: 0; + } +} + + + + + diff --git a/Mario-game/images/clouds.png b/Mario-game/images/clouds.png new file mode 100644 index 000000000..4ddc808f4 Binary files /dev/null and b/Mario-game/images/clouds.png differ diff --git a/Mario-game/images/game-over.png b/Mario-game/images/game-over.png new file mode 100644 index 000000000..3681da3b8 Binary files /dev/null and b/Mario-game/images/game-over.png differ diff --git a/Mario-game/images/mario.gif b/Mario-game/images/mario.gif new file mode 100644 index 000000000..9199d1ebf Binary files /dev/null and b/Mario-game/images/mario.gif differ diff --git a/Mario-game/images/pipe.png b/Mario-game/images/pipe.png new file mode 100644 index 000000000..b20d387ef Binary files /dev/null and b/Mario-game/images/pipe.png differ diff --git a/Mario-game/index.html b/Mario-game/index.html new file mode 100644 index 000000000..bef8cd99e --- /dev/null +++ b/Mario-game/index.html @@ -0,0 +1,19 @@ + + + + + Mario jump + + + + + + +
+ + + +
+ + + \ No newline at end of file diff --git a/Mario-game/js/script.js b/Mario-game/js/script.js new file mode 100644 index 000000000..f65ed99ad --- /dev/null +++ b/Mario-game/js/script.js @@ -0,0 +1,46 @@ +const mario = document.querySelector('.mario'); +const pipe = document.querySelector('.pipe'); +const clouds = document.querySelector('.clouds'); + +const jump = () => { + mario.classList.add('jump'); + + setTimeout(()=> { + + mario.classList.remove('jump'); + + }, 500) +} + +const loop = setInterval(() => { + + + const cloudsPosition = clouds.offsetLeft; + const pipePosition = pipe.offsetLeft; + const marioPosition = +window.getComputedStyle(mario).bottom.replace('px', ''); + + if (pipePosition <= 120 && pipePosition > 0 && marioPosition < 80) { + + pipe.style.animation = 'none'; + pipe.style.left = `${pipePosition}px`; + + mario.style.animation = 'none'; + mario.style.bottom = `${marioPosition}px`; + + mario.src = './images/game-over.png'; + mario.style.width = '75px'; + mario.style.marginLeft = '50px'; + + clouds.style.animation = 'none'; + clouds.style.left = `${cloudsPosition}px`; + + clearInterval(loop); + + } + + }, 10); + + + + +document.addEventListener('keydown', jump); \ No newline at end of file diff --git a/MineSweeper_Game/index.html b/MineSweeper_Game/index.html new file mode 100644 index 000000000..7ac5366c4 --- /dev/null +++ b/MineSweeper_Game/index.html @@ -0,0 +1,30 @@ + + + + + + Minesweeper + + + +
+
+

Minesweeper

+
+
+
Mines: 10
+
Time: 0s
+
+
+ +
+ + + + \ No newline at end of file diff --git a/MineSweeper_Game/script.js b/MineSweeper_Game/script.js new file mode 100644 index 000000000..de07b805c --- /dev/null +++ b/MineSweeper_Game/script.js @@ -0,0 +1,453 @@ +class Minesweeper { + constructor(rows, cols, mines) { + this.rows = rows; + this.cols = cols; + this.mines = mines; + this.board = []; + this.gameOver = false; + this.revealedCount = 0; + this.flaggedCount = 0; + this.startTime = null; + this.timerInterval = null; + this.firstClick = true; + this.difficulty = { + easy: { rows: 8, cols: 8, mines: 10 }, + medium: { rows: 16, cols: 16, mines: 40 }, + hard: { rows: 16, cols: 30, mines: 99 } + }; + + this.initializeDOM(); + this.setupEventListeners(); + this.startNewGame(); + } + + initializeDOM() { + this.gameBoard = document.getElementById('game-board'); + this.mineCountDisplay = document.getElementById('mine-count'); + this.timerDisplay = document.getElementById('timer'); + this.newGameBtn = document.getElementById('new-game-btn'); + this.modal = document.getElementById('game-over-modal'); + this.modalMessage = document.getElementById('game-over-message'); + this.modalStats = document.getElementById('game-over-stats'); + this.playAgainBtn = document.getElementById('play-again-btn'); + this.difficultySelect = document.getElementById('difficulty-select'); + } + + setupEventListeners() { + this.newGameBtn.addEventListener('click', () => this.startNewGame()); + this.playAgainBtn.addEventListener('click', () => this.startNewGame()); + this.difficultySelect?.addEventListener('change', (e) => this.changeDifficulty(e.target.value)); + document.addEventListener('keydown', (e) => { + if (e.key === 'r' || e.key === 'R') { + this.startNewGame(); + } + }); + } + + changeDifficulty(level) { + const config = this.difficulty[level]; + if (config) { + this.rows = config.rows; + this.cols = config.cols; + this.mines = config.mines; + this.startNewGame(); + } + } + + startNewGame() { + this.gameOver = false; + this.revealedCount = 0; + this.flaggedCount = 0; + this.firstClick = true; + this.board = this.createBoard(); + this.renderBoard(); + this.updateMineCount(); + this.stopTimer(); + this.timerDisplay.textContent = 'Time: 0s'; + this.modal.style.display = 'none'; + } + + createBoard() { + return Array.from({ length: this.rows }, () => + Array.from({ length: this.cols }, () => ({ + isMine: false, + isRevealed: false, + isFlagged: false, + adjacentMines: 0 + })) + ); + } + + renderBoard() { + this.gameBoard.innerHTML = ''; + this.gameBoard.style.gridTemplateColumns = `repeat(${this.cols}, 30px)`; + + for (let row = 0; row < this.rows; row++) { + for (let col = 0; col < this.cols; col++) { + const cell = document.createElement('div'); + cell.classList.add('cell'); + cell.dataset.row = row; + cell.dataset.col = col; + cell.addEventListener('click', (e) => this.handleCellClick(e)); + cell.addEventListener('contextmenu', (e) => this.handleRightClick(e)); + cell.addEventListener('touchstart', (e) => this.handleTouchStart(e)); + cell.addEventListener('touchend', (e) => this.handleTouchEnd(e)); + this.gameBoard.appendChild(cell); + } + } + } + + handleTouchStart(event) { + event.preventDefault(); + this.touchStartTime = Date.now(); + this.touchTimer = setTimeout(() => { + const cell = event.target; + const row = parseInt(cell.dataset.row); + const col = parseInt(cell.dataset.col); + this.toggleFlag(row, col); + }, 500); + } + + handleTouchEnd(event) { + event.preventDefault(); + clearTimeout(this.touchTimer); + if (Date.now() - this.touchStartTime < 500) { + const cell = event.target; + const row = parseInt(cell.dataset.row); + const col = parseInt(cell.dataset.col); + this.handleCellClick({ target: cell }); + } + } + + placeMines(excludeRow, excludeCol) { + let minesToPlace = this.mines; + while (minesToPlace > 0) { + const row = Math.floor(Math.random() * this.rows); + const col = Math.floor(Math.random() * this.cols); + if (!this.board[row][col].isMine && (row !== excludeRow || col !== excludeCol)) { + this.board[row][col].isMine = true; + minesToPlace--; + } + } + } + + calculateAdjacentMines() { + for (let row = 0; row < this.rows; row++) { + for (let col = 0; col < this.cols; col++) { + if (!this.board[row][col].isMine) { + this.board[row][col].adjacentMines = this.countAdjacentMines(row, col); + } + } + } + } + + countAdjacentMines(row, col) { + let count = 0; + for (let r = row - 1; r <= row + 1; r++) { + for (let c = col - 1; c <= col + 1; c++) { + if (this.isValidCell(r, c) && this.board[r][c].isMine) { + count++; + } + } + } + return count; + } + + isValidCell(row, col) { + return row >= 0 && row < this.rows && col >= 0 && col < this.cols; + } + + handleCellClick(event) { + if (this.gameOver) return; + const cell = event.target; + const row = parseInt(cell.dataset.row); + const col = parseInt(cell.dataset.col); + + if (this.board[row][col].isFlagged) return; + + if (this.firstClick) { + this.firstClick = false; + this.placeMines(row, col); + this.calculateAdjacentMines(); + this.startTimer(); + } + + this.revealCell(row, col); + } + + handleRightClick(event) { + event.preventDefault(); + if (this.gameOver) return; + const cell = event.target; + const row = parseInt(cell.dataset.row); + const col = parseInt(cell.dataset.col); + + this.toggleFlag(row, col); + } + + toggleFlag(row, col) { + const cell = this.board[row][col]; + if (cell.isRevealed) return; + + cell.isFlagged = !cell.isFlagged; + this.flaggedCount += cell.isFlagged ? 1 : -1; + this.updateMineCount(); + this.renderCell(row, col); + + if (this.checkWinCondition()) { + this.endGame(true); + } + } + + checkWinCondition() { + for (let row = 0; row < this.rows; row++) { + for (let col = 0; col < this.cols; col++) { + const cell = this.board[row][col]; + if (cell.isMine && !cell. isFlagged) { + return false; + } + if (!cell.isMine && !cell.isRevealed) { + return false; + } + } + } + return true; + } + + revealCell(row, col) { + const cell = this.board[row][col]; + if (cell.isRevealed || cell.isFlagged) return; + + cell.isRevealed = true; + this.revealedCount++; + this.renderCell(row, col); + + if (cell.isMine) { + this.endGame(false); + return; + } + + if (cell.adjacentMines === 0) { + this.revealAdjacentCells(row, col); + } + + if (this.checkWinCondition()) { + this.endGame(true); + } + } + + revealAdjacentCells(row, col) { + for (let r = row - 1; r <= row + 1; r++) { + for (let c = col - 1; c <= col + 1; c++) { + if (this.isValidCell(r, c)) { + this.revealCell(r, c); + } + } + } + } + + renderCell(row, col) { + const cellElement = this.gameBoard.querySelector(`[data-row="${row}"][data-col="${col}"]`); + const cell = this.board[row][col]; + + if (cell.isRevealed) { + cellElement.classList.add('revealed'); + if (cell.isMine) { + cellElement.textContent = '💣'; + } else if (cell.adjacentMines > 0) { + cellElement.textContent = cell.adjacentMines; + } + } else if (cell.isFlagged) { + cellElement.textContent = '🚩'; + } + } + + updateMineCount() { + this.mineCountDisplay.textContent = `Mines: ${this.mines - this.flaggedCount}`; + } + + startTimer() { + this.startTime = Date.now(); + this.timerInterval = setInterval(() => { + const elapsedTime = Math.floor((Date.now() - this.startTime) / 1000); + this.timerDisplay.textContent = `Time: ${elapsedTime}s`; + }, 1000); + } + + stopTimer() { + clearInterval(this.timerInterval); + } + + endGame(won) { + this.gameOver = true; + this.stopTimer(); + this.modal.style.display = 'block'; + this.modalMessage.textContent = won ? 'You Win!' : 'Game Over!'; + this.modalStats.textContent = `Time: ${this.timerDisplay.textContent} | Revealed: ${this.revealedCount}`; + this.revealAllCells(); + } + + revealAllCells() { + for (let row = 0; row < this.rows; row++) { + for (let col = 0; col < this.cols; col++) { + this.revealCell(row, col); + } + } + } +} + +document.addEventListener('DOMContentLoaded', () => { + const minesweeper = new Minesweeper(8, 8, 10); + + // Add keyboard shortcuts + document.addEventListener('keydown', (e) => { + if (e.key === 'n' || e.key === 'N') { + minesweeper.startNewGame(); + } + }); + + // Add difficulty selector + const difficultySelect = document.getElementById('difficulty-select'); + if (difficultySelect) { + difficultySelect.addEventListener('change', (e) => { + const difficulty = e.target.value; + switch (difficulty) { + case 'easy': + minesweeper.setDifficulty(8, 8, 10); + break; + case 'medium': + minesweeper.setDifficulty(16, 16, 40); + break; + case 'hard': + minesweeper.setDifficulty(16, 30, 99); + break; + } + }); + } + + // Add theme switcher + const themeToggle = document.getElementById('theme-toggle'); + if (themeToggle) { + themeToggle.addEventListener('click', () => { + document.body.classList.toggle('dark-theme'); + themeToggle.textContent = document.body.classList.contains('dark-theme') ? '☀️' : '🌙'; + }); + } + + // Add high scores + const highScores = JSON.parse(localStorage.getItem('minesweeperHighScores')) || []; + + function updateHighScores(time, difficulty) { + highScores.push({ time, difficulty }); + highScores.sort((a, b) => a.time - b.time); + highScores.splice(10); // Keep only top 10 scores + localStorage.setItem('minesweeperHighScores', JSON.stringify(highScores)); + displayHighScores(); + } + + function displayHighScores() { + const highScoresList = document.getElementById('high-scores-list'); + if (highScoresList) { + highScoresList.innerHTML = ''; + highScores.forEach((score, index) => { + const li = document.createElement('li'); + li.textContent = `${index + 1}. ${score.time}s (${score.difficulty})`; + highScoresList.appendChild(li); + }); + } + } + + displayHighScores(); + + // Extend Minesweeper class with new methods + Minesweeper.prototype.setDifficulty = function(rows, cols, mines) { + this.rows = rows; + this.cols = cols; + this.mines = mines; + this.startNewGame(); + }; + + Minesweeper.prototype.endGame = function(won) { + this.gameOver = true; + this.stopTimer(); + const elapsedTime = Math.floor((Date.now() - this.startTime) / 1000); + + if (won) { + const difficulty = this.getDifficulty(); + updateHighScores(elapsedTime, difficulty); + } + + this.modal.style.display = 'block'; + this.modalMessage.textContent = won ? 'You Win!' : 'Game Over!'; + this.modalStats.textContent = `Time: ${elapsedTime}s | Revealed: ${this.revealedCount}`; + this.revealAllCells(); + }; + + Minesweeper.prototype.getDifficulty = function() { + if (this.rows === 8 && this.cols === 8 && this.mines === 10) return 'Easy'; + if (this.rows === 16 && this.cols === 16 && this.mines === 40) return 'Medium'; + if (this.rows === 16 && this.cols === 30 && this.mines === 99) return 'Hard'; + return 'Custom'; + }; + + // Add custom board size input + const customSizeForm = document.getElementById('custom-size-form'); + if (customSizeForm) { + customSizeForm.addEventListener('submit', (e) => { + e.preventDefault(); + const rows = parseInt(document.getElementById('custom-rows').value); + const cols = parseInt(document.getElementById('custom-cols').value); + const mines = parseInt(document.getElementById('custom-mines').value); + if (rows > 0 && cols > 0 && mines > 0 && mines < rows * cols) { + minesweeper.setDifficulty(rows, cols, mines); + } else { + alert('Invalid board size or number of mines'); + } + }); + } + + // Add sound effects + const soundToggle = document.getElementById('sound-toggle'); + let soundEnabled = localStorage.getItem('minesweeperSound') !== 'false'; + + const sounds = { + click: new Audio('click.mp3'), + flag: new Audio('flag.mp3'), + win: new Audio('win.mp3'), + lose: new Audio('lose.mp3') + }; + + function playSound(sound) { + if (soundEnabled) { + sounds[sound].play(); + } + } + + if (soundToggle) { + soundToggle.textContent = soundEnabled ? '🔊' : '🔇'; + soundToggle.addEventListener('click', () => { + soundEnabled = !soundEnabled; + localStorage.setItem('minesweeperSound', soundEnabled); + soundToggle.textContent = soundEnabled ? '🔊' : '🔇'; + }); + } + + // Extend Minesweeper class to include sound effects + const originalHandleCellClick = Minesweeper.prototype.handleCellClick; + Minesweeper.prototype.handleCellClick = function(event) { + originalHandleCellClick.call(this, event); + playSound('click'); + }; + + const originalToggleFlag = Minesweeper.prototype.toggleFlag; + Minesweeper.prototype.toggleFlag = function(row, col) { + originalToggleFlag.call(this, row, col); + playSound('flag'); + }; + + const originalEndGame = Minesweeper.prototype.endGame; + Minesweeper.prototype.endGame = function(won) { + originalEndGame.call(this, won); + playSound(won ? 'win' : 'lose'); + }; +}); \ No newline at end of file diff --git a/MineSweeper_Game/style.css b/MineSweeper_Game/style.css new file mode 100644 index 000000000..5abd3c585 --- /dev/null +++ b/MineSweeper_Game/style.css @@ -0,0 +1,111 @@ +:root { + --primary-color: #3498db; + --secondary-color: #2c3e50; + --cell-revealed: #ecf0f1; + --cell-unrevealed: #34495e; + --mine-color: #e74c3c; + --flag-color: #f1c40f; +} + +body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: var(--secondary-color); +} + +#game-container { + background-color: #fff; + border-radius: 10px; + padding: 80px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +header h1 { + text-align: center; + color: var(--primary-color); +} + +#game-info { + display: flex; + justify-content: space-between; + margin-bottom: 10px; +} + +#game-board { + display: grid; + padding: 100px; + grid-template-columns: repeat(10, 30px); + grid-gap: 1px; + background-color: #bdc3c7; + border: 1px solid #95a5a6; +} + +.cell { + width: 30px; + height: 30px; + display: flex; + justify-content: center; + align-items: center; + font-weight: bold; + cursor: pointer; + background-color: var(--cell-unrevealed); + transition: background-color 0.3s; +} + +.cell.revealed { + background-color: var(--cell-revealed); +} + +.cell.mine { + background-color: var(--mine-color); +} + +.cell.flagged::after { + content: '🚩'; +} + +#new-game-btn { + display: block; + margin: 20px auto 0; + padding: 10px 20px; + background-color: var(--primary-color); + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} + +.modal { + display: none; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +} + +.modal-content { + background-color: #fefefe; + margin: 15% auto; + padding: 20px; + border: 1px solid #888; + width: 300px; + text-align: center; + border-radius: 10px; +} + +#play-again-btn { + margin-top: 20px; + padding: 10px 20px; + background-color: var(--primary-color); + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} \ No newline at end of file diff --git a/Mood Board Generator/index.html b/Mood Board Generator/index.html index 7dd3eb2a4..3b7755d56 100644 --- a/Mood Board Generator/index.html +++ b/Mood Board Generator/index.html @@ -5,6 +5,7 @@ Mood Board Generator +
diff --git a/Ping-Pong-Game/index.html b/Ping-Pong-Game/index.html index 8abddb36c..f692cb54c 100644 --- a/Ping-Pong-Game/index.html +++ b/Ping-Pong-Game/index.html @@ -15,6 +15,12 @@
0 : 0
+ +
+ + +
+
@@ -23,6 +29,7 @@

Left Paddle: W (Up) / S (Down) | Right Paddle: Arrow Up / Arrow Down

+ diff --git a/Ping-Pong-Game/script.js b/Ping-Pong-Game/script.js index 3e7a10838..c542b4e90 100644 --- a/Ping-Pong-Game/script.js +++ b/Ping-Pong-Game/script.js @@ -5,6 +5,26 @@ const paddleRight = document.getElementById('paddleRight'); const ball = document.getElementById('ball'); const scoreLeft = document.getElementById('scoreLeft'); const scoreRight = document.getElementById('scoreRight'); + +const startPauseBtn = document.getElementById('startPauseBtn'); +const resetBtn = document.getElementById('resetBtn'); + + +let ballX = 295; +let ballY = 195; +let ballSpeedX = 5; +let ballSpeedY = 3; +let paddleSpeed = 30; +let leftScore = 0; +let rightScore = 0; +let gameInterval; +let gamePaused = true; +const winningScore = 5; + +// Paddle control for left paddle (controlled by ArrowUp and ArrowDown) +document.addEventListener('keydown', (event) => { + if (event.key === 'ArrowUp' && paddleLeft.offsetTop > 0) { + const startPauseResumeButton = document.getElementById('startPauseResumeButton'); const endButton = document.getElementById('endButton'); @@ -47,22 +67,62 @@ document.addEventListener('keydown', (event) => { // Paddle left controls if (event.key === 'w' && paddleLeft.offsetTop > 0) { + paddleLeft.style.top = (paddleLeft.offsetTop - paddleSpeed) + 'px'; } - if (event.key === 's' && paddleLeft.offsetTop < gameArea.clientHeight - paddleLeft.clientHeight) { + if (event.key === 'ArrowDown' && paddleLeft.offsetTop < gameArea.clientHeight - paddleLeft.clientHeight) { paddleLeft.style.top = (paddleLeft.offsetTop + paddleSpeed) + 'px'; } +}); + +// AI control for the right paddle +function movePaddleRight() { + const ballCenter = ballY + ball.clientHeight / 2; + const paddleCenter = paddleRight.offsetTop + paddleRight.clientHeight / 2; + + if (ballCenter < paddleCenter) { + paddleRight.style.top = Math.max(0, paddleRight.offsetTop - paddleSpeed / 2) + 'px'; + } else if (ballCenter > paddleCenter) { + paddleRight.style.top = Math.min(gameArea.clientHeight - paddleRight.clientHeight, paddleRight.offsetTop + paddleSpeed / 2) + 'px'; + + // Paddle right controls if (event.key === 'ArrowUp' && paddleRight.offsetTop > 0) { paddleRight.style.top = (paddleRight.offsetTop - paddleSpeed) + 'px'; + } - if (event.key === 'ArrowDown' && paddleRight.offsetTop < gameArea.clientHeight - paddleRight.clientHeight) { - paddleRight.style.top = (paddleRight.offsetTop + paddleSpeed) + 'px'; +} + +// Start or Pause the Game +startPauseBtn.addEventListener('click', () => { + if (gamePaused) { + gameInterval = setInterval(gameLoop, 20); + startPauseBtn.textContent = 'Pause'; + } else { + clearInterval(gameInterval); + startPauseBtn.textContent = 'Start'; } + gamePaused = !gamePaused; }); + +// Reset the Game +resetBtn.addEventListener('click', resetGame); + +function resetGame() { + leftScore = 0; + rightScore = 0; + updateScores(); + resetBall(); + startPauseBtn.textContent = 'Start'; + clearInterval(gameInterval); + gamePaused = true; +} + + // Main game loop + function gameLoop() { if (!gameStarted || gamePaused) return; @@ -70,7 +130,10 @@ function gameLoop() { ballX += ballSpeedX; ballY += ballSpeedY; + // Ball collision with top and bottom + // Ball collision with top and bottom walls + if (ballY <= 0 || ballY >= gameArea.clientHeight - ball.clientHeight) { ballSpeedY = -ballSpeedY; } @@ -86,18 +149,31 @@ function gameLoop() { // Ball out of bounds (score update) if (ballX < 0) { rightScore++; + checkWinner(); resetBall(); } else if (ballX > gameArea.clientWidth) { leftScore++; + checkWinner(); resetBall(); } +< + // Update ball position + ball.style.left = ballX + 'px'; + ball.style.top = ballY + 'px'; + + // Move AI paddle + movePaddleRight(); + + updateScores(); + // Update ball and score display updateBallPosition(); scoreLeft.textContent = leftScore; scoreRight.textContent = rightScore; animationFrameId = requestAnimationFrame(gameLoop); + } // Reset ball to the center with random direction @@ -108,6 +184,19 @@ function resetBall() { ballSpeedY = 2 * (Math.random() < 0.5 ? 1 : -1); } + +function updateScores() { + scoreLeft.textContent = leftScore; + scoreRight.textContent = rightScore; +} + +function checkWinner() { + if (leftScore === winningScore || rightScore === winningScore) { + alert(`${leftScore === winningScore ? 'Left' : 'Right'} Player Wins!`); + resetGame(); + } +} + // Start, pause, and resume game button functionality startPauseResumeButton.addEventListener('click', () => { if (!gameStarted) { @@ -140,3 +229,4 @@ endButton.addEventListener('click', () => { // Initialize the game on page load initGame(); endButton.disabled = true; + diff --git a/Ping-Pong-Game/styles.css b/Ping-Pong-Game/styles.css index a7b523a75..29914b3a8 100644 --- a/Ping-Pong-Game/styles.css +++ b/Ping-Pong-Game/styles.css @@ -24,8 +24,13 @@ body { position: absolute; width: 10px; height: 80px; + + background-color: #fff; + transition: 0.1s ease; + background: linear-gradient(135deg, #00ffcc, #0055ff); border-radius: 5px; + } #paddleLeft { @@ -84,3 +89,21 @@ body { .controlButton:hover { background-color: #00ffcc; } + +#controls { + margin-top: 15px; +} + +button { + padding: 8px 12px; + margin: 0 5px; + border: none; + background-color: #5cb85c; + color: white; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background-color: #4cae4c; +} diff --git a/Pokemon-Mini-Game/fire-removebg-preview-removebg-preview.png b/Pokemon-Mini-Game/fire-removebg-preview-removebg-preview.png new file mode 100644 index 000000000..a2277241f Binary files /dev/null and b/Pokemon-Mini-Game/fire-removebg-preview-removebg-preview.png differ diff --git a/Pokemon-Mini-Game/flying-removebg-preview-removebg-preview.png b/Pokemon-Mini-Game/flying-removebg-preview-removebg-preview.png new file mode 100644 index 000000000..21dd47a7e Binary files /dev/null and b/Pokemon-Mini-Game/flying-removebg-preview-removebg-preview.png differ diff --git a/Pokemon-Mini-Game/grass-removebg-preview.png b/Pokemon-Mini-Game/grass-removebg-preview.png new file mode 100644 index 000000000..253376573 Binary files /dev/null and b/Pokemon-Mini-Game/grass-removebg-preview.png differ diff --git a/Pokemon-Mini-Game/ground-removebg-preview.png b/Pokemon-Mini-Game/ground-removebg-preview.png new file mode 100644 index 000000000..fe67f9f89 Binary files /dev/null and b/Pokemon-Mini-Game/ground-removebg-preview.png differ diff --git a/Pokemon-Mini-Game/index .html b/Pokemon-Mini-Game/index .html new file mode 100644 index 000000000..0908785fb --- /dev/null +++ b/Pokemon-Mini-Game/index .html @@ -0,0 +1,112 @@ + + + + + + + Poki-World + + + + + + + + +
+ + + + +
+

Welcome to Poki-verse

+
+ + + + +
+
+

Choose your 
+    pokemon:-

+
+
+ +
ground
+ Ground-type
+
water
+ Water-type
+
fire
+ Fire-type
+
grass
+ Grass-type
+
flying
+ Flying-type
+ +
+
+
+

Choose a Pokemon

+ +

Fight

+ + + +
+ +
+

Your Score

+

0

+
+ +
+

Computer Score

+

0

+
+
+
+ + + +
+

Rules

+

Rules:- + 1. Fire type pokemon weakness is Water and Ground type pokemon.
+ 2. Water type pokemon weakness is Grass and Flying type pokemon.
+ 3. Grass type pokemon weakness is Fire and Flying type pokemon.
+ 4. Flying type pokemon weakness is Fire and Ground type pokemon.
+ 5. Ground type pokemon weakness is Water and Grass type pokemon.
+ 6. Fire type pokemon is strong against Flying and Grass type pokemon.
+ 7. Water type pokemon is strong against Fire and Ground type pokemon.
+ 8. Grass type pokemon is strong against Water and Ground type pokemon.
+ 9. Flying type pokemon is strong against Water and Grass type pokemon.
+ 10. Ground type pokemon is strong against Flying and Fire type pokemon.
+ +

+
+ + + + + +
+

© 2024 GSSOC. All Rights Reserved.

+ +
+ + + + + + +
+ + + + + + diff --git a/Pokemon-Mini-Game/pokeball.jpg b/Pokemon-Mini-Game/pokeball.jpg new file mode 100644 index 000000000..69b890770 Binary files /dev/null and b/Pokemon-Mini-Game/pokeball.jpg differ diff --git a/Pokemon-Mini-Game/pokemon-icon-vector-free.jpg b/Pokemon-Mini-Game/pokemon-icon-vector-free.jpg new file mode 100644 index 000000000..4db6d6dc7 Binary files /dev/null and b/Pokemon-Mini-Game/pokemon-icon-vector-free.jpg differ diff --git a/Pokemon-Mini-Game/pxfuel.jpg b/Pokemon-Mini-Game/pxfuel.jpg new file mode 100644 index 000000000..8f4042770 Binary files /dev/null and b/Pokemon-Mini-Game/pxfuel.jpg differ diff --git a/Pokemon-Mini-Game/pxfuelDark.jpg b/Pokemon-Mini-Game/pxfuelDark.jpg new file mode 100644 index 000000000..9b314402f Binary files /dev/null and b/Pokemon-Mini-Game/pxfuelDark.jpg differ diff --git a/Pokemon-Mini-Game/script.js b/Pokemon-Mini-Game/script.js new file mode 100644 index 000000000..9e1c11e3e --- /dev/null +++ b/Pokemon-Mini-Game/script.js @@ -0,0 +1,270 @@ +// alert("Welcome to Poki-Verse"); +const loader = document.body.querySelector(".loader"); +const forLoadingBody=document.body.querySelector(".forLoadingBody"); + +window.addEventListener("load", (e) => { + setTimeout(()=>{ + loader.classList.add("removeLoader"); + forLoadingBody.classList.remove("forLoadingBody"); + }, 2000); +}); + + +let bodyNode=document.querySelector("body"); +let choose=document.querySelector("#choose"); +let chooseText=document.querySelector("#chooseText"); +let fight=document.querySelector("#fight"); +let fightText=document.querySelector("#fightText"); + + + +let yourScore=document.querySelector("#yourScore"); +let computerScore=document.querySelector("#computerScore"); + +let userchoice; +let randchoice; + + + +function choice(){ + chooseText.innerText=`Your Pokemon ${userchoice}-type fought with ${randchoice}-type`; +} + + +function win(){ + fightText.innerText= "You Won!"; + fight.style.color="#25f505"; + fightText.style.color="#25f505"; + yourScore.innerText=Number(yourScore.innerText)+1; + yourScore.style.color="#1e6133"; + computerScore.style.color="whitesmoke"; +} +function lost(){ + fightText.innerText="You Lost!"; + fightText.style.color="red"; + fight.style.color="red"; + computerScore.innerText=Number(computerScore.innerText)+1; + yourScore.style.color="whitesmoke"; + computerScore.style.color="maroon"; +} +function draw(){ + fightText.innerText="its Draw!"; + fightText.style.color="white"; + fight.style.color="white"; + yourScore.style.color="#394e5c"; + computerScore.style.color="#394e5c"; +} + +bodyNode.style.backgroundImage = "url('pxfuelDark.jpg')"; + + + +let mod=document.querySelector("#mode"); +let modText=document.querySelector("#modeText"); +let anchorColors=document.querySelectorAll("a"); +let wel=document.querySelector("#welcome"); + +let mode="dark"; + document.body.style.color="white"; + +mod.addEventListener("click",(e) =>{ + if (mode === "dark") { + mode = "light"; + bodyNode.style.backgroundImage = "url('pxfuel.jpg')"; + document.body.style.color="black"; + modText.innerText="Dark Mode"; + + anchorColors.forEach((a) => { + a.style.color = "#01438a"; + }); + } else { + mode = "dark"; + bodyNode.style.backgroundImage = "url('pxfuelDark.jpg')"; + document.body.style.color="white"; + modText.innerText="Light Mode"; + + anchorColors.forEach((a) => { + a.style.color = "#aef2f5"; + }); + }} +); + +anchorColors.forEach((a) => { + a.addEventListener("mousemove",(e)=>{ + a.style.fontSize="20px"; + a.style.transition="all 0.3s ease" + }); + a.addEventListener("mouseleave",(e)=>{ + a.style.fontSize="16px"; + }); +}); + + + + + + + +let ground=document.querySelector("#ground"); +let fire=document.querySelector("#fire"); +let grass=document.querySelector("#grass"); +let water=document.querySelector("#water"); +let flying=document.querySelector("#flying"); + +ground.addEventListener("click",(e) =>{ + userchoice="ground"; + console.log(`Your Choice ${userchoice}`); + computerPlay(); + result(userchoice,randchoice); +}) +fire.addEventListener("click",(e) =>{ + userchoice="fire"; + console.log(`Your Choice ${userchoice}`); + computerPlay(); + result(userchoice,randchoice); +}) +grass.addEventListener("click",(e) =>{ + userchoice="grass"; + console.log(`Your Choice ${userchoice}`); + computerPlay(); + result(userchoice,randchoice); +}) +water.addEventListener("click",(e) =>{ + userchoice="water"; + console.log(`Your Choice ${userchoice}`); + randchoice=computerPlay(); + result(userchoice,randchoice); +}) + +flying.addEventListener("click",(e) =>{ + userchoice="flying"; + + console.log(`Your Choice ${userchoice}`); + randchoice=computerPlay(); + result(userchoice,randchoice); +}) + +let computerchoice = ["ground", "fire", "grass", "water", "flying"]; + +function computerPlay() { + randchoice= computerchoice[Math.floor(Math.random()*computerchoice.length)]; +console.log(`Computer Choice ${randchoice}`); +return randchoice; +} + +function result(userchoice,randchoice){ +if(userchoice===randchoice){ + console.log("It's a tie!"); + draw(); + choice(); +} +else{ + if( (userchoice==="ground") &&(randchoice==="flying" ||randchoice==="fire")){ + console.log("You win!"); + win(); + choice(); + } + else if( (userchoice==="ground") &&(randchoice==="grass" ||randchoice==="water")){ + console.log("You Lost!"); + lost(); + choice(); + } + else if( (userchoice==="flying") &&(randchoice==="water" ||randchoice==="grass")){ + console.log("You win!"); + win(); + choice(); + } + else if( (userchoice==="flying") &&(randchoice==="fire" ||randchoice==="ground")){ + console.log("You Lost!"); + lost(); + choice(); + } + else if( (userchoice==="water") &&(randchoice==="fire" ||randchoice==="ground")){ + console.log("You win!"); + win(); + choice(); + } + else if( (userchoice==="water") &&(randchoice==="grass" ||randchoice==="flying")){ + console.log("You Lost!"); + lost(); + choice(); + } + else if( (userchoice==="fire") &&(randchoice==="flying" ||randchoice==="grass")){ + console.log("You win!"); + win(); + choice(); + } + else if( (userchoice==="fire") &&(randchoice==="water" ||randchoice==="ground")){ + console.log("You Lost!"); + lost(); + choice(); + } + else if( (userchoice==="grass") &&(randchoice==="water" ||randchoice==="ground")){ + console.log("You win!"); + win(); + choice(); + } + else if( (userchoice==="grass") &&(randchoice==="fire" ||randchoice==="flying")){ + console.log("You Lost!"); + lost(); + choice(); + } + } +} + + + + +let aboutmeFrame = document.querySelector("#aboutmeFrame"); +let scaledFrameNode = document.querySelector("#scaledFrame"); +scaledFrameNode.classList.add("removeFrame"); +let frame=0; + +aboutmeFrame.addEventListener("click", (e) => { + if (frame === 0){ + scaledFrameNode.classList.remove("removeFrame"); + frame=1; + } + else{ + scaledFrameNode.classList.add("removeFrame"); + frame=0; + } +}); + + +let anchorFontAwesome=document.querySelectorAll("a"); + +anchorFontAwesome.forEach((a) => { + a.addEventListener("mouseenter",(e)=>{ + console.log("Mouse over anchor element:", a.textContent); + a.style.display="inline"; + let icon = document.createElement("icon"); + icon.innerHTML = ' '; + a.append(icon); + icon.style.fontSize="16px"; + icon.style.transition="all 1s ease"; + }); + a.addEventListener("mouseleave",(e)=>{ + let icon = a.querySelector("icon"); + if (icon) { + a.removeChild(icon); + } + }); +}); + + + + + + + + + + + + + + + + + diff --git a/Pokemon-Mini-Game/style.css b/Pokemon-Mini-Game/style.css new file mode 100644 index 000000000..b4328b9bc --- /dev/null +++ b/Pokemon-Mini-Game/style.css @@ -0,0 +1,288 @@ +*{ + margin: 0; + padding: 0; +} + +.loader{ + height:100%; + width:100%; + position: fixed; + z-index: 100; + background-color: #27313d; +} +.forLoadingBody{ + height:100vh; + width:100vw; + position: fixed; + z-index: 99; + background-color: #27313d; + margin:0px; +} +.removeLoader{ + visibility: hidden; +} + +body{ + transition: all 1s ease; +} +div{ + margin-left: 20px; + margin-right: 10px; + margin-bottom: 10px; +} +#result{ + margin:0; +} + +#welcome{ + text-align: center; +} + +#wel{ + color:black; + display: inline-block; + text-align: center; + padding: 40px; + padding-top: 10px; + padding-bottom: 35px; + border: 1px solid black; +background-image: url("pokemon-icon-vector-free.jpg"); + background-size: cover; + border-radius: 20px; +margin-top: 10px; + position:relative; +transition: all .5s ease; +} +#wel:hover{ + text-shadow: 0 0 4px cyan,0 0 3px cyan,0 0 2px cyan,0 0 1px cyan; +} + +#mode{ + padding: 10px; + border-radius: 13px; + font-size: 20px; + position: fixed; + top:15px; + right:15px; +} +button:hover{ + background-color: gray; + color:whitesmoke; + transform: scale(1.08); + cursor:pointer; +} +button:focus { + background-color: #969695; + color:whitesmoke; + transform: scale(1.04); +} +button:active { + background-color: #5c5c5b; + color: white; + transform:scale(0.9); +} + + +.choices{ + display: flex; + /* position: relative; + top:100px; */ + justify-content: space-around; + gap:50px; +} +.choice{ + text-align: center; + font-size: 20px; + font-weight: bold; + +} +.choice img{ + height: 150px; + width:150px; +border-radius: 50%; + transition: all 0.1s ease; +} + + .choice :hover{ + cursor: pointer; + transform: scale(1.1); + filter:brightness(110%); + + } + +.choices:has(img:hover) img:not(:hover){ + opacity: 50%; + transform: scale(0.9); + filter:grayscale(5%); +} + +.choices:has(img:active) img:not(:active){ + opacity: 80%; + transform: scale(0.9); + filter:blur(1px); +} +.choice :active{ + cursor: pointer; + transform: scale(0.85); + filter:brightness(110%) sepia(10%) contrast(10px); + transition: all 0.1s ease; +} +#ground :hover{ + box-shadow: 0px 0px 25px 10px #795548; + +} +#water :hover{ + box-shadow: 0px 0px 25px 5px #03a9f4; +} +#fire :hover{ + box-shadow: 0px 0px 25px 5px#ff9800; +} +#grass :hover{ + box-shadow: 0px 0px 25px 5px #8bc34a; +} +#flying :hover{ + box-shadow: 0px 0px 25px 10px #607d8b; +} + + + + + +#fight{ + display: block; + text-align: center; +padding:10px; + border: 1px solid black; + background-color: #808080; + } +#result{ + display: flex; + font-size: 25px; + font-weight: bold; + padding: 20px; + justify-content: center; + gap:50px; + text-align: center; +border: 1px solid black; + margin-top: 20px; + background-color: #808080; +} + +#choose{ + display: block; + text-align: center; + padding:10px; + border: 1px solid black; + background-color: #808080; + margin-bottom: 20px; + font-size: 25px;; +} + +#rules,#choose_your_pokemon{ + margin-left: 5px; +} +#rules p{ + font-family: 'Times New Roman', Times, serif; +} + +#aboutMe{ +margin: 10px; + text-align: center; + font-weight: bold; + padding: 10px; +} + + #aboutMe a{ + color:#aef2f5; + text-decoration: dotted underline; + + text-decoration-color: #e34646; + text-decoration-thickness: 3px;; +} + #aboutMe p{ +margin: 20px; +} + + + + +#scaledFrame{ + height: 600px; + width: 599px; + border: 1px solid black; + background-color: #808080; +} + +@media(max-width:600px){ + body{ + width:600px; + } + + .choice :active{ + opacity: 100%; + cursor: pointer; + transform: scale(0.9); + } + .choice :hover{ + opacity: 100%; + cursor: pointer; + transform: scale(1); + } + +.choices{ + display: flex; + justify-content: space-around; + gap:50px; + flex-wrap: wrap; +} + #rules{ + font-size: 16px; + } +} + +@media(min-width:600px)and (max-width:1000px){ + body{ + width:900px; + } + .choices{ + display: flex; + justify-content: space-around; + gap:50px; + flex-wrap: wrap; +} + .choice :active{ + opacity: 100%; + cursor: pointer; + transform: scale(0.9); + } + .choice :hover{ + opacity: 100%; + cursor: pointer; + transform: scale(1); + } + +} +/* @media(min-width:900px)and (max-width:1024px){ +body{ + width:1024px; +} +} */ + + + +.removeFrame{ + display:none; +} + + + +body ::selection{ + background-color:#7f98b5; +} +body :not(a)::selection{ + color: #b52a48; +} +.choices ::selection, button::selection, br::selection, #mode ::selection, a::selection, iframe::selection{ + background-color: transparent; +} + diff --git a/Pokemon-Mini-Game/water-removebg-preview (1).png b/Pokemon-Mini-Game/water-removebg-preview (1).png new file mode 100644 index 000000000..6ec19ac15 Binary files /dev/null and b/Pokemon-Mini-Game/water-removebg-preview (1).png differ diff --git a/Pure Html Game/index.html b/Pure Html Game/index.html new file mode 100644 index 000000000..fd453eec6 --- /dev/null +++ b/Pure Html Game/index.html @@ -0,0 +1,72 @@ + + + + + + + Document + + + + + +
+

+ + HELP US TO CAPTURE THE + + + IMPOSTER AIRCRAFT + +

+

+ + There is an impostor aircraft in our squad. We need your help to capture them. + +

+

+ + YOU HAVE JUST ONE SINGLE CHANCE + +

+
+ + + Nave + + + + + Nave + + + + + Nave + + + + + Nave + + + + + Nave + + + + + Nave + + + + + + diff --git a/README.md b/README.md index eeaf5b807..cc4a9a7e3 100644 --- a/README.md +++ b/README.md @@ -327,3 +327,8 @@ Thank you for exploring this project! We’d love to connect and hear from you. [![Email](https://img.shields.io/badge/Email-D14836?style=for-the-badge&logo=gmail&logoColor=white)](mailto:iamrahulmhto@gmail.com) [![GitHub](https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/iamrahulmahato) +
+ + Back to Top + +
\ No newline at end of file diff --git a/Real Estate visualizer/Readme.md b/Real Estate visualizer/Readme.md new file mode 100644 index 000000000..ccdb8092f --- /dev/null +++ b/Real Estate visualizer/Readme.md @@ -0,0 +1,53 @@ +# Boston Housing Market Analysis Dashboard + +This is an interactive web dashboard built with [Dash](https://dash.plotly.com/), which visualizes data from the Boston Housing Market dataset. The application provides several graphs to analyze the housing data, including the distribution of home values, the average home value based on the number of rooms, and a scatter plot showing the relationship between home value and tax rate. + +## Features + +- **Select Highway Access Index (RAD)**: Allows users to filter data based on the RAD value, which indicates the accessibility of radial highways. +- **Graphs**: + - **Median Home Value Distribution**: A histogram showing the distribution of the median home values (MEDV). + - **Average Home Value by Room Count**: A bar chart displaying the average home value for each room count. + - **Home Value vs. Tax Rate**: A scatter plot showing the relationship between home value (MEDV) and the tax rate (TAX), with color coding based on the RAD index. + +## Prerequisites + +Make sure you have the following installed: +- Python 3.x +- Dash (`dash`, `dash_core_components`, `dash_html_components`) +- Plotly (`plotly`) +- Pandas (`pandas`) + +You can install these dependencies using pip: + +```bash +pip install dash plotly pandas +Dataset +This dashboard uses the Boston Housing dataset (data.csv). The dataset should contain the following columns: + +RM: Average number of rooms per dwelling. +RAD: Index of accessibility to radial highways. +MEDV: Median value of owner-occupied homes in $1000s. +TAX: Property tax rate. +Ensure that the data.csv file is located in the correct path for the application to load it correctly. + +How to Run +Clone this repository or download the project files to your local machine. +Make sure you have installed the necessary dependencies (dash, plotly, and pandas). +Update the dataset path in the visulaizer.py file to match the location of your data.csv. +Run the Dash application: +bash +Copy code +python visulaizer.py +Open your web browser and go to the address shown in the terminal (usually http://127.0.0.1:8050/). +App Layout +The app is composed of: + +A header (H1) with the title: "Boston Housing Market Analysis Dashboard". +A dropdown for selecting the RAD value to filter the data. +Three interactive graphs: +Median Home Value Distribution: A histogram of home values. +Average Home Value by Room Count: A bar chart showing average home values grouped by the number of rooms. +Home Value vs. Tax Rate: A scatter plot of home value vs. tax rate. +License +This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/Real Estate visualizer/data.csv b/Real Estate visualizer/data.csv new file mode 100644 index 000000000..e49db77a8 --- /dev/null +++ b/Real Estate visualizer/data.csv @@ -0,0 +1,512 @@ +CRIM,ZN,INDUS,CHAS,NOX,RM,AGE,DIS,RAD,TAX,PTRATIO,B,LSTAT,MEDV +0.00632,18,2.31,0,0.538,6.575,65.2,4.09,1,296,15.3,396.9,4.98,24 +0.02731,0,7.07,0,0.469,6.421,78.9,4.9671,2,242,17.8,396.9,9.14,21.6 +0.02729,0,7.07,0,0.469,7.185,61.1,4.9671,2,242,17.8,392.83,4.03,34.7 +0.03237,0,2.18,0,0.458,6.998,45.8,6.0622,3,222,18.7,394.63,2.94,33.4 +0.06905,0,2.18,0,0.458,7.147,54.2,6.0622,3,222,18.7,396.9,5.33,36.2 +0.02985,0,2.18,0,0.458,6.43,58.7,6.0622,3,222,18.7,394.12,5.21,28.7 +0.08829,12.5,7.87,0,0.524,6.012,66.6,5.5605,5,311,15.2,395.6,12.43,22.9 +0.14455,12.5,7.87,0,0.524,6.172,96.1,5.9505,5,311,15.2,396.9,19.15,27.1 +0.21124,12.5,7.87,0,0.524,5.631,100,6.0821,5,311,15.2,386.63,29.93,16.5 +0.17004,12.5,7.87,0,0.524,6.004,85.9,6.5921,5,311,15.2,386.71,17.1,18.9 +0.22489,12.5,7.87,0,0.524,,94.3,6.3467,5,311,15.2,392.52,20.45,15 +0.11747,12.5,7.87,0,0.524,6.009,82.9,6.2267,5,311,15.2,396.9,13.27,18.9 +0.09378,12.5,7.87,0,0.524,5.889,39,5.4509,5,311,15.2,390.5,15.71,21.7 +0.62976,0,8.14,0,0.538,5.949,61.8,4.7075,4,307,21,396.9,8.26,20.4 +0.63796,0,8.14,0,0.538,6.096,84.5,4.4619,4,307,21,380.02,10.26,18.2 +0.62739,0,8.14,0,0.538,5.834,56.5,4.4986,4,307,21,395.62,8.47,19.9 +1.05393,0,8.14,0,0.538,5.935,29.3,4.4986,4,307,21,386.85,6.58,23.1 +0.7842,0,8.14,0,0.538,5.99,81.7,4.2579,4,307,21,386.75,14.67,17.5 +0.80271,0,8.14,0,0.538,5.456,36.6,3.7965,4,307,21,288.99,11.69,20.2 +0.7258,0,8.14,0,0.538,5.727,69.5,3.7965,4,307,21,390.95,11.28,18.2 +1.25179,0,8.14,0,0.538,5.57,98.1,3.7979,4,307,21,376.57,21.02,13.6 +0.85204,0,8.14,0,0.538,5.965,89.2,4.0123,4,307,21,392.53,13.83,19.6 +1.23247,0,8.14,0,0.538,6.142,91.7,3.9769,4,307,21,396.9,18.72,15.2 +0.98843,0,8.14,0,0.538,5.813,100,4.0952,4,307,21,394.54,19.88,14.5 +0.75026,0,8.14,0,0.538,5.924,94.1,4.3996,4,307,21,394.33,16.3,15.6 +0.84054,0,8.14,0,0.538,5.599,85.7,4.4546,4,307,21,303.42,16.51,13.9 +0.67191,0,8.14,0,0.538,5.813,90.3,4.682,4,307,21,376.88,14.81,16.6 +0.95577,0,8.14,0,0.538,6.047,88.8,4.4534,4,307,21,306.38,17.28,14.8 +0.77299,0,8.14,0,0.538,6.495,94.4,4.4547,4,307,21,387.94,12.8,18.4 +1.00245,0,8.14,0,0.538,6.674,87.3,4.239,4,307,21,380.23,11.98,21 +1.13081,0,8.14,0,0.538,5.713,94.1,4.233,4,307,21,360.17,22.6,12.7 +1.35472,0,8.14,0,0.538,6.072,100,4.175,4,307,21,376.73,13.04,14.5 +1.38799,0,8.14,0,0.538,5.95,82,3.99,4,307,21,232.6,27.71,13.2 +1.15172,0,8.14,0,0.538,5.701,95,3.7872,4,307,21,358.77,18.35,13.1 +1.61282,0,8.14,0,0.538,6.096,96.9,3.7598,4,307,21,248.31,20.34,13.5 +0.06417,0,5.96,0,0.499,,68.2,3.3603,5,279,19.2,396.9,9.68,18.9 +0.09744,0,5.96,0,0.499,5.841,61.4,3.3779,5,279,19.2,377.56,11.41,20 +0.08014,0,5.96,0,0.499,5.85,41.5,3.9342,5,279,19.2,396.9,8.77,21 +0.17505,0,5.96,0,0.499,5.966,30.2,3.8473,5,279,19.2,393.43,10.13,24.7 +0.02763,75,2.95,0,0.428,6.595,21.8,5.4011,3,252,18.3,395.63,4.32,30.8 +0.03359,75,2.95,0,0.428,7.024,15.8,5.4011,3,252,18.3,395.62,1.98,34.9 +0.12744,0,6.91,0,0.448,6.77,2.9,5.7209,3,233,17.9,385.41,4.84,26.6 +0.1415,0,6.91,0,0.448,6.169,6.6,5.7209,3,233,17.9,383.37,5.81,25.3 +0.15936,0,6.91,0,0.448,6.211,6.5,5.7209,3,233,17.9,394.46,7.44,24.7 +0.12269,0,6.91,0,0.448,6.069,40,5.7209,3,233,17.9,389.39,9.55,21.2 +0.17142,0,6.91,0,0.448,5.682,33.8,5.1004,3,233,17.9,396.9,10.21,19.3 +0.18836,0,6.91,0,0.448,5.786,33.3,5.1004,3,233,17.9,396.9,14.15,20 +0.22927,0,6.91,0,0.448,6.03,85.5,5.6894,3,233,17.9,392.74,18.8,16.6 +0.25387,0,6.91,0,0.448,5.399,95.3,5.87,3,233,17.9,396.9,30.81,14.4 +0.21977,0,6.91,0,0.448,5.602,62,6.0877,3,233,17.9,396.9,16.2,19.4 +0.08873,21,5.64,0,0.439,5.963,45.7,6.8147,4,243,16.8,395.56,13.45,19.7 +0.04337,21,5.64,0,0.439,6.115,63,6.8147,4,243,16.8,393.97,9.43,20.5 +0.0536,21,5.64,0,0.439,6.511,21.1,6.8147,4,243,16.8,396.9,5.28,25 +0.04981,21,5.64,0,0.439,5.998,21.4,6.8147,4,243,16.8,396.9,8.43,23.4 +0.0136,75,4,0,0.41,5.888,47.6,7.3197,3,469,21.1,396.9,14.8,18.9 +0.01311,90,1.22,0,0.403,7.249,21.9,8.6966,5,226,17.9,395.93,4.81,35.4 +0.02055,85,0.74,0,0.41,6.383,35.7,9.1876,2,313,17.3,396.9,5.77,24.7 +0.01432,100,1.32,0,0.411,6.816,40.5,8.3248,5,256,15.1,392.9,3.95,31.6 +0.15445,25,5.13,0,0.453,6.145,29.2,7.8148,8,284,19.7,390.68,6.86,23.3 +0.10328,25,5.13,0,0.453,5.927,47.2,6.932,8,284,19.7,396.9,9.22,19.6 +0.14932,25,5.13,0,0.453,5.741,66.2,7.2254,8,284,19.7,395.11,13.15,18.7 +0.17171,25,5.13,0,0.453,5.966,93.4,6.8185,8,284,19.7,378.08,14.44,16 +0.11027,25,5.13,0,0.453,6.456,67.8,7.2255,8,284,19.7,396.9,6.73,22.2 +0.1265,25,5.13,0,0.453,,43.4,7.9809,8,284,19.7,395.58,9.5,25 +0.01951,17.5,1.38,0,0.4161,7.104,59.5,9.2229,3,216,18.6,393.24,8.05,33 +0.03584,80,3.37,0,0.398,6.29,17.8,6.6115,4,337,16.1,396.9,4.67,23.5 +0.04379,80,3.37,0,0.398,5.787,31.1,6.6115,4,337,16.1,396.9,10.24,19.4 +0.05789,12.5,6.07,0,0.409,5.878,21.4,6.498,4,345,18.9,396.21,8.1,22 +0.13554,12.5,6.07,0,0.409,5.594,36.8,6.498,4,345,18.9,396.9,13.09,17.4 +0.12816,12.5,6.07,0,0.409,5.885,33,6.498,4,345,18.9,396.9,8.79,20.9 +0.08826,0,10.81,0,0.413,6.417,6.6,5.2873,4,305,19.2,383.73,6.72,24.2 +0.15876,0,10.81,0,0.413,5.961,17.5,5.2873,4,305,19.2,376.94,9.88,21.7 +0.09164,0,10.81,0,0.413,6.065,7.8,5.2873,4,305,19.2,390.91,5.52,22.8 +0.19539,0,10.81,0,0.413,6.245,6.2,5.2873,4,305,19.2,377.17,7.54,23.4 +0.07896,0,12.83,0,0.437,6.273,6,4.2515,5,398,18.7,394.92,6.78,24.1 +0.09512,0,12.83,0,0.437,6.286,45,4.5026,5,398,18.7,383.23,8.94,21.4 +0.10153,0,12.83,0,0.437,6.279,74.5,4.0522,5,398,18.7,373.66,11.97,20 +0.08707,0,12.83,0,0.437,6.14,45.8,4.0905,5,398,18.7,386.96,10.27,20.8 +0.05646,0,12.83,0,0.437,6.232,53.7,5.0141,5,398,18.7,386.4,12.34,21.2 +0.08387,0,12.83,0,0.437,5.874,36.6,4.5026,5,398,18.7,396.06,9.1,20.3 +0.04113,25,4.86,0,0.426,6.727,33.5,5.4007,4,281,19,396.9,5.29,28 +0.04462,25,4.86,0,0.426,6.619,70.4,5.4007,4,281,19,395.63,7.22,23.9 +0.03659,25,4.86,0,0.426,6.302,32.2,5.4007,4,281,19,396.9,6.72,24.8 +0.03551,25,4.86,0,0.426,6.167,46.7,5.4007,4,281,19,390.64,7.51,22.9 +0.05059,0,4.49,0,0.449,6.389,48,4.7794,3,247,18.5,396.9,9.62,23.9 +0.05735,0,4.49,0,0.449,6.63,56.1,4.4377,3,247,18.5,392.3,6.53,26.6 +0.05188,0,4.49,0,0.449,6.015,45.1,4.4272,3,247,18.5,395.99,12.86,22.5 +0.07151,0,4.49,0,0.449,6.121,56.8,3.7476,3,247,18.5,395.15,8.44,22.2 +0.0566,0,3.41,0,0.489,7.007,86.3,3.4217,2,270,17.8,396.9,5.5,23.6 +0.05302,0,3.41,0,0.489,7.079,63.1,3.4145,2,270,17.8,396.06,5.7,28.7 +0.04684,0,3.41,0,0.489,6.417,66.1,3.0923,2,270,17.8,392.18,8.81,22.6 +0.03932,0,3.41,0,0.489,6.405,73.9,3.0921,2,270,17.8,393.55,8.2,22 +0.04203,28,15.04,0,0.464,6.442,53.6,3.6659,4,270,18.2,395.01,8.16,22.9 +0.02875,28,15.04,0,0.464,6.211,28.9,3.6659,4,270,18.2,396.33,6.21,25 +0.04294,28,15.04,0,0.464,6.249,77.3,3.615,4,270,18.2,396.9,10.59,20.6 +0.12204,0,2.89,0,0.445,6.625,57.8,3.4952,2,276,18,357.98,6.65,28.4 +0.11504,0,2.89,0,0.445,,69.6,3.4952,2,276,18,391.83,11.34,21.4 +0.12083,0,2.89,0,0.445,8.069,76,3.4952,2,276,18,396.9,4.21,38.7 +0.08187,0,2.89,0,0.445,7.82,36.9,3.4952,2,276,18,393.53,3.57,43.8 +0.0686,0,2.89,0,0.445,7.416,62.5,3.4952,2,276,18,396.9,6.19,33.2 +0.14866,0,8.56,0,0.52,6.727,79.9,2.7778,5,384,20.9,394.76,9.42,27.5 +0.11432,0,8.56,0,0.52,6.781,71.3,2.8561,5,384,20.9,395.58,7.67,26.5 +0.22876,0,8.56,0,0.52,6.405,85.4,2.7147,5,384,20.9,70.8,10.63,18.6 +0.21161,0,8.56,0,0.52,6.137,87.4,2.7147,5,384,20.9,394.47,13.44,19.3 +0.1396,0,8.56,0,0.52,6.167,90,2.421,5,384,20.9,392.69,12.33,20.1 +0.13262,0,8.56,0,0.52,5.851,96.7,2.1069,5,384,20.9,394.05,16.47,19.5 +0.1712,0,8.56,0,0.52,5.836,91.9,2.211,5,384,20.9,395.67,18.66,19.5 +0.13117,0,8.56,0,0.52,6.127,85.2,2.1224,5,384,20.9,387.69,14.09,20.4 +0.12802,0,8.56,0,0.52,6.474,97.1,2.4329,5,384,20.9,395.24,12.27,19.8 +0.26363,0,8.56,0,0.52,6.229,91.2,2.5451,5,384,20.9,391.23,15.55,19.4 +0.10793,0,8.56,0,0.52,6.195,54.4,2.7778,5,384,20.9,393.49,13,21.7 +0.10084,0,10.01,0,0.547,6.715,81.6,2.6775,6,432,17.8,395.59,10.16,22.8 +0.12329,0,10.01,0,0.547,5.913,92.9,2.3534,6,432,17.8,394.95,16.21,18.8 +0.22212,0,10.01,0,0.547,6.092,95.4,2.548,6,432,17.8,396.9,17.09,18.7 +0.14231,0,10.01,0,0.547,6.254,84.2,2.2565,6,432,17.8,388.74,10.45,18.5 +0.17134,0,10.01,0,0.547,5.928,88.2,2.4631,6,432,17.8,344.91,15.76,18.3 +0.13158,0,10.01,0,0.547,6.176,72.5,2.7301,6,432,17.8,393.3,12.04,21.2 +0.15098,0,10.01,0,0.547,6.021,82.6,2.7474,6,432,17.8,394.51,10.3,19.2 +0.13058,0,10.01,0,0.547,5.872,73.1,2.4775,6,432,17.8,338.63,15.37,20.4 +0.14476,0,10.01,0,0.547,5.731,65.2,2.7592,6,432,17.8,391.5,13.61,19.3 +0.06899,0,25.65,0,0.581,5.87,69.7,2.2577,2,188,19.1,389.15,14.37,22 +0.07165,0,25.65,0,0.581,6.004,84.1,2.1974,2,188,19.1,377.67,14.27,20.3 +0.09299,0,25.65,0,0.581,5.961,92.9,2.0869,2,188,19.1,378.09,17.93,20.5 +0.15038,0,25.65,0,0.581,5.856,97,1.9444,2,188,19.1,370.31,25.41,17.3 +0.09849,0,25.65,0,0.581,5.879,95.8,2.0063,2,188,19.1,379.38,17.58,18.8 +0.16902,0,25.65,0,0.581,5.986,88.4,1.9929,2,188,19.1,385.02,14.81,21.4 +0.38735,0,25.65,0,0.581,5.613,95.6,1.7572,2,188,19.1,359.29,27.26,15.7 +0.25915,0,21.89,0,0.624,5.693,96,1.7883,4,437,21.2,392.11,17.19,16.2 +0.32543,0,21.89,0,0.624,6.431,98.8,1.8125,4,437,21.2,396.9,15.39,18 +0.88125,0,21.89,0,0.624,5.637,94.7,1.9799,4,437,21.2,396.9,18.34,14.3 +0.34006,0,21.89,0,0.624,6.458,98.9,2.1185,4,437,21.2,395.04,12.6,19.2 +1.19294,0,21.89,0,0.624,6.326,97.7,2.271,4,437,21.2,396.9,12.26,19.6 +0.59005,0,21.89,0,0.624,6.372,97.9,2.3274,4,437,21.2,385.76,11.12,23 +0.32982,0,21.89,0,0.624,5.822,95.4,2.4699,4,437,21.2,388.69,15.03,18.4 +0.97617,0,21.89,0,0.624,5.757,98.4,2.346,4,437,21.2,262.76,17.31,15.6 +0.55778,0,21.89,0,0.624,,98.2,2.1107,4,437,21.2,394.67,16.96,18.1 +0.32264,0,21.89,0,0.624,5.942,93.5,1.9669,4,437,21.2,378.25,16.9,17.4 +0.35233,0,21.89,0,0.624,6.454,98.4,1.8498,4,437,21.2,394.08,14.59,17.1 +0.2498,0,21.89,0,0.624,5.857,98.2,1.6686,4,437,21.2,392.04,21.32,13.3 +0.54452,0,21.89,0,0.624,6.151,97.9,1.6687,4,437,21.2,396.9,18.46,17.8 +0.2909,0,21.89,0,0.624,6.174,93.6,1.6119,4,437,21.2,388.08,24.16,14 +1.62864,0,21.89,0,0.624,5.019,100,1.4394,4,437,21.2,396.9,34.41,14.4 +3.32105,0,19.58,1,0.871,5.403,100,1.3216,5,403,14.7,396.9,26.82,13.4 +4.0974,0,19.58,0,0.871,5.468,100,1.4118,5,403,14.7,396.9,26.42,15.6 +2.77974,0,19.58,0,0.871,4.903,97.8,1.3459,5,403,14.7,396.9,29.29,11.8 +2.37934,0,19.58,0,0.871,6.13,100,1.4191,5,403,14.7,172.91,27.8,13.8 +2.15505,0,19.58,0,0.871,5.628,100,1.5166,5,403,14.7,169.27,16.65,15.6 +2.36862,0,19.58,0,0.871,4.926,95.7,1.4608,5,403,14.7,391.71,29.53,14.6 +2.33099,0,19.58,0,0.871,5.186,93.8,1.5296,5,403,14.7,356.99,28.32,17.8 +2.73397,0,19.58,0,0.871,5.597,94.9,1.5257,5,403,14.7,351.85,21.45,15.4 +1.6566,0,19.58,0,0.871,6.122,97.3,1.618,5,403,14.7,372.8,14.1,21.5 +1.49632,0,19.58,0,0.871,5.404,100,1.5916,5,403,14.7,341.6,13.28,19.6 +1.12658,0,19.58,1,0.871,5.012,88,1.6102,5,403,14.7,343.28,12.12,15.3 +2.14918,0,19.58,0,0.871,5.709,98.5,1.6232,5,403,14.7,261.95,15.79,19.4 +1.41385,0,19.58,1,0.871,6.129,96,1.7494,5,403,14.7,321.02,15.12,17 +3.53501,0,19.58,1,0.871,6.152,82.6,1.7455,5,403,14.7,88.01,15.02,15.6 +2.44668,0,19.58,0,0.871,5.272,94,1.7364,5,403,14.7,88.63,16.14,13.1 +1.22358,0,19.58,0,0.605,6.943,97.4,1.8773,5,403,14.7,363.43,4.59,41.3 +1.34284,0,19.58,0,0.605,6.066,100,1.7573,5,403,14.7,353.89,6.43,24.3 +1.42502,0,19.58,0,0.871,6.51,100,1.7659,5,403,14.7,364.31,7.39,23.3 +1.27346,0,19.58,1,0.605,6.25,92.6,1.7984,5,403,14.7,338.92,5.5,27 +1.46336,0,19.58,0,0.605,7.489,90.8,1.9709,5,403,14.7,374.43,1.73,50 +1.83377,0,19.58,1,0.605,7.802,98.2,2.0407,5,403,14.7,389.61,1.92,50 +1.51902,0,19.58,1,0.605,8.375,93.9,2.162,5,403,14.7,388.45,3.32,50 +2.24236,0,19.58,0,0.605,5.854,91.8,2.422,5,403,14.7,395.11,11.64,22.7 +2.924,0,19.58,0,0.605,6.101,93,2.2834,5,403,14.7,240.16,9.81,25 +2.01019,0,19.58,0,0.605,7.929,96.2,2.0459,5,403,14.7,369.3,3.7,50 +1.80028,0,19.58,0,0.605,5.877,79.2,2.4259,5,403,14.7,227.61,12.14,23.8 +2.3004,0,19.58,0,0.605,6.319,96.1,2.1,5,403,14.7,297.09,11.1,23.8 +2.44953,0,19.58,0,0.605,6.402,95.2,2.2625,5,403,14.7,330.04,11.32,22.3 +1.20742,0,19.58,0,0.605,5.875,94.6,2.4259,5,403,14.7,292.29,14.43,17.4 +2.3139,0,19.58,0,0.605,5.88,97.3,2.3887,5,403,14.7,348.13,12.03,19.1 +0.13914,0,4.05,0,0.51,5.572,88.5,2.5961,5,296,16.6,396.9,14.69,23.1 +0.09178,0,4.05,0,0.51,6.416,84.1,2.6463,5,296,16.6,395.5,9.04,23.6 +0.08447,0,4.05,0,0.51,5.859,68.7,2.7019,5,296,16.6,393.23,9.64,22.6 +0.06664,0,4.05,0,0.51,6.546,33.1,3.1323,5,296,16.6,390.96,5.33,29.4 +0.07022,0,4.05,0,0.51,6.02,47.2,3.5549,5,296,16.6,393.23,10.11,23.2 +0.05425,0,4.05,0,0.51,6.315,73.4,3.3175,5,296,16.6,395.6,6.29,24.6 +0.06642,0,4.05,0,0.51,6.86,74.4,2.9153,5,296,16.6,391.27,6.92,29.9 +0.0578,0,2.46,0,0.488,6.98,58.4,2.829,3,193,17.8,396.9,5.04,37.2 +0.06588,0,2.46,0,0.488,7.765,83.3,2.741,3,193,17.8,395.56,7.56,39.8 +0.06888,0,2.46,0,0.488,6.144,62.2,2.5979,3,193,17.8,396.9,9.45,36.2 +0.09103,0,2.46,0,0.488,7.155,92.2,2.7006,3,193,17.8,394.12,4.82,37.9 +0.10008,0,2.46,0,0.488,6.563,95.6,2.847,3,193,17.8,396.9,5.68,32.5 +0.08308,0,2.46,0,0.488,5.604,89.8,2.9879,3,193,17.8,391,13.98,26.4 +0.06047,0,2.46,0,0.488,6.153,68.8,3.2797,3,193,17.8,387.11,13.15,29.6 +0.05602,0,2.46,0,0.488,7.831,53.6,3.1992,3,193,17.8,392.63,4.45,50 +0.07875,45,3.44,0,0.437,6.782,41.1,3.7886,5,398,15.2,393.87,6.68,32 +0.12579,45,3.44,0,0.437,6.556,29.1,4.5667,5,398,15.2,382.84,4.56,29.8 +0.0837,45,3.44,0,0.437,7.185,38.9,4.5667,5,398,15.2,396.9,5.39,34.9 +0.09068,45,3.44,0,0.437,6.951,21.5,6.4798,5,398,15.2,377.68,5.1,37 +0.06911,45,3.44,0,0.437,6.739,30.8,6.4798,5,398,15.2,389.71,4.69,30.5 +0.08664,45,3.44,0,0.437,7.178,26.3,6.4798,5,398,15.2,390.49,2.87,36.4 +0.02187,60,2.93,0,0.401,6.8,9.9,6.2196,1,265,15.6,393.37,5.03,31.1 +0.01439,60,2.93,0,0.401,6.604,18.8,6.2196,1,265,15.6,376.7,4.38,29.1 +0.01381,80,0.46,0,0.422,7.875,32,5.6484,4,255,14.4,394.23,2.97,50 +0.04011,80,1.52,0,0.404,7.287,34.1,7.309,2,329,12.6,396.9,4.08,33.3 +0.04666,80,1.52,0,0.404,7.107,36.6,7.309,2,329,12.6,354.31,8.61,30.3 +0.03768,80,1.52,0,0.404,7.274,38.3,7.309,2,329,12.6,392.2,6.62,34.6 +0.0315,95,1.47,0,0.403,6.975,15.3,7.6534,3,402,17,396.9,4.56,34.9 +0.01778,95,1.47,0,0.403,7.135,13.9,7.6534,3,402,17,384.3,4.45,32.9 +0.03445,82.5,2.03,0,0.415,6.162,38.4,6.27,2,348,14.7,393.77,7.43,24.1 +0.02177,82.5,2.03,0,0.415,7.61,15.7,6.27,2,348,14.7,395.38,3.11,42.3 +0.0351,95,2.68,0,0.4161,7.853,33.2,5.118,4,224,14.7,392.78,3.81,48.5 +0.02009,95,2.68,0,0.4161,8.034,31.9,5.118,4,224,14.7,390.55,2.88,50 +0.13642,0,10.59,0,0.489,5.891,22.3,3.9454,4,277,18.6,396.9,10.87,22.6 +0.22969,0,10.59,0,0.489,6.326,52.5,4.3549,4,277,18.6,394.87,10.97,24.4 +0.25199,0,10.59,0,0.489,5.783,72.7,4.3549,4,277,18.6,389.43,18.06,22.5 +0.13587,0,10.59,1,0.489,6.064,59.1,4.2392,4,277,18.6,381.32,14.66,24.4 +0.43571,0,10.59,1,0.489,5.344,100,3.875,4,277,18.6,396.9,23.09,20 +0.17446,0,10.59,1,0.489,5.96,92.1,3.8771,4,277,18.6,393.25,17.27,21.7 +0.37578,0,10.59,1,0.489,5.404,88.6,3.665,4,277,18.6,395.24,23.98,19.3 +0.21719,0,10.59,1,0.489,5.807,53.8,3.6526,4,277,18.6,390.94,16.03,22.4 +0.14052,0,10.59,0,0.489,6.375,32.3,3.9454,4,277,18.6,385.81,9.38,28.1 +0.28955,0,10.59,0,0.489,5.412,9.8,3.5875,4,277,18.6,348.93,29.55,23.7 +0.19802,0,10.59,0,0.489,6.182,42.4,3.9454,4,277,18.6,393.63,9.47,25 +0.0456,0,13.89,1,0.55,5.888,56,3.1121,5,276,16.4,392.8,13.51,23.3 +0.07013,0,13.89,0,0.55,6.642,85.1,3.4211,5,276,16.4,392.78,9.69,28.7 +0.11069,0,13.89,1,0.55,5.951,93.8,2.8893,5,276,16.4,396.9,17.92,21.5 +0.11425,0,13.89,1,0.55,6.373,92.4,3.3633,5,276,16.4,393.74,10.5,23 +0.35809,0,6.2,1,0.507,6.951,88.5,2.8617,8,307,17.4,391.7,9.71,26.7 +0.40771,0,6.2,1,0.507,6.164,91.3,3.048,8,307,17.4,395.24,21.46,21.7 +0.62356,0,6.2,1,0.507,6.879,77.7,3.2721,8,307,17.4,390.39,9.93,27.5 +0.6147,0,6.2,0,0.507,6.618,80.8,3.2721,8,307,17.4,396.9,7.6,30.1 +0.31533,0,6.2,0,0.504,8.266,78.3,2.8944,8,307,17.4,385.05,4.14,44.8 +0.52693,0,6.2,0,0.504,8.725,83,2.8944,8,307,17.4,382,4.63,50 +0.38214,0,6.2,0,0.504,8.04,86.5,3.2157,8,307,17.4,387.38,3.13,37.6 +0.41238,0,6.2,0,0.504,7.163,79.9,3.2157,8,307,17.4,372.08,6.36,31.6 +0.29819,0,6.2,0,0.504,7.686,17,3.3751,8,307,17.4,377.51,3.92,46.7 +0.44178,0,6.2,0,0.504,6.552,21.4,3.3751,8,307,17.4,380.34,3.76,31.5 +0.537,0,6.2,0,0.504,5.981,68.1,3.6715,8,307,17.4,378.35,11.65,24.3 +0.46296,0,6.2,0,0.504,7.412,76.9,3.6715,8,307,17.4,376.14,5.25,31.7 +0.57529,0,6.2,0,0.507,8.337,73.3,3.8384,8,307,17.4,385.91,2.47,41.7 +0.33147,0,6.2,0,0.507,8.247,70.4,3.6519,8,307,17.4,378.95,3.95,48.3 +0.44791,0,6.2,1,0.507,6.726,66.5,3.6519,8,307,17.4,360.2,8.05,29 +0.33045,0,6.2,0,0.507,6.086,61.5,3.6519,8,307,17.4,376.75,10.88,24 +0.52058,0,6.2,1,0.507,6.631,76.5,4.148,8,307,17.4,388.45,9.54,25.1 +0.51183,0,6.2,0,0.507,7.358,71.6,4.148,8,307,17.4,390.07,4.73,31.5 +0.08244,30,4.93,0,0.428,6.481,18.5,6.1899,6,300,16.6,379.41,6.36,23.7 +0.09252,30,4.93,0,0.428,6.606,42.2,6.1899,6,300,16.6,383.78,7.37,23.3 +0.11329,30,4.93,0,0.428,6.897,54.3,6.3361,6,300,16.6,391.25,11.38,22 +0.10612,30,4.93,0,0.428,6.095,65.1,6.3361,6,300,16.6,394.62,12.4,20.1 +0.1029,30,4.93,0,0.428,6.358,52.9,7.0355,6,300,16.6,372.75,11.22,22.2 +0.12757,30,4.93,0,0.428,6.393,7.8,7.0355,6,300,16.6,374.71,5.19,23.7 +0.20608,22,5.86,0,0.431,5.593,76.5,7.9549,7,330,19.1,372.49,12.5,17.6 +0.19133,22,5.86,0,0.431,5.605,70.2,7.9549,7,330,19.1,389.13,18.46,18.5 +0.33983,22,5.86,0,0.431,6.108,34.9,8.0555,7,330,19.1,390.18,9.16,24.3 +0.19657,22,5.86,0,0.431,6.226,79.2,8.0555,7,330,19.1,376.14,10.15,20.5 +0.16439,22,5.86,0,0.431,6.433,49.1,7.8265,7,330,19.1,374.71,9.52,24.5 +0.19073,22,5.86,0,0.431,6.718,17.5,7.8265,7,330,19.1,393.74,6.56,26.2 +0.1403,22,5.86,0,0.431,6.487,13,7.3967,7,330,19.1,396.28,5.9,24.4 +0.21409,22,5.86,0,0.431,6.438,8.9,7.3967,7,330,19.1,377.07,3.59,24.8 +0.08221,22,5.86,0,0.431,6.957,6.8,8.9067,7,330,19.1,386.09,3.53,29.6 +0.36894,22,5.86,0,0.431,8.259,8.4,8.9067,7,330,19.1,396.9,3.54,42.8 +0.04819,80,3.64,0,0.392,6.108,32,9.2203,1,315,16.4,392.89,6.57,21.9 +0.03548,80,3.64,0,0.392,5.876,19.1,9.2203,1,315,16.4,395.18,9.25,20.9 +0.01538,90,3.75,0,0.394,7.454,34.2,6.3361,3,244,15.9,386.34,3.11,44 +0.61154,20,3.97,0,0.647,8.704,86.9,1.801,5,264,13,389.7,5.12,50 +0.66351,20,3.97,0,0.647,7.333,100,1.8946,5,264,13,383.29,7.79,36 +0.65665,20,3.97,0,0.647,6.842,100,2.0107,5,264,13,391.93,6.9,30.1 +0.54011,20,3.97,0,0.647,7.203,81.8,2.1121,5,264,13,392.8,9.59,33.8 +0.53412,20,3.97,0,0.647,7.52,89.4,2.1398,5,264,13,388.37,7.26,43.1 +0.52014,20,3.97,0,0.647,8.398,91.5,2.2885,5,264,13,386.86,5.91,48.8 +0.82526,20,3.97,0,0.647,7.327,94.5,2.0788,5,264,13,393.42,11.25,31 +0.55007,20,3.97,0,0.647,7.206,91.6,1.9301,5,264,13,387.89,8.1,36.5 +0.76162,20,3.97,0,0.647,5.56,62.8,1.9865,5,264,13,392.4,10.45,22.8 +0.7857,20,3.97,0,0.647,7.014,84.6,2.1329,5,264,13,384.07,14.79,30.7 +0.57834,20,3.97,0,0.575,8.297,67,2.4216,5,264,13,384.54,7.44,50 +0.5405,20,3.97,0,0.575,7.47,52.6,2.872,5,264,13,390.3,3.16,43.5 +0.09065,20,6.96,1,0.464,5.92,61.5,3.9175,3,223,18.6,391.34,13.65,20.7 +0.29916,20,6.96,0,0.464,5.856,42.1,4.429,3,223,18.6,388.65,13,21.1 +0.16211,20,6.96,0,0.464,6.24,16.3,4.429,3,223,18.6,396.9,6.59,25.2 +0.1146,20,6.96,0,0.464,6.538,58.7,3.9175,3,223,18.6,394.96,7.73,24.4 +0.22188,20,6.96,1,0.464,7.691,51.8,4.3665,3,223,18.6,390.77,6.58,35.2 +0.05644,40,6.41,1,0.447,6.758,32.9,4.0776,4,254,17.6,396.9,3.53,32.4 +0.09604,40,6.41,0,0.447,6.854,42.8,4.2673,4,254,17.6,396.9,2.98,32 +0.10469,40,6.41,1,0.447,7.267,49,4.7872,4,254,17.6,389.25,6.05,33.2 +0.06127,40,6.41,1,0.447,6.826,27.6,4.8628,4,254,17.6,393.45,4.16,33.1 +0.07978,40,6.41,0,0.447,6.482,32.1,4.1403,4,254,17.6,396.9,7.19,29.1 +0.21038,20,3.33,0,0.4429,6.812,32.2,4.1007,5,216,14.9,396.9,4.85,35.1 +0.03578,20,3.33,0,0.4429,7.82,64.5,4.6947,5,216,14.9,387.31,3.76,45.4 +0.03705,20,3.33,0,0.4429,6.968,37.2,5.2447,5,216,14.9,392.23,4.59,35.4 +0.06129,20,3.33,1,0.4429,7.645,49.7,5.2119,5,216,14.9,377.07,3.01,46 +0.01501,90,1.21,1,0.401,7.923,24.8,5.885,1,198,13.6,395.52,3.16,50 +0.00906,90,2.97,0,0.4,7.088,20.8,7.3073,1,285,15.3,394.72,7.85,32.2 +0.01096,55,2.25,0,0.389,6.453,31.9,7.3073,1,300,15.3,394.72,8.23,22 +0.01965,80,1.76,0,0.385,6.23,31.5,9.0892,1,241,18.2,341.6,12.93,20.1 +0.03871,52.5,5.32,0,0.405,6.209,31.3,7.3172,6,293,16.6,396.9,7.14,23.2 +0.0459,52.5,5.32,0,0.405,6.315,45.6,7.3172,6,293,16.6,396.9,7.6,22.3 +0.04297,52.5,5.32,0,0.405,6.565,22.9,7.3172,6,293,16.6,371.72,9.51,24.8 +0.03502,80,4.95,0,0.411,6.861,27.9,5.1167,4,245,19.2,396.9,3.33,28.5 +0.07886,80,4.95,0,0.411,7.148,27.7,5.1167,4,245,19.2,396.9,3.56,37.3 +0.03615,80,4.95,0,0.411,6.63,23.4,5.1167,4,245,19.2,396.9,4.7,27.9 +0.08265,0,13.92,0,0.437,6.127,18.4,5.5027,4,289,16,396.9,8.58,23.9 +0.08199,0,13.92,0,0.437,6.009,42.3,5.5027,4,289,16,396.9,10.4,21.7 +0.12932,0,13.92,0,0.437,6.678,31.1,5.9604,4,289,16,396.9,6.27,28.6 +0.05372,0,13.92,0,0.437,6.549,51,5.9604,4,289,16,392.85,7.39,27.1 +0.14103,0,13.92,0,0.437,5.79,58,6.32,4,289,16,396.9,15.84,20.3 +0.06466,70,2.24,0,0.4,6.345,20.1,7.8278,5,358,14.8,368.24,4.97,22.5 +0.05561,70,2.24,0,0.4,7.041,10,7.8278,5,358,14.8,371.58,4.74,29 +0.04417,70,2.24,0,0.4,6.871,47.4,7.8278,5,358,14.8,390.86,6.07,24.8 +0.03537,34,6.09,0,0.433,6.59,40.4,5.4917,7,329,16.1,395.75,9.5,22 +0.09266,34,6.09,0,0.433,6.495,18.4,5.4917,7,329,16.1,383.61,8.67,26.4 +0.1,34,6.09,0,0.433,6.982,17.7,5.4917,7,329,16.1,390.43,4.86,33.1 +0.05515,33,2.18,0,0.472,7.236,41.1,4.022,7,222,18.4,393.68,6.93,36.1 +0.05479,33,2.18,0,0.472,6.616,58.1,3.37,7,222,18.4,393.36,8.93,28.4 +0.07503,33,2.18,0,0.472,7.42,71.9,3.0992,7,222,18.4,396.9,6.47,33.4 +0.04932,33,2.18,0,0.472,6.849,70.3,3.1827,7,222,18.4,396.9,7.53,28.2 +0.49298,0,9.9,0,0.544,6.635,82.5,3.3175,4,304,18.4,396.9,4.54,22.8 +0.3494,0,9.9,0,0.544,5.972,76.7,3.1025,4,304,18.4,396.24,9.97,20.3 +2.63548,0,9.9,0,0.544,4.973,37.8,2.5194,4,304,18.4,350.45,12.64,16.1 +0.79041,0,9.9,0,0.544,6.122,52.8,2.6403,4,304,18.4,396.9,5.98,22.1 +0.26169,0,9.9,0,0.544,6.023,90.4,2.834,4,304,18.4,396.3,11.72,19.4 +0.26938,0,9.9,0,0.544,6.266,82.8,3.2628,4,304,18.4,393.39,7.9,21.6 +0.3692,0,9.9,0,0.544,6.567,87.3,3.6023,4,304,18.4,395.69,9.28,23.8 +0.25356,0,9.9,0,0.544,5.705,77.7,3.945,4,304,18.4,396.42,11.5,16.2 +0.31827,0,9.9,0,0.544,5.914,83.2,3.9986,4,304,18.4,390.7,18.33,17.8 +0.24522,0,9.9,0,0.544,5.782,71.7,4.0317,4,304,18.4,396.9,15.94,19.8 +0.40202,0,9.9,0,0.544,6.382,67.2,3.5325,4,304,18.4,395.21,10.36,23.1 +0.47547,0,9.9,0,0.544,6.113,58.8,4.0019,4,304,18.4,396.23,12.73,21 +0.1676,0,7.38,0,0.493,6.426,52.3,4.5404,5,287,19.6,396.9,7.2,23.8 +0.18159,0,7.38,0,0.493,6.376,54.3,4.5404,5,287,19.6,396.9,6.87,23.1 +0.35114,0,7.38,0,0.493,6.041,49.9,4.7211,5,287,19.6,396.9,7.7,20.4 +0.28392,0,7.38,0,0.493,5.708,74.3,4.7211,5,287,19.6,391.13,11.74,18.5 +0.34109,0,7.38,0,0.493,6.415,40.1,4.7211,5,287,19.6,396.9,6.12,25 +0.19186,0,7.38,0,0.493,6.431,14.7,5.4159,5,287,19.6,393.68,5.08,24.6 +0.30347,0,7.38,0,0.493,6.312,28.9,5.4159,5,287,19.6,396.9,6.15,23 +0.24103,0,7.38,0,0.493,6.083,43.7,5.4159,5,287,19.6,396.9,12.79,22.2 +0.06617,0,3.24,0,0.46,5.868,25.8,5.2146,4,430,16.9,382.44,9.97,19.3 +0.06724,0,3.24,0,0.46,6.333,17.2,5.2146,4,430,16.9,375.21,7.34,22.6 +0.04544,0,3.24,0,0.46,6.144,32.2,5.8736,4,430,16.9,368.57,9.09,19.8 +0.05023,35,6.06,0,0.4379,5.706,28.4,6.6407,1,304,16.9,394.02,12.43,17.1 +0.03466,35,6.06,0,0.4379,6.031,23.3,6.6407,1,304,16.9,362.25,7.83,19.4 +0.05083,0,5.19,0,0.515,6.316,38.1,6.4584,5,224,20.2,389.71,5.68,22.2 +0.03738,0,5.19,0,0.515,6.31,38.5,6.4584,5,224,20.2,389.4,6.75,20.7 +0.03961,0,5.19,0,0.515,6.037,34.5,5.9853,5,224,20.2,396.9,8.01,21.1 +0.03427,0,5.19,0,0.515,5.869,46.3,5.2311,5,224,20.2,396.9,9.8,19.5 +0.03041,0,5.19,0,0.515,5.895,59.6,5.615,5,224,20.2,394.81,10.56,18.5 +0.03306,0,5.19,0,0.515,6.059,37.3,4.8122,5,224,20.2,396.14,8.51,20.6 +0.05497,0,5.19,0,0.515,5.985,45.4,4.8122,5,224,20.2,396.9,9.74,19 +0.06151,0,5.19,0,0.515,5.968,58.5,4.8122,5,224,20.2,396.9,9.29,18.7 +0.01301,35,1.52,0,0.442,7.241,49.3,7.0379,1,284,15.5,394.74,5.49,32.7 +0.02498,0,1.89,0,0.518,6.54,59.7,6.2669,1,422,15.9,389.96,8.65,16.5 +0.02543,55,3.78,0,0.484,6.696,56.4,5.7321,5,370,17.6,396.9,7.18,23.9 +0.03049,55,3.78,0,0.484,6.874,28.1,6.4654,5,370,17.6,387.97,4.61,31.2 +0.03113,0,4.39,0,0.442,6.014,48.5,8.0136,3,352,18.8,385.64,10.53,17.5 +0.06162,0,4.39,0,0.442,5.898,52.3,8.0136,3,352,18.8,364.61,12.67,17.2 +0.0187,85,4.15,0,0.429,6.516,27.7,8.5353,4,351,17.9,392.43,6.36,23.1 +0.01501,80,2.01,0,0.435,6.635,29.7,8.344,4,280,17,390.94,5.99,24.5 +0.02899,40,1.25,0,0.429,6.939,34.5,8.7921,1,335,19.7,389.85,5.89,26.6 +0.06211,40,1.25,0,0.429,6.49,44.4,8.7921,1,335,19.7,396.9,5.98,22.9 +0.0795,60,1.69,0,0.411,6.579,35.9,10.7103,4,411,18.3,370.78,5.49,24.1 +0.07244,60,1.69,0,0.411,5.884,18.5,10.7103,4,411,18.3,392.33,7.79,18.6 +0.01709,90,2.02,0,0.41,6.728,36.1,12.1265,5,187,17,384.46,4.5,30.1 +0.04301,80,1.91,0,0.413,5.663,21.9,10.5857,4,334,22,382.8,8.05,18.2 +0.10659,80,1.91,0,0.413,5.936,19.5,10.5857,4,334,22,376.04,5.57,20.6 +8.98296,0,18.1,1,0.77,6.212,97.4,2.1222,24,666,20.2,377.73,17.6,17.8 +3.8497,0,18.1,1,0.77,6.395,91,2.5052,24,666,20.2,391.34,13.27,21.7 +5.20177,0,18.1,1,0.77,6.127,83.4,2.7227,24,666,20.2,395.43,11.48,22.7 +4.26131,0,18.1,0,0.77,6.112,81.3,2.5091,24,666,20.2,390.74,12.67,22.6 +4.54192,0,18.1,0,0.77,6.398,88,2.5182,24,666,20.2,374.56,7.79,25 +3.83684,0,18.1,0,0.77,6.251,91.1,2.2955,24,666,20.2,350.65,14.19,19.9 +3.67822,0,18.1,0,0.77,5.362,96.2,2.1036,24,666,20.2,380.79,10.19,20.8 +4.22239,0,18.1,1,0.77,5.803,89,1.9047,24,666,20.2,353.04,14.64,16.8 +3.47428,0,18.1,1,0.718,8.78,82.9,1.9047,24,666,20.2,354.55,5.29,21.9 +4.55587,0,18.1,0,0.718,3.561,87.9,1.6132,24,666,20.2,354.7,7.12,27.5 +3.69695,0,18.1,0,0.718,4.963,91.4,1.7523,24,666,20.2,316.03,14,21.9 +13.5222,0,18.1,0,0.631,3.863,100,1.5106,24,666,20.2,131.42,13.33,23.1 +4.89822,0,18.1,0,0.631,4.97,100,1.3325,24,666,20.2,375.52,3.26,50 +5.66998,0,18.1,1,0.631,6.683,96.8,1.3567,24,666,20.2,375.33,3.73,50 +6.53876,0,18.1,1,0.631,7.016,97.5,1.2024,24,666,20.2,392.05,2.96,50 +9.2323,0,18.1,0,0.631,6.216,100,1.1691,24,666,20.2,366.15,9.53,50 +8.26725,0,18.1,1,0.668,5.875,89.6,1.1296,24,666,20.2,347.88,8.88,50 +11.1081,0,18.1,0,0.668,4.906,100,1.1742,24,666,20.2,396.9,34.77,13.8 +18.4982,0,18.1,0,0.668,4.138,100,1.137,24,666,20.2,396.9,37.97,13.8 +19.6091,0,18.1,0,0.671,7.313,97.9,1.3163,24,666,20.2,396.9,13.44,15 +15.288,0,18.1,0,0.671,6.649,93.3,1.3449,24,666,20.2,363.02,23.24,13.9 +9.82349,0,18.1,0,0.671,6.794,98.8,1.358,24,666,20.2,396.9,21.24,13.3 +23.6482,0,18.1,0,0.671,6.38,96.2,1.3861,24,666,20.2,396.9,23.69,13.1 +17.8667,0,18.1,0,0.671,6.223,100,1.3861,24,666,20.2,393.74,21.78,10.2 +88.9762,0,18.1,0,0.671,6.968,91.9,1.4165,24,666,20.2,396.9,17.21,10.4 +15.8744,0,18.1,0,0.671,6.545,99.1,1.5192,24,666,20.2,396.9,21.08,10.9 +9.18702,0,18.1,0,0.7,5.536,100,1.5804,24,666,20.2,396.9,23.6,11.3 +7.99248,0,18.1,0,0.7,5.52,100,1.5331,24,666,20.2,396.9,24.56,12.3 +20.0849,0,18.1,0,0.7,4.368,91.2,1.4395,24,666,20.2,285.83,30.63,8.8 +16.8118,0,18.1,0,0.7,5.277,98.1,1.4261,24,666,20.2,396.9,30.81,7.2 +24.3938,0,18.1,0,0.7,4.652,100,1.4672,24,666,20.2,396.9,28.28,10.5 +22.5971,0,18.1,0,0.7,5,89.5,1.5184,24,666,20.2,396.9,31.99,7.4 +14.3337,0,18.1,0,0.7,4.88,100,1.5895,24,666,20.2,372.92,30.62,10.2 +8.15174,0,18.1,0,0.7,5.39,98.9,1.7281,24,666,20.2,396.9,20.85,11.5 +6.96215,0,18.1,0,0.7,5.713,97,1.9265,24,666,20.2,394.43,17.11,15.1 +5.29305,0,18.1,0,0.7,6.051,82.5,2.1678,24,666,20.2,378.38,18.76,23.2 +11.5779,0,18.1,0,0.7,5.036,97,1.77,24,666,20.2,396.9,25.68,9.7 +8.64476,0,18.1,0,0.693,6.193,92.6,1.7912,24,666,20.2,396.9,15.17,13.8 +13.3598,0,18.1,0,0.693,5.887,94.7,1.7821,24,666,20.2,396.9,16.35,12.7 +8.71675,0,18.1,0,0.693,6.471,98.8,1.7257,24,666,20.2,391.98,17.12,13.1 +5.87205,0,18.1,0,0.693,6.405,96,1.6768,24,666,20.2,396.9,19.37,12.5 +7.67202,0,18.1,0,0.693,5.747,98.9,1.6334,24,666,20.2,393.1,19.92,8.5 +38.3518,0,18.1,0,0.693,5.453,100,1.4896,24,666,20.2,396.9,30.59,5 +9.91655,0,18.1,0,0.693,5.852,77.8,1.5004,24,666,20.2,338.16,29.97,6.3 +25.0461,0,18.1,0,0.693,5.987,100,1.5888,24,666,20.2,396.9,26.77,5.6 +14.2362,0,18.1,0,0.693,6.343,100,1.5741,24,666,20.2,396.9,20.32,7.2 +9.59571,0,18.1,0,0.693,6.404,100,1.639,24,666,20.2,376.11,20.31,12.1 +24.8017,0,18.1,0,0.693,5.349,96,1.7028,24,666,20.2,396.9,19.77,8.3 +41.5292,0,18.1,0,0.693,5.531,85.4,1.6074,24,666,20.2,329.46,27.38,8.5 +67.9208,0,18.1,0,0.693,5.683,100,1.4254,24,666,20.2,384.97,22.98,5 +20.7162,0,18.1,0,0.659,4.138,100,1.1781,24,666,20.2,370.22,23.34,11.9 +11.9511,0,18.1,0,0.659,5.608,100,1.2852,24,666,20.2,332.09,12.13,27.9 +7.40389,0,18.1,0,0.597,5.617,97.9,1.4547,24,666,20.2,314.64,26.4,17.2 +14.4383,0,18.1,0,0.597,6.852,100,1.4655,24,666,20.2,179.36,19.78,27.5 +51.1358,0,18.1,0,0.597,5.757,100,1.413,24,666,20.2,2.6,10.11,15 +14.0507,0,18.1,0,0.597,6.657,100,1.5275,24,666,20.2,35.05,21.22,17.2 +18.811,0,18.1,0,0.597,4.628,100,1.5539,24,666,20.2,28.79,34.37,17.9 +28.6558,0,18.1,0,0.597,5.155,100,1.5894,24,666,20.2,210.97,20.08,16.3 +45.7461,0,18.1,0,0.693,4.519,100,1.6582,24,666,20.2,88.27,36.98,7 +18.0846,0,18.1,0,0.679,6.434,100,1.8347,24,666,20.2,27.25,29.05,7.2 +10.8342,0,18.1,0,0.679,6.782,90.8,1.8195,24,666,20.2,21.57,25.79,7.5 +25.9406,0,18.1,0,0.679,5.304,89.1,1.6475,24,666,20.2,127.36,26.64,10.4 +73.5341,0,18.1,0,0.679,5.957,100,1.8026,24,666,20.2,16.45,20.62,8.8 +11.8123,0,18.1,0,0.718,6.824,76.5,1.794,24,666,20.2,48.45,22.74,8.4 +11.0874,0,18.1,0,0.718,6.411,100,1.8589,24,666,20.2,318.75,15.02,16.7 +7.02259,0,18.1,0,0.718,6.006,95.3,1.8746,24,666,20.2,319.98,15.7,14.2 +12.0482,0,18.1,0,0.614,5.648,87.6,1.9512,24,666,20.2,291.55,14.1,20.8 +7.05042,0,18.1,0,0.614,6.103,85.1,2.0218,24,666,20.2,2.52,23.29,13.4 +8.79212,0,18.1,0,0.584,5.565,70.6,2.0635,24,666,20.2,3.65,17.16,11.7 +15.8603,0,18.1,0,0.679,5.896,95.4,1.9096,24,666,20.2,7.68,24.39,8.3 +12.2472,0,18.1,0,0.584,5.837,59.7,1.9976,24,666,20.2,24.65,15.69,10.2 +37.6619,0,18.1,0,0.679,6.202,78.7,1.8629,24,666,20.2,18.82,14.52,10.9 +7.36711,0,18.1,0,0.679,6.193,78.1,1.9356,24,666,20.2,96.73,21.52,11 +9.33889,0,18.1,0,0.679,6.38,95.6,1.9682,24,666,20.2,60.72,24.08,9.5 +8.49213,0,18.1,0,0.584,6.348,86.1,2.0527,24,666,20.2,83.45,17.64,14.5 +10.0623,0,18.1,0,0.584,6.833,94.3,2.0882,24,666,20.2,81.33,19.69,14.1 +6.44405,0,18.1,0,0.584,6.425,74.8,2.2004,24,666,20.2,97.95,12.03,16.1 +5.58107,0,18.1,0,0.713,6.436,87.9,2.3158,24,666,20.2,100.19,16.22,14.3 +13.9134,0,18.1,0,0.713,6.208,95,2.2222,24,666,20.2,100.63,15.17,11.7 +11.1604,0,18.1,0,0.74,6.629,94.6,2.1247,24,666,20.2,109.85,23.27,13.4 +14.4208,0,18.1,0,0.74,6.461,93.3,2.0026,24,666,20.2,27.49,18.05,9.6 +15.1772,0,18.1,0,0.74,6.152,100,1.9142,24,666,20.2,9.32,26.45,8.7 +13.6781,0,18.1,0,0.74,5.935,87.9,1.8206,24,666,20.2,68.95,34.02,8.4 +9.39063,0,18.1,0,0.74,5.627,93.9,1.8172,24,666,20.2,396.9,22.88,12.8 +22.0511,0,18.1,0,0.74,5.818,92.4,1.8662,24,666,20.2,391.45,22.11,10.5 +9.72418,0,18.1,0,0.74,6.406,97.2,2.0651,24,666,20.2,385.96,19.52,17.1 +5.66637,0,18.1,0,0.74,6.219,100,2.0048,24,666,20.2,395.69,16.59,18.4 +9.96654,0,18.1,0,0.74,6.485,100,1.9784,24,666,20.2,386.73,18.85,15.4 +12.8023,0,18.1,0,0.74,5.854,96.6,1.8956,24,666,20.2,240.52,23.79,10.8 +10.6718,0,18.1,0,0.74,6.459,94.8,1.9879,24,666,20.2,43.06,23.98,11.8 +6.28807,0,18.1,0,0.74,6.341,96.4,2.072,24,666,20.2,318.01,17.79,14.9 +9.92485,0,18.1,0,0.74,6.251,96.6,2.198,24,666,20.2,388.52,16.44,12.6 +9.32909,0,18.1,0,0.713,6.185,98.7,2.2616,24,666,20.2,396.9,18.13,14.1 +7.52601,0,18.1,0,0.713,6.417,98.3,2.185,24,666,20.2,304.21,19.31,13 +6.71772,0,18.1,0,0.713,6.749,92.6,2.3236,24,666,20.2,0.32,17.44,13.4 +5.44114,0,18.1,0,0.713,6.655,98.2,2.3552,24,666,20.2,355.29,17.73,15.2 +5.09017,0,18.1,0,0.713,6.297,91.8,2.3682,24,666,20.2,385.09,17.27,16.1 +8.24809,0,18.1,0,0.713,7.393,99.3,2.4527,24,666,20.2,375.87,16.74,17.8 +9.51363,0,18.1,0,0.713,6.728,94.1,2.4961,24,666,20.2,6.68,18.71,14.9 +4.75237,0,18.1,0,0.713,6.525,86.5,2.4358,24,666,20.2,50.92,18.13,14.1 +4.66883,0,18.1,0,0.713,5.976,87.9,2.5806,24,666,20.2,10.48,19.01,12.7 +8.20058,0,18.1,0,0.713,5.936,80.3,2.7792,24,666,20.2,3.5,16.94,13.5 +7.75223,0,18.1,0,0.713,6.301,83.7,2.7831,24,666,20.2,272.21,16.23,14.9 +6.80117,0,18.1,0,0.713,6.081,84.4,2.7175,24,666,20.2,396.9,14.7,20 +4.81213,0,18.1,0,0.713,6.701,90,2.5975,24,666,20.2,255.23,16.42,16.4 +3.69311,0,18.1,0,0.713,6.376,88.4,2.5671,24,666,20.2,391.43,14.65,17.7 +6.65492,0,18.1,0,0.713,6.317,83,2.7344,24,666,20.2,396.9,13.99,19.5 +5.82115,0,18.1,0,0.713,6.513,89.9,2.8016,24,666,20.2,393.82,10.29,20.2 +7.83932,0,18.1,0,0.655,6.209,65.4,2.9634,24,666,20.2,396.9,13.22,21.4 +3.1636,0,18.1,0,0.655,5.759,48.2,3.0665,24,666,20.2,334.4,14.13,19.9 +3.77498,0,18.1,0,0.655,5.952,84.7,2.8715,24,666,20.2,22.01,17.15,19 +4.42228,0,18.1,0,0.584,6.003,94.5,2.5403,24,666,20.2,331.29,21.32,19.1 +15.5757,0,18.1,0,0.58,5.926,71,2.9084,24,666,20.2,368.74,18.13,19.1 +13.0751,0,18.1,0,0.58,5.713,56.7,2.8237,24,666,20.2,396.9,14.76,20.1 +4.34879,0,18.1,0,0.58,6.167,84,3.0334,24,666,20.2,396.9,16.29,19.9 +4.03841,0,18.1,0,0.532,6.229,90.7,3.0993,24,666,20.2,395.33,12.87,19.6 +3.56868,0,18.1,0,0.58,6.437,75,2.8965,24,666,20.2,393.37,14.36,23.2 +4.64689,0,18.1,0,0.614,6.98,67.6,2.5329,24,666,20.2,374.68,11.66,29.8 +8.05579,0,18.1,0,0.584,5.427,95.4,2.4298,24,666,20.2,352.58,18.14,13.8 +6.39312,0,18.1,0,0.584,6.162,97.4,2.206,24,666,20.2,302.76,24.1,13.3 +4.87141,0,18.1,0,0.614,6.484,93.6,2.3053,24,666,20.2,396.21,18.68,16.7 +15.0234,0,18.1,0,0.614,5.304,97.3,2.1007,24,666,20.2,349.48,24.91,12 +10.233,0,18.1,0,0.614,6.185,96.7,2.1705,24,666,20.2,379.7,18.03,14.6 +14.3337,0,18.1,0,0.614,6.229,88,1.9512,24,666,20.2,383.32,13.11,21.4 +5.82401,0,18.1,0,0.532,6.242,64.7,3.4242,24,666,20.2,396.9,10.74,23 +5.70818,0,18.1,0,0.532,6.75,74.9,3.3317,24,666,20.2,393.07,7.74,23.7 +5.73116,0,18.1,0,0.532,7.061,77,3.4106,24,666,20.2,395.28,7.01,25 +2.81838,0,18.1,0,0.532,5.762,40.3,4.0983,24,666,20.2,392.92,10.42,21.8 +2.37857,0,18.1,0,0.583,5.871,41.9,3.724,24,666,20.2,370.73,13.34,20.6 +3.67367,0,18.1,0,0.583,6.312,51.9,3.9917,24,666,20.2,388.62,10.58,21.2 +5.69175,0,18.1,0,0.583,6.114,79.8,3.5459,24,666,20.2,392.68,14.98,19.1 +4.83567,0,18.1,0,0.583,5.905,53.2,3.1523,24,666,20.2,388.22,11.45,20.6 +0.15086,0,27.74,0,0.609,5.454,92.7,1.8209,4,711,20.1,395.09,18.06,15.2 +0.18337,0,27.74,0,0.609,5.414,98.3,1.7554,4,711,20.1,344.05,23.97,7 +0.20746,0,27.74,0,0.609,5.093,98,1.8226,4,711,20.1,318.43,29.68,8.1 +0.10574,0,27.74,0,0.609,5.983,98.8,1.8681,4,711,20.1,390.11,18.07,13.6 +0.11132,0,27.74,0,0.609,5.983,83.5,2.1099,4,711,20.1,396.9,13.35,20.1 +0.17331,0,9.69,0,0.585,5.707,54,2.3817,6,391,19.2,396.9,12.01,21.8 +0.27957,0,9.69,0,0.585,5.926,42.6,2.3817,6,391,19.2,396.9,13.59,24.5 +0.17899,0,9.69,0,0.585,5.67,28.8,2.7986,6,391,19.2,393.29,17.6,23.1 +0.2896,0,9.69,0,0.585,5.39,72.9,2.7986,6,391,19.2,396.9,21.14,19.7 +0.26838,0,9.69,0,0.585,5.794,70.6,2.8927,6,391,19.2,396.9,14.1,18.3 +0.23912,0,9.69,0,0.585,6.019,65.3,2.4091,6,391,19.2,396.9,12.92,21.2 +0.17783,0,9.69,0,0.585,5.569,73.5,2.3999,6,391,19.2,395.77,15.1,17.5 +0.177783,0,9.69,0,0.585,6.027,79.7,2.4982,6,391,19.2,396.9,14.33,16.8 +0.06263,0,11.93,0,0.573,6.593,69.1,2.4786,1,273,21,391.99,9.67,22.4 +0.04527,0,11.93,0,0.573,6.12,76.7,2.2875,1,273,21,396.9,9.08,20.6 +0.06076,0,11.93,0,0.573,6.976,91,2.1675,1,273,21,396.9,5.64,23.9 +0.10959,0,11.93,0,0.573,6.794,89.3,2.3889,1,273,21,393.45,6.48,22 +0.04741,0,11.93,0,0.573,6.03,80.8,2.505,1,273,21,396.9,7.88,11.9 +0.98765,0,12.5,0,0.561,6.98,89,2.098,3,320,23,396,12,12 +0.23456,0,12.5,0,0.561,6.98,76,2.654,3,320,23,343,25,32 +0.44433,0,12.5,0,0.561,6.123,98,2.987,3,320,23,343,21,54 +0.77763,0,12.7,0,0.561,6.222,34,2.543,3,329,23,343,76,67 +0.65432,0,12.8,0,0.561,6.76E+00,67,2.987,3,345,23,321,45,24 diff --git a/Real Estate visualizer/visulaizer.py b/Real Estate visualizer/visulaizer.py new file mode 100644 index 000000000..55dc363f4 --- /dev/null +++ b/Real Estate visualizer/visulaizer.py @@ -0,0 +1,86 @@ +import dash +from dash import dcc, html +from dash.dependencies import Input, Output +import plotly.express as px +import pandas as pd + +# Load dataset +data = pd.read_csv( + r"C:\Users\Ananya\OneDrive\Documents\GitHub\master-web-development\Real Estate visualizer\data.csv" +) # Adjust path to your file location + +# Initialize the Dash app +app = dash.Dash(__name__) + +# Define the app layout +app.layout = html.Div( + children=[ + # Main title + html.H1( + "Boston Housing Market Analysis Dashboard", style={"text-align": "center"} + ), + # Highway Access Index (RAD) dropdown + html.Div( + children=[ + html.Label("Select Highway Access Index (RAD):"), + dcc.Dropdown( + id="rad_dropdown", + options=[ + {"label": str(rad), "value": str(rad)} + for rad in sorted(data["RAD"].unique()) + ], + multi=True, + placeholder="Select highway access index", + ), + ], + style={"width": "30%", "display": "inline-block"}, + ), + # Graphs for visualization + dcc.Graph(id="medv_distribution_graph"), + dcc.Graph(id="avg_value_room_graph"), + dcc.Graph(id="value_tax_scatter"), + ] +) + + +# Callback to update graphs based on RAD selection +@app.callback( + [ + Output("medv_distribution_graph", "figure"), + Output("avg_value_room_graph", "figure"), + Output("value_tax_scatter", "figure"), + ], + [Input("rad_dropdown", "value")], +) +def update_graphs(selected_rad): + # Filter data based on highway access index selection + filtered_data = data + if selected_rad: + filtered_data = filtered_data[ + filtered_data["RAD"].astype(str).isin(selected_rad) + ] + + # Plot 1: Distribution of Median Home Value (MEDV) + medv_distribution_fig = px.histogram( + filtered_data, x="MEDV", nbins=50, title="Median Home Value Distribution" + ) + + # Plot 2: Average Home Value by Room Count + avg_value_room_fig = px.bar( + filtered_data.groupby("RM")["MEDV"].mean().reset_index(), + x="RM", + y="MEDV", + title="Average Home Value by Room Count", + ) + + # Plot 3: Scatter Plot of Home Value vs Tax Rate + value_tax_scatter_fig = px.scatter( + filtered_data, x="TAX", y="MEDV", color="RAD", title="Home Value vs. Tax Rate" + ) + + return medv_distribution_fig, avg_value_room_fig, value_tax_scatter_fig + + +# Run the app +if __name__ == "__main__": + app.run_server(debug=True) diff --git a/SpaceBubbleDodger_Game/index.html b/SpaceBubbleDodger_Game/index.html new file mode 100644 index 000000000..a4c5927c0 --- /dev/null +++ b/SpaceBubbleDodger_Game/index.html @@ -0,0 +1,22 @@ + + + + + + Space Bubble Dodger + + + +
+ +
Score: 0
+ +
+ + + + \ No newline at end of file diff --git a/SpaceBubbleDodger_Game/script.js b/SpaceBubbleDodger_Game/script.js new file mode 100644 index 000000000..b08480a8d --- /dev/null +++ b/SpaceBubbleDodger_Game/script.js @@ -0,0 +1,164 @@ +const canvas = document.getElementById('gameCanvas'); +const ctx = canvas.getContext('2d'); +canvas.width = 800; +canvas.height = 600; + +let score = 0; +let bubbles = []; +let gameOver = false; +let level = 1; +let spaceship = { + x: canvas.width / 2, + y: canvas.height - 50, + width: 60, + height: 60, + speed: 10 // Increased base speed +}; + +let bubbleBaseSpeed = 2; +let bubbleSpeedIncrease = 0.5; // Bubble speed increase per level +let spaceshipSpeedIncrease = 2; // Speed increase per level for spaceship + +function createBubble() { + const size = Math.random() * (30 - 10) + 10; + const x = Math.random() * (canvas.width - size); + bubbles.push({ x, y: 0, size, speed: bubbleBaseSpeed + bubbleSpeedIncrease * (level - 1) }); +} + +function drawSpaceship() { + ctx.save(); + ctx.translate(spaceship.x, spaceship.y); + + // Draw the main body + ctx.fillStyle = '#00ffff'; + ctx.beginPath(); + ctx.moveTo(0, -spaceship.height / 2); + ctx.lineTo(-spaceship.width / 2, spaceship.height / 2); + ctx.lineTo(spaceship.width / 2, spaceship.height / 2); + ctx.closePath(); + ctx.fill(); + + // Draw the cockpit + ctx.fillStyle = '#ffffff'; + ctx.beginPath(); + ctx.arc(0, 0, spaceship.width / 6, 0, Math.PI * 2); + ctx.fill(); + + // Draw the wings + ctx.fillStyle = '#ff00ff'; + ctx.beginPath(); + ctx.moveTo(-spaceship.width / 2, spaceship.height / 4); + ctx.lineTo(-spaceship.width, spaceship.height / 2); + ctx.lineTo(-spaceship.width / 2, spaceship.height / 2); + ctx.closePath(); + ctx.fill(); + + ctx.beginPath(); + ctx.moveTo(spaceship.width / 2, spaceship.height / 4); + ctx.lineTo(spaceship.width, spaceship.height / 2); + ctx.lineTo(spaceship.width / 2, spaceship.height / 2); + ctx.closePath(); + ctx.fill(); + + ctx.restore(); +} + +function drawBubbles() { + bubbles.forEach(bubble => { + ctx.fillStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.7)`; + ctx.beginPath(); + ctx.arc(bubble.x, bubble.y, bubble.size, 0, Math.PI * 2); + ctx.fill(); + }); +} + +function update() { + if (gameOver) return; + + bubbles.forEach((bubble, index) => { + bubble.y += bubble.speed; + + // Collision detection + if ( + bubble.y + bubble.size > spaceship.y - spaceship.height / 2 && + bubble.y - bubble.size < spaceship.y + spaceship.height / 2 && + bubble.x + bubble.size > spaceship.x - spaceship.width / 2 && + bubble.x - bubble.size < spaceship.x + spaceship.width / 2 + ) { + gameOver = true; + document.getElementById('game-over').classList.remove('hidden'); + document.getElementById('finalScore').textContent = score; + } + + if (bubble.y - bubble.size > canvas.height) { + bubbles.splice(index, 1); + score++; + document.getElementById('scoreValue').innerText = score; + + // Level up every 5 points + if (score % 5 === 0) { + level++; + showLevelUpPopup(); + spaceship.speed += spaceshipSpeedIncrease; // Increased speed increment + bubbleSpeedIncrease += 0.5; // Increase bubble speed increment + createBubble(); + } + } + }); + + if (bubbles.length < 5) { + createBubble(); + } +} + +function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + drawSpaceship(); + drawBubbles(); +} + +function gameLoop() { + update(); + draw(); + if (!gameOver) { + requestAnimationFrame(gameLoop); + } +} + +document.addEventListener('keydown', (e) => { + if (gameOver) return; + const moveDistance = spaceship.speed; + if (e.code === 'ArrowLeft' && spaceship.x > spaceship.width / 2) { + spaceship.x = Math.max(spaceship.x - moveDistance, spaceship.width / 2); + } else if (e.code === 'ArrowRight' && spaceship.x < canvas.width - spaceship.width / 2) { + spaceship.x = Math.min(spaceship.x + moveDistance, canvas.width - spaceship.width / 2); + } +}); + +document.getElementById('restartButton').addEventListener('click', () => { + score = 0; + bubbles = []; + gameOver = false; + level = 1; + spaceship.speed = 10; // Reset to new base speed + document.getElementById('scoreValue').innerText = score; + document.getElementById('game-over').classList.add('hidden'); + spaceship.x = canvas.width / 2; + gameLoop(); +}); + +function showLevelUpPopup() { + const popup = document.getElementById('level-up-popup'); + popup.textContent = `Level Up! Now at Level ${level}`; + popup.classList.remove('hidden'); + popup.style.opacity = 1; + setTimeout(() => { + popup.style.opacity = 0; + setTimeout(() => { + popup.classList.add('hidden'); + }, 500); + }, 2000); +} + +// Start the game loop +gameLoop(); \ No newline at end of file diff --git a/SpaceBubbleDodger_Game/style.css b/SpaceBubbleDodger_Game/style.css new file mode 100644 index 000000000..da788f5ff --- /dev/null +++ b/SpaceBubbleDodger_Game/style.css @@ -0,0 +1,63 @@ +body { + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #000; + font-family: Arial, sans-serif; +} + +#game-container { + position: relative; +} + +#gameCanvas { + border: 2px solid #fff; +} + +#score { + position: absolute; + top: 10px; + left: 10px; + color: #fff; + font-size: 20px; +} + +#game-over { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: rgba(0, 0, 0, 0.8); + color: #fff; + padding: 20px; + text-align: center; + border-radius: 10px; +} + +#restartButton { + margin-top: 10px; + padding: 10px 20px; + font-size: 16px; + cursor: pointer; +} + +.hidden { + display: none; +} + +.popup { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: rgba(0, 0, 0, 0.7); + color: white; + padding: 20px; + border-radius: 10px; + text-align: center; + opacity: 0; + transition: opacity 0.5s; +} \ No newline at end of file diff --git a/Task Planner/index.html b/Task Planner/index.html new file mode 100644 index 000000000..c07cea819 --- /dev/null +++ b/Task Planner/index.html @@ -0,0 +1,35 @@ + + + + + + + Task Planner + + + + +
+

Task Planner

+
+ + + + + + + + + + + + + +
+ +
+
+ + + + \ No newline at end of file diff --git a/Task Planner/script.js b/Task Planner/script.js new file mode 100644 index 000000000..3728d40a8 --- /dev/null +++ b/Task Planner/script.js @@ -0,0 +1,78 @@ +document.addEventListener("DOMContentLoaded", () => { + const form = document.getElementById("task-form"); + const taskList = document.getElementById("task-list"); + let editMode = false; + let editId = null; + + function loadTasks() { + const tasks = JSON.parse(localStorage.getItem("tasks")) || []; + taskList.innerHTML = ""; + tasks.forEach((task) => displayTask(task)); + } + + function saveAllTasks(tasks) { + localStorage.setItem("tasks", JSON.stringify(tasks)); + } + + function saveTask(task) { + const tasks = JSON.parse(localStorage.getItem("tasks")) || []; + if (editMode) { + const index = tasks.findIndex((t) => t.id === editId); + if (index !== -1) { + tasks[index] = task; + } + editMode = false; + editId = null; + } else { + tasks.push(task); + } + saveAllTasks(tasks); + } + + function displayTask(task) { + const taskItem = document.createElement("div"); + taskItem.className = "task"; + taskItem.innerHTML = ` +

${task.title}

+

${task.description}

+

Due: ${task.date}

+ + + `; + taskList.appendChild(taskItem); + } + + window.editTask = function (id) { + const tasks = JSON.parse(localStorage.getItem("tasks")) || []; + const task = tasks.find((t) => t.id === id); + if (task) { + document.getElementById("task-title").value = task.title; + document.getElementById("task-desc").value = task.description; + document.getElementById("task-date").value = task.date; + editMode = true; + editId = id; + } + }; + + window.deleteTask = function (id) { + const tasks = JSON.parse(localStorage.getItem("tasks")) || []; + const updatedTasks = tasks.filter((task) => task.id !== id); + saveAllTasks(updatedTasks); + loadTasks(); + }; + + form.addEventListener("submit", (e) => { + e.preventDefault(); + const task = { + id: editMode ? editId : Date.now().toString(), + title: document.getElementById("task-title").value, + description: document.getElementById("task-desc").value, + date: document.getElementById("task-date").value, + }; + saveTask(task); + loadTasks(); + form.reset(); + }); + + loadTasks(); +}); diff --git a/Task Planner/style.css b/Task Planner/style.css new file mode 100644 index 000000000..717b40aac --- /dev/null +++ b/Task Planner/style.css @@ -0,0 +1,86 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + } + + body { + font-family: Arial, sans-serif; + background: linear-gradient(135deg, teal 0%, #367588 100%); + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + } + + #app { + background-color: #fff; + border-radius: 8px; + box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.2); + width: 100%; + max-width: 500px; + padding: 20px; + } + + form { + display: flex; + flex-direction: column; + } + + form label { + font-weight: bold; + margin: 8px 0 4px; + } + + form input, form textarea, form button { + padding: 10px; + border: 1px solid #ddd; + border-radius: 5px; + margin-bottom: 15px; + } + + form button { + background-color: #ff7f50; + color: #fff; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s; + } + + form button:hover { + background-color: #ff6347; + } + + .task { + background: #f5f5f5; + border-left: 5px solid #ff7f50; + margin: 10px 0; + padding: 15px; + border-radius: 5px; + } + + .task h3 { + color: #333; + margin-bottom: 5px; + } + + .task button { + background: transparent; + border: none; + cursor: pointer; + color: #ff6347; + font-weight: bold; + margin-left: 5px; + transition: color 0.3s; + } + + .task button:hover { + color: #ff4500; + } + + @media (max-width: 600px) { + #app { + padding: 15px; + } + } + \ No newline at end of file diff --git a/The 3D Cube game/index.html b/The 3D Cube game/index.html new file mode 100644 index 000000000..38a79ac7e --- /dev/null +++ b/The 3D Cube game/index.html @@ -0,0 +1,101 @@ + + + + + + + + + Document + + + + +
+ +
+ +
+ +
+

+ THE + CUBE +

+
+ Double tap to start +
+
+ 0:00 +
+
+ Complete! +
+
+ + Best Time! +
+
+ +
+ + + + + +
+ +
+ + + +
+ +
+
+ Cube:3x3x3 +
+
+ Total solves:- +
+
+ Best time:- +
+
+ Worst time:- +
+
+ Average of 5:- +
+
+ Average of 12:- +
+
+ Average of 25:- +
+
+ +
+ + + + + +
+ +
+ + + + + diff --git a/The 3D Cube game/script.js b/The 3D Cube game/script.js new file mode 100644 index 000000000..115b7962a --- /dev/null +++ b/The 3D Cube game/script.js @@ -0,0 +1,3760 @@ +// Three.js - https://github.com/mrdoob/three.js/ +// RoundedBoxGeometry - https://github.com/pailhead/three-rounded-box + +const animationEngine = (() => { + let uniqueID = 0; + + class AnimationEngine { + constructor() { + this.ids = []; + this.animations = {}; + this.update = this.update.bind(this); + this.raf = 0; + this.time = 0; + } + + update() { + const now = performance.now(); + const delta = now - this.time; + this.time = now; + + let i = this.ids.length; + + this.raf = i ? requestAnimationFrame(this.update) : 0; + + while (i--) + this.animations[this.ids[i]] && + this.animations[this.ids[i]].update(delta); + } + + add(animation) { + animation.id = uniqueID++; + + this.ids.push(animation.id); + this.animations[animation.id] = animation; + + if (this.raf !== 0) return; + + this.time = performance.now(); + this.raf = requestAnimationFrame(this.update); + } + + remove(animation) { + const index = this.ids.indexOf(animation.id); + + if (index < 0) return; + + this.ids.splice(index, 1); + delete this.animations[animation.id]; + animation = null; + } + } + + return new AnimationEngine(); +})(); + +class Animation { + constructor(start) { + if (start === true) this.start(); + } + + start() { + animationEngine.add(this); + } + + stop() { + animationEngine.remove(this); + } + + update(delta) {} +} + +class World extends Animation { + constructor(game) { + super(true); + + this.game = game; + + this.container = this.game.dom.game; + this.scene = new THREE.Scene(); + + this.renderer = new THREE.WebGLRenderer({ + antialias: true, + alpha: true, + }); + this.renderer.setPixelRatio(window.devicePixelRatio); + this.container.appendChild(this.renderer.domElement); + + this.camera = new THREE.PerspectiveCamera(2, 1, 0.1, 10000); + + this.stage = { width: 2, height: 3 }; + this.fov = 10; + + this.createLights(); + + this.onResize = []; + + this.resize(); + window.addEventListener("resize", () => this.resize(), false); + } + + update() { + this.renderer.render(this.scene, this.camera); + } + + resize() { + this.width = this.container.offsetWidth; + this.height = this.container.offsetHeight; + + this.renderer.setSize(this.width, this.height); + + this.camera.fov = this.fov; + this.camera.aspect = this.width / this.height; + + const aspect = this.stage.width / this.stage.height; + const fovRad = this.fov * THREE.Math.DEG2RAD; + + let distance = + aspect < this.camera.aspect + ? this.stage.height / 2 / Math.tan(fovRad / 2) + : this.stage.width / + this.camera.aspect / + (2 * Math.tan(fovRad / 2)); + + distance *= 0.5; + + this.camera.position.set(distance, distance, distance); + this.camera.lookAt(this.scene.position); + this.camera.updateProjectionMatrix(); + + const docFontSize = + aspect < this.camera.aspect + ? (this.height / 100) * aspect + : this.width / 100; + + document.documentElement.style.fontSize = docFontSize + "px"; + + if (this.onResize) this.onResize.forEach((cb) => cb()); + } + + createLights() { + this.lights = { + holder: new THREE.Object3D(), + ambient: new THREE.AmbientLight(0xffffff, 0.69), + front: new THREE.DirectionalLight(0xffffff, 0.36), + back: new THREE.DirectionalLight(0xffffff, 0.19), + }; + + this.lights.front.position.set(1.5, 5, 3); + this.lights.back.position.set(-1.5, -5, -3); + + this.lights.holder.add(this.lights.ambient); + this.lights.holder.add(this.lights.front); + this.lights.holder.add(this.lights.back); + + this.scene.add(this.lights.holder); + } +} + +function RoundedBoxGeometry(size, radius, radiusSegments) { + THREE.BufferGeometry.call(this); + + this.type = "RoundedBoxGeometry"; + + radiusSegments = !isNaN(radiusSegments) + ? Math.max(1, Math.floor(radiusSegments)) + : 1; + + var width, height, depth; + + width = height = depth = size; + radius = size * radius; + + radius = Math.min( + radius, + Math.min(width, Math.min(height, Math.min(depth))) / 2 + ); + + var edgeHalfWidth = width / 2 - radius; + var edgeHalfHeight = height / 2 - radius; + var edgeHalfDepth = depth / 2 - radius; + + this.parameters = { + width: width, + height: height, + depth: depth, + radius: radius, + radiusSegments: radiusSegments, + }; + + var rs1 = radiusSegments + 1; + var totalVertexCount = (rs1 * radiusSegments + 1) << 3; + + var positions = new THREE.BufferAttribute( + new Float32Array(totalVertexCount * 3), + 3 + ); + var normals = new THREE.BufferAttribute( + new Float32Array(totalVertexCount * 3), + 3 + ); + + var cornerVerts = [], + cornerNormals = [], + normal = new THREE.Vector3(), + vertex = new THREE.Vector3(), + vertexPool = [], + normalPool = [], + indices = []; + var lastVertex = rs1 * radiusSegments, + cornerVertNumber = rs1 * radiusSegments + 1; + doVertices(); + doFaces(); + doCorners(); + doHeightEdges(); + doWidthEdges(); + doDepthEdges(); + + function doVertices() { + var cornerLayout = [ + new THREE.Vector3(1, 1, 1), + new THREE.Vector3(1, 1, -1), + new THREE.Vector3(-1, 1, -1), + new THREE.Vector3(-1, 1, 1), + new THREE.Vector3(1, -1, 1), + new THREE.Vector3(1, -1, -1), + new THREE.Vector3(-1, -1, -1), + new THREE.Vector3(-1, -1, 1), + ]; + + for (var j = 0; j < 8; j++) { + cornerVerts.push([]); + cornerNormals.push([]); + } + + var PIhalf = Math.PI / 2; + var cornerOffset = new THREE.Vector3( + edgeHalfWidth, + edgeHalfHeight, + edgeHalfDepth + ); + + for (var y = 0; y <= radiusSegments; y++) { + var v = y / radiusSegments; + var va = v * PIhalf; + var cosVa = Math.cos(va); + var sinVa = Math.sin(va); + + if (y == radiusSegments) { + vertex.set(0, 1, 0); + var vert = vertex + .clone() + .multiplyScalar(radius) + .add(cornerOffset); + cornerVerts[0].push(vert); + vertexPool.push(vert); + var norm = vertex.clone(); + cornerNormals[0].push(norm); + normalPool.push(norm); + continue; + } + + for (var x = 0; x <= radiusSegments; x++) { + var u = x / radiusSegments; + var ha = u * PIhalf; + vertex.x = cosVa * Math.cos(ha); + vertex.y = sinVa; + vertex.z = cosVa * Math.sin(ha); + + var vert = vertex + .clone() + .multiplyScalar(radius) + .add(cornerOffset); + cornerVerts[0].push(vert); + vertexPool.push(vert); + + var norm = vertex.clone().normalize(); + cornerNormals[0].push(norm); + normalPool.push(norm); + } + } + + for (var i = 1; i < 8; i++) { + for (var j = 0; j < cornerVerts[0].length; j++) { + var vert = cornerVerts[0][j].clone().multiply(cornerLayout[i]); + cornerVerts[i].push(vert); + vertexPool.push(vert); + + var norm = cornerNormals[0][j] + .clone() + .multiply(cornerLayout[i]); + cornerNormals[i].push(norm); + normalPool.push(norm); + } + } + } + + function doCorners() { + var flips = [true, false, true, false, false, true, false, true]; + + var lastRowOffset = rs1 * (radiusSegments - 1); + + for (var i = 0; i < 8; i++) { + var cornerOffset = cornerVertNumber * i; + + for (var v = 0; v < radiusSegments - 1; v++) { + var r1 = v * rs1; + var r2 = (v + 1) * rs1; + + for (var u = 0; u < radiusSegments; u++) { + var u1 = u + 1; + var a = cornerOffset + r1 + u; + var b = cornerOffset + r1 + u1; + var c = cornerOffset + r2 + u; + var d = cornerOffset + r2 + u1; + + if (!flips[i]) { + indices.push(a); + indices.push(b); + indices.push(c); + + indices.push(b); + indices.push(d); + indices.push(c); + } else { + indices.push(a); + indices.push(c); + indices.push(b); + + indices.push(b); + indices.push(c); + indices.push(d); + } + } + } + + for (var u = 0; u < radiusSegments; u++) { + var a = cornerOffset + lastRowOffset + u; + var b = cornerOffset + lastRowOffset + u + 1; + var c = cornerOffset + lastVertex; + + if (!flips[i]) { + indices.push(a); + indices.push(b); + indices.push(c); + } else { + indices.push(a); + indices.push(c); + indices.push(b); + } + } + } + } + + function doFaces() { + var a = lastVertex; + var b = lastVertex + cornerVertNumber; + var c = lastVertex + cornerVertNumber * 2; + var d = lastVertex + cornerVertNumber * 3; + + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(a); + indices.push(c); + indices.push(d); + + a = lastVertex + cornerVertNumber * 4; + b = lastVertex + cornerVertNumber * 5; + c = lastVertex + cornerVertNumber * 6; + d = lastVertex + cornerVertNumber * 7; + + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(a); + indices.push(d); + indices.push(c); + + a = 0; + b = cornerVertNumber; + c = cornerVertNumber * 4; + d = cornerVertNumber * 5; + + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); + + a = cornerVertNumber * 2; + b = cornerVertNumber * 3; + c = cornerVertNumber * 6; + d = cornerVertNumber * 7; + + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); + + a = radiusSegments; + b = radiusSegments + cornerVertNumber * 3; + c = radiusSegments + cornerVertNumber * 4; + d = radiusSegments + cornerVertNumber * 7; + + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(b); + indices.push(d); + indices.push(c); + + a = radiusSegments + cornerVertNumber; + b = radiusSegments + cornerVertNumber * 2; + c = radiusSegments + cornerVertNumber * 5; + d = radiusSegments + cornerVertNumber * 6; + + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); + } + + function doHeightEdges() { + for (var i = 0; i < 4; i++) { + var cOffset = i * cornerVertNumber; + var cRowOffset = 4 * cornerVertNumber + cOffset; + var needsFlip = i & (1 === 1); + + for (var u = 0; u < radiusSegments; u++) { + var u1 = u + 1; + var a = cOffset + u; + var b = cOffset + u1; + var c = cRowOffset + u; + var d = cRowOffset + u1; + + if (!needsFlip) { + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(b); + indices.push(d); + indices.push(c); + } else { + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); + } + } + } + } + + function doDepthEdges() { + var cStarts = [0, 2, 4, 6]; + var cEnds = [1, 3, 5, 7]; + + for (var i = 0; i < 4; i++) { + var cStart = cornerVertNumber * cStarts[i]; + var cEnd = cornerVertNumber * cEnds[i]; + + var needsFlip = 1 >= i; + + for (var u = 0; u < radiusSegments; u++) { + var urs1 = u * rs1; + var u1rs1 = (u + 1) * rs1; + + var a = cStart + urs1; + var b = cStart + u1rs1; + var c = cEnd + urs1; + var d = cEnd + u1rs1; + + if (needsFlip) { + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); + } else { + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(b); + indices.push(d); + indices.push(c); + } + } + } + } + + function doWidthEdges() { + var end = radiusSegments - 1; + + var cStarts = [0, 1, 4, 5]; + var cEnds = [3, 2, 7, 6]; + var needsFlip = [0, 1, 1, 0]; + + for (var i = 0; i < 4; i++) { + var cStart = cStarts[i] * cornerVertNumber; + var cEnd = cEnds[i] * cornerVertNumber; + + for (var u = 0; u <= end; u++) { + var a = cStart + radiusSegments + u * rs1; + var b = + cStart + + (u != end + ? radiusSegments + (u + 1) * rs1 + : cornerVertNumber - 1); + + var c = cEnd + radiusSegments + u * rs1; + var d = + cEnd + + (u != end + ? radiusSegments + (u + 1) * rs1 + : cornerVertNumber - 1); + + if (!needsFlip[i]) { + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(b); + indices.push(d); + indices.push(c); + } else { + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); + } + } + } + } + + var index = 0; + + for (var i = 0; i < vertexPool.length; i++) { + positions.setXYZ( + index, + vertexPool[i].x, + vertexPool[i].y, + vertexPool[i].z + ); + + normals.setXYZ( + index, + normalPool[i].x, + normalPool[i].y, + normalPool[i].z + ); + + index++; + } + + this.setIndex(new THREE.BufferAttribute(new Uint16Array(indices), 1)); + this.addAttribute("position", positions); + this.addAttribute("normal", normals); +} + +RoundedBoxGeometry.prototype = Object.create(THREE.BufferGeometry.prototype); +RoundedBoxGeometry.constructor = RoundedBoxGeometry; + +function RoundedPlaneGeometry(size, radius, depth) { + var x, y, width, height; + + x = y = -size / 2; + width = height = size; + radius = size * radius; + + const shape = new THREE.Shape(); + + shape.moveTo(x, y + radius); + shape.lineTo(x, y + height - radius); + shape.quadraticCurveTo(x, y + height, x + radius, y + height); + shape.lineTo(x + width - radius, y + height); + shape.quadraticCurveTo( + x + width, + y + height, + x + width, + y + height - radius + ); + shape.lineTo(x + width, y + radius); + shape.quadraticCurveTo(x + width, y, x + width - radius, y); + shape.lineTo(x + radius, y); + shape.quadraticCurveTo(x, y, x, y + radius); + + const geometry = new THREE.ExtrudeBufferGeometry(shape, { + depth: depth, + bevelEnabled: false, + curveSegments: 3, + }); + + return geometry; +} + +class Cube { + constructor(game) { + this.game = game; + this.size = 3; + + this.geometry = { + pieceCornerRadius: 0.12, + edgeCornerRoundness: 0.15, + edgeScale: 0.82, + edgeDepth: 0.01, + }; + + this.holder = new THREE.Object3D(); + this.object = new THREE.Object3D(); + this.animator = new THREE.Object3D(); + + this.holder.add(this.animator); + this.animator.add(this.object); + + this.game.world.scene.add(this.holder); + } + + init() { + this.cubes = []; + this.object.children = []; + this.object.add(this.game.controls.group); + + if (this.size === 2) this.scale = 1.25; + else if (this.size === 3) this.scale = 1; + else if (this.size > 3) this.scale = 3 / this.size; + + this.object.scale.set(this.scale, this.scale, this.scale); + + const controlsScale = this.size === 2 ? 0.825 : 1; + this.game.controls.edges.scale.set( + controlsScale, + controlsScale, + controlsScale + ); + + this.generatePositions(); + this.generateModel(); + + this.pieces.forEach((piece) => { + this.cubes.push(piece.userData.cube); + this.object.add(piece); + }); + + this.holder.traverse((node) => { + if (node.frustumCulled) node.frustumCulled = false; + }); + + this.updateColors(this.game.themes.getColors()); + + this.sizeGenerated = this.size; + } + + resize(force = false) { + if (this.size !== this.sizeGenerated || force) { + this.size = this.game.preferences.ranges.size.value; + + this.reset(); + this.init(); + + this.game.saved = false; + this.game.timer.reset(); + this.game.storage.clearGame(); + } + } + + reset() { + this.game.controls.edges.rotation.set(0, 0, 0); + + this.holder.rotation.set(0, 0, 0); + this.object.rotation.set(0, 0, 0); + this.animator.rotation.set(0, 0, 0); + } + + generatePositions() { + const m = this.size - 1; + const first = + this.size % 2 !== 0 + ? 0 - Math.floor(this.size / 2) + : 0.5 - this.size / 2; + + let x, y, z; + + this.positions = []; + + for (x = 0; x < this.size; x++) { + for (y = 0; y < this.size; y++) { + for (z = 0; z < this.size; z++) { + let position = new THREE.Vector3( + first + x, + first + y, + first + z + ); + let edges = []; + + if (x == 0) edges.push(0); + if (x == m) edges.push(1); + if (y == 0) edges.push(2); + if (y == m) edges.push(3); + if (z == 0) edges.push(4); + if (z == m) edges.push(5); + + position.edges = edges; + this.positions.push(position); + } + } + } + } + + generateModel() { + this.pieces = []; + this.edges = []; + + const pieceSize = 1 / 3; + + const mainMaterial = new THREE.MeshLambertMaterial(); + + const pieceMesh = new THREE.Mesh( + new RoundedBoxGeometry( + pieceSize, + this.geometry.pieceCornerRadius, + 3 + ), + mainMaterial.clone() + ); + + const edgeGeometry = RoundedPlaneGeometry( + pieceSize, + this.geometry.edgeCornerRoundness, + this.geometry.edgeDepth + ); + + this.positions.forEach((position, index) => { + const piece = new THREE.Object3D(); + const pieceCube = pieceMesh.clone(); + const pieceEdges = []; + + piece.position.copy(position.clone().divideScalar(3)); + piece.add(pieceCube); + piece.name = index; + piece.edgesName = ""; + + position.edges.forEach((position) => { + const edge = new THREE.Mesh(edgeGeometry, mainMaterial.clone()); + const name = ["L", "R", "D", "U", "B", "F"][position]; + const distance = pieceSize / 2; + + edge.position.set( + distance * [-1, 1, 0, 0, 0, 0][position], + distance * [0, 0, -1, 1, 0, 0][position], + distance * [0, 0, 0, 0, -1, 1][position] + ); + + edge.rotation.set( + (Math.PI / 2) * [0, 0, 1, -1, 0, 0][position], + (Math.PI / 2) * [-1, 1, 0, 0, 2, 0][position], + 0 + ); + + edge.scale.set( + this.geometry.edgeScale, + this.geometry.edgeScale, + this.geometry.edgeScale + ); + + edge.name = name; + + piece.add(edge); + pieceEdges.push(name); + this.edges.push(edge); + }); + + piece.userData.edges = pieceEdges; + piece.userData.cube = pieceCube; + + piece.userData.start = { + position: piece.position.clone(), + rotation: piece.rotation.clone(), + }; + + this.pieces.push(piece); + }); + } + + updateColors(colors) { + if (typeof this.pieces !== "object" && typeof this.edges !== "object") + return; + + this.pieces.forEach((piece) => + piece.userData.cube.material.color.setHex(colors.P) + ); + this.edges.forEach((edge) => + edge.material.color.setHex(colors[edge.name]) + ); + } + + loadFromData(data) { + this.size = data.size; + + this.reset(); + this.init(); + + this.pieces.forEach((piece) => { + const index = data.names.indexOf(piece.name); + + const position = data.positions[index]; + const rotation = data.rotations[index]; + + piece.position.set(position.x, position.y, position.z); + piece.rotation.set(rotation.x, rotation.y, rotation.z); + }); + } +} + +const Easing = { + Power: { + In: (power) => { + power = Math.round(power || 1); + + return (t) => Math.pow(t, power); + }, + + Out: (power) => { + power = Math.round(power || 1); + + return (t) => 1 - Math.abs(Math.pow(t - 1, power)); + }, + + InOut: (power) => { + power = Math.round(power || 1); + + return (t) => + t < 0.5 + ? Math.pow(t * 2, power) / 2 + : (1 - Math.abs(Math.pow(t * 2 - 1 - 1, power))) / 2 + 0.5; + }, + }, + + Sine: { + In: () => (t) => 1 + Math.sin((Math.PI / 2) * t - Math.PI / 2), + + Out: () => (t) => Math.sin((Math.PI / 2) * t), + + InOut: () => (t) => (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2, + }, + + Back: { + Out: (s) => { + s = s || 1.70158; + + return (t) => { + return (t -= 1) * t * ((s + 1) * t + s) + 1; + }; + }, + + In: (s) => { + s = s || 1.70158; + + return (t) => { + return t * t * ((s + 1) * t - s); + }; + }, + }, + + Elastic: { + Out: (amplitude, period) => { + let PI2 = Math.PI * 2; + + let p1 = amplitude >= 1 ? amplitude : 1; + let p2 = (period || 0.3) / (amplitude < 1 ? amplitude : 1); + let p3 = (p2 / PI2) * (Math.asin(1 / p1) || 0); + + p2 = PI2 / p2; + + return (t) => { + return p1 * Math.pow(2, -10 * t) * Math.sin((t - p3) * p2) + 1; + }; + }, + }, +}; + +class Tween extends Animation { + constructor(options) { + super(false); + + this.duration = options.duration || 500; + this.easing = options.easing || ((t) => t); + this.onUpdate = options.onUpdate || (() => {}); + this.onComplete = options.onComplete || (() => {}); + + this.delay = options.delay || false; + this.yoyo = options.yoyo ? false : null; + + this.progress = 0; + this.value = 0; + this.delta = 0; + + this.getFromTo(options); + + if (this.delay) setTimeout(() => super.start(), this.delay); + else super.start(); + + this.onUpdate(this); + } + + update(delta) { + const old = this.value * 1; + const direction = this.yoyo === true ? -1 : 1; + + this.progress += (delta / this.duration) * direction; + + this.value = this.easing(this.progress); + this.delta = this.value - old; + + if (this.values !== null) this.updateFromTo(); + + if (this.yoyo !== null) this.updateYoyo(); + else if (this.progress <= 1) this.onUpdate(this); + else { + this.progress = 1; + this.value = 1; + this.onUpdate(this); + this.onComplete(this); + super.stop(); + } + } + + updateYoyo() { + if (this.progress > 1 || this.progress < 0) { + this.value = this.progress = this.progress > 1 ? 1 : 0; + this.yoyo = !this.yoyo; + } + + this.onUpdate(this); + } + + updateFromTo() { + this.values.forEach((key) => { + this.target[key] = + this.from[key] + (this.to[key] - this.from[key]) * this.value; + }); + } + + getFromTo(options) { + if (!options.target || !options.to) { + this.values = null; + return; + } + + this.target = options.target || null; + this.from = options.from || {}; + this.to = options.to || null; + this.values = []; + + if (Object.keys(this.from).length < 1) + Object.keys(this.to).forEach((key) => { + this.from[key] = this.target[key]; + }); + + Object.keys(this.to).forEach((key) => { + this.values.push(key); + }); + } +} + +window.addEventListener("touchmove", () => {}); +document.addEventListener( + "touchmove", + (event) => { + event.preventDefault(); + }, + { passive: false } +); + +class Draggable { + constructor(element, options) { + this.position = { + current: new THREE.Vector2(), + start: new THREE.Vector2(), + delta: new THREE.Vector2(), + old: new THREE.Vector2(), + drag: new THREE.Vector2(), + }; + + this.options = Object.assign( + { + calcDelta: false, + }, + options || {} + ); + + this.element = element; + this.touch = null; + + this.drag = { + start: (event) => { + if (event.type == "mousedown" && event.which != 1) return; + if (event.type == "touchstart" && event.touches.length > 1) + return; + + this.getPositionCurrent(event); + + if (this.options.calcDelta) { + this.position.start = this.position.current.clone(); + this.position.delta.set(0, 0); + this.position.drag.set(0, 0); + } + + this.touch = event.type == "touchstart"; + + this.onDragStart(this.position); + + window.addEventListener( + this.touch ? "touchmove" : "mousemove", + this.drag.move, + false + ); + window.addEventListener( + this.touch ? "touchend" : "mouseup", + this.drag.end, + false + ); + }, + + move: (event) => { + if (this.options.calcDelta) { + this.position.old = this.position.current.clone(); + } + + this.getPositionCurrent(event); + + if (this.options.calcDelta) { + this.position.delta = this.position.current + .clone() + .sub(this.position.old); + this.position.drag = this.position.current + .clone() + .sub(this.position.start); + } + + this.onDragMove(this.position); + }, + + end: (event) => { + this.getPositionCurrent(event); + + this.onDragEnd(this.position); + + window.removeEventListener( + this.touch ? "touchmove" : "mousemove", + this.drag.move, + false + ); + window.removeEventListener( + this.touch ? "touchend" : "mouseup", + this.drag.end, + false + ); + }, + }; + + this.onDragStart = () => {}; + this.onDragMove = () => {}; + this.onDragEnd = () => {}; + + this.enable(); + + return this; + } + + enable() { + this.element.addEventListener("touchstart", this.drag.start, false); + this.element.addEventListener("mousedown", this.drag.start, false); + + return this; + } + + disable() { + this.element.removeEventListener("touchstart", this.drag.start, false); + this.element.removeEventListener("mousedown", this.drag.start, false); + + return this; + } + + getPositionCurrent(event) { + const dragEvent = event.touches + ? event.touches[0] || event.changedTouches[0] + : event; + + this.position.current.set(dragEvent.pageX, dragEvent.pageY); + } + + convertPosition(position) { + position.x = (position.x / this.element.offsetWidth) * 2 - 1; + position.y = -((position.y / this.element.offsetHeight) * 2 - 1); + + return position; + } +} + +const STILL = 0; +const PREPARING = 1; +const ROTATING = 2; +const ANIMATING = 3; + +class Controls { + constructor(game) { + this.game = game; + + this.flipConfig = 0; + + this.flipEasings = [ + Easing.Power.Out(3), + Easing.Sine.Out(), + Easing.Back.Out(1.5), + ]; + this.flipSpeeds = [125, 200, 300]; + + this.raycaster = new THREE.Raycaster(); + + const helperMaterial = new THREE.MeshBasicMaterial({ + depthWrite: false, + transparent: true, + opacity: 0, + color: 0x0033ff, + }); + + this.group = new THREE.Object3D(); + this.group.name = "controls"; + this.game.cube.object.add(this.group); + + this.helper = new THREE.Mesh( + new THREE.PlaneBufferGeometry(200, 200), + helperMaterial.clone() + ); + + this.helper.rotation.set(0, Math.PI / 4, 0); + this.game.world.scene.add(this.helper); + + this.edges = new THREE.Mesh( + new THREE.BoxBufferGeometry(1, 1, 1), + helperMaterial.clone() + ); + + this.game.world.scene.add(this.edges); + + this.onSolved = () => {}; + this.onMove = () => {}; + + this.momentum = []; + + this.scramble = null; + this.state = STILL; + this.enabled = false; + + this.initDraggable(); + } + + enable() { + this.draggable.enable(); + this.enabled = true; + } + + disable() { + this.draggable.disable(); + this.enabled = false; + } + + initDraggable() { + this.draggable = new Draggable(this.game.dom.game); + + this.draggable.onDragStart = (position) => { + if (this.scramble !== null) return; + if (this.state === PREPARING || this.state === ROTATING) return; + + this.gettingDrag = this.state === ANIMATING; + + const edgeIntersect = this.getIntersect( + position.current, + this.edges, + false + ); + + if (edgeIntersect !== false) { + this.dragIntersect = this.getIntersect( + position.current, + this.game.cube.cubes, + true + ); + } + + if (edgeIntersect !== false && this.dragIntersect !== false) { + this.dragNormal = edgeIntersect.face.normal.round(); + this.flipType = "layer"; + + this.attach(this.helper, this.edges); + + this.helper.rotation.set(0, 0, 0); + this.helper.position.set(0, 0, 0); + this.helper.lookAt(this.dragNormal); + this.helper.translateZ(0.5); + this.helper.updateMatrixWorld(); + + this.detach(this.helper, this.edges); + } else { + this.dragNormal = new THREE.Vector3(0, 0, 1); + this.flipType = "cube"; + + this.helper.position.set(0, 0, 0); + this.helper.rotation.set(0, Math.PI / 4, 0); + this.helper.updateMatrixWorld(); + } + + let planeIntersect = this.getIntersect( + position.current, + this.helper, + false + ); + if (planeIntersect === false) return; + + this.dragCurrent = this.helper.worldToLocal(planeIntersect.point); + this.dragTotal = new THREE.Vector3(); + this.state = this.state === STILL ? PREPARING : this.state; + }; + + this.draggable.onDragMove = (position) => { + if (this.scramble !== null) return; + if ( + this.state === STILL || + (this.state === ANIMATING && this.gettingDrag === false) + ) + return; + + const planeIntersect = this.getIntersect( + position.current, + this.helper, + false + ); + if (planeIntersect === false) return; + + const point = this.helper.worldToLocal( + planeIntersect.point.clone() + ); + + this.dragDelta = point.clone().sub(this.dragCurrent).setZ(0); + this.dragTotal.add(this.dragDelta); + this.dragCurrent = point; + this.addMomentumPoint(this.dragDelta); + + if (this.state === PREPARING && this.dragTotal.length() > 0.05) { + this.dragDirection = this.getMainAxis(this.dragTotal); + + if (this.flipType === "layer") { + const direction = new THREE.Vector3(); + direction[this.dragDirection] = 1; + + const worldDirection = this.helper + .localToWorld(direction) + .sub(this.helper.position); + const objectDirection = this.edges + .worldToLocal(worldDirection) + .round(); + + this.flipAxis = objectDirection + .cross(this.dragNormal) + .negate(); + + this.selectLayer(this.getLayer(false)); + } else { + const axis = + this.dragDirection != "x" + ? this.dragDirection == "y" && + position.current.x > this.game.world.width / 2 + ? "z" + : "x" + : "y"; + + this.flipAxis = new THREE.Vector3(); + this.flipAxis[axis] = 1 * (axis == "x" ? -1 : 1); + } + + this.flipAngle = 0; + this.state = ROTATING; + } else if (this.state === ROTATING) { + const rotation = this.dragDelta[this.dragDirection]; + + if (this.flipType === "layer") { + this.group.rotateOnAxis(this.flipAxis, rotation); + this.flipAngle += rotation; + } else { + this.edges.rotateOnWorldAxis(this.flipAxis, rotation); + this.game.cube.object.rotation.copy(this.edges.rotation); + this.flipAngle += rotation; + } + } + }; + + this.draggable.onDragEnd = (position) => { + if (this.scramble !== null) return; + if (this.state !== ROTATING) { + this.gettingDrag = false; + this.state = STILL; + return; + } + + this.state = ANIMATING; + + const momentum = this.getMomentum()[this.dragDirection]; + const flip = + Math.abs(momentum) > 0.05 && + Math.abs(this.flipAngle) < Math.PI / 2; + + const angle = flip + ? this.roundAngle( + this.flipAngle + Math.sign(this.flipAngle) * (Math.PI / 4) + ) + : this.roundAngle(this.flipAngle); + + const delta = angle - this.flipAngle; + + if (this.flipType === "layer") { + this.rotateLayer(delta, false, (layer) => { + this.game.storage.saveGame(); + + this.state = this.gettingDrag ? PREPARING : STILL; + this.gettingDrag = false; + + this.checkIsSolved(); + }); + } else { + this.rotateCube(delta, () => { + this.state = this.gettingDrag ? PREPARING : STILL; + this.gettingDrag = false; + }); + } + }; + } + + rotateLayer(rotation, scramble, callback) { + const config = scramble ? 0 : this.flipConfig; + + const easing = this.flipEasings[config]; + const duration = this.flipSpeeds[config]; + const bounce = config == 2 ? this.bounceCube() : () => {}; + + this.rotationTween = new Tween({ + easing: easing, + duration: duration, + onUpdate: (tween) => { + let deltaAngle = tween.delta * rotation; + this.group.rotateOnAxis(this.flipAxis, deltaAngle); + bounce(tween.value, deltaAngle, rotation); + }, + onComplete: () => { + if (!scramble) this.onMove(); + + const layer = this.flipLayer.slice(0); + + this.game.cube.object.rotation.setFromVector3( + this.snapRotation( + this.game.cube.object.rotation.toVector3() + ) + ); + this.group.rotation.setFromVector3( + this.snapRotation(this.group.rotation.toVector3()) + ); + this.deselectLayer(this.flipLayer); + + callback(layer); + }, + }); + } + + bounceCube() { + let fixDelta = true; + + return (progress, delta, rotation) => { + if (progress >= 1) { + if (fixDelta) { + delta = (progress - 1) * rotation; + fixDelta = false; + } + + this.game.cube.object.rotateOnAxis(this.flipAxis, delta); + } + }; + } + + rotateCube(rotation, callback) { + const config = this.flipConfig; + const easing = [ + Easing.Power.Out(4), + Easing.Sine.Out(), + Easing.Back.Out(2), + ][config]; + const duration = [100, 150, 350][config]; + + this.rotationTween = new Tween({ + easing: easing, + duration: duration, + onUpdate: (tween) => { + this.edges.rotateOnWorldAxis( + this.flipAxis, + tween.delta * rotation + ); + this.game.cube.object.rotation.copy(this.edges.rotation); + }, + onComplete: () => { + this.edges.rotation.setFromVector3( + this.snapRotation(this.edges.rotation.toVector3()) + ); + this.game.cube.object.rotation.copy(this.edges.rotation); + callback(); + }, + }); + } + + selectLayer(layer) { + this.group.rotation.set(0, 0, 0); + this.movePieces(layer, this.game.cube.object, this.group); + this.flipLayer = layer; + } + + deselectLayer(layer) { + this.movePieces(layer, this.group, this.game.cube.object); + this.flipLayer = null; + } + + movePieces(layer, from, to) { + from.updateMatrixWorld(); + to.updateMatrixWorld(); + + layer.forEach((index) => { + const piece = this.game.cube.pieces[index]; + + piece.applyMatrix(from.matrixWorld); + from.remove(piece); + piece.applyMatrix(new THREE.Matrix4().getInverse(to.matrixWorld)); + to.add(piece); + }); + } + + getLayer(position) { + const scalar = { 2: 6, 3: 3, 4: 4, 5: 3 }[this.game.cube.size]; + const layer = []; + + let axis; + + if (position === false) { + const piece = this.dragIntersect.object.parent; + + axis = this.getMainAxis(this.flipAxis); + position = piece.position.clone().multiplyScalar(scalar).round(); + } else { + axis = this.getMainAxis(position); + } + + this.game.cube.pieces.forEach((piece) => { + const piecePosition = piece.position + .clone() + .multiplyScalar(scalar) + .round(); + + if (piecePosition[axis] == position[axis]) layer.push(piece.name); + }); + + return layer; + } + + keyboardMove(type, move, callback) { + if (this.state !== STILL) return; + if (this.enabled !== true) return; + + if (type === "LAYER") { + const layer = this.getLayer(move.position); + + this.flipAxis = new THREE.Vector3(); + this.flipAxis[move.axis] = 1; + this.state = ROTATING; + + this.selectLayer(layer); + this.rotateLayer(move.angle, false, (layer) => { + this.game.storage.saveGame(); + this.state = STILL; + this.checkIsSolved(); + }); + } else if (type === "CUBE") { + this.flipAxis = new THREE.Vector3(); + this.flipAxis[move.axis] = 1; + this.state = ROTATING; + + this.rotateCube(move.angle, () => { + this.state = STILL; + }); + } + } + + scrambleCube() { + if (this.scramble == null) { + this.scramble = this.game.scrambler; + this.scramble.callback = + typeof callback !== "function" ? () => {} : callback; + } + + const converted = this.scramble.converted; + const move = converted[0]; + const layer = this.getLayer(move.position); + + this.flipAxis = new THREE.Vector3(); + this.flipAxis[move.axis] = 1; + + this.selectLayer(layer); + this.rotateLayer(move.angle, true, () => { + converted.shift(); + + if (converted.length > 0) { + this.scrambleCube(); + } else { + this.scramble = null; + this.game.storage.saveGame(); + } + }); + } + + getIntersect(position, object, multiple) { + this.raycaster.setFromCamera( + this.draggable.convertPosition(position.clone()), + this.game.world.camera + ); + + const intersect = multiple + ? this.raycaster.intersectObjects(object) + : this.raycaster.intersectObject(object); + + return intersect.length > 0 ? intersect[0] : false; + } + + getMainAxis(vector) { + return Object.keys(vector).reduce((a, b) => + Math.abs(vector[a]) > Math.abs(vector[b]) ? a : b + ); + } + + detach(child, parent) { + child.applyMatrix(parent.matrixWorld); + parent.remove(child); + this.game.world.scene.add(child); + } + + attach(child, parent) { + child.applyMatrix(new THREE.Matrix4().getInverse(parent.matrixWorld)); + this.game.world.scene.remove(child); + parent.add(child); + } + + addMomentumPoint(delta) { + const time = Date.now(); + + this.momentum = this.momentum.filter( + (moment) => time - moment.time < 500 + ); + + if (delta !== false) this.momentum.push({ delta, time }); + } + + getMomentum() { + const points = this.momentum.length; + const momentum = new THREE.Vector2(); + + this.addMomentumPoint(false); + + this.momentum.forEach((point, index) => { + momentum.add(point.delta.multiplyScalar(index / points)); + }); + + return momentum; + } + + roundAngle(angle) { + const round = Math.PI / 2; + return Math.sign(angle) * Math.round(Math.abs(angle) / round) * round; + } + + snapRotation(angle) { + return angle.set( + this.roundAngle(angle.x), + this.roundAngle(angle.y), + this.roundAngle(angle.z) + ); + } + + checkIsSolved() { + const start = performance.now(); + + let solved = true; + const sides = { + "x-": [], + "x+": [], + "y-": [], + "y+": [], + "z-": [], + "z+": [], + }; + + this.game.cube.edges.forEach((edge) => { + const position = edge.parent + .localToWorld(edge.position.clone()) + .sub(this.game.cube.object.position); + + const mainAxis = this.getMainAxis(position); + const mainSign = + position.multiplyScalar(2).round()[mainAxis] < 1 ? "-" : "+"; + + sides[mainAxis + mainSign].push(edge.name); + }); + + Object.keys(sides).forEach((side) => { + if (!sides[side].every((value) => value === sides[side][0])) + solved = false; + }); + + if (solved) this.onSolved(); + } +} + +class Scrambler { + constructor(game) { + this.game = game; + + this.dificulty = 0; + + this.scrambleLength = { + 2: [7, 9, 11], + 3: [20, 25, 30], + 4: [30, 40, 50], + 5: [40, 60, 80], + }; + + this.moves = []; + this.conveted = []; + this.pring = ""; + } + + scramble(scramble) { + let count = 0; + this.moves = typeof scramble !== "undefined" ? scramble.split(" ") : []; + + if (this.moves.length < 1) { + const scrambleLength = + this.scrambleLength[this.game.cube.size][this.dificulty]; + + const faces = this.game.cube.size < 4 ? "UDLRFB" : "UuDdLlRrFfBb"; + const modifiers = ["", "'", "2"]; + const total = + typeof scramble === "undefined" ? scrambleLength : scramble; + + while (count < total) { + const move = + faces[Math.floor(Math.random() * faces.length)] + + modifiers[Math.floor(Math.random() * 3)]; + + if ( + count > 0 && + move.charAt(0) == this.moves[count - 1].charAt(0) + ) + continue; + if ( + count > 1 && + move.charAt(0) == this.moves[count - 2].charAt(0) + ) + continue; + + this.moves.push(move); + count++; + } + } + + this.callback = () => {}; + this.convert(); + this.print = this.moves.join(" "); + + return this; + } + + convert(moves) { + this.converted = []; + + this.moves.forEach((move) => { + const convertedMove = this.convertMove(move); + const modifier = move.charAt(1); + + this.converted.push(convertedMove); + if (modifier == "2") this.converted.push(convertedMove); + }); + } + + convertMove(move) { + const face = move.charAt(0); + const modifier = move.charAt(1); + + const axis = { D: "y", U: "y", L: "x", R: "x", F: "z", B: "z" }[ + face.toUpperCase() + ]; + let row = { D: -1, U: 1, L: -1, R: 1, F: 1, B: -1 }[face.toUpperCase()]; + + if (this.game.cube.size > 3 && face !== face.toLowerCase()) + row = row * 2; + + const position = new THREE.Vector3(); + position[ + { D: "y", U: "y", L: "x", R: "x", F: "z", B: "z" }[ + face.toUpperCase() + ] + ] = row; + + const angle = (Math.PI / 2) * -row * (modifier == "'" ? -1 : 1); + + return { position, axis, angle, name: move }; + } +} + +class Transition { + constructor(game) { + this.game = game; + + this.tweens = {}; + this.durations = {}; + this.data = { + cubeY: -0.2, + cameraZoom: 0.85, + }; + + this.activeTransitions = 0; + } + + init() { + this.game.controls.disable(); + + this.game.cube.object.position.y = this.data.cubeY; + this.game.cube.animator.position.y = 4; + this.game.cube.animator.rotation.x = -Math.PI / 3; + this.game.world.camera.zoom = this.data.cameraZoom; + this.game.world.camera.updateProjectionMatrix(); + + this.tweens.buttons = {}; + this.tweens.timer = []; + this.tweens.title = []; + this.tweens.best = []; + this.tweens.complete = []; + this.tweens.prefs = []; + this.tweens.theme = []; + this.tweens.stats = []; + } + + buttons(show, hide) { + const buttonTween = (button, show) => { + return new Tween({ + target: button.style, + duration: 300, + easing: show ? Easing.Power.Out(2) : Easing.Power.In(3), + from: { opacity: show ? 0 : 1 }, + to: { opacity: show ? 1 : 0 }, + onUpdate: (tween) => { + const translate = show ? 1 - tween.value : tween.value; + button.style.transform = `translate3d(0, ${ + translate * 1.5 + }em, 0)`; + }, + onComplete: () => + (button.style.pointerEvents = show ? "all" : "none"), + }); + }; + + hide.forEach( + (button) => + (this.tweens.buttons[button] = buttonTween( + this.game.dom.buttons[button], + false + )) + ); + + setTimeout( + () => + show.forEach((button) => { + this.tweens.buttons[button] = buttonTween( + this.game.dom.buttons[button], + true + ); + }), + hide ? 500 : 0 + ); + } + + cube(show, theming = false) { + this.activeTransitions++; + + try { + this.tweens.cube.stop(); + } catch (e) {} + const currentY = this.game.cube.animator.position.y; + const currentRotation = this.game.cube.animator.rotation.x; + + this.tweens.cube = new Tween({ + duration: show ? 3000 : 1250, + easing: show ? Easing.Elastic.Out(0.8, 0.6) : Easing.Back.In(1), + onUpdate: (tween) => { + this.game.cube.animator.position.y = show + ? theming + ? 0.9 + (1 - tween.value) * 3.5 + : (1 - tween.value) * 4 + : currentY + tween.value * 4; + + this.game.cube.animator.rotation.x = show + ? ((1 - tween.value) * Math.PI) / 3 + : currentRotation + (tween.value * -Math.PI) / 3; + }, + }); + + if (theming) { + if (show) { + this.game.world.camera.zoom = 0.75; + this.game.world.camera.updateProjectionMatrix(); + } else { + setTimeout(() => { + this.game.world.camera.zoom = this.data.cameraZoom; + this.game.world.camera.updateProjectionMatrix(); + }, 1500); + } + } + + this.durations.cube = show ? 1500 : 1500; + + setTimeout(() => this.activeTransitions--, this.durations.cube); + } + + float() { + try { + this.tweens.float.stop(); + } catch (e) {} + this.tweens.float = new Tween({ + duration: 1500, + easing: Easing.Sine.InOut(), + yoyo: true, + onUpdate: (tween) => { + this.game.cube.holder.position.y = -0.02 + tween.value * 0.04; + this.game.cube.holder.rotation.x = 0.005 - tween.value * 0.01; + this.game.cube.holder.rotation.z = + -this.game.cube.holder.rotation.x; + this.game.cube.holder.rotation.y = + this.game.cube.holder.rotation.x; + + this.game.controls.edges.position.y = + this.game.cube.holder.position.y + + this.game.cube.object.position.y; + }, + }); + } + + zoom(play, time) { + this.activeTransitions++; + + const zoom = play ? 1 : this.data.cameraZoom; + const duration = time > 0 ? Math.max(time, 1500) : 1500; + const rotations = time > 0 ? Math.round(duration / 1500) : 1; + const easing = Easing.Power.InOut(time > 0 ? 2 : 3); + + this.tweens.zoom = new Tween({ + target: this.game.world.camera, + duration: duration, + easing: easing, + to: { zoom: zoom }, + onUpdate: () => { + this.game.world.camera.updateProjectionMatrix(); + }, + }); + + this.tweens.rotate = new Tween({ + target: this.game.cube.animator.rotation, + duration: duration, + easing: easing, + to: { y: -Math.PI * 2 * rotations }, + onComplete: () => { + this.game.cube.animator.rotation.y = 0; + }, + }); + + this.durations.zoom = duration; + + setTimeout(() => this.activeTransitions--, this.durations.zoom); + } + + elevate(complete) { + this.activeTransitions++; + + const cubeY = (this.tweens.elevate = new Tween({ + target: this.game.cube.object.position, + duration: complete ? 1500 : 0, + easing: Easing.Power.InOut(3), + to: { y: complete ? -0.05 : this.data.cubeY }, + })); + + this.durations.elevate = 1500; + + setTimeout(() => this.activeTransitions--, this.durations.elevate); + } + + complete(show, best) { + this.activeTransitions++; + + const text = best + ? this.game.dom.texts.best + : this.game.dom.texts.complete; + + if (text.querySelector("span i") === null) + text.querySelectorAll("span").forEach((span) => + this.splitLetters(span) + ); + + const letters = text.querySelectorAll(".icon, i"); + + this.flipLetters(best ? "best" : "complete", letters, show); + + text.style.opacity = 1; + + const duration = this.durations[best ? "best" : "complete"]; + + if (!show) + setTimeout( + () => (this.game.dom.texts.timer.style.transform = ""), + duration + ); + + setTimeout(() => this.activeTransitions--, duration); + } + + stats(show) { + if (show) this.game.scores.calcStats(); + + this.activeTransitions++; + + this.tweens.stats.forEach((tween) => { + tween.stop(); + tween = null; + }); + + let tweenId = -1; + + const stats = this.game.dom.stats.querySelectorAll(".stats"); + const easing = show ? Easing.Power.Out(2) : Easing.Power.In(3); + + stats.forEach((stat, index) => { + const delay = index * (show ? 80 : 60); + + this.tweens.stats[tweenId++] = new Tween({ + delay: delay, + duration: 400, + easing: easing, + onUpdate: (tween) => { + const translate = show + ? (1 - tween.value) * 2 + : tween.value; + const opacity = show ? tween.value : 1 - tween.value; + + stat.style.transform = `translate3d(0, ${translate}em, 0)`; + stat.style.opacity = opacity; + }, + }); + }); + + this.durations.stats = 0; + + setTimeout(() => this.activeTransitions--, this.durations.stats); + } + + preferences(show) { + this.ranges( + this.game.dom.prefs.querySelectorAll(".range"), + "prefs", + show + ); + } + + theming(show) { + this.ranges( + this.game.dom.theme.querySelectorAll(".range"), + "prefs", + show + ); + } + + ranges(ranges, type, show) { + this.activeTransitions++; + + this.tweens[type].forEach((tween) => { + tween.stop(); + tween = null; + }); + + const easing = show ? Easing.Power.Out(2) : Easing.Power.In(3); + + let tweenId = -1; + let listMax = 0; + + ranges.forEach((range, rangeIndex) => { + const label = range.querySelector(".range__label"); + const track = range.querySelector(".range__track-line"); + const handle = range.querySelector(".range__handle"); + const list = range.querySelectorAll(".range__list div"); + + const delay = rangeIndex * (show ? 120 : 100); + + label.style.opacity = show ? 0 : 1; + track.style.opacity = show ? 0 : 1; + handle.style.opacity = show ? 0 : 1; + handle.style.pointerEvents = show ? "all" : "none"; + + this.tweens[type][tweenId++] = new Tween({ + delay: show ? delay : delay, + duration: 400, + easing: easing, + onUpdate: (tween) => { + const translate = show ? 1 - tween.value : tween.value; + const opacity = show ? tween.value : 1 - tween.value; + + label.style.transform = `translate3d(0, ${translate}em, 0)`; + label.style.opacity = opacity; + }, + }); + + this.tweens[type][tweenId++] = new Tween({ + delay: show ? delay + 100 : delay, + duration: 400, + easing: easing, + onUpdate: (tween) => { + const translate = show ? 1 - tween.value : tween.value; + const scale = show ? tween.value : 1 - tween.value; + const opacity = scale; + + track.style.transform = `translate3d(0, ${translate}em, 0) scale3d(${scale}, 1, 1)`; + track.style.opacity = opacity; + }, + }); + + this.tweens[type][tweenId++] = new Tween({ + delay: show ? delay + 100 : delay, + duration: 400, + easing: easing, + onUpdate: (tween) => { + const translate = show ? 1 - tween.value : tween.value; + const opacity = 1 - translate; + const scale = 0.5 + opacity * 0.5; + + handle.style.transform = `translate3d(0, ${translate}em, 0) scale3d(${scale}, ${scale}, ${scale})`; + handle.style.opacity = opacity; + }, + }); + + list.forEach((listItem, labelIndex) => { + listItem.style.opacity = show ? 0 : 1; + + this.tweens[type][tweenId++] = new Tween({ + delay: show ? delay + 200 + labelIndex * 50 : delay, + duration: 400, + easing: easing, + onUpdate: (tween) => { + const translate = show ? 1 - tween.value : tween.value; + const opacity = show ? tween.value : 1 - tween.value; + + listItem.style.transform = `translate3d(0, ${translate}em, 0)`; + listItem.style.opacity = opacity; + }, + }); + }); + + listMax = list.length > listMax ? list.length - 1 : listMax; + + range.style.opacity = 1; + }); + + this.durations[type] = show + ? (ranges.length - 1) * 100 + 200 + listMax * 50 + 400 + : (ranges.length - 1) * 100 + 400; + + setTimeout(() => this.activeTransitions--, this.durations[type]); + } + + title(show) { + this.activeTransitions++; + + const title = this.game.dom.texts.title; + + if (title.querySelector("span i") === null) + title + .querySelectorAll("span") + .forEach((span) => this.splitLetters(span)); + + const letters = title.querySelectorAll("i"); + + this.flipLetters("title", letters, show); + + title.style.opacity = 1; + + const note = this.game.dom.texts.note; + + this.tweens.title[letters.length] = new Tween({ + target: note.style, + easing: Easing.Sine.InOut(), + duration: show ? 800 : 400, + yoyo: show ? true : null, + from: { + opacity: show ? 0 : parseFloat(getComputedStyle(note).opacity), + }, + to: { opacity: show ? 1 : 0 }, + }); + + setTimeout(() => this.activeTransitions--, this.durations.title); + } + + timer(show) { + this.activeTransitions++; + + const timer = this.game.dom.texts.timer; + + timer.style.opacity = 0; + this.game.timer.convert(); + this.game.timer.setText(); + + this.splitLetters(timer); + const letters = timer.querySelectorAll("i"); + this.flipLetters("timer", letters, show); + + timer.style.opacity = 1; + + setTimeout(() => this.activeTransitions--, this.durations.timer); + } + + splitLetters(element) { + const text = element.innerHTML; + + element.innerHTML = ""; + + text.split("").forEach((letter) => { + const i = document.createElement("i"); + + i.innerHTML = letter; + + element.appendChild(i); + }); + } + + flipLetters(type, letters, show) { + try { + this.tweens[type].forEach((tween) => tween.stop()); + } catch (e) {} + letters.forEach((letter, index) => { + letter.style.opacity = show ? 0 : 1; + + this.tweens[type][index] = new Tween({ + easing: Easing.Sine.Out(), + duration: show ? 800 : 400, + delay: index * 50, + onUpdate: (tween) => { + const rotation = show + ? (1 - tween.value) * -80 + : tween.value * 80; + + letter.style.transform = `rotate3d(0, 1, 0, ${rotation}deg)`; + letter.style.opacity = show ? tween.value : 1 - tween.value; + }, + }); + }); + + this.durations[type] = (letters.length - 1) * 50 + (show ? 800 : 400); + } +} + +class Timer extends Animation { + constructor(game) { + super(false); + + this.game = game; + this.reset(); + } + + start(continueGame) { + this.startTime = continueGame + ? Date.now() - this.deltaTime + : Date.now(); + this.deltaTime = 0; + this.converted = this.convert(); + + super.start(); + } + + reset() { + this.startTime = 0; + this.currentTime = 0; + this.deltaTime = 0; + this.converted = "0:00"; + } + + stop() { + this.currentTime = Date.now(); + this.deltaTime = this.currentTime - this.startTime; + this.convert(); + + super.stop(); + + return { time: this.converted, millis: this.deltaTime }; + } + + update() { + const old = this.converted; + + this.currentTime = Date.now(); + this.deltaTime = this.currentTime - this.startTime; + this.convert(); + + if (this.converted != old) { + localStorage.setItem("theCube_time", this.deltaTime); + this.setText(); + } + } + + convert() { + const seconds = parseInt((this.deltaTime / 1000) % 60); + const minutes = parseInt(this.deltaTime / (1000 * 60)); + + this.converted = minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + } + + setText() { + this.game.dom.texts.timer.innerHTML = this.converted; + } +} + +const RangeHTML = [ + '
', + '
', + '
', + '
', + '
', + "
", + '
', + "
", +].join("\n"); + +document.querySelectorAll("range").forEach((el) => { + const temp = document.createElement("div"); + temp.innerHTML = RangeHTML; + + const range = temp.querySelector(".range"); + const rangeLabel = range.querySelector(".range__label"); + const rangeList = range.querySelector(".range__list"); + + range.setAttribute("name", el.getAttribute("name")); + rangeLabel.innerHTML = el.getAttribute("title"); + + if (el.hasAttribute("color")) { + range.classList.add("range--type-color"); + range.classList.add("range--color-" + el.getAttribute("name")); + } + + if (el.hasAttribute("list")) { + el.getAttribute("list") + .split(",") + .forEach((listItemText) => { + const listItem = document.createElement("div"); + listItem.innerHTML = listItemText; + rangeList.appendChild(listItem); + }); + } + + el.parentNode.replaceChild(range, el); +}); + +class Range { + constructor(name, options) { + options = Object.assign( + { + range: [0, 1], + value: 0, + step: 0, + onUpdate: () => {}, + onComplete: () => {}, + }, + options || {} + ); + + this.element = document.querySelector('.range[name="' + name + '"]'); + this.track = this.element.querySelector(".range__track"); + this.handle = this.element.querySelector(".range__handle"); + this.list = [].slice.call( + this.element.querySelectorAll(".range__list div") + ); + + this.value = options.value; + this.min = options.range[0]; + this.max = options.range[1]; + this.step = options.step; + + this.onUpdate = options.onUpdate; + this.onComplete = options.onComplete; + + this.setValue(this.value); + + this.initDraggable(); + } + + setValue(value) { + this.value = this.round(this.limitValue(value)); + this.setHandlePosition(); + } + + initDraggable() { + let current; + + this.draggable = new Draggable(this.handle, { calcDelta: true }); + + this.draggable.onDragStart = (position) => { + current = this.positionFromValue(this.value); + this.handle.style.left = current + "px"; + }; + + this.draggable.onDragMove = (position) => { + current = this.limitPosition(current + position.delta.x); + this.value = this.round(this.valueFromPosition(current)); + this.setHandlePosition(); + + this.onUpdate(this.value); + }; + + this.draggable.onDragEnd = (position) => { + this.onComplete(this.value); + }; + } + + round(value) { + if (this.step < 1) return value; + + return ( + Math.round((value - this.min) / this.step) * this.step + this.min + ); + } + + limitValue(value) { + const max = Math.max(this.max, this.min); + const min = Math.min(this.max, this.min); + + return Math.min(Math.max(value, min), max); + } + + limitPosition(position) { + return Math.min(Math.max(position, 0), this.track.offsetWidth); + } + + percentsFromValue(value) { + return (value - this.min) / (this.max - this.min); + } + + valueFromPosition(position) { + return ( + this.min + + (this.max - this.min) * (position / this.track.offsetWidth) + ); + } + + positionFromValue(value) { + return this.percentsFromValue(value) * this.track.offsetWidth; + } + + setHandlePosition() { + this.handle.style.left = this.percentsFromValue(this.value) * 100 + "%"; + } +} + +class Preferences { + constructor(game) { + this.game = game; + } + + init() { + this.ranges = { + size: new Range("size", { + value: this.game.cube.size, + range: [2, 5], + step: 1, + onUpdate: (value) => { + this.game.cube.size = value; + + this.game.preferences.ranges.scramble.list.forEach( + (item, i) => { + item.innerHTML = + this.game.scrambler.scrambleLength[ + this.game.cube.size + ][i]; + } + ); + }, + onComplete: () => this.game.storage.savePreferences(), + }), + + flip: new Range("flip", { + value: this.game.controls.flipConfig, + range: [0, 2], + step: 1, + onUpdate: (value) => { + this.game.controls.flipConfig = value; + }, + onComplete: () => this.game.storage.savePreferences(), + }), + + scramble: new Range("scramble", { + value: this.game.scrambler.dificulty, + range: [0, 2], + step: 1, + onUpdate: (value) => { + this.game.scrambler.dificulty = value; + }, + onComplete: () => this.game.storage.savePreferences(), + }), + + fov: new Range("fov", { + value: this.game.world.fov, + range: [2, 45], + onUpdate: (value) => { + this.game.world.fov = value; + this.game.world.resize(); + }, + onComplete: () => this.game.storage.savePreferences(), + }), + + theme: new Range("theme", { + value: { cube: 0, erno: 1, dust: 2, camo: 3, rain: 4 }[ + this.game.themes.theme + ], + range: [0, 4], + step: 1, + onUpdate: (value) => { + const theme = ["cube", "erno", "dust", "camo", "rain"][ + value + ]; + this.game.themes.setTheme(theme); + }, + onComplete: () => this.game.storage.savePreferences(), + }), + + hue: new Range("hue", { + value: 0, + range: [0, 360], + onUpdate: (value) => this.game.themeEditor.updateHSL(), + onComplete: () => this.game.storage.savePreferences(), + }), + + saturation: new Range("saturation", { + value: 100, + range: [0, 100], + onUpdate: (value) => this.game.themeEditor.updateHSL(), + onComplete: () => this.game.storage.savePreferences(), + }), + + lightness: new Range("lightness", { + value: 50, + range: [0, 100], + onUpdate: (value) => this.game.themeEditor.updateHSL(), + onComplete: () => this.game.storage.savePreferences(), + }), + }; + + this.ranges.scramble.list.forEach((item, i) => { + item.innerHTML = + this.game.scrambler.scrambleLength[this.game.cube.size][i]; + }); + } +} + +class Confetti { + constructor(game) { + this.game = game; + this.started = 0; + + this.options = { + speed: { min: 0.0011, max: 0.0022 }, + revolution: { min: 0.01, max: 0.05 }, + size: { min: 0.1, max: 0.15 }, + colors: [0x41aac8, 0x82ca38, 0xffef48, 0xef3923, 0xff8c0a], + }; + + this.geometry = new THREE.PlaneGeometry(1, 1); + this.material = new THREE.MeshLambertMaterial({ + side: THREE.DoubleSide, + }); + + this.holders = [ + new ConfettiStage(this.game, this, 1, 20), + new ConfettiStage(this.game, this, -1, 30), + ]; + } + + start() { + if (this.started > 0) return; + + this.holders.forEach((holder) => { + this.game.world.scene.add(holder.holder); + holder.start(); + this.started++; + }); + } + + stop() { + if (this.started == 0) return; + + this.holders.forEach((holder) => { + holder.stop(() => { + this.game.world.scene.remove(holder.holder); + this.started--; + }); + }); + } + + updateColors(colors) { + this.holders.forEach((holder) => { + holder.options.colors.forEach((color, index) => { + holder.options.colors[index] = + colors[["D", "F", "R", "B", "L"][index]]; + }); + }); + } +} + +class ConfettiStage extends Animation { + constructor(game, parent, distance, count) { + super(false); + + this.game = game; + this.parent = parent; + + this.distanceFromCube = distance; + + this.count = count; + this.particles = []; + + this.holder = new THREE.Object3D(); + this.holder.rotation.copy(this.game.world.camera.rotation); + + this.object = new THREE.Object3D(); + this.holder.add(this.object); + + this.resizeViewport = this.resizeViewport.bind(this); + this.game.world.onResize.push(this.resizeViewport); + this.resizeViewport(); + + this.geometry = this.parent.geometry; + this.material = this.parent.material; + + this.options = this.parent.options; + + let i = this.count; + while (i--) this.particles.push(new Particle(this)); + } + + start() { + this.time = performance.now(); + this.playing = true; + + let i = this.count; + while (i--) this.particles[i].reset(); + + super.start(); + } + + stop(callback) { + this.playing = false; + this.completed = 0; + this.callback = callback; + } + + reset() { + super.stop(); + + this.callback(); + } + + update() { + const now = performance.now(); + const delta = now - this.time; + this.time = now; + + let i = this.count; + + while (i--) + if (!this.particles[i].completed) this.particles[i].update(delta); + + if (!this.playing && this.completed == this.count) this.reset(); + } + + resizeViewport() { + const fovRad = this.game.world.camera.fov * THREE.Math.DEG2RAD; + + this.height = + 2 * + Math.tan(fovRad / 2) * + (this.game.world.camera.position.length() - this.distanceFromCube); + this.width = this.height * this.game.world.camera.aspect; + + const scale = 1 / this.game.transition.data.cameraZoom; + + this.width *= scale; + this.height *= scale; + + this.object.position.z = this.distanceFromCube; + this.object.position.y = this.height / 2; + } +} + +class Particle { + constructor(confetti) { + this.confetti = confetti; + this.options = this.confetti.options; + + this.velocity = new THREE.Vector3(); + this.force = new THREE.Vector3(); + + this.mesh = new THREE.Mesh( + this.confetti.geometry, + this.confetti.material.clone() + ); + this.confetti.object.add(this.mesh); + + this.size = THREE.Math.randFloat( + this.options.size.min, + this.options.size.max + ); + this.mesh.scale.set(this.size, this.size, this.size); + + return this; + } + + reset(randomHeight = true) { + this.completed = false; + + this.color = new THREE.Color( + this.options.colors[ + Math.floor(Math.random() * this.options.colors.length) + ] + ); + this.mesh.material.color.set(this.color); + + this.speed = + THREE.Math.randFloat( + this.options.speed.min, + this.options.speed.max + ) * -1; + this.mesh.position.x = THREE.Math.randFloat( + -this.confetti.width / 2, + this.confetti.width / 2 + ); + this.mesh.position.y = randomHeight + ? THREE.Math.randFloat(this.size, this.confetti.height + this.size) + : this.size; + + this.revolutionSpeed = THREE.Math.randFloat( + this.options.revolution.min, + this.options.revolution.max + ); + this.revolutionAxis = ["x", "y", "z"][Math.floor(Math.random() * 3)]; + this.mesh.rotation.set( + (Math.random() * Math.PI) / 3, + (Math.random() * Math.PI) / 3, + (Math.random() * Math.PI) / 3 + ); + } + + stop() { + this.completed = true; + this.confetti.completed++; + } + + update(delta) { + this.mesh.position.y += this.speed * delta; + this.mesh.rotation[this.revolutionAxis] += this.revolutionSpeed; + + if (this.mesh.position.y < -this.confetti.height - this.size) + this.confetti.playing ? this.reset(false) : this.stop(); + } +} + +class Scores { + constructor(game) { + this.game = game; + + this.data = { + 2: { + scores: [], + solves: 0, + best: 0, + worst: 0, + }, + 3: { + scores: [], + solves: 0, + best: 0, + worst: 0, + }, + 4: { + scores: [], + solves: 0, + best: 0, + worst: 0, + }, + 5: { + scores: [], + solves: 0, + best: 0, + worst: 0, + }, + }; + } + + addScore(time) { + const data = this.data[this.game.cube.sizeGenerated]; + + data.scores.push(time); + data.solves++; + + if (data.scores.lenght > 100) data.scores.shift(); + + let bestTime = false; + + if (time < data.best || data.best === 0) { + data.best = time; + bestTime = true; + } + + if (time > data.worst) data.worst = time; + + this.game.storage.saveScores(); + + return bestTime; + } + + calcStats() { + const s = this.game.cube.sizeGenerated; + const data = this.data[s]; + + this.setStat("cube-size", `${s}x${s}x${s}`); + this.setStat("total-solves", data.solves); + this.setStat("best-time", this.convertTime(data.best)); + this.setStat("worst-time", this.convertTime(data.worst)); + this.setStat("average-5", this.getAverage(5)); + this.setStat("average-12", this.getAverage(12)); + this.setStat("average-25", this.getAverage(25)); + } + + setStat(name, value) { + if (value === 0) value = "-"; + + this.game.dom.stats.querySelector( + `.stats[name="${name}"] b` + ).innerHTML = value; + } + + getAverage(count) { + const data = this.data[this.game.cube.sizeGenerated]; + + if (data.scores.length < count) return 0; + + return this.convertTime( + data.scores.slice(-count).reduce((a, b) => a + b, 0) / count + ); + } + + convertTime(time) { + if (time <= 0) return 0; + + const seconds = parseInt((time / 1000) % 60); + const minutes = parseInt(time / (1000 * 60)); + + return minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + } +} + +class Storage { + constructor(game) { + this.game = game; + + const userVersion = localStorage.getItem("theCube_version"); + + if (!userVersion || userVersion !== window.gameVersion) { + this.clearGame(); + this.clearPreferences(); + this.migrateScores(); + localStorage.setItem("theCube_version", window.gameVersion); + } + } + + init() { + this.loadPreferences(); + this.loadScores(); + } + + loadGame() { + try { + const gameInProgress = + localStorage.getItem("theCube_playing") === "true"; + + if (!gameInProgress) throw new Error(); + + const gameCubeData = JSON.parse( + localStorage.getItem("theCube_savedState") + ); + const gameTime = parseInt(localStorage.getItem("theCube_time")); + + if (!gameCubeData || gameTime === null) throw new Error(); + if (gameCubeData.size !== this.game.cube.sizeGenerated) + throw new Error(); + + this.game.cube.loadFromData(gameCubeData); + + this.game.timer.deltaTime = gameTime; + + this.game.saved = true; + } catch (e) { + this.game.saved = false; + } + } + + saveGame() { + const gameInProgress = true; + const gameCubeData = { names: [], positions: [], rotations: [] }; + const gameTime = this.game.timer.deltaTime; + + gameCubeData.size = this.game.cube.sizeGenerated; + + this.game.cube.pieces.forEach((piece) => { + gameCubeData.names.push(piece.name); + gameCubeData.positions.push(piece.position); + gameCubeData.rotations.push(piece.rotation.toVector3()); + }); + + localStorage.setItem("theCube_playing", gameInProgress); + localStorage.setItem( + "theCube_savedState", + JSON.stringify(gameCubeData) + ); + localStorage.setItem("theCube_time", gameTime); + } + + clearGame() { + localStorage.removeItem("theCube_playing"); + localStorage.removeItem("theCube_savedState"); + localStorage.removeItem("theCube_time"); + } + + loadScores() { + try { + const scoresData = JSON.parse( + localStorage.getItem("theCube_scores") + ); + + if (!scoresData) throw new Error(); + + this.game.scores.data = scoresData; + } catch (e) {} + } + + saveScores() { + const scoresData = this.game.scores.data; + + localStorage.setItem("theCube_scores", JSON.stringify(scoresData)); + } + + clearScores() { + localStorage.removeItem("theCube_scores"); + } + + migrateScores() { + try { + const scoresData = JSON.parse( + localStorage.getItem("theCube_scoresData") + ); + const scoresBest = parseInt( + localStorage.getItem("theCube_scoresBest") + ); + const scoresWorst = parseInt( + localStorage.getItem("theCube_scoresWorst") + ); + const scoresSolves = parseInt( + localStorage.getItem("theCube_scoresSolves") + ); + + if (!scoresData || !scoresBest || !scoresSolves || !scoresWorst) + return false; + + this.game.scores.data[3].scores = scoresData; + this.game.scores.data[3].best = scoresBest; + this.game.scores.data[3].solves = scoresSolves; + this.game.scores.data[3].worst = scoresWorst; + + localStorage.removeItem("theCube_scoresData"); + localStorage.removeItem("theCube_scoresBest"); + localStorage.removeItem("theCube_scoresWorst"); + localStorage.removeItem("theCube_scoresSolves"); + } catch (e) {} + } + + loadPreferences() { + try { + const preferences = JSON.parse( + localStorage.getItem("theCube_preferences") + ); + + if (!preferences) throw new Error(); + + this.game.cube.size = parseInt(preferences.cubeSize); + this.game.controls.flipConfig = parseInt(preferences.flipConfig); + this.game.scrambler.dificulty = parseInt(preferences.dificulty); + + this.game.world.fov = parseFloat(preferences.fov); + this.game.world.resize(); + + this.game.themes.colors = preferences.colors; + this.game.themes.setTheme(preferences.theme); + + return true; + } catch (e) { + this.game.cube.size = 3; + this.game.controls.flipConfig = 0; + this.game.scrambler.dificulty = 1; + + this.game.world.fov = 10; + this.game.world.resize(); + + this.game.themes.setTheme("cube"); + + this.savePreferences(); + + return false; + } + } + + savePreferences() { + const preferences = { + cubeSize: this.game.cube.size, + flipConfig: this.game.controls.flipConfig, + dificulty: this.game.scrambler.dificulty, + fov: this.game.world.fov, + theme: this.game.themes.theme, + colors: this.game.themes.colors, + }; + + localStorage.setItem( + "theCube_preferences", + JSON.stringify(preferences) + ); + } + + clearPreferences() { + localStorage.removeItem("theCube_preferences"); + } +} + +class Themes { + constructor(game) { + this.game = game; + this.theme = null; + + this.defaults = { + cube: { + U: 0xfff7ff, // white + D: 0xffef48, // yellow + F: 0xef3923, // red + R: 0x41aac8, // blue + B: 0xff8c0a, // orange + L: 0x82ca38, // green + P: 0x08101a, // piece + G: 0xd1d5db, // background + }, + erno: { + U: 0xffffff, + D: 0xffd500, + F: 0xc41e3a, + R: 0x0051ba, + B: 0xff5800, + L: 0x009e60, + P: 0x08101a, + G: 0x8abdff, + }, + dust: { + U: 0xfff6eb, + D: 0xe7c48d, + F: 0x8f253e, + R: 0x607e69, + B: 0xbe6f62, + L: 0x849f5d, + P: 0x08101a, + G: 0xe7c48d, + }, + camo: { + U: 0xfff6eb, + D: 0xbfb672, + F: 0x37241c, + R: 0x718456, + B: 0x805831, + L: 0x37431d, + P: 0x08101a, + G: 0xbfb672, + }, + rain: { + U: 0xfafaff, + D: 0xedb92d, + F: 0xce2135, + R: 0x449a89, + B: 0xec582f, + L: 0xa3a947, + P: 0x08101a, + G: 0x87b9ac, + }, + }; + + this.colors = JSON.parse(JSON.stringify(this.defaults)); + } + + getColors() { + return this.colors[this.theme]; + } + + setTheme(theme = false, force = false) { + if (theme === this.theme && force === false) return; + if (theme !== false) this.theme = theme; + + const colors = this.getColors(); + + this.game.dom.prefs + .querySelectorAll(".range__handle div") + .forEach((range) => { + range.style.background = + "#" + colors.R.toString(16).padStart(6, "0"); + }); + + this.game.cube.updateColors(colors); + + this.game.confetti.updateColors(colors); + + this.game.dom.back.style.background = + "#" + colors.G.toString(16).padStart(6, "0"); + } +} + +class ThemeEditor { + constructor(game) { + this.game = game; + + this.editColor = "R"; + + this.getPieceColor = this.getPieceColor.bind(this); + } + + colorFromHSL(h, s, l) { + h = Math.round(h); + s = Math.round(s); + l = Math.round(l); + + return new THREE.Color(`hsl(${h}, ${s}%, ${l}%)`); + } + + setHSL(color = null, animate = false) { + this.editColor = color === null ? "R" : color; + + const hsl = new THREE.Color( + this.game.themes.getColors()[this.editColor] + ); + + const { h, s, l } = hsl.getHSL(hsl); + const { hue, saturation, lightness } = this.game.preferences.ranges; + + if (animate) { + const ho = hue.value / 360; + const so = saturation.value / 100; + const lo = lightness.value / 100; + + const colorOld = this.colorFromHSL( + hue.value, + saturation.value, + lightness.value + ); + + if (this.tweenHSL) this.tweenHSL.stop(); + + this.tweenHSL = new Tween({ + duration: 200, + easing: Easing.Sine.Out(), + onUpdate: (tween) => { + hue.setValue((ho + (h - ho) * tween.value) * 360); + saturation.setValue((so + (s - so) * tween.value) * 100); + lightness.setValue((lo + (l - lo) * tween.value) * 100); + + const colorTween = colorOld.clone().lerp(hsl, tween.value); + + const colorTweenStyle = colorTween.getStyle(); + const colorTweenHex = colorTween.getHSL(colorTween); + + hue.handle.style.color = colorTweenStyle; + saturation.handle.style.color = colorTweenStyle; + lightness.handle.style.color = colorTweenStyle; + + saturation.track.style.color = this.colorFromHSL( + colorTweenHex.h * 360, + 100, + 50 + ).getStyle(); + lightness.track.style.color = this.colorFromHSL( + colorTweenHex.h * 360, + colorTweenHex.s * 100, + 50 + ).getStyle(); + + this.game.dom.theme.style.display = "none"; + this.game.dom.theme.offsetHeight; + this.game.dom.theme.style.display = ""; + }, + onComplete: () => { + this.updateHSL(); + this.game.storage.savePreferences(); + }, + }); + } else { + hue.setValue(h * 360); + saturation.setValue(s * 100); + lightness.setValue(l * 100); + + this.updateHSL(); + this.game.storage.savePreferences(); + } + } + + updateHSL() { + const { hue, saturation, lightness } = this.game.preferences.ranges; + + const h = hue.value; + const s = saturation.value; + const l = lightness.value; + + const color = this.colorFromHSL(h, s, l).getStyle(); + + hue.handle.style.color = color; + saturation.handle.style.color = color; + lightness.handle.style.color = color; + + saturation.track.style.color = this.colorFromHSL(h, 100, 50).getStyle(); + lightness.track.style.color = this.colorFromHSL(h, s, 50).getStyle(); + + this.game.dom.theme.style.display = "none"; + this.game.dom.theme.offsetHeight; + this.game.dom.theme.style.display = ""; + + const theme = this.game.themes.theme; + + this.game.themes.colors[theme][this.editColor] = this.colorFromHSL( + h, + s, + l + ).getHex(); + this.game.themes.setTheme(); + } + + colorPicker(enable) { + if (enable) { + this.game.dom.game.addEventListener( + "click", + this.getPieceColor, + false + ); + } else { + this.game.dom.game.removeEventListener( + "click", + this.getPieceColor, + false + ); + } + } + + getPieceColor(event) { + const clickEvent = event.touches + ? event.touches[0] || event.changedTouches[0] + : event; + + const clickPosition = new THREE.Vector2( + clickEvent.pageX, + clickEvent.pageY + ); + + let edgeIntersect = this.game.controls.getIntersect( + clickPosition, + this.game.cube.edges, + true + ); + let pieceIntersect = this.game.controls.getIntersect( + clickPosition, + this.game.cube.cubes, + true + ); + + if (edgeIntersect !== false) { + const edge = edgeIntersect.object; + + const position = edge.parent + .localToWorld(edge.position.clone()) + .sub(this.game.cube.object.position) + .sub(this.game.cube.animator.position); + + const mainAxis = this.game.controls.getMainAxis(position); + if (position.multiplyScalar(2).round()[mainAxis] < 1) + edgeIntersect = false; + } + + const name = edgeIntersect + ? edgeIntersect.object.name + : pieceIntersect + ? "P" + : "G"; + + this.setHSL(name, true); + } + + resetTheme() { + this.game.themes.colors[this.game.themes.theme] = JSON.parse( + JSON.stringify(this.game.themes.defaults[this.game.themes.theme]) + ); + + this.game.themes.setTheme(); + + this.setHSL(this.editColor, true); + } +} + +const States = { + 3: { + checkerboard: { + names: [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, + ], + positions: [ + { x: 1 / 3, y: -1 / 3, z: 1 / 3 }, + { x: -1 / 3, y: 1 / 3, z: 0 }, + { x: 1 / 3, y: -1 / 3, z: -1 / 3 }, + { x: -1 / 3, y: 0, z: -1 / 3 }, + { x: 1 / 3, y: 0, z: 0 }, + { x: -1 / 3, y: 0, z: 1 / 3 }, + { x: 1 / 3, y: 1 / 3, z: 1 / 3 }, + { x: -1 / 3, y: -1 / 3, z: 0 }, + { x: 1 / 3, y: 1 / 3, z: -1 / 3 }, + { x: 0, y: 1 / 3, z: -1 / 3 }, + { x: 0, y: -1 / 3, z: 0 }, + { x: 0, y: 1 / 3, z: 1 / 3 }, + { x: 0, y: 0, z: 1 / 3 }, + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: -1 / 3 }, + { x: 0, y: -1 / 3, z: -1 / 3 }, + { x: 0, y: 1 / 3, z: 0 }, + { x: 0, y: -1 / 3, z: 1 / 3 }, + { x: -1 / 3, y: -1 / 3, z: 1 / 3 }, + { x: 1 / 3, y: 1 / 3, z: 0 }, + { x: -1 / 3, y: -1 / 3, z: -1 / 3 }, + { x: 1 / 3, y: 0, z: -1 / 3 }, + { x: -1 / 3, y: 0, z: 0 }, + { x: 1 / 3, y: 0, z: 1 / 3 }, + { x: -1 / 3, y: 1 / 3, z: 1 / 3 }, + { x: 1 / 3, y: -1 / 3, z: 0 }, + { x: -1 / 3, y: 1 / 3, z: -1 / 3 }, + ], + rotations: [ + { x: -Math.PI, y: 0, z: Math.PI }, + { x: Math.PI, y: 0, z: 0 }, + { x: -Math.PI, y: 0, z: Math.PI }, + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: Math.PI }, + { x: 0, y: 0, z: 0 }, + { x: -Math.PI, y: 0, z: Math.PI }, + { x: Math.PI, y: 0, z: 0 }, + { x: -Math.PI, y: 0, z: Math.PI }, + { x: 0, y: 0, z: Math.PI }, + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: Math.PI }, + { x: -Math.PI, y: 0, z: 0 }, + { x: Math.PI, y: 0, z: Math.PI }, + { x: Math.PI, y: 0, z: 0 }, + { x: 0, y: 0, z: Math.PI }, + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: Math.PI }, + { x: Math.PI, y: 0, z: Math.PI }, + { x: -Math.PI, y: 0, z: 0 }, + { x: Math.PI, y: 0, z: Math.PI }, + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: Math.PI }, + { x: 0, y: 0, z: 0 }, + { x: Math.PI, y: 0, z: Math.PI }, + { x: -Math.PI, y: 0, z: 0 }, + { x: Math.PI, y: 0, z: Math.PI }, + ], + size: 3, + }, + }, +}; + +class IconsConverter { + constructor(options) { + options = Object.assign( + { + tagName: "icon", + className: "icon", + styles: false, + icons: {}, + observe: false, + convert: false, + }, + options || {} + ); + + this.tagName = options.tagName; + this.className = options.className; + this.icons = options.icons; + + this.svgTag = document.createElementNS( + "http://www.w3.org/2000/svg", + "svg" + ); + this.svgTag.setAttribute("class", this.className); + + if (options.styles) this.addStyles(); + if (options.convert) this.convertAllIcons(); + + if (options.observe) { + const MutationObserver = + window.MutationObserver || window.WebKitMutationObserver; + this.observer = new MutationObserver((mutations) => { + this.convertAllIcons(); + }); + this.observer.observe(document.documentElement, { + childList: true, + subtree: true, + }); + } + + return this; + } + + convertAllIcons() { + document.querySelectorAll(this.tagName).forEach((icon) => { + this.convertIcon(icon); + }); + } + + convertIcon(icon) { + const svgData = this.icons[icon.attributes[0].localName]; + + if (typeof svgData === "undefined") return; + + const svg = this.svgTag.cloneNode(true); + const viewBox = svgData.viewbox.split(" "); + + svg.setAttributeNS(null, "viewBox", svgData.viewbox); + svg.style.width = viewBox[2] / viewBox[3] + "em"; + svg.style.height = "1em"; + svg.innerHTML = svgData.content; + + icon.parentNode.replaceChild(svg, icon); + } + + addStyles() { + const style = document.createElement("style"); + style.innerHTML = `.${this.className} { display: inline-block; font-size: inherit; overflow: visible; vertical-align: -0.125em; preserveAspectRatio: none; }`; + document.head.appendChild(style); + } +} + +const Icons = new IconsConverter({ + icons: { + settings: { + viewbox: "0 0 512 512", + content: + '', + }, + back: { + viewbox: "0 0 512 512", + content: + '', + }, + trophy: { + viewbox: "0 0 576 512", + content: + '', + }, + cancel: { + viewbox: "0 0 352 512", + content: + '', + }, + theme: { + viewbox: "0 0 512 512", + content: + '', + }, + reset: { + viewbox: "0 0 512 512", + content: + '', + }, + trash: { + viewbox: "0 0 448 512", + content: + '', + }, + }, + + convert: true, +}); + +const STATE = { + Menu: 0, + Playing: 1, + Complete: 2, + Stats: 3, + Prefs: 4, + Theme: 5, +}; + +const BUTTONS = { + Menu: ["stats", "prefs"], + Playing: ["back"], + Complete: [], + Stats: [], + Prefs: ["back", "theme"], + Theme: ["back", "reset"], + None: [], +}; + +const SHOW = true; +const HIDE = false; + +class Game { + constructor() { + this.dom = { + ui: document.querySelector(".ui"), + game: document.querySelector(".ui__game"), + back: document.querySelector(".ui__background"), + prefs: document.querySelector(".ui__prefs"), + theme: document.querySelector(".ui__theme"), + stats: document.querySelector(".ui__stats"), + texts: { + title: document.querySelector(".text--title"), + note: document.querySelector(".text--note"), + timer: document.querySelector(".text--timer"), + complete: document.querySelector(".text--complete"), + best: document.querySelector(".text--best-time"), + theme: document.querySelector(".text--theme"), + }, + buttons: { + prefs: document.querySelector(".btn--prefs"), + back: document.querySelector(".btn--back"), + stats: document.querySelector(".btn--stats"), + reset: document.querySelector(".btn--reset"), + theme: document.querySelector(".btn--theme"), + }, + }; + + this.world = new World(this); + this.cube = new Cube(this); + this.controls = new Controls(this); + this.scrambler = new Scrambler(this); + this.transition = new Transition(this); + this.timer = new Timer(this); + this.preferences = new Preferences(this); + this.scores = new Scores(this); + this.storage = new Storage(this); + this.confetti = new Confetti(this); + this.themes = new Themes(this); + this.themeEditor = new ThemeEditor(this); + + this.initActions(); + + this.state = STATE.Menu; + this.newGame = false; + this.saved = false; + + this.storage.init(); + this.preferences.init(); + this.cube.init(); + this.transition.init(); + + this.storage.loadGame(); + this.scores.calcStats(); + + setTimeout(() => { + this.transition.float(); + this.transition.cube(SHOW); + + setTimeout(() => this.transition.title(SHOW), 700); + setTimeout( + () => this.transition.buttons(BUTTONS.Menu, BUTTONS.None), + 1000 + ); + }, 500); + } + + initActions() { + let tappedTwice = false; + + this.dom.game.addEventListener( + "click", + (event) => { + if (this.transition.activeTransitions > 0) return; + if (this.state === STATE.Playing) return; + + if (this.state === STATE.Menu) { + if (!tappedTwice) { + tappedTwice = true; + setTimeout(() => (tappedTwice = false), 300); + return false; + } + + this.game(SHOW); + } else if (this.state === STATE.Complete) { + this.complete(HIDE); + } else if (this.state === STATE.Stats) { + this.stats(HIDE); + } + }, + false + ); + + this.controls.onMove = () => { + if (this.newGame) { + this.timer.start(true); + this.newGame = false; + } + }; + + this.dom.buttons.back.onclick = (event) => { + if (this.transition.activeTransitions > 0) return; + + if (this.state === STATE.Playing) { + this.game(HIDE); + } else if (this.state === STATE.Prefs) { + this.prefs(HIDE); + } else if (this.state === STATE.Theme) { + this.theme(HIDE); + } + }; + + this.dom.buttons.reset.onclick = (event) => { + if (this.state === STATE.Theme) { + this.themeEditor.resetTheme(); + } + }; + + this.dom.buttons.prefs.onclick = (event) => this.prefs(SHOW); + + this.dom.buttons.theme.onclick = (event) => this.theme(SHOW); + + this.dom.buttons.stats.onclick = (event) => this.stats(SHOW); + + this.controls.onSolved = () => this.complete(SHOW); + } + + game(show) { + if (show) { + if (!this.saved) { + this.scrambler.scramble(); + this.controls.scrambleCube(); + this.newGame = true; + } + + const duration = this.saved + ? 0 + : this.scrambler.converted.length * + (this.controls.flipSpeeds[0] + 10); + + this.state = STATE.Playing; + this.saved = true; + + this.transition.buttons(BUTTONS.None, BUTTONS.Menu); + + this.transition.zoom(STATE.Playing, duration); + this.transition.title(HIDE); + + setTimeout(() => { + this.transition.timer(SHOW); + this.transition.buttons(BUTTONS.Playing, BUTTONS.None); + }, this.transition.durations.zoom - 1000); + + setTimeout(() => { + this.controls.enable(); + if (!this.newGame) this.timer.start(true); + }, this.transition.durations.zoom); + } else { + this.state = STATE.Menu; + + this.transition.buttons(BUTTONS.Menu, BUTTONS.Playing); + + this.transition.zoom(STATE.Menu, 0); + + this.controls.disable(); + if (!this.newGame) this.timer.stop(); + this.transition.timer(HIDE); + + setTimeout( + () => this.transition.title(SHOW), + this.transition.durations.zoom - 1000 + ); + + this.playing = false; + this.controls.disable(); + } + } + + prefs(show) { + if (show) { + if (this.transition.activeTransitions > 0) return; + + this.state = STATE.Prefs; + + this.transition.buttons(BUTTONS.Prefs, BUTTONS.Menu); + + this.transition.title(HIDE); + this.transition.cube(HIDE); + + setTimeout(() => this.transition.preferences(SHOW), 1000); + } else { + this.cube.resize(); + + this.state = STATE.Menu; + + this.transition.buttons(BUTTONS.Menu, BUTTONS.Prefs); + + this.transition.preferences(HIDE); + + setTimeout(() => this.transition.cube(SHOW), 500); + setTimeout(() => this.transition.title(SHOW), 1200); + } + } + + theme(show) { + this.themeEditor.colorPicker(show); + + if (show) { + if (this.transition.activeTransitions > 0) return; + + this.cube.loadFromData(States["3"]["checkerboard"]); + + this.themeEditor.setHSL(null, false); + + this.state = STATE.Theme; + + this.transition.buttons(BUTTONS.Theme, BUTTONS.Prefs); + + this.transition.preferences(HIDE); + + setTimeout(() => this.transition.cube(SHOW, true), 500); + setTimeout(() => this.transition.theming(SHOW), 1000); + } else { + this.state = STATE.Prefs; + + this.transition.buttons(BUTTONS.Prefs, BUTTONS.Theme); + + this.transition.cube(HIDE, true); + this.transition.theming(HIDE); + + setTimeout(() => this.transition.preferences(SHOW), 1000); + setTimeout(() => { + const gameCubeData = JSON.parse( + localStorage.getItem("theCube_savedState") + ); + + if (!gameCubeData) { + this.cube.resize(true); + return; + } + + this.cube.loadFromData(gameCubeData); + }, 1500); + } + } + + stats(show) { + if (show) { + if (this.transition.activeTransitions > 0) return; + + this.state = STATE.Stats; + + this.transition.buttons(BUTTONS.Stats, BUTTONS.Menu); + + this.transition.title(HIDE); + this.transition.cube(HIDE); + + setTimeout(() => this.transition.stats(SHOW), 1000); + } else { + this.state = STATE.Menu; + + this.transition.buttons(BUTTONS.Menu, BUTTONS.None); + + this.transition.stats(HIDE); + + setTimeout(() => this.transition.cube(SHOW), 500); + setTimeout(() => this.transition.title(SHOW), 1200); + } + } + + complete(show) { + if (show) { + this.transition.buttons(BUTTONS.Complete, BUTTONS.Playing); + + this.state = STATE.Complete; + this.saved = false; + + this.controls.disable(); + this.timer.stop(); + this.storage.clearGame(); + + this.bestTime = this.scores.addScore(this.timer.deltaTime); + + this.transition.zoom(STATE.Menu, 0); + this.transition.elevate(SHOW); + + setTimeout(() => { + this.transition.complete(SHOW, this.bestTime); + this.confetti.start(); + }, 1000); + } else { + this.state = STATE.Stats; + this.saved = false; + + this.transition.timer(HIDE); + this.transition.complete(HIDE, this.bestTime); + this.transition.cube(HIDE); + this.timer.reset(); + + setTimeout(() => { + this.cube.reset(); + this.confetti.stop(); + + this.transition.stats(SHOW); + this.transition.elevate(0); + }, 1000); + + return false; + } + } +} + +window.version = "0.99.2"; +window.game = new Game(); diff --git a/The 3D Cube game/style.scss b/The 3D Cube game/style.scss new file mode 100644 index 000000000..d26edbaa7 --- /dev/null +++ b/The 3D Cube game/style.scss @@ -0,0 +1,359 @@ +@font-face + font-family: 'BungeeFont' + font-weight: normal + font-style: normal + src: url('data:font/truetype;charset=utf-8;base64,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') format('woff2'), url('data:font/truetype;charset=utf-8;base64,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') format('woff') + +=inset($position: absolute) + position: $position + top: 0 + left: 0 + width: 100% + height: 100% + +* + &, + &:before, + &:after + -webkit-user-select: none + -moz-user-select: none + user-select: none + box-sizing: border-box + cursor: inherit + margin: 0 + padding: 0 + outline: none + font-size: inherit + font-family: inherit + font-weight: inherit + font-style: inherit + text-transform: uppercase + + &:focus + outline: none + +html + -webkit-tap-highlight-color: transparent + -webkit-text-size-adjust: 100% + -webkit-font-smoothing: antialiased + -moz-osx-font-smoothing: grayscale + -ms-text-size-adjust: 100% + -webkit-text-size-adjust: 100% + overflow: hidden + height: 100% + +body + font-family: 'BungeeFont', sans-serif + font-weight: normal + font-style: normal + line-height: 1 + cursor: default + overflow: hidden + height: 100% + font-size: 5rem + +.icon + display: inline-block + font-size: inherit + overflow: visible + vertical-align: -0.125em + preserveAspectRatio: none + +.range + position: relative + width: 14em + z-index: 1 + opacity: 0 + + &:not(:last-child) + margin-bottom: 2em + + &__label + position: relative + font-size: 0.9em + line-height: 0.75em + padding-bottom: 0.5em + z-index: 2 + + &__track + position: relative + height: 1em + margin-left: 0.5em + margin-right: 0.5em + z-index: 3 + + &-line + position: absolute + background: rgba(#000, 0.2) + height: 2px + top: 50% + margin-top: -1px + left: -0.5em + right: -0.5em + transform-origin: left center + + &__handle + position: absolute + width: 0 + height: 0 + top: 50% + left: 0 + cursor: pointer + z-index: 1 + + div + transition: background 500ms ease + position: absolute + left: 0 + top: 0 + width: 0.9em + height: 0.9em + border-radius: 0.2em + margin-left: -0.45em + margin-top: -0.45em + background: #41aac8 + border-bottom: 2px solid rgba(#000, 0.2) + + .range.is-active & + transform: scale(1.25) + + &:after + content: '' + position: absolute + left: 0 + top: 0 + width: 3em + height: 3em + margin-left: -1.5em + margin-top: -1.5em + + &__list + display: flex + flex-flow: row nowrap + justify-content: space-between + position: relative + padding-top: 0.5em + font-size: 0.55em + color: rgba(#000, 0.5) + z-index: 1 + + &--type + &-color + &:not(:last-child) + margin-bottom: 1em + + .range + &__list + display: none + + &__handle + > div + background: currentColor !important + + &__track + &-line + background: transparent + + &:after + +inset + content: '' + opacity: 0.5 + + &--color + &-hue + .range + &__handle + color: red + + &__track + color: red + + &-line + &:after + background: linear-gradient(to right, red, yellow, lime, cyan, blue, magenta, red) + + + &-saturation + .range + &__handle + color: red + + &__track + color: red + + &-line + &:after + background: linear-gradient(to right, gray, currentColor) + + &-lightness + .range + &__handle + color: red + + &__track + color: red + + &-line + &:after + background: linear-gradient(to right, black, currentColor, white) + +.stats + position: relative + width: 14em + z-index: 1 + display: flex + justify-content: space-between + opacity: 0 + + &:not(:last-child) + margin-bottom: 1.5em + + > i + display: block + color: rgba(#000, 0.5) + font-size: 0.9em + + > b + display: block + font-size: 0.9em + + > i + font-size: 0.75em + + &[name="worst-time"] + display: none + +.text + position: absolute + left: 0 + right: 0 + text-align: center + line-height: 0.75 + perspective: 100rem + opacity: 0 + + i + display: inline-block + opacity: 0 + white-space: pre-wrap + + &--title + bottom: 75% + font-size: 4.4em + height: 1.2em + + span + display: block + + &:first-child + font-size: 0.5em + margin-bottom: 0.2em + + &--note + top: 87% + font-size: 1em + + &--timer + bottom: 78% + font-size: 3.5em + line-height: 1 + + &--complete, + &--best-time + font-size: 1.5em + top: 83% + line-height: 1em + +.btn + -webkit-appearance: none + -moz-appearance: none + appearance: none + background-color: transparent + border-radius: 0 + border-width: 0 + position: absolute + pointer-events: none + font-size: 1.2em + color: rgba(#000, 0.25) + opacity: 0 + + &:after + position: absolute + content: '' + width: 3em + height: 3em + left: 50% + top: 50% + margin-left: -1.5em + margin-top: -1.5em + border-radius: 100% + + &--bl + bottom: 0.8em + left: 0.8em + + &--br + bottom: 0.8em + right: 0.8em + + &--bc + bottom: 0.8em + left: calc(50% - 0.5em) + + svg + display: block + + &--cancel + display: none !important +.ui + pointer-events: none + color: #070d15 + + &, + &__background, + &__game, + &__texts, + &__prefs, + &__theme, + &__stats, + &__buttons + +inset + overflow: hidden + + &__background + z-index: 1 + transition: background 500ms ease + background: #d1d5db + + &:after + +inset + content: '' + background-image: linear-gradient(to bottom, rgba(#fff, 1) 50%, rgba(#fff, 0) 100%) + + &__game + pointer-events: all + z-index: 2 + + canvas + display: block + width: 100% + height: 100% + + &__texts + z-index: 3 + + &__prefs, + &__stats, + &__theme + display: flex + flex-flow: column nowrap + justify-content: center + align-items: center + overflow: hidden + z-index: 4 + + &__theme + padding-top: 15em + + &__buttons + z-index: 5 diff --git a/Valorous Rabbit/index.html b/Valorous Rabbit/index.html new file mode 100644 index 000000000..4bfd5deeb --- /dev/null +++ b/Valorous Rabbit/index.html @@ -0,0 +1,37 @@ + + + + + + + + + + + Document + + + + +
+
+ Game Over +
+
+
distance
+
000
+
+ +
Click to jump — Grab the carrots / avoid the hedgehogs +
+ + + + + + + + diff --git a/Valorous Rabbit/script.js b/Valorous Rabbit/script.js new file mode 100644 index 000000000..f1147e1de --- /dev/null +++ b/Valorous Rabbit/script.js @@ -0,0 +1,1552 @@ +//THREEJS RELATED VARIABLES + +var scene, + camera, + fieldOfView, + aspectRatio, + nearPlane, + farPlane, + gobalLight, + shadowLight, + backLight, + renderer, + container, + controls, + clock; +var delta = 0; +var floorRadius = 200; +var speed = 6; +var distance = 0; +var level = 1; +var levelInterval; +var levelUpdateFreq = 3000; +var initSpeed = 5; +var maxSpeed = 48; +var monsterPos = 0.65; +var monsterPosTarget = 0.65; +var floorRotation = 0; +var collisionObstacle = 10; +var collisionBonus = 20; +var gameStatus = "play"; +var cameraPosGame = 160; +var cameraPosGameOver = 260; +var monsterAcceleration = 0.004; +var malusClearColor = 0xb44b39; +var malusClearAlpha = 0; +var audio = new Audio( + "https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/Antonio-Vivaldi-Summer_01.mp3" +); + +var fieldGameOver, fieldDistance; + +//SCREEN & MOUSE VARIABLES + +var HEIGHT, + WIDTH, + windowHalfX, + windowHalfY, + mousePos = { + x: 0, + y: 0, + }; + +//3D OBJECTS VARIABLES + +var hero; + +// Materials +var blackMat = new THREE.MeshPhongMaterial({ + color: 0x100707, + shading: THREE.FlatShading, +}); + +var brownMat = new THREE.MeshPhongMaterial({ + color: 0xb44b39, + shininess: 0, + shading: THREE.FlatShading, +}); + +var greenMat = new THREE.MeshPhongMaterial({ + color: 0x7abf8e, + shininess: 0, + shading: THREE.FlatShading, +}); + +var pinkMat = new THREE.MeshPhongMaterial({ + color: 0xdc5f45, //0xb43b29,//0xff5b49, + shininess: 0, + shading: THREE.FlatShading, +}); + +var lightBrownMat = new THREE.MeshPhongMaterial({ + color: 0xe07a57, + shading: THREE.FlatShading, +}); + +var whiteMat = new THREE.MeshPhongMaterial({ + color: 0xa49789, + shading: THREE.FlatShading, +}); +var skinMat = new THREE.MeshPhongMaterial({ + color: 0xff9ea5, + shading: THREE.FlatShading, +}); + +// OTHER VARIABLES + +var PI = Math.PI; + +//INIT THREE JS, SCREEN AND MOUSE EVENTS + +function initScreenAnd3D() { + HEIGHT = window.innerHeight; + WIDTH = window.innerWidth; + windowHalfX = WIDTH / 2; + windowHalfY = HEIGHT / 2; + + scene = new THREE.Scene(); + + scene.fog = new THREE.Fog(0xd6eae6, 160, 350); + + aspectRatio = WIDTH / HEIGHT; + fieldOfView = 50; + nearPlane = 1; + farPlane = 2000; + camera = new THREE.PerspectiveCamera( + fieldOfView, + aspectRatio, + nearPlane, + farPlane + ); + camera.position.x = 0; + camera.position.z = cameraPosGame; + camera.position.y = 30; + camera.lookAt(new THREE.Vector3(0, 30, 0)); + + renderer = new THREE.WebGLRenderer({ + alpha: true, + antialias: true, + }); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setClearColor(malusClearColor, malusClearAlpha); + + renderer.setSize(WIDTH, HEIGHT); + renderer.shadowMap.enabled = true; + + container = document.getElementById("world"); + container.appendChild(renderer.domElement); + + window.addEventListener("resize", handleWindowResize, false); + document.addEventListener("mousedown", handleMouseDown, false); + document.addEventListener("touchend", handleMouseDown, false); + + /* + controls = new THREE.OrbitControls(camera, renderer.domElement); + //controls.minPolarAngle = -Math.PI / 2; + //controls.maxPolarAngle = Math.PI / 2; + //controls.noZoom = true; + controls.noPan = true; + //*/ + + clock = new THREE.Clock(); +} + +function handleWindowResize() { + HEIGHT = window.innerHeight; + WIDTH = window.innerWidth; + windowHalfX = WIDTH / 2; + windowHalfY = HEIGHT / 2; + renderer.setSize(WIDTH, HEIGHT); + camera.aspect = WIDTH / HEIGHT; + camera.updateProjectionMatrix(); +} + +function handleMouseDown(event) { + if (gameStatus == "play") hero.jump(); + else if (gameStatus == "readyToReplay") { + replay(); + } +} + +function createLights() { + globalLight = new THREE.AmbientLight(0xffffff, 0.9); + + shadowLight = new THREE.DirectionalLight(0xffffff, 1); + shadowLight.position.set(-30, 40, 20); + shadowLight.castShadow = true; + shadowLight.shadow.camera.left = -400; + shadowLight.shadow.camera.right = 400; + shadowLight.shadow.camera.top = 400; + shadowLight.shadow.camera.bottom = -400; + shadowLight.shadow.camera.near = 1; + shadowLight.shadow.camera.far = 2000; + shadowLight.shadow.mapSize.width = shadowLight.shadow.mapSize.height = 2048; + + scene.add(globalLight); + scene.add(shadowLight); +} + +function createFloor() { + floorShadow = new THREE.Mesh( + new THREE.SphereGeometry(floorRadius, 50, 50), + new THREE.MeshPhongMaterial({ + color: 0x7abf8e, + specular: 0x000000, + shininess: 1, + transparent: true, + opacity: 0.5, + }) + ); + //floorShadow.rotation.x = -Math.PI / 2; + floorShadow.receiveShadow = true; + + floorGrass = new THREE.Mesh( + new THREE.SphereGeometry(floorRadius - 0.5, 50, 50), + new THREE.MeshBasicMaterial({ + color: 0x7abf8e, + }) + ); + //floor.rotation.x = -Math.PI / 2; + floorGrass.receiveShadow = false; + + floor = new THREE.Group(); + floor.position.y = -floorRadius; + + floor.add(floorShadow); + floor.add(floorGrass); + scene.add(floor); +} + +Hero = function () { + this.status = "running"; + this.runningCycle = 0; + this.mesh = new THREE.Group(); + this.body = new THREE.Group(); + this.mesh.add(this.body); + + var torsoGeom = new THREE.CubeGeometry(7, 7, 10, 1); + + this.torso = new THREE.Mesh(torsoGeom, brownMat); + this.torso.position.z = 0; + this.torso.position.y = 7; + this.torso.castShadow = true; + this.body.add(this.torso); + + var pantsGeom = new THREE.CubeGeometry(9, 9, 5, 1); + this.pants = new THREE.Mesh(pantsGeom, whiteMat); + this.pants.position.z = -3; + this.pants.position.y = 0; + this.pants.castShadow = true; + this.torso.add(this.pants); + + var tailGeom = new THREE.CubeGeometry(3, 3, 3, 1); + tailGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0, -2)); + this.tail = new THREE.Mesh(tailGeom, lightBrownMat); + this.tail.position.z = -4; + this.tail.position.y = 5; + this.tail.castShadow = true; + this.torso.add(this.tail); + + this.torso.rotation.x = -Math.PI / 8; + + var headGeom = new THREE.CubeGeometry(10, 10, 13, 1); + + headGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0, 7.5)); + this.head = new THREE.Mesh(headGeom, brownMat); + this.head.position.z = 2; + this.head.position.y = 11; + this.head.castShadow = true; + this.body.add(this.head); + + var cheekGeom = new THREE.CubeGeometry(1, 4, 4, 1); + this.cheekR = new THREE.Mesh(cheekGeom, pinkMat); + this.cheekR.position.x = -5; + this.cheekR.position.z = 7; + this.cheekR.position.y = -2.5; + this.cheekR.castShadow = true; + this.head.add(this.cheekR); + + this.cheekL = this.cheekR.clone(); + this.cheekL.position.x = -this.cheekR.position.x; + this.head.add(this.cheekL); + + var noseGeom = new THREE.CubeGeometry(6, 6, 3, 1); + this.nose = new THREE.Mesh(noseGeom, lightBrownMat); + this.nose.position.z = 13.5; + this.nose.position.y = 2.6; + this.nose.castShadow = true; + this.head.add(this.nose); + + var mouthGeom = new THREE.CubeGeometry(4, 2, 4, 1); + mouthGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0, 3)); + mouthGeom.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI / 12)); + this.mouth = new THREE.Mesh(mouthGeom, brownMat); + this.mouth.position.z = 8; + this.mouth.position.y = -4; + this.mouth.castShadow = true; + this.head.add(this.mouth); + + var pawFGeom = new THREE.CubeGeometry(3, 3, 3, 1); + this.pawFR = new THREE.Mesh(pawFGeom, lightBrownMat); + this.pawFR.position.x = -2; + this.pawFR.position.z = 6; + this.pawFR.position.y = 1.5; + this.pawFR.castShadow = true; + this.body.add(this.pawFR); + + this.pawFL = this.pawFR.clone(); + this.pawFL.position.x = -this.pawFR.position.x; + this.pawFL.castShadow = true; + this.body.add(this.pawFL); + + var pawBGeom = new THREE.CubeGeometry(3, 3, 6, 1); + this.pawBL = new THREE.Mesh(pawBGeom, lightBrownMat); + this.pawBL.position.y = 1.5; + this.pawBL.position.z = 0; + this.pawBL.position.x = 5; + this.pawBL.castShadow = true; + this.body.add(this.pawBL); + + this.pawBR = this.pawBL.clone(); + this.pawBR.position.x = -this.pawBL.position.x; + this.pawBR.castShadow = true; + this.body.add(this.pawBR); + + var earGeom = new THREE.CubeGeometry(7, 18, 2, 1); + earGeom.vertices[6].x += 2; + earGeom.vertices[6].z += 0.5; + + earGeom.vertices[7].x += 2; + earGeom.vertices[7].z -= 0.5; + + earGeom.vertices[2].x -= 2; + earGeom.vertices[2].z -= 0.5; + + earGeom.vertices[3].x -= 2; + earGeom.vertices[3].z += 0.5; + earGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 9, 0)); + + this.earL = new THREE.Mesh(earGeom, brownMat); + this.earL.position.x = 2; + this.earL.position.z = 2.5; + this.earL.position.y = 5; + this.earL.rotation.z = -Math.PI / 12; + this.earL.castShadow = true; + this.head.add(this.earL); + + this.earR = this.earL.clone(); + this.earR.position.x = -this.earL.position.x; + this.earR.rotation.z = -this.earL.rotation.z; + this.earR.castShadow = true; + this.head.add(this.earR); + + var eyeGeom = new THREE.CubeGeometry(2, 4, 4); + + this.eyeL = new THREE.Mesh(eyeGeom, whiteMat); + this.eyeL.position.x = 5; + this.eyeL.position.z = 5.5; + this.eyeL.position.y = 2.9; + this.eyeL.castShadow = true; + this.head.add(this.eyeL); + + var irisGeom = new THREE.CubeGeometry(0.6, 2, 2); + + this.iris = new THREE.Mesh(irisGeom, blackMat); + this.iris.position.x = 1.2; + this.iris.position.y = 1; + this.iris.position.z = 1; + this.eyeL.add(this.iris); + + this.eyeR = this.eyeL.clone(); + this.eyeR.children[0].position.x = -this.iris.position.x; + + this.eyeR.position.x = -this.eyeL.position.x; + this.head.add(this.eyeR); + + this.body.traverse(function (object) { + if (object instanceof THREE.Mesh) { + object.castShadow = true; + object.receiveShadow = true; + } + }); +}; + +BonusParticles = function () { + this.mesh = new THREE.Group(); + var bigParticleGeom = new THREE.CubeGeometry(10, 10, 10, 1); + var smallParticleGeom = new THREE.CubeGeometry(5, 5, 5, 1); + this.parts = []; + for (var i = 0; i < 10; i++) { + var partPink = new THREE.Mesh(bigParticleGeom, pinkMat); + var partGreen = new THREE.Mesh(smallParticleGeom, greenMat); + partGreen.scale.set(0.5, 0.5, 0.5); + this.parts.push(partPink); + this.parts.push(partGreen); + this.mesh.add(partPink); + this.mesh.add(partGreen); + } +}; + +BonusParticles.prototype.explose = function () { + var _this = this; + var explosionSpeed = 0.5; + for (var i = 0; i < this.parts.length; i++) { + var tx = -50 + Math.random() * 100; + var ty = -50 + Math.random() * 100; + var tz = -50 + Math.random() * 100; + var p = this.parts[i]; + p.position.set(0, 0, 0); + p.scale.set(1, 1, 1); + p.visible = true; + var s = explosionSpeed + Math.random() * 0.5; + TweenMax.to(p.position, s, { + x: tx, + y: ty, + z: tz, + ease: Power4.easeOut, + }); + TweenMax.to(p.scale, s, { + x: 0.01, + y: 0.01, + z: 0.01, + ease: Power4.easeOut, + onComplete: removeParticle, + onCompleteParams: [p], + }); + } +}; + +function removeParticle(p) { + p.visible = false; +} + +Hero.prototype.run = function () { + this.status = "running"; + + var s = Math.min(speed, maxSpeed); + + this.runningCycle += delta * s * 0.7; + this.runningCycle = this.runningCycle % (Math.PI * 2); + var t = this.runningCycle; + + var amp = 4; + var disp = 0.2; + + // BODY + + this.body.position.y = 6 + Math.sin(t - Math.PI / 2) * amp; + this.body.rotation.x = 0.2 + Math.sin(t - Math.PI / 2) * amp * 0.1; + + this.torso.rotation.x = Math.sin(t - Math.PI / 2) * amp * 0.1; + this.torso.position.y = 7 + Math.sin(t - Math.PI / 2) * amp * 0.5; + + // MOUTH + this.mouth.rotation.x = Math.PI / 16 + Math.cos(t) * amp * 0.05; + + // HEAD + this.head.position.z = 2 + Math.sin(t - Math.PI / 2) * amp * 0.5; + this.head.position.y = 8 + Math.cos(t - Math.PI / 2) * amp * 0.7; + this.head.rotation.x = -0.2 + Math.sin(t + Math.PI) * amp * 0.1; + + // EARS + this.earL.rotation.x = Math.cos(-Math.PI / 2 + t) * (amp * 0.2); + this.earR.rotation.x = Math.cos(-Math.PI / 2 + 0.2 + t) * (amp * 0.3); + + // EYES + this.eyeR.scale.y = this.eyeL.scale.y = + 0.7 + Math.abs(Math.cos(-Math.PI / 4 + t * 0.5)) * 0.6; + + // TAIL + this.tail.rotation.x = Math.cos(Math.PI / 2 + t) * amp * 0.3; + + // FRONT RIGHT PAW + this.pawFR.position.y = 1.5 + Math.sin(t) * amp; + this.pawFR.rotation.x = (Math.cos(t) * Math.PI) / 4; + + this.pawFR.position.z = 6 - Math.cos(t) * amp * 2; + + // FRONT LEFT PAW + + this.pawFL.position.y = 1.5 + Math.sin(disp + t) * amp; + this.pawFL.rotation.x = (Math.cos(t) * Math.PI) / 4; + + this.pawFL.position.z = 6 - Math.cos(disp + t) * amp * 2; + + // BACK RIGHT PAW + this.pawBR.position.y = 1.5 + Math.sin(Math.PI + t) * amp; + this.pawBR.rotation.x = (Math.cos(t + Math.PI * 1.5) * Math.PI) / 3; + + this.pawBR.position.z = -Math.cos(Math.PI + t) * amp; + + // BACK LEFT PAW + this.pawBL.position.y = 1.5 + Math.sin(Math.PI + t) * amp; + this.pawBL.rotation.x = (Math.cos(t + Math.PI * 1.5) * Math.PI) / 3; + + this.pawBL.position.z = -Math.cos(Math.PI + t) * amp; +}; + +Hero.prototype.jump = function () { + if (this.status == "jumping") return; + this.status = "jumping"; + var _this = this; + var totalSpeed = 10 / speed; + var jumpHeight = 45; + + TweenMax.to(this.earL.rotation, totalSpeed, { + x: "+=.3", + ease: Back.easeOut, + }); + TweenMax.to(this.earR.rotation, totalSpeed, { + x: "-=.3", + ease: Back.easeOut, + }); + + TweenMax.to(this.pawFL.rotation, totalSpeed, { + x: "+=.7", + ease: Back.easeOut, + }); + TweenMax.to(this.pawFR.rotation, totalSpeed, { + x: "-=.7", + ease: Back.easeOut, + }); + TweenMax.to(this.pawBL.rotation, totalSpeed, { + x: "+=.7", + ease: Back.easeOut, + }); + TweenMax.to(this.pawBR.rotation, totalSpeed, { + x: "-=.7", + ease: Back.easeOut, + }); + + TweenMax.to(this.tail.rotation, totalSpeed, { + x: "+=1", + ease: Back.easeOut, + }); + + TweenMax.to(this.mouth.rotation, totalSpeed, { + x: 0.5, + ease: Back.easeOut, + }); + + TweenMax.to(this.mesh.position, totalSpeed / 2, { + y: jumpHeight, + ease: Power2.easeOut, + }); + TweenMax.to(this.mesh.position, totalSpeed / 2, { + y: 0, + ease: Power4.easeIn, + delay: totalSpeed / 2, + onComplete: function () { + //t = 0; + _this.status = "running"; + }, + }); +}; + +Monster = function () { + this.runningCycle = 0; + + this.mesh = new THREE.Group(); + this.body = new THREE.Group(); + + var torsoGeom = new THREE.CubeGeometry(15, 15, 20, 1); + this.torso = new THREE.Mesh(torsoGeom, blackMat); + + var headGeom = new THREE.CubeGeometry(20, 20, 40, 1); + headGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0, 20)); + this.head = new THREE.Mesh(headGeom, blackMat); + this.head.position.z = 12; + this.head.position.y = 2; + + var mouthGeom = new THREE.CubeGeometry(10, 4, 20, 1); + mouthGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, -2, 10)); + this.mouth = new THREE.Mesh(mouthGeom, blackMat); + this.mouth.position.y = -8; + this.mouth.rotation.x = 0.4; + this.mouth.position.z = 4; + + this.heroHolder = new THREE.Group(); + this.heroHolder.position.z = 20; + this.mouth.add(this.heroHolder); + + var toothGeom = new THREE.CubeGeometry(2, 2, 1, 1); + + toothGeom.vertices[1].x -= 1; + toothGeom.vertices[4].x += 1; + toothGeom.vertices[5].x += 1; + toothGeom.vertices[0].x -= 1; + + for (var i = 0; i < 3; i++) { + var toothf = new THREE.Mesh(toothGeom, whiteMat); + toothf.position.x = -2.8 + i * 2.5; + toothf.position.y = 1; + toothf.position.z = 19; + + var toothl = new THREE.Mesh(toothGeom, whiteMat); + toothl.rotation.y = Math.PI / 2; + toothl.position.z = 12 + i * 2.5; + toothl.position.y = 1; + toothl.position.x = 4; + + var toothr = toothl.clone(); + toothl.position.x = -4; + + this.mouth.add(toothf); + this.mouth.add(toothl); + this.mouth.add(toothr); + } + + var tongueGeometry = new THREE.CubeGeometry(6, 1, 14); + tongueGeometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0, 7)); + + this.tongue = new THREE.Mesh(tongueGeometry, pinkMat); + this.tongue.position.z = 2; + this.tongue.rotation.x = -0.2; + this.mouth.add(this.tongue); + + var noseGeom = new THREE.CubeGeometry(4, 4, 4, 1); + this.nose = new THREE.Mesh(noseGeom, pinkMat); + this.nose.position.z = 39.5; + this.nose.position.y = 9; + this.head.add(this.nose); + + this.head.add(this.mouth); + + var eyeGeom = new THREE.CubeGeometry(2, 3, 3); + + this.eyeL = new THREE.Mesh(eyeGeom, whiteMat); + this.eyeL.position.x = 10; + this.eyeL.position.z = 5; + this.eyeL.position.y = 5; + this.eyeL.castShadow = true; + this.head.add(this.eyeL); + + var irisGeom = new THREE.CubeGeometry(0.6, 1, 1); + + this.iris = new THREE.Mesh(irisGeom, blackMat); + this.iris.position.x = 1.2; + this.iris.position.y = -1; + this.iris.position.z = 1; + this.eyeL.add(this.iris); + + this.eyeR = this.eyeL.clone(); + this.eyeR.children[0].position.x = -this.iris.position.x; + this.eyeR.position.x = -this.eyeL.position.x; + this.head.add(this.eyeR); + + var earGeom = new THREE.CubeGeometry(8, 6, 2, 1); + earGeom.vertices[1].x -= 4; + earGeom.vertices[4].x += 4; + earGeom.vertices[5].x += 4; + earGeom.vertices[5].z -= 2; + earGeom.vertices[0].x -= 4; + earGeom.vertices[0].z -= 2; + + earGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 3, 0)); + + this.earL = new THREE.Mesh(earGeom, blackMat); + this.earL.position.x = 6; + this.earL.position.z = 1; + this.earL.position.y = 10; + this.earL.castShadow = true; + this.head.add(this.earL); + + this.earR = this.earL.clone(); + this.earR.position.x = -this.earL.position.x; + this.earR.rotation.z = -this.earL.rotation.z; + this.head.add(this.earR); + + var eyeGeom = new THREE.CubeGeometry(2, 4, 4); + + var tailGeom = new THREE.CylinderGeometry(5, 2, 20, 4, 1); + tailGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 10, 0)); + tailGeom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2)); + tailGeom.applyMatrix(new THREE.Matrix4().makeRotationZ(Math.PI / 4)); + + this.tail = new THREE.Mesh(tailGeom, blackMat); + this.tail.position.z = -10; + this.tail.position.y = 4; + this.torso.add(this.tail); + + var pawGeom = new THREE.CylinderGeometry(1.5, 0, 10); + pawGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, -5, 0)); + this.pawFL = new THREE.Mesh(pawGeom, blackMat); + this.pawFL.position.y = -7.5; + this.pawFL.position.z = 8.5; + this.pawFL.position.x = 5.5; + this.torso.add(this.pawFL); + + this.pawFR = this.pawFL.clone(); + this.pawFR.position.x = -this.pawFL.position.x; + this.torso.add(this.pawFR); + + this.pawBR = this.pawFR.clone(); + this.pawBR.position.z = -this.pawFL.position.z; + this.torso.add(this.pawBR); + + this.pawBL = this.pawBR.clone(); + this.pawBL.position.x = this.pawFL.position.x; + this.torso.add(this.pawBL); + + this.mesh.add(this.body); + this.torso.add(this.head); + this.body.add(this.torso); + + this.torso.castShadow = true; + this.head.castShadow = true; + this.pawFL.castShadow = true; + this.pawFR.castShadow = true; + this.pawBL.castShadow = true; + this.pawBR.castShadow = true; + + this.body.rotation.y = Math.PI / 2; +}; + +Monster.prototype.run = function () { + var s = Math.min(speed, maxSpeed); + this.runningCycle += delta * s * 0.7; + this.runningCycle = this.runningCycle % (Math.PI * 2); + var t = this.runningCycle; + + this.pawFR.rotation.x = (Math.sin(t) * Math.PI) / 4; + this.pawFR.position.y = -5.5 - Math.sin(t); + this.pawFR.position.z = 7.5 + Math.cos(t); + + this.pawFL.rotation.x = (Math.sin(t + 0.4) * Math.PI) / 4; + this.pawFL.position.y = -5.5 - Math.sin(t + 0.4); + this.pawFL.position.z = 7.5 + Math.cos(t + 0.4); + + this.pawBL.rotation.x = (Math.sin(t + 2) * Math.PI) / 4; + this.pawBL.position.y = -5.5 - Math.sin(t + 3.8); + this.pawBL.position.z = -7.5 + Math.cos(t + 3.8); + + this.pawBR.rotation.x = (Math.sin(t + 2.4) * Math.PI) / 4; + this.pawBR.position.y = -5.5 - Math.sin(t + 3.4); + this.pawBR.position.z = -7.5 + Math.cos(t + 3.4); + + this.torso.rotation.x = (Math.sin(t) * Math.PI) / 8; + this.torso.position.y = 3 - Math.sin(t + Math.PI / 2) * 3; + + //this.head.position.y = 5-Math.sin(t+Math.PI/2)*2; + this.head.rotation.x = -0.1 + Math.sin(-t - 1) * 0.4; + this.mouth.rotation.x = 0.2 + Math.sin(t + Math.PI + 0.3) * 0.4; + + this.tail.rotation.x = 0.2 + Math.sin(t - Math.PI / 2); + + this.eyeR.scale.y = 0.5 + Math.sin(t + Math.PI) * 0.5; +}; + +Hero.prototype.nod = function () { + var _this = this; + var sp = 0.5 + Math.random(); + + // HEAD + var tHeadRotY = -Math.PI / 6 + (Math.random() * Math.PI) / 3; + TweenMax.to(this.head.rotation, sp, { + y: tHeadRotY, + ease: Power4.easeInOut, + onComplete: function () { + _this.nod(); + }, + }); + + // EARS + var tEarLRotX = Math.PI / 4 + (Math.random() * Math.PI) / 6; + var tEarRRotX = Math.PI / 4 + (Math.random() * Math.PI) / 6; + + TweenMax.to(this.earL.rotation, sp, { + x: tEarLRotX, + ease: Power4.easeInOut, + }); + TweenMax.to(this.earR.rotation, sp, { + x: tEarRRotX, + ease: Power4.easeInOut, + }); + + // PAWS BACK LEFT + + var tPawBLRot = (Math.random() * Math.PI) / 2; + var tPawBLY = -4 + Math.random() * 8; + + TweenMax.to(this.pawBL.rotation, sp / 2, { + x: tPawBLRot, + ease: Power1.easeInOut, + yoyo: true, + repeat: 2, + }); + TweenMax.to(this.pawBL.position, sp / 2, { + y: tPawBLY, + ease: Power1.easeInOut, + yoyo: true, + repeat: 2, + }); + + // PAWS BACK RIGHT + + var tPawBRRot = (Math.random() * Math.PI) / 2; + var tPawBRY = -4 + Math.random() * 8; + TweenMax.to(this.pawBR.rotation, sp / 2, { + x: tPawBRRot, + ease: Power1.easeInOut, + yoyo: true, + repeat: 2, + }); + TweenMax.to(this.pawBR.position, sp / 2, { + y: tPawBRY, + ease: Power1.easeInOut, + yoyo: true, + repeat: 2, + }); + + // PAWS FRONT LEFT + + var tPawFLRot = (Math.random() * Math.PI) / 2; + var tPawFLY = -4 + Math.random() * 8; + + TweenMax.to(this.pawFL.rotation, sp / 2, { + x: tPawFLRot, + ease: Power1.easeInOut, + yoyo: true, + repeat: 2, + }); + + TweenMax.to(this.pawFL.position, sp / 2, { + y: tPawFLY, + ease: Power1.easeInOut, + yoyo: true, + repeat: 2, + }); + + // PAWS FRONT RIGHT + + var tPawFRRot = (Math.random() * Math.PI) / 2; + var tPawFRY = -4 + Math.random() * 8; + + TweenMax.to(this.pawFR.rotation, sp / 2, { + x: tPawFRRot, + ease: Power1.easeInOut, + yoyo: true, + repeat: 2, + }); + + TweenMax.to(this.pawFR.position, sp / 2, { + y: tPawFRY, + ease: Power1.easeInOut, + yoyo: true, + repeat: 2, + }); + + // MOUTH + var tMouthRot = (Math.random() * Math.PI) / 8; + TweenMax.to(this.mouth.rotation, sp, { + x: tMouthRot, + ease: Power1.easeInOut, + }); + // IRIS + var tIrisY = -1 + Math.random() * 2; + var tIrisZ = -1 + Math.random() * 2; + var iris1 = this.iris; + var iris2 = this.eyeR.children[0]; + TweenMax.to([iris1.position, iris2.position], sp, { + y: tIrisY, + z: tIrisZ, + ease: Power1.easeInOut, + }); + + //EYES + if (Math.random() > 0.2) + TweenMax.to([this.eyeR.scale, this.eyeL.scale], sp / 8, { + y: 0, + ease: Power1.easeInOut, + yoyo: true, + repeat: 1, + }); +}; + +Hero.prototype.hang = function () { + var _this = this; + var sp = 1; + var ease = Power4.easeOut; + + TweenMax.killTweensOf(this.eyeL.scale); + TweenMax.killTweensOf(this.eyeR.scale); + + this.body.rotation.x = 0; + this.torso.rotation.x = 0; + this.body.position.y = 0; + this.torso.position.y = 7; + + TweenMax.to(this.mesh.rotation, sp, { y: 0, ease: ease }); + TweenMax.to(this.mesh.position, sp, { y: -7, z: 6, ease: ease }); + TweenMax.to(this.head.rotation, sp, { + x: Math.PI / 6, + ease: ease, + onComplete: function () { + _this.nod(); + }, + }); + + TweenMax.to(this.earL.rotation, sp, { x: Math.PI / 3, ease: ease }); + TweenMax.to(this.earR.rotation, sp, { x: Math.PI / 3, ease: ease }); + + TweenMax.to(this.pawFL.position, sp, { y: -1, z: 3, ease: ease }); + TweenMax.to(this.pawFR.position, sp, { y: -1, z: 3, ease: ease }); + TweenMax.to(this.pawBL.position, sp, { y: -2, z: -3, ease: ease }); + TweenMax.to(this.pawBR.position, sp, { y: -2, z: -3, ease: ease }); + + TweenMax.to(this.eyeL.scale, sp, { y: 1, ease: ease }); + TweenMax.to(this.eyeR.scale, sp, { y: 1, ease: ease }); +}; + +Monster.prototype.nod = function () { + var _this = this; + var sp = 1 + Math.random() * 2; + + // HEAD + var tHeadRotY = -Math.PI / 3 + Math.random() * 0.5; + var tHeadRotX = Math.PI / 3 - 0.2 + Math.random() * 0.4; + TweenMax.to(this.head.rotation, sp, { + x: tHeadRotX, + y: tHeadRotY, + ease: Power4.easeInOut, + onComplete: function () { + _this.nod(); + }, + }); + + // TAIL + + var tTailRotY = -Math.PI / 4; + TweenMax.to(this.tail.rotation, sp / 8, { + y: tTailRotY, + ease: Power1.easeInOut, + yoyo: true, + repeat: 8, + }); + + // EYES + + TweenMax.to([this.eyeR.scale, this.eyeL.scale], sp / 20, { + y: 0, + ease: Power1.easeInOut, + yoyo: true, + repeat: 1, + }); +}; + +Monster.prototype.sit = function () { + var sp = 1.2; + var ease = Power4.easeOut; + var _this = this; + TweenMax.to(this.torso.rotation, sp, { x: -1.3, ease: ease }); + TweenMax.to(this.torso.position, sp, { + y: -5, + ease: ease, + onComplete: function () { + _this.nod(); + gameStatus = "readyToReplay"; + }, + }); + + TweenMax.to(this.head.rotation, sp, { + x: Math.PI / 3, + y: -Math.PI / 3, + ease: ease, + }); + TweenMax.to(this.tail.rotation, sp, { x: 2, y: Math.PI / 4, ease: ease }); + TweenMax.to(this.pawBL.rotation, sp, { x: -0.1, ease: ease }); + TweenMax.to(this.pawBR.rotation, sp, { x: -0.1, ease: ease }); + TweenMax.to(this.pawFL.rotation, sp, { x: 1, ease: ease }); + TweenMax.to(this.pawFR.rotation, sp, { x: 1, ease: ease }); + TweenMax.to(this.mouth.rotation, sp, { x: 0.3, ease: ease }); + TweenMax.to(this.eyeL.scale, sp, { y: 1, ease: ease }); + TweenMax.to(this.eyeR.scale, sp, { y: 1, ease: ease }); + + //TweenMax.to(this.body.rotation, sp, {y:Math.PI/4}); +}; + +Carrot = function () { + this.angle = 0; + this.mesh = new THREE.Group(); + + var bodyGeom = new THREE.CylinderGeometry(5, 3, 10, 4, 1); + bodyGeom.vertices[8].y += 2; + bodyGeom.vertices[9].y -= 3; + + this.body = new THREE.Mesh(bodyGeom, pinkMat); + + var leafGeom = new THREE.CubeGeometry(5, 10, 1, 1); + leafGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 5, 0)); + leafGeom.vertices[2].x -= 1; + leafGeom.vertices[3].x -= 1; + leafGeom.vertices[6].x += 1; + leafGeom.vertices[7].x += 1; + + this.leaf1 = new THREE.Mesh(leafGeom, greenMat); + this.leaf1.position.y = 7; + this.leaf1.rotation.z = 0.3; + this.leaf1.rotation.x = 0.2; + + this.leaf2 = this.leaf1.clone(); + this.leaf2.scale.set(1, 1.3, 1); + this.leaf2.position.y = 7; + this.leaf2.rotation.z = -0.3; + this.leaf2.rotation.x = -0.2; + + this.mesh.add(this.body); + this.mesh.add(this.leaf1); + this.mesh.add(this.leaf2); + + this.body.traverse(function (object) { + if (object instanceof THREE.Mesh) { + object.castShadow = true; + object.receiveShadow = true; + } + }); +}; + +Hedgehog = function () { + this.angle = 0; + this.status = "ready"; + this.mesh = new THREE.Group(); + var bodyGeom = new THREE.CubeGeometry(6, 6, 6, 1); + this.body = new THREE.Mesh(bodyGeom, blackMat); + + var headGeom = new THREE.CubeGeometry(5, 5, 7, 1); + this.head = new THREE.Mesh(headGeom, lightBrownMat); + this.head.position.z = 6; + this.head.position.y = -0.5; + + var noseGeom = new THREE.CubeGeometry(1.5, 1.5, 1.5, 1); + this.nose = new THREE.Mesh(noseGeom, blackMat); + this.nose.position.z = 4; + this.nose.position.y = 2; + + var eyeGeom = new THREE.CubeGeometry(1, 3, 3); + + this.eyeL = new THREE.Mesh(eyeGeom, whiteMat); + this.eyeL.position.x = 2.2; + this.eyeL.position.z = -0.5; + this.eyeL.position.y = 0.8; + this.eyeL.castShadow = true; + this.head.add(this.eyeL); + + var irisGeom = new THREE.CubeGeometry(0.5, 1, 1); + + this.iris = new THREE.Mesh(irisGeom, blackMat); + this.iris.position.x = 0.5; + this.iris.position.y = 0.8; + this.iris.position.z = 0.8; + this.eyeL.add(this.iris); + + this.eyeR = this.eyeL.clone(); + this.eyeR.children[0].position.x = -this.iris.position.x; + this.eyeR.position.x = -this.eyeL.position.x; + + var spikeGeom = new THREE.CubeGeometry(0.5, 2, 0.5, 1); + spikeGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0, 1, 0)); + + for (var i = 0; i < 9; i++) { + var row = i % 3; + var col = Math.floor(i / 3); + var sb = new THREE.Mesh(spikeGeom, blackMat); + sb.rotation.x = + -Math.PI / 2 + (Math.PI / 12) * row - 0.5 + Math.random(); + sb.position.z = -3; + sb.position.y = -2 + row * 2; + sb.position.x = -2 + col * 2; + this.body.add(sb); + var st = new THREE.Mesh(spikeGeom, blackMat); + st.position.y = 3; + st.position.x = -2 + row * 2; + st.position.z = -2 + col * 2; + st.rotation.z = Math.PI / 6 - (Math.PI / 6) * row - 0.5 + Math.random(); + this.body.add(st); + + var sr = new THREE.Mesh(spikeGeom, blackMat); + sr.position.x = 3; + sr.position.y = -2 + row * 2; + sr.position.z = -2 + col * 2; + sr.rotation.z = + -Math.PI / 2 + (Math.PI / 12) * row - 0.5 + Math.random(); + this.body.add(sr); + + var sl = new THREE.Mesh(spikeGeom, blackMat); + sl.position.x = -3; + sl.position.y = -2 + row * 2; + sl.position.z = -2 + col * 2; + sl.rotation.z = + Math.PI / 2 - (Math.PI / 12) * row - 0.5 + Math.random(); + this.body.add(sl); + } + + this.head.add(this.eyeR); + var earGeom = new THREE.CubeGeometry(2, 2, 0.5, 1); + this.earL = new THREE.Mesh(earGeom, lightBrownMat); + this.earL.position.x = 2.5; + this.earL.position.z = -2.5; + this.earL.position.y = 2.5; + this.earL.rotation.z = -Math.PI / 12; + this.earL.castShadow = true; + this.head.add(this.earL); + + this.earR = this.earL.clone(); + this.earR.position.x = -this.earL.position.x; + this.earR.rotation.z = -this.earL.rotation.z; + this.earR.castShadow = true; + this.head.add(this.earR); + + var mouthGeom = new THREE.CubeGeometry(1, 1, 0.5, 1); + this.mouth = new THREE.Mesh(mouthGeom, blackMat); + this.mouth.position.z = 3.5; + this.mouth.position.y = -1.5; + this.head.add(this.mouth); + + this.mesh.add(this.body); + this.body.add(this.head); + this.head.add(this.nose); + + this.mesh.traverse(function (object) { + if (object instanceof THREE.Mesh) { + object.castShadow = true; + object.receiveShadow = true; + } + }); +}; + +Hedgehog.prototype.nod = function () { + var _this = this; + var speed = 0.1 + Math.random() * 0.5; + var angle = -Math.PI / 4 + (Math.random() * Math.PI) / 2; + TweenMax.to(this.head.rotation, speed, { + y: angle, + onComplete: function () { + _this.nod(); + }, + }); +}; + +function createHero() { + hero = new Hero(); + hero.mesh.rotation.y = Math.PI / 2; + scene.add(hero.mesh); + hero.nod(); +} + +function createMonster() { + monster = new Monster(); + monster.mesh.position.z = 20; + //monster.mesh.scale.set(1.2,1.2,1.2); + scene.add(monster.mesh); + updateMonsterPosition(); +} + +function updateMonsterPosition() { + monster.run(); + monsterPosTarget -= delta * monsterAcceleration; + monsterPos += (monsterPosTarget - monsterPos) * delta; + if (monsterPos < 0.56) { + gameOver(); + } + + var angle = Math.PI * monsterPos; + monster.mesh.position.y = + -floorRadius + Math.sin(angle) * (floorRadius + 12); + monster.mesh.position.x = Math.cos(angle) * (floorRadius + 15); + monster.mesh.rotation.z = -Math.PI / 2 + angle; +} + +function gameOver() { + fieldGameOver.className = "show"; + gameStatus = "gameOver"; + monster.sit(); + hero.hang(); + monster.heroHolder.add(hero.mesh); + TweenMax.to(this, 1, { speed: 0 }); + TweenMax.to(camera.position, 3, { z: cameraPosGameOver, y: 60, x: -30 }); + carrot.mesh.visible = false; + obstacle.mesh.visible = false; + clearInterval(levelInterval); +} + +function replay() { + gameStatus = "preparingToReplay"; + + fieldGameOver.className = ""; + + TweenMax.killTweensOf(monster.pawFL.position); + TweenMax.killTweensOf(monster.pawFR.position); + TweenMax.killTweensOf(monster.pawBL.position); + TweenMax.killTweensOf(monster.pawBR.position); + + TweenMax.killTweensOf(monster.pawFL.rotation); + TweenMax.killTweensOf(monster.pawFR.rotation); + TweenMax.killTweensOf(monster.pawBL.rotation); + TweenMax.killTweensOf(monster.pawBR.rotation); + + TweenMax.killTweensOf(monster.tail.rotation); + TweenMax.killTweensOf(monster.head.rotation); + TweenMax.killTweensOf(monster.eyeL.scale); + TweenMax.killTweensOf(monster.eyeR.scale); + + //TweenMax.killTweensOf(hero.head.rotation); + + monster.tail.rotation.y = 0; + + TweenMax.to(camera.position, 3, { + z: cameraPosGame, + x: 0, + y: 30, + ease: Power4.easeInOut, + }); + TweenMax.to(monster.torso.rotation, 2, { x: 0, ease: Power4.easeInOut }); + TweenMax.to(monster.torso.position, 2, { y: 0, ease: Power4.easeInOut }); + TweenMax.to(monster.pawFL.rotation, 2, { x: 0, ease: Power4.easeInOut }); + TweenMax.to(monster.pawFR.rotation, 2, { x: 0, ease: Power4.easeInOut }); + TweenMax.to(monster.mouth.rotation, 2, { x: 0.5, ease: Power4.easeInOut }); + + TweenMax.to(monster.head.rotation, 2, { + y: 0, + x: -0.3, + ease: Power4.easeInOut, + }); + + TweenMax.to(hero.mesh.position, 2, { x: 20, ease: Power4.easeInOut }); + TweenMax.to(hero.head.rotation, 2, { x: 0, y: 0, ease: Power4.easeInOut }); + TweenMax.to(monster.mouth.rotation, 2, { x: 0.2, ease: Power4.easeInOut }); + TweenMax.to(monster.mouth.rotation, 1, { + x: 0.4, + ease: Power4.easeIn, + delay: 1, + onComplete: function () { + resetGame(); + }, + }); +} + +Fir = function () { + var height = 200; + var truncGeom = new THREE.CylinderGeometry(2, 2, height, 6, 1); + truncGeom.applyMatrix( + new THREE.Matrix4().makeTranslation(0, height / 2, 0) + ); + this.mesh = new THREE.Mesh(truncGeom, greenMat); + this.mesh.castShadow = true; +}; + +var firs = new THREE.Group(); + +function createFirs() { + var nTrees = 100; + for (var i = 0; i < nTrees; i++) { + var phi = (i * (Math.PI * 2)) / nTrees; + var theta = Math.PI / 2; + //theta += .25 + Math.random()*.3; + theta += + Math.random() > 0.05 + ? 0.25 + Math.random() * 0.3 + : -0.35 - Math.random() * 0.1; + + var fir = new Tree(); + fir.mesh.position.x = Math.sin(theta) * Math.cos(phi) * floorRadius; + fir.mesh.position.y = + Math.sin(theta) * Math.sin(phi) * (floorRadius - 10); + fir.mesh.position.z = Math.cos(theta) * floorRadius; + + var vec = fir.mesh.position.clone(); + var axis = new THREE.Vector3(0, 1, 0); + fir.mesh.quaternion.setFromUnitVectors(axis, vec.clone().normalize()); + floor.add(fir.mesh); + } +} + +function createCarrot() { + carrot = new Carrot(); + scene.add(carrot.mesh); +} + +function updateCarrotPosition() { + carrot.mesh.rotation.y += delta * 6; + carrot.mesh.rotation.z = Math.PI / 2 - (floorRotation + carrot.angle); + carrot.mesh.position.y = + -floorRadius + + Math.sin(floorRotation + carrot.angle) * (floorRadius + 50); + carrot.mesh.position.x = + Math.cos(floorRotation + carrot.angle) * (floorRadius + 50); +} + +function updateObstaclePosition() { + if (obstacle.status == "flying") return; + + // TODO fix this, + if (floorRotation + obstacle.angle > 2.5) { + obstacle.angle = -floorRotation + Math.random() * 0.3; + obstacle.body.rotation.y = Math.random() * Math.PI * 2; + } + + obstacle.mesh.rotation.z = floorRotation + obstacle.angle - Math.PI / 2; + obstacle.mesh.position.y = + -floorRadius + + Math.sin(floorRotation + obstacle.angle) * (floorRadius + 3); + obstacle.mesh.position.x = + Math.cos(floorRotation + obstacle.angle) * (floorRadius + 3); +} + +function updateFloorRotation() { + floorRotation += delta * 0.03 * speed; + floorRotation = floorRotation % (Math.PI * 2); + floor.rotation.z = floorRotation; +} + +function createObstacle() { + obstacle = new Hedgehog(); + obstacle.body.rotation.y = -Math.PI / 2; + obstacle.mesh.scale.set(1.1, 1.1, 1.1); + obstacle.mesh.position.y = floorRadius + 4; + obstacle.nod(); + scene.add(obstacle.mesh); +} + +function createBonusParticles() { + bonusParticles = new BonusParticles(); + bonusParticles.mesh.visible = false; + scene.add(bonusParticles.mesh); +} + +function checkCollision() { + var db = hero.mesh.position.clone().sub(carrot.mesh.position.clone()); + var dm = hero.mesh.position.clone().sub(obstacle.mesh.position.clone()); + + if (db.length() < collisionBonus) { + getBonus(); + } + + if (dm.length() < collisionObstacle && obstacle.status != "flying") { + getMalus(); + } +} + +function getBonus() { + bonusParticles.mesh.position.copy(carrot.mesh.position); + bonusParticles.mesh.visible = true; + bonusParticles.explose(); + carrot.angle += Math.PI / 2; + //speed*=.95; + monsterPosTarget += 0.025; +} + +function getMalus() { + obstacle.status = "flying"; + var tx = + Math.random() > 0.5 ? -20 - Math.random() * 10 : 20 + Math.random() * 5; + TweenMax.to(obstacle.mesh.position, 4, { + x: tx, + y: Math.random() * 50, + z: 350, + ease: Power4.easeOut, + }); + TweenMax.to(obstacle.mesh.rotation, 4, { + x: Math.PI * 3, + z: Math.PI * 3, + y: Math.PI * 6, + ease: Power4.easeOut, + onComplete: function () { + obstacle.status = "ready"; + obstacle.body.rotation.y = Math.random() * Math.PI * 2; + obstacle.angle = -floorRotation - Math.random() * 0.4; + + obstacle.angle = obstacle.angle % (Math.PI * 2); + obstacle.mesh.rotation.x = 0; + obstacle.mesh.rotation.y = 0; + obstacle.mesh.rotation.z = 0; + obstacle.mesh.position.z = 0; + }, + }); + // + monsterPosTarget -= 0.04; + TweenMax.from(this, 0.5, { + malusClearAlpha: 0.5, + onUpdate: function () { + renderer.setClearColor(malusClearColor, malusClearAlpha); + }, + }); +} + +function updateDistance() { + distance += delta * speed; + var d = distance / 2; + fieldDistance.innerHTML = Math.floor(d); +} + +function updateLevel() { + if (speed >= maxSpeed) return; + level++; + speed += 2; +} + +function loop() { + delta = clock.getDelta(); + updateFloorRotation(); + + if (gameStatus == "play") { + if (hero.status == "running") { + hero.run(); + } + updateDistance(); + updateMonsterPosition(); + updateCarrotPosition(); + updateObstaclePosition(); + checkCollision(); + } + + render(); + requestAnimationFrame(loop); +} + +function render() { + renderer.render(scene, camera); +} + +window.addEventListener("load", init, false); + +function init(event) { + initScreenAnd3D(); + createLights(); + createFloor(); + createHero(); + createMonster(); + createFirs(); + createCarrot(); + createBonusParticles(); + createObstacle(); + initUI(); + resetGame(); + loop(); + + //setInterval(hero.blink.bind(hero), 3000); +} + +function resetGame() { + scene.add(hero.mesh); + hero.mesh.rotation.y = Math.PI / 2; + hero.mesh.position.y = 0; + hero.mesh.position.z = 0; + hero.mesh.position.x = 0; + + monsterPos = 0.56; + monsterPosTarget = 0.65; + speed = initSpeed; + level = 0; + distance = 0; + carrot.mesh.visible = true; + obstacle.mesh.visible = true; + gameStatus = "play"; + hero.status = "running"; + hero.nod(); + audio.play(); + updateLevel(); + levelInterval = setInterval(updateLevel, levelUpdateFreq); +} + +function initUI() { + fieldDistance = document.getElementById("distValue"); + fieldGameOver = document.getElementById("gameoverInstructions"); +} + +//////////////////////////////////////////////// +// MODELS +//////////////////////////////////////////////// + +// TREE + +Tree = function () { + this.mesh = new THREE.Object3D(); + this.trunc = new Trunc(); + this.mesh.add(this.trunc.mesh); +}; + +Trunc = function () { + var truncHeight = 50 + Math.random() * 150; + var topRadius = 1 + Math.random() * 5; + var bottomRadius = 5 + Math.random() * 5; + var mats = [ + blackMat, + brownMat, + pinkMat, + whiteMat, + greenMat, + lightBrownMat, + pinkMat, + ]; + var matTrunc = blackMat; //mats[Math.floor(Math.random()*mats.length)]; + var nhSegments = 3; //Math.ceil(2 + Math.random()*6); + var nvSegments = 3; //Math.ceil(2 + Math.random()*6); + var geom = new THREE.CylinderGeometry( + topRadius, + bottomRadius, + truncHeight, + nhSegments, + nvSegments + ); + geom.applyMatrix( + new THREE.Matrix4().makeTranslation(0, truncHeight / 2, 0) + ); + + this.mesh = new THREE.Mesh(geom, matTrunc); + + for (var i = 0; i < geom.vertices.length; i++) { + var noise = Math.random(); + var v = geom.vertices[i]; + v.x += -noise + Math.random() * noise * 2; + v.y += -noise + Math.random() * noise * 2; + v.z += -noise + Math.random() * noise * 2; + + geom.computeVertexNormals(); + + // FRUITS + + if (Math.random() > 0.7) { + var size = Math.random() * 3; + var fruitGeometry = new THREE.CubeGeometry(size, size, size, 1); + var matFruit = mats[Math.floor(Math.random() * mats.length)]; + var fruit = new THREE.Mesh(fruitGeometry, matFruit); + fruit.position.x = v.x; + fruit.position.y = v.y + 3; + fruit.position.z = v.z; + fruit.rotation.x = Math.random() * Math.PI; + fruit.rotation.y = Math.random() * Math.PI; + + this.mesh.add(fruit); + } + + // BRANCHES + + if (Math.random() > 0.5 && v.y > 10 && v.y < truncHeight - 10) { + var h = 3 + Math.random() * 5; + var thickness = 0.2 + Math.random(); + + var branchGeometry = new THREE.CylinderGeometry( + thickness / 2, + thickness, + h, + 3, + 1 + ); + branchGeometry.applyMatrix( + new THREE.Matrix4().makeTranslation(0, h / 2, 0) + ); + var branch = new THREE.Mesh(branchGeometry, matTrunc); + branch.position.x = v.x; + branch.position.y = v.y; + branch.position.z = v.z; + + var vec = new THREE.Vector3(v.x, 2, v.z); + var axis = new THREE.Vector3(0, 1, 0); + branch.quaternion.setFromUnitVectors(axis, vec.clone().normalize()); + + this.mesh.add(branch); + } + } + + this.mesh.castShadow = true; +}; diff --git a/Valorous Rabbit/style.scss b/Valorous Rabbit/style.scss new file mode 100644 index 000000000..7ca25e49c --- /dev/null +++ b/Valorous Rabbit/style.scss @@ -0,0 +1,106 @@ + +@import url('https://fonts.googleapis.com/css?family=Voltaire'); + + +#world{ + position: absolute; + width:100%; + height: 100%; + background-color: #dbe6e6; + overflow: hidden; +} + + +#gameoverInstructions{ + position:absolute; + font-family:'Voltaire', sans-serif; + font-weight:bold; + text-transform: uppercase; + font-size:120px; + text-align:center; + color:#ffc5a2; + opacity:0; + left:50%; + top:50%; + width:100%; + transform : translate(-50%,-100%); + user-select: none; + transition: all 500ms ease-in-out; + + &.show{ + opacity:1; + transform : translate(-50%,-50%); + transition: all 500ms ease-in-out; + }; +} + + +#dist{ + position:absolute; + left:50%; + top:50px; + transform:translate(-50%,0%); + user-select: none; +} + +.label{ + position:relative; + font-family:'Voltaire', sans-serif; + text-transform:uppercase; + color:#ffa873;//100707; + font-size:12px; + letter-spacing:2px; + text-align:center; + margin-bottom:5px; +} + + +#distValue{ + position:relative; + text-transform:uppercase; + color:#dc5f45;//dc5f45; + font-size:40px; + font-family:'Voltaire'; + text-align:center; +} + +#credits{ + position:absolute; + width:100%; + margin: auto; + bottom:0; + margin-bottom:20px; + font-family:'Voltaire', sans-serif; + color:#544027; + font-size:12px; + letter-spacing:0.5px; + text-transform: uppercase; + text-align : center; + user-select: none; +} +#credits a { + color:#544027; + +} + +#credits a:hover { + color:#dc5f45; +} + +#instructions{ + position:absolute; + width:100%; + bottom:0; + margin: auto; + margin-bottom:50px; + font-family:'Voltaire', sans-serif; + color:#dc5f45; + font-size:16px; + letter-spacing:1px; + text-transform: uppercase; + text-align : center; + user-select: none; +} +.lightInstructions { + color:#5f9042; +} diff --git a/Wall Reaction game/index.html b/Wall Reaction game/index.html new file mode 100644 index 000000000..8ce9d04ae --- /dev/null +++ b/Wall Reaction game/index.html @@ -0,0 +1,25 @@ + + + + + + Code Snippet Generator + + + + + %div.info + %div.time + %div.points +%div.wall + - (1...101).each do |i| + %div.dot + %div.start + %button Start game! + %div.end + %div.score + %button Play again! + + + + diff --git a/Wall Reaction game/script.js b/Wall Reaction game/script.js new file mode 100644 index 000000000..bb57ab5a3 --- /dev/null +++ b/Wall Reaction game/script.js @@ -0,0 +1,56 @@ +$(document).ready(function() { + $(document).on("click", ".start button, .end button", function() { + App.start_game(); + }); + $(document).on("click", ".dot.active", function() { + App.add_point(); + }); + }); + + App = { + current_active: "", + dot_selector: ".wall .dot", + points: 0, + time: 30, + time_interval: "", + + init_game: function() { + this.points = 0; + this.time = 30; + }, + + start_game: function() { + this.init_game(); + $(".start, .end").fadeOut("fast"); + this.set_dot(); + $(".info .time").html("00:" + this.time); + $(".info .points").html(this.points); + this.time_interval = setInterval("App.update_time()", 1000); + }, + + update_time: function() { + this.time -= 1; + if (this.time > 0) { + if (String(this.time).length == 1) { + this.time = "0" + this.time; + } + $(".info .time").html("00:" + this.time); + } else { + $(".info .time").html("00:00"); + clearInterval(this.time_interval); + $(".end .score").html("Game over!
You scored " + this.points + " points!").parent().fadeIn("fast"); + } + }, + + add_point: function() { + this.set_dot(); + this.points += 1; + $(".info .points").html(this.points); + }, + + set_dot: function() { + $(this.dot_selector).removeClass("active"); + var active = Math.floor((Math.random() * ($(this.dot_selector).length - 1))+1); + $(this.dot_selector + ":eq("+active+")").addClass("active"); + } + }; \ No newline at end of file diff --git a/Wall Reaction game/styles.css b/Wall Reaction game/styles.css new file mode 100644 index 000000000..4efa4a30c --- /dev/null +++ b/Wall Reaction game/styles.css @@ -0,0 +1,128 @@ +$size: 400px; + +* { + margin: 0; + padding: 0; + border: 0; +} + +html, body { + height: 100%; +} + +body { + font-family: sans-serif; + background: #2c3e50; +} + +.info { + position: fixed; + top: 0px; + left: 0px; + background: #ecf0f1; + border-radius: 0 0 5px 0; + height: auto; + width: auto; + padding: 20px; + color: #000; + .time { + &:before { + content: "Time left:"; + margin-right: 5px; + } + } + .points { + &:before { + content: "Points:"; + margin-right: 20px; + } + } +} + +.wall { + width: $size; + height: $size; + position: absolute; + margin: auto; + top: 0; + left: 0; + bottom: 0; + right: 0; + .dot { + height: 10%; + width: 10%; + background: #ecf0f1; + border-radius: 50%; + float: left; + &.active { + background: #e74c3c; + cursor: pointer; + } + } + .start { + position: absolute; + height: 100%; + width: 100%; + background: #ecf0f1; + border-radius: 5px; + z-index: 100; + top: 0; + left: 0; + button { + width: $size / 2; + height: $size / 10; + font-size: $size / 20; + text-transform: uppercase; + background: #e74c3c; + border-radius: 5px; + position: absolute; + margin: auto; + top: 0; + left: 0; + right: 0; + bottom: 0; + cursor: pointer; + &:hover { + background: darken(#e74c3c, 20%); + } + } + } + .end { + position: absolute; + width: 100%; + height: 100%; + background: #ecf0f1; + z-index: 100; + border-radius: 5px; + display: none; + .score { + text-align: center; + color: #000; + font-size: $size / 15; + line-height: $size / 5; + } + button { + width: $size / 2; + height: $size / 10; + font-size: $size / 20; + text-transform: uppercase; + background: #e74c3c; + border-radius: 5px; + position: absolute; + margin: auto; + top: 0; + left: 0; + right: 0; + bottom: 0; + cursor: pointer; + &:hover { + background: darken(#e74c3c, 20%); + } + } + } + &:after { + content: ""; + clear: both; + display: table; + } +} \ No newline at end of file diff --git a/activity.css b/activity.css new file mode 100644 index 000000000..503bf9f8c --- /dev/null +++ b/activity.css @@ -0,0 +1,142 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + margin-left: 5px; + } + + body { + font-family: "Poppins", sans-serif; + background-attachment: fixed; + background-size: cover; + /* color: #333; */ + background-color:#040423; + } + + header { + padding: 1em 0; + display: flex; + justify-content: space-between; + align-items: center; + } + + header nav a { + color: #d0defa; + text-decoration: none; + font-size: 25px; + transition: color 0.3s; + } + + header nav a:hover { + color: #534141; + } + + .fa-home, + .fa-arrow-left { + color: #ffffff; + padding: 0 15px; + } + + .fa-home:hover { + color: #be8f8f; + } + + .fa-arrow-left:hover { + color: #be8f8f; + } + + .fa-arrow-left { + position: absolute; + right: 5px; + } + + + main { + display: flex; + flex-direction: column; + align-items: center; + padding: 2em 0; + color: #4b4646; + } + + h1 { + font-size: 2em; + color: #ffffff; + margin-bottom: 1em; + } + + .activity-feed { + background-color: #4a4444; + padding: 1.5em; + border-radius: 10px; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); + width: 80%; + max-width: 850px; + } + + .activity-feed ul { + list-style: none; + } + + .activity-feed li { + display: flex; + align-items: center; + margin-bottom: 1em; + padding: 1em; + background-color: #d0defa; + border-radius: 8px; + transition: transform 0.2s, box-shadow 0.2s; + } + + .activity-feed li:hover { + transform: translateY(-5px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + } + + .activity-feed i { + font-size: 1.5em; + margin-right: 15px; + color: #514f4f; + } + + .activity-feed p { + font-size: 1em; + color: #333; + } + + .fa-shopping-cart, + .fa-truck, + .fa-check { + color: #e74c3c; + } + + .fa-envelope { + color: #3498db; + } + + .fa-book, + .fa-exchange { + color: #f1c40f; + } + + + + + /* Responsive Design */ + @media only screen and (max-width: 768px) { + main { + padding: 1em; + } + .activity-feed { + width: 90%; + } + } + + @media only screen and (max-width: 480px) { + header nav a { + font-size: 20px; + } + .activity-feed { + width: 100%; + } + } \ No newline at end of file diff --git a/activity.js b/activity.js new file mode 100644 index 000000000..b85f5b97f --- /dev/null +++ b/activity.js @@ -0,0 +1,80 @@ +const listItems = document.querySelectorAll('.activity-feed li'); + +listItems.forEach((item, index) => { + item.addEventListener('click', () => { + const detailElement = item.querySelector('.detail'); + + // If details are already open, close them + if (detailElement) { + detailElement.remove(); + } else { + // Close any open details + const openDetails = document.querySelectorAll('.detail'); + openDetails.forEach((detail) => { + detail.remove(); + }); + + // Open the clicked item's details + const detailHtml = getDetailHtml(index); + const detailContainer = document.createElement('div'); + detailContainer.className = 'detail'; + detailContainer.innerHTML = detailHtml; + item.appendChild(detailContainer); + } + }); +}); + +function getDetailHtml(index) { + let detailHtml = ''; + switch (index) { + case 0: + detailHtml = ` +

Profile Update

+

Update ID: #1001

+

Date: 2024-11-05

+

Time: 13:30

+

Details: Profile information updated successfully

+ `; + break; + case 1: + detailHtml = ` +

Friend Request

+

Request ID: #1002

+

Date: 2024-11-05

+

Time: 11:00

+

From: Ritu

+ `; + break; + case 2: + detailHtml = ` +

New Game Alert

+

Game ID: #1003

+

Date: 2024-11-04

+

Time: 16:00

+

Game: Mystery Island

+ `; + break; + case 3: + detailHtml = ` +

Message from Divya

+

Message ID: #1004

+

Date: 2024-11-04

+

Time: 15:00

+

Message: "Are you joining the game tonight?"

+ `; + break; + case 4: + detailHtml = ` +

Message from Support

+

Message ID: #1005

+

Date: 2024-11-03

+

Time: 10:30

+

Message: "Your issue has been resolved"

+ `; + break; + default: + detailHtml = ''; + } + return detailHtml; +} + diff --git a/assets/image/Flames Game.png b/assets/image/Flames Game.png new file mode 100644 index 000000000..910db6d62 Binary files /dev/null and b/assets/image/Flames Game.png differ diff --git a/assets/image/High_Priestess-tarot.jpg b/assets/image/High_Priestess-tarot.jpg new file mode 100644 index 000000000..d3f0d6e3b Binary files /dev/null and b/assets/image/High_Priestess-tarot.jpg differ diff --git a/assets/image/The-Fool-tarot.jpg b/assets/image/The-Fool-tarot.jpg new file mode 100644 index 000000000..6b6e6bf75 Binary files /dev/null and b/assets/image/The-Fool-tarot.jpg differ diff --git a/assets/image/The-Magician-tarot.jpg b/assets/image/The-Magician-tarot.jpg new file mode 100644 index 000000000..63d9b9fe0 Binary files /dev/null and b/assets/image/The-Magician-tarot.jpg differ diff --git a/assets/image/mindful-breathing.jpg b/assets/image/mindful-breathing.jpg new file mode 100644 index 000000000..d96815974 Binary files /dev/null and b/assets/image/mindful-breathing.jpg differ diff --git a/assets/image/sketch-pad-img.jpg b/assets/image/sketch-pad-img.jpg new file mode 100644 index 000000000..7a3dd220f Binary files /dev/null and b/assets/image/sketch-pad-img.jpg differ diff --git a/assets/image/tarot.jpg b/assets/image/tarot.jpg new file mode 100644 index 000000000..45f891d8c Binary files /dev/null and b/assets/image/tarot.jpg differ diff --git a/assets/image/the-Empress-tarot.jpg b/assets/image/the-Empress-tarot.jpg new file mode 100644 index 000000000..49fc9c3b8 Binary files /dev/null and b/assets/image/the-Empress-tarot.jpg differ diff --git a/assets/image/the-emperor-tarot.jpg b/assets/image/the-emperor-tarot.jpg new file mode 100644 index 000000000..a8cca94fb Binary files /dev/null and b/assets/image/the-emperor-tarot.jpg differ diff --git a/challenges/challengehtml.html b/challenges/challengehtml.html index 66fc2e2a2..1dcb6f4ce 100644 --- a/challenges/challengehtml.html +++ b/challenges/challengehtml.html @@ -4,6 +4,7 @@ HTML Quiz + + +

------ Web Development Challenges ------


diff --git a/challenges/medcss.html b/challenges/medcss.html index 424298833..741182ef4 100644 --- a/challenges/medcss.html +++ b/challenges/medcss.html @@ -4,6 +4,7 @@ CSS MEDIUM Quiz + - -
- -
-
-

Contact Us

+ + +
+
+
+

Contact Us

+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
-
-
-
-
- - -
-
- - -
-
- - -
- -
-
-
- Contact Image -
+
+ Contact Image
-
- - +
+
+ + diff --git a/css/style.css b/css/style.css index 711194d4d..517175070 100644 --- a/css/style.css +++ b/css/style.css @@ -19,6 +19,7 @@ html { scrollbar-width: thin; scrollbar-color: #08ecbb #3c0751; + scroll-behavior: smooth; } * { @@ -31,6 +32,73 @@ html { } +/* Navbar Styles */ +.navbar { + width: 100%; + background-color: #333; + color: #fff; + padding: 1rem; +} + +.navbar-container { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1200px; + margin: 0 auto; +} + +.navbar-logo { + font-size: 1.5rem; + color: #fff; + text-decoration: none; +} + +.navbar-menu { + list-style: none; + display: flex; + gap: 1rem; +} + +.navbar-menu li { + display: inline; +} + +.navbar-menu a { + color: #fff; + text-decoration: none; + font-weight: bold; +} + +.navbar-menu a:hover { + text-decoration: underline; +} + +@media (max-width: 768px) { + .navbar-menu { + display: none; + } +} + +.navbar-container { + display: flex; + align-items: center; +} + +.logo-image { + height: 40px; /* Adjust the size as needed */ + width: auto; /* Keep aspect ratio */ + margin-right: 10px; /* Add some space between logo and text */ +} + +.navbar-logo { + display: flex; + align-items: center; + color: #fff; + text-decoration: none; + font-size: 1.5rem; +} + #progress-container { position: fixed ; top: 0px; @@ -75,7 +143,7 @@ html [data-theme="dark"] { --btn-text: #1e293b; --head: #fff; --card: #192333; - --p: #94A3B8; + --p: #2b1714; --input-bg: #2c3a4b; /* Input background color */ --input-border: #3b4a5a; /* Input border color */ @@ -651,7 +719,7 @@ body { flex-grow: 1; } [data-theme="dark"] .card-description { - color: #cdd2d8; + color: #1e2c3c; } .counter-cover-color { background: rgba(255, 206, 0, 0.2); @@ -1017,6 +1085,20 @@ body { margin: 20px auto; /* Centering the section */ text-align: center; /* Center text */ } +.container [data-theme="dark"] { + --bg: #1e293b; + --btn-color: #fecdd3; + --btn-text: #1e293b; + --head: #fff; + --card: #192333; + --p: #94A3B8; + + --input-bg: #2c3a4b; /* Input background color */ + --input-border: #3b4a5a; /* Input border color */ + --input-text: #ffffff; /* Input text color */ + --input-placeholder: #fffff0; /* Placeholder text color */ +} + #feedback-section h1 { font-family: 'Arial', sans-serif; /* Font family */ @@ -1182,7 +1264,7 @@ input:focus { } footer{ - background-color: #141d2b; + background-color: #2b1714; height: 20vh; width: 100vw; } diff --git a/faq.html b/faq.html index d34e9885a..2fc53e565 100644 --- a/faq.html +++ b/faq.html @@ -3,7 +3,7 @@ - + FAQ Page + + Master web development - + +
@@ -377,40 +101,30 @@ - - +
@@ -441,14 +155,7 @@

Open Projects

--> -
- - -
+
@@ -470,7 +177,7 @@

Open Projects

A simple air balloon game.......
- +
@@ -565,11 +272,12 @@

Open Projects

Master web development

Organize Projects

+
@@ -578,7 +286,6 @@

Organize Projects

-
@@ -588,19 +295,36 @@

Organize Projects

- - - -
- - + + +
+
+
+ +
-
-
+ +
+ Diwali Wish +
+
+

Animated Diwali Wish

+

+ An animated website to wish Happy Diwali. +

+
+
Generate Gradient @@ -624,7 +348,42 @@

iRadio

+ + +
+ +
+
+

Mindfulness Breathing App

+

+ Lets inhale positivity and exhale negativty. +

+
+
+ + +
+ +
+
+

Digital Sketchpad with Colors & Brushes

+

+ Lets explore world of artistic creativity. +

+
+
+ +
+ +
+
+

Virtual Tarot Card Reading

+

+ What does your fate says? +

+
+
@@ -835,6 +594,21 @@

Love Calculator

+ + +
+ Flames Game +
+
+

Flames Game Calculator

+

+ Check the relationship between two persons. +

+
+
+ + +
Magic Square @@ -950,7 +724,7 @@

Snake Game

- +
@@ -1045,6 +819,26 @@

Quantum Themed Maze Game

Virtual Memory Jar

+

+ +

+
+ + +
+ 3D Room Decorator +
+
+

3D Room Decorator

+

+ Design and decorate a 3D room layout to visualize your space. +

+
+
+ + +
+ Microblog with Customizable Animated Text Virtual Memory Jar @@ -1058,21 +852,71 @@

Virtual Memory Jar

Password Generator

- Generate secure passwords easily! + Generate secure passwords easily!

-
+ +
+
-

Meme Generator

+

Generative Mandala Creator

- A meme generator. + Create intricate, generative mandala designs with this creative tool. +

+
+
+ + +
+ Lofi Room Ambience Generator +
+
+

Customizable “Lofi Room” Ambience Generator

+

+ Create a personalized ambiance with customizable sounds for relaxation and focus. +

+
+
+ +
+ Quantum Themed Maze Game +
+
+

Quantum Themed Maze Game

+

+ Navigate through a maze filled with quantum puzzles and challenges. +

+
+
+ +
+ +
+
+

Virtual Memory Jar

+

+ + Virtual Memory Jar + +

+
+
+ +
+ +
+
+

Password Generator

+

+ Generate secure passwords easily!

+ @@ -1146,17 +990,7 @@

AI-Powered Style Picker

- -
- AI-Powered Style Picker -
-
-

AI-Powered Style Picker

-

- Customize your style with AI-powered suggestions! -

-
-
+
@@ -1164,75 +998,335 @@

AI-Powered Style Picker

-

Random User Generator

+

Password Generator

+ + A random password generator. Generate random user profiles with this tool.

- - +
- +
-

Ping Pong Game

+

Physics-Based Particle Sandbox

- A simple Ping Pong Game. + Physics-Based Particle Sandbox

- - +
- +
-

Todo List

+

Meme Generator

- A simple Todo list. + Create and share memes easily!

- +
- link tree icon +
-

Linktree Clone

+

Meme Generator

- Linktree clone made using HTML & CSS. + A meme generator.

- - +
- link tree icon + Real-Time Heatmap
-

Tic Tac Toe

+

Real-Time Heatmap with Mouse Interactions

- A minimalistic two player game. + Create a real-time heatmap based on mouse movements!

- - -
- number guessing + + + + + + +
+ Memory Tiles
-

Guess my Number

+

Memory Tiles

- Number guessing 1 to 20. + Test your memory with this fun game.

-
- -
- BMI CALCULATOR -
+ + + + +
+
+ Digital Scrapbook +
+
+

Digital Scrapbook with Animated Stickers

+

+ Create your digital scrapbook with fun, animated stickers! + +

+
+
+ + + +
+ Random User Generator + +
+
+

Random User Generator

+

+ Generate random user profiles with this tool. +

+
+
+ +
+ +
+
+

Random User Generator

+

+ A Web-app to generate information of users +

+
+
+ +
+ +
+
+

Ping Pong Game

+

+ A simple Ping Pong Game. +

+
+
+ +
+ +
+
+

Todo List

+

+ A simple Todo list. +

+
+
+ +
+ link tree icon +
+
+

Linktree Clone

+

+ Linktree clone made using HTML & CSS. +

+
+
+ + +
+ link tree icon +
+
+

Tic Tac Toe

+

+ A minimalistic two player game. +

+
+
+ +
+ number guessing +
+
+

Guess my Number

+

+ Number guessing 1 to 20. +

+
+
+ +
+ BMI CALCULATOR +

BMI Calculator

@@ -1297,9 +1391,14 @@

Emotion Detection for Feedback Forms

+ + + +
Sudoku Solver +

Sudoku Solver

@@ -1308,7 +1407,6 @@

Sudoku Solver

-
Simon Game @@ -1353,7 +1451,13 @@

Wordle Game

+ + + + + +
@@ -1388,6 +1492,23 @@

Time Capsule Website

+ + + + +
+ Internet Speed Checker +
+
+

Internet Speed Checker

+

+ Check your internet speed with this simple tool. +

+
+
+
@@ -1403,6 +1524,7 @@

Certificate Generator

+
Internet Speed Checker
@@ -1412,12 +1534,173 @@

Internet Speed Checker

Check your internet speed with this simple tool.

+ --> + + + +
+ pattern logo +
+
+

Mind Match: Pattern Logic Game

+

+ Mind Match: Pattern Logic Game +

+
+
+ +
+ color logo +
+
+

Colorblind Mode: Puzzle Through Perspective

+

+ Colorblind Mode: Puzzle Through Perspective +

+
+
+ +
+ horoscope logo +
+
+

Personalized Horoscope & Star Map Viewer

+

+ Personalized Horoscope & Star Map Viewer +

+
+
+ + +
+ Fabric logo +
+
+

Fabric Tool

+

+ Creates innovative designs with fabric +

+
-
- Poem logo + Poem logo

Poetry generator

@@ -1427,6 +1710,7 @@

Poetry generator

+
pattern logo @@ -1518,6 +1802,7 @@

Responsive Fractal Generator

+
Travel logo @@ -1528,7 +1813,32 @@

Travel Destination Generator

Generates destinations for travel

-
+ + --> + + +
+ Memory Match Game logo +
+
+

Memory Match Game

+

+ Match the pairs of cards. +

+
+
+ +
-

Weather Outfit Finder

+

Color Personality Analyzer

- Finds the outfit that suits the weather. + Analyze your personality based on color!

+ +
--> + +
Color Personality Analyser

- +
Weather Outfit Finder logo @@ -1660,7 +2026,7 @@

Weather Outfit Finder

- +
Interactive Storybook logo @@ -1673,7 +2039,7 @@

Interactive Storybook

- +
Recipe Finder
- -
- QR Generator logo - -
-
-

Random QR Generator

-

- Generate random QR codes quickly! -

-
-
-
QR Generator logo @@ -1714,11 +2067,13 @@

Random QR Generator

+
- Image Generator logo
+

Age calculator

Calculates age @@ -1809,149 +2164,1014 @@

3D product display

Github Profile Viewer

-

- GitHub Profile Viewer enables users to effortlessly explore and analyze GitHub profiles, - highlighting key statistics and contributions. -

-
-
- - -
- Rolling Triumph -
-
-

Rolling Triumph

-

- Dice game web app for strategic scoring. -

-
-
- -
- -
-
-

Saveman

-

- Guess the word -

-
-
- -
- link tree icon -
-
-

Connect-4-Dot

-

- Connect dots and win the game. -

-
-
- - -
- -
-
-

Dictionary

-

- Dictionary app -

-
-
- - - -
- Flashcard Quiz App logo -
-
-

Flashcard Quiz App

-

- Create and review flashcards for effective learning. -

-
-
- - - - -
- -
-
-

Weather

- Check weather + Generates QR code

-
+ + + +
+ Image Generator logo +
+
+

Age calculator

+

+ Calculates age +

+
+
+ + +
+ Password Generator logo +
+
+

Password Toggle

+

+ Toggles password +

+
+
+ + +
+ habits logo +
+
+

Habit tracker

+

+ Tracks habits +

+
+
+ + +
+ creative writing logo +
+
+

Creative writing

+

+ Generates creative writing prompts +

+
+
+ + +
+ plant +
+
+

Interactive Virtual Plant Care Guide

+

+ The best guide for your gardening journey! +

+
+
+ + +
+ data +
+
+

Interactive Data Visualization Dashboard

+

+ Visualises the data +

+
+
+ + +
+ Product display +
+
+

3D product display

+

+ Displays the product in third dimension +

+
+
+ + + + +
+ +
+
+

Github Profile Viewer

+

+ GitHub Profile Viewer enables users to effortlessly explore and analyze GitHub profiles, + highlighting key statistics and contributions. +

+
+
+ + + + + + +
+ Rolling Triumph +
+
+

Rolling Triumph

+

+ Dice game web app for strategic scoring. +

+
+
+ +
+ +
+
+

Saveman

+

+ Guess the word +

+
+
+ +
+ link tree icon +
+
+

Connect-4-Dot

+

+ Connect dots and win the game. +

+
+
+ + + +
+ +
+
+

Dictionary

+

+ Dictionary app +

+
+
+ + + +
+ Flashcard Quiz App logo +
+
+

Flashcard Quiz App

+

+ Create and review flashcards for effective learning. +

+
+
+ + + + +
+ +
+
+

Weather

+

+ Check weather +

+
+
+ + + +
+ +
+
+

Currency Convertor

+

+ Convert currencies from different countries +

+
+
+ + +
+ Brick Breaker +
+
+

Brick Breaker

+

+ A Brick Breaker Game. +

+
+
+ + +
+ trivia +
+
+

TRIVIA

+

+ Test Your Knowledge +

+
+
+ + +
+ wordle logo +
+
+

GeoQuest

+

+ Explore the World, One Chenge at a Time!

+
+
+ + +
+ Lorem_ipsum +
+
+

Lorem Ipsum Generator

+

+ Guess the word in 6 attempts +

+
+
+ + + +
+ +
+
+

Cow and Bull Game

+

+ A classic number-guessing game. +

+
+
+ + +
+ Whac a mole logo +
+
+

Whack a Mole

+

+ Smash moles, score big fun! +

+
+
+ + +
+ +
+
+

Pokedex

+

+ Find Details of your favorite Pokemon +

+
+ +
+ + +
+ Super Mario +
+
+

Super Mario

+

+ A Popular Game. +

+
+
+ + +
+ magic8ball +
+
+

Magic 8 Ball

+

+ Think of a question and let Magic 8 Ball decide the answer for you. +

+
+
+ + +
+ Rock paper scissor icon +
+
+

Rock Paper Scissors

+

+ Choose your move Tie! +

+
+
+ + +
+ +
+
+

Password manager

+

+ Manage your passwords +

+
+
+ + +
+ +
+
+

Eye that follow

+

+ EYE THAT FOLLOW YPOUR MOUSE +

+
+
+ + + +
+ Notes Taking +
+
+

Notes Taking

+

+ To write notes. +

+
+
+ + +
+ Dice Game +
+
+

Dice Game

+

+ The Player who gets the more number on the dice wins.(Just Refresh Me!!) +

+
+
+ + +
+ Slide puzzle +
+
+

Slide Puzzle

+

+ A Slide Puzzle game +

+
+
+ + +
+ Flappy Bird +
+
+

Flappy Bird

+

+ A fun game. +

+
+
+ + +
+ Doodle Jump +
+
+

Doodle Jump

+

+ Enjoy and Jump. +

+
+
+ +
+ Email Validator +
+
+

Email Validator

+

+ Validate E-MAIL +

+
+
+ +
+ maze game' +
+
+

Maze Game

+

+ Play exciting maze game +

+
+
+ + +
+ Doodle Jump +
+
+

Tetris Game

+

+ Arrange fing blocks to clear lines. +

+
+
+ + + + +
+ Othello Game +
+
+

Othello Game

+

+ Othello is a strategic board game where players aim to flip their opponent's discs to their color. +

+
+
- -
- -
-
-

Currency Convertor

-

- Convert currencies from different countries -

-
-
- - + +
+ Eigen Value and Vector Calculator +
+
+

Eigen Value and Vector Calculator

+

+ Eigen Value and Vector Calculator +

+
+
+ + +
- Brick Breaker +
-

Brick Breaker

+

Bhagvad Gita Quote Generator

- A Brick Breaker Game. -

+ Discover timeless wisdom with Bhagavad Gita quotes. +

- - -
- trivia -
-
+ + + + +
+
+
+

Fund Tracking

+

+ This project helps visually represent government funding and tracking. +

+
+
+ + + +
+ Eigen Value and Vector Calculator +
+
+

Eigen Value and Vector Calculator

+

+ Eigen Value and Vector Calculator +

+
+
+ + +
+ Candy Game +
+
+

Candy Game

+

+ A Candy game. +

+
+
+ + +
+ Stick Hero +
+
+

Stick Hero

+

+ A fun popular game. +

+
+
+ + +
+ Among Us +
+
+

Among Us

+

+ Find who is imposter. +

+
+
+ + +
+ Joke Generator +
+
+

Joke Generator

+

+ Find who is imposter. +

+
+
+ + + +
+ Train Track Game +
+
+

Train Track Game

+

+ A Train Track Game which is created by Three Js. +

+
+
+ + +
+ Building Block +
+
+

Building Block

+

+ A Building Block Game. +

+
+
+ + +
+ Lamb Lane +
+
+

Lamb Lane

+

+ A Lamb Lane Game. +

+
+
+ + +
+ Cut The Rope +
+
+

Cut The Rope

+

+ Cut the rope in game. +

+
+
+ + +
+  Url Shortner +
+
+

Url Shortner

+

+ Url Shortner +

+
+
+ + +
+ Email Validator +
+
+

Email Validator

+

+ Validate E-MAIL +

+
+
+ + +
+ new +
+
+

New Year Countdown

+

+ New year countdown +

+
+
+ + +
+ bil +
+
+

Bill Splitter

+

+ Split your bills +

+
+
+ + +
+ Email Validator +
+
+

Password Strength

+

+ Check Your Password's Strength. + +

+
+
+ + +
+ Mole Bop +
+
+

Mole Bop

+

+ Catch the Bop. +

+
+
+ + +
+ Virtual Keyboard' +
+
+

Virtual Keyboard

+

+ Virtual Keyboard +

+
+
+ + +
+ 2048 +
+
+

2048

+

+ 2048 is an easy and fun puzzle game. +

+
+
+ + +
+ wordle' +
+
+

Wordle

+

+ Enjoy the popular game +

+
+
+ + +
+ Minesweeper +
+
+

Minesweeper

+

+ Classic Minesweeper game. +

+
+
+ + +
+ a coin representing how to win the game +
+
+

Space Collision Game

+

+ Space Collision: Catch Coins and avoid Enemies to Score +

+
+
+ + +
+ pianoImage +
+
+

Piano

+

+ Experience the most realistic virtual piano. +

+
+
+ + +
+ Word Scrable Image +
+
+

Word Scramble

+

+ A fun Word Scramble Game where players rearrange jumbled letters to form correct words within a time + limit. +

+
+
+ + +
+ bubblePoster +
+
+

Bubble Hit Game

+

+ Tap the Right Bubble, Beat the Clock!" +

+
+
+ + + +
+ chatbot +
+
+

Chatbot

+

+ A simple chatbot.
+ Please use your own google gemini api key for the chatbot to work. +

+
+
+ +
+ Calculator +
+
+

Tip Calculator

+

+ Quickly calculate tips and total bills with ease. +

+
+
+ + +
+ Brick Breaker +
+
+

Random Image Generator

+

+ A Random Image Generator Website +

+
+
+ + +
+ Rolling Triumph +
+
+

Rolling Triumph

+

+ Dice game web app for strategic scoring. +

+
+
+ +
+ +
+
+

Saveman

+

+ Guess the word +

+
+
+ +
+ link tree icon +
+
+

Connect-4-Dot

+

+ Connect dots and win the game. +

+
+
+ + + +
+ +
+
+

Dictionary

+

+ Dictionary app +

+
+
+ + + +
+ Flashcard Quiz App logo +
+
+

Flashcard Quiz App

+

+ Create and review flashcards for effective learning. +

+
+
+ + + + +
+ +
+
+

Weather

+

+ Check weather +

+
+
+ + + +
+ +
+
+

Currency Convertor

+

+ Convert currencies from different countries +

+
+
- -
- wordle logo -
-
-

GeoQuest

-

- Explore the World, One Chenge at a Time!

-
-
+ +
+ Brick Breaker +
+
+

Brick Breaker

+

+ A Brick Breaker Game. +

+
+
+ + +
+ trivia +
+
+

TRIVIA

+

+ Test Your Knowledge +

+
+
+ + +
+ wordle logo +
+
+

GeoQuest

+

+ Explore the World, One Chenge at a Time!

+
+
+ + +
+ Lorem_ipsum +
+
+

Lorem Ipsum Generator

+

+ Guess the word in 6 attempts +

+
+
- -
- Lorem_ipsum -
-
-

Lorem Ipsum Generator

-

- Guess the word in 6 attempts -

-
-
- - +
@@ -2129,32 +3349,34 @@

Maze Game

- +
- Doodle Jump + Tetris Game

Tetris Game

- Arrange fing blocks to clear lines. + Arrange falling blocks to clear lines.

- -
- + + +
+ Connect4 Game
-

Connect4(Two player)

+

Connect4 (Two player)

Connect4: Where Strategy Meets Fun, One Disc at a Time!

- + +
- Candy Game + Candy Game

Candy Game

@@ -2163,9 +3385,10 @@

Candy Game

- + +
- Stick Hero + Stick Hero

Stick Hero

@@ -2177,17 +3400,7 @@

Stick Hero

-
-
- Among Us -
-
-

Among Us

-

- Find who is imposter. -

-
-
+
Joke Generator @@ -2299,915 +3512,1088 @@

Bill Splitter

Password Strength

- Check Your Password's Strength. - -

-
-
- - -
- Mole Bop -
-
-

Mole Bop

-

- Catch the Bop. -

-
-
- -
- Virtual Keyboard' -
-
-

Virtual Keyboard

-

- Virtual Keyboard -

-
-
- - -
- 2048 -
-
-

2048

-

- 2048 is an easy and fun puzzle game. -

-
-
- - - - -
- wordle' -
-
-

Wordle

-

- Enjoy the popular game -

-
-
- - - -
- Minesweeper -
-
-

Minesweeper

-

- Classic Minesweeper game. -

-
-
- - -
- a coin representing how to win the game -
-
-

Space Collision Game

-

- Space Collision: Catch Coins and avoid Enemies to Score -

-
-
- - -
- pianoImage -
-
-

Piano

-

- Experience the most realistic virtual piano. -

-
-
- - -
- Word Scrable Image -
-
-

Word Scramble

-

- A fun Word Scramble Game where players rearrange jumbled letters to form correct words within a time - limit. -

-
-
- - - -
- bubblePoster -
-
-

Bubble Hit Game

-

- Tap the Right Bubble, Beat the Clock!" -

-
-
- - - -
- chatbot -
-
-

Chatbot

-

- A simple chatbot.
- Please use your own google gemini api key for the chatbot to work. -

-
-
- - -
- Brick Breaker -
-
-

Random Image Generator

-

- A Random Image Generator Website -

-
-
- - - -
- Shooter Game -
-
-

Shooter Game

-

- Take aim and unleash your skills in shooter experience! -

-
-
- - - -
- relaxio image -
-
-

Relaxio Stress Buster

-

- Keep calm and just relax. -

-
-
- - -
- LeetCode Question Generator -
-
-

LeetCode Question Generator

-

- Generate random LeetCode questions for practice. -

-
-
- - -
- chatbot -
-
-

Photo Gallery

-

- List all the random Photos. -

-
-
- - -
- chatbot -
-
-

Netflix Clone

-

- A UI-based Clone of the popular mainstream service "NETFLIX". -

-
-
- - - - - -
- recipebook -
-
-

Recipe Book

-

- Gather Delicious Secret Recipes! -

-
-
- - - - - - - - - -
- Tennis -
-
-

Tennis Game

-

- multiplayers Tennis Game -

-
-
- - - - - -
- music visualizer -
-
-

3D Music Visualizer

-

- Experience the most realistic 3D music visualizer. + Check Your Password's Strength. +

- - - +
- Calculator + Mole Bop
-

Tip Calculator

+

Mole Bop

- Quickly calculate tips and total bills with ease. + Catch the Bop.

- - +
- Brick Breaker + Virtual Keyboard'
-

Random Image Generator

+

Virtual Keyboard

- A Random Image Generator Website + Virtual Keyboard

- - -
- Shooter Game + + +
+ 2048
-

Shooter Game

+

2048

- Take aim and unleash your skills in shooter experience! + 2048 is an easy and fun puzzle game.

- - - + + +
- Drawing App + wordle'
-

Drawing App

+

Wordle

- Unleash Your Creativity with Every Stroke! + Enjoy the popular game

- - - +
- Othello Game + Minesweeper
-

Othello Game

+

Minesweeper

- Othello is a strategic board game where players aim to flip their opponent's discs to their color. + Classic Minesweeper game.

- - -
- Credit Card Validator + +
+ a coin representing how to win the game
-

Credit Card Validator

+

Space Collision Game

- Validate your credit card number with ease. + Space Collision: Catch Coins and avoid Enemies to Score

+ -
- Tennis + Shooter Game
-

Tennis Game

+

Shooter Game

+

+ Take aim and unleash your skills in shooter experience! +

+
+
+ + +
+ relaxio image +
+
+

Relaxio Stress Buster

+

+ Keep calm and just relax. +

+
+
+ + +
+ LeetCode Question Generator +
+
+

LeetCode Question Generator

+

+ Generate random LeetCode questions for practice. +

+
+
+ + + + +
+ chatbot +
+
+

Photo Gallery

+

+ List all the random Photos. +

+
+
+ + +
+ chatbot +
+
+

Netflix Clone

+

+ A UI-based Clone of the popular mainstream service "NETFLIX". +

+
+
+ + + + + + +
+ recipebook +
+
+

Recipe Book

- multiplayers Tennis Game + Gather Delicious Secret Recipes!

-
+ + + + + +
+ Tennis +
+
+

Tennis Game

+

+ multiplayers Tennis Game +

+
+
+ + + + + +
+ music visualizer +
+
+

3D Music Visualizer

+

+ Experience the most realistic 3D music visualizer. +

+
+
+ + + +
+ Calculator +
+
+

Tip Calculator

+

+ Quickly calculate tips and total bills with ease. +

+
+
+ + +
+ Shooter Game +
+
+

Shooter Game

+

+ Take aim and unleash your skills in shooter experience! +

+
+
+ + +
+ Drawing App +
+
+

Drawing App

+

+ Unleash Your Creativity with Every Stroke! +

+
+
+ + + +
+ Othello Game +
+
+

Othello Game

+

+ Othello is a strategic board game where players aim to flip their opponent's discs to their color. +

+
+
+ + + +
+ Credit Card Validator +
+
+

Credit Card Validator

+

+ Validate your credit card number with ease. +

+
+
+ + + +
+ Tennis +
+
+

Tennis Game

+

+ multiplayers Tennis Game +

+
+
+ + + + + +
+ recipebook +
+
+

Recipe Book

+

Gather Delicious Secret Recipes!

+
+
+ + +
+ Sudoku_Solver +
+
+

Sudoku Solver

+

+ Solve any valid Sudoku in just 1 click +

+
+
+ + + + + +
+ Bus image +
+
+

Bus Reservation System

+

Fast, Easy, and Reliable Bus Reservations.

+
+
+ + + +
+ Tower of Hanoi +
+
+

Tower of Hanoi

+

Solve the classic Tower of Hanoi puzzle.

+
+
+ + + +
+ Odd Even Game +
+
+

Odd Even Game

+

Try your luck with the odd or even game.

+
+
+ +
+ Flappy Bird +
+
+

Flappy Bird

+

A fun game.

+
+
+ +
+ Doodle Jump +
+
+

Doodle Jump

+

Enjoy and Jump.

+
+
+ +
+ Email Validator +
+
+

Email Validator

+

Validate E-MAIL

+
+
+ +
+ maze game' +
+
+

Maze Game

+

Play exciting maze game

+
+
+ +
+ Minesweeper +
+
+

Minesweeper

+

Classic Minesweeper game.

+
+
+ +
+ Doodle Jump +
+
+

Tetris Game

+

+ Arrange falling blocks to clear lines. +

+
+
+ - - -
- recipebook -
-
-

Recipe Book

-

Gather Delicious Secret Recipes!

-
-
- - +
- Sudoku_Solver + chatbot
-

Sudoku Solver

+

Chatbot

- Solve any valid Sudoku in just 1 click + A simple chatbot.
+ Please use your own google gemini api key for the chatbot to work.

- -
- Bus image -
-
-

Bus Reservation System

-

Fast, Easy, and Reliable Bus Reservations.

-
-
+ - - -
- Tower of Hanoi -
-
-

Tower of Hanoi

-

Solve the classic Tower of Hanoi puzzle.

-
-
- - -
- Tower of Hanoi -
-
-

Tower of Hanoi

-

Solve the classic Tower of Hanoi puzzle.

-
-
+ - - -
- Odd Even Game -
-
-

Odd Even Game

-

Try your luck with the odd or even game.

-
-
+ - - -
- Odd Even Game -
-
-

Odd Even Game

-

Try your luck with the odd or even game.

-
-
- + + + + + +
+ recipebook +
+
+

Recipe Book

+

+ Gather Delicious Secret Recipes! +

+
+
+ + + - -
- Flappy Bird -
-
-

Flappy Bird

-

A fun game.

-
-
- -
- Doodle Jump -
-
-

Doodle Jump

-

Enjoy and Jump.

-
-
- -
- Email Validator -
-
-

Email Validator

-

Validate E-MAIL

-
-
- -
- maze game' -
-
-

Maze Game

-

Play exciting maze game

-
-
- -
- Minesweeper -
-
-

Minesweeper

-

Classic Minesweeper game.

-
-
- -
- Doodle Jump -
-
-

Tetris Game

-

- Arrange falling blocks to clear lines. -

-
-
- -
- -
-
-

Connect4(Two player)

-

- Connect4: Where Strategy Meets Fun, One Disc at a Time! -

-
-
- -
- Flappy Bird -
-
-

Among us

-

Find who is imposter.

-
-
- -
- Joke Generator -
-
-

Joke Generator

-

Find who is imposter.

-
-
- -
- Email Validator -
-
-

Email Validator

-

Validate E-MAIL

-
-
- -
- maze game' -
-
-

Maze Game

-

Play exciting maze game

-
-
- -
- Minesweeper -
-
-

Minesweeper

-

Classic Minesweeper game.

-
-
- -
- Doodle Jump -
-
-

Tetris Game

-

- Arrange falling blocks to clear lines. -

-
-
- -
- -
-
-

Connect4(Two player)

-

- Connect4: Where Strategy Meets Fun, One Disc at a Time! -

-
-
- -
- Candy Game -
-
-

Candy Game

-

A Candy game.

-
-
- -
- Stick Hero -
-
-

Stick Hero

-

A fun popular game.

-
-
- -
- Among Us -
-
-

Among Us

-

Find who is imposter.

-
-
- -
- Joke Generator -
-
-

Joke Generator

-

Find who is imposter.

-
-
- + + + + + + + + + + + + + + + + + + + + + + +
- Cut The Rope + Calculator
-

Cut The Rope

-

Cut the rope in game.

+

Tip Calculator

+

+ Quickly calculate tips and total bills with ease. +

- + +
-  Url Shortner + Brick Breaker
-

Url Shortner

-

Url Shortner

+

Random Image Generator

+

+ A Random Image Generator Website +

- + +
- Email Validator + Shooter Game
-

Email Validator

-

Validate E-MAIL

+

Shooter Game

+

+ Take aim and unleash your skills in shooter experience! +

- + + + +
- new + Drawing App
-

New Year Countdown

-

New year countdown

+

Drawing App

+

+ Unleash Your Creativity with Every Stroke! +

+ - + +
- bil + Othello Game
-

Bill Splitter

-

Split your bills

+

Othello Game

+

+ Othello is a strategic board game where players aim to flip their opponent's discs to their color. +

+ + -
- Email Validator + Credit Card Validator
-

Password Strength

-

Check Your Password's Strength.

+

Credit Card Validator

+

+ Validate your credit card number with ease. +

- - +
- Othello Game + Tennis
-

Othello Game

+

Tennis Game

- Othello is a strategic board game where players aim to flip their opponent's discs to their color. + multiplayers Tennis Game

- + + +
- Mole Bop + recipebook
-

Mole Bop

-

Catch the Bop.

+

Recipe Book

+

Gather Delicious Secret Recipes!

- + +
- Virtual Keyboard' -
-
-

Virtual Keyboard

-

Virtual Keyboard

-
-
- - -
- 2048 + Sudoku_Solver
-

2048

+

Sudoku Solver

- 2048 is an easy and fun puzzle game. + Solve any valid Sudoku in just 1 click

- - - +
- wordle' + Bus image
-

Wordle

-

Enjoy the popular game

+

Bus Reservation System

+

Fast, Easy, and Reliable Bus Reservations.

- -
- Minesweeper -
-
-

Minesweeper

-

Classic Minesweeper game.

-
-
- -
- a coin representing how to win the game -
-
-

Space Collision Game

-

- Space Collision: Catch Coins and avoid Enemies to Score -

-
-
+ +
+ Candy Game +
+
+

Candy Game

+

A Candy game.

+
+
+ +
+ Stick Hero +
+
+

Stick Hero

+

A fun popular game.

+
+
+ + +
+ Joke Generator +
+
+

Joke Generator

+

Find who is imposter.

+
+
+ +
+ Train Track Game +
+
+

Train Track Game

+

+ A Train Track Game which is created by Three Js. +

+
+
+ +
+ Building Block +
+
+

Building Block

+

A Building Block Game.

+
+
+ +
+ Lamb Lane +
+
+

Lamb Lane

+

A Lamb Lane Game.

+
+
+ +
+ Cut The Rope +
+
+

Cut The Rope

+

Cut the rope in game.

+
+
+ +
+  Url Shortner +
+
+

Url Shortner

+

Url Shortner

+
+
+ +
+ Email Validator +
+
+

Email Validator

+

Validate E-MAIL

+
+
+ +
+ new +
+
+

New Year Countdown

+

New year countdown

+
+
+ + + + +
+ Tower of Hanoi +
+
+

Tower of Hanoi

+

Solve the classic Tower of Hanoi puzzle.

+
+
+ + + +
+ bil +
+
+

Bill Splitter

+

Split your bills

+
+
+ + +
+ Email Validator +
+
+

Password Strength

+

Check Your Password's Strength.

+
+
+ +< + + + +
+ Othello Game +
+
+

Othello Game

+

+ Othello is a strategic board game where players aim to flip their opponent's discs to their color. +

+
+
+ + + +
+ Mole Bop +
+
+

Mole Bop

+

Catch the Bop.

+
+
+ +
+ Virtual Keyboard' +
+
+

Virtual Keyboard

+

Virtual Keyboard

+
+
+ + +
+ 2048 +
+
+

2048

+

+ 2048 is an easy and fun puzzle game. +

+
+
+ + + +
+ wordle' +
+
+

Wordle

+

Enjoy the popular game

+
+
+ + +
+ Minesweeper +
+
+

Minesweeper

+

Classic Minesweeper game.

+
+
+ + +
+ a coin representing how to win the game +
+
+

Space Collision Game

+

+ Space Collision: Catch Coins and avoid Enemies to Score +

+
+
+ + + +
+ Odd Even Game +
+
+

Odd Even Game

+

Try your luck with the odd or even game.

+
+
+ + - +
- pianoImage + Flappy Bird
-

Piano

-

- Experience the most realistic virtual piano. -

+

Flappy Bird

+

A fun game.

- -
- Word Scrable Image -
-
-

Word Scramble

-

- A fun Word Scramble Game where players rearrange jumbled letters - to form correct words within a time limit. -

-
-
- - -
- bubblePoster -
-
-

Bubble Hit Game

-

- Tap the Right Bubble, Beat the Clock!" -

-
-
+ +
+ pianoImage +
+
+

Piano

+

+ Experience the most realistic virtual piano. +

+
+
- - -
- chatbot -
-
-

Chatbot

-

- A simple chatbot.Please use your own google gemini api key for the chatbot to - work -

-
-
+ +
+ Word Scrable Image +
+
+

Word Scramble

+

+ A fun Word Scramble Game where players rearrange jumbled letters + to form correct words within a time limit. +

+
+
- -
- relaxio image -
-
-

Relaxio Stress Buster

-

Keep calm and just relax.

-
-
+ + +
+ bubblePoster +
+
+

Bubble Hit Game

+

+ Tap the Right Bubble, Beat the Clock!" +

+
+
- -
- LeetCode Question Generator -
-
-

LeetCode Question Generator

-

- Generate random LeetCode questions for practice. -

-
-
+ + +
+ chatbot +
+
+

Chatbot

+

+ A simple chatbot.Please use your own google gemini api key for the chatbot to + work +

+
+
+ +
+ relaxio image +
+
+

Relaxio Stress Buster

+

Keep calm and just relax.

+
+
- -
- chatbot -
-
-

Photo Gallery

-

List all the random Photos.

-
-
+ +
+ chatbot +
+
+

TESLA CLONE

+

+ A UI-based Clone of the popular car company TESLA. +

+
+
- -
- chatbot -
-
-

Netflix Clone

-

- A UI-based Clone of the popular mainstream service "NETFLIX". -

-
-
- -
- chatbot -
-
-

TESLA CLONE

-

- A UI-based Clone of the popular car company TESLA. -

-
-
+ +
+ music visualizer +
+
+

3D Music Visualizer

+

+ Experience the most realistic 3D music visualizer. +

+
+
+
+ recipebook +
+
+

Recipe Book

+

+ Gather Delicious Secret Recipes! +

+
+
- + + + +
+ Othello Game +
+
+

Othello Game

+

+ Othello is a strategic board game where players aim to flip their opponent's discs to their color. +

+
+
+ +
+ Eigen Value and Vector Calculator +
+
+

Eigen Value and Vector Calculator

+

+ Eigen Value and Vector Calculator +

+
+
+ +
+ +
+
+

Bhagvad Gita Quote Generator

+

+ Discover timeless wisdom with Bhagavad Gita quotes. +

+
+
+
- music visualizer + music visualizer

3D Music Visualizer

- Experience the most realistic 3D music visualizer. + Experience the most realistic 3D music visualizer.

-
+ + +
+ Othello Game +
+
+

Othello Game

+

+ Othello is a strategic board game where players aim to flip their opponent's discs to their color. +

+
+
+ +
+ recipebook +
+
+

Online Bookstore

+

+ For the love of reading-Anytime Anywhere +

+
+
+ + +
+ Eigen Value and Vector Calculator +
+
+

Eigen Value and Vector Calculator

+

+ Eigen Value and Vector Calculator +

+
+
+ + + + + + + + + + + - - -
- recipebook -
-
-

Recipe Book

-

- Gather Delicious Secret Recipes! -

-
-
- + + + + + + +
@@ -3265,67 +4651,68 @@

3D Music Visualizer

- - - -
- recipebook -
-
-

Recipe Book

-

- Gather Delicious Secret Recipes! -

-
-
- - - -
- Othello Game -
-
-

Othello Game

-

- Othello is a strategic board game where players aim to flip their opponent's discs to their color. -

-
-
- - -
- recipebook -
-
-

Online Bookstore

-

- For the love of reading-Anytime Anywhere -

-
-
+ + + + + + + + + + + + + + + + + +
+
+
+

Fund Tracking

+

+ This project helps visually represent government funding and tracking. +

+
+
+ +
+ Eigen Value and Vector Calculator +
+
+

Eigen Value and Vector Calculator

+

+ Eigen Value and Vector Calculator +

+
+
- -
- Eigen Value and Vector Calculator -
-
-

Eigen Value and Vector Calculator

-

- Eigen Value and Vector Calculator -

-
-
-
+
+
+

We Value Your Feedback!

@@ -3342,25 +4729,40 @@

We Value Your Feedback!

+ + +
+ + +
+ + + + + +
+

Rating: 0

-
- -
- - - - - -
-

Rating: 0

-
- -
- - -
- +
+ + +
+ + +
@@ -3438,6 +4840,7 @@

We Value Your Feedback!

}); }); + - +
@@ -3588,63 +4991,45 @@

Where can I find the source code?

- - -
- - - - - - - - - - +
- - + + + + + + + + + + + + + + - @@ -3769,8 +5117,8 @@

Follow Us

+ -visitor - - - - -main - main - - - - - - -