こんにちは。KOUKIです。
今日は、Udemyで習ったgolangで作るピンポン球を紹介したいと思います。
こんなやつです↓
事前準備
1 2 3 4 |
mkdir ball cd ball/ touch main.go go get github.com/inancgumus/screen |
実装
ボールが動き回るマスを作る
最初にボールが動き回るためのマスを作りましょう。

これを表現するには、二次元のスライスを用意すればOKです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main func main() { const ( // 横のマス width = 50 // 縦のマス height = 10 ) board := make([][]bool, width) for column := range board { board[column] = make([]bool, height) } } |
縦(y)のマスを10、横(x)のマスを50に設定しました。そして、x、yの値を両方格納できるように[][]boolで、二次元のスライスを格納します。
例えば[1][2]boolとなった場合は、以下のようにボールが格納されるイメージです。

ボールを描画する
次は、ボールを描画してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package main import "fmt" func main() { const ( width = 50 height = 10 cellEmpty = ' ' cellBall = '⚾' ) var ( // ボールを格納 cell rune ) board := make([][]bool, width) for column := range board { board[column] = make([]bool, height) } // 描画用のサンプル board[12][2] = true board[16][2] = true board[14][4] = true board[10][6] = true board[18][6] = true board[12][7] = true board[14][7] = true board[16][7] = true // ボールを描画する for y := range board[0] { for x := range board { cell = cellEmpty if board[x][y] { cell = cellBall } fmt.Print(string(cell), " ") } fmt.Println() } } |
board[0]は列が入っているので、これをループします。
cell変数にはボールを格納する役割を果たしてもらいます。そして、boardのスライスにtrueが格納された時、ボールを描画します(例:board[16][7] = true)。
プログラムを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 |
$ go run main.go ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ |
問題なく描画されましたね。
バッファを作る
プログラムの効率化のために、バッファを作成して、そこにボールを格納しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package main import "fmt" func main() { const ( width = 50 height = 10 cellEmpty = ' ' cellBall = '⚾' ) var ( cell rune ) board := make([][]bool, width) for column := range board { board[column] = make([]bool, height) } // ボール格納用のバッファを用意する buf := make([]rune, 0, width*height) // 初期化 buf = buf[:0] board[12][2] = true board[16][2] = true board[14][4] = true board[10][6] = true board[18][6] = true board[12][7] = true board[14][7] = true board[16][7] = true for y := range board[0] { for x := range board { cell = cellEmpty if board[x][y] { cell = cellBall } // ボールと空白をバッファに格納 buf = append(buf, cell, ' ') } // 改行を格納 buf = append(buf, '\n') } // 描画する fmt.Print(string(buf)) } |
バッファを作ることで、ループごとに描画していた処理を一度に短縮できます。
加えて、先にバッファを作ってメモリを確保した方が処理速度も早くなるのでオススメです。
また、「buf = buf[:0]」についてですが、この後この処理をfor文で囲むのですが、bufの先頭から値を格納した方が処理速度が早くなるので、このようにしてます。逆にこれがないと遅くなりますね。
描画も問題なくできています。
1 2 3 4 5 6 7 8 9 10 11 |
$ go run main.go ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ |
ボールポジションとベロシティを設定
次は、ボールのポジションとベロシティを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
package main import "fmt" func main() { const ( width = 50 height = 10 cellEmpty = ' ' cellBall = '⚾' // 描画速度 maxFrames = 1200 ) var ( px int // x position py int // y position // velocityを設定(球が動く範囲) vx, vy = 1, 1 cell rune ) board := make([][]bool, width) for column := range board { board[column] = make([]bool, height) } // ボールを動かすためのループを設定 for i := 0; i < maxFrames; i++ { // velocityをx,yのポジションに格納する px += vx py += vy // ボールがマスを跳び越さないように条件を定める // これを越えるとスライスのindexエラーになるため if px <= 0 || px >= width-1 { vx *= -1 } if py <= 0 || py >= height-1 { vy *= -1 } board[px][py] = true buf := make([]rune, 0, width*height) buf = buf[:0] for y := range board[0] { for x := range board { cell = cellEmpty if board[x][y] { cell = cellBall } buf = append(buf, cell, ' ') } buf = append(buf, '\n') } fmt.Print(string(buf)) } } |
ベロシティは、ボールの飛び交う範囲をここでは示していますね。マスの枠が決まっているので、その範囲を超えないように調整しています。
プログラムを実行してみましょう。
1 2 3 4 5 6 |
go run main.go ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ ⚾ |
めちゃくちゃボールが出てきますね。
ボールを削除する
ボールの描画が残ったままだと先ほどの出力になってしまうので、ボールを削除しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
package main import ( "fmt" "time" ) func main() { const ( width = 50 height = 10 cellEmpty = ' ' cellBall = '⚾' maxFrames = 1200 // 描画速度を設定 speed = time.Second / 20 ) var ( px, py int vx, vy = 1, 1 cell rune ) board := make([][]bool, width) for column := range board { board[column] = make([]bool, height) } for i := 0; i < maxFrames; i++ { px += vx py += vy if px <= 0 || px >= width-1 { vx *= -1 } if py <= 0 || py >= height-1 { vy *= -1 } // 描画したボールを削除する for y := range board[0] { for x := range board { board[x][y] = false } } board[px][py] = true buf := make([]rune, 0, width*height) buf = buf[:0] for y := range board[0] { for x := range board { cell = cellEmpty if board[x][y] { cell = cellBall } buf = append(buf, cell, ' ') } buf = append(buf, '\n') } fmt.Print(string(buf)) time.Sleep(speed) } } |
描画前に以前格納したポジション情報を削除する処理を入れました。また、ボールの描画速度が早すぎるので、それを緩めるためにtime.Sleepを追加しています。
実行すると以下のようになります。
ボールがピンポンしてますね。もう少しです。
screenパッケージで描画を調整する
最後にscreenパッケージを使って、パッケージの描画を調整しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
package main import ( "fmt" "time" "github.com/inancgumus/screen" ) func main() { const ( width = 50 height = 10 cellEmpty = ' ' cellBall = '⚾' maxFrames = 1200 speed = time.Second / 20 ) var ( px, py int vx, vy = 1, 1 cell rune ) board := make([][]bool, width) for column := range board { board[column] = make([]bool, height) } // terminal上の不要な出力を削除 screen.Clear() for i := 0; i < maxFrames; i++ { px += vx py += vy if px <= 0 || px >= width-1 { vx *= -1 } if py <= 0 || py >= height-1 { vy *= -1 } for y := range board[0] { for x := range board { board[x][y] = false } } board[px][py] = true buf := make([]rune, 0, width*height) buf = buf[:0] for y := range board[0] { for x := range board { cell = cellEmpty if board[x][y] { cell = cellBall } buf = append(buf, cell, ' ') } buf = append(buf, '\n') } // カーソルを左上に持ってくる screen.MoveTopLeft() fmt.Print(string(buf)) time.Sleep(speed) } } |
これで完成です!
おわりに
このプログラムをみたとき、Go言語ってこんなことができるんだと感動しました^^
色々なバリエーションを楽しめますよね。
それでは、また!
最近のコメント