parent
bd5f9acb68
commit
1e840ae7c2
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "minesweeper",
|
||||
"version": "0.80.5",
|
||||
"version": "0.81.0",
|
||||
"description": "Just Minesweeper!",
|
||||
"author": "Felix W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -24,6 +24,7 @@ export class Display {
|
|||
|
||||
private coverSymbol: HTMLCanvasElement | undefined;
|
||||
private flagSymbol: HTMLCanvasElement | undefined;
|
||||
private markSymbol: HTMLCanvasElement | undefined;
|
||||
private uncoveredMineSymbol: HTMLCanvasElement | undefined;
|
||||
private mineSymbol: HTMLCanvasElement | undefined;
|
||||
private digitSymbols: HTMLCanvasElement[] | undefined;
|
||||
|
@ -74,6 +75,10 @@ export class Display {
|
|||
ctx = this.flagSymbol.getContext("2d")!;
|
||||
fillText(this.font.flag, this.font.fontFace, "#f00");
|
||||
|
||||
this.markSymbol = createCanvas(this.scale, this.scale);
|
||||
ctx = this.markSymbol.getContext("2d")!;
|
||||
fillText(this.font.mark, this.font.fontFace, "#00f");
|
||||
|
||||
this.uncoveredMineSymbol = createCanvas(this.scale, this.scale);
|
||||
ctx = this.uncoveredMineSymbol.getContext("2d")!;
|
||||
ctx.fillStyle = "#f00";
|
||||
|
@ -275,9 +280,9 @@ export class Display {
|
|||
if (this.field!.hasLost || this.field!.hasWon || this.mouseSquare === null)
|
||||
return true;
|
||||
if (this.mouseHoldUncover && this.mouseSquare === it)
|
||||
return it.hasFlag;
|
||||
return it.hasFlag || it.hasMark;
|
||||
if (this.mouseHoldChord && (this.mouseSquare === it || this.mouseSquare.neighbors.indexOf(it) >= 0))
|
||||
return it.hasFlag;
|
||||
return it.hasFlag || it.hasMark;
|
||||
|
||||
return true;
|
||||
})
|
||||
|
@ -326,6 +331,8 @@ export class Display {
|
|||
let icon;
|
||||
if (square.hasFlag)
|
||||
icon = this.flagSymbol;
|
||||
else if (square.hasMark)
|
||||
icon = this.markSymbol;
|
||||
else if (square.hasMine && !square.isCovered)
|
||||
icon = this.uncoveredMineSymbol;
|
||||
else if (!square.isCovered)
|
||||
|
@ -438,6 +445,7 @@ export interface IconFont {
|
|||
fontFace: string;
|
||||
|
||||
flag: string;
|
||||
mark: string;
|
||||
uncoveredMine: string;
|
||||
mine: string;
|
||||
clock: string;
|
||||
|
@ -451,6 +459,7 @@ export class BasicIconFont implements IconFont {
|
|||
fontFace = "Courier New";
|
||||
|
||||
flag = "F";
|
||||
mark = "?";
|
||||
uncoveredMine = "X";
|
||||
mine = "Mines";
|
||||
clock = "Time";
|
||||
|
@ -464,6 +473,7 @@ export class ForkAwesomeFont implements IconFont {
|
|||
fontFace = "ForkAwesome";
|
||||
|
||||
flag = "\uf024";
|
||||
mark = "\uf059";
|
||||
uncoveredMine = "\uf1e2";
|
||||
mine = "\uf1e2";
|
||||
clock = "\uf017";
|
||||
|
|
|
@ -208,6 +208,7 @@ export class Field {
|
|||
|
||||
if (square === undefined) throw new Error(`Cannot chord undefined square at (${coords}).`);
|
||||
if (square.isCovered || this.hasWon || this.hasLost) return;
|
||||
if (square.getNeighborCount(it => it.hasMark) > 0) return;
|
||||
if (square.getNeighborCount(it => it.hasFlag) !== square.getNeighborCount(it => it.hasMine)) return;
|
||||
|
||||
this.statistics.squaresChorded++;
|
||||
|
@ -220,31 +221,6 @@ export class Field {
|
|||
if (this.hasLost) this.statistics.squaresChordedLeadingToLoss++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the flag at the given square.
|
||||
*
|
||||
* @param coords the coordinates of the square to flag
|
||||
*/
|
||||
flag(coords: { x: number, y: number }): void {
|
||||
const square = this.squares[coords.y][coords.x];
|
||||
if (square === undefined) throw new Error(`Cannot toggle flag of undefined square at (${coords}).`);
|
||||
if (!square.isCovered || this.hasWon || this.hasLost) return;
|
||||
|
||||
this.addAction(new Action(
|
||||
() => {
|
||||
square.hasFlag = !square.hasFlag;
|
||||
if (square.hasFlag) this.statistics.squaresFlagged++;
|
||||
this._flagCount += (square.hasFlag ? 1 : -1);
|
||||
},
|
||||
() => {
|
||||
square.hasFlag = !square.hasFlag;
|
||||
this._flagCount += (square.hasFlag ? 1 : -1);
|
||||
return true;
|
||||
},
|
||||
() => true
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncovers this square, revealing the contents beneath.
|
||||
*
|
||||
|
@ -282,7 +258,7 @@ export class Field {
|
|||
const uncoverQueue: Square[] = [square];
|
||||
while (uncoverQueue.length > 0) {
|
||||
const next = uncoverQueue.pop()!;
|
||||
if (!next.isCovered || next.hasFlag) continue;
|
||||
if (!next.isCovered || next.hasFlag || next.hasMark) continue;
|
||||
|
||||
let remainingFlags: Square[] | undefined;
|
||||
this.addAction(new Action(
|
||||
|
@ -337,12 +313,61 @@ export class Field {
|
|||
));
|
||||
|
||||
if (next.hasMine) break;
|
||||
if (next.getNeighborCount(it => it.hasMine || it.hasFlag) === 0)
|
||||
if (next.getNeighborCount(it => it.hasMine || it.hasFlag || it.hasMark) === 0)
|
||||
uncoverQueue.push(...next.neighbors.filter(it => it.isCovered));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the flag at the given square.
|
||||
*
|
||||
* @param coords the coordinates of the square to toggle the flag at
|
||||
*/
|
||||
toggleFlag(coords: { x: number, y: number }): void {
|
||||
const square = this.squares[coords.y][coords.x];
|
||||
if (square === undefined) throw new Error(`Cannot toggle flag of undefined square at (${coords}).`);
|
||||
if (!square.isCovered || square.hasMark || this.hasWon || this.hasLost) return;
|
||||
|
||||
this.addAction(new Action(
|
||||
() => {
|
||||
square.hasFlag = !square.hasFlag;
|
||||
if (square.hasFlag) this.statistics.squaresFlagged++;
|
||||
this._flagCount += (square.hasFlag ? 1 : -1);
|
||||
},
|
||||
() => {
|
||||
square.hasFlag = !square.hasFlag;
|
||||
this._flagCount += (square.hasFlag ? 1 : -1);
|
||||
return true;
|
||||
},
|
||||
() => true
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the question mark at the given square.
|
||||
*
|
||||
* @param coords the coordinates of the square to toggle the question mark at
|
||||
*/
|
||||
toggleMark(coords: { x: number, y: number }): void {
|
||||
const square = this.squares[coords.y][coords.x];
|
||||
if (square === undefined) throw new Error(`Cannot toggle flag of undefined square at (${coords}).`);
|
||||
if (!square.isCovered || square.hasFlag || this.hasWon || this.hasLost) return;
|
||||
|
||||
this.addAction(new Action(
|
||||
() => {
|
||||
square.hasMark = !square.hasMark;
|
||||
if (square.hasMark) this.statistics.squaresMarked++;
|
||||
},
|
||||
() => {
|
||||
square.hasMark = !square.hasMark;
|
||||
if (square.hasMark) this.statistics.squaresMarked++;
|
||||
return true;
|
||||
},
|
||||
() => true
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the given callback such that all calls to `#addAction` can be undone with a single invocation of `#undo`.
|
||||
|
@ -433,6 +458,7 @@ export class Square {
|
|||
isCovered: boolean;
|
||||
hasMine: boolean;
|
||||
hasFlag: boolean;
|
||||
hasMark: boolean;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -451,6 +477,7 @@ export class Square {
|
|||
this.isCovered = true;
|
||||
this.hasMine = hasMine;
|
||||
this.hasFlag = false;
|
||||
this.hasMark = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -463,6 +490,7 @@ export class Square {
|
|||
const copy = new Square(field, this.x, this.y, this.hasMine);
|
||||
copy.isCovered = this.isCovered;
|
||||
copy.hasFlag = this.hasFlag;
|
||||
copy.hasMark = this.hasMark;
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
|
|
@ -293,8 +293,19 @@ export class Game {
|
|||
this.leftDown = true;
|
||||
break;
|
||||
case 2:
|
||||
if (!this.leftDown)
|
||||
this.field.flag(coords);
|
||||
if (!this.leftDown) {
|
||||
const square = this.field.getSquareOrElse(coords);
|
||||
if (square !== undefined) {
|
||||
if (square.hasFlag) {
|
||||
this.field.toggleFlag(coords);
|
||||
this.field.toggleMark(coords);
|
||||
} else if (square.hasMark) {
|
||||
this.field.toggleMark(coords);
|
||||
} else {
|
||||
this.field.toggleFlag(coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.rightDown = true;
|
||||
break;
|
||||
|
|
|
@ -19,13 +19,15 @@ export class Solver {
|
|||
|
||||
if (!field.hasStarted) {
|
||||
field.runUndoably(() => {
|
||||
field.squareList.filter(it => it.hasFlag).forEach(it => field.flag(it.coords));
|
||||
field.squareList.filter(it => it.hasFlag).forEach(it => field.toggleFlag(it.coords));
|
||||
field.squareList.filter(it => it.hasMark).forEach(it => field.toggleMark(it.coords));
|
||||
field.uncover({x: Math.floor(field.width / 2), y: Math.floor(field.height / 2)});
|
||||
});
|
||||
}
|
||||
|
||||
field.runUndoably(() => {
|
||||
field.squareList.filter(it => it.hasFlag).forEach(it => field.flag(it.coords));
|
||||
field.squareList.filter(it => it.hasFlag).forEach(it => field.toggleFlag(it.coords));
|
||||
field.squareList.filter(it => it.hasMark).forEach(it => field.toggleMark(it.coords));
|
||||
|
||||
let flagCount = -1;
|
||||
let coveredCount = -1;
|
||||
|
@ -137,7 +139,7 @@ export class Solver {
|
|||
.forEach(square => {
|
||||
field.chord(square);
|
||||
if (square.getNeighborCount(it => it.isCovered) === square.getNeighborCount(it => it.hasMine))
|
||||
square.neighbors.filter(it => !it.hasFlag).forEach(it => field.flag(it));
|
||||
square.neighbors.filter(it => !it.hasFlag).forEach(it => field.toggleFlag(it));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -249,7 +251,7 @@ export class Solver {
|
|||
|
||||
const [solution, square] = target;
|
||||
if (solution === 0) field.uncover(square.coords);
|
||||
else if (solution === 1) field.flag(square.coords);
|
||||
else if (solution === 1) field.toggleFlag(square.coords);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ export interface Statistics {
|
|||
squaresChorded: number;
|
||||
squaresChordedLeadingToLoss: number;
|
||||
squaresFlagged: number;
|
||||
squaresMarked: number;
|
||||
squaresUncovered: number;
|
||||
|
||||
hintsRequested: number;
|
||||
|
@ -171,6 +172,16 @@ export class LocalStatistics implements Statistics {
|
|||
this.write(statistics);
|
||||
}
|
||||
|
||||
get squaresMarked(): number {
|
||||
return +(this.read()["squaresMarked"] ?? 0);
|
||||
}
|
||||
|
||||
set squaresMarked(value: number) {
|
||||
const statistics = this.read();
|
||||
statistics["squaresMarked"] = "" + value;
|
||||
this.write(statistics);
|
||||
}
|
||||
|
||||
get squaresUncovered(): number {
|
||||
return +(this.read()["squaresUncovered"] ?? 0);
|
||||
}
|
||||
|
@ -261,6 +272,10 @@ export class LocalStatistics implements Statistics {
|
|||
<th>Squares flagged</th>
|
||||
<td>${this.squaresFlagged}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Squares marked</th>
|
||||
<td>${this.squaresMarked}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Squares uncovered</th>
|
||||
<td>${this.squaresUncovered}</td>
|
||||
|
@ -296,6 +311,7 @@ export class MemoryStatistics implements Statistics {
|
|||
squaresChorded: number = 0;
|
||||
squaresChordedLeadingToLoss: number = 0;
|
||||
squaresFlagged: number = 0;
|
||||
squaresMarked: number = 0;
|
||||
squaresUncovered: number = 0;
|
||||
hintsRequested: number = 0;
|
||||
solverUsages: number = 0;
|
||||
|
@ -313,6 +329,7 @@ export class MemoryStatistics implements Statistics {
|
|||
this.squaresChorded = 0;
|
||||
this.squaresChordedLeadingToLoss = 0;
|
||||
this.squaresFlagged = 0;
|
||||
this.squaresMarked = 0;
|
||||
this.squaresUncovered = 0;
|
||||
this.hintsRequested = 0;
|
||||
this.solverUsages = 0;
|
||||
|
|
Loading…
Reference in New Issue