SitdGames Posted March 27, 2014 Share Posted March 27, 2014 Hey guys, got a bit of a complicated question for you... I was challenged to build a Finite State Machine based game by a friend with some rudimentary AI in php. After doing a little research, planned it out to have the following classes in this structure -world --food --nest ---ant Where world would be the highest level class, and contain all the instances of different objects + save/load functionality and such. The problem I'm having is acessing items in the higher level class, without either adding the initial declaration of $world as a global variable or just including a reference in the initial instance of each class item. I have to working, but it's nothing what I'd like. for instance: To access and sort through all the posx/posy coords of the ants presently on screen, I have to go: $this->world->checklocation($this->posx , $this->posy) using a reference I passed to it in the constructor(the world item). Is there a easier way to access information from a higher level object other then passing a reference to it from the original? Right now the only reason that the lower level objects know about the highest one at the moment, is I used the global keyword... and I'd rather know the right way of doing this, rather then the duct-taped way I'm using. (I've included the source code... warning though, it's messy and undocumented) <?php /* *Sim Ant: Copywright 2014 Nicholas Fagerlid */ class WORLD{ public $food; public $nest; public $turn; public $worldname; public $foodcounter; public $locationid; public $numofactive; public $turnoutput; public $absturn; const VERSION = '110'; function WORLD(){ $this->foodcounter = 0; $this->locationid[0] = array(0, 0); $numofactive = 0; if(file_exists("saves/" . $_SERVER['REMOTE_ADDR'] . ".csv")){ $this->getfromcsv(); } else { $this->nest['green'] = new NEST("green", 20, 20, $this, 0, true); $this->nest['red'] = new NEST("red", 1, 1, $this, 0, true); $this->nest['orange'] = new NEST("orange", 1, 20, $this, 0, true); $this->nest['blue'] = new NEST("blue", 20, 1, $this, 0, true); $this->food[$this->foodcounter] = new FOOD(rand(1,19), rand(1,19), $this->foodcounter); $this->foodcounter++; $this->food[$this->foodcounter] = new FOOD(rand(1,19), rand(1,19), $this->foodcounter); $this->turn = 0; $this->absturn = 0; } } function checklocation($posx, $posy){ //print_r($this->locationid); $taken = true; foreach($this->locationid as $key => $loc){ if($this->locationid[$key][0] == $posx && $this->locationid[$key][1] == $posy){ $taken = false; } } return $taken; } function savetocsv(){ $numofnests = 0; $output = ""; $nestline = ""; foreach($this->nest as $keya => $nest){ if($this->nest[$keya]->isdead == 0){ $numofants = 0; $antline = ""; foreach($nest->ant as $keyb => $ant){ if($this->nest[$keya]->ant[$keyb]->isdead == 0){ $antline .= "," . $this->nest[$keya]->ant[$keyb]->posx . "," . $this->nest[$keya]->ant[$keyb]->posy . "," . $this->nest[$keya]->ant[$keyb]->brain->update() . "," . $this->nest[$keya]->ant[$keyb]->quality . "," . $this->nest[$keya]->ant[$keyb]->stamina; $numofants++; } } $nestline .= "," . $this->nest[$keya]->type . "," . $this->nest[$keya]->posx . "," . $this->nest[$keya]->posy . "," . $this->nest[$keya]->storedfood . "," . $numofants . $antline; $numofnests++; } } $output = self::VERSION . "," . $this->absturn . "," . $this->turn . "," . $numofnests . $nestline; $numoffood = 0; $foodset = ""; foreach($this->food as $keyc => $food){ if($this->food[$keyc]->used == 0){ $foodset .= "," . $this->food[$keyc]->posx() . "," . $this->food[$keyc]->posy() . "," . $this->food[$keyc]->quality() . "," . $this->food[$keyc]->name(); $numoffood++; } } $output .= "," . $numoffood . $foodset; file_put_contents("saves/" . $_SERVER['REMOTE_ADDR'] . ".csv", $output); } function getfromcsv(){ $this->foodcounter = 0; $name = "saves/" . $_SERVER['REMOTE_ADDR'] . ".csv"; $file = file_get_contents($name); //echo $file; $array = str_getcsv($file, ", "); $reversed = array_reverse($array); $version = array_pop($reversed); if($version == self::VERSION){ $this->absturn = array_pop($reversed); $this->turn = array_pop($reversed); $numofnests = array_pop($reversed); for( $i = 1; $i <= $numofnests; $i++){ $nesttype = array_pop($reversed); $this->nest[$nesttype] = new NEST($nesttype, array_pop($reversed), array_pop($reversed), $this, array_pop($reversed), false); $numofants = array_pop($reversed); for( $j = 1; $j <= $numofants; $j++){ $this->nest[$nesttype]->loadant(array_pop($reversed), array_pop($reversed), array_pop($reversed), array_pop($reversed), array_pop($reversed)); } } $numoffood = array_pop($reversed); for($i = 1; $i <= $numoffood; $i++){ $this->food[$this->foodcounter] = new FOOD(array_pop($reversed), array_pop($reversed), $this->foodcounter, array_pop($reversed), array_pop($reversed)); $this->foodcounter++; } } else { $this->turnoutput = "VERSION NUMBER INCOMPATIBLE!<br>NEW VERSION IS ". self::VERSION . "<br>"; $this->nest['green'] = new NEST("green", 20, 20, $this, 0, true); $this->nest['red'] = new NEST("red", 1, 1, $this, 0, true); $this->nest['orange'] = new NEST("orange", 1, 20, $this, 0, true); $this->nest['blue'] = new NEST("blue", 20, 1, $this, 0, true); $this->food[$this->foodcounter] = new FOOD(rand(1,19), rand(1,19), $this->foodcounter); $this->foodcounter++; $this->food[$this->foodcounter] = new FOOD(rand(1,19), rand(1,19), $this->foodcounter); $this->turn = 0; $this->absturn = 0; } } function runturn(){ if(count($this->nest) >= 1){ $this->absturn++; foreach($this->nest as $key => $nest){ $this->nest[$key]->checkfood(); if(count($this->nest[$key]->ant) < 1){ $this->turnoutput .= "<b>Nest ran out of workers and crumbled at " . $this->nest[$key]->posx . "-" . $this->nest[$key]->posy . "</b><br>"; $this->nest[$key]->isdead = 1; $this->food[$this->foodcounter] = new FOOD($this->nest[$key]->posx, $this->nest[$key]->posy, $this->foodcounter, 10, "Crumbled<br>Nest"); $this->foodcounter++; $this->food[$this->foodcounter] = new FOOD($this->nest[$key]->posx, $this->nest[$key]->posy, $this->foodcounter, 10, "Crumbled<br>Nest"); $this->foodcounter++; } } if($this->turn >= 4 && $this->foodcounter < 25){ $this->turn = 0; $this->food[$this->foodcounter] = new FOOD(rand(1,19), rand(1,19), $this->foodcounter); $this->foodcounter++; } else { $this->turn++; } if(count($this->food) <= 1){ $this->food[$this->foodcounter] = new FOOD(rand(1,19), rand(1,19), $this->foodcounter); $this->foodcounter++; } $objectlocation = array(); foreach($this->nest as $keya => $nest){ if($this->nest[$keya]->ant[0] != null){ //echo count($this->nest) . "<br>"; foreach($nest->ant as $keyb => $ant){ $this->nest[$keya]->ant[$keyb]->update(); array_unshift($objectlocation, "<td width='50' height='50' style='background-color:" . $this->nest[$keya]->ant[$keyb]->type . ";'><b style='color:white;'>ant<b/></td>", $this->nest[$keya]->ant[$keyb]->posx, $this->nest[$keya]->ant[$keyb]->posy); } } array_unshift($objectlocation, "<td width='50' height='50'><b><i>nest</i></b></td>", $this->nest[$keya]->posx, $this->nest[$keya]->posy); } foreach($this->food as $keyc => $food){ array_unshift($objectlocation, "<td width='50' height='50' style='background-color:pink;'><i>" . $this->food[$keyc]->name() . "</i></td>", $this->food[$keyc]->posx, $this->food[$keyc]->posy); } for( $i = 0; $i <= 19; $i++){ for( $j = 0; $j <= 19; $j++){ $playfield[$i][$j] = "<td width='50' height='50'>grass</td>"; } } for( $i = 0; $i < count($objectlocation); $i){ $playfield[$objectlocation[$i + 1] - 1][$objectlocation[$i + 2] - 1] = $objectlocation[$i]; $i += 3; } $output = "<table border='1' style='text-align:center;'>"; for($i = 0; $i <= 19; $i++){ $output .= '<tr>'; for($j = 0; $j <= 19; $j++){ $output .= "" . $playfield[$i][$j] . ''; } $output .= '</tr>'; } $output .= '</table>'; echo $output . "," . "Turn " . $this->absturn . ":<br>" . $this->turnoutput; $this->savetocsv(); } else { echo "Everyone died...<br>It's game over man! Game over!<br> The world lasted " . $this->absturn . " turns."; } } /**************************************************************************************************** *Credit to Jay Williams * @link http://gist.github.com/385876 ****************************************************************************************************/ function csv_to_array($filename='', $delimiter=',') { if(!file_exists($filename) || !is_readable($filename)) return FALSE; $header = NULL; $data = array(); if (($handle = fopen($filename, 'r')) !== FALSE) { while (($row = fgetcsv($handle, 1000, $delimiter)) !== FALSE) { if(!$header) $header = $row; else $data[] = array_combine($header, $row); } fclose($handle); } return $data; } } class FOOD{ public $posx; public $posy; public $quality; public $id; public $used; public $name; function FOOD($posix, $posiy, $id, $quality = 0, $name = "seed"){ $this->posx = $posix; $this->posy = $posiy; $this->id = $id; $this->used = 0; $this->name = $name; if($quality == 0){ $this->quality = rand(1,3); } else { $this->quality = $quality; } } public function posx(){ return $this->posx; } public function posy(){ return $this->posy; } public function quality(){ return $this->quality; } public function id(){ return $this->id; } public function used(){ return $this->used; } public function name(){ return $this->name; } public function editused($edit){ $this->used = $edit; } } class NEST{ public $ant; public $posx; public $posy; public $type; public $storedfood; public $isdead; public $world; function NEST($type, $posx, $posy, world $world, $storedfood = 0, $genant = false){ $this->type = $type; $this->posx = $posx; $this->posy = $posy; $this->isdead = 0; $this->storedfood = $storedfood; if($genant == true){ $this->newant($posx, $posy); } $this->world = &$world; } public function appendtoturn($text){ $this->world->turnoutput .= $text . "<br>"; } function loadant($posx, $posy, $brain, $quality, $stamina){ $this->ant[] = new ANT($posx, $posy, $this->type, $this->world, $brain, $quality, $stamina); } function newant($posx, $posy){ $this->ant[] = new ANT($posx, $posy, $this->type, $this->world); } function checkfood(){ if($this->storedfood >= 10){ $this->newant($this->posx - rand(0, 2), $this->posy - rand(0, 2)); $this->storedfood= 0; } } } class FSM{ public $activestate; function FSM(){ } function setstate($state){ $this->activestate = $state; } function update(){ if ($this->activestate != null) { $action = $this->activestate; return $action; } } } class ANT{ public $posx; public $posy; public $brain; public $hasfood; public $quality; public $home; public $type; public $world; public $stamina; public $isdead; public $locationid; function ANT($posix, $posiy, $type, $worl, $brain = 'stunned', $quality = 0, $stamina = 50){ global $world; $this->world = &$world; $this->posx = $posix; $this->posy = $posiy; $this->type = $type; $this->quality = $quality; $this->home = &$this->world->nest[$type]; $this->brain = new FSM(); $this->brain->setstate($brain); $this->stamina = $stamina; $locid = $this->islocid(); $this->locationid = $locid; $this->isdead = 0; if($brain == 'stunned'){ $this->appendtoturn("<b>A new " . $this->type . " ant has been born!</b>"); } $world->locationid[$this->locationid] = array($posix, $posiy); } public function appendtoturn($text){ $this->world->turnoutput .= $text . "<br>"; } public function islocid(){ $active = &$this->world->numofactive; $active++; return $active; } function booststamina(){ if($this->stamina < 200){ $this->stamina = $this->stamina + 10; } } function drainstamina(){ $this->stamina--; if($this->stamina <= 0){ if($this->quality >= 1){ $this->brain->setstate("eatfood"); return false; } else { $this->appendtoturn("<b>" . $this->type . " ant died at " . $this->posx . "-" . $this->posy . "</b>"); $this->isdead = 1; $this->world->food[$this->world->foodcounter] = new FOOD($this->posx, $this->posy, $this->world->foodcounter, 0.5, "dead<br>ant"); return false; } } else { return true; } } function eatfood(){//consume up to 5 food to restore stamina if($this->quality > 5){ $this->quality = $this->quality - 5; $this->stamina = $this->stamina + 15; $this->brain->setstate("gohome"); $this->appendtoturn("<b>" . $this->type . "</b>(" . $this->stamina . ") ant ate a part of it's food!"); } else { $this->stamina = $this->quality * 3; $this->quality = 0; $this->hasfood = false; $this->brain->setstate("findleaf"); $this->appendtoturn("<b>" . $this->type . "</b>(" . $this->stamina . ") ant ate all the food it was holding!"); } $this->appendtoturn("<b>" . $this->type . "</b>(" . $this->stamina . ") ant looks healthier!"); } //Findleaf - paths to the next availible food object. function findleaf(){ if($this->hasfood == false && $this->drainstamina()){ $foods = array(); foreach($this->world->food as $key => $food){ $foods[$key][] = $this->world->food[$key]->posx(); $foods[$key][] = $this->world->food[$key]->posy(); $foods[$key][] = $this->world->food[$key]->quality(); $foods[$key][] = $key; $foods[$key][] = $this->world->food[$key]->name(); } $tilesaway = 100; $order = array(); foreach($foods as $key => $item){ $xtest = abs($item[0] - $this->posx); $ytest = abs($item[1] - $this->posy); $testvalue = floor(($xtest + $ytest) / 2); if( $testvalue < $tilesaway ){ $tilesaway = $testvalue; array_unshift($order, $item[0], $item[1], $item[2], $item[3], $item[4]); } } //print_r($order); if($order[0] < $this->posx && $order[1] > $this->posy){ if($this->world->checklocation($this->posx + 1, $this->posy - 1)){ $this->posy++; $this->posx--; } elseif($this->world->checklocation($this->posx - 1, $this->posy)){ $this->posx++; } elseif($this->world->checklocation($this->posx, $this->posy + 1)){ $this->posy--; } } elseif($order[0] < $this->posx && $order[1] < $this->posy){ if($this->world->checklocation($this->posx + 1, $this->posy + 1)){ $this->posy--; $this->posx--; } elseif($this->world->checklocation($this->posx + 1, $this->posy)){ $this->posx--; } elseif($this->world->checklocation($this->posx, $this->posy + 1)){ $this->posy--; } } elseif($order[0] > $this->posx && $order[1] > $this->posy){ if($this->world->checklocation($this->posx - 1, $this->posy - 1)){ $this->posy++; $this->posx++; } elseif($this->world->checklocation($this->posx - 1, $this->posy)){ $this->posx++; } elseif($this->world->checklocation($this->posx, $this->posy - 1)){ $this->posy++; } } elseif($order[0] > $this->posx && $order[1] < $this->posy){ if($this->world->checklocation($this->posx - 1, $this->posy + 1)){ $this->posy--; $this->posx++; } elseif($this->world->checklocation($this->posx - 1, $this->posy)){ $this->posx++; } elseif($this->world->checklocation($this->posx, $this->posy - 1)){ $this->posy--; } }elseif($order[0] > $this->posx){ if($this->world->checklocation($this->posx + 1, $this->posy)){ $this->posx++; } } elseif($order[0] < $this->posx){ if($this->world->checklocation($this->posx - 1, $this->posy)){ $this->posx--; } } elseif($order[1] > $this->posy){ if($this->world->checklocation($this->posx, $this->posy + 1)){ $this->posy++; } } elseif($order[1] < $this->posy){ if($this->world->checklocation($this->posx, $this->posy - 1)){ $this->posy--; } } $this->appendtoturn("<b>" . $this->type . "</b>(" . $this->stamina . ") ant moved to " . $order[4] . " at " . $order[0] . "-" . $order[1]); if($order[0] == $this->posx && $order[1] == $this->posy && $this->world->food[$order[3]]->used == 0){ $this->booststamina(); $this->hasfood = true; $this->quality = $order[2]; //echo "<br>Order 2:" . $order[2] . "<br>Quality:" . $this->quality . "<br>"; $this->world->food[($order[3])]->editused(1); $this->brain->setstate("gohome"); $this->appendtoturn("<b>" . $this->type . "</b>(" . $this->stamina . ") ant picked up " . $order[4] . " at " . $order[0] . "-" . $order[1]); } } else { $this->brain->setstate("gohome"); } } function stunned(){ $this->brain->setstate("findleaf"); } //gohome - Paths back to the ant lair function gohome(){ $this->drainstamina(); if($this->isdead == 0){ $this->appendtoturn("<b>" . $this->type . "</b>(" . $this->stamina . ") ant moved towards home at " . $this->world->nest[$this->type]->posx . "-" . $this->world->nest[$this->type]->posy); if($this->world->nest[$this->type]->posx < $this->posx && $this->world->nest[$this->type]->posy > $this->posy){ if($this->world->checklocation($this->posx + 1, $this->posy - 1)){ $this->posy++; $this->posx--; } elseif($this->world->checklocation($this->posx - 1, $this->posy)){ $this->posx++; } elseif($this->world->checklocation($this->posx, $this->posy + 1)){ $this->posy--; } } elseif($this->world->nest[$this->type]->posx < $this->posx && $this->world->nest[$this->type]->posy < $this->posy){ if($this->world->checklocation($this->posx + 1, $this->posy + 1)){ $this->posy--; $this->posx--; } elseif($this->world->checklocation($this->posx + 1, $this->posy)){ $this->posx--; } elseif($this->world->checklocation($this->posx, $this->posy + 1)){ $this->posy--; } } elseif($this->world->nest[$this->type]->posx > $this->posx && $this->world->nest[$this->type]->posy > $this->posy){ if($this->world->checklocation($this->posx - 1, $this->posy - 1)){ $this->posy++; $this->posx++; } elseif($this->world->checklocation($this->posx - 1, $this->posy)){ $this->posx++; } elseif($this->world->checklocation($this->posx, $this->posy - 1)){ $this->posy++; } } elseif($this->world->nest[$this->type]->posx > $this->posx && $this->world->nest[$this->type]->posy < $this->posy){ if($this->world->checklocation($this->posx - 1, $this->posy + 1)){ $this->posy--; $this->posx++; } elseif($this->world->checklocation($this->posx - 1, $this->posy)){ $this->posx++; } elseif($this->world->checklocation($this->posx, $this->posy - 1)){ $this->posy--; } } elseif($this->world->nest[$this->type]->posx > $this->posx){ if($this->world->checklocation($this->posx + 1, $this->posy)){ $this->posx++; } } elseif($this->world->nest[$this->type]->posx < $this->posx){ if($this->world->checklocation($this->posx - 1, $this->posy)){ $this->posx--; } } elseif($this->world->nest[$this->type]->posy > $this->posy){ if($this->world->checklocation($this->posx, $this->posy + 1)){ $this->posy++; } } elseif($this->world->nest[$this->type]->posy < $this->posy){ if($this->world->checklocation($this->posx, $this->posy-1)){ $this->posy--; } } if($this->world->nest[$this->type]->posy == $this->posy && $this->world->nest[$this->type]->posx == $this->posx){ $this->appendtoturn("<b>" . $this->type . "</b>(" . $this->stamina . ") ant arrived at home at " . $this->world->nest[$this->type]->posx . "-" . $this->world->nest[$this->type]->posy); $this->booststamina(); $this->hasfood = false; $this->world->nest[$this->type]->storedfood = $this->world->nest[$this->type]->storedfood + $this->quality; //echo "<br>Quality:" . $this->world->nest[$this->type]->storedfood + $this->quality . "<br>"; $this->quality = 0; $this->brain->setstate("findleaf"); } } } //runaway - Paths away from tile function runaway(){ $this->drainstamina(); } function update(){ //Updates the FSM, running the function stored in the brain. Either findleaf, gohome or runaway. $action = $this->brain->update(); $this->{$action}(); $this->world->locationid[$this->locationid] = array($this->posx, $this->posy); } } $world = new WORLD(); $world->runturn(); ?> Quote Link to comment Share on other sites More sharing options...
gristoi Posted March 27, 2014 Share Posted March 27, 2014 i am not going to look through your entire code, but a few warning flags are a. you are contemplating using globals and b. you are not using __construct(). Looking though your setup you are declaring new instances of nest. i would advise you look at 2 things - Dependency Injection and the IoC container Quote Link to comment Share on other sites More sharing options...
SitdGames Posted March 27, 2014 Author Share Posted March 27, 2014 Almighty, thanks for the info! I planned the code so that all of the AI and the individual ants were tied to the specific named nest, so that all I'd have to do to simulate it was iterate through each of the nests. I'm going to keep tinkering with it in my free time, see if I can optimize if further, but from the looks of It I may need to do some extensive rewriting. I have removed all the global declarations and replaced the overloaded functions with __construct. Is there a distinct advantage to using __construct over a overloaded function call, or is it more of a coding standard? I've looked over several articles on both Dependency Injection and IoC contaners, and I've plotted something I'm going to try out a bit latter, that hopefully will clean up some of the bigger problems I'm facing. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.