Created
August 25, 2022 14:40
-
-
Save carbontwelve/26f68755dcb035efc8990d3650ede15e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* PHP Space Mines Simulation | |
* Based upon Space Mines from Usborne Computer Space Games | |
* @see https://photogabble.co.uk/noteworthy/php-space-mines-introduction/ | |
*/ | |
function lerp($a, $b, $t): float | |
{ | |
return $a + ($b - $a) * $t; | |
} | |
function minMax($value, $min = 0, $max = 1) | |
{ | |
if ($value < $min) return $min; | |
if ($value > $max) return $max; | |
return $value; | |
} | |
enum BuildingKind: string | |
{ | |
case Mine = 'Mine'; | |
} | |
enum ResourceKind: string | |
{ | |
case Ore = 'Ore'; | |
case Food = 'Food'; | |
} | |
interface ColonyModifier | |
{ | |
public function modify(Colony $colony); | |
} | |
interface HasBuildings | |
{ | |
public function buildings(): array; | |
public function buildingsOfKind(BuildingKind $kind): array; | |
} | |
abstract class Building implements ColonyModifier | |
{ | |
public BuildingKind $kind; | |
public function __construct(BuildingKind $kind) | |
{ | |
$this->kind = $kind; | |
} | |
} | |
class Mine extends Building | |
{ | |
public function __construct() | |
{ | |
parent::__construct(BuildingKind::Mine); | |
} | |
public function modify(Colony $colony) | |
{ | |
$oreProduction = 60 + (60 * $this->productionPopulationModifier(count($colony->buildingsOfKind($this->kind)), $colony->population)); | |
$oreProduction += ($oreProduction * $this->productionSatisfactionModifier($colony->happiness)); | |
$colony->ore += ceil($oreProduction); | |
} | |
private function productionPopulationModifier(int $mines, int $population): float | |
{ | |
$skeletonCrew = 20 * $mines; | |
$workLoadPercentage = $population > 0 | |
? minMax($population / $skeletonCrew) | |
: 0; | |
return round(lerp(-1, 1, $workLoadPercentage), 2); | |
} | |
private function productionSatisfactionModifier(float $satisfaction): float | |
{ | |
if ($satisfaction < 0) return -0.5; | |
if ($satisfaction > 0 && $satisfaction < 10) return 0.15; | |
return 0.25; | |
} | |
} | |
class Colony implements HasBuildings | |
{ | |
public int $ore; | |
public int $oreSold; | |
public int $meals; | |
public int $population; | |
public float $happiness; | |
public int $money; | |
public int $turn; | |
private array $buildings; | |
public function __construct(int $ore = 0, int $meals = 1500, int $population = 35, float $happiness = 0.0, int $money = 0, int $turn = 1, array $buildings = null) | |
{ | |
$this->ore = $ore; | |
$this->meals = $meals; | |
$this->population = $population; | |
$this->happiness = $happiness; | |
$this->money = $money; | |
$this->turn = $turn; | |
$this->oreSold = 0; | |
if (is_null($buildings)) { | |
$this->buildings = [ | |
new Mine(), | |
new Mine(), | |
new Mine(), | |
]; | |
} else { | |
$this->buildings = $buildings; | |
} | |
} | |
public function buildings(): array | |
{ | |
return $this->buildings; | |
} | |
public function buildingsOfKind(BuildingKind $kind): array | |
{ | |
return array_filter($this->buildings, function (Building $item) use ($kind) { | |
return $item->kind === $kind; | |
}); | |
} | |
public function score(): int | |
{ | |
$mod = $this->happiness < 0 | |
? -0.2 | |
: 0.2; | |
$score = (($this->population * 1000) + ($this->oreSold / 2) + ($this->money * 2)); | |
return floor($score + ($score - ($score * $mod))); | |
} | |
public function status(): void | |
{ | |
echo "TURN $this->turn" . PHP_EOL; | |
echo "There are $this->population people in the colony" . PHP_EOL; | |
echo "You have " . count($this->buildingsOfKind(BuildingKind::Mine)) . " mines and $this->money money" . PHP_EOL; | |
echo "Ore in store: $this->ore, total ore sold $this->oreSold" . PHP_EOL; | |
echo "Meals in store: $this->meals" . PHP_EOL; | |
echo "Satisfaction: $this->happiness" . PHP_EOL; | |
} | |
} | |
class SatisfactionModifier implements ColonyModifier | |
{ | |
public function modify(Colony $colony) | |
{ | |
$colony->happiness += ( | |
$this->foodModifier($colony->meals, $colony->population) + | |
$this->mineModifier(count($colony->buildingsOfKind(BuildingKind::Mine)), $colony->population) | |
); | |
$colony->happiness = minMax($colony->happiness, -20, 20); | |
} | |
private function foodModifier(int $foodReserve, int $population): float | |
{ | |
$days = 28; // 4 weeks | |
$mealsPerDay = 3; | |
$mealsRequired = $population * $days * $mealsPerDay; | |
$foodReservePercentage = $foodReserve > 0 | |
? minMax($foodReserve / $mealsRequired) | |
: 0; | |
return round(lerp(-1.5, 1.5, $foodReservePercentage), 2); | |
} | |
private function mineModifier(int $mines, int $population): float | |
{ | |
// min of 10 people per mine until they get over-worked, 20 is the optimal | |
$skeletonCrew = 20 * $mines; | |
$workLoadPercentage = $population > 0 | |
? minMax($population / $skeletonCrew) | |
: 0; | |
return round(lerp(-1.5, 1.5, $workLoadPercentage), 2); | |
} | |
} | |
class PopulationModifier implements ColonyModifier | |
{ | |
public function modify(Colony $colony) | |
{ | |
// Migration | |
// TODO this should be between 1 and the value found based upon happiness | |
$colony->population += (int)($colony->happiness / 2); | |
if ($colony->population < 0) $colony->population = 0; | |
// Food consumption | |
$colony->meals -= $colony->population * 3; | |
if ($colony->meals < 0) $colony->meals = 0; | |
} | |
} | |
class GatherResources implements ColonyModifier | |
{ | |
public function modify(Colony $colony) | |
{ | |
/** @var Mine $mine */ | |
foreach ($colony->buildingsOfKind(BuildingKind::Mine) as $mine) { | |
$mine->modify($colony); | |
} | |
} | |
} | |
class SellOre implements ColonyModifier { | |
private int $amount; | |
private int $value; | |
public function __construct(int $amount, int $value) { | |
$this->amount = $amount; | |
$this->value = $value; | |
} | |
public function modify(Colony $colony) | |
{ | |
$colony->oreSold += $this->amount; | |
$colony->ore -= $this->amount; | |
$colony->money += $this->amount * $this->value; | |
} | |
} | |
$player = new Colony(); | |
for ($i = 0; $i < 30; $i++) { | |
$player->status(); | |
(new SatisfactionModifier)->modify($player); | |
(new PopulationModifier())->modify($player); | |
(new GatherResources())->modify($player); | |
(new SellOre($player->ore, rand(7,12)))->modify($player); | |
if ($player->happiness == -20) break; | |
$player->turn++; | |
} | |
echo "Player Score: " . $player->score() . PHP_EOL; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Default output is: