3

I don't know what it is called as, I believe it is instances.

Let's say we have a "Monster" class. This class should contain "Health" value. It is set to "5" on class creation. It should also contain an unique ID value for each monster.

I think about something like this:

$monster_1 = new Monster(); //sets monster ID to 1 $monster_2 = new Monster(); //sets monster ID to 2 $monster_3 = new Monster(); //sets monster ID to 3 

So, let's say I want to calculate the total HP of all monsters.

$totalHp = $monster_1->getHp() + $monster_2->getHp() + $monster_3->getHp(); 

It will also work, but what if I add a new monster named $monster_4. I would have to add it to $totalHp calculation manually.

I could make an array instead of $monster variables, and calculate the $totalHp with foreach - but it looks weird...

I don't know how it is being done in other languages.

Is there any other way to archieve this?

Ps. I'm trying to get the logic behind "http://www.scirra.com/demos/ghosttutorial/" game. You create a Monster class, and each Monster uses this class as an instance. My solution would definitely work, but I'm not sure if it is a hacky way to archieve this.

11
  • You could do it with eval() but it'd be quite hacky. The array thing seems your best bet, I'd say go with it. Commented Jun 8, 2012 at 19:30
  • 9
    don't use eval(), never. Commented Jun 8, 2012 at 19:31
  • 2
    Storing all monsters in an array is a perfectly fine solution. Commented Jun 8, 2012 at 19:33
  • @OZ_ That's why I said it'd be quite hacky. Commented Jun 8, 2012 at 19:36
  • 1
    @OZ_ Classical cargo cult programming. If you never use the one feature that differentiates scripting from compiled languages, you're doing something seriously wrong. Meme coding and not understanding use cases (right tool for the right job) isn't overly professional. Commented Jun 8, 2012 at 19:43

10 Answers 10

3

Create a helper class which holds a set of Monsters, maybe a MonsterTeam class;

class Monster { ... } class MonsterTeam { private $_monsters = []; public function addMonster(Monster &$monster) { array_push($this->_monsters, $monster); } public function getTotalHp() { $hp = 0; foreach ($this->_monsters as $monster) { $hp += $monster->getHp(); } return $hp; } } $team = new MonsterTeam(); $monsterA = new Monster(); $team->addMonster($monsterA); $monsterB = new Monster(); $team->addMonster($monsterB); echo "Total HP: " . $team->getTotalHp(); 
Sign up to request clarification or add additional context in comments.

6 Comments

Why do you pass the object by reference? I don't think it matters in objects.
This is a proper answer. You need a collection object that supports Dependency Injection so you can add all kinds of different monsters to the group. Next remove the reference pointer and specify the Monster class as an abstract class to be extended by each type of monster.
This answer requires that any code that creates a monster also have access to the MosterTeam Check out my answer.
I really don't see the point of using loops every time getTotalHp() is called when you can simply keep totalHp in sync at all times by providing the appropriate class methods. Check my answer.
@scaraveos - because the MonsterTeam class doesn't add or remove hp from a monster. It's really stupid to keep a separate variable for holding that value, should you keep all common values in separate variables, you mean?
|
2

First of all, they're called Objects or Instances, as in Object oriented programming?

Second, nothing is stopping you from creating an array (it does not look weird) of monsters:

$monsters = array( new Monster(), new Monster(), new Monster(), new Monster() ); foreach($monsters as $monster) { $totalHP += $monster->hp; } 

About the unique ID, that one should be the responsibility of the MonsterFactory, meaning, a monster should be assigned an ID, it should not assign it to itself, rather the ID should be assigned to it. Something like this when creating a monster:

new Monster(4); //ID is 4 

Who keeps track of it? Someone else, not the Monster.

1 Comment

@Matthieu: Agreed. Corrected.
1

You could create a $monsters array like this:

$monsters = array(); 

Then every time you want to add a monster, you do this:

$monsters[] = new Monster(); 

You could then loop through your array to get total hitpoints like this:

$totalHP = 0; foreach($monsters as $monster) { $totalHP += $monster->getHp(); } 

Where after that loop, $totalHP would contain the total hitpoints of all the monsters in your array.

2 Comments

This is not very flexible, you'd need to update that array from every place in the code that crates or deletes a monster
@JuanMendes Yet certainly more flexible than having a separate variable ($monster_1, $monster_2, etc.) for each monster, no? His question wasn't about flexibility, it wasn't about "how should I rewrite everything?" It was about how to calculate remaining health for a group of monsters. That was it.
1

You can use an object that acts as a container for Monsters and use that to keep track for you:

class MonsterContainer { private $monsters = array(); public function __construct() { // do stuff } public function addMonster(Monster $monster) { $this->monsters[] = $monster; } public function totalMonstersHP() { $hp = 0; foreach ($this->monsters as $monster) { $hp += $monster->getHp() } return $hp; } } $monster_1 = new Monster(); $monster_2 = new Monster(); $monster_3 = new Monster(); $container = new MonsterContainer(); $container->addMonster(monster_1); $container->addMonster(monster_2); $container->addMonster(monster_3); echo $container->totalMonstersHP(); 

4 Comments

This is the way! Maby class Monster extends Enemy
It's violation of incapsulation.
Same. This is a global variable. That's not the way.
I've updated my answer to show an alternative way that you all may find much more appealing.
1

You could create a collection factory; it would be responsible for the creation of new monsters. Fusing two classes together is normally not my thing, but I like the fact that I can make the constructor private this way :)

It uses two static variables (glorified global I suppose), one for the monster id and one for the collection of monsters, using SplObjectStorage.

Methods that need to iterate over all monsters use the each() method, so any logic you want can be defined outside of the class.

class Monster { private $id; public $health; private static $instances; private static $nextid = 1; // private constructor private function __construct($id) { $this->id = $id; $this->health = 5; } public static function create() { if (!self::$instances) { self::$instances = new SplObjectStorage; } $o = new self(self::$nextid++); self::$instances->attach($o); return $o; } public static function remove($o) { if (!self::$instances) { return; } self::$instances->detach($o); } // iterate over all objects public static function each($cb) { if (!self::$instances) { return; } foreach (self::$instances as $o) { $cb($o); } } } 

Small usage example

$m1 = Monster::create(); $m2 = Monster::create(); $total_health = 0; Monster::each(function(Monster $m) use (&$total_health) { $total_health += $m->health; }); echo "Total health: $total_health\n"; 

Comments

0

I second alexn, using an array to store you class instances (yes, that's the correct term) is perfectly fine.

You can then use foreach to calculate the total HP.

Comments

0

Create MonstersCollection class, which will contain methods getNewMonster() and getTotalHP().

pseudo code:

class MonstersCollection { private $Monsters; public function getNewMonster() { $Monster=new Monster(); $this->monsters[]=$Monster; return $Monster; } //and you know how to write method to get total HP :) } 

3 Comments

So, you've made a fancy array?
This is not an elegant way to do this, at the very least, you need to make __construct private so none else can create monsters.
@JuanMendes agree, more correct code in Björn's answer - here is just example to show the idea.
-1

It's quite common for classes to keep track of its instances.

class Monster { private $monsters = array(); public function __construct() { self::monsters[] = $this; } public function __destruct() { array_splice(self::monsters, array_search($this, self::monsters), 1 ); } public static function getTotalHp() { $total = 0; foreach(self::monsters as $monster) { $total += $monster->getHp(); } return $total; } } 

8 Comments

This does a for each loop each time getTotalHp() is called. That is not really what I would call good practice. The __destruct() method looks painfully slow as well having to search and splice an array. Finally your $monsters property is non static but you use it a static context in getTotalHp().
@scaraveos Why is that not good practice? It's not going to take long, and it keeps you from having to update the total value every time the hp is changed. Premature optimization if you ask me. Lost your upvote!
"Its not going to take long" is not an excuse. Practices like this will eventually add up in a real world application and the results will not be good (or fast).
@scaraveos Code for readability first, if performance is a problem, than you find the bottlenecks. I guarantee you you won't find more than 1ms difference when counting 20,000 monsters. Your approach requires maintenance of the static variables in at least four places, mine is just in the constructor/destructor. Here's a good article about premature optimization. geekswithblogs.net/BlackRabbitCoder/archive/2010/06/13/… You owe it to yourself to at least give it a read.
The defintion of premature optimization (called an Anti-Pattern) from Wikipedia: Coding early-on for perceived efficiency, sacrificing good design, maintainability, and sometimes even real-world efficiency
|
-2

Maybe you can create a new class , or function in the same Monster class that is called in the constructor, and each time the class is created it calls a global variable that is increased.

1 Comment

No, don't use globals. Calculating the ID is not the Monster class's responsibility.
-2

You can create the observer pattern that could update the totalHp property in a centralized object automatically on Monster instantiation.

But a faster/easier way would be something like this in your Monster class:

class Monster { ... private $hp = 5; private static $totalHp = 0; ... public function getHp() { return $this->hp; } public function __construct(...) { ... self::totalHp += $this->getHp(); ... } public function __destruct(...) { ... self::totalHp -= $this->getHp(); ... } ... public static function getTotalHp() { return self::totalHp; } /** * Provide negative number to reduce HP or positive to increase HP. */ public function alterHp($hp) { $this->hp += $hp; self::totalHp += $hp; } } 

Then to get the current the totalHp value you would have to do the following in your client code:

$totalHp = Monster::getTotalHp(); 

11 Comments

Same comment as for John Conde's answer
I voted this up but it's brittle, what if getHp() changes? It would be better to track all instances and call getHp() for all of them when getTotalHp is called.
This is a quick code sample. I am not going to implement the whole application here. And ideally getHp() should only be a getter for the $hp property. Nothing else.
Now totalHp is updated only on hp change. This ensures that we have precise totalHp without running loops over and over again.
I don't see how. The totalHp property is maintained properly throughout the program execution. And I am not doing any loops or array searches that would slow the program down.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.