# 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