Building a Snake Game With Fyne and Go.
Tags: go, fyne
Published: 2022-01-16 17:09:43

1. How to install GO.
2. How to install fyne.
3. Drawing Snake and Fruit on Fyne Canvas. 
4. Adding a timer to move the snake. 
5. Using Keys to Control direction.

** 1 How to install Go**
The GO can be downloaded and installed from the official Golang website [https://go.dev/doc/install](https://go.dev/doc/install) 
In this tutorial i will be using a windows environment. I f you are on Mac or Linux please follow the tutorial given on the official   [golang docs ](https://go.dev/doc/install)  

**2  How to install Fyne**
2.1.  For those on Windows environments download and install the [GCC compiler](https://jmeubank.github.io/tdm-gcc/download/). If you are running on a different OS you need to follow this guide [Fyne Getting Started](https://developer.fyne.io/started/).

2.2  Using command Line create directory myapp
```
mkdir myapp && cd myapp
```
2.3.  In myapp directory Run the following command and replace MODULE_NAME with your preferred module name (this should be called in a new folder specific for your application).
```
go mod init MODULE_NAME
```
2.4. You now need to download the Fyne module. This will be done using the following command:
```
go get fyne.io/fyne/v2
```
2.5.  To finish your module’s set up, you now need to tidy the module file to correctly reference Fyne as a dependency.
```
 go mod tidy
```
**3  Drawing Snake and Fruit on Fyne Canvas**
3.1.  Firstly, we will create a setup function that will build the game screen. We will call
this function **setupGame** and create  list that we we populate.

3.2  By iterating through a loop of **10** elements (i from 0 to 9) and make a new Rectangle for each position. The elements are all created **10 x 20** in size and placed one above the other using the Move function. We
will add them all to the segment **slice**.

3.3  The return from this method is a **container with no layout** so that we can later use a manual layout for the visual elements:
```go
func setupGame() *fyne.Container {
	for i := 0; i < lenght; i++ {
		seg := snakePart{9, float32(5 + i)}
		snakeParts = append(snakeParts, seg)

		r := canvas.NewRectangle(&color.RGBA{G: 0x66, A: 0xff})
		r.Resize(fyne.NewSize(10, 20))
		r.Move(fyne.NewPos(150, float32(500+i*20)))
		segments = append(segments, r)
	}

	head = canvas.NewRectangle(&color.RGBA{G: 0x66, A: 0xff})
	head.Resize(fyne.NewSize(10, 20))
	head.Move(fyne.NewPos(snakeParts[0].x*50, snakeParts[0].y*50))

	fruit = canvas.NewCircle(&color.RGBA{R: 0x66, A: 0xff})
	fruit.Resize(fyne.NewSize(10, 10))
	fruitPos= fyne.NewPos(snakeParts[0].x*20, snakeParts[0].y*20)

	fruit.Move(fruitPos)

	segments = append(segments, head)
	segments = append(segments, fruit)

	c:= fyne.NewContainerWithoutLayout(segments...)
	return c
}

```
3.4.  With the graphical setup code, we can wrap this in the usual application load code, this time passing the result of **setupGame()** to the **SetContent** function. As this game will not have dynamic sizing, we will call **SetFixedSize(true)** so that the window cannot be resized
```go
func main() {
	a := app.New()

	w := a.NewWindow("Snake")
	w.Resize(fyne.NewSize(800, 800))
	w.SetFixedSize(false)
	game = setupGame()
	w.SetContent(game)

	w.Canvas().SetOnTypedKey(keyTyped)

	go runGame()
	w.ShowAndRun()
}

```

**4. Adding a timer to move the snake**
The next step is to add some motion to the game. We will start with a simple timer that
repositions the snake on screen:

4.1. To help manage the game state, we will define a new type to store the x, y value of
each snake segment, named **snakePart**. We then make a slice that contains all of
the elements, and this is what we will update as the snake moves around the screen. 
```go
type snakePart struct {
	x, y float32
}
var (
	snakeParts []snakePart
	game       *fyne.Container
	head       *canvas.Rectangle
    fruit      *canvas.Circle
	move       = moveDown
	segments []fyne.CanvasObject
	lenght        int = 20
	fruitPos     fyne.Position 
   )
```
4.2.  We create a new function that will update the rectangles that we created earlier based on updated snake section information. To make sure that the game refreshes each time we move the snake, we need to move the rectangles and call  ** Refresh()**.
```go
func refreshGame() {

	for i, seg := range snakeParts {
		rect := game.Objects[i]
		rect.Move(fyne.NewPos(seg.x*10, seg.y*20))
	}

	game.Refresh()
}

```
4.3.  To run the main game loop, we need one more function that will use a timer of  **250 milliseconds** to move the snake. We call this function **runGame**.

4.4.  To move the snake forward , we copy the position of each element from that of the element that is one segment further forward, working from the tail to the head.

**5. Using keys to control direction**
5.1. To start with, we define a new type (moveType) that will be used to describe the
next direction in which to move. The move variable is then defined to hold the next move direction:
```go
type moveType int
const (
 moveUp moveType = iota
 moveDown
 moveLeft
 moveRight
)
var move = moveUp
```
5.2. Create a new keyTyped function as follows that will perform the keyboard
mapping:
```go
func keyTyped(e *fyne.KeyEvent) {
 switch e.Name {
 case fyne.KeyUp:
 move = moveUp
 case fyne.KeyDown:
 move = moveDown
 case fyne.KeyLeft:
 move = moveLeft
 case fyne.KeyRight:
 move = moveRight
 }
}
```
5.3. For the key events to be triggered we use the **SetOnKeyTyped()** function on the window's canvas.
```
w.Canvas().SetOnTypedKey(keyTyped)
```

5.4.  Lastly, the code moves the head to a new position, in this case further up the screen (by using **snakeParts[0].y-1**). Refer to the following function:

5.5.  We set up our animations inside the for loop in the runGame function, before the
timer pause

```go
func runGame() {
	nextPart := snakePart{snakeParts[0].x, snakeParts[0].y - 1}
	for {
		oldPos := fyne.NewPos(snakeParts[0].x*10, snakeParts[0].y*10)
		newPos := fyne.NewPos(nextPart.x*10, nextPart.y*10)
		canvas.NewPositionAnimation(oldPos, newPos, time.Millisecond*250, func(p fyne.Position) {
			head.Move(p)
			canvas.Refresh(head)
		}).Start()

		end := len(snakeParts) - 1
		canvas.NewPositionAnimation(fyne.NewPos(snakeParts[end].x*10, snakeParts[end].y*10),
			fyne.NewPos(snakeParts[end-1].x*10, snakeParts[end-1].y*10), time.Millisecond*250,
			func(p fyne.Position) {
				tail := game.Objects[end]
				tail.Move(p)
				canvas.Refresh(tail)
			}).Start()

		time.Sleep(time.Millisecond * 250)
		for i := len(snakeParts) - 1; i >= 1; i-- {
			snakeParts[i] = snakeParts[i-1]
		}
		snakeParts[0] = nextPart //SetUp the new head position
		refreshGame()

	}
}

```


5.6.  After the refresh line, we need to update the movement calculations to set up nextPart ready for the next movement:
```go
 switch move {
 case moveUp:
 nextPart = snakePart{nextPart.x,
 nextPart.y - 1}
 case moveDown:
 nextPart = snakePart{nextPart.x,
 nextPart.y + 1}
 case moveLeft:
 nextPart = snakePart{nextPart.x - 1,
 nextPart.y}
 case moveRight:
 nextPart = snakePart{nextPart.x + 1,
 nextPart.y}
 }
```