はじめに
みんなー、雑にゲーム作って遊んでるー!? (ゝω・)v
今日はrubyでCLI(CUI)、つまりコンソール上で操作するためのUIを簡単に実装できるgemのTTYについて紹介しよう。同様のことをしてくれるgemは他にもcursesやthorなどがあるがTTYは最も様々な機能が充実している。つよい。
TTYが提供するものは
- yes/no、複数選択肢の入力UI
- Pager、unixのlessのように長文を表示するUI
- プログレスバーの表示
- 表示する文字列の装飾(前景色/背景色/bold...)
- テーブル表示(mysqlのコンソール出力のような)
などなど。上記は代表的なものだけを抜き出しただけで14種類ものライブラリがTTYにパッケージされている。詳しくは本家githubを参照してほしい。
https://github.com/piotrmurach/tty
題材とするゲーム
今回、CUIゲーム化するのはこのゲーム。ゲームマーケット2013に出版された可愛い動物のドラフトゲーム、Banquet。数分で終わるお手軽な読み合いゲームだ。
勝手にプログラムなんか書いて公開して怒られないのかって? 大丈夫、制作者は自分だ。
遊べる状態のコードをgithubに上げておいたから参考にしたり遊んだりしてくれ。
…ただ、すまないけどまともなAIはまだ書いてないんだ。
https://github.com/owlworks/banquet
選択肢の表示
さて。では実際にコードを書いてみよう。まずは選択肢UIについて。
TTYには強力な選択肢機能がある。実際に試してみよう。
require 'tty'
animal = TTY::Prompt.new.select('好きな動物は?') do |menu|
menu.choice 'ねこ'
menu.choice 'Cat'
menu.choice 'にゃんこ'
menu.choice 'Felis silvestris catus'
end
puts animal
ちょうクールだね!
でも返り値が表示されてる名前そのままじゃ実際の処理では使いにくいよね。大丈夫、TTY::Prompt#selectはmenu.choiceで表示名ではなくハッシュ型の返り値を得ることも出来るんだ。例えばこんな風にね。
require 'tty'
animal = TTY::Prompt.new.select('好きな動物は?') do |menu|
menu.choice 'ねこ', {key: :neko, num: 1}
menu.choice 'Cat', {key: :cat, num: 2}
menu.choice 'にゃんこ', {key: :nyanko, num: 3}
menu.choice 'Felis silvestris catus', {key: :catus, num:4}
end
puts animal.inspect
=> {:key=>:nyanko, :num=>3}
ぐれいと。こうなればBanquetに含まれる、手札の中からカードを選んでもらうような処理も書けるね。
# 引数pileはCardオブジェクトを要素とする配列, descriptionは選択股の説明文
def select_card_cli(pile, description)
refresh_layout_game # カードを選ぶ前に盤面の情報を更新する
choice = TTY::Prompt.new.select(description) do |menu|
pile.each_with_index do |card, index|
menu.choice card.layout, {index_num: index}
end
end
choice
end
pick = select_card_cli(pile, 'Pickup card for your hand.')
puts pick.inspect
=> {:index_num=>0}
また、menu.choiceで指定する表示名は改行コードを含めて2行となってもよい。その場合も1行めの文字列にカーソルが合う。実際、上のコードに書かれているcard.layoutはこんな内容になっていて改行コードを含んでいる。
pile.first.layout
=> "\e[30;41;1m[ ネコ ] (3)\e[0m\n プレイしたとき対戦相手がプレイしたカードとこのカードを入れ替える"
文字の装飾
「\e[30;41;1m」とかいうのは何かって? よし、その話をしよう。これはTTYの一部であるpastelの機能だ。Card#layoutの中身はこうなっている。
# 説明に関係ある部分だけ抜粋
class Card
attr :card_key, :name, :text, :color
INDENT = ' ' * 5
def layout
[name_layout, text_layout ].join("\n")
end
def name_layout
PASTEL.black.send("on_#{@color.to_s}").bold("[#{@name.center(8)}] (#{@score})")
end
def text_layout
(INDENT + @text).each_char.each_slice(80).map(&:join).join("\n#{INDENT}")
end
end
ちょっと書き方がわかりにくいので公式githubのシンプルな例で説明しよう。
require 'tty'
pastel = Pastel.new
decorated = pastel.black.on_red.bold('Oh, How beautiful you are!')
puts decorated
puts decorated.inspect
Pastelオブジェクトをメソッドチェーンして末端のメソッドに文字列を引数として渡して上げるとコンソール上に標準出力された際に装飾される文字記号が付与されて返って来る。
(指定可能な色やメソッドについては公式githubを。 https://github.com/piotrmurach/pastel)
締め
今回のゲームで利用したのはここまで。TTYの機能を利用すればCUIアプリケーションのUI部分で困ることはまずないだろう。さっそくちょっとだけUIがクールなCUIアプリを書こう! まったくこの内容でコードの総量がたったの250行くらいだっていうからお手軽だよ。
現場から以上です。╭( ・ㅂ・)و