README

Minesweeper API

GoDoc
Build Status codecov Go Report Card Codacy Badge Maintainability

The Minesweeper API is the Application Programming Interface of the minesweeper game. The game's logic is embedded in this API itself with zero third-party libraries involved.

Although, there is much no use of this game as a library, you may find this useful for educational purposes because it contains the implementation of the minesweeper game backed with coding vetting frameworks and test-driven development. The idea of this project is to provide you how this project integrates with DevOps, how it can be consumed with REST API, and how the code can achieve high code quality.

The best part: this library contains 100% of all source codes written in Go.



Install

go get -u github.com/rrborja/minesweeper

Usage

Creating the Instance

Create the instance of the game by calling minesweeper.NewGame() method. The method can accept an arbitrary number of arguments but only one argument can be processed and the rest are ignored. The method accepts the Grid argument to set the board's size. For example, Grid{Width: 15, Height: 10}

Setting the Grid:
If the Grid is not provided in the NewGame() arguments, you must explicitly provide the board's Grid by calling SetGrid() of the game's instance.

NewGame() returns two values: the instance itself and the event handler. The instance is the instance of the Minesweeper interface that has methods as use cases to solve a minesweeper game. The event handler is a buffered channel that you can use to create a separate goroutine and listen for game events. Such events are minesweeper.Win and minesweeper.Lose.

Setting the Difficulty

Set the difficulty of the game by calling SetDifficulty() of the game's instance. Values accepted by this method as arguments are minesweeper.Easy, minesweeper.Medium and minesweeper.Hard.

Start the Game

Call the Play() of the game's instance to generate the location of mines and to start the game. You may encounter errors such as UnspecifiedGridError if no grid is set, UnspecifiedDifficultyError if no difficulty is set, and GameAlreadyStartedError if the Play() has already been called twice or more.

Visit a Cell

Call the Visit() of the game's instance to visit the cell. The method will accept two arguments of the type int which are represented by the xy-coordinate of the game's board to which the location of the cell in the board is to be visited.

The method will return two values:

  • a slice of cells that has been visited by the player and by the game
  • the error ExplodedError indicating whether a visited cell reveals a mine.

When you receive the ExplodedError error, the game ends. Calling Visit() after a game ended may produce a nil pointer dereference panic. It does happen due to that the game is over, the game's instance is automatically garbage collected.

The slice being returned may contain multiple values. If it's a single element slice, that means the visited cell is a warning number indicating the number of mines neighbored to the visited cell. If it's a multiple element slice, that means the visited cell is an unknown number and neighboring cells are recursively visited until any numbered cell is reached. The first element of the slice will always be the original player's visited cell.

Flag a cell

Call Flag() of the game's instance to mark an unprobed cell. Doing this will prevent the Visit() method from visiting the marked cell.

Pro tip:

  • If you visit an already visited numbered cell again and the number of neighboring cells that have been flagged equals to the number of the visited cell, the game will automatically visit all unprobed neighbored cells by returning the slice containing those cells. Just make the players ensure that they have correctly marked the cells deduced that they have mines, otherwise, the game will end if the cell is incorrectly marked.

Example

package main

import (
    "fmt"
    "os"

    "github.com/rrborja/minesweeper"
)

func Listen(event Event) {
    select event {
    case <- minesweeper.Win:
        fmt.Println("You won the game!")
    case <- minesweeper.Lose:
        fmt.Println("Game over!")
    default:
        panic("Unexpected event")
    }
    os.Exit(0)
}

func main() {
    game, event := minesweeper.NewGame(Grid{Width: 15, Height: 10})
    game.SetDifficulty(Easy)
    game.Play()

    go Listen(event)

    reader := bufio.NewReader(os.Stdin)
    for {
        var x, y int
        fmt.Println("Enter the X-coordinate to visit a cell: ")
        fmt.Scan(&x)
        fmt.Println("Enter the Y-coordinate to visit a cell: ")
        fmt.Scan(&y)

        visitedCells, err := game.Visit(x, y)
        if err != nil {
            fmt.Printf("Mine is revealed at %v:%v", x, y)
        }

        for _, cell := range visitedCells {
            fmt.Print("[%v:%v] ", cell.X(), cell.Y())
        }
    }
}

TODO

  1. Print the game's visual state of the board in console
  2. Provide a way to allow the game's API to be used for REST API

License

This project Minesweeper API is released under the GNU General Public License, either version 2 of the License, or (at your option) any later version.

GPG Verified

Commits are signed by verified PGPs

Contributing

Please see the CONTRIBUTING.md file for details.

Expand ▾ Collapse ▴

Documentation

Index

Constants

View Source
const (

	// Win is the game's event. This will trigger whenever all non-mine
	// cells are visited.
	Win eventType

	// Lose is the game's event. This will trigger whenever a cell
	// containing the mine is visited.
	Lose
)

Variables

This section is empty.

Functions

func Flag

func Flag(x int, y int)

    Flag marks the cell, according to the coordinates supplied in the method argument, as flagged. When a particular cell is flagged, the cell in question will prevent from being handled by the game when the Visit(int, int) method with the same coordinate of the cell in question is called.

    func NewGame

    func NewGame(grid ...Grid) (Minesweeper, Event)

      NewGame creates a separate minesweeper instance. Unlike minesweeper.New, this function creates a non-singleton instance. Functions of this package such as Visit will become the methods of this instance.

      func Play

      func Play() error

        Play allows the game to setup all the mines in place randomly. The placement is non-deterministic since the implementation uses the "crypto/rand" package.

        An error will return when this method is called twice or more.

        More importantly, Grid size and Difficulty must be specified, otherwise, you will encounter an UnspecifiedGridError and UnspecifiedDifficultyError, respectively.

        func SetDifficulty

        func SetDifficulty(difficulty Difficulty) error

          SetDifficulty sets the difficulty of the game as a basis of the number of mines. An error will return if this method is being called when a game is already being played or better yet, the Play() method has already been called.

          func SetGrid

          func SetGrid(width int, height int) error

            SetGrid sets the board's size. You can't change the board's size once the Play() method has been called, otherwise, a GameAlreadyStartedError will return.

            Nothing will return if the setting the board's size is successful.

            Whenever the game ends, this instance will be garbage collected. Calling any exported methods when the game ended may result in nil pointer dereference. Creating a new setup of the game is ideal.

            Types

            type Block

            type Block struct {
            	Node
            	Value int
            	// contains filtered or unexported fields
            }

              Block stores the information of a particular minesweeper cell. It has the value itself, type of the value in the cell, and the location in the grid. It also reports when a cell is visited or flagged.

              func Visit

              func Visit(x int, y int) ([]Block, error)

                Visit visits a particular cell according to the xy-coordinates of the argument supplied by this method being called. There are three scenarios that depend to the generated configuration of the game:

                A warning number: We may expect a number when visiting a particular cell. The returned []Block will return an array of revealed cells. Accordingly, this type of scenario will always return an array of one Block element.

                No mine but no warning number: When encountered, a visited cell will recursively visit all neighboring or adjacent cells because if a visited cells contains no warning number, then there are no neighboring mines per se. Thus, visiting all the neighboring cells are safe to be visited and continues to visit any probed blank cell recursively until no blank cell is left to be probed. The returned []Block will return an array of all probed cells with the first element as the original visited cell.

                A mine: When encountered, the game ends revealing all mines by the returned []Block array. It will also return an ExplodedError as a second return value. Furthermore, when event handling is supported, a Lose event will enqueue to the game's even channel buffer. You can remember this buffer when you originally called the NewGame(...Grid) function and store the second returned value as your event listener in a separate goroutine.

                There is also one trivial scenario. Visiting a block that is flagged (i.e. originally called the Flag(int, int) method as a result) will have no effect on the state of the game. If a flagged cell with a suspected mine is visited, it will prevent it from being visited and the game would likely treat the called method as if nothing was called at all.

                The last Visit() method call with the last non-mine cell will trigger the Win event. The game ends eventually.

                func (*Block) Flagged

                func (block *Block) Flagged() bool

                  Flagged responds if a cell is visited or not

                  func (Block) String

                  func (block Block) String() string

                  func (*Block) Visited

                  func (block *Block) Visited() bool

                    Visited responds if a cell is visited or not

                    func (Block) X

                    func (block Block) X() int

                      X returns the X coordinate of the block in the minesweeper grid

                      func (Block) Y

                      func (block Block) Y() int

                        Y returns the Y coordinate of the block in the minesweeper grid

                        type Difficulty

                        type Difficulty uint8

                          Difficulty is the state of the game's difficulty. Values of this type are minesweeper.Easy, minesweeper.Medium and minesweeper.Hard

                          const (
                          
                          	// Easy is the difficulty of the game. The amount of mines present
                          	// in the game with this difficulty would result to 10% of the
                          	// total area of the board's size.
                          	Easy Difficulty
                          
                          	// Medium is the difficulty of the game. The amount of mines
                          	// present in the game with this difficulty would result to 20%
                          	// of the total area of the board's size.
                          	Medium
                          
                          	// Hard is the difficulty of the game. The amount of mines present
                          	// in the game with this difficulty would result to 50% of the
                          	// total area of the board's size.
                          	Hard
                          )

                          type Event

                          type Event chan eventType

                            Event is a channel type used to receive the game's realtime event. Particular values accepted to this channel are minesweeper.Win and minesweeper.Lose

                            func New

                            func New(grid ...Grid) Event

                              New creates a new minesweeper environment. Note that this only creates the minesweeper instance without the necessary settings such as the game's difficulty and the game's board size and calling this method will not start the game.

                              This method returns the minesweeper interface and the event handler. The event handler allows you to setup event listeners in such situation when a game seamlessly triggers a win or lose event. The event handler is a buffered channel that, when used, allows you to setup a particular goroutine to independently listen for these events.

                              As for this method's argument, this method appears to accept an arbitrary number of trailing arguments of type Grid. It can only, however, handle only one Grid instance and the rest of the arguments will be ignored. Although supplying this Grid is also optional, you may encounter an UnspecifiedGridError panic when calling the Play() method if the Grid is not supplied. You may explicitly supply it by calling the SetGrid(int, int) method.

                              type ExplodedError

                              type ExplodedError struct {
                              	// contains filtered or unexported fields
                              }

                                ExplodedError is the error type used to handle a situation when a mine is visited

                                func (ExplodedError) Error

                                func (Exploded ExplodedError) Error() string

                                type GameAlreadyStartedError

                                type GameAlreadyStartedError struct{}

                                  GameAlreadyStartedError is the error type used to handle errors when attempting to restart the game, changing the Grid size when the game is started, or changing the difficulty while the game is started.

                                  func (GameAlreadyStartedError) Error

                                  func (GameAlreadyStarted GameAlreadyStartedError) Error() string

                                  type Grid

                                  type Grid struct{ Width, Height int }

                                    Grid is the game's board size defined by its Grid.Width and Grid.Height

                                    type Minesweeper

                                    type Minesweeper interface {
                                    	SetGrid(int, int) error
                                    
                                    	SetDifficulty(Difficulty) error
                                    
                                    	Play() error
                                    
                                    	Flag(int, int)
                                    
                                    	Visit(int, int) ([]Block, error)
                                    }

                                      Minesweeper is the main point of consumption and manipulation of the Minesweeper's game state. Methods provided by this interface are common use cases to solve a minesweeper game.

                                      Any instance derived by this interface is compatible for type casting to the rendering.Tracker and visited.StoryTeller interfaces.

                                      type Node

                                      type Node uint8

                                        Node is the type of the cell's value. Values of this type are minesweeper.Unknown, minesweeper.Bomb and minesweeper.Number

                                        const (
                                        	// Unknown is the type of the value contained in a minesweeper
                                        	// cell. It has no value which means no mines are neighbored in
                                        	// the cell.
                                        	Unknown Node = 1 << iota
                                        
                                        	// Bomb is the type of the value contained in a minesweeper cell.
                                        	// This is the equivalent of a mine in the game.
                                        	Bomb
                                        
                                        	// Number is the type of the value contained in a minesweeper
                                        	// cell. The number indicated the number of mines neighbored in
                                        	// the cell.
                                        	Number
                                        )

                                        type UnspecifiedDifficultyError

                                        type UnspecifiedDifficultyError struct{}

                                          UnspecifiedDifficultyError is the error type used to handle errors when the Play() method is called but the Difficulty is not set in the game.

                                          func (UnspecifiedDifficultyError) Error

                                          func (UnspecifiedDifficulty UnspecifiedDifficultyError) Error() string

                                          type UnspecifiedGridError

                                          type UnspecifiedGridError struct{}

                                            UnspecifiedGridError is the error type used to handle errors when the Play() method is called but the Grid size is not set in the game.

                                            func (UnspecifiedGridError) Error

                                            func (UnspecifiedGrid UnspecifiedGridError) Error() string

                                            Directories

                                            Path Synopsis