Jump to content

Recommended Posts

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();


?>

 

 

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 

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.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.