Shackers, shoesers, and shoobs,
This one should work on all three platforms.
Here's a lil' game of 'roids to satiate all of your Shoes-related
space-flying and gun-blasting needs. Don't play too safe,
suicide attacks can sometimes come in handy.
Give it a spin. When (or if) the asteroids crush you,
press 'N' to get a new game.
_why: The keypress event that Shoes gives you isn't so
hot for flying a spaceship. Slam the throttle hard left
(press the left arrow) and you'll get one jerk left, a pause,
and then a rapid torrent of lefts. Not so smooth. For the
same reason, you can't shoot and steer at the same time.
So while the keypress event is well and good for key
sniffing and delegation, maybe we could get something else?
How about a method on the Shoes app:
key_pressed?(:left)
Would return true or false.
G'night.
— Jeremy
# Omygawshkenas. Jan 2008.
# This code is hereby released into the public domain.
class Asteroids
WIDTH = 700
HEIGHT = 600
def self.new_game
@lives = 3
@level = 0
@text = nil
@ship = Ship.new(WIDTH/2, HEIGHT/2)
@roids_count = 3
self.start_new_level
end
def self.start_new_level
@level += 1
unsafe_to_start = true
while unsafe_to_start do
Roid.reset
@roids_count.times {|i| Roid.new(rand(WIDTH), rand(HEIGHT), (rand - 0.5)*2.5, (rand - 0.5)*2.5, Roid::LARGE) }
unsafe_to_start = Roid.check_for_collision_with(@ship, Ship::UNIT*5)
end
end
def self.game_over
@text = "Game Over"
end
def self.draw
@ship.check_for_collision_with_roids if @ship
Ray.check_for_collision_with_roids
$app.clear do
$app.background $app.black
$app.para("Lives left: [EMAIL PROTECTED]", :top => 5, :left => 5, :stroke => $app.gray(0.5), :font => "13px")
$app.para("Level: [EMAIL PROTECTED]", :top => 5, :left => WIDTH - 75, :stroke => $app.gray(0.5), :font => "13px")
$app.title(@text, :top => 240, :left => 80, :stroke => $app.gray(0.1), :font => "bold 100px") if @text
@ship.draw if @ship
Ray.draw_all
Roid.draw_all
end
end
def self.ship_explodes
center_x, center_y = @ship.center_x, @ship.center_y
if @lives > 0
@lives -= 1
unsafe_to_start = true
@ship = Ship.new(WIDTH/2, HEIGHT/2)
while unsafe_to_start do
unsafe_to_start = Roid.check_for_collision_with(@ship, Ship::UNIT*5)
@ship = Ship.new(rand(WIDTH), rand(HEIGHT)) if unsafe_to_start
end
else
@ship = nil
self.game_over
end
100.times do |i|
direction = ((2 * Math::PI) * i/100)
Ray.new(center_x, center_y, 10*Math.cos(direction), 10*Math.sin(direction), direction)
end
end
def self.maybe_start_new_level
if Roid.roids_left == 0
@roids_count += 1
self.start_new_level
end
end
def self.keypress(key)
@ship.move(key) if [:left, :right, :up].include?(key) && @ship
@ship.shoot if (key == ' ') && @ship
self.new_game if key == 'n'
end
end
class CelestialBody
attr_accessor :x, :y, :size
def center_x
@x + @size/2
end
def center_y
@y + @size/2
end
def intersects_with?(thing, margin = 0)
return true if Math.sqrt((center_x - thing.center_x)**2 + (center_y - thing.center_y)**2) < (@size/2 + ((thing.size + margin) / 2))
return false
end
end
class Roid < CelestialBody
LARGE = 80
MEDIUM = 45
SMALL = 25
def self.add_roid(roid)
@roids << roid
end
def self.remove_roid(roid)
@roids.delete_at(roid)
end
def self.draw_all
@roids.each {|roid| roid.draw }
end
def self.roids_left
@roids.length
end
def self.reset
@roids = []
end
def initialize(x, y, vel_x, vel_y, size)
@x, @y = x, y
@vel_x = vel_x + (rand - 0.5)*2.5
@vel_y = vel_y + (rand - 0.5)*2.5
@size = size
Roid.add_roid(self)
end
def draw
@x += @vel_x
@y += @vel_y
@x = @x % Asteroids::WIDTH
@y = @y % Asteroids::HEIGHT
$app.stroke($app.gray(1.0, 0.5))
$app.fill($app.rgb(0.2, 0.2, 0.5, 0.5))
$app.oval(@x, @y, @size, @size)
end
def explode(index)
Roid.remove_roid(index)
case @size
when LARGE
2.times { Roid.new(center_x - MEDIUM/2, center_y - MEDIUM/2, @vel_x, @vel_y, MEDIUM) }
when MEDIUM
2.times { Roid.new(center_x - SMALL/2, center_y - SMALL/2, @vel_x, @vel_y, SMALL) }
when SMALL
Asteroids.maybe_start_new_level
end
end
def self.check_for_collision_with(thing, margin = 0)
@roids.each_with_index do |roid, index|
return [roid, index] if roid.intersects_with?(thing, margin)
end
return false
end
end
class Ship < CelestialBody
UNIT = 30
def initialize(x, y)
@x, @y = x, y
@vel_x, @vel_y = 0, 0
@direction = 0
@size = UNIT
end
def draw
@x += @vel_x
@y += @vel_y
@x = @x % Asteroids::WIDTH
@y = @y % Asteroids::HEIGHT
$app.stroke($app.gray(1.0, 0.5))
$app.fill($app.rgb(0.7, 0.2, 0.2, 0.5))
$app.oval(@x, @y, UNIT, UNIT)
$app.oval(@x + 20*Math.cos(@direction) + 10, @y + 20*Math.sin(@direction) + 10, UNIT/3, UNIT/3)
$app.fill($app.rgb(1.0, 1.0, 0.8, 0.5))
$app.oval(@x + 5*Math.cos(@direction) + 12.5, @y + 5*Math.sin(@direction) + 12.5, UNIT/5, UNIT/5)
end
def shoot
Ray.new(@x + UNIT/2, @y + UNIT/2, @vel_x, @vel_y, @direction)
end
def move(way)
case way
when :left
@direction -= 0.25
when :right
@direction += 0.25
when :up
@vel_x += 1.5*Math.cos(@direction)
@vel_y += 1.5*Math.sin(@direction)
["@vel_x", "@vel_y"].each do |vel|
instance_variable_set(vel, 10) if instance_variable_get(vel) > 10
instance_variable_set(vel, -10) if instance_variable_get(vel) < -10
end
end
end
def check_for_collision_with_roids
if roid = Roid.check_for_collision_with(self)
Asteroids.ship_explodes
end
end
end
class Ray
@rays = []
attr_accessor :x, :y, :size
def self.add_ray(ray)
@rays << ray
end
def self.draw_all
@rays.each_with_index {|ray, i| ray.draw(i) }
end
def self.remove_ray(index)
@rays.delete_at(index)
end
def self.check_for_collision_with_roids
@rays.each_with_index do |ray, index|
if roid_list = Roid.check_for_collision_with(ray)
Ray.remove_ray(index)
roid_list[0].explode(roid_list[1])
end
end
end
def initialize(x, y, vel_x, vel_y, direction)
@x, @y, @vel_x, @vel_y, @direction = x, y, vel_x, vel_y, direction
@vel_x += 8 * Math.cos(@direction)
@vel_y += 8 * Math.sin(@direction)
@size = 5
Ray.add_ray(self)
end
def draw(index)
@x += @vel_x
@y += @vel_y
if @x > Asteroids::WIDTH || @x < 0 || @y > Asteroids::HEIGHT || @y < 0
Ray.remove_ray(index)
else
$app.stroke($app.rgb(1.0, 1.0, 0.8, 0.8))
$app.line(@x, @y, @x + @vel_x, @y + @vel_y)
end
end
def center_x
@x
end
def center_y
@y
end
end
Shoes.app :width => Asteroids::WIDTH, :height => Asteroids::HEIGHT, :title => "Asteroids", :resizable => false do
$app = self
Asteroids.new_game
animate(60) do
Asteroids.draw
end
keypress do |k|
Asteroids.keypress(k)
end
end