Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ruby/SDL でテトリス
# Gem 'rubysdl' に付属しているサンプルを modify したものです
require 'sdl'
class Object
def deep_clone
Marshal::load(Marshal.dump(self))
end
end
class Pattern
def initialize(x, y, *data)
@x = x
@y = y
@data = data
end
attr_reader :data, :x, :y
alias width x
def rotate
@data = @data.reverse.transpose
@x, @y = @y, @x
end
PAT1 = Pattern.new(4, 1, [1, 1, 1, 1])
PAT2 = Pattern.new(3, 2, [1, 1, 1], [0, 1, 0])
PAT3 = Pattern.new(3, 2, [1, 1, 1], [1, 0, 0])
PAT4 = Pattern.new(3, 2, [1, 1, 1], [0, 0, 1])
PAT5 = Pattern.new(2, 2, [1, 1], [1, 1])
PAT6 = Pattern.new(3, 2, [1, 1, 0], [0, 1, 1])
PAT7 = Pattern.new(3, 2, [0, 1, 1], [1, 1, 0])
def Pattern::patterns
[PAT1, PAT2, PAT3, PAT4, PAT5, PAT6, PAT7]
end
end
class Field
def initialize(width = 10, height = 15)
@height = height
@width = width
@field = Array.new(height) {Array.new(width, 0)}
backup_background
end
attr_reader :width, :height
def test
@field[0][0] = 1
@field[0][@width - 1] = 1
@field[@height - 1][0] = 1
@field[@height - 1][@width - 1] = 1
end
#ブロックが書き込めないか?(書き込めればfalse, 書き込められなければtrueを返す)
def bg_is_collision(offset_x, offset_y, pattern)
return true if pattern.x + offset_x > @width
return true if pattern.y + offset_y > @height
y = offset_y
pattern.data.each do |row|
x = offset_x
row.each do |cell|
return true if (cell != 0) and (@bg[y][x] != 0)
x += 1
end
y += 1
end
return false
end
def restore_background
@field = @bg
end
def backup_background
@bg = @field.deep_clone
end
#ブロックを書き込む
def or_pattern(offset_x, offset_y, pattern)
backup_background
y = offset_y
pattern.data.each do |row|
x = offset_x
row.each do |cell|
@field[y][x] = cell if cell != 0
x += 1
end
y += 1
end
end
#消せる行を調べてArrayで返す
def find_filled_rows
res = []
@field.each_index do |y|
ok = true
@field[y].each do |cell|
if cell == 0
ok = false
break
end
end
res << y if ok
end
res
end
def remove_rows(*rows)
rows.uniq!
rows.each {|y| @bg[y] = nil}
@bg.compact!
extra = Array.new(rows.size) {Array.new(@width, 0)}
@bg = extra + @bg
raise "integrity error" if @bg.size != @height
end
end
class Field
def load_render_data
@image = SDL::Surface.load_bmp("icon.bmp")
@image.set_color_key(SDL::SRCCOLORKEY, 0)
@image = @image.display_format
@step_x = 32 # todo: image width
@step_y = 32 # todo: image height
# todo: raise exception is field does not fit to screen!
@offset_x = 100
@offset_y = 20
@fill_removal_dir = false
end
#画面を更新する
def render
y = @offset_y
@field.each do |row|
render_line(y, row)
y += @step_y
end
end
def render_line(y, cells)
x = @offset_x
cells.each do |cell|
i = (cell == 0) ? 63 : 255
@image.set_alpha(SDL::SRCALPHA, i)
$screen.put(@image, x, y)
x += @step_x
end
end
FILL = 20 * 256 * 256 + 0 * 256 + 10
#rowsの行を消すのを描画する
def render_removal(rows)
$screen.fill_rect(0, 0, 640, 512, 0)
render
rows.reverse_each do |row|
y = row * @step_y + @offset_y
clear_line = ->(x, dir) {
@width.times do
$screen.fill_rect(x, y, @step_x, @step_y, FILL)
$screen.flip
x += @step_x * dir
end
@fill_removal_dir = !@fill_removal_dir
}
if @fill_removal_dir
clear_line.(@offset_x, 1)
else
clear_line.(@offset_x + (@width - 1) * @step_x, -1)
end
end
end
end
SDL.init(SDL::INIT_VIDEO)
$screen = SDL::Screen.open(640, 512, 24, SDL::SWSURFACE)
field = Field.new
#field.test
field.load_render_data
launch_new_pattern = true
while true
if launch_new_pattern
launch_new_pattern = false
time_step = 0.5 #ブロックが1段落ちるのにかかる時間
time = Time.now
pat = Pattern::patterns.sample.clone
pat_x, pat_y = 5, 0 #ブロックの位置
field.backup_background
if field.bg_is_collision(pat_x, pat_y, pat)
puts "Sorry you are game over"
sleep 3
raise "game over"
end
field.or_pattern(pat_x, pat_y, pat)
end
# timer events(一定の時間が経ったらブロックをひとつ下げる)
while Time.now > time + time_step
pat_y += 1
if field.bg_is_collision(pat_x, pat_y, pat)
launch_new_pattern = true
rows = field.find_filled_rows
if rows.size > 0
puts "rows filled"
p rows
field.render_removal(rows)
field.backup_background
field.remove_rows(*rows)
field.restore_background
end
else
field.restore_background
field.or_pattern(pat_x, pat_y, pat)
end
time += time_step
end
old_pat_x = pat_x
old_pat_y = pat_y
rotate = false
# handle keystrokes
while event = SDL::Event.poll
case event
when SDL::Event::Quit then exit
when SDL::Event::KeyDown
case event.sym
when SDL::Key::ESCAPE then exit
when SDL::Key::UP then rotate = true
when SDL::Key::DOWN then time_step = 0.1
when SDL::Key::LEFT
pat_x -= 1 if pat_x > 0
when SDL::Key::RIGHT
pat_x += 1 if pat_x < field.width - pat.width
end
end
end
SDL::Key.scan
#ブロックを移動・回転させる条件のとき、移動・回転できるならそうする
if (pat_x != old_pat_x) or (pat_y != old_pat_y) or (rotate == true)
old_pat = pat.clone
pat.rotate if rotate
# if collision then restore last working-state
if field.bg_is_collision(pat_x, pat_y, pat)
pat_x = old_pat_x
pat_y = old_pat_y
pat = old_pat
else
# collision avoided.. therefore don't launch new pattern
launch_new_pattern = false
field.restore_background
field.or_pattern(pat_x, pat_y, pat)
end
end
# repaint screen
$screen.fill_rect(0, 0, 640, 512, 0)
field.render
$screen.flip
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment