Screencast: Assignment 2: Implementing Pylons and Players
Read this first, then use the screencast links below. There may be some fumbling in this screencast, because I need to cover a lot of stuff, and I’m not going to be able to do a lot of editing. Of course, I ran through most of this when I covered Keith’s section, but I think I can still screw it up!
For Assignment 2, we ask you to implement Pylons and Players according to our requirements, as well as a couple according to your designs.
In this screencast, I’m going to give you a tour of some of the code. Here’s our agenda for the screencast:
- Game Commands: First I’ll run the example game, and show you the basic commands (pause, resume, etc.). All of this is described in the assignment direction, but it could be useful for you to see this in action. One thing I notice I didn’t say emphatically in the screencast is: press “p” to pause, press “r” to resume, and “t” to “single-tick.”
- rake test Then I’ll show you what running the tests looks like.
- Logging: Using the MyLogger module: You can include MyLogger in any class; by this means
you can log behavior in your Player or Pylon. This works great when you pause the game, and “single-tick.” In the screencast, watch for when I make a key point about the way the GameManager uses the Map: At each location in the Map is an Array of the Entities at that particular location. I’ll show how to use the logging to establish this fact. - modify_requested_move callback For the Pylon class, modify_requested_move is called by the GameManager on a Pylon to give it a chance to change the direction of a Player that has run into it. By this means, a Pylon can bounce a Player into a new direction.
- accept_tick For the Player class, accept_tick is called by the GameManager on a Player to let it know that time has passed. The Player is given two bits of data, the tick_number and a view. The view is simply a very small instance of Map, centered on the Player. Even if the player is at coordinates -3, 2, say, on the big Map, the view is always relative to the Player. The Player is centered at 0, 0, and the small view only extends to -1..1 and -1..1 on each of the x, y axes. Thus, the Player gets only a limited view of the playing field. But it is enough to do some interesting things.There is a lot you can do with your Player, but I’m going to focus on iterating through the view, and showing that at each location there’s an Array. Unfortunately I can’t go into a huge amount of detail here, because it would reveal the solution, but I at least want to show you how to see what’s going on with the view.
Click for Screencast 1 (items 1-4)
Click for Screencast 2 (item 5)
Code from the screencast: Copy to clipboard and paste into a file in the root of the project download.
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'world'))
class SplitterPylon < World::Pylon
def initialize(*args)
@appearance = @identity = ":"
super
end
def modify_requested_move(entity, direction)
# since you've read the code, you know that a Pylon is an
# Entity, and Entities have access to the GameManager. Because
# of this, we can call public methods on the GameManager.
#
# Also, since this method is passed an Entity, we can look
# at its class and create a new one . . .
begin
entity.class.new(game_manager, 0, 0, World::EAST)
rescue
message "Splitter: Couldn't create a new #{entity}!"
end
# Let the entity continue in its current direction
direction
end
end
class SpiralPlayer < World::BasicPlayer
include World::MyLogger
MY_MOVES = [ World::EAST, World::SOUTH, World::WEST, World::NORTH ]
RESET_LIMIT_AT = 10
def initialize(*args)
super
@limit_in_direction = 0
@num_in_direction = 0
@appearance = @identity = '@'
end
def accept_tick(tick_number, view)
# The spiral player will change its direction at longer and longer
# intervals. E.g., something like this:
# TICK DIRECTION
# 0 Same
# 1 Change
# 2 Same
# 3 Same
# 4 Change
# 5 Same
# 6 Same
# 7 Same
# 8 Change
# etc.
# Reset the "limit" when it gets bit
if @limit_in_direction > RESET_LIMIT_AT
@limit_in_direction = 0
end
log "SP", "#{@limit_in_direction} #{@num_in_direction}"
if @num_in_direction >= @limit_in_direction
@limit_in_direction += 1
@num_in_direction = 0
i = MY_MOVES.find_index(@direction)
new_direction = MY_MOVES[ (i + 1) % MY_MOVES.size ]
@direction = request_move(new_direction)
else
@num_in_direction += 1
super
end
end
end
# This Pylon does nothing, and has a very basic
# #inspect method, which will help when we look
# at the log
class DoNothingPylon < World::Pylon
def initialize(*args)
@appearance = @identity = "?"
super
end
def inspect
@identity
end
end
# This Player is simply for studying what is in the
# view
class ViewAnalysisPlayer < World::BasicPlayer
include World::MyLogger
def initialize(*args)
super
@appearance = @identity = 'V'
end
def accept_tick(tick_number, view)
log "VAP", "--- iterating through each location in a view"
view.each do |location|
log "VAP", "#{location.inspect}"
end
log "VAP", "---"
log "VAP", "--- iterating through each location in a view"
log "VAP", "--- (and with coordinates)"
view.each_with_coordinates do |location, x, y|
log "VAP", "#{x}, #{y}: #{location}"
end
log "VAP", "---"
if (view.all? { |location| location == [] || location == [self] })
log "VAP", "I'm the only one in the view!!"
end
@direction = request_move(direction)
end
def inspect
@identity
end
end
# This is a Player that will "chase" another Player
# if the other Player gets close enough. There is
# some tricky stuff in this Player; I'll discuss it
# in a screencast if time permits, but don't count on
# it.
class ChasePlayer < World::BasicPlayer
include World::MyLogger
def initialize(*args)
super
@appearance = @identity = '&'
end
def accept_tick(tick_number, view)
log "CP", "#{view.inspect}"
# Default: Have the new direction be the current direction
new_direction = direction
player_to_chase = nil
player_x = nil
player_y = nil
# Find an entity to chase: We will go through each
# location in the view.
view.each_with_coordinates do |entities, x, y|
# Remember: Each Map (view) element is an Array
# Can we find an Entity at this location that is a Player,
# but also not our own self?
# If so, break out of the loop, keeping references to the
# player_to_chase, and its x and y
if player_to_chase = entities.find { |e| e.is_a?(World::Player) && e != self }
player_x = x
player_y = y
break
end
end
# After the loop above, if player_to_chase is set,
# we also have two variables, player_x and player_y.
# Remember that the view is a small grid. Suppose
# the "other" Player was found to be at coordinates
# 1, -1. We'll mark that Player as "P," and our own
# player as "&" (our "appearance"):
#
# -1 0 1
# 1:
# 0: &
# -1: P
#
# Now, here's the magic for setting the new direction.
# If you look in location.rb, you'll see that all of
# the directions such as NW, SOUTH, etc., are simply
# built from the Location class (Delta is really just
# a synonym for Location, but it is more expressive
# of the idea that to move in a given direction means
# moving the current location by a "delta" amount).
# So NW happens to be -1, 1, and SOUTH is 0, -1. For
# example, if we're at 3, 3, then to move NW would be
# to go to (3, 3) moved by (-1, 1). Understand? That's
# (3-1, 3+1), or (2, 4). This means that the direction
# we would want to go in to chase P is its coordinates
# on the small grid. Got it? If P is at 1, -1 (see above),
# then we should go in direction 1, -1 (SE). So we know
# the direction to go in based on the position in the view.
# Did we find one? If so, set our new direction to what
# that entity is . . .
if player_to_chase
# read above. World::Location(player_x, player_y)
# is really just picking the direction based on the
# view coordinates for the other entity.
new_direction = World::Location(player_x, player_y)
changed_direction = true
end
# Change direction to the new one.
@direction = request_move(new_direction)
end
end
gm = World::GameManager.new(-10, 10, -7, 7)
# create a Splitter
# SplitterPylon.new(gm, 9, 0)
# Couple more Players
# SpiralPlayer.new(gm, 0, 0, World::WEST)
# ChasePlayer.new(gm, -8, 1, World::EAST)
ViewAnalysisPlayer.new(gm, 0, 0, World::EAST)
DoNothingPylon.new(gm, 7, 0)
# Start the game
gm.start
I have been unsuccessful in running the above code as staff_game.rb from the root of a freshly downloaded 'e168-assignment2-jgn-1.1.001'.
Right at launch I receive "Exception during tick: Press q to stop; check log for message". A quick look through the log, and I see:
ticker: ----- Exception: Tick #0:
undefined method `all?' for #
staff_game.rb:104:in `accept_tick'
My first thought is that it is unhappy with 'all', but from the screencast, I had thought that this became available because of the use of each. Any suggestions on how to correct this would be appreciated.
@Ken Vedaa
To get the all? method, which you would call on view (which is an instance of the Map class), you must include the Enumerable module.
The Enumerable module provides this method; to do so, it leverages the each method.
(This is stated a number of times across the assignment writeup, what we've said in lecture and section, in the screencasts, etc.)
@Ken Vedaa
I believe all? is defined on the module Enumerable. The Enumerable documentation states:
"The class must provide a method each, which yields successive members of the collection."
My guess is it has something to do with that. (Remember what task 1 is from the assignment...)
@Brendan Shea
Thanks, Brendan -- exactly.
noooo now i need another idea for the player (I were doing chaser :)
@Lesha
I think you can come up with a variation that will work. Avoider?
so basically if there is another player in a view chose opposite direction of that player?
@Lesha
Try it. It would be interesting to put an Avoider and a Chaser right next to each other and see what happens. Also remember that you can create lots of Players: It would be kind of cool to create, say, 20 Avoiders in very close proximity to one another and see what happens.
ty John
We implement this in real life at work by having a badge that needs to get passed around which designates who in the secure lab has the responsibility of securing the facility on departure. Hence near the end of the day, everyone avoids everyone else in order to lower the risk of running into someone trying to hand off the badge.
@Ken Vedaa
It should be possible to implement Players where they "hand off" some object. You would manage that object through a class method.