Same controls as regular minesweeper. You can use the "options" button to change the size of the board and the number of mines. The "step" option will display the probability of each tile being a mine and can make moves one at a time. The "fast" option allows the AI to play as fast as possible.
xxxxxxxxxx
// Minesweeper AI by Ben Liu
// June 20, 2020
// Number of tiles in the game
let columns;
let rows;
// Number of mines in the game
let numOfMines;
// Regular minesweeper rules:
// Easy: 9x9, 10 mines
// Medium: 16x16, 40 mines
// Expert: 30x16, 99 mines
// Calculates and sets the size of the window
let sizeX;
let sizeY;
// Global variables
let totalTiles;
let timer;
let foundIn;
let flagsLeft;
let tilesLeft;
let aiMode;
let firstClick;
let gameOver;
let win;
let aiChanged;
let inOption;
let restarted;
let tempInfo;
let editing;
let timeStart;
let timeEnd;
// 2D array of all tile objects
let tiles;
let ai;
function setup() {
textAlign(CENTER, CENTER);
columns = 30;
rows = 16;
numOfMines = 99;
sizeX = columns * 40 + 201;
sizeY = rows * 40 + 101;
totalTiles = columns * rows;
timer = 0;
foundIn = 0;
flagsLeft = numOfMines;
tilesLeft = 0;
aiMode = 0;
firstClick = true;
gameOver = false;
win = false;
aiChanged = true;
inOption = false;
restarted = true;
tempInfo = [String(columns), String(rows), String(numOfMines)];
editing = -1;
timeStart = 0;
timeEnd = 0;
tiles = [Array(columns)].map(e => Array(rows));
// Create all the tiles
createTiles();
ai = new AI();
createCanvas(sizeX, sizeY);
window.addEventListener('contextmenu', function (e) {
e.preventDefault();
}, false);
frameRate(20);
}
// function settings() {
// createCanvas(sizeX, sizeY);
// }
// This is the "main" method - will loop as long as the program is running
function draw() {
noStroke();
fill(205);
rect(0, 0, sizeX, 100);
rect(sizeX - 200, 100, 200, sizeY - 100);
stroke(0);
strokeWeight(4);
line(0, 98, width, 98);
line(width - 198, 100, width - 198, height);
// Display the icons
if(!inOption){
drawFlag(0, 20);
drawClock(100, 40);
drawRestart(width - 150, 40);
// Display the text for the timer and the flags left
fill(0);
noStroke();
textSize(20);
// Processing runs at 60 fps on default settings
// Therefore, each 60 iterations of the void_draw is one second
text(floor(timer/20), 150, 40);
text(flagsLeft, 50, 40);
}
else{
drawOptions();
}
drawOptionButton(width - 60, 40);
drawAIButton();
// Displays all the tiles
let total = 0;
let tile;
for (let i = 0; i < columns; i++) {
for (let j = 0; j < rows; j++) {
tile = tiles[i][j];
// Display methods
tile.displayTile();
tile.displayFlag();
tile.displayNum();
if (aiMode == 1) {
tile.displayProbability();
}
if (tile.getRevealed()) {
// If the player reveals a tile with a mine, then it's game over
if (tile.getMine()) {
gameOver = true;
}
// If the player reveals a tile with no mines around it,
// reveal all the adjacent tiles automatically
if (tile.getTileNum() == 0 && !tile.getMine() && !tile.getZeroRevealed()) {
revealEmptyTiles(i, j);
tile.setZeroRevealed(true);
}
// Each revealed tile adds the accumulator variable "total"
total++;
}
// When it's game over, then
// 1. Show all the mines
// 2. Display a X if the player flagged a tile without a mine (incorrect flagging)
// 3. Show which mine they revealed with an X
if (gameOver) {
tile.displayAllMines();
if (tile.getFlag()&& !tile.getMine()) {
tile.displayCross();
}
if (tile.getRevealed()&& tile.getMine()) {
tile.displayCross();
}
}
}
}
// This determines whether all the tiles without a mine have been revealed
// (this means the player won)
tilesLeft = totalTiles - total - numOfMines;
if (tilesLeft == 0 && !gameOver) {
win = true;
}
if (aiMode == 1 && aiChanged && !gameOver && !win) {
timeStart = millis();
// Display tile probability in "step" mode
ai.calculateProbability();
timeEnd = millis();
foundIn = timeEnd - timeStart;
aiChanged = false;
} else if (aiMode == 2) {
// Run the probability calculations in "fast" mode
if (firstClick) {
let c = floor(random(0, columns));
let r = floor(random(0, rows));
revealEvent(c, r);
}
else{
ai.calculateProbability();
let probability = 100;
let minimum = 100;
let revealedTile = false;
tile = tiles[0][0];
for (let i = 0; i < columns; i++) {
for (let j = 0; j < rows; j++) {
if( !tiles[i][j].getRevealed() && !tiles[i][j].getFlag() && !gameOver && !win){
probability = tiles[i][j].getProbability();
// Flag a tile if it 100% has a mine
if (probability >= 99) {
flagEvent(i, j);
revealedTile = true;
// Reveal a tile if it 0% has a mine
} else if (probability <= 1 && !tiles[i][j].getFlag()) {
revealEvent(i, j);
revealedTile = true;
// Else find the tile with the lowest probabilty and reveal it
} else if (probability < minimum) {
minimum = probability;
tile = tiles[i][j];
}
}
}
}
if (!revealedTile && !gameOver && !win) {
tile.reveal();
}
}
}
// Display how much time it took to find the probabilities
if(aiMode == 1){
fill(0);
textSize(15);
text("Probabilities found in:", width - 100, height - 60);
textSize(20);
text(floor(foundIn) + " milliseconds", width - 100, height - 30);
}
// Display the win message
if (win && !inOption) {
fill(0, 200, 0);
textSize(30);
text("You win!", width - 300, 40);
if (restarted) {
restarted = false;
if(aiMode == 2){
timeEnd = millis();
}
}
if(aiMode == 2){
// Display how much time it took to finish the board
fill(0);
textSize(20);
text("Done in:", width - 100, height - 60);
text(floor(timeEnd - timeStart) + " milliseconds", width - 100, height - 30);
}
// Display the lose message
} else if (gameOver && !inOption) {
textSize(30);
fill(255, 0, 0);
text("Game over", width - 300, 40);
} else {
// Only run the timer if the game is active
timer++;
}
}
function mousePressed() {
aiChanged = true;
if(inOption && mouseY >= 15 && mouseY <= 65){
if(mouseX >= 90 && mouseX <= 140){
editing = 0;
}
else if(mouseX >= 230 && mouseX <= 280){
editing = 1;
}
else if(mouseX >= 370 && mouseX <= 420){
editing = 2;
}
}
// Restart if the player clicks the restart button
if (mouseX >= width - 175 && mouseX <= width - 125 && mouseY >= 15 && mouseY <= 65 && !inOption) {
restart();
}
else if(mouseX >= width - 110 && mouseX <= width - 10 && mouseY >= 15 && mouseY <= 65){
//Set the new tiles and mines
if(inOption){
inOption = false;
columns = parseInt(tempInfo[0]);
rows = parseInt(tempInfo[1]);
if(columns < 9){
columns = 9;
}
if(rows < 9){
rows = 9;
}
numOfMines = parseInt(tempInfo[2]);
sizeX = columns * 40 + 201;
sizeY = rows * 40 + 101;
restart();
resizeCanvas(sizeX, sizeY);
}
else{
inOption = true;
editing = -1;
}
}
// Switch modes
else if (mouseX >= width - 150 && mouseX <= width - 50 && mouseY >= 175 && mouseY <= 225) {
aiMode = 0;
} else if (mouseY >= 250 && mouseY <= 300 && mouseX >= width - 200) {
if(mouseX >= width - 150 && mouseX <= width - 50 && aiMode != 1){
aiMode = 1;
return;
}
if(mouseX >= width - 90 && mouseX <= width - 10 && aiMode == 1 && !gameOver && !win){
// Reveal or flag a tile whenever the button is pressed in "step" mode
let c = 0;
let r = 0;
if (firstClick) {
c = floor(random(0, columns));
r = floor(random(0, rows));
revealEvent(c, r);
} else {
let probability;
let minimum = 100;
for (let i = 0; i < columns; i++) {
for (let j = 0; j < rows; j++) {
probability = tiles[i][j].getProbability();
if (probability == 100) {
flagEvent(i, j);
return;
} else if (probability < minimum && !tiles[i][j].getRevealed() && !tiles[i][j].getFlag()) {
minimum = probability;
c = i;
r = j;
}
}
}
revealEvent(c, r);
}
}
} else if (mouseX >= width - 150 && mouseX <= width - 50 && mouseY >= 325 && mouseY <= 375) {
if(aiMode != 2){
aiMode = 2;
timeStart = millis();
}
} else if (mouseY >= 100 && mouseY <= sizeY && mouseX < sizeX - 200) {
// Calculates the tile's column and row from the mouse's position (in pixels)
let column = floor(mouseX / 40);
let row = floor((mouseY - 100) / 40);
if (!gameOver&& !win) {
// Reveals a tile if the player left-clicks on a tile
if (mouseButton == LEFT) {
if (!tiles[column][row].getFlag() && !tiles[column][row].getRevealed()) {
revealEvent(column, row);
}
}
// Flags/Unflags a tile if the player right-clicks on a tile
else if (mouseButton == RIGHT) {
if (!tiles[column][row].getRevealed()) {
flagEvent(column, row);
}
}
}
}
}
function keyPressed(){
//Keyboard input in the options menu
if(inOption){
switch(key){
case '1':
tempInfo[editing] += "1";
break;
case '2':
tempInfo[editing] += "2";
break;
case '3':
tempInfo[editing] += "3";
break;
case '4':
tempInfo[editing] += "4";
break;
case '5':
tempInfo[editing] += "5";
break;
case '6':
tempInfo[editing] += "6";
break;
case '7':
tempInfo[editing] += "7";
break;
case '8':
tempInfo[editing] += "8";
break;
case '9':
tempInfo[editing] += "9";
break;
case '0':
tempInfo[editing] += "0";
break;
}
if(keyCode == BACKSPACE){
if(tempInfo[editing].length > 0){
tempInfo[editing] = tempInfo[editing].substring(0, tempInfo[editing].length - 1);
}
}
if(tempInfo[editing].length == 0){
tempInfo[editing] = "0";
}
else if(tempInfo[editing].charAt(0) == '0'){
tempInfo[editing] = tempInfo[editing].substring(1, tempInfo[editing].length);
}
let maximum;
if(editing == 0){
maximum = 40;
if(parseInt(tempInfo[editing]) > maximum){
tempInfo[editing] = String(maximum);
}
}
else if(editing == 1){
maximum = 20;
if(parseInt(tempInfo[editing]) > maximum){
tempInfo[editing] = String(maximum);
}
}
else if(editing == 2){
maximum = floor(parseInt(tempInfo[0]) * parseInt(tempInfo[1]) / 2);
if(parseInt(tempInfo[editing]) > maximum){
tempInfo[editing] = String(maximum);
}
}
}
}
/**
*method revealEvent - Reveals a certain tile. If it is the first click, then create the mines
*@param c the column of the tile
*@param r the row of the tile
*/
function revealEvent(c, r) {
aiChanged = true;
tiles[c][r].reveal();
// Only create the mines after the first tile is revealed
// This guarantees the player does not lose on the first turn
if (firstClick) {
// Create all the mines
createMines(c, r);
// Calculate the number of mines around the tile
calcTileNum();
firstClick = false;
}
}
/**
*method flagEvent - Flags or unflags a certain tile
*@param c the column of the tile
*@param r the row of the tile
*/
function flagEvent(c, r) {
aiChanged = true;
if (!tiles[c][r].getFlag()) {
tiles[c][r].setFlag(true);
flagsLeft--;
} else {
tiles[c][r].setFlag(false);
flagsLeft++;
}
}
/**
*method createTiles - creates all the tiles and adds them to the array
*/
function createTiles() {
for (let i = 0; i < columns; i++) {
for (let j = 0; j < rows; j++) {
// Instantiate all the tile objects
tiles[i][j] = new Tile(i, j);
}
}
}
/**
*method createMines - assigns mines to tiles at random
*@param c the column of the tile of the first click
*@param r the row of the tile of the first click
*/
function createMines(c, r) {
let minesLeft = numOfMines;
let emptySpaces = 1;
while (minesLeft > 0) {
// Selects tiles at random until all the mines are created
let i = floor(random(0, columns));
let j = floor(random(0, rows));
// Only create a mine if the tile does not already have a mine and is not
// within a tile of the initial click
if (!tiles[i][j].getMine() && (abs(c-i) > emptySpaces || abs(r-j) > emptySpaces)) {
tiles[i][j].setMine(true);
minesLeft--;
}
}
}
/**
*method createTiles - calculates how many mines are adjacent to each tile
*/
function calcTileNum() {
let total = 0;
// For each tile...
for (let x = 0; x < columns; x++) {
for (let y = 0; y < rows; y++) {
// Check the tiles surrounding it
for (let dx = -1; dx <= 1; dx++) {
let c = x + dx;
// Skip checking a tile for mines if the selected tile is outside of the map
if (c >= 0 && c < columns) {
for (let dy = -1; dy <= 1; dy++) {
let r = y + dy;
// Skip checking a tile for mines if the selected tile is outside of the map
// Skip checking a tile for mines if the selected tile is the tile being checked
if (r >= 0 && r < rows && (dx != 0 || dy != 0)) {
// If the tile has a mine, add one to the accumulator
if (tiles[c][r].getMine()) {
total++;
}
}
}
}
}
// Once the inner loops are done, the we know the number of mines in the surrounding tiles
tiles[x][y].setTileNum(total);
total = 0;
}
}
}
/**
*method restart - restarts the game by instantiating all tiles again and reseting all variables
*/
function restart() {
tiles = [Array(columns)].map(e => Array(rows));
createTiles();
firstClick = true;
gameOver = false;
win = false;
aiChanged = true;
timer = 0;
totalTiles = columns * rows;
flagsLeft = numOfMines;
restarted = true;
tempInfo[0] = String(columns);
tempInfo[1] = String(rows);
tempInfo[2] = String(numOfMines);
if (aiMode == 2) {
timeStart = millis();
}
}
/**
*method revealEmptyTiles - reveals all adjacent tiles.
*This method is used when a tile of value 0 is revealed
*@param x the column of the tile
*@param y the row of the tile
*/
function revealEmptyTiles(x, y) {
aiChanged = true;
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
let c = x + dx;
let r = y + dy;
if (c >= 0 && c < columns && r >= 0 && r < rows) {
tiles[c][r].reveal();
}
}
}
}
/**
*method drawFlag - draws a flag at the specified coordinates
*@param x the x-coordinate
*@param y the y-coordinate
*/
function drawFlag(x, y) {
strokeWeight(0);
noStroke();
fill(0);
rect(x + 10, y + 28, 20, 5);
rect(x + 13, y + 26, 14, 2);
rect(x + 18, y + 10, 4, 20);
fill(255, 0, 0);
triangle(x + 8, y + 14, x + 22, y + 8, x + 22, y + 20);
}
/**
*method drawClock - draws a clock at the specified coordinates
*@param x the x-coordinate
*@param y the y-coordinate
*/
function drawClock(x, y) {
stroke(0);
strokeWeight(2);
noFill();
ellipse(x, y, 30, 30);
line(x, y - 5, x, y);
line(x + 10, y, x, y);
}
/**
*method drawRestart - draws a restart button at the specified coordinates
*@param x the x-coordinate
*@param y the y-coordinate
*/
function drawRestart(x, y) {
stroke(0);
strokeWeight(2);
fill(80, 180, 255);
rect(x - 25, y - 25, 50, 50);
noFill();
strokeWeight(3);
ellipse(x, y, 30, 30);
triangle(x - 2, y - 15, x + 2, y - 18, x + 2, y - 12);
stroke(80, 180, 255);
strokeWeight(8);
line(x - 9, y - 15, x - 4, y - 8);
}
/**
*method drawOption - draws a option button at the specified coordinates
*@param x the x-coordinate
*@param y the y-coordinate
*/
function drawOptionButton(x, y){
stroke(0);
strokeWeight(2);
fill(150);
rect(x - 50, y - 25, 100, 50);
fill(0);
noStroke();
textSize(25);
text("Options", x, y-3);
}
function drawOptions(){
stroke(0);
fill(255);
if(editing == 0){
strokeWeight(5);
}
else{
strokeWeight(2);
}
rect(90, 15, 50, 50);
if(editing == 1){
strokeWeight(5);
}
else{
strokeWeight(2);
}
rect(230, 15, 50, 50);
if(editing == 2){
strokeWeight(5);
}
else{
strokeWeight(2);
}
rect(370, 15, 50, 50);
fill(0);
noStroke();
textSize(20);
text("Columns", 45, 40);
text(tempInfo[0], 115, 40);
text("Rows", 200, 40);
text(tempInfo[1], 255, 40);
text("Mines", 335, 40);
text(tempInfo[2], 395, 40);
}
/**
*method drawAIButton - draws all the AI buttons
*@param x the x-coordinate
*@param y the y-coordinate
*/
function drawAIButton() {
let x = width - 195;
let y = 99;
stroke(0);
strokeWeight(1);
fill(255, 0, 0);
rect(x, y, 194, 50);
fill(0, 0, 200);
if (aiMode == 0) {
strokeWeight(10);
rect(x + 45, y + 75, 100, 50);
strokeWeight(2);
rect(x + 45, y + 150, 100, 50);
rect(x + 45, y + 225, 100, 50);
} else if (aiMode == 1) {
strokeWeight(2);
rect(x + 45, y + 75, 100, 50);
strokeWeight(10);
rect(x + 10, y + 150, 80, 50);
if(mouseX >= x + 90 && mouseX <= x + 190 && mouseY >= y + 150 && mouseY <= y + 200){
strokeWeight(5);
}
else{
strokeWeight(2);
}
rect(x + 110, y + 150, 80, 50);
strokeWeight(2);
rect(x + 45, y + 225, 100, 50);
} else {
strokeWeight(2);
rect(x + 45, y + 75, 100, 50);
rect(x + 45, y + 150, 100, 50);
strokeWeight(10);
rect(x + 45, y + 225, 100, 50);
}
fill(255);
noStroke();
textSize(30);
text("AI", x + 95, y + 22);
text("Off", x + 95, y + 97);
if(aiMode == 1){
text("Step", x + 50, y + 172);
text("Next", x + 150, y + 172);
}
else{
text("Step", x + 95, y + 172);
}
text("Fast", x + 95, y + 247);
}
class AI{
constructor(){
this.uncoveredTiles = [];
this.allSections = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []];
}
/**
*method calculateProbabilty - calculates the probability of unrevealed tiles that border revealed tiles
*/
calculateProbability() {
this.findUncovered();
// Find the total number of unrevealed tiles
let total = 0;
for (let i = 0; i < columns; i++) {
for (let j = 0; j < rows; j++) {
if (tiles[i][j].getRevealed()) {
total++;
}
}
}
let localTilesLeft = totalTiles - total;
// Calculate the probability of the unknown tiles
let generalProbability = flagsLeft / localTilesLeft * 100;
// Reset values
for (let i = 0; i < columns; i++) {
for (let j = 0; j < rows; j++) {
tiles[i][j].setProbability(generalProbability);
}
}
let listLength1;
let listLength2;
let listLength3;
let combinations;
let previousTiles = [];
let currentTiles = [];
let temp1 = [];
let temp2 = [];
let current;
// For each section in the ArrayList...
for (let i = 0; i < 20; i++) {
temp1 = this.allSections[i];
listLength1 = temp1.length;
previousTiles = [];
combinations = [[]];
//Here we find all the possible combinations in one section
//This algorithm checks a few tiles at a time and saves the possible combinations, which is then used to create the next set of possible combinations
//This is far faster than checking all the tiles at once
for (let j = 0; j < listLength1; j++) {
temp2 = temp1[j].neighborCovered;
listLength2 = temp2.length;
//Find the uncovered tile to be checked
current = temp1[j];
//Find the current covered tiles to be checked
currentTiles = [];
for (let k = 0; k < listLength2; k++) {
if (!previousTiles.includes(temp2[k])) {
currentTiles.push(temp2[k]);
}
}
//Create all possible combinations up to this point
combinations = this.checkTiles(currentTiles, previousTiles, combinations, current);
listLength3 = currentTiles.length;
for (let k = 0; k < listLength3; k++) {
previousTiles.push(currentTiles[k]);
}
}
//When we find all the possible combinations, we set the probabilities of the tile
for (let j = 0; j < combinations.length; j++) {
let list = combinations[j];
for (let k = 0; k < list.length; k++) {
if (list[k] == 1) {
previousTiles[k].addInstance();
}
}
}
for (let j = 0; j < previousTiles.length; j++) {
let probability = previousTiles[j].getInstance() / combinations.length * 100;
if(combinations.length == 0){
probability = 1000;
}
previousTiles[j].setProbability(probability);
}
}
}
/**
*method checkTiles - creates the possible combinations for an ArrayList of tiles
*@param current the "new" section being checked (covered tiles)
*@param previous the "old" section (covered tiles). The possible combinations are already set for these tiles so we don't have check them again
*@param previousCombinations the ArrayList that stores the possible combinations of the old section
*@param currentTile the tile that needs to be checked (uncovered tile)
*@return all the possible combinations, combining the old and the new section
*/
checkTiles(current, previous, previousCombinations, currentTile) {
let currentCombinations = this.createAllPossibilities(current.length); //All possiblities in "current" list
let list = []; //A combination in "currentCombinations"
let validList = []; //Combinations that are valid
let combinedValid = []; //Used to combine the "previous" and "current" combination lists
let mines = 0;
let valid = true;
let tile;
let listLength1;
let listLength2;
//Reset values
for (let i = 0; i < columns; i++) {
for (let j = 0; j < rows; j++) {
tiles[i][j].setMineInstance(0);
tiles[i][j].setInstance(0);
}
}
listLength1 = previousCombinations.length;
// If there is no new tiles to check, then check if the previous tiles are still valid with
// the new information (the number of mines around this tile)
if (current.length == 0) {
for (let i = 0; i < listLength1; i++) {
listLength2 = previousCombinations[i].length;
for (let j = 0; j < listLength2; j++) {
previous[j].setMineInstance(previousCombinations[i][j]);
}
valid = this.isValid(currentTile);
if (valid) {
validList.push(previousCombinations[i]);
}
}
}
// If there are no previous tiles
else if (listLength1 == 0) {
for (let i = 0; i < currentCombinations.length; i++) {
mines = 0;
// Set the tiles to either have or not have a mine for this instance
list = currentCombinations[i];
for (let j = 0; j < list.length; j++) {
tile = current[j];
tile.setMineInstance(list[j]);
if (list[j] == 1) {
mines++;
}
}
// Checks to see if the combination is valid
valid = this.isValid(currentTile);
// If it is valid, then add it to the list of valid combinations
if (valid) {
validList.push(list);
}
}
// If there are previous tiles
} else {
let mines2 = 0;
for (let i = 0; i < listLength1; i++) {
let temp = previousCombinations[i];
listLength2 = temp.length;
mines = 0;
// Set the mines from the previous combinations
for (let j = 0; j < listLength2; j++) {
previous[j].setMineInstance(temp[j]);
if (temp[j] == 1) {
mines ++;
}
}
for (let j = 0; j < currentCombinations.length; j++) {
// Set the tiles to either have or not have a mine for the new tiles
list = currentCombinations[j];
mines2 = mines;
for (let k = 0; k < list.length; k++) {
tile = current[k];
tile.setMineInstance(list[k]);
if (list[k] == 1) {
mines2 ++;
}
}
// If the number of mines in this combination exceed the actual number of tiles, it is invalid
if (mines2 > flagsLeft) {
valid = false;
} else {
// Checks to see if the combination is valid
valid = this.isValid(currentTile);
}
// If it is valid, then combine it with the previous combination and add it to the valid list
if (valid) {
let combinedValid = [];
for (let k = 0; k < listLength2; k++) {
combinedValid[k] = previousCombinations[i][k];
}
for (let k = listLength2; k < list.length + listLength2; k++) {
combinedValid[k] = list[k - listLength2];
}
validList.push(combinedValid);
}
}
}
}
return validList;
}
/**
*method isValid - checks whether the combination is valid around one tile
*@param tile the tile that will be checked
*@return true if the combination is valid
*/
isValid(tile) {
let total = 0;
let x = tile.getC();
let y = tile.getR();
let c, r;
let valid = true;
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
c = x + dx;
r = y + dy;
if (c >= 0 && r >= 0 && c < columns && r < rows && (tiles[c][r].getMineInstance() == 1 || tiles[c][r].getFlag())) {
total++;
}
}
}
//Checks whether the number of mines in a combination matched the actual number of mines around the tile
if (total != tile.getTileNum()) {
valid = false;
}
return valid;
}
/**
*method createAllPossibilities - creates all possibilities of mine/not mine.
*Basically uses binary counting to achieve its task
*@return all possible combinations, as an integer array
*/
createAllPossibilities(size) {
let combinationList = [];
for(let i = 0; i<size; i++){
combinationList[i] = 0;
}
let list = [];
if (size > 0) {
//Binary counting
combinationList[0] = -1;
let maximum = round(pow(2, size));
for (let i = 0; i < maximum; i++) {
combinationList[0] += 1;
for (let j = 0; j < size; j++) {
if (combinationList[j] > 1) {
combinationList[j] = 0;
combinationList[j + 1] += 1;
}
}
//Add that set of mine/not mine to the arraylist
let temp = [];
temp = combinationList.slice();
list.push(temp);
}
}
return list;
}
/**
*method findUncovered - finds all the uncovered tiles that border covered tiles and assigns them to a section
*/
findUncovered() {
// Reset
this.uncoveredTiles.splice(0, this.uncoveredTiles.length);
for (let i = 0; i < 20; i++) {
this.allSections[i].splice(0, this.allSections[i].length);
}
for (let x = 0; x < columns; x++) {
for (let y = 0; y < rows; y++) {
tiles[x][y].neighborCovered.splice(0, tiles[x][y].neighborCovered.length);
}
}
let good = false;
let sec = 1;
let listLength = 0;
let newSection;
for (let x = 0; x < columns; x++) {
for (let y = 0; y < rows; y++) {
if (tiles[x][y].getRevealed() && tiles[x][y].getTileNum() != 0) {
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
let c = x+dx;
let r = y+dy;
if (c >= 0 && c < columns && r >= 0 && r < rows && !tiles[c][r].getRevealed() && !tiles[c][r].getFlag()) {
// Mark this uncovered tile as a tile that will be added to the list
good = true;
// Add the tile to the uncovered tile's list of adjacent covered tiles
tiles[x][y].neighborCovered.push(tiles[c][r]);
}
}
}
// Add the uncovered tile to the list and check which section it should have
if (good) {
listLength = this.uncoveredTiles.length;
// If the tile is the first in the list
if (listLength == 0) {
tiles[x][y].setSection(sec);
} else {
newSection = true;
let list = tiles[x][y].neighborCovered;
for (let i = 0; i < listLength && newSection; i++) {
let current = this.uncoveredTiles[i];
let listLength1 = current.neighborCovered.length;
for (let j = 0; j < listLength1 && newSection; j++) {
let temp = current.neighborCovered[j];
// If the tile shares a neighbor with another tile, then assign the section of the other tile
if (list.includes(temp)) {
newSection = false;
tiles[x][y].setSection(current.getSection());
}
}
}
// If it found no previous tiles that share a neighbor, then start a new section
if (newSection) {
sec++;
tiles[x][y].setSection(sec);
}
}
this.uncoveredTiles.push(tiles[x][y]);
} else {
// If the tile should not be added to the list, then set the section to 0
tiles[x][y].setSection(0);
}
good = false;
}
}
}
listLength = this.uncoveredTiles.length;
this.changeTiles(listLength);
let temp;
let tempSection;
// Add tiles to the section lists
for (let i = 0; i < listLength; i++) {
temp = this.uncoveredTiles[i];
tempSection = temp.getSection();
if (tempSection > 0 && tempSection <= 20 && !this.allSections[tempSection - 1].includes(temp)) {
this.allSections[tempSection - 1].push(temp);
}
}
}
/**
*method changeTiles - goes through the list and makes sure all the sections are correct
*If the sections aren't correct, change them
*/
changeTiles(listLength) {
let a;
let b;
let aSection;
let bSection;
let smaller = 0;
let larger = 0;
let needsToChange = true;
while (needsToChange) {
needsToChange = false;
for (let i = 0; i < listLength && !needsToChange; i++) {
a = this.uncoveredTiles[i];
aSection = a.getSection();
for (let j = i; j < listLength && !needsToChange; j++) {
b = this.uncoveredTiles[j];
bSection = b.getSection();
// If the sections of the two tiles don't match...
if (aSection != bSection) {
let list = a.neighborCovered;
let listLength1 = b.neighborCovered.length;
for (let k = 0; k < listLength1 && !needsToChange; k++) {
let temp = b.neighborCovered[k];
// ...And they share a neighbor, then the section is wrong
if (list.includes(temp)) {
if (aSection < bSection) {
smaller = aSection;
larger = bSection;
} else {
smaller = bSection;
larger = aSection;
}
// Exit the loop
needsToChange = true;
}
}
}
}
}
// Change the sections
if (needsToChange) {
for (let i = 0; i < listLength; i++) {
if (this.uncoveredTiles[i].getSection() == larger) {
this.uncoveredTiles[i].setSection(smaller);
}
}
}
}
}
}
class Tile {
constructor(c, r) {
this.column = c;
this.row = r;
// Variables x and y scale the column and row, making it easier to draw things
this.x = this.column * 40;
this.y = this.row * 40 + 100;
this.tileNum = 0;
this.neighborCovered = [];
this.revealed = false;
this.mine = false;
this.flag = false;
this.zeroRevealed = false;
this.totalInstance = 0;
this.mineThisInstance = 0;
this.section = 0;
this.probability = 0.0;
}
// Display methods
displayTile() {
if (!this.revealed) {
strokeWeight(2);
stroke(0);
fill(150);
rect(this.x, this.y, 40, 40);
fill(200);
noStroke();
quad(this.x, this.y, this.x+40, this.y, this.x+35, this.y+5, this.x+5, this.y+5);
quad(this.x, this.y, this.x, this.y+40, this.x+5, this.y+35, this.x+5, this.y+5);
fill(100);
quad(this.x+40, this.y+40, this.x+40, this.y, this.x+35, this.y+5, this.x+35, this.y+35);
quad(this.x+40, this.y+40, this.x, this.y+40, this.x+5, this.y+35, this.x+35, this.y+35);
} else {
stroke(50);
strokeWeight(1);
fill(230);
rect(this.x, this.y, 40, 40);
}
}
displayProbability() {
if (!this.flag && !this.revealed) {
noStroke();
if (this.probability == 0) {
fill(0, 230, 0);
} else if (this.probability == 100) {
fill(255, 0, 0);
} else {
fill(230, 230, 0);
}
rect(this.x + 5, this.y + 5, 30, 30);
fill(0);
textSize(12);
text(round(this.probability), this.x + 20, this.y + 20);
}
}
displayAllMines() {
if (this.mine && !this.flag) {
noStroke();
fill(0);
ellipse(this.x + 20, this.y + 20, 20, 20);
strokeWeight(2);
stroke(0);
line(this.x + 20, this.y + 7, this.x + 20, this.y + 33);
line(this.x + 7, this.y + 20, this.x + 33, this.y + 20);
strokeWeight(1);
line(this.x + 10, this.y + 10, this.x + 30, this.y + 30);
line(this.x + 10, this.y + 30, this.x + 30, this.y + 10);
noStroke();
fill(255);
rect(this.x + 16, this.y + 16, 4, 4);
}
}
displayFlag() {
if (this.flag && !this.revealed) {
strokeWeight(0);
noStroke();
fill(0);
rect(this.x + 10, this.y + 28, 20, 5);
rect(this.x + 13, this.y + 26, 14, 2);
rect(this.x + 18, this.y + 10, 4, 20);
fill(255, 0, 0);
triangle(this.x + 8, this.y + 14, this.x + 22, this.y + 8, this.x + 22, this.y + 20);
}
}
displayCross() {
stroke(200, 0, 0);
strokeWeight(4);
line(this.x + 5, this.y + 5, this.x + 35, this.y + 35);
line(this.x + 5, this.y + 35, this.x + 35, this.y + 5);
}
displayNum() {
if (!this.mine && this.revealed ) {
textSize(30);
noStroke();
if (this.tileNum == 1) {
fill(30, 30, 255);
} else if (this.tileNum == 2) {
fill(30, 150, 30);
} else if (this.tileNum == 3) {
fill(255, 0, 0);
} else if (this.tileNum == 4) {
fill(0, 0, 150);
} else if (this.tileNum == 5) {
fill(150, 0, 0);
} else if (this.tileNum == 6) {
fill(0, 150, 150);
} else if (this.tileNum == 7) {
fill(0, 0, 0);
} else if (this.tileNum == 8) {
fill(150, 150, 150);
}
if (this.tileNum != 0) {
text(this.tileNum, this.x + 20, this.y + 18);
}
}
}
// Accessors
getC() {
return this.column;
}
getR() {
return this.row;
}
getMine() {
return this.mine;
}
getFlag() {
return this.flag;
}
getRevealed() {
return this.revealed;
}
getZeroRevealed(){
return this.zeroRevealed;
}
getTileNum() {
return this.tileNum;
}
getInstance() {
return this.totalInstance;
}
getProbability() {
return this.probability;
}
getMineInstance() {
return this.mineThisInstance;
}
getSection(){
return this.section;
}
// Mutators
setMine(newMine) {
this.mine = newMine;
}
setFlag(newFlag) {
this.flag = newFlag;
}
reveal() {
this.revealed = true;
}
setZeroRevealed(newZeroRevealed){
this.zeroRevealed = newZeroRevealed;
}
setTileNum(newTileNum) {
this.tileNum = newTileNum;
}
setInstance(newInstance) {
this.totalInstance = newInstance;
}
addInstance() {
this.totalInstance++;
}
setProbability(newProbability) {
this.probability = newProbability;
}
setMineInstance(newMineThisInstance) {
this.mineThisInstance = newMineThisInstance;
}
setSection(newSection){
this.section = newSection;
}
toString() {
return "column: " + (column+1) + " row: " + (row+1);
}
}