Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery

Moose class design for game

by wanna_code_perl (Pilgrim)
on Sep 06, 2013 at 01:33 UTC ( #1052643=perlquestion: print w/replies, xml ) Need Help??
wanna_code_perl has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,

I'm toying with some proof-of-concept code for an online game with the logic written in Perl (and Moose). The game would have a typical client/server architecture, with the closed-source server determining all critical game state and logic, and open-source client handling the player UI and game view. What I'm not sure of is how to model the game state in the client without duplication of code. Say I have the bad guys modeled like so (simplified):

package TheGame::BadGuy; use Moose; has health => (is => 'rw', isa => 'Num'); # 0 == dead has max_health=> (is => 'rw', isa => 'Num'); # 100% health has inventory => (is => 'rw', isa => 'ArrayRef[TheGame::Item]'); sub health_percent { return $_[0]->health / $_[0]->max_health; } # ... many more attributes and methods

The server of course needs low-level r/w access to this information. The client, however, will run on the player's computer and must therefore have limited access but enough to display the bad guys on screen and determine how the player may interact with them.

The rub is: I'm operating under the assumption that someone will start hacking at the Perl client code. Obviously the client can't just do $badguy->health(0); and expect a meaningful result; that's not the issue. The issue is exposing only select fields to the client (possibly munged) so it's non-trivial to reverse-engineer the game mechanics (see Note), and how to design the class(es) efficiently. For example:

  • The client shouldn't know the exact health amount of the bad guy, but the client needs the percentage to show a health bar.
  • The client shouldn't know anything about the bad guy's inventory at all.

So, then, what's the best way to go about this? Keeping in mind the client will receive some kind of serialized copy of the relevant data (and only the relevant data), here are some options I've brainstormed so far:

  1. Subclass to TheGame::BadGuy::Client, with the "hidden" fields overridden to throw exceptions (the underlying hash elements wouldn't ever have been initialized, either). This sounds good on the surface, but it also implies I have to either release the server TheGame::BadGuy source with the client, or create an alternate TheGame::BadGuy package, which makes me more than a little queasy.
  2. Don't bother subclassing anything. Just instantiate a TheGame::BadGuy in the client, with only the desired fields populated. Allow undef for others. Override health_percentage, or maybe just normalize health and max_health to e.g. 0..1 or 0..100.
  3. Create a new non-subclassed TheGame::Client::BadGuy class that mirrors the desired fields, with no mention of any other fields.
  4. Go up one more level: TheGame::BadGuy is the superclass for TheGame::BadGuy::Server and TheGame::BadGuy::Client
  5. Something else?

There are tradeoffs to each of these. But it still feels like there's a pattern I'm missing (it's been a while since I did any serious OO design, plus this is my first non-trivial effort with Moose).

Note: I believe that determined players should be able to figure out most of the game mechanics, but doing so should involve observation, math, and intuition, not just a couple of trivial method calls. In fact I'd be honored if someone cared enough to go to the effort.

Replies are listed 'Best First'.
Re: Moose class design for game
by boftx (Deacon) on Sep 06, 2013 at 04:17 UTC

    My gut is telling me that you simply shouldn't allow the client to do anything that would alter the characteristics of object attributes on the server other than by calling well-defined actions such as "hit" or "run away." Granted, the server might need to send some values to the display, but it should not matter in the least if the client alters those values since it would result in an invalid display and the client would still be limited to interacting with the server by using only valid actions.

    I think you would be much more concerned about securing access to the server so that clients could not alter the code itself that resides there. Along with such basic things as understanding how "taint" is your friend when dealing with input from sh...., uhhh, users.

    Without having done any research right now, I would wager that there are several examples of game APIs on CPAN. I would look them up, especially those that deal with card games, and see how they address the issues you have brought up. Never forget the fundamental rule of programming: Don't reinvent the wheel, steal it! (Okay, I'm showing my age and Unix roots, but it applies more than ever today given how that is a primary goal of OO design.)

      Granted, the server might need to send some values to the display, but it should not matter in the least if the client alters those values

      Yes, that's part of what I tried to explain. My apologies if that bit got lost in the ramble. Since it's a client/server architecture, changing object attributes cannot affect the server's copy unless I am dumb enough to add in logic to directly or indirectly propagate those changes in the protocol, which--and you'll have to take my word for this--I'm not. :-)

      As you put it, though, how to "send some values to the display" from an OO design standpoint was pretty much my question. To that end, your suggestion of borrowing some wisdom from CPAN is well taken. Thanks!

Re: Moose class design for game
by tobyink (Abbot) on Sep 06, 2013 at 06:52 UTC

    Keep the common BadGuy stuff in a role, and compose that into different subclasses for the client and server.

    package TheGame::Roles::BadGuy { use Moose::Role; has 'name' => (is => 'ro', isa => 'Str'); requires 'health'; } package TheGame::Client::BadGuy { use Moose; with 'TheGame::Roles::BadGuy'; sub health { my $self = shift; my $health = do { ... }; return int($health/10)*10; # round to nearest 10% } } package TheGame::Server::BadGuy { use Moose; with 'TheGame::Roles::BadGuy'; has health => (is => 'rw', isa => 'Num'); has inventory => (is => 'rw', isa => 'ArrayRef'); }
    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

      That seems to fit rather neatly. Thanks! When I got my degree, roles were something you dressed up for as part of the fine arts elective, not a software design pattern. Thanks for shining a light on another thing I clearly need to do some updated reading on. :-)

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1052643]
Approved by kcott
Front-paged by NetWallah
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (8)
As of 2018-06-18 20:12 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (110 votes). Check out past polls.