require 'pqueue' class GamePlay def self.move_forward_in_time(game_state) game_state.plants.each do |pl| if pl.lives_remaining == 0 game_state.plants -= [pl] next end if pl.on_clock_tick[:emit_projectile] game_state.projectiles += pl.emits end end game_state.zombies.each do |z| game_state.message_queue += z.on_clock_tick end game_state.projectiles.each do |pr| pr.on_clock_tick end z_queue = PQueue.new(game_state.zombies) { |a,b| a.x < b.x } pr_queue = PQueue.new(game_state.projectiles) { |a,b| a.x > b.x } until (z = z_queue.pop).nil? until (pr = pr_queue.pop).nil? if pr.previous_x <= z.previous_x and pr.x > z.x game_state.message_queue += ["", "--- Projectile hits zombie"] game_state.projectiles -= [pr] if z.on_collision(pr)[:dropped_to_the_ground] game_state.message_queue += ["", "------ Zombie drops to the ground"] game_state.zombies -= [z] break end end end end encounters = {} game_state.plants.each do |pl| encounters[pl.x] ||= { :plants => [], :zombies => [] } encounters[pl.x][:plants] << pl end game_state.zombies.select { |z| z.walking }.each do |z| encounters[z.x] ||= { :plants => [], :zombies => [] } encounters[z.x][:zombies] << z end encounters.values.each do |v| if !v[:plants].empty? and !v[:zombies].empty? v[:zombies].each do |z| z.start_devouring(v[:plants][0]) game_state.message_queue += ["", "--- Zombie starts devouring plant"] end end end game_state end def self.start system("clear") puts puts "Initial distance between plant and zombie (cm):" distance = gets.chomp.to_i gs = GameState.new([Gun.new(0)], [ConeheadZombie.new(distance)], [], []) until gs.zombies.empty? or gs.plants.empty? sleep(0.03) gs = self.move_forward_in_time(gs) gs.message_queue.each do |message| puts message end gs.message_queue = [] end puts if gs.zombies.empty? puts "PLANTS WIN !!!" end if gs.plants.empty? puts "ZOMBIES WIN !!!" end puts end end class GameState < Struct.new(:plants, :zombies, :projectiles, :message_queue) end class Plant attr_reader :x, :lives_remaining def initialize(x) @x = x @lives_remaining = 6 @countdown = 0 end def on_clock_tick if @countdown == 0 and @lives_remaining > 0 @reaction = { :emit_projectile => true } @countdown = 10 else @reaction = { :emit_projectile => false } end @countdown -= 1 @reaction end def on_bite message_queue = [] if @lives_remaining > 0 @lives_remaining -= 1 message_queue += ["", "--- Plant gets bitten"] if @lives_remaining == 0 message_queue += ["", "------ Plant is completely devoured"] end end message_queue end end class Gun < Plant def emits [Pea.new(@x)] end end class Repeater < Plant def emits [Pea.new(@x), Pea.new(@x)] end end class SnowGun < Plant def emits [SnowPea.new(@x)] end end class Zombie attr_reader :previous_x, :x, :lives_remaining, :walking def initialize(initial_x) @previous_x = initial_x @x = initial_x @slow_moving = false @walking = true initialize_lives_remaining end def on_clock_tick message_queue = [] @previous_x = @x if @walking if @slow_moving @x -= 1 else @x -= 2 end else if @plant_being_devoured.lives_remaining == 0 @walking = true @plant_being_devoured = nil else if @bite_countdown == 0 message_queue = @plant_being_devoured.on_bite @bite_countdown = 5 if @slow_moving @bite_countdown = 10 end end @bite_countdown -= 1 end end message_queue end def on_collision(projectile) @lives_remaining -= 1 if projectile.is_a?(SnowPea) @slow_moving = true @bite_countdown *= 2 end { :dropped_to_the_ground => @lives_remaining == 0 } end def start_devouring(pl) @walking = false @plant_being_devoured = pl @bite_countdown = 5 if @slow_moving @bite_countdown = 10 end end end class RegularZombie < Zombie def initialize_lives_remaining @lives_remaining = 10 end end class ConeheadZombie < Zombie def initialize_lives_remaining @lives_remaining = 28 end end class BucketheadZombie < Zombie def initialize_lives_remaining @lives_remaining = 65 end end class Projectile attr_reader :previous_x, :x def initialize(initial_x) @previous_x = initial_x @x = initial_x end def on_clock_tick @previous_x = @x @x += 30 end end class Pea < Projectile end class SnowPea < Projectile end GamePlay.start