262 lines
7.1 KiB
JavaScript
262 lines
7.1 KiB
JavaScript
const board = [];
|
|
|
|
const canvas = document.getElementById("minesweeper");
|
|
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
|
|
const context = canvas.getContext("2d");
|
|
|
|
let scrollX = 0, scrollY = 0;
|
|
|
|
const CELL_SIZE = 20;
|
|
const STROKE_SIZE = 2;
|
|
const MINE_CHANCE = 0.2;
|
|
|
|
class Cell {
|
|
constructor(isRevealed, isMine, isFlagged) {
|
|
this.isRevealed = isRevealed;
|
|
this.isMine = isMine;
|
|
this.isFlagged = isFlagged;
|
|
}
|
|
}
|
|
|
|
// Initialize board
|
|
for (let x = 0; x < Math.floor(window.innerWidth / CELL_SIZE) + 1; x++) {
|
|
board.push([]);
|
|
|
|
for (let y = 0; y < Math.floor(window.innerHeight / CELL_SIZE) + 1; y++) {
|
|
board[x].push(new Cell(false, Math.random() < MINE_CHANCE, false));
|
|
}
|
|
}
|
|
|
|
function draw() {
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
context.translate(-scrollX, -scrollY);
|
|
|
|
for (let x = 0; x < board.length; x++) {
|
|
for (let y = 0; y < board[x].length; y++) {
|
|
const cell = board[x][y];
|
|
|
|
context.fillStyle = cell.isRevealed ? "#AAA" : "#BDBDBD";
|
|
context.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
|
|
|
|
context.fillStyle = cell.isRevealed ? "#424242" : "#EEE";
|
|
context.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE - STROKE_SIZE, STROKE_SIZE);
|
|
context.fillRect(x * CELL_SIZE, y * CELL_SIZE, STROKE_SIZE, CELL_SIZE - STROKE_SIZE);
|
|
|
|
context.fillStyle = cell.isRevealed ? "#EEE" : "#424242";
|
|
context.fillRect(x * CELL_SIZE + CELL_SIZE - STROKE_SIZE, y * CELL_SIZE + STROKE_SIZE, STROKE_SIZE, CELL_SIZE - STROKE_SIZE);
|
|
context.fillRect(x * CELL_SIZE + STROKE_SIZE, y * CELL_SIZE + CELL_SIZE - STROKE_SIZE, CELL_SIZE - STROKE_SIZE, STROKE_SIZE);
|
|
|
|
if (cell.isFlagged) {
|
|
context.fillStyle = "red";
|
|
context.beginPath();
|
|
context.arc(x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2, CELL_SIZE / 3, 0, 2 * Math.PI);
|
|
context.fill();
|
|
}
|
|
|
|
if (cell.isRevealed) {
|
|
if (cell.isMine) {
|
|
context.fillStyle = "black";
|
|
context.beginPath();
|
|
context.arc(x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2, CELL_SIZE / 3, 0, 2 * Math.PI);
|
|
context.fill();
|
|
} else if (getCellNumber(x, y) > 0) {
|
|
context.font = "18px monospace";
|
|
context.textAlign = "center";
|
|
context.fillStyle = ["#0000ff", "#00ff00", "#ff0000", "#010280", "#7e0000", "#027f80", "#000000", "#808080"][getCellNumber(x, y) - 1];
|
|
context.fillText(getCellNumber(x, y), x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE * 0.8);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
}
|
|
|
|
function screenCoordsToBoardCoords(x, y) {
|
|
return {
|
|
x: Math.floor((x - scrollX) / CELL_SIZE),
|
|
y: Math.floor((y - scrollY) / CELL_SIZE),
|
|
};
|
|
}
|
|
|
|
function createColumnAtRight() {
|
|
const newArr = [];
|
|
for (let i = 0; i < board[0].length; i++) {
|
|
newArr.push(new Cell(false, Math.random() < MINE_CHANCE, false));
|
|
}
|
|
board.push(newArr);
|
|
}
|
|
|
|
function createColumnAtLeft() {
|
|
const newArr = [];
|
|
for (let i = 0; i < board[0].length; i++) {
|
|
newArr.push(new Cell(false, Math.random() < MINE_CHANCE, false));
|
|
}
|
|
board.unshift(newArr);
|
|
|
|
scrollX += CELL_SIZE;
|
|
}
|
|
|
|
function createRowAtBottom() {
|
|
for (let i = 0; i < board.length; i++) {
|
|
board[i].push(new Cell(false, Math.random() < MINE_CHANCE, false));
|
|
}
|
|
}
|
|
|
|
function createRowAtTop() {
|
|
for (let i = 0; i < board.length; i++) {
|
|
board[i].unshift(new Cell(false, Math.random() < MINE_CHANCE, false));
|
|
}
|
|
|
|
scrollY += CELL_SIZE;
|
|
}
|
|
|
|
function getCellsAround(x, y) {
|
|
if (x + 1 >= board.length) {
|
|
createColumnAtRight();
|
|
}
|
|
|
|
if (y + 1 >= board[0].length) {
|
|
createRowAtBottom();
|
|
}
|
|
|
|
if (x <= 0) {
|
|
createColumnAtLeft();
|
|
x++;
|
|
}
|
|
|
|
if (y <= 0) {
|
|
createRowAtTop();
|
|
y++;
|
|
}
|
|
|
|
return [
|
|
[x - 1, y - 1],
|
|
[x, y - 1],
|
|
[x + 1, y - 1],
|
|
[x - 1, y],
|
|
[x + 1, y],
|
|
[x - 1, y + 1],
|
|
[x, y + 1],
|
|
[x + 1, y + 1],
|
|
];
|
|
}
|
|
|
|
function getCellNumber(x, y) {
|
|
return getCellsAround(x, y)
|
|
.reduce((acc, coords) => acc += board[coords[0]][coords[1]].isMine ? 1 : 0, 0);
|
|
}
|
|
|
|
function revealCell(x, y) {
|
|
if (board[x][y].isFlagged) {
|
|
return;
|
|
}
|
|
board[x][y].isRevealed = true;
|
|
|
|
if (getCellNumber(x, y) === 0) {
|
|
getCellsAround(x, y, false).forEach(coords => {
|
|
const cell = board[coords[0]][coords[1]];
|
|
|
|
if (!cell.isRevealed && getCellNumber(x, y) === 0)
|
|
revealCell(coords[0], coords[1]);
|
|
});
|
|
}
|
|
}
|
|
|
|
function cellMarkedAsMine(cell) {
|
|
return cell.isFlagged || (cell.isRevealed && cell.isMine);
|
|
}
|
|
|
|
function revealSafeCell(x, y) {
|
|
if (!board[x][y].isRevealed || board[x][y].isMine) {
|
|
return;
|
|
}
|
|
const targetValue = getCellNumber(x, y);
|
|
const neighbours = getCellsAround(x, y);
|
|
const revealedBombs = neighbours
|
|
.map(([x, y]) => board[x][y])
|
|
.map(cellMarkedAsMine)
|
|
.reduce((acc, isMine) => acc + (isMine ? 1 : 0), 0);
|
|
if (revealedBombs === targetValue) {
|
|
neighbours.forEach(([x, y]) => revealCell(x, y))
|
|
}
|
|
}
|
|
|
|
function flagCell(x, y) {
|
|
if (board[x][y].isRevealed) {
|
|
return;
|
|
}
|
|
board[x][y].isFlagged = !board[x][y].isFlagged;
|
|
}
|
|
|
|
window.addEventListener("contextmenu", event => {
|
|
event.preventDefault();
|
|
})
|
|
|
|
const MouseButton = Object.freeze({
|
|
Left: 0,
|
|
Middle: 1,
|
|
Right: 2,
|
|
})
|
|
|
|
window.addEventListener("mousedown", event => {
|
|
const { x, y } = screenCoordsToBoardCoords(event.pageX + scrollX * 2, event.pageY + scrollY * 2);
|
|
|
|
switch (event.button) {
|
|
case MouseButton.Left:
|
|
revealCell(x, y);
|
|
break;
|
|
case MouseButton.Middle:
|
|
event.preventDefault();
|
|
revealSafeCell(x, y);
|
|
break;
|
|
case MouseButton.Right:
|
|
event.preventDefault();
|
|
flagCell(x, y);
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
|
|
draw();
|
|
});
|
|
|
|
function columnContainsOnlyUnrevealed(column) {
|
|
return column.every(cell => !cell.isRevealed);
|
|
}
|
|
|
|
window.addEventListener("wheel", event => {
|
|
let deltaX = 0, deltaY = 0;
|
|
|
|
if (event.shiftKey) {
|
|
deltaX = event.deltaY;
|
|
} else {
|
|
deltaX = event.deltaX;
|
|
deltaY = event.deltaY;
|
|
}
|
|
|
|
console.log(scrollY + deltaY + window.innerHeight, board.length * CELL_SIZE);
|
|
if (scrollY + deltaY + window.innerHeight > board.length * CELL_SIZE) {
|
|
if (board.slice(-20).every(columnContainsOnlyUnrevealed)) {
|
|
console.log("ignoring");
|
|
return;
|
|
}
|
|
|
|
const diff = scrollY + window.innerHeight - board.length * CELL_SIZE;
|
|
|
|
for (let i = 0; i < diff + CELL_SIZE; i += CELL_SIZE)
|
|
createRowAtBottom();
|
|
}
|
|
|
|
scrollX += deltaX;
|
|
scrollY += deltaY;
|
|
|
|
draw();
|
|
});
|
|
|
|
draw();
|
|
|