Code-Akisame

Tetris AI for a viewer

Apr 27th, 2019
506
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //Define 10x20 grid as the board
  2. var grid = [
  3. [0,0,0,0,0,0,0,0,0,0],
  4. [0,0,0,0,0,0,0,0,0,0],
  5. [0,0,0,0,0,0,0,0,0,0],
  6. [0,0,0,0,0,0,0,0,0,0],
  7. [0,0,0,0,0,0,0,0,0,0],
  8. [0,0,0,0,0,0,0,0,0,0],
  9. [0,0,0,0,0,0,0,0,0,0],
  10. [0,0,0,0,0,0,0,0,0,0],
  11. [0,0,0,0,0,0,0,0,0,0],
  12. [0,0,0,0,0,0,0,0,0,0],
  13. [0,0,0,0,0,0,0,0,0,0],
  14. [0,0,0,0,0,0,0,0,0,0],
  15. [0,0,0,0,0,0,0,0,0,0],
  16. [0,0,0,0,0,0,0,0,0,0],
  17. [0,0,0,0,0,0,0,0,0,0],
  18. [0,0,0,0,0,0,0,0,0,0],
  19. [0,0,0,0,0,0,0,0,0,0],
  20. [0,0,0,0,0,0,0,0,0,0],
  21. [0,0,0,0,0,0,0,0,0,0],
  22. [0,0,0,0,0,0,0,0,0,0],
  23. ];
  24.  
  25. //Block shapes
  26. var shapes = {
  27.     I: [[0,0,0,0], [1,1,1,1], [0,0,0,0], [0,0,0,0]],
  28.     J: [[2,0,0], [2,2,2], [0,0,0]],
  29.     L: [[0,0,3], [3,3,3], [0,0,0]],
  30.     O: [[4,4], [4,4]],
  31.     S: [[0,5,5], [5,5,0], [0,0,0]],
  32.     T: [[0,6,0], [6,6,6], [0,0,0]],
  33.     Z: [[7,7,0], [0,7,7], [0,0,0]]
  34. };
  35.  
  36. //Block colors
  37. var colors = ["F92338", "C973FF", "1C76BC", "FEE356", "53D504", "36E0FF", "F8931D"];
  38.  
  39. //Used to help create a seeded generated random number for choosing shapes. makes results deterministic (reproducible) for debugging
  40. var rndSeed = 1;
  41.  
  42. //BLOCK SHAPES
  43. //coordinates and shape parameter of current block we can update
  44. var currentShape = {x: 0, y: 0, shape: undefined};
  45. //store shape of upcoming block
  46. var upcomingShape;
  47. //stores shapes
  48. var bag = [];
  49. //index for shapes in the bag
  50. var bagIndex = 0;
  51.  
  52. //GAME VALUES
  53. //Game score
  54. var score = 0;
  55. // game speed
  56. var speed = 500;
  57. // boolean for changing game speed
  58. var changeSpeed = false;
  59. //for storing current state, we can load later
  60. var saveState;
  61. //stores current game state
  62. var roundState;
  63. //list of available game speeds
  64. var speeds = [500,100,1,0];
  65. //inded in game speed array
  66. var speedIndex = 0;
  67. //turn ai on or off
  68. var ai = true;
  69. //drawing game vs updating algorithms
  70. var draw = true;
  71. //how many so far?
  72. var movesTaken = 0;
  73. //max number of moves allowed in a generation
  74. var moveLimit = 500;
  75. //consists of move the 7 move parameters
  76. var moveAlgorithm = {};
  77. //set to highest rate move
  78. var inspectMoveSelection = false;
  79.  
  80.  
  81. //GENETIC ALGORITHM VALUES
  82. //stores number of genomes, init at 50
  83. var populationSize = 50;
  84. //stores genomes
  85. var genomes = [];
  86. //index of current genome in genomes array
  87. var currentGenome = -1;
  88. //generation number
  89. var generation = 0;
  90. //stores values for a generation
  91. var archive = {
  92.     populationSize: 0,
  93.     currentGeneration: 0,
  94.     elites: [],
  95.     genomes: []
  96. };
  97. //rate of mutation
  98. var mutationRate = 0.05;
  99. //helps calculate mutation
  100. var mutationStep = 0.2;
  101.  
  102.  
  103. //main function, called on load
  104. function initialize() {
  105.     //init pop size
  106.     archive.populationSize = populationSize;
  107.     //get the next available shape from the bag
  108.     nextShape();
  109.     //applies the shape to the grid
  110.     applyShape();
  111.     //set both save state and current state from the game
  112.     saveState = getState();
  113.     roundState = getState();
  114.     //create an initial population of genomes
  115.     createInitialPopulation();
  116.     //the game loop
  117.     var loop = function(){
  118.         //boolean for changing game speed
  119.         if (changeSpeed) {
  120.             //restart the clock
  121.             //stop time
  122.             clearInterval(interval);
  123.             //set time, like a digital watch
  124.             interval = setInterval(loop, speed);
  125.             //and don't change it
  126.             changeInterval = false;
  127.         }
  128.         if (speed === 0) {
  129.             //no need to draw on screen elements
  130.             draw = false;
  131.             //updates the game (update fitness, make a move, evaluate next move)
  132.             update();
  133.             update();
  134.             update();
  135.         } else {
  136.             //draw the elements
  137.             draw = true;
  138.         }
  139.         //update regardless
  140.         update();
  141.         if (speed === 0) {
  142.             //now draw elements
  143.             draw = true;
  144.             //now update the score
  145.             updateScore();
  146.         }
  147.     };
  148.     //timer interval
  149.     var interval = setInterval(loop, speed);
  150. }
  151. document.onLoad = initialize();
  152.  
  153.  
  154. //key options
  155. window.onkeydown = function (event) {
  156.  
  157.     var characterPressed = String.fromCharCode(event.keyCode);
  158.     if (event.keyCode == 38) {
  159.         rotateShape();
  160.     } else if (event.keyCode == 40) {
  161.         moveDown();
  162.     } else if (event.keyCode == 37) {
  163.         moveLeft();
  164.     } else if (event.keyCode == 39) {
  165.         moveRight();
  166.     } else if (shapes[characterPressed.toUpperCase()] !== undefined) {
  167.         removeShape();
  168.         currentShape.shape = shapes[characterPressed.toUpperCase()];
  169.         applyShape();
  170.     } else if (characterPressed.toUpperCase() == "Q") {
  171.         saveState = getState();
  172.     } else if (characterPressed.toUpperCase() == "W") {
  173.         loadState(saveState);
  174.     } else if (characterPressed.toUpperCase() == "D") {
  175.         //slow down
  176.         speedIndex--;
  177.         if (speedIndex < 0) {
  178.             speedIndex = speeds.length - 1;
  179.         }
  180.         speed = speeds[speedIndex];
  181.         changeSpeed = true;
  182.     } else if (characterPressed.toUpperCase() == "E") {
  183.         //speed up
  184.         speedIndex++;
  185.         if (speedIndex >= speeds.length) {
  186.             speedIndex = 0;
  187.         }
  188.         //adjust speed index
  189.         speed = speeds[speedIndex];
  190.         changeSpeed = true;
  191.         //Turn on/off AI
  192.     } else if (characterPressed.toUpperCase() == "A") {
  193.         ai = !ai;
  194.     } else if (characterPressed.toUpperCase() == "R") {
  195.         //load saved generation values
  196.         loadArchive(prompt("Insert archive:"));
  197.     } else if (characterPressed.toUpperCase() == "G") {
  198.         if (localStorage.getItem("archive") === null) {
  199.             alert("No archive saved. Archives are saved after a generation has passed, and remain across sessions. Try again once a generation has passed");
  200.         } else {
  201.             prompt("Archive from last generation (including from last session):", localStorage.getItem("archive"));
  202.         }
  203.     } else if (characterPressed.toUpperCase() == "F") {
  204.         //?
  205.         inspectMoveSelection = !inspectMoveSelection;
  206.     } else {
  207.         return true;
  208.     }
  209.     //outputs game state to the screen (post key press)
  210.     output();
  211.     return false;
  212. };
  213.  
  214. /**
  215.  * Creates the initial population of genomes, each with random genes.
  216.  */
  217.  function createInitialPopulation() {
  218.     //inits the array
  219.     genomes = [];
  220.     //for a given population size
  221.     for (var i = 0; i < populationSize; i++) {
  222.         //randomly initialize the 7 values that make up a genome
  223.         //these are all weight values that are updated through evolution
  224.         var genome = {
  225.             //unique identifier for a genome
  226.             id: Math.random(),
  227.             //The weight of each row cleared by the given move. the more rows that are cleared, the more this weight increases
  228.             rowsCleared: Math.random() - 0.5,
  229.             //the absolute height of the highest column to the power of 1.5
  230.             //added so that the algorithm can be able to detect if the blocks are stacking too high
  231.             weightedHeight: Math.random() - 0.5,
  232.             //The sum of all the column’s heights
  233.             cumulativeHeight: Math.random() - 0.5,
  234.             //the highest column minus the lowest column
  235.             relativeHeight: Math.random() - 0.5,
  236.             //the sum of all the empty cells that have a block above them (basically, cells that are unable to be filled)
  237.             holes: Math.random() * 0.5,
  238.             // the sum of absolute differences between the height of each column
  239.             //(for example, if all the shapes on the grid lie completely flat, then the roughness would equal 0).
  240.             roughness: Math.random() - 0.5,
  241.         };
  242.         //add them to the array
  243.         genomes.push(genome);
  244.     }
  245.     evaluateNextGenome();
  246.  }
  247.  
  248. /**
  249.  * Evaluates the next genome in the population. If there is none, evolves the population.
  250.  */
  251.  function evaluateNextGenome() {
  252.     //increment index in genome array
  253.     currentGenome++;
  254.     //If there is none, evolves the population.
  255.     if (currentGenome == genomes.length) {
  256.         evolve();
  257.     }
  258.     //load current gamestate
  259.     loadState(roundState);
  260.     //reset moves taken
  261.     movesTaken = 0;
  262.     //and make the next move
  263.     makeNextMove();
  264.  }
  265.  
  266. /**
  267.  * Evolves the entire population and goes to the next generation.
  268.  */
  269.  function evolve() {
  270.  
  271.     console.log("Generation " + generation + " evaluated.");
  272.     //reset current genome for new generation
  273.     currentGenome = 0;
  274.     //increment generation
  275.     generation++;
  276.     //resets the game
  277.     reset();
  278.     //gets the current game state
  279.     roundState = getState();
  280.     //sorts genomes in decreasing order of fitness values
  281.     genomes.sort(function(a, b) {
  282.         return b.fitness - a.fitness;
  283.     });
  284.     //add a copy of the fittest genome to the elites list
  285.     archive.elites.push(clone(genomes[0]));
  286.     console.log("Elite's fitness: " + genomes[0].fitness);
  287.  
  288.     //remove the tail end of genomes, focus on the fittest
  289.     while(genomes.length > populationSize / 2) {
  290.         genomes.pop();
  291.     }
  292.     //sum of the fitness for each genome
  293.     var totalFitness = 0;
  294.     for (var i = 0; i < genomes.length; i++) {
  295.         totalFitness += genomes[i].fitness;
  296.     }
  297.  
  298.     //get a random index from genome array
  299.     function getRandomGenome() {
  300.         return genomes[randomWeightedNumBetween(0, genomes.length - 1)];
  301.     }
  302.     //create children array
  303.     var children = [];
  304.     //add the fittest genome to array
  305.     children.push(clone(genomes[0]));
  306.     //add population sized amount of children
  307.     while (children.length < populationSize) {
  308.         //crossover between two random genomes to make a child
  309.         children.push(makeChild(getRandomGenome(), getRandomGenome()));
  310.     }
  311.     //create new genome array
  312.     genomes = [];
  313.     //to store all the children in
  314.     genomes = genomes.concat(children);
  315.     //store this in our archive
  316.     archive.genomes = clone(genomes);
  317.     //and set current gen
  318.     archive.currentGeneration = clone(generation);
  319.     console.log(JSON.stringify(archive));
  320.     //store archive, thanks JS localstorage! (short term memory)
  321.     localStorage.setItem("archive", JSON.stringify(archive));
  322. }
  323.  
  324. /**
  325.  * Creates a child genome from the given parent genomes, and then attempts to mutate the child genome.
  326.  * @param  {Genome} mum The first parent genome.
  327.  * @param  {Genome} dad The second parent genome.
  328.  * @return {Genome}     The child genome.
  329.  */
  330.  function makeChild(mum, dad) {
  331.     //init the child given two genomes (its 7 parameters + initial fitness value)
  332.     var child = {
  333.         //unique id
  334.         id : Math.random(),
  335.         //all these params are randomly selected between the mom and dad genome
  336.         rowsCleared: randomChoice(mum.rowsCleared, dad.rowsCleared),
  337.         weightedHeight: randomChoice(mum.weightedHeight, dad.weightedHeight),
  338.         cumulativeHeight: randomChoice(mum.cumulativeHeight, dad.cumulativeHeight),
  339.         relativeHeight: randomChoice(mum.relativeHeight, dad.relativeHeight),
  340.         holes: randomChoice(mum.holes, dad.holes),
  341.         roughness: randomChoice(mum.roughness, dad.roughness),
  342.         //no fitness. yet.
  343.         fitness: -1
  344.     };
  345.     //mutation time!
  346.  
  347.     //we mutate each parameter using our mutationstep
  348.     if (Math.random() < mutationRate) {
  349.         child.rowsCleared = child.rowsCleared + Math.random() * mutationStep * 2 - mutationStep;
  350.     }
  351.     if (Math.random() < mutationRate) {
  352.         child.weightedHeight = child.weightedHeight + Math.random() * mutationStep * 2 - mutationStep;
  353.     }
  354.     if (Math.random() < mutationRate) {
  355.         child.cumulativeHeight = child.cumulativeHeight + Math.random() * mutationStep * 2 - mutationStep;
  356.     }
  357.     if (Math.random() < mutationRate) {
  358.         child.relativeHeight = child.relativeHeight + Math.random() * mutationStep * 2 - mutationStep;
  359.     }
  360.     if (Math.random() < mutationRate) {
  361.         child.holes = child.holes + Math.random() * mutationStep * 2 - mutationStep;
  362.     }
  363.     if (Math.random() < mutationRate) {
  364.         child.roughness = child.roughness + Math.random() * mutationStep * 2 - mutationStep;
  365.     }
  366.     return child;
  367.  }
  368.  
  369. /**
  370.  * Returns an array of all the possible moves that could occur in the current state, rated by the parameters of the current genome.
  371.  * @return {Array} An array of all the possible moves that could occur.
  372.  */
  373.  function getAllPossibleMoves() {
  374.     var lastState = getState();
  375.     var possibleMoves = [];
  376.     var possibleMoveRatings = [];
  377.     var iterations = 0;
  378.     //for each possible rotation
  379.     for (var rots = 0; rots < 4; rots++) {
  380.  
  381.         var oldX = [];
  382.         //for each iteration
  383.         for (var t = -5; t <= 5; t++) {
  384.             iterations++;
  385.             loadState(lastState);
  386.             //rotate shape
  387.             for (var j = 0; j < rots; j++) {
  388.                 rotateShape();
  389.             }
  390.             //move left
  391.             if (t < 0) {
  392.                 for (var l = 0; l < Math.abs(t); l++) {
  393.                     moveLeft();
  394.                 }
  395.             //move right
  396.             } else if (t > 0) {
  397.                 for (var r = 0; r < t; r++) {
  398.                     moveRight();
  399.                 }
  400.             }
  401.             //if the shape has moved at all
  402.             if (!contains(oldX, currentShape.x)) {
  403.                 //move it down
  404.                 var moveDownResults = moveDown();
  405.                 while (moveDownResults.moved) {
  406.                     moveDownResults = moveDown();
  407.                 }
  408.                 //set the 7 parameters of a genome
  409.                 var algorithm = {
  410.                     rowsCleared: moveDownResults.rowsCleared,
  411.                     weightedHeight: Math.pow(getHeight(), 1.5),
  412.                     cumulativeHeight: getCumulativeHeight(),
  413.                     relativeHeight: getRelativeHeight(),
  414.                     holes: getHoles(),
  415.                     roughness: getRoughness()
  416.                 };
  417.                 //rate each move
  418.                 var rating = 0;
  419.                 rating += algorithm.rowsCleared * genomes[currentGenome].rowsCleared;
  420.                 rating += algorithm.weightedHeight * genomes[currentGenome].weightedHeight;
  421.                 rating += algorithm.cumulativeHeight * genomes[currentGenome].cumulativeHeight;
  422.                 rating += algorithm.relativeHeight * genomes[currentGenome].relativeHeight;
  423.                 rating += algorithm.holes * genomes[currentGenome].holes;
  424.                 rating += algorithm.roughness * genomes[currentGenome].roughness;
  425.                 //if the move loses the game, lower its rating
  426.                 if (moveDownResults.lose) {
  427.                     rating -= 500;
  428.                 }
  429.                 //push all possible moves, with their associated ratings and parameter values to an array
  430.                 possibleMoves.push({rotations: rots, translation: t, rating: rating, algorithm: algorithm});
  431.                 //update the position of old X value
  432.                 oldX.push(currentShape.x);
  433.             }
  434.         }
  435.     }
  436.     //get last state
  437.     loadState(lastState);
  438.     //return array of all possible moves
  439.     return possibleMoves;
  440.  }
  441.  
  442. /**
  443.  * Returns the highest rated move in the given array of moves.
  444.  * @param  {Array} moves An array of possible moves to choose from.
  445.  * @return {Move}       The highest rated move from the moveset.
  446.  */
  447.  function getHighestRatedMove(moves) {
  448.     //start these values off small
  449.     var maxRating = -10000000000000;
  450.     var maxMove = -1;
  451.     var ties = [];
  452.     //iterate through the list of moves
  453.     for (var index = 0; index < moves.length; index++) {
  454.         //if the current moves rating is higher than our maxrating
  455.         if (moves[index].rating > maxRating) {
  456.             //update our max values to include this moves values
  457.             maxRating = moves[index].rating;
  458.             maxMove = index;
  459.             //store index of this move
  460.             ties = [index];
  461.         } else if (moves[index].rating == maxRating) {
  462.             //if it ties with the max rating
  463.             //add the index to the ties array
  464.             ties.push(index);
  465.         }
  466.     }
  467.     //eventually we'll set the highest move value to this move var
  468.     var move = moves[ties[0]];
  469.     //and set the number of ties
  470.     move.algorithm.ties = ties.length;
  471.     return move;
  472. }
  473.  
  474. /**
  475.  * Makes a move, which is decided upon using the parameters in the current genome.
  476.  */
  477.  function makeNextMove() {
  478.     //increment number of moves taken
  479.     movesTaken++;
  480.     //if its over the limit of moves
  481.     if (movesTaken > moveLimit) {
  482.         //update this genomes fitness value using the game score
  483.         genomes[currentGenome].fitness = clone(score);
  484.         //and evaluates the next genome
  485.         evaluateNextGenome();
  486.     } else {
  487.         //time to make a move
  488.  
  489.         //we're going to re-draw, so lets store the old drawing
  490.         var oldDraw = clone(draw);
  491.         draw = false;
  492.         //get all the possible moves
  493.         var possibleMoves = getAllPossibleMoves();
  494.         //lets store the current state since we will update it
  495.         var lastState = getState();
  496.         //whats the next shape to play
  497.         nextShape();
  498.         //for each possible move
  499.         for (var i = 0; i < possibleMoves.length; i++) {
  500.             //get the best move. so were checking all the possible moves, for each possible move. moveception.
  501.             var nextMove = getHighestRatedMove(getAllPossibleMoves());
  502.             //add that rating to an array of highest rates moves
  503.             possibleMoves[i].rating += nextMove.rating;
  504.         }
  505.         //load current state
  506.         loadState(lastState);
  507.         //get the highest rated move ever
  508.         var move = getHighestRatedMove(possibleMoves);
  509.         //then rotate the shape as it says too
  510.         for (var rotations = 0; rotations < move.rotations; rotations++) {
  511.             rotateShape();
  512.         }
  513.         //and move left as it says
  514.         if (move.translation < 0) {
  515.             for (var lefts = 0; lefts < Math.abs(move.translation); lefts++) {
  516.                 moveLeft();
  517.             }
  518.             //and right as it says
  519.         } else if (move.translation > 0) {
  520.             for (var rights = 0; rights < move.translation; rights++) {
  521.                 moveRight();
  522.             }
  523.         }
  524.         //update our move algorithm
  525.         if (inspectMoveSelection) {
  526.             moveAlgorithm = move.algorithm;
  527.         }
  528.         //and set the old drawing to the current
  529.         draw = oldDraw;
  530.         //output the state to the screen
  531.         output();
  532.         //and update the score
  533.         updateScore();
  534.     }
  535.  }
  536.  
  537. /**
  538.  * Updates the game.
  539.  */
  540.  function update() {
  541.     //if we have our AI turned on and the current genome is nonzero
  542.     //make a move
  543.     if (ai && currentGenome != -1) {
  544.         //move the shape down
  545.         var results = moveDown();
  546.         //if that didn't do anything
  547.         if (!results.moved) {
  548.             //if we lost
  549.             if (results.lose) {
  550.                 //update the fitness
  551.                 genomes[currentGenome].fitness = clone(score);
  552.                 //move on to the next genome
  553.                 evaluateNextGenome();
  554.             } else {
  555.                 //if we didnt lose, make the next move
  556.                 makeNextMove();
  557.             }
  558.         }
  559.     } else {
  560.         //else just move down
  561.         moveDown();
  562.     }
  563.     //output the state to the screen
  564.     output();
  565.     //and update the score
  566.     updateScore();
  567.  }
  568.  
  569. /**
  570.  * Moves the current shape down if possible.
  571.  * @return {Object} The results of the movement of the piece.
  572.  */
  573.  function moveDown() {
  574.     //array of possibilities
  575.     var result = {lose: false, moved: true, rowsCleared: 0};
  576.     //remove the shape, because we will draw a new one
  577.     removeShape();
  578.     //move it down the y axis
  579.     currentShape.y++;
  580.     //if it collides with the grid
  581.     if (collides(grid, currentShape)) {
  582.         //update its position
  583.         currentShape.y--;
  584.         //apply (stick) it to the grid
  585.         applyShape();
  586.         //move on to the next shape in the bag
  587.         nextShape();
  588.         //clear rows and get number of rows cleared
  589.         result.rowsCleared = clearRows();
  590.         //check again if this shape collides with our grid
  591.         if (collides(grid, currentShape)) {
  592.             //reset
  593.             result.lose = true;
  594.             if (ai) {
  595.             } else {
  596.                 reset();
  597.             }
  598.         }
  599.         result.moved = false;
  600.     }
  601.     //apply shape, update the score and output the state to the screen
  602.     applyShape();
  603.     score++;
  604.     updateScore();
  605.     output();
  606.     return result;
  607.  }
  608.  
  609. /**
  610.  * Moves the current shape to the left if possible.
  611.  */
  612.  function moveLeft() {
  613.     //remove current shape, slide it over, if it collides though, slide it back
  614.     removeShape();
  615.     currentShape.x--;
  616.     if (collides(grid, currentShape)) {
  617.         currentShape.x++;
  618.     }
  619.     //apply the new shape
  620.     applyShape();
  621.  }
  622.  
  623. /**
  624.  * Moves the current shape to the right if possible.
  625.  */
  626.  //same deal
  627.  function moveRight() {
  628.     removeShape();
  629.     currentShape.x++;
  630.     if (collides(grid, currentShape)) {
  631.         currentShape.x--;
  632.     }
  633.     applyShape();
  634.  }
  635.  
  636. /**
  637.  * Rotates the current shape clockwise if possible.
  638.  */
  639.  //slide it if we can, else return to original rotation
  640.  function rotateShape() {
  641.     removeShape();
  642.     currentShape.shape = rotate(currentShape.shape, 1);
  643.     if (collides(grid, currentShape)) {
  644.         currentShape.shape = rotate(currentShape.shape, 3);
  645.     }
  646.     applyShape();
  647.  }
  648.  
  649. /**
  650.  * Clears any rows that are completely filled.
  651.  */
  652.  function clearRows() {
  653.     //empty array for rows to clear
  654.     var rowsToClear = [];
  655.     //for each row in the grid
  656.     for (var row = 0; row < grid.length; row++) {
  657.         var containsEmptySpace = false;
  658.         //for each column
  659.         for (var col = 0; col < grid[row].length; col++) {
  660.             //if its empty
  661.             if (grid[row][col] === 0) {
  662.                 //set this value to true
  663.                 containsEmptySpace = true;
  664.             }
  665.         }
  666.         //if none of the columns in the row were empty
  667.         if (!containsEmptySpace) {
  668.             //add the row to our list, it's completely filled!
  669.             rowsToClear.push(row);
  670.         }
  671.     }
  672.     //increase score for up to 4 rows. it maxes out at 12000
  673.     if (rowsToClear.length == 1) {
  674.         score += 400;
  675.     } else if (rowsToClear.length == 2) {
  676.         score += 1000;
  677.     } else if (rowsToClear.length == 3) {
  678.         score += 3000;
  679.     } else if (rowsToClear.length >= 4) {
  680.         score += 12000;
  681.     }
  682.     //new array for cleared rows
  683.     var rowsCleared = clone(rowsToClear.length);
  684.     //for each value
  685.     for (var toClear = rowsToClear.length - 1; toClear >= 0; toClear--) {
  686.         //remove the row from the grid
  687.         grid.splice(rowsToClear[toClear], 1);
  688.     }
  689.     //shift the other rows
  690.     while (grid.length < 20) {
  691.         grid.unshift([0,0,0,0,0,0,0,0,0,0]);
  692.     }
  693.     //return the rows cleared
  694.     return rowsCleared;
  695.  }
  696.  
  697. /**
  698.  * Applies the current shape to the grid.
  699.  */
  700.  function applyShape() {
  701.     //for each value in the current shape (row x column)
  702.     for (var row = 0; row < currentShape.shape.length; row++) {
  703.         for (var col = 0; col < currentShape.shape[row].length; col++) {
  704.             //if its non-empty
  705.             if (currentShape.shape[row][col] !== 0) {
  706.                 //set the value in the grid to its value. Stick the shape in the grid!
  707.                 grid[currentShape.y + row][currentShape.x + col] = currentShape.shape[row][col];
  708.             }
  709.         }
  710.     }
  711.  }
  712.  
  713. /**
  714.  * Removes the current shape from the grid.
  715.  */
  716.  //same deal but reverse
  717.  function removeShape() {
  718.     for (var row = 0; row < currentShape.shape.length; row++) {
  719.         for (var col = 0; col < currentShape.shape[row].length; col++) {
  720.             if (currentShape.shape[row][col] !== 0) {
  721.                 grid[currentShape.y + row][currentShape.x + col] = 0;
  722.             }
  723.         }
  724.     }
  725.  }
  726.  
  727. /**
  728.  * Cycles to the next shape in the bag.
  729.  */
  730.  function nextShape() {
  731.     //increment the bag index
  732.     bagIndex += 1;
  733.     //if we're at the start or end of the bag
  734.     if (bag.length === 0 || bagIndex == bag.length) {
  735.         //generate a new bag of genomes
  736.         generateBag();
  737.     }
  738.     //if almost at end of bag
  739.     if (bagIndex == bag.length - 1) {
  740.         //store previous seed
  741.         var prevSeed = rndSeed;
  742.         //generate upcoming shape
  743.         upcomingShape = randomProperty(shapes);
  744.         //set random seed
  745.         rndSeed = prevSeed;
  746.     } else {
  747.         //get the next shape from our bag
  748.         upcomingShape = shapes[bag[bagIndex + 1]];
  749.     }
  750.     //get our current shape from the bag
  751.     currentShape.shape = shapes[bag[bagIndex]];
  752.     //define its position
  753.     currentShape.x = Math.floor(grid[0].length / 2) - Math.ceil(currentShape.shape[0].length / 2);
  754.     currentShape.y = 0;
  755.  }
  756.  
  757. /**
  758.  * Generates the bag of shapes.
  759.  */
  760.  function generateBag() {
  761.     bag = [];
  762.     var contents = "";
  763.     //7 shapes
  764.     for (var i = 0; i < 7; i++) {
  765.         //generate shape randomly
  766.         var shape = randomKey(shapes);
  767.         while(contents.indexOf(shape) != -1) {
  768.             shape = randomKey(shapes);
  769.         }
  770.         //update bag with generated shape
  771.         bag[i] = shape;
  772.         contents += shape;
  773.     }
  774.     //reset bag index
  775.     bagIndex = 0;
  776.  }
  777.  
  778. /**
  779.  * Resets the game.
  780.  */
  781.  function reset() {
  782.     score = 0;
  783.     grid = [[0,0,0,0,0,0,0,0,0,0],
  784.     [0,0,0,0,0,0,0,0,0,0],
  785.     [0,0,0,0,0,0,0,0,0,0],
  786.     [0,0,0,0,0,0,0,0,0,0],
  787.     [0,0,0,0,0,0,0,0,0,0],
  788.     [0,0,0,0,0,0,0,0,0,0],
  789.     [0,0,0,0,0,0,0,0,0,0],
  790.     [0,0,0,0,0,0,0,0,0,0],
  791.     [0,0,0,0,0,0,0,0,0,0],
  792.     [0,0,0,0,0,0,0,0,0,0],
  793.     [0,0,0,0,0,0,0,0,0,0],
  794.     [0,0,0,0,0,0,0,0,0,0],
  795.     [0,0,0,0,0,0,0,0,0,0],
  796.     [0,0,0,0,0,0,0,0,0,0],
  797.     [0,0,0,0,0,0,0,0,0,0],
  798.     [0,0,0,0,0,0,0,0,0,0],
  799.     [0,0,0,0,0,0,0,0,0,0],
  800.     [0,0,0,0,0,0,0,0,0,0],
  801.     [0,0,0,0,0,0,0,0,0,0],
  802.     [0,0,0,0,0,0,0,0,0,0],
  803.     ];
  804.     moves = 0;
  805.     generateBag();
  806.     nextShape();
  807.  }
  808.  
  809. /**
  810.  * Determines if the given grid and shape collide with one another.
  811.  * @param  {Grid} scene  The grid to check.
  812.  * @param  {Shape} object The shape to check.
  813.  * @return {Boolean} Whether the shape and grid collide.
  814.  */
  815.  function collides(scene, object) {
  816.     //for the size of the shape (row x column)
  817.     for (var row = 0; row < object.shape.length; row++) {
  818.         for (var col = 0; col < object.shape[row].length; col++) {
  819.             //if its not empty
  820.             if (object.shape[row][col] !== 0) {
  821.                 //if it collides, return true
  822.                 if (scene[object.y + row] === undefined || scene[object.y + row][object.x + col] === undefined || scene[object.y + row][object.x + col] !== 0) {
  823.                     return true;
  824.                 }
  825.             }
  826.         }
  827.     }
  828.     return false;
  829.  }
  830.  
  831. //for rotating a shape, how many times should we rotate
  832.  function rotate(matrix, times) {
  833.     //for each time
  834.     for (var t = 0; t < times; t++) {
  835.         //flip the shape matrix
  836.         matrix = transpose(matrix);
  837.         //and for the length of the matrix, reverse each column
  838.         for (var i = 0; i < matrix.length; i++) {
  839.             matrix[i].reverse();
  840.         }
  841.     }
  842.     return matrix;
  843.  }
  844. //flip row x column to column x row
  845.  function transpose(array) {
  846.     return array[0].map(function(col, i) {
  847.         return array.map(function(row) {
  848.             return row[i];
  849.         });
  850.     });
  851.  }
  852.  
  853. /**
  854.  * Outputs the state to the screen.
  855.  */
  856.  function output() {
  857.     if (draw) {
  858.         var output = document.getElementById("output");
  859.         var html = "<h1>TetNet</h1><h5>Evolutionary approach to Tetris AI</h5>var grid = [";
  860.         var space = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  861.         for (var i = 0; i < grid.length; i++) {
  862.             if (i === 0) {
  863.                 html += "[" + grid[i] + "]";
  864.             } else {
  865.                 html += "<br />" + space + "[" + grid[i] + "]";
  866.             }
  867.         }
  868.         html += "];";
  869.         for (var c = 0; c < colors.length; c++) {
  870.             html = replaceAll(html, "," + (c + 1), ",<font color=\"" + colors[c] + "\">" + (c + 1) + "</font>");
  871.             html = replaceAll(html, (c + 1) + ",", "<font color=\"" + colors[c] + "\">" + (c + 1) + "</font>,");
  872.         }
  873.         output.innerHTML = html;
  874.     }
  875.  }
  876.  
  877. /**
  878.  * Updates the side information.
  879.  */
  880.  function updateScore() {
  881.     if (draw) {
  882.         var scoreDetails = document.getElementById("score");
  883.         var html = "<br /><br /><h2>&nbsp;</h2><h2>Score: " + score + "</h2>";
  884.         html += "<br /><b>--Upcoming--</b>";
  885.         for (var i = 0; i < upcomingShape.length; i++) {
  886.             var next =replaceAll((upcomingShape[i] + ""), "0", "&nbsp;");
  887.             html += "<br />&nbsp;&nbsp;&nbsp;&nbsp;" + next;
  888.         }
  889.         for (var l = 0; l < 4 - upcomingShape.length; l++) {
  890.             html += "<br />";
  891.         }
  892.         for (var c = 0; c < colors.length; c++) {
  893.             html = replaceAll(html, "," + (c + 1), ",<font color=\"" + colors[c] + "\">" + (c + 1) + "</font>");
  894.             html = replaceAll(html, (c + 1) + ",", "<font color=\"" + colors[c] + "\">" + (c + 1) + "</font>,");
  895.         }
  896.         html += "<br />Speed: " + speed;
  897.         if (ai) {
  898.             html += "<br />Moves: " + movesTaken + "/" + moveLimit;
  899.             html += "<br />Generation: " + generation;
  900.             html += "<br />Individual: " + (currentGenome + 1)  + "/" + populationSize;
  901.             html += "<br /><pre style=\"font-size:12px\">" + JSON.stringify(genomes[currentGenome], null, 2) + "</pre>";
  902.             if (inspectMoveSelection) {
  903.                 html += "<br /><pre style=\"font-size:12px\">" + JSON.stringify(moveAlgorithm, null, 2) + "</pre>";
  904.             }
  905.         }
  906.         html = replaceAll(replaceAll(replaceAll(html, "&nbsp;,", "&nbsp;&nbsp;"), ",&nbsp;", "&nbsp;&nbsp;"), ",", "&nbsp;");
  907.         scoreDetails.innerHTML = html;
  908.     }
  909.  }
  910.  
  911. /**
  912.  * Returns the current game state in an object.
  913.  * @return {State} The current game state.
  914.  */
  915.  function getState() {
  916.     var state = {
  917.         grid: clone(grid),
  918.         currentShape: clone(currentShape),
  919.         upcomingShape: clone(upcomingShape),
  920.         bag: clone(bag),
  921.         bagIndex: clone(bagIndex),
  922.         rndSeed: clone(rndSeed),
  923.         score: clone(score)
  924.     };
  925.     return state;
  926.  }
  927.  
  928. /**
  929.  * Loads the game state from the given state object.
  930.  * @param  {State} state The state to load.
  931.  */
  932.  function loadState(state) {
  933.     grid = clone(state.grid);
  934.     currentShape = clone(state.currentShape);
  935.     upcomingShape = clone(state.upcomingShape);
  936.     bag = clone(state.bag);
  937.     bagIndex = clone(state.bagIndex);
  938.     rndSeed = clone(state.rndSeed);
  939.     score = clone(state.score);
  940.     output();
  941.     updateScore();
  942.  }
  943.  
  944. /**
  945.  * Returns the cumulative height of all the columns.
  946.  * @return {Number} The cumulative height.
  947.  */
  948.  function getCumulativeHeight() {
  949.     removeShape();
  950.     var peaks = [20,20,20,20,20,20,20,20,20,20];
  951.     for (var row = 0; row < grid.length; row++) {
  952.         for (var col = 0; col < grid[row].length; col++) {
  953.             if (grid[row][col] !== 0 && peaks[col] === 20) {
  954.                 peaks[col] = row;
  955.             }
  956.         }
  957.     }
  958.     var totalHeight = 0;
  959.     for (var i = 0; i < peaks.length; i++) {
  960.         totalHeight += 20 - peaks[i];
  961.     }
  962.     applyShape();
  963.     return totalHeight;
  964.  }
  965.  
  966. /**
  967.  * Returns the number of holes in the grid.
  968.  * @return {Number} The number of holes.
  969.  */
  970.  function getHoles() {
  971.     removeShape();
  972.     var peaks = [20,20,20,20,20,20,20,20,20,20];
  973.     for (var row = 0; row < grid.length; row++) {
  974.         for (var col = 0; col < grid[row].length; col++) {
  975.             if (grid[row][col] !== 0 && peaks[col] === 20) {
  976.                 peaks[col] = row;
  977.             }
  978.         }
  979.     }
  980.     var holes = 0;
  981.     for (var x = 0; x < peaks.length; x++) {
  982.         for (var y = peaks[x]; y < grid.length; y++) {
  983.             if (grid[y][x] === 0) {
  984.                 holes++;
  985.             }
  986.         }
  987.     }
  988.     applyShape();
  989.     return holes;
  990.  }
  991.  
  992. /**
  993.  * Returns an array that replaces all the holes in the grid with -1.
  994.  * @return {Array} The modified grid array.
  995.  */
  996.  function getHolesArray() {
  997.     var array = clone(grid);
  998.     removeShape();
  999.     var peaks = [20,20,20,20,20,20,20,20,20,20];
  1000.     for (var row = 0; row < grid.length; row++) {
  1001.         for (var col = 0; col < grid[row].length; col++) {
  1002.             if (grid[row][col] !== 0 && peaks[col] === 20) {
  1003.                 peaks[col] = row;
  1004.             }
  1005.         }
  1006.     }
  1007.     for (var x = 0; x < peaks.length; x++) {
  1008.         for (var y = peaks[x]; y < grid.length; y++) {
  1009.             if (grid[y][x] === 0) {
  1010.                 array[y][x] = -1;
  1011.             }
  1012.         }
  1013.     }
  1014.     applyShape();
  1015.     return array;
  1016.  }
  1017.  
  1018. /**
  1019.  * Returns the roughness of the grid.
  1020.  * @return {Number} The roughness of the grid.
  1021.  */
  1022.  function getRoughness() {
  1023.     removeShape();
  1024.     var peaks = [20,20,20,20,20,20,20,20,20,20];
  1025.     for (var row = 0; row < grid.length; row++) {
  1026.         for (var col = 0; col < grid[row].length; col++) {
  1027.             if (grid[row][col] !== 0 && peaks[col] === 20) {
  1028.                 peaks[col] = row;
  1029.             }
  1030.         }
  1031.     }
  1032.     var roughness = 0;
  1033.     var differences = [];
  1034.     for (var i = 0; i < peaks.length - 1; i++) {
  1035.         roughness += Math.abs(peaks[i] - peaks[i + 1]);
  1036.         differences[i] = Math.abs(peaks[i] - peaks[i + 1]);
  1037.     }
  1038.     applyShape();
  1039.     return roughness;
  1040.  }
  1041.  
  1042. /**
  1043.  * Returns the range of heights of the columns on the grid.
  1044.  * @return {Number} The relative height.
  1045.  */
  1046.  function getRelativeHeight() {
  1047.     removeShape();
  1048.     var peaks = [20,20,20,20,20,20,20,20,20,20];
  1049.     for (var row = 0; row < grid.length; row++) {
  1050.         for (var col = 0; col < grid[row].length; col++) {
  1051.             if (grid[row][col] !== 0 && peaks[col] === 20) {
  1052.                 peaks[col] = row;
  1053.             }
  1054.         }
  1055.     }
  1056.     applyShape();
  1057.     return Math.max.apply(Math, peaks) - Math.min.apply(Math, peaks);
  1058.  }
  1059.  
  1060. /**
  1061.  * Returns the height of the biggest column on the grid.
  1062.  * @return {Number} The absolute height.
  1063.  */
  1064.  function getHeight() {
  1065.     removeShape();
  1066.     var peaks = [20,20,20,20,20,20,20,20,20,20];
  1067.     for (var row = 0; row < grid.length; row++) {
  1068.         for (var col = 0; col < grid[row].length; col++) {
  1069.             if (grid[row][col] !== 0 && peaks[col] === 20) {
  1070.                 peaks[col] = row;
  1071.             }
  1072.         }
  1073.     }
  1074.     applyShape();
  1075.     return 20 - Math.min.apply(Math, peaks);
  1076.  }
  1077.  
  1078. /**
  1079.  * Loads the archive given.
  1080.  * @param  {String} archiveString The stringified archive.
  1081.  */
  1082.  function loadArchive(archiveString) {
  1083.     archive = JSON.parse(archiveString);
  1084.     genomes = clone(archive.genomes);
  1085.     populationSize = archive.populationSize;
  1086.     generation = archive.currentGeneration;
  1087.     currentGenome = 0;
  1088.     reset();
  1089.     roundState = getState();
  1090.     console.log("Archive loaded!");
  1091.  }
  1092.  
  1093. /**
  1094.  * Clones an object.
  1095.  * @param  {Object} obj The object to clone.
  1096.  * @return {Object}     The cloned object.
  1097.  */
  1098.  function clone(obj) {
  1099.     return JSON.parse(JSON.stringify(obj));
  1100.  }
  1101.  
  1102. /**
  1103.  * Returns a random property from the given object.
  1104.  * @param  {Object} obj The object to select a property from.
  1105.  * @return {Property}     A random property.
  1106.  */
  1107.  function randomProperty(obj) {
  1108.     return(obj[randomKey(obj)]);
  1109.  }
  1110.  
  1111. /**
  1112.  * Returns a random property key from the given object.
  1113.  * @param  {Object} obj The object to select a property key from.
  1114.  * @return {Property}     A random property key.
  1115.  */
  1116.  function randomKey(obj) {
  1117.     var keys = Object.keys(obj);
  1118.     var i = seededRandom(0, keys.length);
  1119.     return keys[i];
  1120.  }
  1121.  
  1122.  function replaceAll(target, search, replacement) {
  1123.     return target.replace(new RegExp(search, 'g'), replacement);
  1124.  }
  1125.  
  1126. /**
  1127.  * Returns a random number that is determined from a seeded random number generator.
  1128.  * @param  {Number} min The minimum number, inclusive.
  1129.  * @param  {Number} max The maximum number, exclusive.
  1130.  * @return {Number}     The generated random number.
  1131.  */
  1132.  function seededRandom(min, max) {
  1133.     max = max || 1;
  1134.     min = min || 0;
  1135.  
  1136.     rndSeed = (rndSeed * 9301 + 49297) % 233280;
  1137.     var rnd = rndSeed / 233280;
  1138.  
  1139.     return Math.floor(min + rnd * (max - min));
  1140.  }
  1141.  
  1142.  function randomNumBetween(min, max) {
  1143.     return Math.floor(Math.random() * (max - min + 1) + min);
  1144.  }
  1145.  
  1146.  function randomWeightedNumBetween(min, max) {
  1147.     return Math.floor(Math.pow(Math.random(), 2) * (max - min + 1) + min);
  1148.  }
  1149.  
  1150.  function randomChoice(propOne, propTwo) {
  1151.     if (Math.round(Math.random()) === 0) {
  1152.         return clone(propOne);
  1153.     } else {
  1154.         return clone(propTwo);
  1155.     }
  1156.  }
  1157.  
  1158.  function contains(a, obj) {
  1159.     var i = a.length;
  1160.     while (i--) {
  1161.         if (a[i] === obj) {
  1162.             return true;
  1163.         }
  1164.     }
  1165.     return false;
  1166.  }
Advertisement
Comments
  • User was banned
Add Comment
Please, Sign In to add comment