Ruby ではじめるプログラミング 【第 2 回】

著者:だん

はじめに

前回 (Ruby ではじめるプログラミング 【第 1 回】) は次のこのようなプログラムの基本的な部分を紹介しました。

mage2_s.png
  • Ruby を実行するまでの手順
  • 変数やメソッド(命令)呼び出し
  • プログラムには流れがあるということ
  • 制御命令を使って流れをコントロールできるということ

今回は知っているととても便利な機能を紹介します。

式展開

「あなたの名前は○○○です。」の○○○の部分に変数を当てはめて表示するプログラムを作ってみましょう。

puts '名前を入力してください。'
name = gets
print 'あなたの名前は'
print name
puts 'です。'

puts メソッドを使うと途中で改行されてしまうので、改行を行わない print メソッドを使用しています。しかし、このプログラムを実行すると期待通りの結果になりません。

「名前を入力してください。」と表示されたらキーボードから dan と入力してみます。 すると、実行結果はこうなります。

あなたの名前はdan
です。

dan のところで改行されてしまいました。これは gets メソッドで入力した文字列データの最後には改行コードが付いているからです。文字列の末尾の改行コードは chomp メソッドで取り除くことができます。

puts '名前を入力してください。'
name = gets.chomp
print 'あなたの名前は'
print name
puts 'です。'

実行結果はこうなります。

あなたの名前はdanです。

gets の後ろにある chomp は gets の戻り値である文字列を操作するメソッドです。入力された文字列の末尾の改行コードを取り除いてから変数 name に代入しているわけです。

式展開で文字列に変数を埋め込む

このプログラムは式展開という機能を使うとこのように書くことができます。

puts '名前を入力してください。'
name = gets.chomp
puts "あなたの名前は#{ name }です。"

#{} の中に変数を書いておくと、その変数の中身が文字列として埋め込まれます。 それから、式展開の機能を使うためには文字列を ' (シングルコーテーション)ではなく "(ダブルコーテーション)で囲む必要があります。

また、実は #{} の中には変数以外にもいろいろなものを書くことができます。例えば四則演算やメソッド呼び出しが書けます。

puts "1+1= #{1+1} です。"
puts "10*10+5= #{10*10+5} です。"

このプログラムは実行すると次のようになります。

1+1= 2 です。
10*10+5= 105 です。

もちろん変数を使うこともできます。また、式展開では数値も自動的に文字列へ変換されます。

a = 5
b = 2
puts "#{a}+#{b}= #{a+b} です。"
puts "#{a}-#{b}= #{a-b} です。"

このプログラムは実行すると次のようになります。

5+2= 7 です。
5-2= 3 です。

自分で実際に動かしながらいろいろ遊んでみてください。

やってみよう(その 1 )

式展開を使って作った文字列を変数に代入し、その変数を別の文字列の中で式展開してみましょう。

p メソッド

p メソッドを使うと様々なオブジェクトの中身を人間にわかりやすい形で表示することができます。

オブジェクトとは、これまでに紹介した数値文字列データのことです。

p 100
p 'abc'

この 100 'abc' オブジェクトです。このプログラムを実行すると次のような結果が表示されます。

100
"abc"

文字列データ(文字列オブジェクト)は " で囲まれて表示されます。 次のようにオブジェクトを変数に代入しても同様に中身を表示することができます。

a = 100
b = 'abc'
p a
p b

gets メソッドの戻り値を p メソッドで表示してみましょう

name = gets
p name

実行結果はこうなります。

dan           # ここで dan と入力する
"dan\n"

入力した文字列が表示されていますが、文字列の最後に \n という文字が付いています。この \n が改行コードです。 改行コードを出力すると、コマンドプロンプト上では改行して表示されます。

chomp メソッドを使って末尾の改行コードを取り除くとこうなります。

name = gets.chomp
p name

実行結果です。

dan           # ここで dan と入力する
"dan"

\n がなくなっているのがわかりますね。

p メソッドを使うといろいろなオブジェクトの中身を表示することができます。

times によるループ

times メソッドは前回も紹介しました。 times には便利な機能があるので紹介します。

times を使用したサンプルプログラムです。

5.times do |n|
  puts n
end

実行結果はこうなります。

0
1
2
3
4

5.times do の後ろに |n| という見慣れないコードがあります。

この n は変数です。変数 n には 0,1,2,3,4 が順番に n に代入されながらループ処理が実行されていきます。この機能はこの後の配列の解説でも使用するので覚えておいてください。

エラー

便利な機能の紹介からは話がずれますが、ここではエラーについて簡単に説明します。

いざプログラムを実行してみると、思わぬエラーが発生してプログラムが動かないことがあります。 エラーは単純な入力ミスや誤った文法でプログラムを書けばすぐに発生します。 エラーが発生すると、その原因を調べるための手がかりがエラーメッセージとして表示されます。

では、わざとエラーを起こしてエラーメッセージを実際に確認してみましょう。

エラーになるプログラム

これは変数 a を表示する正常なプログラムです。

a = 0
puts a

エラーが起こるように 1 行目をわざとコメントにします。

# a = 0
puts a

このプログラムを実行すると次のようなエラーメッセージが表示されます。

test.rb:2: undefined local variable or method `a' for main:Object (NameError)

このエラーメッセージは a という名前の変数やメソッドは存在しないということを知らせています。

次のプログラムでは文字列データの「こんにちは」の最後に ' がありません。

puts 'こんにちは

これを実行すると、文字列が閉じられていないという旨のエラーメッセージが表示されます。

test.rb:1: unterminated string meets end of file

次のプログラムは if に対応する end をコメントにして消してあります。

a = 0
if a == 0
  puts 'a は 0 です。'
# end

これもエラーになります。

test.rb:5: syntax error

「syntax error」は文法が間違っているときに出るエラーメッセージです。

行番号に注目

それぞれのエラーメッセージの先頭を見ると test.rb:数字: と表示されています。 test.rb はプログラムのソースファイル名です。その後ろの数字はエラーが発生している場所の行番号を示しています。

エラーには他にも種類がありますが、ほとんどの場合は該当する行番号の部分になんらかの間違いがあります。エラーが発生した場合はエラーメッセージを頼りにもう一度ソースファイルをよく確かめてみてください。

基本的にエラーメッセージは英語で表示されるのでつい敬遠したくなるかもしれません。しかし、エラーメッセージはトラブル解決の重要な手がかりになるので、英語が苦手でも注意深く見るようにしてください。

配列

多くのプログラミング言語には配列(はいれつ)という便利な機能があります。もちろん Ruby でも配列を使うことができます。

配列は少々難しい機能ですが、Ruby プログラミングを修得するうえでは避けて通れません。とても重要な機能なので、ここでは配列についてあせらずじっくりと解説します。

配列を作る

配列を使うと複数のオブジェクト(データ)をひとまとめにすることができます。配列を作るにはいくつかの方法がありますが、今回紹介するのはこの形です。

変数名 = [オブジェクト0, オブジェクト1, オブジェクト2, ……]

オブジェクト(データ)を , で区切ってデータ全体を [ ] で囲みます。そして作られた配列に変数を使って名前を付けます。

次のプログラムを見てください。ある 3 人の身長データを配列でまとめたものです。

values = [150, 174, 180]

このプログラムでは 150 と 174 と 180 という 3 つのデータをひとまとめにして、それに values という名前を付けています。values は好きなように決めることができる変数名です。

values の中身は 3 つの数字が順番に並んだ配列です。150 という数値にアクセスするには次のようにします。

values[0]

これで 150 という数値を取り出すことができます。

values = [150, 174, 180]
p values[0] # 150 を表示
p values[1] # 174 を表示
p values[2] # 180 を表示

実行結果はこうなります。

150
174
180

= を使って内容を変更することもできます。

values = [150, 174, 180]
values[0] = 155
p values[0] # 中身は 150 から 155 に変わっている
p values[1]
p values[2]

実行結果はこうなります。

155
174
180

ここでひとつ注意があります。上記のコードを見るとわかるように配列内の最初のデータは values[1] ではなく values[0] で得られます。 2 番目のデータは values[1]、3 番目のデータは values[2] です。普段わたしたちはモノを数えるときは 1 から 1, 2, 3, ……と数えますが、コンピュータは 0 から 0, 1, 2, …… と数えるのです。この慣習は少々やっかいですが、慣れればたいしたことはありません。

添字(インデックス)

values の 後ろに書いた [0] や [1] の数字部分を添字(そえじ)といいます。添字はインデックスと呼ばれることもあります。添字には数値を代入した変数を用いることができます。

values = [150, 174, 180]
index = 1          # 変数 index に 1 を代入
p values[index] # 174 が表示される

これはとても便利です。前回紹介した「じゃんけんロボット」は、配列を使うと次のように書くことができます。

puts 'じゃんけん'
sleep 1
values = ['グー', 'チョキ', 'パー']
r = rand(3) # r に 0 ? 2 の乱数を代入
puts values[r]

なんと if 文も case 文もなくなってしまいました。プログラム全体が短くなりましたね。

配列の中には数値だけでなく、文字列やその他のオブジェクトを入れることもできます。

平均身長

次のプログラムは配列にセットされた身長データから、全員の平均身長を求めるプログラムです。

   1|values = [150, 174, 180]
   2|
   3|n = values.size
   4|puts "values には #{n} 人分のデータがあります。"
   5|
   6|total = 0
   7|n.times do |index|
   8|  puts "身長が #{values[index]} cm の人がいます。"
   9|  total += values[index]
  10|end
  11|
  12|puts "全員の身長の合計は #{total} cm です。"
  13|puts "全員の身長の平均は #{total/n} cm です。"

3 行目の .size は配列の要素数を返すメソッドです。ここでは変数 n に 3 が代入されます。

7 行目の index は変数です。index には 0, 1, 2 から順に数値が代入されながらループ処理が実行されていきます。

total += values[index]

この部分は変数 total に values[index] の値を加算する処理です。

この平均身長計算プログラムの実行結果はこうなります。

values には 3 人分のデータがあります。
身長が 150 cm の人がいます。
身長が 174 cm の人がいます。
身長が 180 cm の人がいます。
全員の身長の合計は 504 cm です。
全員の身長の平均は 168 cm です。

values の中身を変更・追加していろいろ試してみてください。

配列もオブジェクト

配列の中にはどんなオブジェクトも入れることもできます。

values = [0, 5, 'abc', 'あいう']

そして、配列もひとつのオブジェクトとしてあつかうことができます。p メソッドで中身を表示してみましょう。

values = [0, 5, 'abc', 'xyz']
p values

実行結果はこうなります。

[0, 5, "abc", "xyz"]

p メソッドを使うといつでも簡単に配列の中身を確認できるのでとても便利です。 配列全体もひとつのオブジェクトだったのです。

配列データの追加と削除

push メソッドで配列の最後にオブジェクト(データ)を追加することができます。

values = []    # 空の配列を作成
p values       # values の中身を表示
values.push 0  # values に 0 を追加
p values
values.push 1  # values に 1 を追加
p values

実行結果はこうなります。

[]
[0]
[0, 1]

pop メソッドで配列の最後のオブジェクト(データ)を取り出すことができます。取り出したオブジェクトは配列から削除されます。

values = ['a', 'b']
p values
puts values.pop  # values から 'b' を削除
p values
puts values.pop  # values から 'a' を削除
p values

実行結果はこうなります。

["a", "b"]
["a"]
[]

配列の中に配列(多次元配列)

配列にはどんなオブジェクトでも入れることができます。そして、配列もひとつのオブジェクトなので、配列に配列を入れることもできます。

試してみましょう。 tbl はただの変数名です。

tbl = [[0,1,2], [3,4,5], ['a','b','c']]
p tbl

このプログラムでは、配列 tbl の中に 3 つの配列オブジェクトが入っています。このように配列の中に配列があるオブジェクトを 2 次元配列といいます。

実行してみましょう。表示結果はこうなります。

[[0, 1, 2], [3, 4, 5], ["a", "b", "c"]]

p メソッドは、多次元配列の中身も表示してくれます。

tbl から要素を取り出すにはこうします。

tbl = [[0,1,2], [3,4,5], ['a','b','c']]
p tbl[0]
p tbl[1]
p tbl[2]
p tbl[0][0]
p tbl[0][1]
p tbl[0][2]
p tbl[2][0]
p tbl[2][1]

実行結果です。

[0, 1, 2]
[3, 4, 5]
["a", "b", "c"]
0
1
2
"a"
"b"

tbl[0] は [0, 1, 2] を指しています。 tbl[0][0] で 0 を取り出すことができます。 tbl[0][1] で 1 を tbl[2][0] で 3 を取り出すことができます。

プログラムと実行結果を照らし合わせて、どのような仕組みになっているのか自分で考えてみてください。

さて、同じ配列定義をこのように書くこともできます。

tbl = [
  [0,1,2],
  [3,4,5],
  ['a','b','c'],
]
p tbl

実行結果はまったく同じです。要素の数が多い場合はこのように改行を入れた方が見やすくなります。

2 次元配列と先ほど説明した times のループ機能を使ってこのようなプログラムを作ってみました。画面にある文字絵を表示するプログラムです。

   1|tbl = [
   2|  [1,1,1,0,0],
   3|  [1,0,0,1,0],
   4|  [1,0,0,1,0],
   5|  [1,1,1,0,0],
   6|  [1,0,1,0,0],
   7|  [1,0,0,1,0],
   8|]
   9|
  10|y_max = tbl.size
  11|y_max.times do |y|
  12|  x_max = tbl[y].size
  13|  x_max.times do |x|
  14|    if tbl[y][x] == 1
  15|      print "■"
  16|    else
  17|      print "□"
  18|    end
  19|  end
  20|  print "\n" # 改行する
  21|end

2 重ループと 2 次元配列を使ったすこしややこしいプログラムですが、もう特に説明する必要はないでしょう。 パズルゲームを解く気持ちでひとつひとつの処理を自分で追ってみてください。

やってみよう(その 2 )

このプログラムを 3 次元配列( 3 重ループ)に改造して複数の絵を表示するプログラムを作成してください。

配列を使ったゲームブック

前回作成したゲームブックプログラムを配列を使って作ってみます。 プログラムを見ただけでは動作をイメージできないので、実際に実行してから次のソースを見てください。

   1|msg0 = "3本の分かれ道があります。どの道を進みますか。\n" +
   2|       "  1 左の道\n  2 真ん中の道\n  3 右の道"
   3|msg1 = "あっ!\n落とし穴に落ちてしまいました。\n〜 GAME " +
   4|       "OVER 〜"
   5|msg2 = "真ん中の道をまっすぐ歩いていくと……\n宝箱をみつ" +
   6|       "けました!\n  1 そのままにしておく\n  2 あける"
   7|msg3 = "しばらく歩き続けると もとの場所にもどってしまい" +
   8|       "ました。\n  1 次へ"
   9|msg4 = "宝箱には見向きもせず お家に帰りました。\n〜 GAM" +
  10|       "E OVER 〜"
  11|msg5 = "パカッ\nまばゆい光があふれだす……\n100枚の金" +
  12|       "貨を手に入れました!"
  13|
  14|tbl = [
  15|  [msg0,   1,   2,   3],
  16|  [msg1, nil, nil, nil],
  17|  [msg2,   4,   5, nil],
  18|  [msg3,   0, nil, nil],
  19|  [msg4, nil, nil, nil],
  20|  [msg5, nil, nil, nil],
  21|]
  22|
  23|scene = 0
  24|while true
  25|  scene_data = tbl[scene]
  26|  message = scene_data[0]
  27|  puts message
  28|
  29|  if scene_data[1] == nil
  30|    exit
  31|  end
  32|
  33|  print '  数字を入力してください '
  34|  input_value = gets.to_i
  35|
  36|  if input_value > 0
  37|    next_scene = scene_data[input_value]
  38|    if next_scene == nil
  39|      puts '不正な値が入力されました'
  40|    else
  41|      scene = next_scene
  42|    end
  43|  else
  44|    puts '不正な値が入力されました'
  45|  end
  46|
  47|  sleep 0.5
  48|  print "\n"
  49|end

先頭の msg0 ? msg5 はゲームの各場面で表示するメッセージです。スペースの都合で 2 行に渡って文字列を代入しています。

m0 = "abcxyz"
m1 = "abc" + "xyz"
m2 = "abc" +
     "xyz"

m0, m1, m2 はまったく同じ内容になります。

tbl のデータ構造

14 行目の tbl は シーンデータの配列です。

tbl = [
  シーンデータ,
  シーンデータ,
  シーンデータ,
]

シーンデータはそのシーンで表示する「メッセージ」と「ジャンプ先 ID」の配列です。

["メッセージ", ジャンプ先 ID1, ジャンプ先 ID2, ジャンプ先 ID3 ]

「ジャンプ先 ID 」にはそのシーンで選択された選択肢に対応したジャンプ先の ID (インデックス)を入れておきます。

  • 選択肢 1 が選択されたときは「ジャンプ先 ID1 」へジャンプする
  • 選択肢 2 が選択されたときは「ジャンプ先 ID2 」へジャンプする
  • 選択肢 3 が選択されたときは「ジャンプ先 ID3 」へジャンプする

nil は特別なオブジェクトです。今回は nil について説明しませんが、選択肢がない場合は nil を入れておくことにします。また、選択肢 1 が nil だった場合はそのシーンでゲームを終了することにします。

まとめると tbl は以下のような構造の 2 次元配列です。

tbl = [
  ["メッセージ", ジャンプ先 ID1, ジャンプ先 ID2, ジャンプ先 ID3 ],
  ["メッセージ", ジャンプ先 ID1, ジャンプ先 ID2, ジャンプ先 ID3 ],
  ["メッセージ", ジャンプ先 ID1, ジャンプ先 ID2, ジャンプ先 ID3 ],
]

シーンデータを取り出す

scene_data = tbl[scene]

でその場面のシーンデータ(メッセージとジャンプ先 ID)の配列を変数 scene_data に代入しています。変数 scene には場面番号が入っています。場面番号が tbl のインデックスとなり、tbl[scene] でその場面のシーンを取り出すことができます。

プログラムの終了条件

if scene_data[1] == nil
  exit
end

という部分は「ジャンプ先 ID」が nil だったらプログラムを終了するという意味です。

シーンの変更

input_value には選択された数字が代入されます。gets.to_i で入力された文字を数値に変換していますが、to_i メソッドは数字以外の文字が与えられた場合は 0 を返します。

input_value が 0 より大きい値だった場合は正常処理を行い、それ以外(0 以下)の場合は不正な値が入力されたとみなし、シーン変更の処理は行いません。 36 行目がこの場合わけの判定になります。

input_value が 0 より大きい値だった場合は

next_scene = scene_data[input_value]

で次のシーンの番号を取り出します。ここで問題があります。input_value の値が 4 以上だった場合はシーンデータ配列の範囲外のデータを取り出そうとしてしまいます。Ruby では配列の範囲外のデータを取り出そうとした場合は nil が返ります。38 行目の

if next_scene == nil

で場合わけをすれば next_scene に正しい値が代入できたかどうかが判定できます。 正しい値だった場合は 41 行目の

scene = next_scene

で scene を書き換えます。scene が書き換わると場面も移行します。

配列の利点

前回は場面をすべて if 文や case 文で場合わけすることで処理していました。 つまり場面が増えるとその分 if 文や case 文の場合わけを追加して制御構造を追加する必要がありました。

しかし、今回のプログラムではシーンデータを配列データにしたことにより、場面を増やす場合は配列内のデータを増やすだけです。シーンをいくつ増やしても制御構造を増やす必要はありません。これはとても大きなメリットです。テーブルデータを変更するだけで新しいシナリオを追加したり、シーンジャンプの仕組みを変更したりできます。

やってみよう(その 3)

テーブルデータを変更して新しいシナリオのゲームを作成しながら遊んでみてください。 面白いスクリプトができたら、ぜひ るびま編集部 まで送ってください。

まとめ

今回はいくつかの便利な機能と配列について紹介しました。 そして、特に配列に重点を置いて解説しました。

配列を使ってゲームで使用するメッセージやシーンのジャンプデータをテーブル化する方法はとても便利です。 プロジェクトが大きくなればなるほど、このような工夫が開発効率を高めることにつながります。そして、開発効率がよくなれば同じ労力でより優れたアプリケーションが作ることができます。

次回は今回紹介したデータテーブルをより使いやすくする方法を紹介します。

著者について

だん (dan at dgames dot jp)

ゲームメーカーに勤めるゲームクリエイター。

最近までずっとコンシューマーゲームソフトの開発という閉じた世界にいた。 Ruby 歴は 2 年と短く、まだまだ勉強中の初心者。

Ruby ではじめるプログラミング 連載一覧