diff options
author | Daniel Weipert <code@drogueronin.de> | 2023-10-04 11:32:04 +0200 |
---|---|---|
committer | Daniel Weipert <code@drogueronin.de> | 2023-10-04 11:32:04 +0200 |
commit | 94a3dd52da3ae180af37c6fd0e8c24b3562da388 (patch) | |
tree | acced055660bfd65ca3e955ea26d412457ba4507 | |
parent | fa00b957378a393f8edbfc98ef111d35d18ecb09 (diff) |
initial commit 2
-rw-r--r-- | .env.example | 3 | ||||
-rw-r--r-- | public/assets/style.css | 104 | ||||
-rw-r--r-- | src/Controller/Map.php | 43 | ||||
-rw-r--r-- | src/Controller/Unit.php | 248 | ||||
-rw-r--r-- | src/Controller/Village.php | 79 | ||||
-rw-r--r-- | src/EventRunner.php | 6 | ||||
-rw-r--r-- | src/Model/Building.php | 6 | ||||
-rw-r--r-- | src/Model/Event/SendUnits.php | 41 | ||||
-rw-r--r-- | src/Model/Event/TrainUnits.php | 2 | ||||
-rw-r--r-- | src/Model/Unit.php | 52 | ||||
-rw-r--r-- | src/Model/Unit/Farmer.php | 3 | ||||
-rw-r--r-- | src/Model/Unit/Miner.php | 3 | ||||
-rw-r--r-- | src/Model/Unit/PitWorker.php | 3 | ||||
-rw-r--r-- | src/Model/Unit/WoodCutter.php | 5 | ||||
-rw-r--r-- | src/Model/Village.php | 17 | ||||
-rw-r--r-- | views/base.twig | 6 | ||||
-rw-r--r-- | views/map.twig | 32 | ||||
-rw-r--r-- | views/root.twig | 2 | ||||
-rw-r--r-- | views/village.twig | 196 |
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> — {{ village.name }}</div> + <div> + <span> + <a href="/map/{{ village.x }}/{{ village.y }}"> + {{ village.x }} x {{ village.y }} + </a> + </span> + — + {{ village.name }} + </div> <div class="resources"> - <span>wood: <b>{{ village.wood }}</b> / {{ village.getStorage(village.id).getResourceCapacity('wood') }} – {{ village.getBuilding(village.id, 'WoodCutter').getResourceIncrementor() }}</span> - <span>clay: <b>{{ village.clay }}</b> / {{ village.getStorage(village.id).getResourceCapacity('clay') }} – {{ village.getBuilding(village.id, 'ClayPit').getResourceIncrementor() }}</span> - <span>iron: <b>{{ village.iron }}</b> / {{ village.getStorage(village.id).getResourceCapacity('iron') }} – {{ village.getBuilding(village.id, 'IronMine').getResourceIncrementor() }}</span> - <span>food: <b>{{ village.food }}</b> / {{ village.getStorage(village.id).getResourceCapacity('food') }} – {{ 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> - <span>clay: {{ building.getResourceRequirements()['clay'] }}</span> + <span> + <i class="icon icon-clay"></i> + {{ building.getResourceRequirements()['clay'] }} + </span> - <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> - <span>clay: {{ unit.getResourceRequirements()['clay'] }}</span> + <span> + <i class="icon icon-clay"></i> + {{ unit.getResourceRequirements(unit, 1)['clay'] }} + </span> - <span>iron: {{ unit.getResourceRequirements()['iron'] }}</span> + <span> + <i class="icon icon-iron"></i> + {{ unit.getResourceRequirements(unit, 1)['iron'] }} + </span> - <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> |