ぷよぷよ


![puyopuyo.gif](https://qiita-image-store.s3.amazonaws.com/0/154291/7ab9e577-e7eb-f439-7e75-5020eae9d05c.gif)
(beep(えいっ!) beep(ファイヤー) beep(アイスストーム) beep(ダイヤキュート) beep(ブレインダムド) beep(ジュゲム) beep(ばよえーん))


質素なテトリスと違い、ぷよぷよには可愛らしい絵や音がある
それらを取り除いた、ただのパズルゲームを作るのはそう難しくない
と思って作り始めると休日が終わっていた、ナンテコッタイ

# おさらい
[wikipedia](https://ja.wikipedia.org/wiki/%E3%81%B7%E3%82%88%E3%81%B7%E3%82%88#.E5.9F.BA.E6.9C.AC.E7.9A.84.E3.81.AA.E3.83.AB.E3.83.BC.E3.83.AB)
> 各作品によってルールの細部は異なる。ここでは基本的なルールのみを記す。
> フィールドは縦12マス×横6マスの格子で構成される。格子の1マスにつき1個のブロック（ぷよぷよ略してぷよ）を置くことができる。ただし、上方向は、画面外に1マス分だけぷよを置くことができる。
> 上からぷよが2つ1組で落下してくる（「組ぷよ」と呼ばれる[注 4]）。ぷよは種類ごとに色が異なり、色は3-5色（通常は4色）ある。プレイヤーはぷよに対して回転、横移動、高速落下のいずれかの操作を行う。
> 次に落下するぷよはフィールドの枠外にNEXTぷよとして予告される。配られるぷよの配分は麻雀のツモに例えられている。
> 落下してきたぷよがフィールドの床やほかのぷよに衝突すると、その位置にぷよが固定される。ただし、組ぷよを横にして置いたりなどして、ぷよに1マス分でも下方向に空白がある場合は、強制的にそのぷよだけ落下する。
> 固定されたぷよと同色の'ぷよ'が周囲4方向にいる場合、それらは互いにくっつく。
> ぷよが4個以上くっつくと消滅し得点となる。
> ぷよの消滅により上にあった'ぷよ'が落下する。このとき再びぷよが4個以上くっつくと消滅し、連鎖が起きる。なお、普通に4つ色を並べて消す行為だけでも1連鎖と考え、消滅した回数（○回）に応じて○連鎖と呼ばれる。複数色を同時に消した場合でも、1連鎖扱いとなる。
> ぷよを消したときに入る得点は、消したぷよの数に、設定された「連鎖倍率」を掛けることで計算できる。
> 左から3列目が一番上まで埋まると"窒息して"ゲームオーバー。

今回はNEXTぷよとゲームオーバーは未実装、色は4色

# PuyoPuyo
[テトリス](http://qiita.com/kumatoki/items/b6df574678c1419771c)
これをベースにします
変更点は主に落下と消去
スコアやコンボ判定を追加

```ruby
class PuyoPuyo 
  attr_accessor :puyo, :board
  def initialize(y,x)
    @board = Array.new(y) { Array.new(x) { 0 } }
    @puyo = rand_puyo
    @score = 0
    @comb = 0
  end

  def rand_puyo
    [[rand(1..4), [0,@board.size[0]/2]], [rand(1..4), [1,@board.size[0]/2]]]
  end

  def move?(ps)
    ps.all? do |y,x|
      @board.size > y && 0 <= y ?
        @board[y].size > x && 0 <= x ?
          @board[y][x] == 0 :
          false :
        false
    end
  end

  def rotate
    r = Math::PI / 2
    cy = @puyo.map { |c,(y,x)| y }.reduce(:+) / 2
    cx = @puyo.map { |c,(y,x)| x }.reduce(:+) / 2
    ps = @puyo.map do |c,(y,x)|
      [
        (cy + (x - cx) * Math.sin(r) + (y - cy) * Math.cos(r)).round,
        (cx + (x - cx) * Math.cos(r) - (y - cy) * Math.sin(r)).round
      ]
    end
    p ps

    if move?(ps)
      @puyo[0][1] = ps[0]
      @puyo[1][1] = ps[1]
    end
  end

  def down
    ps = @puyo.map { |c,(y,x)| [y + 1, x] }
    if move?(ps)
      @puyo[0][1] = ps[0]
      @puyo[1][1] = ps[1]
    end
  end

  def right
    ps = @puyo.map { |c,(y,x)| [y, x + 1] }
    if move?(ps)
      @puyo[0][1] = ps[0]
      @puyo[1][1] = ps[1]
    end
  end


  def left
    ps = @puyo.map { |c,(y,x)| [y, x - 1] }
    if move?(ps)
      @puyo[0][1] = ps[0]
      @puyo[1][1] = ps[1]
    end
  end

  
  def puyo_fall
    ps = @puyo.map { |c,(y,x)| [y + 1, x] }
    if move?(ps)
      @puyo[0][1] = ps[0]
      @puyo[1][1] = ps[1]
    else
      @board[@puyo[0][1][0]][@puyo[0][1][1]] = @puyo[0][0]
      @board[@puyo[1][1][0]][@puyo[1][1][1]] = @puyo[1][0]
      @puyo = rand_puyo
    end
  end

  def board_fall
    for y in (@board.size - 1).downto(0)
      for x in 0 .. @board[y].size - 1
        if @board[y][x] == 0
          a = 0
          for yy in y.downto(0)
            if @board.size > yy && 0 <= yy && @board[yy][x] > 0
              @board[y-a][x] = @board[yy][x]
              @board[yy][x] = 0
              a += 1
            end
          end
        end
      end
    end
  end

  def board_delete
    for y in 0 .. @board.size - 1
      for x in 0 .. @board[y].size - 1
        if @board[y][x] > 0
          ss = [[y,x]]
          ds = [[y,x]]
          while !ss.empty?
            sy,sx = ss.pop
            for ay,ax in [[-1,0],[1,0],[0,-1],[0,1]]
              yy = ay + sy
              xx = ax + sx
              if @board.size > yy && 0 <= yy && @board[yy].size > xx && 0 <= xx &&
                  @board[yy][xx] == @board[y][x] && !ds.any? { |a| a == [yy,xx] }
                ss.push([yy,xx])
                ds.push([yy,xx])
              end
            end
          end

          if ds.size >= 4
            for dy,dx in ds
              @board[dy][dx] = 0
            end
            @comb += 1
            @score += ds.size * @comb
          end
        end
      end
    end
  end

  require "curses"
  C = Curses

  def controller(c)
    case c
    when "w"
      rotate
    when "s"
      down
    when "d"
      right
    when "a"
      left
    when "q"
      C.close_screen
      exit
    else
      nil
    end
  end

  def display_init
    C.init_screen
    C.start_color
    C.use_default_colors
    C.noecho
    C.curs_set(0)

    [
      C::COLOR_BLACK,
      C::COLOR_RED,
      C::COLOR_GREEN,
      C::COLOR_YELLOW,
      C::COLOR_BLUE,
    ].each.with_index do |c,i|
      C.init_pair(i, C::COLOR_BLACK, c)
    end
  end

  def display
    C.clear
    C.addstr("-" * (@board[0].size + 2))
    C.addstr("\n")
    for y in 0 .. @board.size - 1
      C.addstr("|")
      for x in 0 .. @board[y].size - 1
        c = @puyo[0][1] == [y,x] ? @puyo[0][0] :
          @puyo[1][1] == [y,x] ? @puyo[1][0] : @board[y][x]
        C.attron(C.color_pair(c))
        C.addstr(" ")
        C.attroff(C.color_pair(c))
      end
      C.addstr("|")
      C.addstr("\n")
    end
    C.addstr("-" * (@board[0].size + 2))
    C.addstr("\nscore: #{@score}\ncomb: #{@comb}\n")
    C.refresh
  end

  def run
    display_init
    display

    m = Mutex.new

    def delete
      @comb = 0
      c = @comb
      board_delete
      while @comb != c
        #C.flash
        C.beep
        c = @comb
        board_fall
        display

        sleep 0.5
        board_delete
        display
      end
    end

    Thread.new do
      loop do 
        sleep 1
        m.synchronize do
          puyo_fall
          board_fall
          display
          sleep 0.5
          delete
          display
        end
      end
    end

    loop do 
      c = C.getch.to_s
      m.synchronize do
        controller(c)
        board_fall
        display
        delete
        display
      end
    end
  end
end

pp = PuyoPuyo.new(12,6)

#board = <<END
#4
#1
#4
#32132
#21321
#321321
#321321
#344242
#423122
#142314
#142314
#142314
#END
#
#pp.puyo = [[1,[0,5]],[1,[1,5]]]
#
#bs = board.split(/\n/).map { |b| b.split(//) }
#bs.each_with_index do |b,y|
#  b.each_with_index do |c,x|
#    pp.board[y][x] = c.to_i
#  end
#end
#

pp.run
```

# end
cpu対戦やネットワーク対戦があるとだいぶ楽しくなりそう（作らない）