here

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.

  1. -----------------------------------------------------------
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
  1. 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
  1. Puts 'what' at the given location in this galaxy, iff that location is empty.
  2. 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
  1. 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
  1. 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
  1. Places the given 'thing' at a random location in this galaxy, and returns
  2. 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
  1. Places 'how_many' objects of type 'what' in this galaxy. 'what' may be
  2. 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
  1. 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
  1. -----------------------------------------------------------
module Location
attr_accessor :row, :column
  1. Given a sector row or column number, returns the corresponding range of array.
  2. row or column indices
def to_indices sector
return $sector_size * sector .. $sector_size * (sector + 1) - 1
end
  1. Given a row or column index, returns the corresponding sector index.
def to_sector index
return index / $sector_size
end
  1. 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
  1. Given a row or column, find the range of rows or columns in that
  2. same sector.
def range index
min = (index / $sector_size) * $sector_size
max = min + $sector_size - 1
return min..max
end
  1. 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
  1. -----------------------------------------------------------
class EmptySpace
def to_s
' '
end
end
  1. -----------------------------------------------------------
class Star
include Location
def to_s
'*'
end
end
  1. -----------------------------------------------------------
class Starbase
include Location
def to_s
'B'
end
end
  1. -----------------------------------------------------------
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
  1. -----------------------------------------------------------
class Klingon  < Starship
include Location
@attack = false
$klingon_count = 0
def initialize
super
$klingon_count += 1
end
def to_s
'K'
end
  1. 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
  1. 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
  1. 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
  1. -----------------------------------------------------------
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
  1. 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
  1. 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
  1. 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
  1. Fully stock and replenish the Enterprise.
def restock
@energy_level = 10
@torpedo_count = 5
end
  1. 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
  1. -----------------------------------------------------------
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
  1. -----------------------------------------------------------
  2. This is where the action starts.
trek = Strek.new
trek.play_game