[[目次へもどる>PuyoPuyo]] * FieldとBlock [#q44c70ef] #contents ** Block [#d245f240] まず,ぷよぷよは商標登録だと思われるので ぷよのことを「ブロック」と呼ぶことにしよう. 実際開発段階ではただの矩形で表示するし. まあ結局いたるところでぷよぷよぷよぷよ連呼するんだけどね! このブロックを表すクラス「Block」を定義した. #sh(ruby){{ class Block attr_accessor :color, :row, :line def initialize(col) @color = col @row = -1 @line = -1 end def inspect sprintf("|(%2d,%2d):%s|",@row,@line,@color.to_s) end def to_s sprintf("|(%2d,%2d):%s|",@row,@line,@color.to_s) end end }} まずブロックは色,列,行をプロパティに持つ.なんで行,列じゃなくて列,行かだって? 今にわかります. また,デバッグのために「to_s」と「inspect」メソッドをオーバーライド. これでブロックの位置と色がわかりやすく表示される. ** Field [#o917b6ea] 次にブロックが配置される場所が必要だ. ぷよぷよで言うところのキャラ絵があるところ. これを我々はフィールドと呼ぶことにする. #sh(ruby){{ class Field end }} *** table [#aac2d968] フィールドは普通6列の12行(だっけ?)で構成されるタイル状のデータ構造を持ちそうに見える. なのでそれをテーブルと呼ぶことにして定義しよう. #sh(ruby){{ def initialize(row_s, line_s) @row_s = row_s @line_s = line_s init_table end def init_table @table = [] @row_s.times do |row| @table.push Array.new(@line_s) end end }} なんで@row_s,@line_sを受け取るようにしてるかといえばもちろん拡張のため. ちびぷよでプレイとか実現できるかもしれない. テーブルの構造は至って簡単.配列の配列である. まず列数の要素を持つ配列を作成し,その要素それぞれに行数分の要素の配列を格納する. なんで行,列じゃなくて列,行かだって? 今にわかります. テーブルの初期値はnilなので, @table[r][l]がnilならそのマスにブロックは無いことになる. ちなみにrowは左から0,1,2,3,4,5である. lineは下から0,1,2,3,4,5...である. *** blocklist [#oa46f32d] さて,テーブルを作ってそこにブロックを格納していくのはいいが, フィールドにあるすべてのブロックになんらかの処理を行いたいときにテーブルのすべてのマスに対してブロックがあるかないかを調べていたらバカである. そこで,テーブルとは別にブロックの一覧を配列で持つことにした. んでもってこれらを利用してフィールドにブロックを配置するメソッド「set」を定義した. #sh(ruby){{ def initialize(row_s, line_s) @row_s = row_s @line_s = line_s init_table init_blocklist end def init_table @table = [] @row_s.times do |row| @table.push Array.new(@line_s) end end def init_blocklist @blocklist = [] end def set(r,l,col) if @table[r][l] @blocklist.delete @table[r][l] end block = Block.new(col) block.row = r block.line = l @table[r][l] = block @blocklist.push block end }} (注意)テーブルにブロックを格納といったが,Rubyではすべてがオブジェクトへの参照へ過ぎないので,あるブロックblockに対して, @table[r][l] = block; @blocklist.push block とした場合,それぞれ同じblockを参照することになる. ** ブロックを消す [#a1a9004c] さあ,ブロックをフィールドに配置できたらとりあえずやることはただ一つ! ブロックを消しましょう. *** つながってる? [#id19c5f1] ブロックを消す条件は - 同じ色のブロックが上下左右に4個以上つながっている~ これである. これをなるべくいい感じに行う方法を考えた. + まずフィールド上のブロックすべてをチェック対象としてチェックリストに追加 + つながっているブロックを管理するためのコネクトテーブルを作成~ コネクトテーブルは「つながっているブロックの配列」の配列 + チェックリストの先頭のブロックを取得 + そのブロックを起点にしてつながりチェックを開始~ このとき新たにコネクトテーブルに空配列の要素を追加 + ブロックがテーブルの範囲外ならreturn + ブロックがチェックリストに入っていなかったらすでにチェック済みなのでreturn + ブロックの色が起点ブロックと異なっていたらreturn + ここまで来たらブロックがつながっているのでブロックをチェックリストから削除し, + 現在のコネクトテーブルに追加 + 追加したブロックを起点ブロックとして上下左右を6から再帰的にチェック + チェックリストがまだ残ってるなら3に戻る この流れで二個以上つながっているブロックのグループがコネクトテーブルに格納される. #sh(ruby){{ def check_connection(r,l,col) return if r < 0 || r >= @row_s return if l < 0 || l >= @line_s block = @table[r][l] return unless @checklist.include? block return unless @table[r][l] && @table[r][l].color == col @checklist.delete block @connect_table[-1].push block check_connection(r,l+1,col) # up check_connection(r,l-1,col) # down check_connection(r+1,l,col) # right check_connection(r-1,l,col) # left end def make_connect_table init_connect_table @checklist = @blocklist.dup while !@checklist.empty? block = @checklist.first r = block.row; l = block.line col = block.color @connect_table.push [] check_connection(r,l,col) end end }} *** 消す [#o844b741] これは簡単.先ほど取得したつながってるグループのサイズが4以上ならそのグループに所属しているブロックをテーブルおよびブロックリストから消せばよい. #sh(ruby){{ def eliminate_connection @connect_table.each do |connection| next if connection.size < 4 @eliminated = true # check flag connection.each do |block| @table[block.row][block.line] = nil @blocklist.delete block end end end }} ** 落ちる [#d6c81823] さて,ブロックが消えたら当然スキマスイッチが出てくるのでなんとかしないといけない. スキマスイッチとは以下のような状態のスキマのことである.我々はこのスキマをスキマスイッチと呼ぼう. b ←ここ b ---- 重力で落ちるゲームというくらいだから隙間の上にあるブロックを落とせばいいだろう. まず当然のことながら各行ごとに考えれば十分なのでこんなんした. #sh(ruby){{ def falldown @fallen = false @row_s.times do |r| falldown_line(r) end @fallen end }} ちなみに@fallenってのは落ちたらtrue,変化なかったらfalseになる. 当然のことながらって言ったけど,これが先ほどから言っている > なんで行,列じゃなくて列,行かだって? 今にわかります. の答えである.まず最初に列で分割できたほうが何かと都合が良い. まあ表示するときは先に行で分割したいからめんどくさいことやってるけどね・・・.((Array#transposeを使う)) 次に各行ごとの落下処理を考える. まず行のサイズだけインデックスを回して(0〜11),最初にブロックが無かった位置をベースとする. ようするにこのベースがスキマスイッチの候補である. そしたらベースの一個上のマスからまたしてもインデックスを回して,今度はブロックが見つかったらそこでストップする. あとはストップした位置以降すべてのマスをベースまでおろしてくればいい. これでおっけーかと思いきや,同じ列にスキマスイッチが一人とは限らない.ちょうどスキマスイッチが二人組のように. ようするに先ほどベースだったマスの一つ上からもう一度ブロックの無い位置を調べ,そこを新たなベースとして...っていうのを繰り返す. これでこの行についてすべてのスキマスイッチが埋まる. #sh(ruby){{ def falldown_line(r) @line_s.times do |base| next if @table[r][base] # fall down to base ((base+1)...@line_s).each do |b_line| next unless @table[r][b_line] # skip space sequence @fallen = true # check flag @table[r].slice!(base...b_line) # fall down @table[r].concat(Array.new(b_line-base)) # fill empty # update block pos (base...@line_s).each do |l| next unless @table[r][l] @table[r][l].row = r @table[r][l].line = l end break end end end }} ** ここまでをテスト [#y200f967] ここまで正常に動作するかテストコードを実行してみよう! $ ruby field.rb さりげなく文字列から一気にフィールドにブロックを配置できるメソッド「set_table」を定義していたのでこれをつかう. #sh(ruby){{ if __FILE__ == "field.rb" field = Field.new(6,12) tstr =<<EOF ...... b..... y..... b..... b.r... r.b... r....y bbb.yg rygrbg rbygrb bygrbg bygrbg EOF field.set_table(tstr) field.print_field loop do fallen = field.falldown field.print_field if fallen eliminated = field.eliminate field.print_field if eliminated break if !fallen && !eliminated end }} 落下もしくは消去が起きなくなるまでループが続く. これを実行すると以下の出力が得られる. 長いな. -------- | | |b | |y | |b | |b r | |r b | |r y| |bbb yg| |rygrbg| |rbygrb| |bygrbg| |bygrbg| -------- -------- | | |b | |y | |b | |b | |r r | |r b y| |bbb yg| |rygrbg| |rbygrb| |bygrbg| |bygrbg| -------- -------- | | |b | |y | |b | |b | |r r | |r y| | yg| |rygrbg| |rbygrb| |bygrbg| |bygrbg| -------- -------- | | | | |b | |y | |b | |b | |r y| |r r yg| |rygrbg| |rbygrb| |bygrbg| |bygrbg| -------- -------- | | | | |b | |y | |b | |b | | y| | r yg| | ygrbg| | bygrb| |bygrbg| |bygrbg| -------- -------- | | | | | | | | | | | | |b y| |y r yg| |bygrbg| |bbygrb| |bygrbg| |bygrbg| -------- -------- | | | | | | | | | | | | |b y| |y r yg| | ygrbg| | ygrb| | ygrbg| | ygrbg| -------- -------- | | | | | | | | | | | | | y| | r yg| | grbg| | yygrb| |bygrbg| |yygrbg| -------- -------- | | | | | | | | | | | | | y| | r yg| | grbg| | grb| |b grbg| | grbg| -------- -------- | | | | | | | | | | | | | y| | yg| | rrbg| | ggrb| | grbg| |b grbg| -------- -------- | | | | | | | | | | | | | y| | yg| | rrbg| | rb| | rbg| |b rbg| -------- -------- | | | | | | | | | | | | | y| | yg| | bg| | rrb| | rbg| |b rrbg| -------- -------- | | | | | | | | | | | | | y| | yg| | bg| | b| | bg| |b bg| -------- -------- | | | | | | | | | | | | | y| | g| | yg| | bb| | bg| |b bg| -------- -------- | | | | | | | | | | | | | y| | g| | yg| | | | g| |b g| -------- -------- | | | | | | | | | | | | | | | y| | g| | g| | g| |b yg| -------- -------- | | | | | | | | | | | | | | | y| | | | | | | |b y | -------- -------- | | | | | | | | | | | | | | | | | | | | | | |b yy| --------