こんな感じ
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
実は
バグあります
回転させるたびに左へずれていくという致命的なやつです
非常に残念ですね