class Tetris::Model::Tetromino

Constants

LETTER_COLORS
ORIENTATIONS

Attributes

blocks[RW]
column[RW]
game[R]
letter[R]
orientation[RW]
preview[R]
preview?[R]
row[RW]

Public Class Methods

new(game) click to toggle source
# File examples/tetris/model/tetromino.rb, line 45
def initialize(game)
  @game = game
  @letter = LETTER_COLORS.keys.sample
  @orientation = :north
  @blocks = default_blocks
  @preview = true
  new_row = 0
  new_column = (Model::Game::PREVIEW_PLAYFIELD_WIDTH - width)/2
  update_playfield(new_row, new_column)
end

Public Instance Methods

add_to_playfield() click to toggle source
# File examples/tetris/model/tetromino.rb, line 78
def add_to_playfield
  update_playfield_block do |playfield_row, playfield_column, row_index, column_index|
    playfield[playfield_row][playfield_column].color = blocks[row_index][column_index].color if playfield_row >= 0 && playfield[playfield_row][playfield_column]&.clear? && !blocks[row_index][column_index].clear? && playfield[playfield_row][playfield_column].color != blocks[row_index][column_index].color
  end
end
bottom_most_block_for_column(column) click to toggle source
# File examples/tetris/model/tetromino.rb, line 123
def bottom_most_block_for_column(column)
  bottom_most_blocks.detect {|bottom_most_block| (@column + bottom_most_block[:column_index]) == column}
end
bottom_most_blocks() click to toggle source

Returns bottom-most blocks of a tetromino, which could be from multiple rows depending on shape (e.g. T)

# File examples/tetris/model/tetromino.rb, line 108
def bottom_most_blocks
  width.times.map do |column_index|
    row_blocks_with_row_index = @blocks.each_with_index.to_a.reverse.detect do |row_blocks, row_index|
      !row_blocks[column_index].clear?
    end
    bottom_most_block = row_blocks_with_row_index[0][column_index]
    bottom_most_block_row = row_blocks_with_row_index[1]
    {
      block: bottom_most_block,
      row_index: bottom_most_block_row,
      column_index: column_index
    }
  end
end
color() click to toggle source
# File examples/tetris/model/tetromino.rb, line 300
def color
  LETTER_COLORS[@letter]
end
default_blocks() click to toggle source
# File examples/tetris/model/tetromino.rb, line 261
def default_blocks
  case @letter
  when :I
    [
      [block, block, block, block]
    ]
  when :J
    [
      [block, block, block],
      [empty, empty, block],
    ]
  when :L
    [
      [block, block, block],
      [block, empty, empty],
    ]
  when :O
    [
      [block, block],
      [block, block],
    ]
  when :S
    [
      [empty, block, block],
      [block, block, empty],
    ]
  when :T
    [
      [block, block, block],
      [empty, block, empty],
    ]
  when :Z
    [
      [block, block, empty],
      [empty, block, block],
    ]
  end
end
down!(instant: false) click to toggle source
# File examples/tetris/model/tetromino.rb, line 185
def down!(instant: false)
  launch! if preview?
  unless stopped?
    block_count = 1
    if instant
      remaining_height, bottom_touching_block = remaining_height_and_bottom_touching_block
      block_count = remaining_height - @row
    end
    new_row = @row + block_count
    update_playfield(new_row, @column)
  end
end
height() click to toggle source
# File examples/tetris/model/tetromino.rb, line 181
def height
  @blocks.size
end
hypothetical_tetromino() click to toggle source
# File examples/tetris/model/tetromino.rb, line 242
def hypothetical_tetromino
  clone.tap do |hypo_clone|
    remove_from_playfield
    hypo_clone.blocks = @blocks.map do |row_blocks|
      row_blocks.map do |column_block|
        column_block.clone
      end
    end
  end
end
include_block?(block) click to toggle source
# File examples/tetris/model/tetromino.rb, line 304
def include_block?(block)
  @blocks.flatten.include?(block)
end
launch!() click to toggle source
# File examples/tetris/model/tetromino.rb, line 60
def launch!
  remove_from_playfield
  @preview = false
  new_row = 1 - height
  new_column = (game.playfield_width - width)/2
  update_playfield(new_row, new_column)
  game.tetrominoes << self
end
left!() click to toggle source
# File examples/tetris/model/tetromino.rb, line 198
def left!
  unless left_blocked?
    new_column = @column - 1
    update_playfield(@row, new_column)
  end
end
left_blocked?() click to toggle source
# File examples/tetris/model/tetromino.rb, line 152
def left_blocked?
  (@column == 0) ||
    left_most_blocks.any? { |left_most_block|
      (@row + left_most_block[:row_index]) >= 0 &&
        playfield[@row + left_most_block[:row_index]][@column + left_most_block[:column_index] - 1].occupied?
    }
end
left_most_blocks() click to toggle source

Returns right-most blocks of a tetromino, which could be from multiple columns depending on shape (e.g. T)

# File examples/tetris/model/tetromino.rb, line 161
def left_most_blocks
  @blocks.each_with_index.map do |row_blocks, row_index|
    column_block_with_column_index = row_blocks.each_with_index.to_a.detect do |column_block, column_index|
      !column_block.clear?
    end
    if column_block_with_column_index
      left_most_block = column_block_with_column_index[0]
      {
        block: left_most_block,
        row_index: row_index,
        column_index: column_block_with_column_index[1]
      }
    end
  end.compact
end
playfield() click to toggle source
# File examples/tetris/model/tetromino.rb, line 56
def playfield
  @preview ? game.preview_playfield : game.playfield
end
remaining_height_and_bottom_touching_block() click to toggle source
# File examples/tetris/model/tetromino.rb, line 253
def remaining_height_and_bottom_touching_block
  playfield_remaining_heights = game.playfield_remaining_heights(self)
  bottom_most_blocks.map do |bottom_most_block|
    playfield_column = @column + bottom_most_block[:column_index]
    [playfield_remaining_heights[playfield_column] - (bottom_most_block[:row_index] + 1), bottom_most_block]
  end.min_by(&:first)
end
remove_from_playfield() click to toggle source
# File examples/tetris/model/tetromino.rb, line 84
def remove_from_playfield
  return if @row.nil? || @column.nil?
  update_playfield_block do |playfield_row, playfield_column, row_index, column_index|
    playfield[playfield_row][playfield_column].clear if playfield_row >= 0 && !blocks[row_index][column_index].clear? && playfield[playfield_row][playfield_column]&.color == color
  end
end
right!() click to toggle source
# File examples/tetris/model/tetromino.rb, line 205
def right!
  unless right_blocked?
    new_column = @column + 1
    update_playfield(@row, new_column)
  end
end
right_blocked?() click to toggle source
# File examples/tetris/model/tetromino.rb, line 127
def right_blocked?
  (@column == game.playfield_width - width) ||
    right_most_blocks.any? { |right_most_block|
      (@row + right_most_block[:row_index]) >= 0 &&
        playfield[@row + right_most_block[:row_index]][@column + right_most_block[:column_index] + 1].occupied?
    }
end
right_most_blocks() click to toggle source

Returns right-most blocks of a tetromino, which could be from multiple columns depending on shape (e.g. T)

# File examples/tetris/model/tetromino.rb, line 136
def right_most_blocks
  @blocks.each_with_index.map do |row_blocks, row_index|
    column_block_with_column_index = row_blocks.each_with_index.to_a.reverse.detect do |column_block, column_index|
      !column_block.clear?
    end
    if column_block_with_column_index
      right_most_block = column_block_with_column_index[0]
      {
        block: right_most_block,
        row_index: row_index,
        column_index: column_block_with_column_index[1]
      }
    end
  end.compact
end
rotate!(direction) click to toggle source

Rotate in specified direcation, which can be :right (clockwise) or :left (counterclockwise)

# File examples/tetris/model/tetromino.rb, line 213
def rotate!(direction)
  return if stopped?
  can_rotate = nil
  new_blocks = nil
  game.hypothetical do
    hypothetical_rotated_tetromino = hypothetical_tetromino
    new_blocks = hypothetical_rotated_tetromino.rotate_blocks(direction)
    can_rotate = !hypothetical_rotated_tetromino.stopped? && !hypothetical_rotated_tetromino.right_blocked? && !hypothetical_rotated_tetromino.left_blocked?
  end
  if can_rotate
    remove_from_playfield
    self.orientation = ORIENTATIONS[ORIENTATIONS.rotate(direction == :right ? -1 : 1).index(@orientation)]
    self.blocks = new_blocks
    update_playfield(@row, @column)
  end
rescue => e
  puts e.full_message
end
rotate_blocks(direction) click to toggle source
# File examples/tetris/model/tetromino.rb, line 232
def rotate_blocks(direction)
  new_blocks = Matrix[*@blocks].transpose.to_a
  if direction == :right
    new_blocks = new_blocks.map(&:reverse)
  else
    new_blocks = new_blocks.reverse
  end
  Matrix[*new_blocks].to_a
end
stopped?() click to toggle source
# File examples/tetris/model/tetromino.rb, line 91
def stopped?
  return true if @stopped || @preview
  playfield_remaining_heights = game.playfield_remaining_heights(self)
  result = bottom_most_blocks.any? do |bottom_most_block|
    playfield_column = @column + bottom_most_block[:column_index]
    playfield_remaining_heights[playfield_column] &&
      @row + bottom_most_block[:row_index] >= playfield_remaining_heights[playfield_column] - 1
  end
  if result && !game.hypothetical?
    @stopped = result
    game.consider_eliminating_lines
    @game.consider_adding_tetromino
  end
  result
end
update_playfield(new_row = nil, new_column = nil) click to toggle source
# File examples/tetris/model/tetromino.rb, line 69
def update_playfield(new_row = nil, new_column = nil)
  remove_from_playfield
  if !new_row.nil? && !new_column.nil?
    @row = new_row
    @column = new_column
    add_to_playfield
  end
end
width() click to toggle source
# File examples/tetris/model/tetromino.rb, line 177
def width
  @blocks[0].size
end

Private Instance Methods

block() click to toggle source
# File examples/tetris/model/tetromino.rb, line 310
def block
  Block.new(color)
end
empty() click to toggle source
# File examples/tetris/model/tetromino.rb, line 314
def empty
  Block.new
end
update_playfield_block(&updater) click to toggle source
# File examples/tetris/model/tetromino.rb, line 318
def update_playfield_block(&updater)
  @row.upto(@row + height - 1) do |playfield_row|
    @column.upto(@column + width - 1) do |playfield_column|
      row_index = playfield_row - @row
      column_index = playfield_column - @column
      updater.call(playfield_row, playfield_column, row_index, column_index)
    end
  end
end