Skip to content

Instantly share code, notes, and snippets.

@amitkgupta
Last active September 3, 2017 18:56
Show Gist options
  • Save amitkgupta/d243718fbc7011116002d477a82a3487 to your computer and use it in GitHub Desktop.
Save amitkgupta/d243718fbc7011116002d477a82a3487 to your computer and use it in GitHub Desktop.
Simplistic text-based minesweeper game
package main
import (
"errors"
"fmt"
"math/rand"
"os"
"os/exec"
)
type cell struct {
mine bool
adjacentMines int
revealed bool
}
type minefield struct {
height, width, numMines int
grid [][]*cell
}
func newMinefield(height, width, numMines int) (*minefield, error) {
if height < 0 || width < 0 || numMines < 0 || numMines > height*width {
return nil, errors.New("invalid minefield parameters.")
}
grid := make([][]*cell, height)
for i := range grid {
grid[i] = make([]*cell, width)
for j := range grid[i] {
grid[i][j] = new(cell)
}
}
mineIndices := rand.Perm(height * width)[:numMines]
for _, idx := range mineIndices {
grid[idx%height][idx/height].mine = true
}
for x := range grid {
for y := range grid[x] {
for i := x - 1; i <= x+1; i++ {
for j := y - 1; j <= y+1; j++ {
if i >= 0 && i < height && j >= 0 && j < width && grid[i][j].mine {
grid[x][y].adjacentMines++
}
}
}
}
}
return &minefield{
height: height,
width: width,
numMines: numMines,
grid: grid,
}, nil
}
func (m *minefield) reveal(x, y int) (bool, error) {
if x < 0 || x >= m.height || y < 0 || y >= m.width {
return false, errors.New("invalid coordinates.")
}
m.grid[x][y].revealed = true
return m.grid[x][y].mine, nil
}
func (m *minefield) clear() bool {
numRevealed := 0
for _, row := range m.grid {
for _, cell := range row {
if cell.revealed {
numRevealed++
}
}
}
return numRevealed == m.height*m.width-m.numMines
}
func (m minefield) render() {
c := exec.Command("clear")
c.Stdout = os.Stdout
c.Run()
for _, row := range m.grid {
for _, cell := range row {
if !cell.revealed {
fmt.Printf("x ")
} else if cell.mine {
fmt.Printf("* ")
} else {
fmt.Printf("%d ", cell.adjacentMines)
}
}
fmt.Println()
}
}
type gameState int
const (
unready gameState = iota
ready
won
lost
)
type minesweeper struct {
field *minefield
state gameState
}
func (m *minesweeper) setup() {
m.state = unready
fmt.Println("enter height.")
var height int
_, err := fmt.Scanf("%d\n", &height)
if err != nil {
fmt.Println("invalid parameters, try again.")
return
}
fmt.Println("enter width.")
var width int
_, err = fmt.Scanf("%d\n", &width)
if err != nil {
fmt.Println("invalid parameters, try again.")
return
}
fmt.Println("enter number of mines.")
var numMines int
_, err = fmt.Scanf("%d\n", &numMines)
if err != nil {
fmt.Println("invalid parameters, try again.")
return
}
field, err := newMinefield(height, width, numMines)
if err != nil {
fmt.Println("invalid parameters, try again.")
return
}
m.field = field
m.state = ready
}
func (m *minesweeper) play() {
for m.state != ready { m.setup() }
for m.state != won && m.state != lost { m.run_one_iteration() }
if m.state == won {
fmt.Println("you win.")
}
if m.state == lost {
fmt.Println("you lose.")
}
}
func (m *minesweeper) run_one_iteration() {
fmt.Println("enter row & column coordinates, e.g. '2 5'.")
var x, y int
_, err := fmt.Scanf("%d %d\n", &x, &y)
if err != nil {
fmt.Println("unparseable input, try again.")
return
}
x--
y--
mine, err := m.field.reveal(x, y)
if err != nil {
fmt.Println("coordinates don't fit in minefield, try again.")
return
}
m.field.render()
if mine {
m.state = lost
}
if m.field.clear() {
m.state = won
}
}
func main() {
new(minesweeper).play()
}
# This represents the minefield.
class Minefield
Cell = Struct.new(:mine, :adjacent_mines, :revealed)
class InvalidCoordinates < StandardError; end
class TooManyMines < StandardError; end
def initialize(height:, width:, num_mines:)
@height = Integer(height)
@width = Integer(width)
@num_mines = Integer(num_mines)
raise TooManyMines if @num_mines > @height * @width
@grid = Array.new(height) do
Array.new(width) { Cell.new(false, 0, false) }
end
@grid.flatten.sample(num_mines).each { |c| c.mine = true }
populate_adjacency_counts
end
def reveal(x, y)
raise InvalidCoordinates unless valid(x, y)
cell = @grid[x][y]
cell.revealed = true
cell.mine
end
def clear?
@grid.map { |row| row.count(&:revealed) }.inject(:+) \
== @height * @width - @num_mines
end
def render
system('clear')
@grid.each do |row|
row.each do |cell|
print 'x ' unless cell.revealed
print '* ' if cell.revealed && cell.mine
print "#{cell.adjacent_mines} " if cell.revealed && !cell.mine
end
puts
end
end
private
def populate_adjacency_counts
@grid.each_index do |x|
@grid[x].each_index do |y|
(x - 1..x + 1).each do |i|
(y - 1..y + 1).each do |j|
@grid[x][y].adjacent_mines += 1 if mine_at?(i, j)
end
end
end
end
end
def mine_at?(i, j)
valid(i, j) && @grid[i][j].mine
end
def valid(i, j)
i >= 0 && i < @height && j >= 0 && j < @width
end
end
# Represents the Minesweeper game.
class Minesweeper
def setup
puts 'enter height.'
height = Integer(gets)
puts 'enter width.'
width = Integer(gets)
puts 'enter number of mines.'
num_mines = Integer(gets)
@field = Minefield.new(height: height, width: width, num_mines: num_mines)
@state = :ready
rescue ArgumentError, Minefield::TooManyMines
puts 'invalid parameters, try again.'
end
def play
setup until @state == :ready
loop do
run_one_iteration
end_game('you lose.') if @state == :lost
end_game('you win.') if @state == :won
end
end
private
def end_game(message)
puts message
exit
end
def run_one_iteration
puts 'enter row & column coordinates, e.g. \'2 5\'.'
x, y = extract_coordinates(gets)
mine = @field.reveal(x, y)
@field.render
@state = :lost if mine
@state = :won if @field.clear?
rescue InvalidInput
puts 'unparseable input, try again.'
rescue Minefield::InvalidCoordinates
puts 'coordinates don\'t fit in minefield, try again.'
end
class InvalidInput < StandardError; end
def extract_coordinates(input)
parts = input.split(' ')
raise InvalidInput if parts.count != 2
return Integer(parts[0]) - 1, Integer(parts[1]) - 1
rescue ArgumentError
raise InvalidInput
end
end
def main
Minesweeper.new.play
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment