Our Process
Making a game in JS was definitly a challenge, but we were able to utilize inspiration from The Game of Life code and help from ChatGPT in order to make a game that is not only functional but looks aesthetically nice. Using containers in html was also a much simpler way to organize not only our JS but our CSS styling too.
%%html
<html>
<style>
.container {
text-align: center;
}
#board {
display: grid;
grid-template-columns: repeat(10, 30px);
gap: 2px;
margin: 20px auto;
}
.cell {
width: 30px;
height: 30px;
background-color: #ccc;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
#restart {
margin-top: 20px;
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
}
</style>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minesweeper</title>
</head>
<body>
<div class="container">
<h1>Minesweeper</h1>
<div id="board"></div>
<button id="restart">Restart</button>
</div>
</body>
<script>
document.addEventListener("DOMContentLoaded", function () {
const board = document.getElementById("board");
const restartButton = document.getElementById("restart");
const size = 10; // Adjust the size of the board as needed
const bombCount = 15; // Adjust the number of bombs as needed
let cells = [];
let bombs = [];
// Initialize the board
function initBoard() {
cells = [];
bombs = [];
board.innerHTML = "";
// Create cells
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
const cell = document.createElement("div");
cell.classList.add("cell");
cell.dataset.row = row;
cell.dataset.col = col;
cell.addEventListener("click", handleCellClick); // Add click event listener here
cells.push(cell);
board.appendChild(cell);
}
}
// Place bombs randomly
while (bombs.length < bombCount) {
const randomIndex = Math.floor(Math.random() * cells.length);
const cell = cells[randomIndex];
if (!cell.classList.contains("bomb")) {
cell.classList.add("bomb");
bombs.push(cell);
}
}
}
// Handle cell click
function handleCellClick(event) {
const cell = event.target;
if (cell.classList.contains("opened") || cell.classList.contains("flagged")) {
return;
}
if (cell.classList.contains("bomb")) {
// Game over
cell.classList.add("exploded");
revealBombs();
gameOver();
} else {
const row = parseInt(cell.dataset.row);
const col = parseInt(cell.dataset.col);
const bombCount = countAdjacentBombs(row, col);
cell.classList.add("opened");
if (bombCount > 0) {
cell.textContent = bombCount;
} else {
// Auto-expand if no adjacent bombs
expandEmptyArea(row, col);
}
checkWin();
}
}
// Count adjacent bombs
function countAdjacentBombs(row, col) {
let count = 0;
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
const r = row + i;
const c = col + j;
if (r >= 0 && r < size && c >= 0 && c < size) {
const adjacentCell = cells.find(
(cell) => cell.dataset.row == r && cell.dataset.col == c
);
if (adjacentCell.classList.contains("bomb")) {
count++;
}
}
}
}
return count;
}
// Expand empty area
function expandEmptyArea(row, col) {
const queue = [{ row, col }];
const visited = new Set();
while (queue.length > 0) {
const { row, col } = queue.shift();
const cell = cells.find(
(c) => c.dataset.row == row && c.dataset.col == col
);
if (!cell || visited.has(`${row}-${col}`)) {
continue;
}
visited.add(`${row}-${col}`);
const bombCount = countAdjacentBombs(row, col);
if (bombCount === 0) {
cell.classList.add("opened");
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
const r = row + i;
const c = col + j;
if (r >= 0 && r < size && c >= 0 && c < size) {
queue.push({ row: r, col: c });
}
}
}
} else {
cell.classList.add("opened");
cell.textContent = bombCount;
}
}
}
// Reveal all bombs
function revealBombs() {
bombs.forEach((bomb) => {
bomb.classList.add("revealed-bomb");
bomb.textContent = "X";
});
}
// Game over
function gameOver() {
cells.forEach((cell) => {
cell.removeEventListener("click", handleCellClick);
if (cell.classList.contains("bomb")) {
cell.classList.add("revealed-bomb");
}
});
restartButton.style.display = "block";
}
// Check for a win
function checkWin() {
const unopenedCells = cells.filter(
(cell) => !cell.classList.contains("opened")
);
const unopenedBombCells = unopenedCells.filter((cell) =>
cell.classList.contains("bomb")
);
if (unopenedBombCells.length === 0) {
// All non-bomb cells opened; player wins
unopenedCells.forEach((cell) => {
cell.classList.add("flagged");
});
restartButton.style.display = "block";
}
}
// Restart the game
restartButton.addEventListener("click", initBoard);
// Initialize the game
initBoard();
});
</script>
</html>
ChatGPT Review
The provided JavaScript Minesweeper code appears to be a functional implementation of the classic Minesweeper game. Overall, the code structure and logic seem well-organized and appropriately commented. Below is a review of the code with some feedback:
- Readability and Structure:
- The code is well-structured and easy to read, with clear separation of concerns.
- The use of comments to explain the purpose of functions and sections of code is commendable and enhances code readability.
- HTML and CSS:
- The HTML and CSS sections are minimal but sufficient for this simple game.
- The styling (CSS) is straightforward, making the game board and restart button visually appealing.
- Game Initialization:
- The
initBoard
function initializes the game board by creating cells and randomly placing bombs. This function is clear and handles the core setup of the game.
- The
- Event Handling:
- Event listeners are appropriately added to cells and the restart button.
- The click event on cells is used to reveal the content and handle various game states.
- The restart button resets the game board when clicked, which is a nice feature.
- Bomb Placement:
- Bombs are placed randomly, ensuring they are distributed across the board.
- This randomness adds to the replayability of the game.
- Game Logic:
- The game logic, including checking for adjacent bombs, expanding empty areas, and handling wins and losses, is well-implemented and follows the Minesweeper rules accurately.
- User Interface Feedback:
- The code provides visual feedback to the user when a bomb is clicked (exploded bomb) or when the game is won (flagged cells).
- Revealing the location of bombs upon game over is a standard Minesweeper feature.
- Improvement Suggestions:
- Consider adding a timer to track the time taken to complete the game.
- You could add difficulty levels or allow the user to customize the board size and bomb count.
- Implementing right-click functionality for flagging cells would enhance the user experience.
- You may want to improve the user interface by providing more information, such as the number of remaining flags or a message when the game is won or lost.
- It might be beneficial to add some form of win/lose notification for accessibility purposes (e.g., screen reader support).
In summary, the code provided is a good starting point for a Minesweeper game. It demonstrates a solid understanding of the game’s mechanics and offers a clean, readable implementation. You can consider expanding on this codebase to add more features and enhance the user experience further.