RQt Tic-Tac-Toe ~rqtで遊べるマルバツゲーム~
サンタさんから久しくゲームをもらっていない皆さん、こんにちは。
私からのクリスマスプレゼントです。
rqt_tic_tac_toeとは
rqt_tic_tac_toe
は、Tic-Tac-Toe(○×ゲーム、あるいは三目並べ)のRQt用プラグインです。
簡単に言うと、RQtで動く○×ゲームです。
この記事では、まずインストール方法と遊び方を説明して、その後に技術的な話をします。
インストール
ソースコードはGitHubに公開しています。これをダウンロードしてビルドします。
cd ~/ros2_ws/src
git clone https://github.com/ShotaAk/rqt_tic_tac_toe.git
rosdep install -r -y -i --from-paths .
cd ~/ros2_ws
colcon build --symlink-install
RQtにプラグインを認識させるため、次のコマンドを実行します。
source ~/ros2_ws/install/setup.bash
rqt --force-discover
RQtのPlugins
にTic-Tac-Toeが追加されたらインストール完了です。
遊び方
黒色のボードをポチポチ押して遊んでください。
リセット機能もあるので無限に遊べます。やったね!
ボードサイズの変更
ボードサイズを変えることもできます。
2x2 は短期決戦におすすめです。
2x2に飽きたらボードサイズを大きくしましょう。頭を使いすぎないように注意してね。
対戦モード
対戦モードも搭載してます。
ネットワーク(同じROS_DOMAIN_ID)に接続して白熱したオンライン対戦を楽しもう!
frame ID
にあなたの名前を入力したら、ボードをポチポチして遊んでください。
対戦相手が見つかると、sync ID
に名前が表示されます。
戦いたい相手をsync ID
にセットして、Resetしたらゲーム開始です!
どちらが先手を取るのか、緊張の一手を楽しんでください。
マウスカーソルのリアルタイム表示
また、対戦モードでは相手の動きがリアルタイムで表示されます。
カーソルのその先を読む、まさに心理戦。
まとめ
いかがでしたか?
これからRQtを起動するのが楽しみになりますね!
ここから技術的な話
rqt_tic_tac_toeの技術的な内容を解説します。
RQtプラグイン作成の始め方
RQtプラグインは、ROS 2パッケージとして作成することで使用できます。
パッケージの作り方が分かればよいのですが、RQtについて丁寧に解説された公式ドキュメントはありません。
そのため、web記事を参照するか、他のRQtパッケージを流用して開発することになります。
私は下記の記事とrqt_topic
パッケージを参考にしました。
RQtプラグインの作り方はブログで紹介されているので、ここでは重要な項目だけ解説します。
Pythonで作るかC++で作るか
RQtプラグインはC++、Pythonのどちらでも作成できます。
先程のrqt_topic
はPythonで実装されています。
rqt_image_view
パッケージはC++で実装されています。画像の処理時間を気にするため、C++を選択したのだと思います。
rqt_tic_tac_toe
は重い処理を実行しないので、Pythonで開発することにしました。
Qt DesignerでGUIを作ると簡単
GUI画面の作り方はコードで生成する、またはQt Designerで作成するの2択です。
コードで生成する場合、拡張性や自由度は高くなりますが、どのようなデザインになっているのか理解しづらくなるという難点も生まれます。
そのため、今回はQt DesginerでGUIを作成しました。
Qt DesignerはROS 2インストール時に一緒にインストールされます(未検証)。
例えば、rqt_tic_tac_toeのGUIは次のコマンドで編集できます。
$ cd rqt_tic_tac_toe/resource
$ /usr/lib/qt5/bin/designer TicTacToeWidget.ui
コマンドを実行すると次のような画面が表示されます。
Qt Desginerの使い方は公式ドキュメントを参照してください。
ボードをカスタムウィジェットとして作成
Tic-Tac-Toeのボードの作り方が特殊なので解説します。
先程のQt Desginerの画面を見るとTic-Tac-Toeのボードが描かれてないことがわかります。
ボードはQtのQWdigetを継承したカスタムウィジェットとして作成しています。
Qt Desginerに用意されているボタンやラベルでは、ボードを表現するのが難しいのでカスタムウィジェットとして作ることを選択しました。
カスタムウィジェットの場合、ウィジェットに何をさせるか、というのをコードで実装します。
今回は詳しく解説しませんが、実装を見たい場合はboard_wdiget.py
のpaintEvent
から見ることをおすすめします。
カスタムウィジェットの作り方
カスタムウィジェットの作り方を簡単に紹介します。
- GUIに
Widget
を置く - Widgetを右クリックし
格上げ先を指定
を選択 -
格上げされたクラス名
に、これから作るPythonのクラス名を入力 -
ヘッダファイル
に、これから作るPythonモジュール名を入力 -
格上げ
を押す
- コード内でUIファイルを読み込む際に、辞書型で
ウィジェット名 : クラス名
を設定する
以上です。
重要なPythonスクリプトの紹介
rqt_tic_tac_toe
で登場する重要なPythonスクリプトファイルを紹介します。
詳細はGitHubを見てください。
- tic_tac_toe.py: プラグインの本体。ROSノードの本体もここにいる。
- board_widget.py: ボードを描画するカスタムウィジェット。
- game.py: Tic-Tac-Toeのゲーム部分。
RQtプラグインでトピックをPub/Subする
RQtプラグインの中にもROSノードがいるので、トピックをPub/Subできます。
ノードの作り方:
Pub/Subの作り方:
コードを見ると、tic_tac_toe/command
とtic_tac_toe/cursor_pos
というトピックを、
自分自身でPub/Subしていることがわかります。
ここが通信対戦を実現するポイントです。
通信対戦の同期方法
rqt_tic_tac_toe
の通信対戦は、トピック内のframe_idを読むことで実現しています。
それぞれのノードが操作コマンド(tic_tac_toe/command
)をPublishし、Subscribeします。
受け取ったframe_idが、GUIで設定しているSync IDと一致したら、相手のコマンドを自身のボードに反映する。という仕組みです。
同じ名前のトピックでやり取りしているので、相手のノード名が何なのかを気にする必要が無いです。
相手カーソルの取得
相手カーソル位置(ボード上の緑色の丸)も同じ仕組みで取得しています。
tic_tac_toe/cursor_pos
というトピックとしてカーソル位置をPublishし、お互いにSubscribeしています。
ボード描画機能の実装
ボード描画機能は、board_widget.pyに実装しています。
カスタムウィジェットの話で登場したものです。
全て解説するのは大変なので、要点だけまとめます
- tic_tac_toe.pyから、16 msec周期でupdateされる: リンク
- updateすると、描画関数paintEvent()が自動で実行される: リンク
- マウス入力があると、mouse( )Event関数が自動で実行される: リンク
Tic-Tac-Toeのゲーム部分の実装
ゲーム部分はgame.pyに実装しています。
正しく動作しているか確認するため、ユニットテストファイルも作成しました。
例:
実現できなかったこと
最後に、今回のTic-Tac-Toeで実現できなかったことをまとめます。
サービスによる対戦申し込み
同じ名前のサービスを用意すると、自分自身のサービスサーバにアクセスしちゃうので実装を諦めました。
サービス名にネームスペースを用意して、動的にクライアントを生成したら解決するかな?
特定ノードが出すトピックだけサブスクライブする
今回の実装では、同じ名前のトピックを全てのノードがPublishしてます。
ノードが増えると(Tic-Tac-Toeをたくさん起動すると)、Pub/Subするトピック量も増えます。
おそらく、Subscribeの処理が追いつかなくなります。
こちらも、動的にSubscriberを作れば解決するのかな?
ボードを大きくしても3目並べとして遊べること
今回の実装では、ボードサイズに合わせて、4目並べ、5目並べ、とルールが変わります。
実装してないだけです。
とはいえ、これ以上ゲーム性を高めることに意味があるのか、悩ましいです。
ロボットとの連携
tic_tac_toe/command
トピックをPublishすれば、RQtが無くてもボードを操作できます。
現実世界のロボットでマルバツを描き、画面の向こうの人間と戦う、みたいなことができるかも?
終わりに
長くなりましたが、最後に一言だけ、
良いお年を。
Discussion