views:

252

answers:

4

I am making a card game in ruby.

I have the Game class, which has an array of Player objects.

 array_of_players = Array[
  Player.new("Ben"),
  Player.new("Adam"),
  Player.new("Peter"),
  Player.new("Fred"),
 ]
 my_game = Game.new(array_of_players)

 puts my_game.players[2].name #=> Peter

Each player also has access to the Game, so that they can access the important bits of the game like so

self.game.last_card_dealt

Each player also has cards (Player.cards), and I want to make sure that players can't access each other's cards. However, the Game does need access to the cards, so I don't think using private is appropriate, and the players need access to some of each other's information, so I don't think I want that to be private either...

Basically, I want these to work.

self.cards #where self is a Player object
self.players[0].cards #where self is the Game
self.game.players[0].name #where self is a Player object

And this to fail:

self.hand.players[0].cards  #=> Nice try sucker! Cheating is for losers.

How are more complex permissions like this handled? Thanks.

+1  A: 

Keep Game.player private to disallow players from accessing other players through the array.

e.g., When self is a player, self.game.players[0].name is kind of silly.

Perhaps you'd like a public Game.player_names method that just returns an array of player names?

On top of that, you could make a public Players.opponents method.

Examples

Game.player_names

class Game
  # ...
  def player_names
    self.players.collect { |p| p.name }
  end

  private
  # private game methods
end

Player.opponents

class Player
  # ...
  def opponents(i=nil)
    return i.nil? ? self.game.player_names : self.game.player_names[i]
  end
end
macek
+1  A: 

This was fun to play with. I'm not sure if this is the best possible answer, but it works. The key is to pass the calling object to Player.cards(obj), and check if it's either the Player itself, or of type Game, both of which have legal access.

class Player
  attr_accessor :name, :game
  attr_writer :cards

  def initialize(name)
    @name = name
    @game = nil
    @cards = nil
  end

  def cards(caller)
    puts "%s cards called by %s." % [self, caller]
    if caller.kind_of?(Game) or caller == self
      puts "Here's your cards %s." % @cards
    else
      puts "Nice try sucker!  Cheating is for losers."
    end
  end
end

class Cards
  def initialize
    @cards = [1, 2, 3]
  end
end

class Game
  attr_reader :players

  def initialize(players)
    @players = players.each do |p|
      puts "Added %s to game." % p.name
      p.game = self
      p.cards = Cards.new
    end
  end
end

g = Game.new([Player.new('Bob'), Player.new('Ben')])

puts "\nCalling each Player's cards as each Player:\n\n"
g.players.each do |gp|
  g.players.each do |p|
    p.cards(gp)
  end
end

puts "\nCalling each Player's cards as Game:\n\n"
g.players.each do |p|
  p.cards(g)
end

And the output:

    Added Bob to game.
    Added Ben to game.

    Calling each Player's cards as each Player:

    #<Player:0x100122b30> cards called by #<Player:0x100122b30>.
    Here's your cards #<Cards:0x1001229c8>.
    #<Player:0x100122ae0> cards called by #<Player:0x100122b30>.
    Nice try sucker!  Cheating is for losers.
    #<Player:0x100122b30> cards called by #<Player:0x100122ae0>.
    Nice try sucker!  Cheating is for losers.
    #<Player:0x100122ae0> cards called by #<Player:0x100122ae0>.
    Here's your cards #<Cards:0x100122928>.

    Calling each Player's cards as Game:

    #<Player:0x100122b30> cards called by #<Game:0x100122ab8>.
    Here's your cards #<Cards:0x1001229c8>.
    #<Player:0x100122ae0> cards called by #<Game:0x100122ab8>.
    Here's your cards #<Cards:0x100122928>.
bojo
Thanks for your reply. I wonder though what is to stop an ill intended player from sneakily passing in the player object of the player they are calling .cards on? Aren't we back to square-one? Eg. self.game.player[0].cards(self.game.player[0])
doctororange
First I will say I believe Mladen's comment above applies completely to this line of thought. However, I also think it's interesting to puzzle these things out (maybe the puzzle here is your goal, and not the practicality of it?). In which case I would say, you then require all queries to route through Game (our chosen delegator) via public interface methods, and hand all info back based on the permissions the caller has. (i.e. a User could only get game info, their own cards, their opponents names, and their opponents visible cards only by querying Game for it).
bojo
+2  A: 

This is more practical than my other answer, and uses the Game object as a delegate to all information in the game itself (Players, Cards, etc.). Note that you still have to trust the caller to pass themselves, but seriously where do you draw the line?

class Player
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

class Cards
  attr_accessor :cards
end

class Game
  attr_reader :name, :players

  def initialize(players)
    @name = "Game Master"
    @hands = []
    @players = players.each do |p|
      puts "Added %s to game." % p.name
      @hands << {:player => p, :cards => Cards.new}
    end
  end

  def view_hand(player, caller)
    @hands.each do |hand|
      if hand[:player] == player
        if hand[:player] == caller or caller == self
          puts "%s: You can access all these cards: %s" % [caller.name, hand[:cards]]
        else
          # Do something to only display limited cards depending on this caller's view capabilities
          puts "%s: You can only access the cards I will let you see: %s" % [caller.name, hand[:cards]]
        end
      end
    end
  end

  def my_cards(player)
    @hands.each do |hand|
      puts "%s's cards: %s" % [player.name, hand[:cards]] if hand[:player] == player
    end
  end

end

g = Game.new([Player.new('Bob'), Player.new('Ben')])

puts "\nCalling each Player's cards as each Player:\n\n"
g.players.each do |gp|
  g.players.each do |p|
    g.view_hand(gp, p)
  end
end

puts "\nCalling each Player's cards as Game:\n\n"
g.players.each do |p|
  g.view_hand(p, g)
end

puts "\nEach Player calls for their own cards:\n\n"
g.players.each do |p|
  g.my_cards(p)
end

The output:

    Added Bob to game.
    Added Ben to game.

    Calling each Player's cards as each Player:

    Bob: You can access all these cards: #<Cards:0x100121c58>
    Ben: You can only access the cards I will let you see: #<Cards:0x100121c58>
    Bob: You can only access the cards I will let you see: #<Cards:0x100121bb8>
    Ben: You can access all these cards: #<Cards:0x100121bb8>

    Calling each Player's cards as Game:

    Game Master: You can access all these cards: #<Cards:0x100121c58>
    Game Master: You can access all these cards: #<Cards:0x100121bb8>

    Each Player calls for their own cards:

    Bob's cards: #<Cards:0x100121c58>
    Ben's cards: #<Cards:0x100121bb8>
bojo
A: 

Thanks for all your responses.

In the end I figured that I could give the authorised object a key which is used to allow it access to the meat of a method.

Game object has @auth_object and sets it to the player object it intends to access the secret methods of, and the player secret method checks if hand.auth_object is self, otherwise it does nothing. Then @auth_object is set back to nil. There is an attr_reader but no writer for @auth_object.

That works.

doctororange