Assignment 2: Classes and Methods

October 15th, 2009 Leave a comment Go to comments

Errata

  1. Due date changed to 19-Oct.; the due date for Assignment 3 may get pushed back a bit, but don’t count on it.
  2. By student request, see below at question/answer 8 for what happens during a tick.
  3. In the directions below, I note that the World::Map#clear method iterates through all of the members of the map. Our student B. pointed out to me that the #clear method was iterating throught the y’s and then, in the inner loop, through the x’s. But our tests that check #each and #each_with_coordinates do it the other way. Therefore, I’veĀ  changed World::Map#clear so that it clears as so:
        def clear
          (@x_min..@x_max).each do |x|
            (@y_min..@y_max).each do |y|
              self[x, y] = []
            end
          end
        end
    

    You can get the updated download from the downloads page.

    This iteration strategy fits the test; in other words, when you’re implementing each, consider looping through the elements this way. B. also suggested that we might not need a defined order in which the elements should be enumerated. What do you think? In this assignment, there is an order, but maybe we can imagine a Map as something more like a Set with no specific order.


The purpose of this assignment is to give you some experience with classes (and modules) and methods, and, in particular, classes that must fit into the requirements of a framework. In this project, your code is called by other code. It isn’t standalone.

The basic idea of the code is that there is a Map, and Players move around on the Map. Also on the Map are Pylons, which are essentially obstacles. The Pylons can affect the movement of the Players. There is a ticker that ticks, and at each tick, a GameManager is notified; it, in turn, notifies each of the Players that a tick has happened. The Player can then respond to its environment, and request a change of position. Your work is mostly about Pylons and Players, though the first task regards the Map.

The inspiration for this assignment were some photographs and maps regarding Ariel Pylon Racing. Here’s an example course:

4 Pylon course 46-47-48

(from Society of Air Racing Historians)

And here’s a picture of a plane rounding a Pylon:

rounding-pylon

(from Reno Pylon Racing School)

In a nutshell, you have five tasks to accomplish (more detail near the end):

  1. Implement each and each_with_coordinates methods on the World::Map class (and pass tests). Then you will include Enumerable into this class. This is key for the other tasks.
  2. Implement some Pylons and Players (and pass tests).
  3. Invent a Pylon of your own design.
  4. Invent a Player of your own design.
  5. Write briefly about the design choices you made.

The download has a number of classes in the lib/world/ directory that manage the game and animate the screen with the ancient and venerable “Curses” library (to learn more, see Wikipedia and the Ruby Cookbook at Safari — you will not need to do any raw Curses programming yourself [--you're welcome :-)]). Open the download, and in the project root, try

ruby example_game.rb

You should see something like this:

game-image

Each of the single characters is some kind of Entity in the game. Some are stationary; those are the Pylons. The moving ones are the Players. The Players 1, 2, and 3 are instances of the ExamplePlayer class (for this and others, see lib/world/example_entities.rb). @ is a SlowPlayer (the @ is supposed to evoke a snail), and # is a BlinkyPlayer (not shown in the image above because it’s currently blinked off). The | and = Pylons “reflect” players when they are hit. Notice that Player 3 is relatively unconstrainted, and when it hits an edge, it comes out on the other side: Thus the coordinate system “wraps around.”

Once in the game, there are some commands you can type. Each one is a single key-press:

q: quit the game.
p: pause the game. Once paused, press t to go to the next tick.
t: go to the next tick (only works when game paused).
r: resume from pause.

While the game is running, you should also take a look at the log. To do this, start up two terminal windows. In one window, run the game. In the other window, do

tail -f info.log

“tail -f” means show the end of the file, but “follow” it as new lines are added. By this means, you can see the log grow and grow, focusing on the newest information. The tail command is standard on Unix/Linux, and comes with the Windows Cygwin package. The log will look something like this (truncated on the right):

game-log

Before I get into the code, I want you to notice some things about the log. The different components identify their log lines with a prefix. For example, there’s a line prefixed with “GM” (GameManager), that gives information about the internal data structures at a particular tick. The entire map state is logged for each tick. Notice that the big map of positions is centered at 0, 0, and extends from negative to positive coordinates in each direction. In the example game, there are five Players. Each Player gets a limited view of the whole map, centered on the Player itself. So in what you see above, Player 1 sees only a 3×3 grid (-1..1, -1..1) centered on itself. Just to its north-west is Player 3. Player 3 sees the same thing (not shown), but centered on himself. In the views of the maps (both the whole map and the Player-oriented sub-views), if there are two Entities on the same location, you will see a +. Finally, you may have observed that the views “wrap around”; if a Player is in the very upper-right of the map, the rows above the Player will actually be at the bottom of the whole map, and the columns at the right of the player in the limited view will wrap around to the far left side. The upshot of this is that no Player knows exactly where it is; it only has a limited, relativistic view of its position on the map.

One more key piece of information about the Map class and views: At each coordinate of the Map, the GameManager stores an Array. The Array contains all of the Entities at that location. The Array may be empty. Thus, when Player#accept_tick is called, and the Player is given a view, if you want to figure out what players are where, you need to iterate through view, but also through each Array at each location. This point — that at each coordinate in a view is an Array of all of the Entities at that location — is key for implementing the ShyPlayer.

The GameManager manages Entities (you may find the class definitions for Entity, Pylon, and Player in lib/entity.rb). An Entity is initialized with a reference to a GameManager, a preferred starting x coordinate, and a preferred starting y coordinate.

There are two kinds of Entities, Pylons and Players. As I have said above, Pylons are like obstacles. They don’t move, but they can affect the movement of Players. Players move, and they also affect the movement of other Players.

Here is the key information regarding Entities, Pylons, and Players (and some related topics, such as directions). Remember that Pylon and Player both subclass Entity, so anything I say about Entity applies to Pylon and Player as well.

  1. In the source code for Entity, notice that during initialization, GameManager#setup_entity is called. The reason for this is that Entities don’t get to determine their own location and direction state; then have to ask the GameManager to do it for them. If you look at GameManager#setup_entity, you will see that it picks a location, records the entity and its location, and, finally, if the Entity is a Player, calls #setup back on the entity. When it calls back to the entity, it gives the entity its direction, as well as the default radius. Later on we will see that the Player never knows where it is; it only gets passed “views” centered on itself at a certain radius. It never sees the entire Map.
  2. When the GameManager sees that a Player is running into another Entity, it calls #modify_requested_move on the “target” entity. This gives the target entity a chance to change the direction of whatever is about to crash into it. For example, the target entity might “reflect” (change the direction to the opposition direction). It is also possible to suggest new coordinates as well as change the direction. Read the comments in the source.
  3. Each Entity has an “identity” — this is a one-character String that is supposed to be unique through the lifetime of the Entity. Each Entity also has an “appearance.” This is also one-character, but the Entity is allowed to change its appearance whenever it wants. The appearance is what is shown on the Map.
  4. An Entity can broadcast an information message (with the message method) which will be displayed on the screen so that a human user can be told that something interesting has happened.
  5. A Pylon is really the same as an Entity, but I decided to just subclass Entity should I want to give any special characteristics later on to Pylons.
  6. Now, Players. Players have a direction (see next item) and a visual radius. Player#setup is called by the GameManager to start the Player its official direction and visual_radius. The player never sets these directly — it makes proposals to the GameManager, the GameManager evaluates it, and then the GameManager updates the player.Player#request_move calls the GameManager to ask for a specific new direction. Player#request_move is called by Player#accept_tick.Player#accept_tick is called at regular intervals by the GameManager. There are two values that are passed in: The tick_number, and the view. Based on this information, Players can make fun decisions about what kind of move they want to request.
  7. Directions: There are eight values for directions, World::NORTH, World::NE, World::EAST, World::SE, World::SOUTH, World::SW, World::WEST, World::NW. (In the implementation, these are “Locations,” which are just two-value pairs, so that World::NORTH is actually just the pair (0, +1) meaning that going North leaving the x coordinate the same, and incrementing the y coordinate.

If you turn your attention to entity_examples.rb, you’ll see some example Pylons and Players. For HorizontalPylon, the only method that is altered is modify_requested_move. See how it works? It looks up the approaching Entity’s direction in the NEW_DIRECTION Hash. If the approaching Entity is moving EAST, then it returns a new direction of WEST. And if WEST, it returns EAST. By this means, it bounces approachers in the opposite direction. VerticalPylon does the same thing for the other access. That’s about the only method you can customize for a Pylon, aside from controlling the appearance. (In this version, Pylons don’t accept ticks — we might do that in the future.)

Questions and Answers:

  1. Q: My player isn’t moving!
    A: accept_tick needs to call request_move(@direction). If you’re not doing that in your implementation, then call super
  2. Q: I changed the tick_interval in the Ticker class to be very small, and nothing works.
    A: The smallest interval we tested was 0.1.
  3. Q: The WormHolePylon is required to make Players jump to a random location. How do I do that?
    A: While Entities don’t know where they are, they have a reference to the GameManager, and on the GameManager, there are methods random_x and random_y. By this means, you can have modify_requested_move return a suggested location that is random_x, random_y
  4. Q: I would like to have my Players affect one another directly. Can I do that?
    A: Yes. When accept_tick is called on a Player, it is passed a view (a subset of the big map), as discussed above. You can iterate through the map, examine what’s at each coordinate, and check to see if any of the Entities at that coordinate are of a particular class, or “respond to” a method that interests you; and, if you wanted to, you could call such methods. Clearly, this would only be appropriate for Players you’ve designed yourself, but it’s fair game. NOTE: You might want to think about what kind of access you provide to other players: Should methods open to other players of the same class be public, protected, or private?
  5. Q: What about Pylons? I’d like to write a Pylon that can change the state of a Player.
    A: A Pylon is passed a reference to an Entity in modify_requested_move, so why not?
  6. Q: I’m having a hard time with the ShyPlayer, figuring out whether it has come near other Players. What’s going on?
    A: When accept_tick is called, a Player is given a limited view of the Map; this limited view is centered on the Player, that is, the Player is at 0, 0. Each location is itself an Array, because more than one Entity can be at the same spot. So, to figure out what’s around, you would need to iterate through the view, and then, for each location, iterate through its Array. Then for each Array element, you would want to determine whether the element is a (use the is_a? method) Player or an Entity.
  7. Q: I’m stumped for what custom Pylon(s) and Player(s) I should write.
    A: Here are some ideas: A Player that will “chase” any other player that comes into its view; a Player that will stop moving when it “sees” certain players; a Player that sends a message, changing its appearance successively to different letters (for example, “m” then “i” then “n” then “a” . . . minaswan!); Players that swarm; Pylons that turn themselves on and off based on what Players come in contact with them; Players that respond to “over-population”; A Pylon or Player that provides “interesting” debug information in the log about its history and interactions with other Entities. If you have other ideas, post them in the comments.

    One thing I would advise is: start simply, and debug each Pylon or Player as you go along.

  8. Q: What happens during a tick?
    A: Some students have been writing Pylons and Players (or thinking about it), and are curious about the exact sequence in which events happen. Here’s how the GameManager (hereafter, GM) works (Remember that instance methods are prefixed with the pound sign (#) and class methods with the period (.)):

    You should read along in the code!

    At periodic intervals, a Thread in the Ticker calls the GM’s #accept_tick method. #accept_tick iterates through all of the Entities in the order in which they were added to the GM. If an Entity is a Player, the Player’s #accept_tick method is called. by this means, at the periodic interval, actions are triggered in the GM and then in each Player. Understand that Players don’t all receive a particular tick “at the same time.” The Players get the ticks in Player order. The upshot of this is that a Player may NOT assume that the state of the GM when it accepts a tick is the way the game was before ANY other Players received that same tick; the game (that is, the state of the Pylons and Players on the Map) may have been changed by other Players who have gotten this very tick earlier.

    The GM will call a Player’s #accept_tick method with two arguments: the tick_number and a view of the Map centered on the coordinates of the Player. Notice that for this view, the Player is at 0, 0. Players don’t know where they are in Map coordinates; they only get a “relative” view. The tick_number is important. If a Player wanted to keep a history of all prior views, it would want to save the view for each tick under that tick_number. I didn’t require that you do that, but it could be a neat trick to have a Player that has a memory of what has transpired. You could create a Player that has a specific behavior after it has “seen” some other specific Player more than a certain number of times.

    So far we’ve focused on GM#accept_tick: By all means, look at the code. It’s pretty simple.

    Now let’s turn to the Player.

    The key to Player#accept_tick is that in the default implementation, it calls GM#request_move. This is very important. Players do not move themselves; they ask the GM for a new direction. When they ask for a new direction with #request_move, they can provide two arguments: the direction, and a flag regarding whether the move should be skipped. Notice that Player#accept_tick actually delegates to #request_move, and that #request_move calls the GM’s #request_move (with self as the first argument). OK?

    Now the GM gets to think about what the Player has requested (look at the source for GameManager#request_move).

    If skip_move is true, it just returns the current direction right back to the Player. So the Player just continues going in whatever direction it was before.

    Otherwise:

    First the GM picks a new location that is based on the direction that the Player requests. This is provisional — it’s what we’ll use if no Pylons or other Players make changes. For example, the Player might request to go WEST. The GM figures out the current location adjusted by stepping in the WEST direction. Again, this is the provisional new direction; nothing has happened yet.

    Then the GM removes the Player from its old location. In the code it looks like this:

    @map[old_location.x, old_location.y].delete(entity)
    

    Once it’s gone, it can re-plot what’s at that old location. The Player isn’t there any more, so, typically, it will be a blank spot (empty Array), though it might be that there’s still a Pylon or another Player there, or maybe multiple entities. but in any case, we “plot” what’s at that old location:

    plot(old_location, display(old_location.x, old_location.y))
    

    (the #display method figures out the appropriate character to display.)

    Now it’s time to decide the true new location of the Player.

    The GM picks the “last” Entity (which we call the “dominant_entity”) to have been placed at that location:

    if dominant_entity = @map[new_location.x, new_location.y].last
      modification = *dominant_entity.modify_requested_move(entity, direction)
      new_direction = modification[0]
      if modification.size == 3
        new_location = World::Location.find(modification[1], modification[2])
      end
    end
    

    [Arguably this is not the right way to do it; we could have designed this so that every Entity on a Map location gets to try to modify the Player's request, but that seemed too complicated to me.]

    Notice that if @map[new_location.x, new_location.y].last evaluates to nil, the condition on the if statement will be false, and the code block won’t run. Also notice that an assignment of a value of dominant_entity is happening in the “if” statement; I personally use this idiom all the time.

    So in any case, we call #modify_requested_move on the dominant_entity. This gives the dominant_entity a chance to change the direction of the Player. In the typical case, it returns one value, the direction. Additionally, it can return an x and y, which is why we splat the return value to get an Array, and handle either the direction alone (modification[0]), or the direction along with the specified x and y (in modification[1] and modification[2]).

    So now the Player has a new location. We add the Player to the Map location, update the Hash that correlates Entities with their locations, and plot what’s at the updated location:

    @map[new_location.x, new_location.y] << entity
    @entity_to_location[entity] = new_location
    plot(new_location, display(new_location.x, new_location.y))
    

    Finally, we return the new_direction to the Player (it may not have gotten what it wanted!).

    And that’s it.

Recap of your tasks / what you must hand in:

  1. Implement each and each_with_coordinates methods on the World::Map class (and pass tests). Then you will include Enumerable into this class. This is key for the other tasks.Hints: (a) Notice that in the implementation of World::Map (in lib/world/map.rb) that the clear method is going through all of the elements. Since an each method goes through all of the elements, you might get some ideas for how to implement each (and each_with_coordinates) from clear. (b) How do determine what is at a particular location on the map? See Map#at.
  2. Implement some Pylons and Players (and pass tests). Specifically: World::RightMirrorPylon, World::LeftMirrorPylon, World::WormHolePylon, and World::ShyPlayer. The specifications are in the documenation tree at doc/index.html in the download.
  3. Invent (a) Pylon(s) of your own design.
  4. Invent (a) Player(s) of your own design.
  5. Write briefly about the design choices you made in readme-student.txt.

All student code must go in lib/world/student_solution.rb and lib/student_game.rb! student_game.rb should be an example game (like example_game.rb in the project root) that illustrates what you’ve done. We will downgrade submissions that alter lib/world/game_manager.rb, lib/world/map.rb, etc. Take a look at lib/world/student_solution.rb: For the first task (implementing Map#each and Map#each_with_coordinates) we have provided a stub of the class; your code is essentially “monkeypatching” the existing version of Map in map.rb. Do not monkeypatch any other core classes.

All of these tasks are summarized in the doc/index.html file in the download, which is here: http://e168f09.plugh.org/extras/downloads/e168-assignment2-jgn-1.1.001.zip.

  1. Ron Newman
    October 10th, 2009 at 11:15 | #1

    Thanks for explaining the tick in detail.

    At Antony's 6:30 section, you said that instead of editing example_game.rb, we should create a new file with our own game -- but I don't remember what name you said to give to the file, or where we should put it. You said something about putting it in a particular place to make sure that both Rake and our graders will find it.

  2. Gary
    October 11th, 2009 at 20:49 | #2

    My shy player passes all tests but visually it is not doing what I would like it to do. Am I missing something or is the following map (subset) acceptable where S is headed SW, and 3 is headed NW?

    --S
    -3-
    ---

  3. October 12th, 2009 at 08:30 | #3

    @Gary

    Could you paste in a very, very simple starting game situation, setting one players at certain coordinates and directions, and then say at what tick you are observing the anomaly?

    (Don't paste in any of your "shy" code -- I just want to see what happens with my implementation given your scenario.)

  4. Gary
    October 12th, 2009 at 14:49 | #4

    @john

    For the following game with two players and no pylons:
    p1 = ExamplePlayer.new(gm, 5, 5, World::NORTH)
    p1.identity = '1'
    ShyPlayer.new(gm, 6, -5, World::SOUTH)

    it seems like at tick 6 the shy player should no longer be shy.

    Thanks,
    Gary

  5. October 12th, 2009 at 14:53 | #5

    @Gary

    Thanks, I'll look into it.

  6. Ben Ho
    October 12th, 2009 at 18:24 | #6

    @Gary

    I would like to jump in and say I am experiencing the same issues as to what Gary is saying. I was testing something like this:

    p1 = ExamplePlayer.new(gm, 6, 5, World::SOUTH)
    p1.identity = '1'
    ShyPlayer.new(gm, 6, 3, World::SOUTH)

    For me, my tests passed as well.

    I noticed that my shy player would be "one y-value off"; meaning, my shy player(x,y) would be shy from a player (x, y+2) away, but not (x, y-1) away.

    So this will not be shy:

    p1 = ExamplePlayer.new(gm, 6, 2, World::SOUTH)
    p1.identity = '1'
    ShyPlayer.new(gm, 6, 3, World::SOUTH)

    Initially, I thought there was something wrong with my own implementation, but it is interesting to see another person with the same issue.

    -Ben

  7. October 12th, 2009 at 18:43 | #7

    Gary and Ben,

    In Ben's case, I think that what is happening is that the other player is moving out of the way before your shy player can see it. Remember that the players get their ticks in the order in which they are added to the game. So the ExamplePlayer gets its tick, moves out of the way, and then is out of the visual range of the ShyPlayer.

    In Gary's case: I'm still looking at it.

    Ben, So . . . try reversing the order of adding them to the game, and I think you'll see the shy player becoming shy, and staying shy.

  8. Ben Ho
    October 12th, 2009 at 19:20 | #8

    Hi John,
    Yup, as you said, the player is shy when the order is reversed.

    So I am assuming that, at least with my case, the output is being displayed as it should? Thanks.

    -Ben

  9. October 12th, 2009 at 20:20 | #9

    @Ben Ho

    Ben -- Yep, you're good. I think Gary's is right, too, but I need to go through the code to explain it properly.

  10. Ron Newman
    October 12th, 2009 at 22:02 | #10

    I'm almost ready to turn mine in, but still need an answer to the question I asked on October 10 (first comment on this page 2)

  11. October 12th, 2009 at 23:04 | #11

    @Ron Newman

    Oct. 11th? If you passed the tests, you should be fine. But do *MAKE A NOTE* about the concern you have about the behavior in your readme-student.rdoc.

  12. Ron Newman
    October 12th, 2009 at 23:17 | #12

    No, no, I mean this one:

    "At Antony's 6:30 section, you said that instead of editing example_game.rb, we should create a new file with our own game -- but I don't remember what name you said to give to the file, or where we should put it. You said something about putting it in a particular place to make sure that both Rake and our graders will find it."

  13. Antony
    October 12th, 2009 at 23:22 | #13

    @Ron Newman

    I believe it was lib/student_game.rb

    It won't hurt to mention the name when you submit, just make sure it's in lib (or a subdirectory).

  14. Ron Newman
    October 13th, 2009 at 00:16 | #14

    Thanks. I also saw behavior similar to Gary's and Ben's for the ShyPlayer.

    Next year, it might be better for the game manager to make all moves 'simultaneously' within a tick, rather than one-by-one. It would save you the long explanation in FAQ #8 ;-)

  15. October 14th, 2009 at 12:08 | #15

    @Ron Newman

    It's a good idea. We'll see . . .

  16. Ron Newman
    October 14th, 2009 at 19:13 | #16

    When one of my Pylons or Players writes a message to the screen, and that message is shorter than whatever message was there before, the end of the previous message isn't erased. This makes the new message confusing and hard to read. Is there an easy way for me to fix this?

  17. October 14th, 2009 at 19:48 | #17

    @Ron Newman

    That's funny -- I was sure I had fixed that weeks ago. Pad it with spaces, and I'll look into a general fix. Curses is very annoying with line erasing.

  18. October 14th, 2009 at 20:30 | #18

    Gary (and Ron, and Ben): Yes, the problem is with the fact that the Players take their ticks in order.

    At least the ShyPlayer does EVENTUALLY get unshy. I guess I will have to change the simulation so that the Players are all using the same state of the Map at each tick.

    I don't think this mars too much the point of the exercise, though I, like you, would rather that the state change in an individual player be more obvious.

  19. Ron Newman
    October 14th, 2009 at 23:15 | #19

    @john: If you add the following code to example_game.rb , right after the line

    p3.identity = '3'

    you'll see that "FooFooFooFooFoo" remains on the right side of the terminal window even as other shorter messages flash on and off.

    class << p3
       def accept_tick (tick_number, view)
          message "Foo"*25 if (tick_number % 10 == 0)
          super
       end
    end
    

    Oddly, if I change it to message just one "Foo" instead of 25, the bug does NOT occur -- the short "Foo" message erases the longer message that preceded it.

  20. October 14th, 2009 at 23:20 | #20

    @Ron Newman

    This is reminding me of the Woody Allen joke: Patient: "doctor, it hurts me when I go like this!" Doctor: "Well don't do that!"

    The offending code is in the Screen claas:

        def message_at_line_number(line_number, s)
          C.setpos(@h + 3 + line_number, 2)
          C.addstr('%-60s' % s)
          C.setpos(@h + 3 + line_number, 0) # Put the cursor out of the way
          C.refresh
        end
    

    As you can see, the '%-60s' format was an attempt to do something reasonable with the width of the messaging. Probably simply truncating messages that don't fit would have been a better strategy.

  21. Ron Newman
    October 14th, 2009 at 23:27 | #21

    If I change that format to "%-80s", the bug seems to go away.

    Does that cause any other problem? If not, I'd like to just edit it in my package before I submit it.

  22. October 14th, 2009 at 23:29 | #22

    @Ron Newman

    That will be ok.

    Though I'd call it a feature rather than a bug.

  23. Ron Newman
    October 14th, 2009 at 23:31 | #23

    I ran into the bug because I have players that send messages like this one:

    "AttractivePlayer: tick 15, attract player S, new direction -1, -1"

  24. October 14th, 2009 at 23:33 | #24

    @Ron Newman

    I hear you. In fact I lengthened it to 60 for the very same reason.

  25. Lateral Punk
    October 16th, 2009 at 15:54 | #25

    will enum.any? short-circuit itself as soon as it founds an element that is true? e.g. it wont' continue searching for more elements.

  26. October 16th, 2009 at 16:22 | #26

    @Lateral Punk

    Well, it should, but it would depend on the implementation of Enumerable. Maybe a lame implementation would go through everything, and then just report "true" if any is a match. A smart one would return "true" immediately.

    Here's the implementation from the Rubinius project (Ruby in Ruby; for the MRI [Matz's Ruby Interpreter], Enumerable is probable in C):

      def any?
        if block_given?
          each { |o| return true if yield(o) }
        else
          each { |o| return true if o }
        end
        false
      end
    

    So . . . makes sense?

    Notice, by the way, how any? leverages each . . .

  27. Lateral Punk
    October 16th, 2009 at 17:25 | #27

    make sense.

    Also, I couldn't figure out how to determine the Max Int, Min Int, supported by your platform in Ruby. I need Max Int for some distance calculations. Could I use this:
    http://drawohara.com/post/117643208/ruby-integer-max-and-integer-min

    or is there something already in Ruby?

  28. October 16th, 2009 at 19:23 | #28

    @Lateral Punk

    Could you say a bit more about your requirements -- why you would need max/min int? Is it for a personal project?

  29. Lateral Punk
    October 16th, 2009 at 19:45 | #29

    I'm trying to find all the players "closest" to the current player (within the given view radius). In order to do that I need to keep track of a min_dist variable. Pseudocode:
    min_dist = max_int
    for all players at current location
    if dist(me,cur_player) < min_dist
    process cur_player
    min_dist = dist(me,cur_player)
    end
    end

    it's jut your standard usage of min,max temp variables.

  30. Lateral Punk
    October 16th, 2009 at 19:48 | #30

    I'm trying to determine all the players closest to me:

    min_dist = MAX_INT
    for all players at current location
    cur_dist = dist(me,curr_player)
    if (cur_dist < min_dist)
    cur_dist = min_dist
    do_process
    end
    end

    its ur standard usage of min,max temp vars...
    I guess I could recode it so that min_dist is initialized to the first distance in the view in the locations...i just hate doing that sort of stuff

  31. October 16th, 2009 at 20:09 | #31

    @Lateral Punk

    Remember that you are given a view centered on the player (who is at 0, 0 from the point of view of the player).

    The view radius is 1.

    So the little view goes from x = -1 to +1, y = -1 to +1. There are only nine positions with the player in the middle.

    So . . . if you see a player, it's either on your own spot, or on one of the ones surrounding. There should be no way for a player to peak at the enter grid, or get the coordinates of the other players. The individual player only sees things relative to himself.

    So -- I'm not sure why you would need to compute any distances.

  32. Lateral Punk
    October 16th, 2009 at 20:21 | #32

    Yeah I realize that the max distance would always be 1. My idea was when the view radius is changed to something > 1 then my logic could apply. Seeing that it was just a constant, I thought it would make sense to allow this possibility. I was trying to be generic, though I do understand that we can't change the default radius. So, is this worth my effort?

    Nonetheless, my question still holds. Is there a MIN_INT, MAX_FLOAT, etc in Ruby?

  33. Ron Newman
    October 16th, 2009 at 20:37 | #33

    No, because Ruby automatically overflows Fixnums to Bignums that can become arbitrarily large.

  34. Lateral Punk
    October 16th, 2009 at 20:44 | #34

    no worries though, i just realized i can do:
    min_dist = visual_radius*visual_radius
    as my initializer.

    Oh yeah, you maybe wondering why i'm doing visual_radius*visual_radius. It's because I'm comparing all distances squared so as to avoid the expensive sqrt:

    cur_dist = (x2-x1)(x2-x1) + (y2-y1)*(y2-y1)

    e.g. I only care about the approximate magnitude of the distance. This is a short cut used in computer graphics a lot to avoid the expensive sqrt.

  35. Lateral Punk
    October 16th, 2009 at 20:44 | #35

    oh I see..makes sense now :)

  36. October 16th, 2009 at 21:09 | #36

    @Lateral Punk

    Here's one thing you can do (not for the assignment, though). I'm sitting here waiting for Flash to be output from my screencasting software, so I'm literally dead in the water for a few minutes.

    So . . . suppose you increased the visual radius constant, which is in World::GameManager (you can do this to play around, but don't make this change for submitted code). Now the view presented to the Player might well go from -3..3, -3..3.

    At this point, the distances would be sort of interesting to think about.

    Since the Player is always at 0, 0, it should be very easy to calculate the distance as the square root of the x-delta plus the y-delta.

    But this raises a very interesting question: How WOULD you get the coordinates of another Player in the view that is presented to your player?

    This is actually kind of hard, because when you iterate through the view with each, you just get the Array of Entities at each particular location. Players don't actually know where they are, so even if you have a reference to a Player, you can't say: player.x or player.y

    The secret is that you can iterate through the view with .each_with_coordinates, which will give you the relative coordinates as you iterate -- and every time you hit a player, you'll know where it is, relatively speaking.

    Not sure if that makes sense.

    When I get the screencast up for creating Pylons and Players, this technique is illustrated by the ChasePlayer (in the code bundle: Not sure if I will actually discuss it in the screencast).

  37. Lateral Punk
    October 16th, 2009 at 22:03 | #37

    "The secret is that you can iterate through the view with .each_with_coordinates"

    that's exactly what I'm doing :)

    My idea is that I'm trying to make a smart NinjaPlayer. though he may be able to see further than 1 radius, he really only wants to beat up the closest enemy :)

  38. Brendan Shea
    October 17th, 2009 at 08:34 | #38

    I noticed something that seems like a shortcoming in the WormHolePylonTest:

    The test is checking for randomness of the x and y values returned to ensure they are evenly distributed. However, if your WormHolePylon always returns the same x and y (or doesn't return it at all) it will pass the test. The reason is that the 'sample space' that the test ensures is randomized is defined by the values that your WormHolePylon returns -- so if you always return 0, for instance, it will ensure that all the values you return are 'randomized' across that one; and by definition, they are. :)

    I think in order to address this, the test would need to initialize the hash values (ys and xs) to 0 for all x and y values that it 'expects' the returned x and y values to be randomized across.

    I noticed this when I forgot to do anything with the returned x and y values but the test still passed.

  39. October 17th, 2009 at 08:57 | #39

    @Brendan Shea

    I think you're right. I think the place to alter the code would be the use of xs.keys.size in the last set of assertions, so that it would use the number of x's that are determined by the game/map size (compare with the way the move values are checked).

    Brendan, tell me if this works. At the top of test_modify_requested_move, add:

    World::GameManager.send(:public, :get_map)
    

    This is a cheat so that we can get at the map (which is protected) via the game manager.

    Then replace the x, y tests with:

        xs.keys.each do |x|
          assert (NUM_SAMPLES / gm.get_map.width - xs[x]).abs < NUM_SAMPLES / 40, "#{xs[x]}"
        end
        ys.keys.each do |y|
          assert (NUM_SAMPLES / gm.get_map.height - ys[y]).abs < NUM_SAMPLES / 40, "#{ys[y]}"
        end
    

    Thoughts?

  40. Brendan Shea
    October 17th, 2009 at 10:00 | #40

    @john

    Yep, that works!

    It also illustrates how the ability to change access permissions (and redefine methods!) is handy for unit testing. That sort of thing is something I run into all the time in Java-land (for instance, "how do I mock a final class?") so I can imagine how useful this is in practice.

  41. October 17th, 2009 at 10:03 | #41

    @Brendan Shea

    Changing the access is sleazy. A somewhat more graceful way to do it is to subclass and access the item "from the inside."

    We do the same thing in one of the other tests, and there, I note that changing the access method is kind of "cheating."

    Keith may have some advice on a better way to do this -- he's a much better test-oriented developer than I.

  42. Brendan Shea
    October 17th, 2009 at 10:21 | #42

    @john

    IMO, anything in a test that is 'scaffolding' (i.e. doesn't affect the class or method under test, often mock objects) that makes the test better/easier, or makes the class more testable, is fair game. But I see your point.

    In Java, I often use libs like PowerMock (http://code.google.com/p/powermock/) to do things that Java doesn't let you do otherwise (like mock a final class, mock a final static method -- even a Java system one!) which turns out to be quite handy in certain situations. If you look under the hood to see what PowerMock is doing, though, its pretty scary. :)

    But I digress -- this is a Ruby class, not a Java one :) I'd be interested to check out some mock libraries in Ruby to see what you can do there (and how they work!)

  43. David Palmer
    October 17th, 2009 at 10:46 | #43

    i have implemented Map#each and Map#each_with_coordinates, and they pass the unit tests (yay!)

    and i think i have the right idea for the Shy Player, but when i run the unit test for Shy Play i get this error:

    NameError: uninitialized constant ShyPlayer::Player

    i'm following the hint to use any? {... block ... }

    i shook the google tree and found all answers pointing to a missing 'require' but i'm not convinced that's the problem, and am more convinced i'm doing something quite wrong.

  44. October 17th, 2009 at 11:07 | #44

    @David Palmer

    First double-check that the file that defines your ShyPlayer is in the lib/world directory.

    Reason: The tests require everything in that directory. If your ShyPlayer is in the wrong place, it won't be found.

    Next, you may have done something like this:

    class ShyPlayer < World::BasicPlayer
      # In here you might be wrongly referring to Player, instead of World::Player
    
      # wrong
      if something.is_a?(Player)
      end
    
      # right
      if something.is_a?(World::Player)
      end
    
    end
    

    In what's above, you'd have a problem (depending on how your requires are set up), 'cos without the prefix World::, Ruby doesn't realize that the Player class you want is in the World module. It would think that Player is "namespaced" by the current class (ShyPlayer), which would explain the message (uninitialized constant ShyPlayer::Player).

  45. Mike Byrne
    October 17th, 2009 at 12:52 | #45

    I'm confused about the purpose of the Class Location, I'm not sure we'll need it for assignment 2 but anyway...if I put this log statement
    log('location test', "#{World::Location(-4,3)}")and run an example game, the info log shows location test: -4, 3.
    I guess my question is this, if we know a set of coordinates, why would you send them to World::Location(), only to have it return the same coordinates...or in other words, what is this Location class all about?

  46. Mike Byrne
    October 17th, 2009 at 13:29 | #46

    disregard my question about Class Location. The new Screencast, Assignment 2, Implementing Pylons and Players, has a great example in the remarks of the code...never mind

  47. Brendan Shea
    October 17th, 2009 at 15:36 | #47

    I just wanted to confirm -- the only thing we should need to do to change the appearance of a player on the screen is to modify the @appearance instance var, right? Based on the GameManager.display() method that seemed to be the case.

    The reason I ask is that, I am seeing some odd behavior with my ShyPlayer. When I set up a test game with a ShyPlayer and an ExamplePlayer that comes near it, it modifies @appearance properly, but the game screen doesn't update. So:

    {ruby}
    gm = World::GameManager.new(-8, 9, -5, 5)

    # create our player so that it will graze the player to its right
    ExamplePlayer.new(gm, 1, 4, World::SOUTH)

    # create our ShyPlayer
    ShyPlayer.new(gm, 0, 0)

    # Start the game
    gm.start
    {/ruby}

    doesn't result in the appearance of the ShyPlayer changing at all. I have the ShyPlayer 'message'-ing when it tries to change the appearance, though, so I know that it is doing it at the right time.

    At first I thought it might be related to the order of when the players were added to the GM (as per some other comments) but that doesn't seem to make a difference for this.

  48. Brian Mantenuto
    October 17th, 2009 at 16:46 | #48

    quick verification. Should I be able to remove a player during game play simply by setting it to nil?

  49. October 17th, 2009 at 17:01 | #49

    @Brendan Shea

    Do you see the change in the NEXT tick? The screen updates are handled successively by player, you might well not see the change quite when you expect it.

    Sometimes if you alter the order in which you add players to the game you will see something closer to what you expect.

    Also, Brendan: You're passing the tests for the ShyPlayer?

  50. October 17th, 2009 at 17:11 | #50

    @Brian Mantenuto

    Removing players is actually "out of scope," and is not supported by the framework.

    It *might* work that when you get the view, and access a location, you could delete an element in the Array that contains the elements.

    It would be something like this (I want to nuke all Players of class FooPlayer if they come in range):

    view.each { |location| location.delete_if { |e| e.is_a?(FooPlayer) } }

    I haven't tried this myself, and it might well blow up, because there the GamePlayer does keep references to the existing Entities.

    This is actually an architectural problem in the game: I went to great lengths to prevent Players from manipulating the data in other Entities directly, but the view provides an opening to do all kinds of naughty things. Next time, the view and its contents will be read-only.

    I actually want to change the system so that it is possible for Players to attack one another, and have Strength, Wisdom, Cleverness attributes -- stuff like that, so that a Player might have a motivation to eat another Player and get its goodness.

Comment pages
  1. No trackbacks yet.