自分用メモ

https://wita.noyatsu.club/

Go言語とgo-glでライフゲーム

f:id:witalosk:20190505140833p:plain 突然Go言語に興味が出てきたので学習しながらいろいろやってみます. よってプロから見るとダメダメなコードを書いている可能性が大です.悪しからず.

そもそもGo言語とは

Googleが開発したコンパイルするタイプの言語(≠スクリプト言語)です. 文法はめっちゃシンプル.並列処理に強くて高速らしい.

ビルドして実行するときはコンソールで

go run hoge.go

と叩く.

文法

「パッケージ」というものを最初に宣言する.必ずmainパッケージがある必要がある. プログラムはmainパッケージの中のmain関数から実行される.

変数

varで宣言できる.string, int, float64, bool, nilのデータ型がある. 宣言方法はいろいろ.そして1文字目に 1 文字目が小文字の場合はそのパッケージだけで見える変数(private)で, 大文字の場合は他のパッケージからも見える変数(public).

var hoge int

var hoge2 = 2 // 型名は省略可能

hoge3 := 3 // varも省略可能

var a, b int // 同時に定義して
a, b = 1, 100 // 同時に代入

var (
  x = 123
  y = "hogey"
) // 複数を同時に

// 配列
var arr [5]int

arr := [3]int{1, 3, 6}

「スライス」という便利機能があるらしい.

定数

constで定義する.

const {
  hoge = 1
  hoge2 = 2
}

関数

func hogehoge()で宣言.引数と戻り値の方は絶対指定しなければならない.

package main

import "fmt"

func hoge () {
  fmt.Println("Hello, world!")
}

func hoge2 (text string) {
  fmt.Println(text)
}

func hoge3 (i int) int {
  return i*i
}

// 複数の戻り値を与えられる
func hoge4(a int, b int) (int, int) {
    return b, a
}

繰り返し・条件分岐

繰り返しはfor文オンリー.while的に使うことも可能.

for i := 0; i < 5; i++ {
  if i == 0 {
    // hoge
  } else if i == 4 {
    // hogehoge
    break
  }
}

// while的
n := 0
for n < 5 {
  // hoge
  n++
}

GolangOpenGL

さてGo言語で画面描画をしようとするとやっぱりOpenGL. Go用のものは以下です. github.com Goではコマンドからサクッとパッケージをインストールできます.( 楽! ) go-glの最新版だといろいろ変わっているので,2,1にしました(甘え)

 $ go get -u github.com/go-gl/gl/v2.1/gl
 $ go get -u github.com/go-gl/glfw/v3.2/glfw

今回はglfwもいっしょにインストール.

円を描画

package main

import "github.com/go-gl/gl/v2.1/gl"
import "github.com/go-gl/glfw/v3.2/glfw"
import "log"
import "math"
import "runtime"

func init() {
    runtime.LockOSThread()
}

func main() {
    windowWidth := 640 //!< 横幅
    windowHeight := 640 //!< 縦幅

    // glfwを初期化
    if err := glfw.Init(); err != nil {
        log.Fatalln("[Error] Failed to initialize glfw:", err)
    }
    defer glfw.Terminate() // main関数が閉じられるときに呼び出される

    // Windowを作る
    glfw.WindowHint(glfw.Resizable, glfw.False)
    glfw.WindowHint(glfw.ContextVersionMajor, 2)
    glfw.WindowHint(glfw.ContextVersionMinor, 1)
    window, err := glfw.CreateWindow(windowWidth, windowHeight, "GL Test", nil, nil)
    if err != nil {
        panic(err) // 強制的に終了
    }
    window.MakeContextCurrent()

    // GLの初期化
    if err := gl.Init(); err != nil {
        panic(err) // 強制的に終了
    }

    for !window.ShouldClose() { // ウィンドウを閉じるまで繰り返す
        gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

        r := 0.5 //!< 半径
        n := 100 //!< 円の分割数

        // 円を描く
        gl.Begin(gl.POLYGON)
        gl.Color4f(0.2, 0.3, 0.7, 1.0)  // 円の色(RGBA)

        // 円を描画
        for i := 0; i < n; i++ {
            x := r * math.Cos(2.0 * 3.14 * (float64(i)/float64(n)))
            y := r * math.Sin(2.0 * 3.14 * (float64(i)/float64(n)))
            gl.Vertex3f(float32(x), float32(y), 0.0) // 頂点座標
        }
        gl.End();
        gl.Flush();  

        window.SwapBuffers()
        glfw.PollEvents()
    }
}

実行結果:
f:id:witalosk:20190415152600p:plain
やったね!描画できたよ!!

ライフゲームを作る

ライフゲーム (Conway's Game of Life[1]) は1970年にイギリスの数学者ジョン・ホートン・コンウェイ (John Horton Conway) が考案した生命の誕生、進化、淘汰などのプロセスを簡易的なモデルで再現したシミュレーションゲームである。 ライフゲーム - Wikipedia

  • 誕生 - 死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
  • 生存 - 生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
  • 過疎 - 生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
  • 過密 - 生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。
package main

import "github.com/go-gl/gl/v2.1/gl"
import "github.com/go-gl/glfw/v3.2/glfw"
import "log"
//import "math"
import "runtime"

var windowWidth = 1305 //!< 横幅
var windowHeight = 1305 //!< 縦幅
var n = 32 //!< セルサイズ
var cells [32][32]int //!< セルの配列
var prevCells [32][32]int //!< 前ステップのセルの配列
var count = 0 //!< フレームカウント


func init() {
    runtime.LockOSThread()

    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {
            cells[i][j] = 0
            prevCells[i][j] = 0
        }
    }
    //penta(10,15)
    //penta(5,5)
    //glider(25,20)
    hansyoku(15,15)
}

func penta(x int, y int) {
    cells[x][y] = 1
    cells[x+3][y] = 1
    cells[x+5][y] = 1
    cells[x+6][y] = 1
    cells[x+8][y] = 1
    cells[x+11][y] = 1

    cells[x][y+1] = 1
    cells[x+1][y+1] = 1
    cells[x+2][y+1] = 1
    cells[x+3][y+1] = 1
    cells[x+5][y+1] = 1
    cells[x+6][y+1] = 1
    cells[x+8][y+1] = 1
    cells[x+9][y+1] = 1
    cells[x+10][y+1] = 1
    cells[x+11][y+1] = 1

    cells[x][y+2] = 1
    cells[x+3][y+2] = 1
    cells[x+5][y+2] = 1
    cells[x+6][y+2] = 1
    cells[x+8][y+2] = 1
    cells[x+11][y+2] = 1
}

func glider(x int, y int) {
    cells[x][y] = 1
    cells[x][y+1] = 1
    cells[x][y+2] = 1
    cells[x+1][y] = 1
    cells[x+2][y+1] = 1
}

func hansyoku(x int, y int) {
    cells[x][y] = 1
    cells[x+1][y] = 1
    cells[x+2][y] = 1
    cells[x+4][y] = 1
    cells[x][y+1] = 1
    cells[x+3][y+2] = 1
    cells[x+4][y+2] = 1
    cells[x+1][y+3] = 1
    cells[x+2][y+3] = 1
    cells[x+4][y+3] = 1
    cells[x][y+4] = 1
    cells[x+2][y+4] = 1
    cells[x+4][y+4] = 1
}


func display() {
    prevCells = cells

    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {

            // セルをカウント
            num := 0
            if i-1>=0 && j-1>=0 {num += prevCells[i-1][j-1]}
            if j-1>=0 {num += prevCells[i][j-1]}
            if i+1<n && j-1>=0 {num += prevCells[i+1][j-1]}
            if i-1>=0 {num += prevCells[i-1][j]}
            if i+1<n {num += prevCells[i+1][j]}
            if i-1>=0 && j+1<n {num += prevCells[i-1][j+1]}
            if j+1<n {num += prevCells[i][j+1]}
            if i+1<n && j+1<n {num += prevCells[i+1][j+1]}

            // セルを設定
            if num <=1 {
                cells[i][j] = 0
            } else if num <= 2 {
                if prevCells[i][j] == 1 {
                    cells[i][j] = 1
                } else {
                    cells[i][j] = 0
                }
            } else if num == 3 {
                cells[i][j] = 1
            } else {
                cells[i][j] = 0
            }

            // 色を設定
            if cells[i][j] == 1{
                gl.Color4f(0.2, 0.3, 0.7, 1.0)
            } else {
                gl.Color4f(0.1, 0.1, 0.1, 1.0)
            }

            gl.PushMatrix()
            gl.Translatef(float32(i)*float32(windowWidth/n), float32(j)*float32(windowHeight/n), 0.0)
            rect(10.0)
            gl.PopMatrix()
        }
    }
    gl.Flush();  

    count = count + 1
}

func rect(ext float32) {
    gl.Begin(gl.POLYGON)
    gl.Vertex3f(-float32(ext), float32(ext), 0.0)
    gl.Vertex3f(float32(ext), float32(ext), 0.0)
    gl.Vertex3f(float32(ext), -float32(ext), 0.0)
    gl.Vertex3f(-float32(ext), -float32(ext), 0.0)
    gl.End()
}

func main() {
    // glfwを初期化
    if err := glfw.Init(); err != nil {
        log.Fatalln("[Error] Failed to initialize glfw:", err)
    }
    defer glfw.Terminate() // main関数が閉じられるときに呼び出される

    // Windowを作る
    glfw.WindowHint(glfw.Resizable, glfw.False)
    glfw.WindowHint(glfw.ContextVersionMajor, 2)
    glfw.WindowHint(glfw.ContextVersionMinor, 1)
    window, err := glfw.CreateWindow(windowWidth, windowHeight, "Life Game", nil, nil)
    if err != nil {
        panic(err) // 強制的に終了
    }
    window.MakeContextCurrent()

    // GLの初期化
    if err := gl.Init(); err != nil {
        panic(err) // 強制的に終了
    }

    // 座標変換 (画面幅*画面幅の座標にする)
    gl.Scalef(2.0/float32(windowWidth), 2.0/float32(windowHeight), 1.0)
    gl.Translatef(-float32(windowWidth)/2.0 + 25.0, -float32(windowHeight)/2.0 + 25.0, 0.0)

    // タイマーのセッティング
    fps, currentTime, lastTime, elapsedTime := 4.0, 0.0, 0.0, 0.0
    glfw.SetTime(0.0)

    // メインループ
    for !window.ShouldClose() { // ウィンドウを閉じるまで繰り返す
        // タイマー
        currentTime = glfw.GetTime()
        elapsedTime = currentTime - lastTime

        if(elapsedTime >= 1.0/fps){
            gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
            display()
            window.SwapBuffers()
            glfw.PollEvents()

            lastTime = glfw.GetTime()
        }
    }

}

実行結果

f:id:witalosk:20190505141810p:plain