snake

package module
v0.0.0-...-b8e146b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 28, 2021 License: MIT Imports: 8 Imported by: 0

README

Snake game in Go

I made a simple Snake game on the terminal to learn Go basics and practice Test Driven Development.

Setup

git clone https://github.com/castagnadaniele/go-snake
cd go-snake
go run cmd/cli/main.go

Controls

Use arrow keys to move the snake.

Press SPACEBAR to start a new game.

Press Q to quit.

Documentation

Index

Constants

View Source
const (
	ErrHeadOutOfBoard             = SnakeErr("snake: head out of board")
	ErrSnakeMustMoveBeforeGrowing = SnakeErr("snake: must move before growing")
	ErrHeadHitBody                = SnakeErr("snake: head hit body")
)
View Source
const BodyBackgroundColor = tcell.ColorGray
View Source
const BodyForegroundColor = tcell.ColorWhite
View Source
const BodyRune = '▮'
View Source
const (
	ErrBoardFull = FoodError("snake: food: board full, can not generate food coordinate")
)
View Source
const FoodBackgroundColor = tcell.ColorBlack
View Source
const FoodForegroundColor = tcell.ColorRed
View Source
const FoodRune = '◆'
View Source
const LoseMessage = "Game lost! Press SPACEBAR to start a new game or press Q to quit..."
View Source
const WinMessage = "Game won! Press SPACEBAR to start a new game or press Q to quit..."

Variables

This section is empty.

Functions

func AssertCoordinate

func AssertCoordinate(t testing.TB, got Coordinate, want Coordinate)

AssertCoordinate asserts that got coordinate and want coordiante are equal.

func AssertCoordinates

func AssertCoordinates(t testing.TB, got []Coordinate, want []Coordinate)

AssertCoordinates asserts that got and want coordinates are deep equal.

func AssertDirection

func AssertDirection(t testing.TB, got Direction, want Direction)

AssertDirection asserts that the direction I got is the direction I want.

func AssertError

func AssertError(t testing.TB, got error, want error)

AssertError asserts that got is the error I want.

func AssertNoError

func AssertNoError(t testing.TB, got error)

AssertNoError asserts that got is nil.

func Has

func Has(d, flag Direction) bool

Has returns true if flag has d bit turned on

func NewSnakeInvalidMoveErr

func NewSnakeInvalidMoveErr(face, move Direction) error

func WaitAndReceiveGameChannels

func WaitAndReceiveGameChannels(t testing.TB, g *Game) (snakeCoordinate []Coordinate, gameResult *bool, foodCoordinate *Coordinate)

WaitAndReceiveGameChannels returns a ([]Coordinate, *bool, *Coordinate) tuple with snake coordinates or game result or food coordinate. It waits to receive values from the game exposed receive channels.

Types

type Cloak

type Cloak interface {
	Start(d time.Duration)
	Tick() <-chan time.Time
	Stop()
}

Cloak is the interface that wraps a ticker.

Start starts the ticker.

Tick wraps the ticker receive channel.

Stop stops the ticker.

type Controller

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

Controller struct coordinates a snake game with a view.

func NewController

func NewController(game GameDirector, view ViewHandler) *Controller

NewController returns a Controller pointer initializing the game and the view.

func (*Controller) Start

func (c *Controller) Start(d time.Duration)

Start starts the controller internal game, then loops and waits on the view direction channel, on the game snake coordinates receiver channel, on the game food coordinate receiver channel and on the game result receiver channel. When it receives a new direction from the view it sends it to the game. When it receives new snake or food coordinates it refreshes the view screen. When it receives a game result it display win or lose accordingly to the result.

Should be used as a go routine.

func (*Controller) WaitForQuitSignal

func (c *Controller) WaitForQuitSignal() <-chan struct{}

WaitForQuitSignal returns an empty struct receiver channel on which the controller sends when it has received a quit signal from view. After calling Controller.Start on the main go routine the consumer should wait on this channel.

type Coordinate

type Coordinate struct {
	X int
	Y int
}

Coordinate implements board coordinates.

type DefaultCloak

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

Ticker wrapper implementation

func NewCloak

func NewCloak() *DefaultCloak

NewCloak returns a pointer to DefaultCloak. Start must be called in order to receive ticks.

NewCloak initializes an internal sync.WaitGroup which allows to start the internal ticker on a later time.

func (*DefaultCloak) Start

func (c *DefaultCloak) Start(d time.Duration)

Start initializes the internal time.Ticker releasing the internal sync.WaitGroup.

func (*DefaultCloak) Stop

func (c *DefaultCloak) Stop()

Stop stops the internal ticker after waiting for the cloak to start.

func (*DefaultCloak) Tick

func (c *DefaultCloak) Tick() <-chan time.Time

Tick returns the internal time.Ticker.C receive channel waiting for the cloak start.

type Direction

type Direction int8
const (
	Up Direction = 1 << iota
	Down
	Left
	Right
)

func (Direction) String

func (d Direction) String() string

type Food

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

Food struct which implements snake food coordinate random generation.

func NewFood

func NewFood(width, height int) *Food

NewFood returns a pointer to Food and seeds the generator with current time

func (*Food) Generate

func (f *Food) Generate(c []Coordinate) (Coordinate, error)

Generate returns a random coordinate for the food which is not in c Coordinates. If c length is equal to all the available cells in the board it returns ErrBoardFull.

type FoodError

type FoodError string

FoodError type defines food errors

func (FoodError) Error

func (e FoodError) Error() string

type FoodGenerator

type FoodGenerator interface {
	// Generate should return the coordinate of the next food
	// to spawn on the board (this new coordinate should not be contained
	// in c), or should return error if the board is full.
	Generate(c []Coordinate) (Coordinate, error)
}

FoodGenerator interface describes a food producer.

Generate should return the coordinate of the next food to spawn on the board (this new coordinate should not be contained in c), or should return error if the board is full.

type FoodStub

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

FoodStub stubs a food generator

func (*FoodStub) Generate

func (s *FoodStub) Generate(c []Coordinate) (Coordinate, error)

Generate returns the first food stub value from FoodStub internal array, then pops it from the array.

func (*FoodStub) Seed

func (s *FoodStub) Seed(c []FoodStubValue)

Seed loads the c food values into FoodStub internal array.

type FoodStubValue

type FoodStubValue struct {
	Coord Coordinate
	Err   error
}

FoodStubValue stores the coordinate and the error returned from FoodStub Generate.

type Game

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

Game coordinates the snake behaviour with the cloak ticks.

func NewGame

func NewGame(snake *Snake, cloak Cloak, foodProducer FoodGenerator) *Game

NewGame returns a pointer to Game, which handles snake methods on cloak ticks

func (*Game) Quit

func (g *Game) Quit()

Quit stops the game internal go routine, then closes all the internal channels.

func (*Game) ReceiveFoodCoordinate

func (g *Game) ReceiveFoodCoordinate() <-chan Coordinate

ReceiveFoodCoordinate returns the food coordinate receive channel.

func (*Game) ReceiveGameResult

func (g *Game) ReceiveGameResult() <-chan bool

ReceiveGameResult returns the game result receive channel.

func (*Game) ReceiveSnakeCoordinates

func (g *Game) ReceiveSnakeCoordinates() <-chan []Coordinate

ReceiveSnakeCoordinates returns the snake coordinates receive channel.

func (*Game) Restart

func (g *Game) Restart(d time.Duration)

Restart stops the game internal go routine, reset the snake and starts a new game event loop internal go routine.

func (*Game) SendMove

func (g *Game) SendMove(d Direction)

SendMove sends d Direction to the internal Direction channel which will be pooled inside the Start go routine to change snake direction

func (*Game) Start

func (g *Game) Start(d time.Duration)

Start starts cloak to tick every d time.Duration, then starts a go routine to loop on the ticker events moving the snake and sending the new coordinates on the internal channel

type GameDirector

type GameDirector interface {
	// Start should exec a new snake move after an interval, should handle
	// snake collision with food, should handle new food generation and should
	// change snake face direction on user input.
	//
	// Start should run a go routine which does the above operations.
	Start(d time.Duration)
	// SendMove should send the new direction in an internal channel.
	SendMove(d Direction)
	// ReceiveSnakeCoordinates should expose a receiver channel which emits
	// the new snake coordinates after each interval.
	ReceiveSnakeCoordinates() <-chan []Coordinate
	// ReceiveFoodCoordinate should expose a receiver channel which emits
	// the new food coordinate after the snake eats the food.
	ReceiveFoodCoordinate() <-chan Coordinate
	// ReceiveGameResult should expose a receiver channel which emits
	// when the game is won or is lost.
	ReceiveGameResult() <-chan bool
	// Restart should stop the game internal go routine, should reset the snake
	// and should start a new internal go routine event loop
	Restart(d time.Duration)
	// Quit should stop the game internal go routine and then release resources.
	Quit()
}

GameDirector interface defines how to coordinate the snake and food interaction in a game.

type Snake

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

Snake is the struct which implements the snake behaviour.

func NewSnake

func NewSnake(width, height int) *Snake

NewSnake returns a new Snake struct pointer initializing snake coordinates and setting width and height of the board and snake length 3.

func NewSnakeOfLength

func NewSnakeOfLength(width, height, length int) *Snake

NewSnakeOfLength returns a new Snake struct pointer initializing snake coordinates and setting width and height of the board and snake length.

func (*Snake) Face

func (s *Snake) Face() Direction

Face returns where the snake head is facing.

func (*Snake) GetCoordinates

func (s *Snake) GetCoordinates() []Coordinate

GetCoordinates returns the snake internal coordinates.

func (*Snake) Grow

func (s *Snake) Grow() error

Grow grows snake tail appending the last cutted tail. If snake did not move before growing, it returns ErrSnakeMustMoveBeforeGrowing error.

func (*Snake) IsValidMove

func (s *Snake) IsValidMove(d Direction) bool

IsValidMove tests if direction is valid for next snake move.

func (*Snake) Move

func (s *Snake) Move(d Direction) error

Move moves the snake head towards direction d, cutting tail coordinate and appending new coordinate on head. Returns ErrHeadOutOfBoard error when head would move out of the board. Returns SnakeInvalidMoveErr error if direction d is inconsistent with face direction. Returns ErrHeadHitBody error if the head would move above a body coordinate.

func (*Snake) Reset

func (s *Snake) Reset()

Reset resets snake internal coordinates, face direction and last tail coordinate pointer.

type SnakeErr

type SnakeErr string

SnakeErr type defines snake errors

func (SnakeErr) Error

func (e SnakeErr) Error() string

type SnakeInvalidMoveErr

type SnakeInvalidMoveErr struct {
	Face Direction
	Move Direction
}

SnakeInvalidMoveErr implements invalid move direction error

func (SnakeInvalidMoveErr) Error

func (e SnakeInvalidMoveErr) Error() string

type View

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

View struct which prints the snake game elements on terminal.

func NewView

func NewView(screen tcell.Screen) *View

NewView returns a View struct pointer setting the screen, starting the screen events loop channel in a go routine and starts polling the screen events channel for directions in another go routine.

func (*View) DisplayLose

func (v *View) DisplayLose()

DisplayLose clears the screen and displays a lose message.

func (*View) DisplayWin

func (v *View) DisplayWin()

DisplayWin clears the screen and displays a win message.

func (*View) ReceiveDirection

func (v *View) ReceiveDirection() <-chan Direction

ReceiveDirection returns a Direction receiver channel which will be fed when the screen will receive directional key events.

func (*View) ReceiveNewGameSignal

func (v *View) ReceiveNewGameSignal() <-chan struct{}

ReceiveNewGameSignal returns an empty struct receiver channel which will signal when the user presses SPACEBAR to request a game restart.

func (*View) ReceiveQuitSignal

func (v *View) ReceiveQuitSignal() <-chan struct{}

ReceiveQuitSignal returns an empty struct receiver channel which will signal when the user presses the Q button to request to exit from the game.

func (*View) Refresh

func (v *View) Refresh(snakeCoordinates *[]Coordinate, foodCoordinate *Coordinate)

Refresh clears the screen, then prints the snake body on the snake coordinates and the food on the food coordinates. The snake body will be printed overwriting the food, if their coordinates overlap. It will not print the respective coordinates if the snake or the food coordinates are nil.

func (*View) Release

func (v *View) Release()

Release releases the underlying screen resources.

type ViewHandler

type ViewHandler interface {
	// Refresh should receive the snake and food coordinates and should display them.
	Refresh(snakeCoordinates *[]Coordinate, foodCoordinate *Coordinate)
	// ReceiveDirection should return a Direction receiver channel on which the ViewHandler
	// should send new change direction input from the user.
	ReceiveDirection() <-chan Direction
	// DisplayWin should display a win screen.
	DisplayWin()
	// DisplayLose should display a lose screen.
	DisplayLose()
	// ReceiveNewGameSignal should return an empty struct receiver channel on which
	// the ViewHandler should send new game input from the user.
	ReceiveNewGameSignal() <-chan struct{}
	// ReceiveQuitSignal should returna an empty struct receiver channel on which
	// the ViewHandler should send quit game input from the user.
	ReceiveQuitSignal() <-chan struct{}
}

ViewHandler interface defines how a view should handle screen refresh and how should expose snake's change direction input.

Directories

Path Synopsis
cmd
cli

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳