Rubyからncurseswを使って簡単なゲームを作った話です。とりあえず例として作成してみた避けゲーの動作の様子を以下に示します。
動作環境
- Ubuntu 14.04
- Ruby 2.2.0
- ncursesw(gem) 1.4.9
必要なもののインストールは以下のコマンドで
$ sudo apt-get install libncursesw5-dev
$ gem install ncursesw
ncurses でもいいのですが、今回は日本語なども文字化けすることなく表示できる ncursesw を選択しました。
ソースコード
以下に示す main.rb を実行すれば冒頭のGIFと同じゲームが始まります。操作方法とルールは上下左右の方向キーで自機を動かし、飛んでくる弾を避けるだけです。
画面の大きさに合わせて弾が生成されるので、実行時のサイズは縦横ともに制限はありません。むしろそれによって難易度調整ができるとも言えます。
require 'ncursesw'
class MiniGame
def initialize
Ncurses.initscr
Ncurses.curs_set(0) # カーソル非表示
Ncurses.cbreak # RAW(改行を待たない)モードにする
Ncurses.noecho # 入力された内容をエコーしない
Ncurses.keypad(Ncurses.stdscr, true) # KEY_* 用
color_config
@height, @width = Ncurses.LINES, Ncurses.COLS
@exit = false
@player = [@height - 1, (@width / 2) - 2]
@bullets = Array.new(@width - 1) do
Array.new(@height) { false }
end
end
# 全てのスレッドを実行する
def run
threads = []
threads << drawing_thread
threads << input_thread
threads << game_thread
threads.each(&:join)
close_scr
rescue Interrupt
close_scr
end
private
# 色関連の設定
def color_config
@has_colors = Ncurses.has_colors?
if @has_colors
Ncurses.start_color
if Ncurses.use_default_colors == Ncurses::OK
Ncurses.init_pair(1, Ncurses::COLOR_CYAN, -1)
end
end
end
# 描画するスレッド
def drawing_thread
Thread.new do
until @exit
draw_player
draw_bullets
Ncurses.refresh
sleep 0.01
Ncurses.clear
end
end
end
# 入力を監視するスレッド
def input_thread
Thread.new do
until @exit
case Ncurses.getch
when "q".ord then @exit = true
when Ncurses::KEY_UP
@player[0] -= 1
@player[0] = 1 if @player[0] < 2
when Ncurses::KEY_DOWN
@player[0] += 1
@player[0] = @height - 1 if @player[0] > @height - 1
when Ncurses::KEY_RIGHT
@player[1] += 1
@player[1] = @width - 3 if @player[1] > @width - 3
when Ncurses::KEY_LEFT
@player[1] -= 1
@player[1] = 1 if @player[1] < 1
end
end
end
end
# 飛んでくる弾を生成したり当たり判定をしたりするスレッド
def game_thread
Thread.new do
until @exit
@bullets.map! do |row|
row.unshift rand < 0.02
row.pop
row
end
@exit = true if gameover?
sleep 0.1
end
Ncurses.move(0, 0)
Ncurses.addstr("Game Over!\n")
Ncurses.addstr("Please press any key...\n")
end
end
# 自機を描画
def draw_player
Ncurses.attron(Ncurses::COLOR_PAIR(1)) if @has_colors
Ncurses.move(*@player.zip([-1, 0]).map{ |a, b| a+b })
Ncurses.addstr("▓")
Ncurses.move(*@player.zip([0, -1]).map{ |a, b| a+b })
Ncurses.addstr("▓▓▓")
Ncurses.attroff(Ncurses::COLOR_PAIR(1)) if @has_colors
end
# 飛んでくる弾を描画
def draw_bullets
@bullets.each_with_index do |row, j|
row.each_with_index do |bullet, i|
if bullet
Ncurses.move(i, j)
Ncurses.addstr("▓")
end
end
end
end
# 弾にあたったらゲームオーバー
def gameover?
@player.tap do |i, j|
break [[i, j], [i, j-1], [i, j+1], [i-1, j]]
.any? { |pi, pj| @bullets[pj][pi] }
end
end
# 後処理
def close_scr
Ncurses::curs_set(1)
Ncurses.nocbreak
Ncurses.echo
Ncurses.endwin
end
end
game = MiniGame.new
game.run
ncurseswの使い方
Rubyで ncursesw や ncurses を扱うことについて書かれたサイトはそんなに多くは見かけませんが、C言語でのサンプルとか説明が豊富で、結構参考になります(関数名とかは同じなので)。ここでは主に今回つくったゲームで使った機能について触れます。
初期設定と後処理
initialize メソッド内のもろもろの設定を行うメソッドと、close_scr メソッド内のそれを念の為元に戻すメソッドなどについて。
-
Ncurses.initscr
/Ncurses.endwin
- 初期化のため最初に呼び出す / こちらはプログラムの最後に
-
Ncurses.curs_set(0)
/Ncurses::curs_set(1)
- 0でカーソルを非表示 / 1で表示
-
Ncurses.cbreak
/Ncurses.nocbreak
- キー入力において改行を待たずに入力が即座に反映されるように
- する / しない
-
Ncurses.noecho
/Ncurses.echo
- キー入力をスクリーンにエコー
- しない / する
-
Ncurses.keypad(Ncurses.stdscr, true)
- 後述の Ncurses.getch において、ture にすることでファンクションキーや方向キーなどを押した際にキーコードを返してくれるようになる
色の設定
色は背景色と前景色がペアとして扱われ、そのペアにつけられた番号を使って文字や背景への色付けを行います。
def color_config
@has_colors = Ncurses.has_colors?
if @has_colors
Ncurses.start_color
if Ncurses.use_default_colors == Ncurses::OK
Ncurses.init_pair(1, Ncurses::COLOR_CYAN, -1)
end
end
end
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def draw_player
Ncurses.attron(Ncurses::COLOR_PAIR(1)) if @has_colors
#
# この間はCOLOR_PAIR(1)の色で描画される
#
Ncurses.attroff(Ncurses::COLOR_PAIR(1)) if @has_colors
end
-
Ncurses.has_colors?
- 現在の端末で色が使用できるか否かを返す
-
Ncurses.start_color
- 色の使用を開始する際に呼び出す
-
Ncurses.use_default_colors
- 背景色と前景色に使う色番号をどちらも -1 にする
- -1 はデフォルトの端末の背景色・前景色の意味
-
Ncurses.init_pair(1, Ncurses::COLOR_CYAN, -1)
- 色のペアをつくる
- この例ではペア番号の1に前景がCYAN、背景がデフォルト色というペアになっている
- 設定したペアの使い方は draw_player メソッド内を参照
その他
-
Ncurses.getch
- キーの入力待ちをする
- Ncurses::KEY_* のようにそれぞれのキーに定数が用意されている
-
Ncurses.move(i, j)
- スクリーン上のi行j列にカーソルを移動
-
Ncurses.addstr("文字列")
- カーソル位置に文字列を追加
-
Ncurses.refresh
- addstr などで追加した文字列をスクリーンに反映
-
Ncurses.clear
- スクリーン上の文字列などの情報をクリア
おわりに
この避けゲーは以前つくった常駐型アプリっぽいものにミニゲームとして実装されているので、もしよければこちらものぞいてみてください。
参考サイト
https://github.com/sup-heliotrope/ncursesw-ruby
NCURSES Programming HOWTO
せりか式 ncurses