Where an old school developer and his imaginary cat create a Minesweeper clone with the help of ChatGPT and Bard
The ChatGPT Edition
Featured image by Stable Diffusion
Hey! Welcome 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
- Generate test code (will remove this in the next installment)
- v2
- Implement/deploy as an Apple app
- Implement/deploy as an Android app
- v3
- Containerize!
How scientific will this be? Not at all. I will try to use the same prompts across all two LLMs, but who knows what that means in reality. My expectation is that both will be either identical of close enough that they should all work the same (after all, the implementation details don’t matter, do they? I think the cat just fainted). In addition, I will change bits and pieces as needed to clean them up. However, the prompts are the ones I used and the output will be minimally changed.
This post will use ChatGPT and will get us through v1. The next post will be for Bard and then we start again with v2.
Why am I doing this? A few reasons:
- How lazy can I be about generating the first iteration of code for a (non-)trivial project?
- How lazy can you be when generating the first iteration of code for a (non-)trivial project?
- Will the code generated by GPT-4 and Bard be identical?
Grab some popcorn (or catnip depending on your species and tastes).
Generate requirements for the game
Prompt: create a requirements document for the implementation of minesweeper. list bullet points of behavior and flow of the game
I changed Minesweeper to Bomb Grid
The above seems reasonable. What it doesn’t tell you is that after you’ve managed to find all the mines you have to click on the grid one last time so that it can tell you that you won (at least that is what happened the first time I successfully played). Probably worthy of a change request, but not a big deal. The cat agrees.
The below code was tested on Eclipse 2023-06. Yeah, I really am old school.
v1
Generate code
Prompt: generate the html and css files based on the requirements
I may have executed the above prompt once or twice…
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minesweeper Game</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="game-container">
<div class="control-panel">
<button id="new-game-button" onclick="startNewGame()">New Game</button>
<select id="difficulty" onchange="changeDifficulty()">
<option value="easy">Easy</option>
<option value="medium">Medium</option>
<option value="hard">Hard</option>
</select>
<div>Time: <span id="time">0</span> seconds</div>
<div>Mines left: <span id="mines-count">10</span></div>
</div>
<div class="grid" id="grid">
<!-- Grid cells will be generated by JavaScript -->
</div>
</div>
<script src="bombgrid.js"></script>
</body>
</html>
styles.css
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.game-container {
border: 1px solid #ddd;
padding: 20px;
background-color: white;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.control-panel {
text-align: center;
margin-bottom: 20px;
}
.control-panel button, .control-panel select {
padding: 5px 10px;
margin: 5px;
border-radius: 5px;
cursor: pointer;
}
.control-panel div {
margin-top: 10px;
font-size: 1em;
}
.grid {
display: grid;
grid-template-columns: repeat(9, 30px); /* Adjust based on difficulty level */
grid-gap: 2px;
}
.grid-cell {
width: 30px;
height: 30px;
background-color: #ccc;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-weight: bold;
border: 1px solid #ddd;
}
.grid-cell.revealed {
background-color: #e0e0e0;
}
.grid-cell.mine {
background-color: red;
}
.grid-cell.flagged {
background-color: orange;
}
/* Responsive Design */
@media (max-width: 600px) {
.grid {
grid-template-columns: repeat(9, 20px);
}
.grid-cell {
width: 20px;
height: 20px;
}
}
bombgrid.js
This code had a bug as described below, but I put in the fix as recommended by ChatGPT and it worked.
let grid, mineCount, timer;
const gridElement = document.getElementById('grid');
const timeElement = document.getElementById('time');
const minesCountElement = document.getElementById('mines-count');
const difficultySelector = document.getElementById('difficulty');
document.addEventListener('DOMContentLoaded', () => {
setupEventListeners();
startNewGame();
});
function setupEventListeners() {
gridElement.addEventListener('click', handleCellClick);
gridElement.addEventListener('contextmenu', handleRightClick);
}
function startNewGame() {
clearInterval(timer);
resetTimer();
const difficulty = difficultySelector.value;
const { rows, cols, mines } = getDifficultySettings(difficulty);
mineCount = mines;
minesCountElement.textContent = mineCount;
grid = createGrid(rows, cols);
placeMines(grid, mineCount);
renderGrid(grid);
}
function getDifficultySettings(difficulty) {
const settings = {
easy: { rows: 9, cols: 9, mines: 10 },
medium: { rows: 16, cols: 16, mines: 40 },
hard: { rows: 24, cols: 24, mines: 99 }
};
return settings[difficulty];
}
function createGrid(rows, cols) {
gridElement.innerHTML = '';
gridElement.style.gridTemplateColumns = `repeat(${cols}, 30px)`; // Set grid columns based on cols
gridElement.style.gridTemplateRows = `repeat(${rows}, 30px)`; // Set grid rows based on rows
const grid = [];
for (let y = 0; y < rows; y++) {
const row = [];
for (let x = 0; x < cols; x++) {
const cellElement = document.createElement('div');
cellElement.classList.add('grid-cell');
cellElement.dataset.x = x;
cellElement.dataset.y = y;
gridElement.appendChild(cellElement);
row.push({ mine: false, revealed: false, flagged: false });
}
grid.push(row);
}
return grid;
}
function placeMines(grid, mineCount) {
let placed = 0;
while (placed < mineCount) {
const x = Math.floor(Math.random() * grid[0].length);
const y = Math.floor(Math.random() * grid.length);
if (!grid[y][x].mine) {
grid[y][x].mine = true;
placed++;
}
}
}
function renderGrid(grid) {
gridElement.innerHTML = '';
grid.forEach((row, y) => {
row.forEach((cell, x) => {
const cellElement = document.createElement('div');
cellElement.classList.add('grid-cell');
cellElement.dataset.x = x;
cellElement.dataset.y = y;
gridElement.appendChild(cellElement);
});
});
}
function handleCellClick(event) {
if (!event.target.classList.contains('grid-cell')) return;
const x = parseInt(event.target.dataset.x);
const y = parseInt(event.target.dataset.y);
revealCell(grid, x, y);
checkWinCondition();
}
function handleRightClick(event) {
event.preventDefault();
if (!event.target.classList.contains('grid-cell')) return;
const x = parseInt(event.target.dataset.x);
const y = parseInt(event.target.dataset.y);
toggleFlag(grid, x, y);
}
function revealCell(grid, x, y) {
const cell = grid[y][x];
if (cell.revealed || cell.flagged) return;
cell.revealed = true;
updateCellDisplay(x, y);
if (cell.mine) {
gameOver(false);
return;
}
// Count adjacent mines
const adjacentMines = countAdjacentMines(grid, x, y);
cell.adjacentMines = adjacentMines;
if (adjacentMines === 0) {
// If there are no adjacent mines, reveal all adjacent cells
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = x + dx, ny = y + dy;
if (nx >= 0 && nx < grid[0].length && ny >= 0 && ny < grid.length) {
revealCell(grid, nx, ny);
}
}
}
} else {
// Update cell display with the number of adjacent mines
updateCellDisplayWithNumber(x, y, adjacentMines);
}
}
function countAdjacentMines(grid, x, y) {
let count = 0;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = x + dx, ny = y + dy;
if (nx >= 0 && nx < grid[0].length && ny >= 0 && ny < grid.length && grid[ny][nx].mine) {
count++;
}
}
}
return count;
}
function updateCellDisplayWithNumber(x, y, number) {
const cellElement = document.querySelector(`.grid-cell[data-x="${x}"][data-y="${y}"]`);
if (number > 0) {
cellElement.textContent = number;
cellElement.classList.add(`num-${number}`);
}
}
function toggleFlag(grid, x, y) {
const cell = grid[y][x];
if (cell.revealed) return;
cell.flagged = !cell.flagged;
updateCellDisplay(x, y);
updateMineCountDisplay();
}
function updateCellDisplay(x, y) {
const cellElement = document.querySelector(`.grid-cell[data-x="${x}"][data-y="${y}"]`);
const cell = grid[y][x];
cellElement.classList.toggle('flagged', cell.flagged);
cellElement.classList.toggle('revealed', cell.revealed);
if (cell.revealed) {
if (cell.mine) {
cellElement.classList.add('mine');
} else if (cell.adjacentMines > 0) {
cellElement.textContent = cell.adjacentMines;
}
}
}
function updateMineCountDisplay() {
const flaggedCount = grid.flat().filter(cell => cell.flagged).length;
minesCountElement.textContent = mineCount - flaggedCount;
}
function checkWinCondition() {
const isWon = grid.every(row => row.every(cell => cell.mine === cell.flagged || (!cell.mine && cell.revealed)));
if (isWon) gameOver(true);
}
function gameOver(won) {
clearInterval(timer);
if (won) {
alert('Congratulations, you won!');
} else {
alert('Game Over. You hit a mine!');
revealAllMines();
}
}
function revealAllMines() {
grid.forEach((row, y) => {
row.forEach((cell, x) => {
if (cell.mine) {
const cellElement = document.querySelector(`.grid-cell[data-x="${x}"][data-y="${y}"]`);
cellElement.classList.add('mine');
}
});
});
}
function resetTimer() {
timeElement.textContent = '0';
timer = setInterval(() => {
timeElement.textContent = parseInt(timeElement.textContent) + 1;
}, 1000);
}
Generate test code
Prompt: write the unit tests.
[ChatGPT proceeds to output various tests in Jest that could not be run on the above code as the above code was not written to be easily tested. So much for that part of the project. All the code would have to be regenerated to be easier to test. Yeah, not doing that.]
The GitHub repo can be found here.
Bugs
The Easy grid was perfect, the Medium and Hard grids were broken (a grid of 9xSomething would appear).
Prompt: createGrid() creates a good grid for easy, but wrong for medium and hard. all the grids have the same number of columns as easy.
New code:
function createGrid(rows, cols) {
gridElement.innerHTML = '';
gridElement.style.gridTemplateColumns = `repeat(${cols}, 30px)`; // Set grid columns based on cols
gridElement.style.gridTemplateRows = `repeat(${rows}, 30px)`; // Set grid rows based on rows
const grid = [];
for (let y = 0; y < rows; y++) {
const row = [];
for (let x = 0; x < cols; x++) {
const cellElement = document.createElement('div');
cellElement.classList.add('grid-cell');
cellElement.dataset.x = x;
cellElement.dataset.y = y;
gridElement.appendChild(cellElement);
row.push({ mine: false, revealed: false, flagged: false });
}
grid.push(row);
}
return grid;
}
Ta da!

Random Comments
All in all, not a bad look. Not great, but I didn’t ask it to create a leading edge UI or improve the existing UI. I wasn’t looking for fancy; I was looking for speed of implementation. Basically, how lazy could I be with the first cut of implementation. Since all of the above took about 15 minute, more or less, I’d say using an LLM that is capable of generating code is a good bet.
Should I have had ChatGPT create a list of the various tests that needed to be run and then generate the actual code? I will have to try that out in a future project. For now, everyone gets a pass.
Next: Bard!