Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //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 = "<h1>TetNet</h1><h5>Evolutionary approach to Tetris AI</h5>var grid = [";
- var space = " ";
- for (var i = 0; i < grid.length; i++) {
- if (i === 0) {
- html += "[" + grid[i] + "]";
- } else {
- html += "<br />" + space + "[" + grid[i] + "]";
- }
- }
- html += "];";
- for (var c = 0; c < colors.length; c++) {
- html = replaceAll(html, "," + (c + 1), ",<font color=\"" + colors[c] + "\">" + (c + 1) + "</font>");
- html = replaceAll(html, (c + 1) + ",", "<font color=\"" + colors[c] + "\">" + (c + 1) + "</font>,");
- }
- output.innerHTML = html;
- }
- }
- /**
- * Updates the side information.
- */
- function updateScore() {
- if (draw) {
- var scoreDetails = document.getElementById("score");
- var html = "<br /><br /><h2> </h2><h2>Score: " + score + "</h2>";
- html += "<br /><b>--Upcoming--</b>";
- for (var i = 0; i < upcomingShape.length; i++) {
- var next =replaceAll((upcomingShape[i] + ""), "0", " ");
- html += "<br /> " + next;
- }
- for (var l = 0; l < 4 - upcomingShape.length; l++) {
- html += "<br />";
- }
- for (var c = 0; c < colors.length; c++) {
- html = replaceAll(html, "," + (c + 1), ",<font color=\"" + colors[c] + "\">" + (c + 1) + "</font>");
- html = replaceAll(html, (c + 1) + ",", "<font color=\"" + colors[c] + "\">" + (c + 1) + "</font>,");
- }
- html += "<br />Speed: " + speed;
- if (ai) {
- html += "<br />Moves: " + movesTaken + "/" + moveLimit;
- html += "<br />Generation: " + generation;
- html += "<br />Individual: " + (currentGenome + 1) + "/" + populationSize;
- html += "<br /><pre style=\"font-size:12px\">" + JSON.stringify(genomes[currentGenome], null, 2) + "</pre>";
- if (inspectMoveSelection) {
- html += "<br /><pre style=\"font-size:12px\">" + JSON.stringify(moveAlgorithm, null, 2) + "</pre>";
- }
- }
- 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;
- }
Advertisement