Last active
September 29, 2018 14:04
-
-
Save obelisk68/c11082995c0333d6b734ddb6ea316ea2 to your computer and use it in GitHub Desktop.
Ruby/SDL でテトリス
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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