
ターミナル上に出力した球(●)を常に動かせるようにしたいとする。
この要件を満たす為には、
package main
import (
"time"
"github.com/gdamore/tcell/v2"
)
func main() {
screen, err := tcell.NewScreen()
if err != nil {
panic(err)
}
if err := screen.Init(); err != nil {
panic(err)
}
defer screen.Fini()
pos_x, pos_y := 5, 5
ball := '●'
for {
screen.Clear()
screen.SetContent(pos_x, pos_y, ball, nil, tcell.StyleDefault)
screen.Show()
pos_x += 1
time.Sleep(1 * time.Second)
}
}
のようなコードにし、
pos_x += 1 time.Sleep(1 * time.Second)
で一秒毎に1ずつ右に移動するようにすれば良いですが、これだと終了することができません。
そこで、
package main
import (
"time"
"github.com/gdamore/tcell/v2"
)
func main() {
screen, err := tcell.NewScreen()
if err != nil {
panic(err)
}
if err := screen.Init(); err != nil {
panic(err)
}
defer screen.Fini()
pos_x, pos_y := 5, 5
ball := '●'
for {
ev := screen.PollEvent()
switch ev := ev.(type) {
case *tcell.EventKey:
switch ev.Key() {
case tcell.KeyEscape:
return
}
default:
screen.Clear()
screen.SetContent(pos_x, pos_y, ball, nil, tcell.StyleDefault)
screen.Show()
pos_x += 1
time.Sleep(1 * time.Second)
}
}
}
のようにEventKeyでEsc(エスケープ)を押した時に終了するように書き換えても、思ったような動きになってくれません。
意図通りの動作にならない要因は、
ev := screen.PollEvent()
で何らかのイベントが着火するまで待機(ポーリング)している為です。
球を常に動かすこととキーボードからのイベントを両立させる為には、球用のイベントループとキーボードからのイベントループを分けることで実現することが出来ます。
イベントループの切り分けは下記のようにします。
package main
import (
"time"
"github.com/gdamore/tcell/v2"
)
func handleKeyEvent(s tcell.Screen, ch chan<- int) {
for {
ev := s.PollEvent()
switch ev := ev.(type) {
case *tcell.EventKey:
switch ev.Key() {
case tcell.KeyEscape:
ch <- 1
}
}
}
}
func main() {
screen, err := tcell.NewScreen()
if err != nil {
panic(err)
}
if err := screen.Init(); err != nil {
panic(err)
}
defer screen.Fini()
ch := make(chan int)
go handleKeyEvent(screen, ch)
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
pos_x, pos_y := 5, 5
ball := '●'
for {
select {
case <-ticker.C:
screen.Clear()
screen.SetContent(pos_x, pos_y, ball, nil, tcell.StyleDefault)
screen.Show()
pos_x += 1
case i := <-ch:
if i == 1 {
return
}
}
}
}
キーボード操作のイベントループは
func handleKeyEvent(s tcell.Screen, ch chan<- int) {
for {
ev := s.PollEvent()
switch ev := ev.(type) {
case *tcell.EventKey:
switch ev.Key() {
case tcell.KeyEscape:
ch <- 1
}
}
}
}
のように関数の形式にしておきます。
この関数はチャネルを用いており、main関数(エンドポイント)内で
ch := make(chan int) go handleKeyEvent(screen, ch)
のようにチャネル用の変数を用意した後にキーボード用のイベントループを実行します。
この時、関数の実行の前に go を追加することで、ゴルーチン(goroutine:並行処理)として実行しておきます。
後は
ticker := time.NewTicker(1 * time.Second) defer ticker.Stop()
とエンドポイント内のイベントループ内で
select {
case <-ticker.C:
screen.Clear()
screen.SetContent(pos_x, pos_y, ball, nil, tcell.StyleDefault)
screen.Show()
pos_x += 1
case i := <-ch:
if i == 1 {
return
}
}
のように毎秒毎の実行とキーボード用のイベントループから送信された値を受取り終了する処理を書いておきます。
処理がぶつかりそうな箇所がありましたら、その都度ゴルーチンを導入すべきか?を検討すると良いでしょう。