ターミナル上に出力した球(●)を常に動かせるようにしたいとする。
この要件を満たす為には、
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 } }
のように毎秒毎の実行とキーボード用のイベントループから送信された値を受取り終了する処理を書いておきます。
処理がぶつかりそうな箇所がありましたら、その都度ゴルーチンを導入すべきか?を検討すると良いでしょう。