Skip to content

Instantly share code, notes, and snippets.

@radanskoric
Last active May 19, 2025 12:26
Show Gist options
  • Save radanskoric/3609d411cbc035eaaaaf314eb6c4cd9a to your computer and use it in GitHub Desktop.
Save radanskoric/3609d411cbc035eaaaaf314eb6c4cd9a to your computer and use it in GitHub Desktop.
Test run of a simple coding agent with the prompt to create an ASCII minesweeper.
#!/usr/bin/env ruby
class Minesweeper
MINE = '*'
HIDDEN = '#'
EMPTY = '.'
attr_reader :board, :visible_board, :width, :height, :mines_count, :game_over, :game_won
def initialize(width = 9, height = 9, mines_count = 10)
@width = width
@height = height
@mines_count = mines_count
@game_over = false
@game_won = false
@mines_placed = false
# Initialize the boards
@board = Array.new(height) { Array.new(width, EMPTY) }
@visible_board = Array.new(height) { Array.new(width, HIDDEN) }
# Place mines on the board
place_mines
end
def place_mines
mines_placed = 0
while mines_placed < @mines_count
x = rand(@width)
y = rand(@height)
if @board[y][x] != MINE
@board[y][x] = MINE
mines_placed += 1
end
end
# Calculate numbers for adjacent cells
calculate_numbers
@mines_placed = true
end
def calculate_numbers
@height.times do |y|
@width.times do |x|
if @board[y][x] != MINE
mines_around = count_adjacent_mines(x, y)
@board[y][x] = mines_around.to_s if mines_around > 0
end
end
end
end
def count_adjacent_mines(x, y)
count = 0
# Check all 8 adjacent cells
(-1..1).each do |dy|
(-1..1).each do |dx|
next if dx == 0 && dy == 0 # Skip the cell itself
new_x = x + dx
new_y = y + dy
# Check if the cell is within bounds
if new_x >= 0 && new_x < @width && new_y >= 0 && new_y < @height
count += 1 if @board[new_y][new_x] == MINE
end
end
end
count
end
def uncover(x, y)
return if @game_over || @game_won
return if x < 0 || x >= @width || y < 0 || y >= @height
return if @visible_board[y][x] != HIDDEN
# Uncover the cell
@visible_board[y][x] = @board[y][x]
# Check if it's a mine
if @board[y][x] == MINE
@game_over = true
reveal_all_mines
return
end
# If empty, recursively uncover adjacent cells
if @board[y][x] == EMPTY
(-1..1).each do |dy|
(-1..1).each do |dx|
uncover(x + dx, y + dy)
end
end
end
# Check if the game is won
check_win
end
def reveal_all_mines
@height.times do |y|
@width.times do |x|
@visible_board[y][x] = @board[y][x] if @board[y][x] == MINE
end
end
end
def check_win
hidden_count = 0
@height.times do |y|
@width.times do |x|
hidden_count += 1 if @visible_board[y][x] == HIDDEN
end
end
if hidden_count == @mines_count
@game_won = true
reveal_all_mines
end
end
def display
puts "\nMinesweeper Board:"
puts " " + (0...@width).map { |i| i.to_s }.join(" ")
@height.times do |y|
print "#{y} "
@width.times do |x|
print "#{@visible_board[y][x]} "
end
puts
end
if @game_over
puts "Game Over! You hit a mine."
elsif @game_won
puts "Congratulations! You won!"
end
end
end
# Game runner
if __FILE__ == $PROGRAM_NAME
puts "Welcome to Minesweeper!"
puts "Enter the board size (width height):"
width, height = gets.chomp.split.map(&:to_i)
width = 9 if width.nil? || width < 3
height = 9 if height.nil? || height < 3
puts "Enter the number of mines:"
mines = gets.chomp.to_i
# Ensure mines don't exceed half the board size
max_mines = (width * height) / 2
mines = [mines, max_mines].min
mines = 10 if mines < 1
game = Minesweeper.new(width, height, mines)
until game.game_over || game.game_won
game.display
puts "Enter coordinates to uncover (x y):"
input = gets.chomp
# Allow the player to exit
break if input.downcase == "exit" || input.downcase == "quit"
x, y = input.split.map(&:to_i)
if x.nil? || y.nil? || x < 0 || x >= game.width || y < 0 || y >= game.height
puts "Invalid coordinates. Try again."
next
end
game.uncover(x, y)
end
# Final board display
game.display
end
#!/usr/bin/env ruby
require 'minitest/autorun'
require_relative 'minesweeper'
class MinesweeperTest < Minitest::Test
def setup
# For testing, we'll use a deterministic board rather than random mines
@game = Minesweeper.new(5, 5, 5)
# Override the random mine placement with a known pattern
@game.instance_variable_set(:@board, [
['.', '.', '.', '.', '.'],
['.', '*', '2', '1', '.'],
['.', '2', '*', '1', '.'],
['.', '1', '1', '1', '.'],
['.', '.', '.', '.', '*']
])
# Reset visible board
@game.instance_variable_set(:@visible_board, Array.new(5) { Array.new(5, Minesweeper::HIDDEN) })
end
def test_initialization
game = Minesweeper.new
assert_equal 9, game.width
assert_equal 9, game.height
assert_equal 10, game.mines_count
assert_equal false, game.game_over
assert_equal false, game.game_won
end
def test_uncover_empty_cell
@game.uncover(0, 0)
assert_equal '.', @game.visible_board[0][0]
# Uncovering an empty cell should also uncover adjacent cells
assert_equal '.', @game.visible_board[0][1]
assert_equal '.', @game.visible_board[1][0]
end
def test_uncover_numbered_cell
@game.uncover(2, 1)
assert_equal '2', @game.visible_board[1][2]
# Adjacent cells should not be automatically uncovered
assert_equal Minesweeper::HIDDEN, @game.visible_board[0][2]
end
def test_uncover_mine
@game.uncover(1, 1)
assert_equal Minesweeper::MINE, @game.visible_board[1][1]
assert_equal true, @game.game_over
assert_equal false, @game.game_won
# All mines should be revealed
assert_equal Minesweeper::MINE, @game.visible_board[2][2]
assert_equal Minesweeper::MINE, @game.visible_board[4][4]
end
def test_win_condition
# Uncover all non-mine cells to win
@game.uncover(0, 0) # This should reveal many cells due to flood fill
# Manually uncover remaining non-mine cells
@game.uncover(3, 1)
@game.uncover(3, 2)
# Check if game is won
assert_equal true, @game.game_won
assert_equal false, @game.game_over
# All mines should be revealed when the game is won
assert_equal Minesweeper::MINE, @game.visible_board[1][1]
assert_equal Minesweeper::MINE, @game.visible_board[2][2]
assert_equal Minesweeper::MINE, @game.visible_board[4][4]
end
def test_count_adjacent_mines
assert_equal 1, @game.count_adjacent_mines(0, 1)
assert_equal 2, @game.count_adjacent_mines(1, 2)
assert_equal 0, @game.count_adjacent_mines(0, 0)
end
def test_invalid_coordinates
# Out of bounds coordinates should be ignored
@game.uncover(-1, 0)
@game.uncover(0, -1)
@game.uncover(5, 0)
@game.uncover(0, 5)
# Board should remain unchanged
@game.visible_board.each do |row|
row.each do |cell|
assert_equal Minesweeper::HIDDEN, cell
end
end
end
def test_cannot_uncover_after_game_over
@game.uncover(1, 1) # Hit a mine
assert_equal true, @game.game_over
# Try to uncover another cell
@game.uncover(0, 0)
# The cell should remain hidden as game is over
assert_equal Minesweeper::MINE, @game.visible_board[1][1] # Mine is visible
# Other cells remain as they were after game over
end
end

Implement a CLI game of Minesweeper using Ruby. Make sure to cover it with tests. The game should work by generating a minesweeper board, printing it and asking the user for the coordinates of the cell they wish to uncover. The board should be printed using the following notation: # for a still hidden field, 1-8 for a field with that many mines in the neighborhood, . for a field with no neighbors with mines and * for a mine. The program should correctly detect losing the game by hitting a mine and winning the game by uncovering all the fields without mines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment