# By:
# CSE341 -- Homework 7
### Insert WARMUP code here. ###################################################
## TODO: Insert methods here.
### End of WARMUP code. ########################################################
class Module # An example of metaprogramming for the help system.
private
# A method that will set the help text for the given command.
def set_cmd_help cmd_name, help_str
help_var = ("help_"+cmd_name.to_s).to_sym
define_method help_var do help_str end
end
end
# A global state mixin. Contains error recovery for bad player
# commands, a help system, and stubs for the basic methods any state
# needs in order to work properly in our game design.
module SystemState
# For any unknown command entered by a player, this method will be
# invoked to inform the player. self is returned as the new active
# state.
def method_missing m, *args
if m.to_s[0..3] === "cmd_" # Check if it's a game command.
puts "Invalid command: please try again or use the 'help' command."
# Since this method is called in place of a command method,
# it must act like a command method and return a State.
self
else # Otherwise, it's a real missing method.
super.method_missing m, *args
end
end
set_cmd_help :help, "\t\t-- print available list of commands"
# A global help command for a player to call at any time. This will
# print all available commands for the user and return self, not
# changing the game state.
def cmd_help
cmd_names = self.public_methods.find_all {|m| m =~ /^cmd_/}
cmd_help = cmd_names.map do |cmd|
cmd_name = cmd.to_s[4..-1]
help_method = "help_"+cmd_name
cmd_help = ""
cmd_help = self.send help_method if self.respond_to? help_method
cmd_name + " " + cmd_help
end
puts "These are the currently available commands:"
print_indented cmd_help
self
end
set_cmd_help :quit, "\t\t-- quit the game"
# Set the game as finished.
def cmd_quit
@endgame = true
self
end
# Returns true iff the quit command has been called.
def finished?
@endgame
end
# When a state is finished, this method will be called to inform the
# user of the results. This stub will raise an exception and must
# be overridden in any state that has a finished? method that
# returns true.
def print_result
# Thank the user for dropping by.
puts
puts "Thanks for dropping by!"
end
end
# A simple state that signals a finished game and has a print_result
# informing the user of a failure game conclusion.
class FailureState
# As all good state classes must, include the SystemState module.
include SystemState
# Always return true to signal an end game scenario.
def finished?
true
end
# Prints a failure message for the player.
def print_result
puts
puts "You failed!!!"
end
end
# A simple state that signals a finished game and has a print_result
# informing the user of a victorious game conclusion.
class VictoryState
# As all good state classes must, include the SystemState module.
include SystemState
# Always returns true to signal an end game scenario.
def finished?
true
end
# Prints a congratulatory message for the player.
def print_result
puts
puts "Good job!!! You win!!!"
end
end
# The humble ancestor for all items or enemies.
class Entity
attr_reader :tag
def initialize tag
@tag = tag
end
def to_s
@tag
end
end
#### Insert YOUR ADVENTURE GAME code here. ####################################
## TODO: Your SystemState extension should go here #############################
## TODO: Your PlayerState mixin module should go here ##########################
# The State of the game when a player is peacefully hanging out in a
# particular room in the world.
class RoomState
# As all good state classes must, include the SystemState module.
include SystemState
include PlayerState # We also want these commands
# Given a world hash and (optionally) a key to the world[:rooms]
# hash, this method will initialize a State in which the player is
# in the given room and current state of the world. If no room is
# given, world[:start_room] is used instead.
def initialize world, room=nil
@world = world
if room
@room = world[:rooms][room]
else
@room = world[:rooms][world[:start_room]]
end
puts @room[:desc]
end
set_cmd_help :look, "\t\t-- look around the room for items, exits, etc."
# Allows the player to look at the room's description, the current
# items in the room, and the available exits to other rooms.
def cmd_look
puts @room[:desc]
# Print items
if @room[:items] and not @room[:items].empty?
puts " Items within reach:"
print_indented @room[:items]
end
# Print exits
puts " You see the following exits:"
print_indented(@room[:exits].map do |dir, room_key|
room = @world[:rooms][room_key]
dir.to_s + " (" + room[:name] + ")"
end)
self # No change in the game's State
end
set_cmd_help :go, "
\t-- go through the exit in the given direction"
# Given a direction (optionally), takes the player to corresponding
# if it exists. If no such direction exists or no direction is
# given, a helpful notification will display to the user.
def cmd_go direction=nil
if not direction
puts "Go where???"
self
elsif not @room[:exits][direction.to_sym]
puts "No such place to go! (Maybe you should look around for an exit!)"
self
else
newroom = @room[:exits][direction.to_sym]
move_to_room @world, newroom
end
end
set_cmd_help :take, "- \t-- take the item and put it in the inventory"
# Allow the player to take an item (with the given item_tag) from
# the room and place it in their inventory. If no such item with
# the item_tag exists or no item_tag is passed, a helpful
# notification will display to the user.
def cmd_take item_tag=nil
if not item_tag
puts "Take what???"
return self
end
when_detected @room[:items], item_tag do |item|
@world[:inventory] ||= [] # To deal with nils
@world[:inventory].push item
@room[:items].delete item
puts "You grabbed the " + item_tag
return self
end
# No item found...
puts "No such item..."
self
end
end
## TODO: Your FightState class and related Entity subclasses should go here ####
#### End of YOUR ADVENTURE GAME code. #########################################
# The first game state entered by the game. This allows the player to
# select a game world or quit without doing anything.
class MainMenuState
# As all good state classes must, include the SystemState module.
include SystemState
# The hash of available worlds for the player. Worlds are defined
# at the end of the file so that they don't clutter the code here.
@@worlds = {}
# Print a welcome message for the user and explain the menu.
def initialize
puts "Welcome to the 341 adventure game!"
puts "I'm your host, the MainMenu!"
puts
cmd_help
end
set_cmd_help :play, "\t-- start a new game"
# If given a valid world name in $worlds, return the initial game
# state (a RoomState) for that world. Otherwise, tell the user
# about any invalid world given and run the worlds command.
def cmd_play world_name=nil
return cmd_worlds if not world_name
world = @@worlds[world_name]
if not world
puts "No such world: " + world_name
cmd_worlds
else
# Introduce world and start in initial room
puts "Welcome to the world of " + world[:long_name] + "!!!"
puts "------------------------"
puts world[:desc]
puts
RoomState.new(world.clone) # Copy world definition for mutation protection
end
end
set_cmd_help :worlds, "\t-- list all available worlds to play"
# Simply print out the available worlds to play without changing the
# game state.
def cmd_worlds
puts "The available worlds:"
print_indented @@worlds.keys
self
end
end
# The main class for playing the adventure game. Simply has a play
# method that'll start up the interactive game REPL.
class Adventure
def play
state = MainMenuState.new
until state.finished?
print "Enter a command: "
command = gets.split "\s"
if not command.empty?
cmd_name = "cmd_"+command[0]
cmd_args = command[1..-1]
puts
# Send command to current state with its arguments. Retrieve
# next game state and save it for next command.
# Commands will be sent any number of arguments that the
# player enters all as strings.
begin
state = state.send cmd_name, *cmd_args
rescue ArgumentError => e
# Check for a player mistake (i.e. they gave a wrong number
# of arguments to a command)
if e.backtrace.first =~ /`#{cmd_name}'$/
# Treat player mistake as an invalid command.
state.method_missing cmd_name, *cmd_args
else
# Otherwise, it's a real exception and should be punted
raise e
end
end
end
end
# On a finished state, print the results for the player and end
# the REPL.
state.print_result
end
end
# Add to the worlds available.
class MainMenuState
@@worlds["TestGame1"] = { # This defines a world labeled "TestGame1"
:long_name => "Test World Number 1", # A long name to describe the world
:desc => "A simple test world with all the right stuff!", # Worlds description
:health => 20, # The player's starting health in this world
:start_room => :room1, # The room to first place the player
:final_room => :room4, # The goal room for the player to reach
:rooms => { # A hash from room id's to their details
:room1 => { # A room with :room1 as its id
:name => "Central Room", # The room's name
:desc => "This is an empty room with nothing in it.", # Room description
:exits => { # Hash from exit id's to room id's
:north => :room3,
:south => :room2}},
:room2 => {
:name => "Armory",
:desc => "You see a room filled with items (that are mostly out of reach)!",
# :items => [ClownHammerWeapon.new, # An enum of items that can be picked up
# BalloonShield.new],
:exits => {
:north => :room1}},
:room3 => {
:name => "Enemy battle",
:desc => "A deadly test room with deadly, deadly enemies.",
# :enemies => [GoblinEnemy.new, # An enum of enemies in wait in this room
# MimeEnemy.new],
:exits => {
:south => :room1,
:north => :room4}},
:room4 => { # A simple room meant for ending the game
:name => "Victory Room",
:desc => "You reached the end of your journey."}}}
end