Learn Golang By Building A 2D Snake Game: A Complete Guide
Tags: go, gamedev, programming, ai
Published: 2023-05-03 10:52:23

Learning a new programming language can be a daunting task, but working on a fun project like building a game can make the process enjoyable and effective. In this tutorial, we will learn Golang by creating a simple snake game. Golang, or Go, is an open-source programming language designed by Google that makes it easy to build simple, reliable, and efficient software.

By the end of this tutorial, you will have a basic understanding of Go and its core concepts, such as variables, functions, loops, and structs, as well as a working snake game to show off your new skills.

---


### Setting Up Your Go Environment
Before you start building the game, You need to set up your Go environment. You can download Go from the official website at https://golang.org/dl/. Once Go installed, Make sure to set up your GOPATH and configure your environment variables correctly.

---


### Introduction to Ebiten
For building a 2d game we are going to use Ebiten package. Ebiten is a 2D game library for Go that makes it easy to develop cross-platform games. Ebiten provides essential game features, Such as drawing images and handling user input. To get started with Ebiten, Install the library by running the following command:

```sh
go get -u github.com/hajimehoshi/ebiten/v2
```

---


### Setting Up the Game Environment
Begin by creating a new Go file and importing the required packages, such as _fmt_ , _log_ , _math/rand_ , _time_ , and _github.com/hajimehoshi/ebiten/v2_. Next, create constants for the screen width and height, and tile size.

```go
const (
    screenWidth  = 320
    screenHeight = 240
    tileSize     = 5
)
```

The main function should initialize the game state and call `ebiten.RunGame()` to start the game loop.

```go
func main() {
    rand.Seed(time.Now().UnixNano())
    game := &Game{
        snake:    NewSnake(),
        food:     NewFood(),
        gameOver: false,
        ticks:    0,
        speed:    10,
    }
    ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
    ebiten.SetWindowTitle("Snake Game")
    if err := ebiten.RunGame(game); err != nil {
        log.Fatal(err)
    }
}
```

This code sets up a basic Ebiten game window with a size of 640×480 pixels.

### Creating the Snake Structure
Create a `Point` struct to represent x and y coordinates, and a `Snake` struct to represent the snake's body, direction, and growth counter.

```go
type Point struct {
    X int
    Y int
}

type Snake struct {
    Body        []Point
    Direction   Point
    GrowCounter int
}
```

Now implement a `NewSnake()` function to create a new snake instance and a `Move()` method to update the snake's position based on its direction. The `Move()` method should also handle the snake's growth by keeping or removing the tail segment based on the `GrowCounter`.

```go
func NewSnake() *Snake {
    return &Snake{
        Body: []Point{
            {X: screenWidth / tileSize / 2, Y: screenHeight / tileSize / 2},
        },
        Direction: Point{X: 1, Y: 0},
    }
}

func (s *Snake) Move() {
    newHead := Point{
        X: s.Body[0].X + s.Direction.X,
        Y: s.Body[0].Y + s.Direction.Y,
    }
    s.Body = append([]Point{newHead}, s.Body...)

    if s.GrowCounter > 0 {
        s.GrowCounter--
    } else {
        s.Body = s.Body[:len(s.Body)-1]
    }
}
```

`NewSnake` function initializes the snake with a single body part located in the center of the screen and sets the initial direction to the right.

`Move` function calculates the new head position based on the current head position and direction. It then adds the new head to the body and removes the tail unless the snake is growing.

### Implementing the Food
Now Create a `Food` struct to represent the food’s position and a `NewFood()` function to create a new food instance with a random position on the game screen.

```go
type Food struct {
    Position Point
}

func NewFood() *Food {
    return &Food{
        Position: Point{
            X: rand.Intn(screenWidth / tileSize),
            Y: rand.Intn(screenHeight / tileSize),
        },
    }
}
```

### Handling User Input and Game Updates
Create a `Game` struct to represent the game state, including the `snake`, `food`, `score`, `speed`, and `game over` flag.

```go
type Game struct {
    snake         *Snake
    food          *Food
    score         int
    gameOver      bool
    ticks         int
    updateCounter int
    speed         int
}
```

Implement the `Update()` method for the `Game` struct to handle user input, update the snake's position, check for collisions, and handle the snake eating the food. If the snake eats the food, increment the score and update the food position.

```go
func (g *Game) Update() error {
    if g.gameOver {
        if inpututil.IsKeyJustPressed(ebiten.KeyR) {
            g.restart()
        }
        return nil
    }

    g.updateCounter++
    if g.updateCounter < g.speed {
        return nil
    }
    g.updateCounter = 0

    // Update the snake's position
    g.snake.Move()

    if ebiten.IsKeyPressed(ebiten.KeyLeft) && g.snake.Direction.X == 0 {
        g.snake.Direction = Point{X: -1, Y: 0}
    } else if ebiten.IsKeyPressed(ebiten.KeyRight) && g.snake.Direction.X == 0 {
        g.snake.Direction = Point{X: 1, Y: 0}
    } else if ebiten.IsKeyPressed(ebiten.KeyUp) && g.snake.Direction.Y == 0 {
        g.snake.Direction = Point{X: 0, Y: -1}
    } else if ebiten.IsKeyPressed(ebiten.KeyDown) && g.snake.Direction.Y == 0 {
        g.snake.Direction = Point{X: 0, Y: 1}
    }

    head := g.snake.Body[0]
    if head.X < 0 || head.Y < 0 || head.X >= screenWidth/tileSize || head.Y >= screenHeight/tileSize {
        g.gameOver = true
        g.speed = 10
    }

    for _, part := range g.snake.Body[1:] {
        if head.X == part.X && head.Y == part.Y {
            g.gameOver = true
            g.speed = 10
        }
    }

    if head.X == g.food.Position.X && head.Y == g.food.Position.Y {
        g.score++
        g.snake.GrowCounter += 1
        g.food = NewFood()
        g.score++
        g.food = NewFood()
        g.snake.GrowCounter = 1

        // Decrease speed (with a lower limit)
        if g.speed > 2 {
            g.speed--
        }
    }

    return nil
}
```

In the update function we're handling, game over logic, speed, user input, and restart game functionalities.

### Drawing the Game Objects
Implement the `Draw()` method for the `Game` struct to render the game objects, such as the snake, food, and game over screen. Use Ebiten's drawing functions, like `DrawRect()`, to draw the snake and food as rectangles and the `text.Draw()` function to display the score and game over message.

```go
func (g *Game) Draw(screen *ebiten.Image) {
    // Draw background
    screen.Fill(color.RGBA{0, 0, 0, 255})

    // Draw snake
    for _, p := range g.snake.Body {
        ebitenutil.DrawRect(screen, float64(p.X*tileSize), float64(p.Y*tileSize), tileSize, tileSize, color.RGBA{0, 255, 0, 255})
    }

    // Draw food
    ebitenutil.DrawRect(screen, float64(g.food.Position.X*tileSize), float64(g.food.Position.Y*tileSize), tileSize, tileSize, color.RGBA{255, 0, 0, 255})

    // Create a font.Face
    face := basicfont.Face7x13

    // Draw game over text
    if g.gameOver {
        text.Draw(screen, "Game Over", face, screenWidth/2-40, screenHeight/2, color.White)
        text.Draw(screen, "Press 'R' to restart", face, screenWidth/2-60, screenHeight/2+16, color.White)
    }

    // Draw score
    scoreText := fmt.Sprintf("Score: %d", g.score)
    text.Draw(screen, scoreText, face, 5, screenHeight-5, color.White)
}
```

### Restarting the Game
At last implement a restart() method for the Game struct to reset the game state when the player presses 'R' after a game over.


```go
func (g *Game) restart() {
    g.snake = NewSnake()
    g.score = 0
    g.gameOver = false
    g.food = NewFood()
    g.speed = 10
}
```

This code checks if the `R` key is pressed when the game is over, and if so, it restarts the game by calling the `restart` method. The `restart` method resets the game state, including the `snake`, `score`, and `gameOver` flag.

### Running Game


![Golang Snake Game](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aw9ib6jf2i871cxygukt.gif)

Follow me on [twitter](https://twitter.com/kusinghofficial) and message me, I’ll send you the full working code at once.

### Conclusion

Congratulations! You’ve now built a simple snake game using Golang and the Ebiten library. In the process, you’ve learned the basics of Golang, including variables, functions, loops, and structs, as well as how to handle user input and collisions in a game.

You can further enhance this game by adding more features such as different levels, obstacles, or power-ups. The possibilities are endless, and you now have a solid foundation to build upon as you continue learning Golang through game development. Happy coding!


_If you’re interested in learning more about programming and related topics, we invite you to check out our website [programmingeeksclub.com](https://programmingeeksclub.com). We offer valuable resources and insights._

_You can find us on [Twitter](https://twitter.com/kusinghofficial) and [Facebook](https://www.facebook.com/profile.php?id=100091365764388)._

_Download my first ebook about mastering markdown, from here: [Download](https://kusingh.gumroad.com/l/mastering-markdown) , reviews and recommendations are appreciated._