Task: Minesweeper game




Go: package main

import (
    "bufio"
    "fmt"
    "math"
    "math/rand"
    "os"
    "strconv"
    "strings"
    "time"
)

type cell struct {
    isMine  bool
    display byte 
}

const lMargin = 4

var (
    grid        [][]cell
    mineCount   int
    minesMarked int
    isGameOver  bool
)

var scanner = bufio.NewScanner(os.Stdin)

func makeGrid(n, m int) {
    if n <= 0 || m <= 0 {
        panic("Grid dimensions must be positive.")
    }
    grid = make([][]cell, n)
    for i := 0; i < n; i++ {
        grid[i] = make([]cell, m)
        for j := 0; j < m; j++ {
            grid[i][j].display = '.'
        }
    }
    min := int(math.Round(float64(n*m) * 0.1)) 
    max := int(math.Round(float64(n*m) * 0.2)) 
    mineCount = min + rand.Intn(max-min+1)
    rm := mineCount
    for rm > 0 {
        x, y := rand.Intn(n), rand.Intn(m)
        if !grid[x][y].isMine {
            rm--
            grid[x][y].isMine = true
        }
    }
    minesMarked = 0
    isGameOver = false
}

func displayGrid(isEndOfGame bool) {
    if !isEndOfGame {
        fmt.Println("Grid has", mineCount, "mine(s),", minesMarked, "mine(s) marked.")
    }
    margin := strings.Repeat(" ", lMargin)
    fmt.Print(margin, " ")
    for i := 1; i <= len(grid); i++ {
        fmt.Print(i)
    }
    fmt.Println()
    fmt.Println(margin, strings.Repeat("-", len(grid)))
    for y := 0; y < len(grid[0]); y++ {
        fmt.Printf("%*d:", lMargin, y+1)
        for x := 0; x < len(grid); x++ {
            fmt.Printf("%c", grid[x][y].display)
        }
        fmt.Println()
    }
}

func endGame(msg string) {
    isGameOver = true
    fmt.Println(msg)
    ans := ""
    for ans != "y" && ans != "n" {
        fmt.Print("Another game (y/n)? : ")
        scanner.Scan()
        ans = strings.ToLower(scanner.Text())
    }
    if scanner.Err() != nil || ans == "n" {
        return
    }
    makeGrid(6, 4)
    displayGrid(false)
}

func resign() {
    found := 0
    for y := 0; y < len(grid[0]); y++ {
        for x := 0; x < len(grid); x++ {
            if grid[x][y].isMine {
                if grid[x][y].display == '?' {
                    grid[x][y].display = 'Y'
                    found++
                } else if grid[x][y].display != 'x' {
                    grid[x][y].display = 'N'
                }
            }
        }
    }
    displayGrid(true)
    msg := fmt.Sprint("You found ", found, " out of ", mineCount, " mine(s).")
    endGame(msg)
}

func usage() {
    fmt.Println("h or ? - this help,")
    fmt.Println("c x y  - clear cell (x,y),")
    fmt.Println("m x y  - marks (toggles) cell (x,y),")
    fmt.Println("n      - start a new game,")
    fmt.Println("q      - quit/resign the game,")
    fmt.Println("where x is the (horizontal) column number and y is the (vertical) row number.\n")
}

func markCell(x, y int) {
    if grid[x][y].display == '?' {
        minesMarked--
        grid[x][y].display = '.'
    } else if grid[x][y].display == '.' {
        minesMarked++
        grid[x][y].display = '?'
    }
}

func countAdjMines(x, y int) int {
    count := 0
    for j := y - 1; j <= y+1; j++ {
        if j >= 0 && j < len(grid[0]) {
            for i := x - 1; i <= x+1; i++ {
                if i >= 0 && i < len(grid) {
                    if grid[i][j].isMine {
                        count++
                    }
                }
            }
        }
    }
    return count
}

func clearCell(x, y int) bool {
    if x >= 0 && x < len(grid) && y >= 0 && y < len(grid[0]) {
        if grid[x][y].display == '.' {
            if !grid[x][y].isMine {
                count := countAdjMines(x, y)
                if count > 0 {
                    grid[x][y].display = string(48 + count)[0]
                } else {
                    grid[x][y].display = ' '
                    clearCell(x+1, y)
                    clearCell(x+1, y+1)
                    clearCell(x, y+1)
                    clearCell(x-1, y+1)
                    clearCell(x-1, y)
                    clearCell(x-1, y-1)
                    clearCell(x, y-1)
                    clearCell(x+1, y-1)
                }
            } else {
                grid[x][y].display = 'x'
                fmt.Println("Kaboom! You lost!")
                return false
            }
        }
    }
    return true
}

func testForWin() bool {
    isCleared := false
    if minesMarked == mineCount {
        isCleared = true
        for x := 0; x < len(grid); x++ {
            for y := 0; y < len(grid[0]); y++ {
                if grid[x][y].display == '.' {
                    isCleared = false
                }
            }
        }
    }
    if isCleared {
        fmt.Println("You won!")
    }
    return isCleared
}

func splitAction(action string) (int, int, bool) {
    fields := strings.Fields(action)
    if len(fields) != 3 {
        return 0, 0, false
    }
    x, err := strconv.Atoi(fields[1])
    if err != nil || x < 1 || x > len(grid) {
        return 0, 0, false
    }
    y, err := strconv.Atoi(fields[2])
    if err != nil || y < 1 || y > len(grid[0]) {
        return 0, 0, false
    }
    return x, y, true
}

func main() {
    rand.Seed(time.Now().UnixNano())
    usage()
    makeGrid(6, 4)
    displayGrid(false)
    for !isGameOver {
        fmt.Print("\n>")
        scanner.Scan()
        action := strings.ToLower(scanner.Text())
        if scanner.Err() != nil || len(action) == 0 {
            continue
        }
        switch action[0] {
        case 'h', '?':
            usage()
        case 'n':
            makeGrid(6, 4)
            displayGrid(false)
        case 'c':
            x, y, ok := splitAction(action)
            if !ok {
                continue
            }
            if clearCell(x-1, y-1) {
                displayGrid(false)
                if testForWin() {
                    resign()
                }
            } else {
                resign()
            }
        case 'm':
            x, y, ok := splitAction(action)
            if !ok {
                continue
            }
            markCell(x-1, y-1)
            displayGrid(false)
            if testForWin() {
                resign()
            }
        case 'q':
            resign()
        }
    }
}