summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Weipert <code@drogueronin.de>2023-10-04 11:32:04 +0200
committerDaniel Weipert <code@drogueronin.de>2023-10-04 11:32:04 +0200
commit94a3dd52da3ae180af37c6fd0e8c24b3562da388 (patch)
treeacced055660bfd65ca3e955ea26d412457ba4507
parentfa00b957378a393f8edbfc98ef111d35d18ecb09 (diff)
initial commit 2
-rw-r--r--.env.example3
-rw-r--r--public/assets/style.css104
-rw-r--r--src/Controller/Map.php43
-rw-r--r--src/Controller/Unit.php248
-rw-r--r--src/Controller/Village.php79
-rw-r--r--src/EventRunner.php6
-rw-r--r--src/Model/Building.php6
-rw-r--r--src/Model/Event/SendUnits.php41
-rw-r--r--src/Model/Event/TrainUnits.php2
-rw-r--r--src/Model/Unit.php52
-rw-r--r--src/Model/Unit/Farmer.php3
-rw-r--r--src/Model/Unit/Miner.php3
-rw-r--r--src/Model/Unit/PitWorker.php3
-rw-r--r--src/Model/Unit/WoodCutter.php5
-rw-r--r--src/Model/Village.php17
-rw-r--r--views/base.twig6
-rw-r--r--views/map.twig32
-rw-r--r--views/root.twig2
-rw-r--r--views/village.twig196
19 files changed, 819 insertions, 32 deletions
diff --git a/.env.example b/.env.example
index 0f3508d..072c25f 100644
--- a/.env.example
+++ b/.env.example
@@ -5,5 +5,8 @@ DB_USER=
DB_PASSWORD=
BASE_BUILDING_BUILD_TIME_FACTOR=256
+BASE_BUILDING_RESOURCE_REQUIREMENT_FACTOR=64
BASE_UNIT_BUILD_TIME_FACTOR=256
+BASE_UNIT_TRAVEL_TIME_FACTOR=60
+BASE_UNIT_RESOURCE_REQUIREMENT_FACTOR=64
BASE_RESOURCE_GENERATION_FACTOR=128
diff --git a/public/assets/style.css b/public/assets/style.css
new file mode 100644
index 0000000..8a40e43
--- /dev/null
+++ b/public/assets/style.css
@@ -0,0 +1,104 @@
+body {
+ background-color: #ecf1ef;
+ color: #000;
+}
+
+
+.wrap {
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+
+button, input[type="submit"] {
+ background-color: #1e362e;
+ color: #fff;
+ border: none;
+ padding: 0.25rem 0.75rem;
+}
+
+button:hover:not(:disabled), input[type="submit"]:hover:not(:disabled) {
+ background-color: #000;
+}
+
+button:disabled, input[type="submit"]:disabled {
+ color: #606060;
+}
+
+
+
+/* Icons */
+
+.icon {
+ display: inline-block;
+ width: 1em;
+ height: 1em;
+
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: center;
+}
+
+.icon-wood {
+ background-image: url("/assets/img/icons/1FAB5.svg");
+}
+
+.icon-clay {
+ background-image: url("/assets/img/icons/1F9F1.svg");
+}
+
+.icon-iron {
+ background-image: url("/assets/img/icons/1F944.svg");
+}
+
+.icon-food {
+ background-image: url("/assets/img/icons/1F954.svg");
+}
+
+.icon-per-increment {
+ background-image: url("/assets/img/icons/1F504.svg");
+}
+
+.icon-storage {
+ background-image: url("/assets/img/icons/1F4E5.svg");
+}
+
+
+/* Village */
+
+.resources {
+ display: grid;
+ grid-template-columns: 2fr 2fr 2fr 2fr 1fr;
+}
+
+.resources > div {
+ padding: 0.25rem;
+ border: 1px solid #000;
+
+ display: flex;
+ align-items: center;
+}
+
+.resources > div > *:not(:first-child) {
+ margin-left: 0.25rem;
+}
+
+.resources .icon {
+ font-size: 1.25em;
+}
+
+
+
+/* Map */
+
+.map__villages {
+ --map-range: 1;
+
+ display: grid;
+}
+
+.map__village {
+ border: 1px solid #000;
+ min-width: 1rem;
+ min-height: 1rem;
+}
diff --git a/src/Controller/Map.php b/src/Controller/Map.php
new file mode 100644
index 0000000..59c1e4e
--- /dev/null
+++ b/src/Controller/Map.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Controller;
+
+use App\DB;
+use App\View;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class Map
+{
+ #[Route(path: '/map/{x}/{y}/{range}', defaults: ['range' => 1], methods: ['GET'])]
+ public function train(Request $request): Response
+ {
+ $x = $request->get('x');
+ $y = $request->get('y');
+ $range = $request->get('range');
+
+ $statement = DB::query(
+ 'select * from villages where x>=:x1 and x<=:x2 and y>=:y1 and y<=:y2',
+ [
+ 'x1' => $x - $range,
+ 'x2' => $x + $range,
+ 'y1' => $y - $range,
+ 'y2' => $y + $range,
+ ]
+ );
+ $villages = $statement->fetchAll();
+
+ $map = [];
+ foreach ($villages as $village) {
+ $map[$village['x']][$village['y']] = $village;
+ }
+
+ return new Response(View::render('map.twig', [
+ 'x' => $x,
+ 'y' => $y,
+ 'range' => $range,
+ 'map' => $map,
+ ]));
+ }
+}
diff --git a/src/Controller/Unit.php b/src/Controller/Unit.php
new file mode 100644
index 0000000..602c6e8
--- /dev/null
+++ b/src/Controller/Unit.php
@@ -0,0 +1,248 @@
+<?php
+
+namespace App\Controller;
+
+use App\DB;
+use App\Model\Unit as Model;
+use App\Model\Event;
+use App\Model\Village;
+use App\Router;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class Unit
+{
+ #[Route(path: '/village/{x}/{y}/unit/{type}/create', methods: ['POST'])]
+ public function train(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+
+ /**@var Model $unit*/
+ $unit = new (Model::resolveType($request->get('type')))();
+ $unit->type = $request->get('type');
+ $unit->homeVillageId = $village->id;
+
+ $amount = intval($request->get('amount'));
+
+ if (! Village::canTrain($village, $unit, $amount)) {
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ // resources
+ foreach (Model::getResourceRequirements($unit, $amount) as $resourceType => $resourceValue) {
+ $village->{$resourceType} -= $resourceValue;
+ }
+ $village->updateResources();
+
+ // event
+ $event = new Event();
+ $event->type = 'TrainUnits';
+ $event->time = (new \DateTime())->add(\DateInterval::createFromDateString($unit->getBuildTime($amount) . ' seconds'));
+ $event->payload = json_encode([
+ 'type' => $request->get('type'),
+ 'amount' => $amount,
+ ]);
+
+ DB::query(
+ 'insert into events (type, time, payload, village_id) VALUES (:type, :time, :payload, :id)',
+ ['type' => $event->type, 'time' => $event->time->format('c'), 'payload' => $event->payload, 'id' => $village->id]
+ );
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ #[Route(path: '/village/{x}/{y}/unit/{type}/location/{lx}/{ly}/recall', methods: ['POST'])]
+ public function recall(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+ $location = Village::getByCoordinates($request->get('lx'), $request->get('ly'));
+
+ /**@var Model $unit*/
+ $unit = new (Model::resolveType($request->get('type')))();
+
+ $amount = intval($request->get('amount'));
+ $amountUnits = DB::query(
+ 'select amount from village_units where home_village_id=:home and residence_village_id=:residence and type=:type',
+ ['home' => $village->id, 'residence' => $location->id, 'type' => $request->get('type')]
+ )->fetchColumn();
+
+ if ($amountUnits - $amount > 0) {
+ $statement = DB::query(
+ <<<SQL
+ update village_units set amount=:amount where home_village_id=:home and residence_village_id=:residence and type=:type
+ SQL,
+ ['amount' => $amountUnits - $amount, 'home' => $village->id, 'residence' => $location->id, 'type' => $request->get('type')]
+ );
+ } else if ($amountUnits - $amount === 0) {
+ DB::query(
+ <<<SQL
+ delete from village_units where home_village_id=:home and residence_village_id=:residence and type=:type
+ SQL,
+ ['home' => $village->id, 'residence' => $location->id, 'type' => $request->get('type')]
+ );
+ }
+
+ // event
+ $event = new Event();
+ $event->type = 'SendUnits';
+ $event->time = (new \DateTime())->add(
+ \DateInterval::createFromDateString(
+ Model::getTravelTime($unit, Village::getDistance($village->x, $village->y, $location->x, $location->y))
+ . ' seconds'
+ )
+ );
+ $event->payload = json_encode([
+ 'type' => 'Recall',
+ 'unit' => $request->get('type'),
+ 'amount' => $amount,
+ 'source' => $location->id,
+ 'destination' => $village->id,
+ ]);
+
+ DB::query(
+ 'insert into events (type, time, payload, village_id) VALUES (:type, :time, :payload, :id)',
+ ['type' => $event->type, 'time' => $event->time->format('c'), 'payload' => $event->payload, 'id' => $village->id]
+ );
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ #[Route(path: '/village/{x}/{y}/unit/{type}/location/{lx}/{ly}/send-back', methods: ['POST'])]
+ public function sendBack(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+ $location = Village::getByCoordinates($request->get('lx'), $request->get('ly'));
+
+ /**@var Model $unit*/
+ $unit = new (Model::resolveType($request->get('type')))();
+
+ $amount = intval($request->get('amount'));
+ $amountUnits = DB::query(
+ 'select amount from village_units where home_village_id=:home and residence_village_id=:residence and type=:type',
+ ['home' => $location->id, 'residence' => $village->id, 'type' => $request->get('type')]
+ )->fetchColumn();
+
+ if ($amountUnits - $amount > 0) {
+ $statement = DB::query(
+ <<<SQL
+ update village_units set amount=:amount where home_village_id=:home and residence_village_id=:residence and type=:type
+ SQL,
+ ['amount' => $amountUnits - $amount, 'home' => $location->id, 'residence' => $village->id, 'type' => $request->get('type')]
+ );
+ } else if ($amountUnits - $amount === 0) {
+ DB::query(
+ <<<SQL
+ delete from village_units where home_village_id=:home and residence_village_id=:residence and type=:type
+ SQL,
+ ['home' => $location->id, 'residence' => $village->id, 'type' => $request->get('type')]
+ );
+ }
+
+ // event
+ $event = new Event();
+ $event->type = 'SendUnits';
+ $event->time = (new \DateTime())->add(
+ \DateInterval::createFromDateString(
+ Model::getTravelTime($unit, Village::getDistance($village->x, $village->y, $location->x, $location->y))
+ . ' seconds'
+ )
+ );
+ $event->payload = json_encode([
+ 'type' => 'SendBack',
+ 'unit' => $request->get('type'),
+ 'amount' => $amount,
+ 'source' => $village->id,
+ 'destination' => $location->id,
+ ]);
+
+ DB::query(
+ 'insert into events (type, time, payload, village_id) VALUES (:type, :time, :payload, :id)',
+ ['type' => $event->type, 'time' => $event->time->format('c'), 'payload' => $event->payload, 'id' => $village->id]
+ );
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ #[Route(path: '/village/{x}/{y}/send-units', methods: ['POST'])]
+ public function sendUnits(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+ $destination = Village::get($request->get('village'));
+
+ /**@var Model $unit*/
+ $unit = new (Model::resolveType($request->get('unit')))();
+
+ $amount = intval($request->get('amount'));
+ $amountUnits = DB::query(
+ 'select amount from village_units where home_village_id=:home and residence_village_id=:home and type=:type',
+ ['home' => $village->id, 'type' => $request->get('unit')]
+ )->fetchColumn();
+
+ if ($amountUnits - $amount > 0) {
+ $statement = DB::query(
+ <<<SQL
+ update village_units set amount=:amount where home_village_id=:home and residence_village_id=:home and type=:type
+ SQL,
+ ['amount' => $amountUnits - $amount, 'home' => $village->id, 'type' => $request->get('unit')]
+ );
+ } else if ($amountUnits - $amount === 0) {
+ DB::query(
+ <<<SQL
+ delete from village_units where home_village_id=:home and residence_village_id=:home and type=:type
+ SQL,
+ ['home' => $village->id, 'type' => $request->get('unit')]
+ );
+ }
+
+ // event
+ $event = new Event();
+ $event->type = 'SendUnits';
+ $event->time = (new \DateTime())->add(
+ \DateInterval::createFromDateString(
+ Model::getTravelTime($unit, Village::getDistance($village->x, $village->y, $destination->x, $destination->y))
+ . ' seconds'
+ )
+ );
+ $event->payload = json_encode([
+ 'type' => 'Borrow',
+ 'unit' => $request->get('unit'),
+ 'amount' => $amount,
+ 'source' => $village->id,
+ 'destination' => $destination->id,
+ ]);
+
+ DB::query(
+ 'insert into events (type, time, payload, village_id) VALUES (:type, :time, :payload, :id)',
+ ['type' => $event->type, 'time' => $event->time->format('c'), 'payload' => $event->payload, 'id' => $village->id]
+ );
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+}
diff --git a/src/Controller/Village.php b/src/Controller/Village.php
index 3854d29..dfca298 100644
--- a/src/Controller/Village.php
+++ b/src/Controller/Village.php
@@ -3,9 +3,13 @@
namespace App\Controller;
use App\DB;
+use App\Model\Event\SendUnits;
+use App\Model\Event\TrainUnits;
use App\Model\Event\UpgradeBuilding;
use App\Model\Village as Model;
+use App\Router;
use App\View;
+use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
@@ -26,8 +30,9 @@ class Village
public function show(Request $request): Response
{
$village = Model::getByCoordinates($request->get('x'), $request->get('y'));
+ $events = [];
- $results = DB::query(
+ $eventsBuilding = DB::query(
<<<SQL
select events.*, village_buildings.type as building from events
join village_buildings
@@ -36,8 +41,7 @@ class Village
SQL, ['id' => $village->id, 'type' => 'UpgradeBuilding']
)->fetchAll();
- $events = [];
- foreach ($results as $row) {
+ foreach ($eventsBuilding as $row) {
$events[$row['type']][] = [
'event' => DB::convertToModel(UpgradeBuilding::class, $row),
'data' => [
@@ -46,9 +50,78 @@ class Village
];
}
+ $eventsUnits = DB::query(
+ <<<SQL
+ select * from events
+ where type=:type and village_id=:id
+ SQL, ['type' => 'TrainUnits', 'id' => $village->id]
+ )->fetchAll();
+
+ foreach ($eventsUnits as $row) {
+ $events[$row['type']][] = [
+ 'event' => DB::convertToModel(TrainUnits::class, $row),
+ 'data' => json_decode($row['payload'], true),
+ ];
+ }
+
+ $eventsUnitsSend = DB::query(
+ <<<SQL
+ select * from events
+ where type=:type and (village_id=:id or (payload->>'destination')::bigint=:id)
+ SQL, ['type' => 'SendUnits', 'id' => $village->id]
+ )->fetchAll();
+
+ foreach ($eventsUnitsSend as $row) {
+ $events[$row['type']][] = [
+ 'event' => DB::convertToModel(SendUnits::class, $row),
+ 'data' => json_decode($row['payload'], true),
+ ];
+ }
+
return new Response(View::render('village.twig', [
'village' => $village,
'events' => $events,
+ 'villages' => DB::fetch(Model::class, "select * from villages where id!=:id", ['id' => $village->id]),
]));
}
+
+ #[Route(path: '/village/{x}/{y}/storage/config', methods: ['POST'])]
+ public function storageConfig(Request $request): Response
+ {
+ $village = Model::getByCoordinates($request->get('x'), $request->get('y'));
+
+ // calculate to max 100%
+ $wood = intval($request->get('wood'));
+ $clay = intval($request->get('clay'));
+ $iron = intval($request->get('iron'));
+ $food = intval($request->get('food'));
+ $total = $wood + $clay + $iron + $food;
+ $woodPercent = $wood / $total;
+ $clayPercent = $clay / $total;
+ $ironPercent = $iron / $total;
+ $foodPercent = $food / $total;
+
+ $wood = round($woodPercent * 100);
+ $clay = round($clayPercent * 100);
+ $iron = round($ironPercent * 100);
+ $food = round($foodPercent * 100);
+ $newTotal = $wood+$clay+$iron+$food;
+ $food += (100 - $newTotal);
+
+ DB::query(
+ <<<SQL
+ update village_storage_config
+ set wood=:wood, clay=:clay, iron=:iron, food=:food
+ where village_id=:id
+ SQL,
+ ['wood' => $wood, 'clay' => $clay, 'iron' => $iron, 'food' => $food, 'id' => $village->id]
+ );
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
}
diff --git a/src/EventRunner.php b/src/EventRunner.php
index d2f1589..4f3bce9 100644
--- a/src/EventRunner.php
+++ b/src/EventRunner.php
@@ -16,6 +16,8 @@ class EventRunner
{
public function __construct()
{
+ // Events
+
$results = DB::query('select * from events where time < now()')->fetchAll();
foreach ($results as $row) {
@@ -79,8 +81,8 @@ class EventRunner
}
DB::query('delete from system where key=:key', ['key' => 'last_resource_tick']);
- $value = (new \DateTime((new \DateTime())->format('Y-m-d H:i')))->format('c');
- DB::query('insert into system (key,value) VALUES (:key,:value)', ['key' => 'last_resource_tick', 'value' => json_encode($value)]);
+ $lastResourceTickMinute = (new \DateTime((new \DateTime())->format('Y-m-d H:i')))->format('c');
+ DB::query('insert into system (key,value) VALUES (:key,:value)', ['key' => 'last_resource_tick', 'value' => json_encode($lastResourceTickMinute)]);
}
}
}
diff --git a/src/Model/Building.php b/src/Model/Building.php
index eb166f9..78d1aa5 100644
--- a/src/Model/Building.php
+++ b/src/Model/Building.php
@@ -69,7 +69,7 @@ class Building
*/
public function getResourceRequirements(): array
{
- return $this->getResourceRequirementsForLevel($this->level);
+ return $this->getResourceRequirementsForLevel($this->level + 1);
}
/**
@@ -77,10 +77,8 @@ class Building
*/
public function getResourceRequirementsForLevel(int $level): array
{
- $level += 1;
-
return array_map(
- fn ($resourceRequirement) => ceil(log($level * 2) * $resourceRequirement * 64 * $level),
+ fn ($resourceRequirement) => ceil(log($level * 2) * $resourceRequirement * $_ENV['BASE_BUILDING_RESOURCE_REQUIREMENT_FACTOR'] * $level),
$this->resourceRequirements
);
}
diff --git a/src/Model/Event/SendUnits.php b/src/Model/Event/SendUnits.php
new file mode 100644
index 0000000..f104a08
--- /dev/null
+++ b/src/Model/Event/SendUnits.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Model\Event;
+
+use App\DB;
+use App\Model\Event;
+
+class SendUnits extends Event
+{
+ /**
+ * @return void
+ */
+ public function __invoke(): void
+ {
+ $payload = json_decode($this->payload, true);
+
+ if ($payload['type'] === 'Recall' || $payload['type'] === 'SendBack') {
+ DB::query(
+ <<<SQL
+ insert into village_units (amount, type, is_traveling, home_village_id, residence_village_id)
+ values (:amount, :type, false, :id, :id)
+ on conflict (type, home_village_id, residence_village_id)
+ do update set amount = village_units.amount+:amount
+ SQL,
+ ['amount' => $payload['amount'], 'type' => $payload['unit'], 'id' => $payload['destination']]
+ );
+ }
+
+ else if ($payload['type'] === 'Borrow') {
+ DB::query(
+ <<<SQL
+ insert into village_units (amount, type, is_traveling, home_village_id, residence_village_id, created_at, updated_at)
+ values (:amount, :type, false, :home, :residence, now(), now())
+ on conflict (type, home_village_id, residence_village_id)
+ do update set amount = village_units.amount+:amount
+ SQL,
+ ['amount' => $payload['amount'], 'type' => $payload['unit'], 'home' => $this->villageId, 'residence' => $payload['destination']]
+ );
+ }
+ }
+}
diff --git a/src/Model/Event/TrainUnits.php b/src/Model/Event/TrainUnits.php
index 0c7e0de..0090d0f 100644
--- a/src/Model/Event/TrainUnits.php
+++ b/src/Model/Event/TrainUnits.php
@@ -19,7 +19,7 @@ class TrainUnits extends Event
insert into village_units (amount, type, is_traveling, home_village_id, residence_village_id)
values (:amount, :type, false, :id, :id)
on conflict (type, home_village_id, residence_village_id)
- do update set amount = excluded.amount+:amount
+ do update set amount = village_units.amount+:amount
SQL,
['amount' => $payload['amount'], 'type' => $payload['type'], 'id' => $this->villageId]
);
diff --git a/src/Model/Unit.php b/src/Model/Unit.php
index a0d1a35..621651c 100644
--- a/src/Model/Unit.php
+++ b/src/Model/Unit.php
@@ -30,6 +30,48 @@ class Unit
return intval(($_ENV['BASE_UNIT_BUILD_TIME_FACTOR'] / ($this->getBuilding()->level ?: 1)) * $amount);
}
+ public static function getTravelTime(Unit $unit, int $distance): int
+ {
+ return self::getTravelTimePerCell($unit) * $distance;
+ }
+
+ public static function getTravelTimePerCell(Unit $unit): int
+ {
+ return intval(ceil($unit->travelTime * $_ENV['BASE_UNIT_TRAVEL_TIME_FACTOR']));
+ }
+
+
+ /**
+ * @return array<string, int>
+ */
+ public static function getResourceRequirements(Unit $unit, int $amount): array
+ {
+ /**@var Building $building*/
+ $building = DB::fetch(
+ Building::resolveType($unit->buildingType),
+ 'select level from village_buildings where type=:type and village_id=:id',
+ ['type' => $unit->buildingType, 'id' => $unit->homeVillageId]
+ )[0] ?? null;
+
+ $currentAmount = DB::query(
+ 'select sum(amount) from village_units where type=:type and home_village_id=:id',
+ ['type' => $unit->type, 'id' => $unit->homeVillageId]
+ )->fetchColumn();
+
+
+ return array_map(
+ function ($resourceRequirement) use ($amount, $currentAmount, $building) {
+ $r = 0;
+ for ($i = 0; $i <= $amount; $i++) {
+ $r += ceil((pow($_ENV['BASE_UNIT_RESOURCE_REQUIREMENT_BASE'], $currentAmount + 1) * $resourceRequirement * $_ENV['BASE_UNIT_RESOURCE_REQUIREMENT_FACTOR']) / ($building->level ?? 1));
+ }
+
+ return $r;
+ },
+ $unit->resourceRequirements
+ );
+ }
+
public function getPopulationDemand(): int
{
return $this->getPopulationDemandForAmount($this->amount);
@@ -43,6 +85,16 @@ class Unit
/* Relations */
+ public function getHomeVillage(): Village
+ {
+ return DB::fetch(Village::class, 'select * from villages where id=:id', ['id' => $this->homeVillageId])[0];
+ }
+
+ public function getResidenceVillage(): Village
+ {
+ return DB::fetch(Village::class, 'select * from villages where id=:id', ['id' => $this->residenceVillageId])[0];
+ }
+
public function getBuilding(): ?Building
{
return Village::getBuilding($this->homeVillageId, $this->buildingType);
diff --git a/src/Model/Unit/Farmer.php b/src/Model/Unit/Farmer.php
index de37802..8e3c1ea 100644
--- a/src/Model/Unit/Farmer.php
+++ b/src/Model/Unit/Farmer.php
@@ -11,5 +11,8 @@ class Farmer extends Unit
public int $populationDemandFactor = 1;
public array $resourceRequirements = [
'wood' => 1.0,
+ 'clay' => 1.0,
+ 'iron' => 1.0,
+ 'food' => 0,
];
}
diff --git a/src/Model/Unit/Miner.php b/src/Model/Unit/Miner.php
index ae6c00a..2d246ef 100644
--- a/src/Model/Unit/Miner.php
+++ b/src/Model/Unit/Miner.php
@@ -11,5 +11,8 @@ class Miner extends Unit
public int $populationDemandFactor = 1;
public array $resourceRequirements = [
'wood' => 1.0,
+ 'clay' => 1.0,
+ 'iron' => 2.0,
+ 'food' => 2.0,
];
}
diff --git a/src/Model/Unit/PitWorker.php b/src/Model/Unit/PitWorker.php
index 4f873b4..d2a52c3 100644
--- a/src/Model/Unit/PitWorker.php
+++ b/src/Model/Unit/PitWorker.php
@@ -11,5 +11,8 @@ class PitWorker extends Unit
public int $populationDemandFactor = 1;
public array $resourceRequirements = [
'wood' => 1.0,
+ 'clay' => 2.0,
+ 'iron' => 1.0,
+ 'food' => 2.0,
];
}
diff --git a/src/Model/Unit/WoodCutter.php b/src/Model/Unit/WoodCutter.php
index 17923ca..d8c2c42 100644
--- a/src/Model/Unit/WoodCutter.php
+++ b/src/Model/Unit/WoodCutter.php
@@ -10,6 +10,9 @@ class WoodCutter extends Unit
public int $travelTime = 1;
public int $populationDemandFactor = 1;
public array $resourceRequirements = [
- 'wood' => 1.0,
+ 'wood' => 2.0,
+ 'clay' => 1.0,
+ 'iron' => 1.0,
+ 'food' => 2.0,
];
}
diff --git a/src/Model/Village.php b/src/Model/Village.php
index cd1c749..b1ab19e 100644
--- a/src/Model/Village.php
+++ b/src/Model/Village.php
@@ -41,6 +41,18 @@ class Village
return true;
}
+ public static function canTrain(Village $village, Unit $unit, int $amount): bool
+ {
+ $resourceRequirements = Unit::getResourceRequirements($unit, $amount);
+ foreach ($resourceRequirements as $resourceType => $requirement) {
+ if ($village->$resourceType < $requirement) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/* DB - Actions */
public static function get(int $id): ?Village
@@ -61,6 +73,11 @@ class Village
);
}
+ public static function getDistance(int $x, int $y, int $dx, int $dy): int
+ {
+ return abs($x - $dx) + abs($y - $dy);
+ }
+
/* DB - Relations */
public static function getBuildings(int $villageId): array
diff --git a/views/base.twig b/views/base.twig
index 94e5037..0063e31 100644
--- a/views/base.twig
+++ b/views/base.twig
@@ -2,7 +2,11 @@
{% block body %}
<div class="wrap">
- <header></header>
+ <header>
+ <nav>
+ <a href="/villages">Overview</a>
+ </nav>
+ </header>
<main>
{% block main %}{% endblock %}
diff --git a/views/map.twig b/views/map.twig
new file mode 100644
index 0000000..43f1a5b
--- /dev/null
+++ b/views/map.twig
@@ -0,0 +1,32 @@
+{% extends 'base.twig' %}
+
+{% block main %}
+<div class="map">
+ <div class="map__up">
+ <a href="/map/{{ x }}/{{ y - 1 }}">Up</a>
+ </div>
+ <div>
+ <div class="map__left">
+ <a href="/map/{{ x - 1 }}/{{ y }}">Left</a>
+ </div>
+ <div class="map__villages" style="grid-template-columns: repeat({{ range*2+1 }}, 1fr); grid-template-rows: repeat({{ range*2+1 }}, 1fr);">
+ {% for row in range(-range, range) %}
+ {% for column in range(-range, range) %}
+ {% set village = map[x + column][y + row] %}
+ <div class="map__village">
+ <a href="/village/{{ village.x }}/{{ village.y }}">
+ {{ map[x + column][y + row].name }}
+ </a>
+ </div>
+ {% endfor %}
+ {% endfor %}
+ </div>
+ <div class="map__right">
+ <a href="/map/{{ x + 1 }}/{{ y }}">Right</a>
+ </div>
+ </div>
+ <div class="map__down">
+ <a href="/map/{{ x }}/{{ y + 1 }}">Down</a>
+ </div>
+</div>
+{% endblock %}
diff --git a/views/root.twig b/views/root.twig
index 11dc665..35399c0 100644
--- a/views/root.twig
+++ b/views/root.twig
@@ -3,6 +3,8 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <link rel="stylesheet" href="/assets/style.css">
</head>
<body>
{% block body %}{% endblock %}
diff --git a/views/village.twig b/views/village.twig
index 7bb55b2..ea6635c 100644
--- a/views/village.twig
+++ b/views/village.twig
@@ -4,15 +4,69 @@
<div class="village">
<div class="village__top">
- <div><span>{{ village.x }} x {{ village.y }}</span> &mdash; {{ village.name }}</div>
+ <div>
+ <span>
+ <a href="/map/{{ village.x }}/{{ village.y }}">
+ {{ village.x }} x {{ village.y }}
+ </a>
+ </span>
+ &mdash;
+ {{ village.name }}
+ </div>
<div class="resources">
- <span>wood: <b>{{ village.wood }}</b> / {{ village.getStorage(village.id).getResourceCapacity('wood') }} &ndash; {{ village.getBuilding(village.id, 'WoodCutter').getResourceIncrementor() }}</span>
- <span>clay: <b>{{ village.clay }}</b> / {{ village.getStorage(village.id).getResourceCapacity('clay') }} &ndash; {{ village.getBuilding(village.id, 'ClayPit').getResourceIncrementor() }}</span>
- <span>iron: <b>{{ village.iron }}</b> / {{ village.getStorage(village.id).getResourceCapacity('iron') }} &ndash; {{ village.getBuilding(village.id, 'IronMine').getResourceIncrementor() }}</span>
- <span>food: <b>{{ village.food }}</b> / {{ village.getStorage(village.id).getResourceCapacity('food') }} &ndash; {{ village.getBuilding(village.id, 'Farm').getResourceIncrementor() }}</span>
+ <div>
+ <i class="icon icon-wood"></i>
+ <span>{{ village.wood }} / {{ village.getStorage(village.id).getResourceCapacity('wood') }}</span>
+ <i class="icon icon-per-increment"></i>
+ <span>{{ village.getBuilding(village.id, 'WoodCutter').getResourceIncrementor() }}</span>
+ </div>
+ <div>
+ <i class="icon icon-clay"></i>
+ <span>{{ village.clay }} / {{ village.getStorage(village.id).getResourceCapacity('clay') }}</span>
+ <i class="icon icon-per-increment"></i>
+ <span>{{ village.getBuilding(village.id, 'ClayPit').getResourceIncrementor() }}</span>
+ </div>
+ <div>
+ <i class="icon icon-iron"></i>
+ <span>{{ village.iron }} / {{ village.getStorage(village.id).getResourceCapacity('iron') }}</span>
+ <i class="icon icon-per-increment"></i>
+ <span>{{ village.getBuilding(village.id, 'IronMine').getResourceIncrementor() }}</span>
+ </div>
+ <div>
+ <i class="icon icon-food"></i>
+ <span>{{ village.food }} / {{ village.getStorage(village.id).getResourceCapacity('food') }}</span>
+ <i class="icon icon-per-increment"></i>
+ <span>{{ village.getBuilding(village.id, 'Farm').getResourceIncrementor() }}</span>
+ </div>
- <span>capacity: {{ village.getStorage(village.id).getCapacity() }}</span>
+ <div onclick="javascript:this.nextElementSibling.showModal()">
+ <i class="icon icon-storage"></i>
+ <span>{{ village.getStorage(village.id).getCapacity() }}</span>
+ </div>
+ <dialog>
+ <button onclick="javascript:this.parentNode.close()">Close</button>
+ <h3 align="center">Storage Config</h3>
+ <form method="post" action="/village/{{ village.x }}/{{ village.y }}/storage/config">
+ <label>
+ Wood:
+ <input type="text" name="wood" value="{{ village.getStorageConfig(village.id).wood }}">
+ </label>
+ <label>
+ <i class="icon icon-clay"></i>
+ <input type="text" name="clay" value="{{ village.getStorageConfig(village.id).clay }}">
+ </label>
+ <label>
+ <i class="icon icon-iron"></i>
+ <input type="text" name="iron" value="{{ village.getStorageConfig(village.id).iron }}">
+ </label>
+ <label>
+ <i class="icon icon-food"></i>
+ <input type="text" name="food" value="{{ village.getStorageConfig(village.id).food }}">
+ </label>
+ <button>Save</button>
+ </form>
+ </dialog>
</div>
</div>
@@ -47,12 +101,68 @@
</table>
{% endif %}
- {% if events.train %}
+ {% if events['TrainUnits'] %}
<h4>Train Units</h4>
+ <table>
+ <thead>
+ <tr>
+ <th>Unit</th>
+ <th>Time</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for event in events['TrainUnits'] %}
+ <tr>
+ <td>{{ event.data.type }}</td>
+ <td class="timer">
+ {% include 'components/timer.twig' with { 'time': event.event.time|date('c') } %}
+ </td>
+ <td>
+ <a class="btn" href="/village/{{ village.x }}/{{ village.y }}/unit/{{ event.data.type }}/train/cancel">
+ Cancel
+ </a>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
{% endif %}
- {% if events.send %}
+ {% if events['SendUnits'] %}
<h4>Send Resources / Units</h4>
+ <table>
+ <thead>
+ <tr>
+ <th>Type</th>
+ <th>Unit</th>
+ <th>Amount</th>
+ <th>Origin</th>
+ <th>Destination</th>
+ <th>Time</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for event in events['SendUnits'] %}
+ <tr>
+ <td>{{ event.data.type }}</td>
+ <td>{{ event.data.unit }}</td>
+ <td>{{ event.data.amount }}</td>
+ <td>{{ village.get(event.data.source).name }}</td>
+ <td>{{ village.get(event.data.destination).name }}</td>
+ <td class="timer">
+ {% include 'components/timer.twig' with { 'time': event.event.time|date('c') } %}
+ </td>
+ <td>
+ <a class="btn" href="/village/{{ village.x }}/{{ village.y }}/unit/{{ event.data.type }}/train/cancel">
+ Cancel
+ </a>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
{% endif %}
</div>
@@ -76,11 +186,20 @@
<td>{{ building.level }}</td>
<td>{{ building.getBuildTime() | buildTime }}</td>
<td class="resources">
- <span>wood: {{ building.getResourceRequirements()['wood'] }}</span>
+ <span>
+ <i class="icon icon-wood"></i>
+ {{ building.getResourceRequirements()['wood'] }}
+ </span>
&nbsp;
- <span>clay: {{ building.getResourceRequirements()['clay'] }}</span>
+ <span>
+ <i class="icon icon-clay"></i>
+ {{ building.getResourceRequirements()['clay'] }}
+ </span>
&nbsp;
- <span>iron: {{ building.getResourceRequirements()['iron'] }}</span>
+ <span>
+ <i class="icon icon-iron"></i>
+ {{ building.getResourceRequirements()['iron'] }}
+ </span>
</td>
<td>
<form action="/village/{{ village.x }}/{{ village.y }}/building/{{ building.type }}/level-up" method="post">
@@ -114,18 +233,30 @@
{{ unit.getBuildTime(1) | buildTime }}
</td>
<td>
- <span>wood: {{ unit.getResourceRequirements()['wood'] }}</span>
+ <span>
+ <i class="icon icon-wood"></i>
+ {{ unit.getResourceRequirements(unit, 1)['wood'] }}
+ </span>
&nbsp;
- <span>clay: {{ unit.getResourceRequirements()['clay'] }}</span>
+ <span>
+ <i class="icon icon-clay"></i>
+ {{ unit.getResourceRequirements(unit, 1)['clay'] }}
+ </span>
&nbsp;
- <span>iron: {{ unit.getResourceRequirements()['iron'] }}</span>
+ <span>
+ <i class="icon icon-iron"></i>
+ {{ unit.getResourceRequirements(unit, 1)['iron'] }}
+ </span>
&nbsp;
- <span>food: {{ unit.getResourceRequirements()['food'] ?? 0 }}</span>
+ <span>
+ <i class="icon icon-food"></i>
+ {{ unit.getResourceRequirements(unit, 1)['food'] ?? 0 }}
+ </span>
</td>
<td>
<form action="/village/{{ village.x }}/{{ village.y }}/unit/{{ unit.type }}/create" method="post" class="inline">
<input type="number" min="0" name="amount" placeholder="Amount">
- <input type="submit" value="Create">
+ <input type="submit" value="Train">
</form>
</td>
</tr>
@@ -141,6 +272,7 @@
<th>Amount</th>
<th>Origin</th>
<th>Location</th>
+ <th>Travel Time</th>
<th></th>
</tr>
</thead>
@@ -151,18 +283,42 @@
<td>{{ unit.amount }}</td>
<td>{{ village.get(unit.homeVillageId).name }}</td>
<td>{{ not unit.isTraveling ? village.get(unit.residenceVillageId).name : '~traveling~' }}</td>
+ <td>{{ unit.getTravelTime(unit, village.getDistance(unit.getHomeVillage().x, unit.getHomeVillage().x, unit.getResidenceVillage().x, unit.getResidenceVillage().y)) | buildTime }}</td>
<td>
{% if not unit.isTraveling %}
- <form action="/village/{{ village.id }}/unit/{{ unit.id }}/send-back" method="post" class="inline">
- <input type="number" min="1" max="{{ unit.amount }}" name="amount" placeholder="Amount" required>
- <input type="submit" value="{{ (unit.homeVillageId != unit.residenceVillageId) ? 'Send Back' : 'Recall Home' }}">
- </form>
+ {% if unit.homeVillageId == village.id %}
+ <form action="/village/{{ village.x }}/{{ village.y }}/unit/{{ unit.type }}/location/{{ unit.getResidenceVillage().x }}/{{ unit.getResidenceVillage().y }}/recall" method="post">
+ <input type="number" min="1" max="{{ unit.amount }}" name="amount" placeholder="Amount" required>
+ <input type="submit" value="Recall Home">
+ </form>
+ {% else %}
+ <form action="/village/{{ village.x }}/{{ village.y }}/unit/{{ unit.type }}/location/{{ unit.getHomeVillage().x }}/{{ unit.getHomeVillage().y }}/send-back" method="post">
+ <input type="number" min="1" max="{{ unit.amount }}" name="amount" placeholder="Amount" required>
+ <input type="submit" value="Send Back">
+ </form>
+ {% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
+
+ <h4>Send Units</h4>
+ <form action="/village/{{ village.x }}/{{ village.y }}/send-units" method="post">
+ <select name="unit">
+ {% for unit in village.getUnits(village.id, 1) %}
+ <option>{{ unit.type }}</option>
+ {% endfor %}
+ </select>
+ <select name="village">
+ {% for v in villages %}
+ <option value="{{ v.id }}">{{ v.name }}</option>
+ {% endfor %}
+ </select>
+ <input type="number" min="1" name="amount" placeholder="Amount" required>
+ <button>Send</button>
+ </form>
</div>
</div>
</div>