LoginSignup
34
22

More than 5 years have passed since last update.

テトリス

Last updated at Posted at 2017-02-01

こんな感じ

Tetris

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

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

移動

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

  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

落下

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

  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

消去

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

  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の初期化がめんどいし色付けもし難いですがターミナルですからね、えぇ

  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

実行

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

  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

縦横決めたら遊ぶだけ

Tetris.new(15,80).run

実は

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

34
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
22