Where an old school developer and his imaginary cat create a Minesweeper clone with the help of ChatGPT and Bard
The Bard Edition
Featured image by Stable Diffusion
Welcome back to what might turn out to be the biggest waste of time on the Internet: comparing the code output from two of the more popular AI chatbots/LLMs to compare how they do in the creation of a Minesweeper clone.
These are the areas where I would like to test all three:
- Generate requirements for the game
- v1
- Generate code
- v2
Implement/deploy as an Apple appImplement/deploy as an Android app
- v3
Containerize!
When we last left our heroes they were banging their heads against the wall when they discovered that the generated code from ChatGPT was untestable. To keep things fair, we will not ask Bard to create testable code. That will be for another day of bloodletting.
Generate requirements for the game
Prompt: create a requirements document for a web-based implementation of minesweeper. list bullet points of behavior and flow of the game.
In (current) typical fashion, Bard gave 3 choices of requirements. I arbitrarily picked the first. The cat agreed.
v1
Generate code
Prompt: generate the html and css files based on the requirements. refer to the game as Bomb Grid, not Minesweeper.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bomb Grid</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Bomb Grid</h1>
<div id="game-container">
<div id="game-board"></div>
<div id="game-info">
<span id="timer">00:00</span>
<span id="flagged-mines">0</span>
<button id="restart-button">Restart</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
style.css
body {
font-family: sans-serif;
margin: 0;
padding: 20px;
}
#game-container {
display: flex;
flex-direction: column;
align-items: center;
}
#game-board {
width: 300px;
height: 300px;
border: 1px solid #ccc;
display: grid;
grid-template-columns: repeat(9, 33px);
grid-template-rows: repeat(9, 33px);
}
.tile {
border: 1px solid #ddd;
cursor: pointer;
text-align: center;
line-height: 33px;
}
.tile:hover {
background-color: #eee;
}
.flagged {
background-color: #ff9999;
}
.revealed {
background-color: #ddd;
}
.mine {
background-color: #ff0000;
}
#game-info {
margin-top: 10px;
display: flex;
justify-content: space-between;
}
#timer, #flagged-mines {
font-weight: bold;
}
#restart-button {
padding: 5px 10px;
border: 1px solid #ccc;
cursor: pointer;
}
bombgrid.js
Prompt: generate the javascript files based on the requirements
[The code was incomplete so I had to tell it to continue. The cat was annoyed.]
const BOMB_SYMBOL = "💣";
const FLAG_SYMBOL = "🚩";
const EMPTY_CELL_SYMBOL = "";
// Define game state variables
let boardSize = 9;
let numBombs = 10;
let gameOver = false;
let timer = 0;
let flaggedCells = 0;
let remainingBombs = numBombs;
// Generate the initial game board
const generateBoard = () => {
const board = [];
for (let i = 0; i < boardSize; i++) {
board.push([]);
for (let j = 0; j < boardSize; j++) {
board[i].push({
isBomb: false,
isRevealed: false,
isFlagged: false,
neighboringBombs: 0,
});
}
}
// Place bombs randomly
for (let i = 0; i < numBombs; i++) {
let placedBomb = false;
while (!placedBomb) {
const row = Math.floor(Math.random() * boardSize);
const col = Math.floor(Math.random() * boardSize);
if (!board[row][col].isBomb) {
board[row][col].isBomb = true;
placedBomb = true;
}
}
}
// Calculate neighboring bombs for each cell
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
if (board[i][j].isBomb) continue;
let count = 0;
for (let rowOffset = -1; rowOffset <= 1; rowOffset++) {
for (let colOffset = -1; colOffset <= 1; colOffset++) {
const neighborRow = i + rowOffset;
const neighborCol = j + colOffset;
if (
neighborRow >= 0 &&
neighborRow < boardSize &&
neighborCol >= 0 &&
neighborCol < boardSize &&
board[neighborRow][neighborCol].isBomb
) {
count++;
}
}
}
board[i][j].neighboringBombs = count;
}
}
return board;
};
// Update the UI based on the game state
const updateUI = () => {
const boardElement = document.getElementById("game-board");
boardElement.innerHTML = "";
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
const cell = document.createElement("div");
cell.classList.add("tile");
if (gameOver) {
cell.classList.add("revealed");
if (board[i][j].isBomb) {
cell.textContent = BOMB_SYMBOL;
} else if (board[i][j].neighboringBombs > 0) {
cell.textContent = board[i][j].neighboringBombs;
} else {
cell.textContent = "";
}
} else if (board[i][j].isFlagged) {
cell.textContent = FLAG_SYMBOL;
} else if (board[i][j].isRevealed) {
if (board[i][j].isBomb) {
cell.textContent = BOMB_SYMBOL;
} else if (board[i][j].neighboringBombs > 0) {
cell.textContent = board[i][j].neighboringBombs;
} else {
cell.textContent = EMPTY_CELL_SYMBOL;
}
} else {
cell.textContent = "";
}
cell.addEventListener("click", () => handleClick(i, j));
cell.addEventListener("contextmenu", (e) => {
e.preventDefault();
handleRightClick(i, j);
});
boardElement.appendChild(cell);
}
}
document.getElementById("timer").textContent = formatTime(timer);
document.getElementById("flagged-mines").textContent = flaggedCells;
};
// Handle click events on cells
const handleClick = (row, col) => {
if (gameOver) return;
if (!board[row][col].isFlagged) {
if (board[row][col].isBomb) {
loseGame();
} else {
revealCell(row, col);
checkWinCondition();
}
}
};
// Handle right-click events on cells
const handleRightClick = (row, col) => {
if (gameOver) return;
if (!board[row][col].isRevealed) {
if (board[row][col].isFlagged) {
flaggedCells--;
remainingBombs++;
board[row][col].isFlagged = false;
} else {
flaggedCells++;
remainingBombs--;
board[row][col].isFlagged = true;
}
updateUI();
}
};
// Reveal a cell and its neighbors if they are empty
const revealCell = (row, col) => {
if (board[row][col].isRevealed || board[row][col].isFlagged) return;
board[row][col].isRevealed = true;
if (board[row][col].neighboringBombs === 0) {
for (let rowOffset = -1; rowOffset <= 1; rowOffset++) {
for (let colOffset = -1; colOffset <= 1; colOffset++) {
const neighborRow = row + rowOffset;
const neighborCol = col + colOffset;
if (
neighborRow >= 0 &&
neighborRow < boardSize &&
neighborCol >= 0 &&
neighborCol < boardSize
) {
revealCell(neighborRow, neighborCol);
}
}
}
}
updateUI();
};
// Check if the player has won the game
const checkWinCondition = () => {
let allCellsRevealed = true;
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
if (!board[i][j].isRevealed && !board[i][j].isBomb) {
allCellsRevealed = false;
break;
}
}
}
if (allCellsRevealed) {
winGame();
}
};
// Update timer every second
setInterval(() => {
if (!gameOver) {
timer++;
updateUI();
}
}, 1000);
// Reset the game
const restartGame = () => {
gameOver = false;
timer = 0;
flaggedCells = 0;
remainingBombs = numBombs;
board = generateBoard();
updateUI();
};
// Handle winning the game
const winGame = () => {
gameOver = true;
alert("Congratulations! You won the game.");
};
// Handle losing the game
const loseGame = () => {
gameOver = true;
alert("Game Over! You hit a bomb.");
updateUI();
};
// Start the game
const board = generateBoard();
updateUI();
document.getElementById("restart-button").addEventListener("click", restartGame);
The GitHub repo can be found here.
Bugs
Yeah, about that. The Bard generated code might be great, but since the UI is basically unusable it is hard to tell. Features are missing and who knows where things might go wrong. Sigh.
Ta da!

Random Comments
OMG is this ugly. It is impossible to tell which parts of the grid have not been clicked on so dying is quite easy (unless no one is watching you in which case you will probably be in an indeterminate state. At least, that’s what the cat said).
Also, even though the requirements mentioned a difficulty setting, Bard kinda left it out. Also, the UX just doesn’t look clean.
Next: v2! Let’s turn both the ChatGPT version and the Bard version into iPhone and Android apps! (Yeah, not gonna do that. Things are changing so fast that I am moving on to other pain-producing projects/blog posts).
Is any of this inspiring you? It is me! The concept of using a requirements document as a starting point seems to have promise. I’ll have to try it out on a few more projects.