Edited at

テトリス

More than 1 year has passed since last update.


こんな感じ


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


実は

バグあります

回転させるたびに左へずれていくという致命的なやつです

非常に残念ですね