Skip to content

Commit

Permalink
Changes to the analysis url query string processing
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidNHill committed Aug 11, 2024
1 parent 3dde72f commit a257ffc
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 19 deletions.
Binary file modified .vs/slnx.sqlite
Binary file not shown.
172 changes: 165 additions & 7 deletions Minesweeper/client/Board.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,9 @@ class Board {
// }
//}
} else if (tile.getValue() < flagCount) {
console.log(tile.asText() + " is over flagged");
//console.log(tile.asText() + " is over flagged");
} else if (tile.getValue() > coveredCount) {
console.log(tile.asText() + " has an invalid value of " + tile.getValue() + " with only " + coveredCount + " surrounding covered tiles");
//console.log(tile.asText() + " has an invalid value of " + tile.getValue() + " with only " + coveredCount + " surrounding covered tiles");
return tile;
}

Expand Down Expand Up @@ -540,7 +540,166 @@ class Board {

}


// this version of the compression only deflates flags which don't result in values < 0
getSimpleCompressedData() {

const start = Date.now();

// The games dimensions
const width = this.width;
const height = this.height;
const mines = this.num_bombs;

// an array to do the processing
let a = [];

// transfer the values into an array
for (let y = 0; y < height; y++) {

let xa = [];

for (let x = 0; x < width; x++) {
const tile = this.getTileXY(x, y);

if (tile.isFlagged()) {
xa.push(10); // placed flag

} else if (tile.isCovered() || tile.isBomb()) { // a covered tile, or an exploded mine
xa.push(9); // hidden

} else {
xa.push(tile.getValue()); // revealed

}
}
a.push(xa);
}

// find obvious mines not flagged, we need these otherwise we can't deflate games with NF style
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {

const value = a[y][x];

if (value < 9) {
const ys = Math.max(0, y - 1);
const ye = Math.min(height - 1, y + 1);

const xs = Math.max(0, x - 1);
const xe = Math.min(width - 1, x + 1);

let spaces = 0;
for (let y1 = ys; y1 <= ye; y1++) {
for (let x1 = xs; x1 <= xe; x1++) {
if (a[y1][x1] > 8) {
spaces++;
}
}
}
if (spaces == value) { // if the number of spaces equals the value then anything unflagged is a 'hidden flag'
//console.log(x + " " + y + " " + value + " " + spaces);
for (let y1 = ys; y1 <= ye; y1++) {
for (let x1 = xs; x1 <= xe; x1++) {
if (a[y1][x1] == 9) {
//console.log( "-- " + x1 + " " + y1);
a[y1][x1] = 11; // a trivially found mine but not flagged
}
}
}
}

}
}
}

// see which flags can be deflated
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {

const value = a[y][x];

if (value == 10 || value == 11) {
const ys = Math.max(0, y - 1);
const ye = Math.min(height - 1, y + 1);

const xs = Math.max(0, x - 1);
const xe = Math.min(width - 1, x + 1);

// check that all the adjacent revealed tiles can be reduced (i.e. not already zero)
let okay = true;
for (let y1 = ys; y1 <= ye; y1++) {
for (let x1 = xs; x1 <= xe; x1++) {
if (a[y1][x1] == 0) {
okay = false
}
}
}
if (okay) { // every adjacent revealed tile can be reduced by 1
for (let y1 = ys; y1 <= ye; y1++) {
for (let x1 = xs; x1 <= xe; x1++) {
if (a[y1][x1] < 9) {
a[y1][x1]--;
}
}
}
} else {
if (a[y][x] == 10) { // placed flag that overflags a revealed tile
a[y][x] = 12; // overflagged ==> flag that isn't inflated

} else if (a[y][x] == 11) { // hidden flag that overflags a revealed tile
a[y][x] = 9; // revert back to a hidden tile
}

}

}
}
}

let data = "";

// convert to a string
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {

const value = a[y][x];

if (value == 10) { // flagged and deflated
data = data + "F";

} else if (value == 11) { // a mine is here but not flagged, deflated
data = data + "I";

} else if (value == 12) { // a flag which wasn't deflated
data = data + "O";

} else if (value == 9) { // a hidden tile
data = data + "H";

} else { // a revealed tile's value (after deflation)
data = data + value;
}
}
}

// get a compressor class
const compressor = new Compressor();

// compress the data to base 62
let cWidth = compressor.compressNumber(width, 2);
let cHeight = compressor.compressNumber(height, 2);
let cMines = compressor.compressNumber(mines, 4);

let cBoard = compressor.compress(data);

let output = cWidth + cHeight + cMines + cBoard;

//console.log("Compressed data length " + output.length + " analysis=" + output);
//console.log("Time to compress " + (Date.now() - start) + " milliseconds");

return output;

}

getCompressedData(reduceMines) {

Expand Down Expand Up @@ -618,9 +777,9 @@ class Compressor {
this.BASE62 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

// this array decides how many digits to allocate to each value on the board
// [0, 1, 2, 3, 4, 5, 6, 7, 8, MINE, HIDDEN, FLAG]
this.VALUES = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "I", "H", "F"];
this.BASES = [10, 7, 5, 5, 4, 3, 3, 1, 1, 4, 10, 8];
// [0, 1, 2, 3, 4, 5, 6, 7, 8, hidden flag, HIDDEN, FLAG, overflagged]
this.VALUES = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "I", "H", "F", "O"];
this.BASES = [10, 7, 5, 5, 4, 3, 3, 1, 1, 4, 10, 8, 1];
this.digits = [];

let start = 0;
Expand All @@ -629,7 +788,6 @@ class Compressor {
start = start + n;
}


//console.log(this.digits);

}
Expand Down
2 changes: 2 additions & 0 deletions Minesweeper/client/Tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class Tile {

this.skull = false; // used when hardcore rule triggers

this.inflate = false; // used when constructing a compressed board

Object.seal(this); // prevent new values being created
}

Expand Down
53 changes: 42 additions & 11 deletions Minesweeper/client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ const docFastPlay = document.getElementById("fastPlay");
const docNgMode = document.getElementById("noGuessMode");
const docHardcore = document.getElementById("hardcore");
const docOverlay = document.getElementById("overlay");
const docAnalysisParm = document.getElementById("analysisParm");
//const docAnalysisParm = document.getElementById("analysisParm");
const urlQueryString = document.getElementById("urlQueryString");

const docBeginner = document.getElementById("beginner");
const docIntermediate = document.getElementById("intermediate");
Expand Down Expand Up @@ -126,6 +127,7 @@ let replayInterrupt = false;
let replaying = false;

let previousBoardHash = 0;
let previousAnalysisQuery = "";
let justPressedAnalyse = false;
let dragging = false; //whether we are dragging the cursor
let dragTile; // the last tile dragged over
Expand Down Expand Up @@ -648,7 +650,7 @@ function switchToAnalysis(doAnalysis) {

updateMineCount(board.bombs_left); // reset the mine count


previousBoardHash = 0; // reset the board hash, so it gets recalculated
}

function setPageTitle() {
Expand Down Expand Up @@ -988,6 +990,8 @@ async function playAgain() {

canvasLocked = false; // just in case it was still locked (after an error for example)

placeAnalysisQuery();

showMessage("Replay game requested");
} else {
showMessage("No game to replay");
Expand Down Expand Up @@ -1190,6 +1194,7 @@ async function newBoardFromString(data, inflate) {
if (char == "F" || char == "M") {
tile.toggleFlag();
newBoard.bombs_left--;
tile.inflate = true;
} else if (char == "0") {
tile.setValue(0);
} else if (char == "1") {
Expand All @@ -1211,6 +1216,10 @@ async function newBoardFromString(data, inflate) {
} else if (char == "I") { // hidden but needs inflating, part of the compression of NF games
tile.setCovered(true);
tile.setFoundBomb();
tile.inflate = true;
} else if (char == "O") { // a flag which doesn't need inflating
tile.toggleFlag();
newBoard.bombs_left--;
} else {
tile.setCovered(true);
}
Expand All @@ -1220,8 +1229,7 @@ async function newBoardFromString(data, inflate) {
// if the data needs inflating then for each flag increase the adjacent tiles value by 1
if (inflate) {
for (let tile of newBoard.tiles) {
if (tile.isFlagged() || tile.isSolverFoundBomb()) {

if (tile.inflate) {
const adjTiles = newBoard.getAdjacent(tile);
for (let i = 0; i < adjTiles.length; i++) {
const adjTile = adjTiles[i];
Expand Down Expand Up @@ -1360,6 +1368,8 @@ async function newGame(width, height, mines, seed) {
const boardSize = width + "x" + height + "x" + mines;
setURLParms("board", boardSize);

placeAnalysisQuery();

showMessage("New game requested with width " + width + ", height " + height + " and " + mines + " mines.");

}
Expand Down Expand Up @@ -2057,6 +2067,7 @@ async function doAnalysis() {
canvasLocked = true;
}

/*
//console.log(docAnalysisParm.value);
let compressed = "";
if (docAnalysisParm.value == "full") {
Expand All @@ -2069,6 +2080,7 @@ async function doAnalysis() {
if (compressed.length < 2000) {
setURLParms("analysis", compressed);
}
*/

// put out a message and wait long enough for the ui to update
showMessage("Analysing...");
Expand Down Expand Up @@ -2129,10 +2141,6 @@ async function doAnalysis() {

async function checkBoard() {

if (!analysisMode || replayMode) {
return;
}

if (canvasLocked) {
console.log("Not checking the board because analysis is being done");
}
Expand All @@ -2146,13 +2154,21 @@ async function checkBoard() {
return;
}

previousBoardHash = currentBoardHash;

// build the analysis URL Query string
placeAnalysisQuery();

// only check the board in analysis mode
if (!analysisMode || replayMode) {
return;
}

// lock the canvas while we check the board
canvasLocked = true;

window.requestAnimationFrame(() => renderHints([], []));

previousBoardHash = currentBoardHash;

console.log("Checking board with hash " + currentBoardHash);

// this will set all the obvious mines which makes the solution counter a lot more efficient on very large boards
Expand Down Expand Up @@ -2192,6 +2208,21 @@ async function checkBoard() {
canvasLocked = false;
}

function placeAnalysisQuery() {

let compressed = null;
if (urlQueryString.checked) {
compressed = board.getSimpleCompressedData();
} else {
compressed = null;
}

if (previousAnalysisQuery != compressed) {
setURLParms("analysis", compressed);
previousAnalysisQuery = compressed;
}

}

// draw a tile to the canvas
function draw(x, y, tileType) {
Expand Down Expand Up @@ -2497,7 +2528,7 @@ function on_click(event) {
}

// remove the analysis parm when playing a game
setURLParms("analysis", null);
//setURLParms("analysis", null);

justPressedAnalyse = false;

Expand Down
5 changes: 4 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,16 @@ <h1 id="headerPanel" style="position: absolute; left: 0px; top: 0px; height: 50p

<button type="button" style="margin-top: 1em;" id="AnalysisButton" onclick="doAnalysis()"><u>A</u>nalyse</button>

<div style="margin-top: 5px;">
<div style="margin-top: 5px;" class="checkboxes">
<label><input type="checkbox" class="checkbox" id="urlQueryString" onclick="placeAnalysisQuery()" /> <span>URL query string</span></label>
<!--
<label>URL parm:</label>
<select id="analysisParm">
<option value="none" selected>None</option>
<option value="full">Full</option>
<option value="reduced">Reduced</option>
</select>
-->
</div>

</nav>
Expand Down

0 comments on commit a257ffc

Please sign in to comment.