Game Experiments

Go, Odin and Raylib tutorials

Making a Tetris game

- Posted in Go-Raylib by

Tetris using Go and raylib

Difficulty - easy to medium - assumes basic knowledge of Go

We will end up with a game like this with sound effects and music. I chose this to kick off with as we do not use any images but create our images in the code.

I suggest you use VS-Code, but notepad will work just fine too. Git is recommended.

if using VS-Code install this plugin enter image description here

Disclaimer

This was the first app I wrote with go and Raylib, and being a complete novice at both Go and Raylib, I decided to convert this c++ tutorial by Nick who explains simply.

You'll recognize the screen immediately as I never made any changes to the look.

What you need

You will need to know about go methods and about go tests

Exercism has a great Go learning track that covers most of Go features including methods. It is a good stating place if you do not know Go very well.

Learn Go with Tests is an online book which covers developing go apps using tests

Get started

  • create a new project like you did before.
  • I named the folder 'tetris' and have the project in a sub-folder called tetrismain. (leave the git files in the tetris folder)
    • create the folders 'Font' and 'sounds'

file structure now

add a '.gitignore' to the folder you ran the git init in and add *.exe as the only thing in it (for now). This will prevent 'extra' execution files from being added to our repository.

run the following (git skill practice)

git add .
git commit -m "first commit"

finally change the code

  • we want the window size to be 500 by 620 (for mobile) and the description to be "raylib-go Tetris"
  • we want a background color (colour) //I use the spelling color to match the spelling often used in code and in the raylib library
  • we want the color to be draw on the screen

When drawing on the screen you will notice we just keep draw over everything else. This can be useful when things don't change and optimization is needed. We don't need that kind of optimization for our game.

We 'clear' the screen by drawing a background color then each and everything else in order, each and every frame.

replace the lines that are similar with the code below

rl.InitWindow(500, 620, "raylib-go Tetris")
defer rl.CloseWindow()

//dark blue
backgroundColor := rl.NewColor(44, 44, 127, 255)

and above the DrawText add this

        rl.ClearBackground(backgroundColor)

and the remove the DrawText.

If you run (go run .) then you will see a screen roughly the size we want with a blue background.

Now lets draw the grid

We want

  • the grid on the left side of the screen
  • 10 across and 20 down
  • with a gap around and a space between each block in the grid
  • a dark color for each empty square
  • each square should be a size of 30 by 30
  • we need an array to keep track of where the colors will be drawn on the grid

Lets add the following variables above the main func

var NumRows, NumCols, CellSize int
var gridData []int

and initialize above the loop

NumRows = 20
NumCols = 10
CellSize = 30

if len(gridData) == 0 {
    gridData = make([]int, NumRows*NumCols)
}
for i := range gridData {
    gridData[i] = 0
}
printGrid()

Note printGrid is a new function. Functions make things simpler.

from google - Functions are a fundamental building block in code used to organize code, promote reusability, and manage complexity.

printGrid will loop through the grid and print each row on a separate line on the terminal. to do that we will need two additional functions.

  • a function to check if we have requested a valid row
  • a function that will return a valid row

We can place these three new functions before or after the main function. In Go it doesn't matter what your style is, but if you plan on working with others make sure you know what they expect.

func inGridRow(row int) bool {
    if row >= NumRows || row < 0 {
        return false
    }
    return true
}

func getRowAt(row int) ([]int, error) {

    if !inGridRow(row) {
        return nil, errors.New("row position out of range")
    }
    sStart := row * NumCols
    sEnd := row*NumCols + NumCols
    s := gridData[sStart:sEnd]

    return s, nil
}

func printGrid() {
    for r := 0; r < NumRows; r++ {
        s, _ := getRowAt(r)
        fmt.Println(s)
    }
}

Okay, starting with "inGridRow(row int) bool" when we call this function we pass a row number and we expect it to return a bool. Bools are true or they are false, nothing else. There are 20 rows and our grid counts from 0 to 19 not 1 to 20. This is common for computer languages. So passing in 0 will return true, but 20 will return false. This function checks if the row number is out of range and returns false, otherwise it is true.

"getRowAt(row int) ([]int, error)". We pass in a row number and if it was out of range we get a nil with an error.

if !inGridRow(row) {
    return nil, errors.New("row position out of range")
}

otherwise we slice the row out and return that. to get the row we need to figure out the starting position and the end position of the row, this is done with

sStart := row * NumCols
sEnd := row*NumCols + NumCols

the start position is the row number multiplied by NumCols i.e. 5x10=50 so row 5 starts at 50 and end is 60 ... a slice doesn't include the last number, also common in coding languages. we get a slice of 50 to 59 for row 5, perfect.

The final function is "printGrid()" which doesn't accept anything or return anything. it simply loops through the array getting each slice and then uses fmt.println to print that slice to the terminal.

We will remove printGrid when we are done as it is only there to help us see that the other two functions work as expected. I'll get into testing soon, which is another way.

final code for main today looks like.

package main

import (
    "errors"
    "fmt"

    rl "github.com/gen2brain/raylib-go/raylib"
)

var NumRows, NumCols, CellSize int
var gridData []int

func main() {
    rl.InitWindow(500, 620, "raylib-go Tetris")
    defer rl.CloseWindow()

    //dark blue
    backgroundColor := rl.NewColor(44, 44, 127, 255)

    NumRows = 20
    NumCols = 10
    CellSize = 30

    if len(gridData) == 0 {
        gridData = make([]int, NumRows*NumCols)
    }
    for i := range gridData {
        gridData[i] = 0
    }
    printGrid()

    rl.SetTargetFPS(60)

    for !rl.WindowShouldClose() {
        rl.BeginDrawing()

        rl.ClearBackground(backgroundColor)

        rl.EndDrawing()
    }
}

func inGridRow(row int) bool {
    if row >= NumRows || row < 0 {
        return false
    }
    return true
}

func getRowAt(row int) ([]int, error) {

    if !inGridRow(row) {
        return nil, errors.New("row position out of range")
    }
    sStart := row * NumCols
    sEnd := row*NumCols + NumCols
    s := gridData[sStart:sEnd]

    return s, nil
}

func printGrid() {
    for r := 0; r < NumRows; r++ {
        s, _ := getRowAt(r)
        fmt.Println(s)
    }
}

and you should see this when running the code with "go run ." enter image description here

Next post will be up soon