テトリス

# こんな感じ

![](https://qiita-image-store.s3.amazonaws.com/0/154291/d72b6147-4c4c-6338-3e9e-9f3ed95bec20.gif)

# Tetris
ブロックは色番号と座標のリストで表現
ボードは色を保持した二次元配列
色番号の0はブロック未配置扱い
それとおなじみのブロックも用意

```ruby
class Tetris
  attr 
    :block, # [color,[[y,x]]
    :board  # [[color]]
  def initialize(y,x)
    @block = rand_block
    @board = Array.new(y) { Array.new(x) { 0 } }
  end

  Blocks = [
    [[0,0],[1,0],[2,0],[3,0]],
    [[0,0],[0,1],[0,2],[1,1]],
    [[0,0],[1,0],[2,0],[0,1]],
    [[0,0],[0,1],[1,1],[2,1]],
    [[0,0],[1,0],[1,1],[2,1]],
    [[0,1],[1,1],[1,0],[2,0]],
    [[0,0],[0,1],[1,0],[1,1]],
  ]

  def rand_block
    [rand(1..7), Blocks[rand(Blocks.size)]]
  end
```

# 移動
移動可能であるかをボードサイズとボード上のブロックが未配置かどうかで判断
ブロック回しはブロックの中心出してそれを基に行列回転
実はこれにバグが残っております、後述

```ruby
  def move?(bs)
    bs.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 = (@block[1].map { |a| a[0] }.reduce(:+) / @block[1].size)
    cx = (@block[1].map { |a| a[1] }.reduce(:+) / @block[1].size)
    bs = @block[1].map do |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

    if move?(bs)
      @block[1] = bs
    end
  end

  def down
    bs = @block[1].map { |y,x| [y + 1, x] }
    if move?(bs)
      @block[1] = bs
    end
  end

  def right
    bs = @block[1].map { |y,x| [y, x + 1] }
    if move?(bs)
      @block[1] = bs
    end
  end


  def left
    bs = @block[1].map { |y,x| [y, x - 1] }
    if move?(bs)
      @block[1] = bs
    end
  end
```

# 落下
タイマーで回すやつ
それ以上落とせなかったら、ボードに書き込み、新たなブロックをセット

```ruby
  def fall
    bs = @block[1].map { |y,x| [y+1,x] }
    if move?(bs)
      @block[1] = bs
    else
      @block[1].each do |y,x|
        @board[y][x] = @block[0]
      end
      @block = rand_block
    end
  end
```

# 消去
ブロックのラインができたら消しましょう、それがテトリスのルールですね
消したらブロック達を落下ないし下へスライド

```ruby
  def delete
    for y in 0 .. @board.size - 1
      if @board[y].all? { |c| c != 0 }
        for yy in 0 .. y - 1
          @board[yy].each.with_index do |c,x|
            @board[y - yy][x] = @board[y - yy - 1][x]
          end
        end
      end
    end
  end
```

# 表示
ターミナルでやるのでcurses使います
コントローラーをまずセット、wasd型で行きましょう
cursesの初期化がめんどいし色付けもし難いですがターミナルですからね、えぇ

```ruby
  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,
      C::COLOR_MAGENTA,
      C::COLOR_CYAN,
      C::COLOR_WHITE,
    ].each.with_index do |c,i|
      C.init_pair(i, C::COLOR_WHITE, 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 = @block[1].any? { |a| a == [y,x] } ? @block[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("\n")
    C.refresh
  end
```

# 実行
の一歩手前
タイマーをスレッドで回し
タイマーには落下
メインで入力制御ループ
更新毎に表示と消せるかどうかを確認
一応排他制御しておきましょう

```ruby
  def run
    display_init

    m = Mutex.new
    Thread.new do
      loop do 
        m.synchronize do
          fall
          delete
          display
        end
        sleep 1
      end
    end

    loop do 
      controller(C.getch.to_s)
      m.synchronize do
        delete
        display
      end
    end
  end
end
```

# main
縦横決めたら遊ぶだけ

```ruby
Tetris.new(15,80).run
```

![](https://qiita-image-store.s3.amazonaws.com/0/154291/d72b6147-4c4c-6338-3e9e-9f3ed95bec20.gif)

# 実は
バグあります
回転させるたびに左へずれていくという致命的なやつです
非常に残念ですね

![](https://qiita-image-store.s3.amazonaws.com/0/154291/2d5344d3-24b2-ef2f-dd74-3fd188a56893.gif)