require 'RubySFML' include SFML # A simple class to handle a single puzzle piece class Piece # Each shape is made up of 4 blocks: # * * * * ** ** ** # * ** ** ** * * ** # * * * * * * # * SHAPES = [ # Negative y is down [[0, 0], [0, 1], [0, -1], [0, -2]], [[0, 0], [0, 1], [1, 0], [0, -1]], [[0, 0], [0, 1], [1, 0], [1, -1]], [[1, 0], [1, 1], [0, 0], [0, -1]], [[0, 0], [0, 1], [1, 1], [0, -1]], [[1, 0], [1, 1], [0, 1], [1, -1]], [[0, 0], [0, 1], [1, 1], [1, 0]], ] # Each shape has its own color COLORS = [Color.Red, Color.Yellow, Color.Cyan, Color.Magenta, Color.Green, Color.Blue, Color.new(255, 128, 64)] attr_accessor :x, :y, :r def initialize(n, x, y) @n, @x, @y = n, x, y @r = 0 end def shape(); SHAPES[@n]; end def color(); COLORS[@n]; end def rleft(); @r = (@r+1) % 4; end def rright(); @r = (@r-1) % 4; end def each() # Iterate each block coordinate (corrected for rotation) case @r when 0 then shape.each {|c| yield([c[0], c[1]])} when 1 then shape.each {|c| yield([-c[1], c[0]])} when 2 then shape.each {|c| yield([-c[0], -c[1]])} when 3 then shape.each {|c| yield([c[1], -c[0]])} end end end class Grid HEIGHT = 20 WIDTH = 10 BLOCK = 18 BORDER = 1 BSIZE = BLOCK + BORDER def initialize() @rows = [] HEIGHT.times { @rows.push Array.new(WIDTH) } @background_image = Image.new(WIDTH*(BSIZE)+BORDER, HEIGHT*BSIZE+BORDER, Color.White) @background = Sprite.new(@background_image, (640-WIDTH*BSIZE)/2-BORDER, (480-HEIGHT*BSIZE)/2-BORDER) @block_image = Image.new(BLOCK, BLOCK, Color.White) @block = Sprite.new(@block_image) @score = 0 @last_down = $clock.to_f @piece = Piece.new(rand(7), WIDTH/2-1, HEIGHT-1) @next_piece = Piece.new(rand(7), WIDTH/2-1, HEIGHT-1) end def [](y) return @rows[y] end def draw_block(win, x, y, color) @block.left = x*BSIZE + @background.left+BORDER @block.top = ((HEIGHT-1)-y)*BSIZE + @background.top+BORDER @block.color = color win.draw(@block) end def solid?(x, y) return true if x < 0 or y < 0 or x >= WIDTH or y >= HEIGHT return @rows[y][x] end def left x = @piece.x @piece.x -= 1 @piece.each {|c| @piece.x = x if solid?(@piece.x+c[0], @piece.y+c[1])} end def right x = @piece.x @piece.x += 1 @piece.each {|c| @piece.x = x if solid?(@piece.x+c[0], @piece.y+c[1])} end def up r = @piece.r @piece.rright @piece.each {|c| @piece.r = r if solid?(@piece.x+c[0], @piece.y+c[1])} end def down @last_down = $clock.to_f y = @piece.y @piece.y -= 1 done = false @piece.each {|c| done = true if solid?(@piece.x+c[0], @piece.y+c[1])} if done @piece.y = y @piece.each {|c| @rows[@piece.y+c[1]][@piece.x+c[0]] = @piece.color} @piece = @next_piece @next_piece = Piece.new(rand(7), WIDTH/2-1, HEIGHT-1) end end def update(win) speed = 0.5 - 0.1 * (@score/1000) down if $clock.to_f - @last_down >= speed collapsed = 0 while y = find_full_row() collapse_row(y) collapsed += 1 end @score += 5 * 2**collapsed if collapsed > 0 end def render(win) win.draw(@background) HEIGHT.times {|y| WIDTH.times {|x| c = @rows[y][x] || Color.Black draw_block(win, x, y, c) } } @piece.each {|c| x, y, c = @piece.x+c[0], @piece.y+c[1], @piece.color next if y < 0 or y > HEIGHT-1 draw_block(win, x, y, c) } score = Text.new("Score: #{@score}", "", 20) #score.left, score.top = 10, 10 win.draw(score) text = Text.new("Coming Next:", "", 20) text.left, text.top = 450, 50 win.draw(text) @next_piece.each {|c| x, y, c = 14+c[0], 16+c[1], @next_piece.color next if y < 0 or y > HEIGHT-1 draw_block(win, x, y, c) } end def collapse_row(i) while i < HEIGHT-1 @rows[i] = @rows[i+1] i += 1 end @rows[HEIGHT-1] = Array.new(WIDTH, nil) end def find_full_row() HEIGHT.times {|y| return y unless @rows[y].index(nil) } return nil end end $clock = Clock.new mode = VideoMode.new(640, 480, 32) win = RenderWindow.new(mode, "RubySFML Test", 0) win.showMouseCursor(false) win.useVerticalSync(true) grid = Grid.new() # Simple game loop done = false while !done while e = win.getEvent() case e.type when Event::Closed then done = true when Event::KeyReleased then done = true if e.code == Key::Escape when Event::KeyPressed case e.code when Key::Up then grid.up when Key::Left then grid.left when Key::Right then grid.right when Key::Down then grid.down end end end grid.update(win) grid.render(win) win.display() sleep(0.01) end