STrek
Years ago I wrote a Star Trek game. Just this summer, to practice my Ruby skills with a program that had multiple classes, I wrote a greatly simplified, "baby" version of the game, which I'm calling "STrek".
The baby version is, unfortunately, pretty boring to play--the original was much, much better--but I did learn a fair amount of Ruby in the process of writing it, so to that extent, it has been a success.
This was developed in Eclipse with the RDT (Ruby Development Tools) plugin. I wrote it with each class in a separate file, but you can get the code as a single file here.
The following listing has all the classes in a single file. I
have made four changes in it: (1) I had to move the global
variables to the end, after the functions they call, (2) I
removed the require statements, (3) I had to
indent the whole thing so that pmWiki would treat it as
preformatted code, and (4) I had to change single quotes to
double quotes in one place, because the single quotes were
confusing pmWiki.
One more thing: The game tells you when you lose, but not when you win. Would you like to fix that? It's two lines: One to print a message, and one to quit the game. If you read the code, it's easy to find where to put these lines.
- -----------------------------------------------------------
class Galaxy
def initialize $galaxy_array = Array.new($galaxy_array_size) for i in 0..$galaxy_array_size $galaxy_array[i] = Array.new($galaxy_array_size, $space) end sprinkle 3, Starbase sprinkle $galaxy_size * $galaxy_size, Klingon sprinkle 25, Star.new end
- Returns a multiline string display of the galaxy
def to_s
horizontal_line = "+" + (((("--") * $sector_size) + "-+") *
$galaxy_size).chomp.chomp + "\n"
s = horizontal_line
for row in 0...$galaxy_array_size
s += '|'
for column in 0...$galaxy_array_size
s += ' ' + $galaxy_array[row][column].to_s
s += ' |' if column % $sector_size == $sector_size - 1
end
s += "\n"
s += horizontal_line if row % $sector_size == $sector_size - 1
end
s
end
- Puts 'what' at the given location in this galaxy, iff that location is empty.
- Returns true if the operation succeeded.
def place what, row, column if $galaxy_array[row][column] == $space $galaxy_array[row][column] = what true else false end end
- Removes and returns an object from the given location in this galaxy.
def remove row, column what_was_there, $galaxy_array[row][column] = $galaxy_array[row][column], $space return what_was_there end
- Finds and returns an empty location in this galaxy.
def find_empty_location begin row = rand $galaxy_array_size column = rand $galaxy_array_size end until $galaxy_array[row][column] == $space return row, column end
- Places the given 'thing' at a random location in this galaxy, and returns
- the chosen location.
def randomly_place_one thing row, column = find_empty_location $galaxy_array[row][column] = thing thing.row, thing.column = row, column return row, column end
- Places 'how_many' objects of type 'what' in this galaxy. 'what' may be
- either an object or a class.
def sprinkle how_many, what
if what.class == Class
how_many.times { thing = what.new; randomly_place_one thing }
else
how_many.times { randomly_place_one what }
end
end
- Returns the location one step from the given location in the given direction.
def one_step row, column, direction
case direction
when :n: row -= 1
when :ne: row -= 1; column += 1
when :e: column += 1
when :se: row += 1; column += 1
when :s: row += 1
when :sw: row += 1; column -= 1
when :w: column -= 1
when :nw: row -= 1; column -= 1
else puts "No such direction: #{direction}"
end
return row, column
end
end
- -----------------------------------------------------------
module Location
attr_accessor :row, :column
- Given a sector row or column number, returns the corresponding range of array.
- row or column indices
def to_indices sector return $sector_size * sector .. $sector_size * (sector + 1) - 1 end
- Given a row or column index, returns the corresponding sector index.
def to_sector index return index / $sector_size end
- Test whether two locations are in the same sector.
def same_sector? row_1, column_1, row_2, column_2 to_sector(row_1) == to_sector(row_2) and to_sector(column_1) == to_sector(column_2) end
- Given a row or column, find the range of rows or columns in that
- same sector.
def range index min = (index / $sector_size) * $sector_size max = min + $sector_size - 1 return min..max end
- Return the distance between two locations.
def distance row_1, column_1, row_2, column_2 [(row_1 - row_2).abs, (column_1 - column_2).abs].max end
end
- -----------------------------------------------------------
class EmptySpace
def to_s ' ' end
end
- -----------------------------------------------------------
class Star include Location
def to_s '*' end
end
- -----------------------------------------------------------
class Starbase include Location
def to_s 'B' end end
- -----------------------------------------------------------
class Starship
def initialize @energy_level = 10 @torpedo_count = 5 end
def move how_far, direction
old_row, old_column = @row, @column
case direction
when :n: @row -= how_far
when :ne: @row -= how_far; @column += how_far
when :e: @column += how_far
when :se: @row += how_far; @column += how_far
when :s: @row += how_far
when :sw: @row += how_far; @column -= how_far
when :w: @column -= how_far
when :nw: @row -= how_far; @column -= how_far
else puts "No such direction: #{direction}"
end
@row %= $galaxy_array_size
@column %= $galaxy_array_size
if $galaxy.place self, @row, @column
$galaxy.remove old_row, old_column
else
puts "Can't move there!"
@row, @column = old_row, old_column
end
end
end
- -----------------------------------------------------------
class Klingon < Starship include Location
@attack = false $klingon_count = 0
def initialize super $klingon_count += 1 end
def to_s 'K' end
- Attack the Enterprise with either a photon torpedo or a phaser beam.
def attack @attack = true if rand(4) == 0 and @torpedo_count > 0 and (@row == $enterprise.row or @column == $enterprise.column) puts "The Klingon at #@row, #@column hits you with a photon torpedo!" @torpedo_count -= 1 $enterprise.take_hit 3 return else puts "The Klingon at #@row, #@column hits you with a phaser beam!" hit = [4 - distance(@row, @column, $enterprise.row, $enterprise.column), 1].max $enterprise.take_hit hit end end
- Get a hit from the Enterprise.
def take_hit amount=1 @energy_level -= amount puts " You hit the Klingon at #@row, #@column! (Energy level now #@energy_level.)" if @energy_level <= 0 puts " The Klingon at #@row, #@column has been destroyed!" $galaxy.remove @row, @column $klingon_count -= 1 end end
- Gradually increase Klingon energy level back to max value.
def restock # Energy level improves when Enterprise changes quadrants. # Torpedos don't restock @energy_level += 1 if @energy_level < 10 end
end
- -----------------------------------------------------------
class Enterprise < Starship include Location
attr_reader :row, :column, :alive, :short_move
def initialize super @row , @column = $galaxy.randomly_place_one self @alive = true restock end
def move how_far, direction old_row, old_column = @row, @column restock_klingons if ! same_sector? @row, @column, old_row, old_column super how_far, direction
if docked? restock puts " You are docked at a starbase." puts " Your energy level is #@energy_level, and you have " + "#@torpedo_count photon torpedos." end puts $galaxy end
- Shoot a photon torpedo.
def photon_torpedo direction
if docked?
restock
puts 'You can\'t fire through starbase shields.'
return
end
if @torpedo_count == 0
puts " You are out of photon torpedos!"
return
end
puts "Torpedos: #@torpedo_count"
row, column = @row, @column
@torpedo_count -= 1
loop do
row, column = $galaxy.one_step row, column, direction
break if ! same_sector? @row, @column, row, column
thing = $galaxy_array[row][column]
if thing.class == Star
puts " The photon torpedo falls into the star at #{row}, #{column}."
return
elsif thing.class == Klingon
thing.take_hit 4
return
elsif thing.class == Starbase
puts " Oh, no! You destroyed your own starbase!"
$galaxy.remove row, column
return
end
end
puts " The photon torpedo disappears off the edge of the quadrant."
end
- Fire phasers.
def phaser direction
if docked?
restock
puts ' You can\'t fire through starbase shields.'
return
end
for row in range(@row)
for column in range(@column)
sum = @row + @column
diff = @row - @column
in_phaser_beam = case direction
when :n: row - column <= diff and row + column <= sum
when :e: row - column <= diff and row + column >= sum
when :s: row - column >= diff and row + column >= sum
when :w: row - column >= diff and row + column <= sum
when :ne: row <= @row and column >= @column
when :se: row >= @row and column >= @column
when :sw: row >= @row and column <= @column
when :nw: row <= @row and column <= @column
else puts "No such direction: #{direction}"
end
next if ! in_phaser_beam
thing = $galaxy_array[row][column]
if thing.class == Klingon
hit = [4 - distance(@row, @column, row, column), 1].max
thing.take_hit hit
end
end
end
end
- Test if adjacent to a starbase.
def docked? for row in @row - 1 .. @row + 1 for column in @column - 1 .. @column + 1 if same_sector?(row, column, @row, @column) and $galaxy_array[row][column].class == Starbase return true end end end return false end
- Fully stock and replenish the Enterprise.
def restock @energy_level = 10 @torpedo_count = 5 end
- Gradually restock Klingon energy (but not torpedos).
def restock_klingons # It's easier to do them all than just the ones in this sector for row in 0...$galaxy_array_size for column in 0...$galaxy_array_size thing = $galaxy_array[row][column] thing.restock if thing.class == Klingon end end end
def take_hit amount=1 @energy_level -= amount if self.class == Enterprise puts " Your energy level is down to #@energy_level." if @energy_level <= 0 puts " Oh no! You have been destroyed! The galaxy will fall to the" puts " forces of evil!" @alive = false $galaxy.remove @row, @column end end end
def method_missing method_id, *args
puts "No such command: #{method_id}"
end
def to_s 'E' end
def list_commands puts "Available commands: move distance, direction (abbreviation: m) phaser direction (abbreviation: pha) photon_torpedo direction (abbreviations: pho, torp) list_commands (abbreviation: list) quit" puts "Directions: :n :ne :e :se :s :sw :w :nw" end
alias m move alias pho photon_torpedo alias torp photon_torpedo alias pha phaser alias list list_commands
end
- -----------------------------------------------------------
class Strek include Location
def initialize $galaxy = Galaxy.new $enterprise = Enterprise.new end
def intro puts "The Orgonian Treaty has collapsed, and Klingons are invading" puts "Federation space! Your job is to destroy the Klingon menace" puts "and make the galaxy safe for democracy!" puts end
def play_game
intro
puts $galaxy
puts
$enterprise.list_commands
while $enterprise.alive
# Klingons attack!
if $enterprise.docked?
puts ' Starbase shields protect you from Klingon attacks.'
else
for row in range($enterprise.row)
for column in range($enterprise.column)
thing = $galaxy_array[row][column]
if thing.class == Klingon
thing.attack
end
end
end
end
break if ! $enterprise.alive
# Enterprise retaliates!
print "\nCommand: "
$stdout.flush
command = '$enterprise.' + (raw_command = gets.chomp)
next if raw_command.strip == ''
break if command == '$enterprise.quit'
begin
eval command
rescue SyntaxError, Exception
puts " Your command \"#{raw_command}\" was not understood."
$enterprise.list_commands
end
end
puts "Game over."
end
end #----------------------------------------------------------- # Global variables.
$sector_size = 5 $galaxy_size = 3 # in sectors $galaxy_array_size = $sector_size * $galaxy_size $space = EmptySpace.new
- -----------------------------------------------------------
- This is where the action starts.
trek = Strek.new trek.play_game