課題
もくじ
プログラムテンプレート
次に示すプログラムのテンプレート(雛型)を使って下さい. このプログラムは名前を適宜変えた上で保存して利用してください.
Hash(ハッシュ)
Paletteで色データを管理するためにHash(ハッシュ)オブジェクトを利用します.
ハッシュは整数以外のデータを添字として利用できる配列のようなオブジェクトで, 「キー」(key)を「値」(value)に対応付けた要素の集まりです.
table = { } # 空のハッシュ(空の配列と同様のもの) # キーと値の対応の登録 # :red → Symbol # 以下の「:名前」も同様にSymbol(Symbolは名前で区別されるオブジェクトです.課題ではSymbolの詳細を理解する必要はありません) table[:red] = [255,0,0] # :redに[255,0,0]を対応付ける ## ここでtableをpメソッドで表示すると次のようになる p table # { :red=>[255,0,0] } table[:blue] = [0,0,255] # :blueに[0,0,255]を対応付ける # ここでtableをpメソッドで表示すると次のようになる p table # { :red=>[255,0,0], :blue=>[0,0,255] } # キーに対応する値の参照 r = table[:red] # r == [255,0,0] b = table[:blue] # b == [0,0,255] # 存在しないキーの値を参照→nil y = table[:yellow] # y == nil # ハッシュに指定したキーが存在しているかどうかを確認(has_key?) table.has_key?(:red) # ==> true (:redは存在する) table.has_key?(:blue) # ==> true (:blue存在する) table.has_key?(:yellow) # ==> false (:yellowは存在しない) # キーに対応する値は変更可能 table[:red] = [224,0,0] # :redに[224,0,0]を対応付ける # ここでtableをpメソッドで表示すると次のようになる p table # { :red=>[224,0,0], :blue=>[0,0,255] }
上に示したように,ハッシュに存在しないキーの値を参照するとnilが得られます. nilは「偽」として扱われます(他に「偽」として扱われるのはfalseのみ).
[参考] ハッシュのデフォルト値
ハッシュに存在しないキーに対応する値をハッシュのデフォルト値と呼びます. 「ハッシュのデフォルト値」のデフォルト値は「nil」です. デフォルト値を変更することもできます.
colid = { :red=>1, :blue=>2 } colid[:red] # ==> 1 colid[:blue] # ==> 2 colid[:yellow] # ==> nil (とくに設定しない限り,存在しないキーに対応するデフォルト値はnil) colid.default = -1 # デフォルト値を-1にする colid[:red] # ==> 1 colid[:blue] # ==> 2 colid[:yellow] # ==> -1 (この時点では存在しないキーに対応するデフォルト値は-1) # キーが存在しているかどうかはデフォルト値の設定とは無関係 colid.has_key?(:red) # ==> true colid.has_key?(:blue) # ==> true colid.has_key?(:yellow) # ==> false
Paletteクラスの仕様
クラスPaletteで描画色を管理するオブジェクトの仕様を以下のように 定めることにします.
パレットのデータ(インスタンス変数)
- @table
色表.パレットに登録されている色データの実体で,色名にRGB値を対応づけた「色セル」の集まり. 色名はSymbol,RGB値は配列[R,G,B]で表現する. つまり「色セル」は次のように色名からその色のRGB値を得るためのものである.色セル: :色名 → [R,G,B]
色表にはいくつでも色セルを順次登録可とする.一度登録した色セルのデータ(色名,RGB値)は変更しないことにする.色表はHashで実現する. - @default
デフォルトカラー[R,G,B](既定のRGB値,1色のみ)
デフォルトカラーの利用法は以下に示す. デフォルトカラーには色名は付けない. デフォルトカラーのRGB値はPaletteを生成するときに引数として渡す. デフォルトカラーは最初に設定した後で変更することは考えなくてよい.
パレットのインスタンスメソッド
- add(name,color)
色表に色名nameの色セルがまだ登録されていない場合, 色名がname,RGB値がcolorの色セルを色表に追加する. 色表に色名nameの色セルが既に登録されていた場合は何もしない.
# パレット生成,[0,0,0]はデフォルトカラー # 生成直後はパレットは空である(登録されている色セルは一つもない)とする pt = Palette.new([0,0,0]) # @table == { },@default == [0,0,0] # 色セルを登録(3色) pt.add(:red,[255,0,0]) # @table == { :red => [255,0,0] } pt.add(:blue,[0,0,255]) # @table == { :red => [255,0,0], :blue => [0,0,255] } pt.add(:yellow,[255,255,0]) # @table == { :red => [255,0,0], :blue => [0,0,255], :yellow => [255,255,0] } # :redを登録?? → :redは登録済みであり再登録はしないことにする pt.add(:red,[244,0,0]) # @table == { :red => [255,0,0], :blue => [0,0,255], :yellow => [255,255,0] } (:redのRGB値は変更されない)
色表に色名nameの色セルが登録されていれば,その色[R,G,B]を返す. 色表に色名nameの色セルが登録されていない場合はデフォルトカラー(@default)を返す.
# 上の例の続きとする(:red,:blue,:yellowは登録済み) r = pt.color(:red) # r == [255,0,0] b = pt.color(:blue) # b == [0,0,255] y = pt.color(:yellow) # y == [255,0,0] # 未登録の色(:cyan)を指定→デフォルトカラーを返すことにする c = pt.color(:cyan) # c == [0,0,0](デフォルトカラー)
色表で,色名xnameの色セルが未登録であり,色名name0とname1の色セルがともに登録されている場合に,色を混ぜあわせて新しい色を色名xnameで登録する.
具体的には,色name0のRGB成分(r0,g0,b0)をそれぞれalpha倍した値と色name1のRGB成分(r1,g1,b1)をそれぞれ(1-alpha)倍した値を合成した色(r2,g2,b2)を作る.ただし0≦alpha≦1とする.
(r0,g0,b0),(r1,g1,b1),(r2,g2,b2)の関係は次のように表現できる(プログラムでこのように記述するわけではないので注意).
(r2,g2,b2) = alpha*(r0,g0,b0) + (1-alpha)*(r1,g1,b1)
ただし(r2,g2,b2)は整数値になるように四捨五入する(roundを使う). この色(r2,g2,b2)を色名xnameで登録する. xnameが登録済みかname0,name1のうち少なくともどちらか一方が登録されていないときは何もしない.
# 上の例の続きとする(:red,:blue,:yellowは登録済み) pt.mix(:violet,:red,:blue,0.5) # この時点で色表は次のようになる # @table == { :red => [255,0,0], :blue => [0,0,255], :yellow => [255,255,0], :violet => [128,0,128] } # :violetを登録?? → 何もしない(:violetは登録済み.変更しないことにする) pt.mix(:violet,:red,:blue,0.25) # :cyanを登録??(ここで指定した:greenは未登録) → 何もしない(混ぜる色が両方とも登録されているときしか登録しない) pt.mix(:cyan,:blue,:green,0.75) # roundメソッドの例 x = 12.3 y = x.round # y == 12 z = 4.56 w = z.round # w == 5
Paletteオブジェクトの初期化メソッドである.
newメソッドから自動的に呼び出される. インスタンス変数の値を初期化する. このメソッドはテンプレートで定義済みである.
def initialize(default_color) @table = { } # 空の色表(Hash)を生成 @default=default_color # デフォルトカラーを設定 end
Palette用のTurtleの拡張
PaletteオブジェクトをTurtleに組み込んで利用できるようにします. そのためにTurtleにインスタンス変数とインスタンスメソッドを追加します.
- @palette: Paletteオブジェクトを保持するTurtleのインスタンス変数
- create_palette: Paletteオブジェクトを生成して,@paletteに対応付けるメソッド
- add_color: @paletteに色を追加するメソッド
- use_color: Paletteの色名かRGB値[R,G,B]か明るさを指定して,Turtleの描画色を指定するメソッド
- [OPTION] mix_color: @paletteに登録済の色を2つ混ぜ合わせて新しい色を登録するメソッド # Paletteのmixメソッドを実現した場合のみ定義する
このような定義を導入する目的は,TurtleのdrawメソッドでPaletteオブジェクトを意識しないで使えるようにすることです. 具体的にはdrawにおいて, Turtle自身の機能であるかのように create_palette, add_color,use_color,(あるいは mix_color)を使うことで,適宜Paletteに色を登録したり, 登録した色(あるいはR,G,Bで指定した色,明るさ)を使えるようにしたいわけです(後述の「補足説明」も参照して下さい).
なおcreate_paletteはテンプレートで次のように定義済みです.
- create_palette(*c)
cをデフォルトカラーとするPaletteを生成して, Turtleのインスタンス変数@paletteに対応付ける. create_paletteの実行後は,@paletteをPaletteオブジェクトとして使える(@palette.addのようにしてPaletteのメソッドを利用可能).# 引数の前に*を付けて定義 # →この場合,メソッドに渡された引数をすべて並べた配列がcに代入される def create_palette(*c) # c = 引数をすべて並べた配列 @palette = Palette.new(c) end def draw # このときcreate_paletteではc == [0,0,0]となる. # create_palette内部では@palette = Palette.new([0,0,0])が実行される create_palette(0,0,0) end
上の例のように,Rubyではメソッド定義において引数の前に*をつけると, 配列にして,当該の引数に対応付けます(このような引数の定義には制限があります.詳細は省略).# 第1引数はm,第2引数以降は順に並べて配列にしてaとする. def scale(m,*a) a.map { |x| m*x } end # scaleで,m == 3, a == [1,2,3] b = scale(3,1,2,3) # ==> b == [3*1,3*2,3*3] == [3,6,9] # 最後の引数はm,それ以外は順に並べて配列にしてaとする. def scale2(*a,m) a.map { |x| m*x } end # scale2で,a == [1,2,3,4], m == 5 c = scale2(1,2,3,4,5) # ==> c == [5*1,5*2,5*3,5*4] == [5,10,15,20]
add_color,use_color(とmix_color)メソッドは以下を参照して,定義して下さい.
- add_color(name,color)
Paletteのaddメソッドを使って, パレット(@palette)に色名name,RGB値がcolorの色を登録する### add_colorはTurtleのインスタンスメソッドであることに注意 def draw # インスタンス変数@paletteにPaletteを準備する # デフォルトカラーは黒とする create_palette(0,0,0) # @paletteに:redを[255,0,0]で登録する add_color(:red,[255,0,0]) # @paletteに:blueを[0,0,255]で登録する add_color(:blue,[0,0,255]) : end def add_color(name,color) end # use_colorについては以下を参照のこと def use_color(*c) end
- use_color(*c)
パレット(@palette)に登録されている色名か,(R,G,B)の値か,明るさによって描画色を定める. 上にも示した通り,「*c」のように引数に「*」を付けると,メソッドでは「c」が配列になる. 具体的にはuser_colorは「c」に指定されたデータ次第で次のように色を設定することにする.
- cの要素が1個でSymbolのときはc[0]を色名として, パレット(@palette) から当該の色データを取り出してset_colorで色を設定する.
- cの要素が1個でSymbolでないときはc[0]が 整数値(0-255)であることを仮定して, その値を明るさとして,set_colorで色を設定する. set_colorは指定された引数が1個で整数yの場合は,明るさyの白黒のモノクロカラーに設定する(内部ではR,G,Bをすべてyに設定する).
- それ以外のときは要素が3つですべて整数値(0-255)であることを仮定して, それらをR,G,Bとしてset_colorで色を設定する.
ここで示した通り,色データが得られたら実際の色の設定にはこれまで通りset_colorを使うことになる.### use_colorはTurtleのメソッドであることに注意 def draw : : # 上のadd_colorの例の続きとする # @paletteは生成済 # @paletteに登録されている色={:red,:blue} # またデフォルトカラーは黒色(0,0,0) len = 100 # 赤色の指定 use_color(:red) forward(len) # set_color(0,255,0)と同じ use_color(0,255,0) turn(90) forward(len) # set_color(192)と同じ(灰色(R=G=B=192)の指定) use_color(192) turn(90) forward(len) # 登録していない色名が指定された場合(→デフォルトカラーに設定する.つまり黒色になる) use_color(:green) turn(90) forward(len) end def add_color(name,color) end def use_color(*c) end
# [参考] オブジェクトのクラスの取得(クラス名として得られる) s = :red # Symbol a = [0,1,2] # 配列 h = { } # ハッシュ c0 = s.class # c0 == Symbol c1 = a.class # c1 == Array c2 = h.class # c2 == Hash c0 == Symbol # ==> true c1 == Symbol # ==> false # [参考] 配列の要素数(size) # a=[0,1,2] (上で定義した配列) n = a.size # n == 3
- [OPTION] mix_color(xname,name0,name1,alpha) # Paletteのmixメソッドを実現した場合のみ定義すればよい
Paletteのmixメソッドを使って, パレット(@palette)の色name0,name1をalpha:(1-alpha)の割合で混ぜあわせて パレットに新しい色xnameを登録する
### mix_colorはTurtleのメソッドであることに注意 def draw : # 上のadd_colorの例の続きとする # (@paletteは生成済で:red,:blueが@paletteに登録済みとする) # :redと:blueを同じ割合で合成した色:violetを@paletteに登録する mix_color(:violet,:red,:blue,0.5) : end def add_color(name,color) end def use_color(*c) end def mix_color(xname,name0,name1,alpha) end
[補足] Palette用のメソッドをTurtleに定義する意義
Paletteクラスを定義しているにも関わらず, Turtleクラスにcreate_palette,add_color,use_color(あるいはmix_color)を定義するのは, 一見回りくどいように思えるかもしれませんが, 上に示したこれらのメソッドの(drawでの)利用例では, @paletteが現れていないことに注目して下さい. これらのメソッドを定義することで,Paletteの存在を一々意識することなく, あたかもTurtle自体の機能として,色を定義して,利用できるようになります. またuse_colorを定義することで,色名と(R,G,B)を区別することなく,色を扱えるようになります.
これらのメソッドが定義されていなければ,色名を利用するたびにPaletteを明示することになります.また名前で定義された色を使う処理が(少し)煩雑になります. 次の例①を見てください.
# 例①: インスタンス変数@paletteを直接使うプログラム def draw set_speed(10) @palette = Palette.new([0,0,0]) # パレットを生成 @palette.add(:red,[255,0,0]) # パレットに:redを登録 set_weight(2) c = @palette.color(:red) # :redに対応する色データを取得 set_color(c) # 描画色をcに設定 forward(100) turn(90) set_color(0,255,0) # 描画色を(0,255,0)に設定 forward(100) end
それに対して,上で示したような一連のメソッドを定義しておけば,他のTurtleのメソッドと同じように(Paletteを使わないメソッドと区別することなく)処理を書けるようになります. 次の例②を見てください.実行する処理は上の例①と同じです.
# 例②: インスタンス変数@paletteを直接には使わないプログラム # 上の例①と同じ処理を実現している.@paletteを意識しなくてもよい. def draw set_speed(10) create_palette(0,0,0) # パレットを生成 add_color(:red,[255,0,0]) # パレットに:redを登録 set_weight(2) use_color(:red) # 描画色を:redに設定 forward(100) turn(90) use_color(0,255,0) # 描画色を(0,255,0)に設定 forward(100) end
[OPTION] ペンの導入
余裕があれば,Paletteに以下に示すような「pen」(ペン)を追加して, Paletteのaddメソッド,colorメソッドと同様に, Paletteに「pen」を追加登録するメソッド,登録されている「pen」を返すメソッドを定義し, さらにuse_colorメソッドと同様に,Turtleに「pen」を指定して使うメソッドを定義してください. それらのメソッドの名前,またメソッドの仕様は自由に決めて, プログラムの先頭の「=begin」「=end」の間に説明を記述してください.
pen
pen(ペン)は色名と線幅によって定まるものとします. 指定する色は,Paletteに登録済みとします(登録済みでない場合は,デフォルトカラーを使うことにします). penには適当な名前(Symbol)をつけて,Paletteに登録して管理することにします. 一旦登録したpenのデータ(色と線幅)は変更しないことにします. Turtleでpenを指定すると,そのpenの色と線幅が以降で使用されるようにします.
サンプルプログラム
次のページにサンプルプログラムを示します. プログラムをブラウザの画面で開いたときに文字化けしてしまう場合には, ダウンロードしてEmacs等で開いてみて下さい.
[参考] Symbol
ここではRubyの「Symbol」(シンボル)について簡単に説明します. 課題のためには以下を理解する必要はありません.
Symbolは「文字列」(文字が並んだデータ)と似ていますが,異なる種類のものです. Symbolとは「名前」で区別されるデータです. コロン(:)のあとに「名前」を書いて定義します. なお「:」は名前には含まれません.それにつづく部分が名前です.
:a_symbol # シンボル
上で述べたようにSymbolは文字列(文字の並び)ではありません. 文字列のデータは書き換えられます.一方でSymbolは書き換えられません. 同じ文字の並びで構成される文字列(の実体)はいくつでも作れますが,同一の名前のSymbol(の実体)は一つしかありません.
# # 文字列とそのデータの実体について調べてみる # s = "ace" # 文字列 t = "ace" # 文字列 s == t # ==> true (s,tの値は等しいか?→等しい) s.equal?(t) # ==> false (s,tは同一の実体か?→そうではない.「equal?」はデータの実体の同一性を判定する) # sの1文字目を"A"に書き換える. s[0,1] = "A" # s == "Ace" # sへの変更はtには影響しない.つまりs,tの実体は同じではない.たまたま同じ文字が並んでいただけ. s == "Ace" # ==> true (sの値は"Ace"に等しいか→等しい) t == "ace" # ==> true (tの値は"ace"に等しいか→等しい) s == t # ==> false (s,tの値は等しいか?→等しくない) s.equal?(t) # ==> false (s,tは同一の実体か?→そうではない) # # Symbolとそのデータの実体について調べてみる # x = :ace # Symbol y = :ace # Symbol z = :Ace # Symbol x == y # ==> true (x,yの値は等しいか?→等しい.名前(ace)が同一である) x == z # ==> false (x,zの値は等しいか?→等しくない.名前(ace,Ace)が異なる) x.equal?(y) # ==> true (x,yの実体は同一である) x.equal?(z) # ==> false (x,zの実体は異なる) # # 文字列とそのデータの実体について調べてみる(2) # t = s # tをsと同じ文字列データと対応させる t.equal?(s) # ==> true (s,tは同一の実体である) t[2,1] = "t" # tの3番目の文字を"t"に書き換える(t=="Act") s == "Act" # ==> true (sの値は"Act"に等しいか→等しい)