From e4351601a7ced91bf5a2c516a38db714d43e6ecf Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Mon, 8 Jan 2024 22:28:02 +0100 Subject: send resources + adminer css --- Justfile | 3 + bin/db.php | 51 ++++++- docker-compose.yml | 2 + docker/adminer.css | 113 +++++++++++++++ src/Model/Building/Marketplace.php | 10 ++ src/Model/Event/SendResources.php | 150 ++++++++++++++++++++ src/Model/Event/SendResourcesMerchants.php | 154 +++++++++++++++++++++ src/Model/Unit/Merchant.php | 8 ++ src/Model/Village.php | 30 +++- src/gemini/Controller/Village.php | 88 ++++++++++++ src/gemini/Gemini.php | 19 +++ views/gemini/send-resources/01-resource-types.twig | 4 + views/gemini/send-resources/02-villages.twig | 6 + views/gemini/send-units/02-villages.twig | 2 +- views/gemini/village.twig | 33 +++-- 15 files changed, 662 insertions(+), 11 deletions(-) create mode 100644 docker/adminer.css create mode 100644 src/Model/Event/SendResources.php create mode 100644 src/Model/Event/SendResourcesMerchants.php create mode 100644 views/gemini/send-resources/01-resource-types.twig create mode 100644 views/gemini/send-resources/02-villages.twig diff --git a/Justfile b/Justfile index 2207542..5610b26 100644 --- a/Justfile +++ b/Justfile @@ -1,2 +1,5 @@ +db: + docker compose exec app php bin/db.php + gemini: find $(pwd) -type f -name '*.php' -o -name '*.twig' | GEMINI=true DB_HOST=localhost entr -r php public/index.php diff --git a/bin/db.php b/bin/db.php index 8047b42..7938f87 100644 --- a/bin/db.php +++ b/bin/db.php @@ -88,7 +88,9 @@ DB::query(<< div { + background: #222; + } + } + + td, + tbody th { + background: transparent !important; + } + th, + thead td, + tr.checked td, + tr:hover td { + background: #333 !important; + } + td, th { + padding: 0.1rem 0.4rem; + } + + .column { + background: #222 !important; + } + + code { + background: transparent; + padding: 0.1rem 0.2rem; + } + .jush, .jush a { + color: #ebebeb; + } + .jush-sqlite_quo { + color: inherit; + } + .jush-num { + color: inherit; + } + .jush-quo { + color: inherit; + } + + .error, .message { + background: #333; + color: #ebebeb; + + b { + background: #222; + padding: 0 0.2rem; + } + } + + #help { + background: #333; + } + + input, select { + background: #333; + color: #ebebeb; + border: 1px solid #777; + } + + input[type="submit"] { + cursor: pointer; + padding: 0.1rem 0.4rem; + line-height: 1rem; + + &:hover { + background: #ebebeb; + color: #222; + } + } +} diff --git a/src/Model/Building/Marketplace.php b/src/Model/Building/Marketplace.php index 2d87379..8978003 100644 --- a/src/Model/Building/Marketplace.php +++ b/src/Model/Building/Marketplace.php @@ -3,9 +3,12 @@ namespace App\Model\Building; use App\Model\Building; +use App\Model\Unit\Merchant; +use App\Model\Village; class Marketplace extends Building { + public string $unitType = 'Merchant'; public int $buildTimeFactor = 1; public int $maxLevel = 25; @@ -19,4 +22,11 @@ class Marketplace extends Building 'clay' => 10.0, 'iron' => 8.0, ]; + + public static function getResourceCapabilities(Village $village): int + { + $merchants = Village::getUnit($village, 'Merchant', Village::FETCH_UNIT_RESIDENCE, Village::RETURN_UNIT_EXISTING); + + return Merchant::getResourceCapabilities($village) * $merchants->amount; + } } diff --git a/src/Model/Event/SendResources.php b/src/Model/Event/SendResources.php new file mode 100644 index 0000000..5c751fd --- /dev/null +++ b/src/Model/Event/SendResources.php @@ -0,0 +1,150 @@ +isCanceled) { + // TODO: switch destination and source + // TODO: add resources back to "destination" + // TODO: add merchants back to "destination" + } + + else { + // TODO: account for storage capacity + DB::query( + 'update villages set wood=wood+:wood, clay=clay+:clay, iron=iron+:iron, food=food+:food where id=:id', + [ + 'wood' => $this->wood, + 'clay' => $this->clay, + 'iron' => $this->iron, + 'food' => $this->food, + 'id' => $this->destination, + ] + ); + + $source = Village::get($this->source); + $destination = Village::get($this->destination); + + $event = new Event(); + $event->time = (new \DateTime())->add( + \DateInterval::createFromDateString( + Unit::getTravelTime(new Merchant(), Village::getDistance($source->x, $source->y, $destination->x, $destination->y)) + . ' seconds' + ) + ); + $event->villageId = $this->source; + + $sendResourcesMerchants = new SendResourcesMerchants(); + $sendResourcesMerchants->dbInsert($this->id); + + + // TODO: add resources to destination + // (TODO: add foreign merchants to destination)? + // TODO: create SendUnits event with merchants back to source + } + } + + public function dbInsert(): void + { + DB::query( + 'insert into events (time, village_id) VALUES (:time, :village_id)', + ['time' => $this->event->time->format('c'), 'village_id' => $this->event->villageId] + ); + + DB::query( + << DB::$connection->lastInsertId(), + 'wood' => $this->wood, + 'clay' => $this->clay, + 'iron' => $this->iron, + 'food' => $this->food, + 'source' => $this->source, 'destination' => $this->destination, + 'is_canceled' => $this->isCanceled ?: 0, // @see https://www.php.net/manual/de/pdostatement.execute.php#126013 + ] + ); + $sendResourcesEventId = DB::$connection->lastInsertId(); + + $resourceCapabilities = Merchant::getResourceCapabilities($this->event->villageId); + $resourcesTotal = $this->wood + $this->clay + $this->iron + $this->food; + $necessaryMerchants = ceil($resourcesTotal / $resourceCapabilities); + + $merchantsAccountedFor = 0; + while ($merchantsAccountedFor < $necessaryMerchants) { + $merchants = DB::fetch( + Merchant::class, + 'select * from village_units where type=:type and residence_village_id=:villageId and is_traveling=false', + ['type' => 'Merchant', 'villageId' => $this->source] + ); + foreach ($merchants as $merchant) { + /**@type Merchant $merchant*/ + + $currentlyNecessaryMerchants = $necessaryMerchants - $merchantsAccountedFor; + $currentlyUseableMerchants = $currentlyNecessaryMerchants - $merchant->amount < 0 ? $currentlyNecessaryMerchants : $merchant->amount; + $merchantsAccountedFor += $currentlyUseableMerchants; + + DB::query( + << $currentlyUseableMerchants, + 'type' => 'Merchant', + 'home' => $merchant->homeVillageId, + 'residence' => $merchant->residenceVillageId, + ] + ); + DB::query('update village_units set amount=amount-:amount where id=:unitId', ['amount' => $necessaryMerchants, 'unitId' => $merchant->id]); + + DB::query( + << $sendResourcesEventId, + 'unit_id' => $merchant->id, + 'amount' => $necessaryMerchants, + ] + ); + } + } + + // TODO: remove resources from source + } + + public function dbDelete(): void + { + DB::query('delete from events where id=:id', ['id' => $this->eventId]); + DB::query('delete from events_send_resources where id=:id', ['id' => $this->id]); + #DB::query('delete from events_send_resources_merchants where event_id=:id', ['id' => $this->id]); + } +} diff --git a/src/Model/Event/SendResourcesMerchants.php b/src/Model/Event/SendResourcesMerchants.php new file mode 100644 index 0000000..a50b2bc --- /dev/null +++ b/src/Model/Event/SendResourcesMerchants.php @@ -0,0 +1,154 @@ +isCanceled) { + // TODO: switch destination and source + // TODO: add resources back to "destination" + // TODO: add merchants back to "destination" + } + + else { + // TODO: account for storage capacity + DB::query( + 'update villages set wood=wood+:wood, clay=clay+:clay, iron=iron+:iron, food=food+:food where id=:id', + [ + 'wood' => $this->wood, + 'clay' => $this->clay, + 'iron' => $this->iron, + 'food' => $this->food, + 'id' => $this->destination, + ] + ); + + // TODO: foreach send_resources_merchants + $source = Village::get($this->source); + $destination = Village::get($this->destination); + $event = new Event(); + $event->time = (new \DateTime())->add( + \DateInterval::createFromDateString( + Unit::getTravelTime(new Merchant(), Village::getDistance($source->x, $source->y, $destination->x, $destination->y)) + . ' seconds' + ) + ); + $event->villageId = $this->source; + $sendUnitsEvent = new SendUnits(); + $sendUnitsEvent->event = $event; + $sendUnitsEvent->type = 'SendBack'; + $sendUnitsEvent->unit = 'Merchant'; + $sendUnitsEvent->amount = $amount; + $sendUnitsEvent->source = $village->id; + $sendUnitsEvent->destination = $destination->id; + $sendUnitsEvent->dbInsert(); + + // TODO: add resources to destination + // (TODO: add foreign merchants to destination)? + // TODO: create SendUnits event with merchants back to source + } + } + + public function dbInsert(int $previouId): void + { + DB::query( + 'update events_send_resources_merchants set event_id=:new_id where event_id=:old_id', + ['old_id' => $previouId, 'new_id' => $this->id] + ); + + /* + DB::query( + 'insert into events (time, village_id) VALUES (:time, :village_id)', + ['time' => $this->event->time->format('c'), 'village_id' => $this->event->villageId] + ); + + DB::query( + << DB::$connection->lastInsertId(), + 'wood' => $this->wood, + 'clay' => $this->clay, + 'iron' => $this->iron, + 'food' => $this->food, + 'source' => $this->source, 'destination' => $this->destination, + 'is_canceled' => $this->isCanceled ?: 0, // @see https://www.php.net/manual/de/pdostatement.execute.php#126013 + ] + ); + $sendResourcesEventId = DB::$connection->lastInsertId(); + + $resourceCapabilities = Merchant::getResourceCapabilities($this->event->villageId); + $resourcesTotal = $this->wood + $this->clay + $this->iron + $this->food; + $necessaryMerchants = ceil($resourcesTotal / $resourceCapabilities); + + $merchantsAccountedFor = 0; + while ($merchantsAccountedFor < $necessaryMerchants) { + $merchants = DB::fetch( + Merchant::class, + 'select * from village_units where type=:type and residence_village_id=:villageId and is_traveling=false', + ['type' => 'Merchant', 'villageId' => $this->source] + ); + foreach ($merchants as $merchant) { + $currentlyNecessaryMerchants = $necessaryMerchants - $merchantsAccountedFor; + $currentlyUseableMerchants = $currentlyNecessaryMerchants - $merchant->amount < 0 ? $currentlyNecessaryMerchants : $merchant->amount; + $merchantsAccountedFor += $currentlyUseableMerchants; + + DB::query( + << $currentlyUseableMerchants, + 'type' => 'Merchant', + 'home' => $merchant->homeVillageId, + 'residence' => $merchant->residenceVillageId, + ] + ); + DB::query('update village_units set amount=amount-:amount where id=:unitId', ['amount' => $necessaryMerchants, 'unitId' => $merchant->id]); + + DB::query( + << $sendResourcesEventId, + 'unit_id' => $merchant->id, + 'amount' => $necessaryMerchants, + ] + ); + } + }*/ + } + + public function dbDelete(): void + { + DB::query('delete from events_send_resources_merchants where event_id=:id', ['id' => $this->id]); + } +} diff --git a/src/Model/Unit/Merchant.php b/src/Model/Unit/Merchant.php index 8958d27..be0b25e 100644 --- a/src/Model/Unit/Merchant.php +++ b/src/Model/Unit/Merchant.php @@ -3,6 +3,7 @@ namespace App\Model\Unit; use App\Model\Unit; +use App\Model\Village; class Merchant extends Unit { @@ -22,4 +23,11 @@ class Merchant extends Unit 'iron' => 2.0, 'food' => 2.0, ]; + + public static function getResourceCapabilities(Village|int $village): int + { + $marketplace = Village::getBuilding($village->id ?? $village, 'Marketplace'); + + return $marketplace->level * 100; + } } diff --git a/src/Model/Village.php b/src/Model/Village.php index c6709a6..019d9ba 100644 --- a/src/Model/Village.php +++ b/src/Model/Village.php @@ -3,6 +3,7 @@ namespace App\Model; use App\DB; +use App\Model\Building\Marketplace; use App\Model\Building\Storage; use App\Model\Village\StorageConfig; @@ -53,6 +54,21 @@ class Village return true; } + public static function canSendResources(Village $village): bool + { + $marketplace = Village::getBuilding($village->id, 'Marketplace'); + if (! $marketplace) { + return false; + } + + $merchants = Village::getUnit($village, $marketplace->unitType, Village::FETCH_UNIT_RESIDENCE, Village::RETURN_UNIT_EXISTING); + if (! $merchants || $merchants->amount === 0) { + return false; + } + + return true; + } + /* DB - Actions */ public static function get(int $id): ?Village @@ -136,8 +152,20 @@ class Village public const RETURN_UNIT_ALL = 2; public const RETURN_UNIT_TRAINABLE = 3; - public static function getUnit(string $unitType, int $flag): ?Unit + public static function getUnit(Village $village, string $unitType, int $fetchFlag = Village::FETCH_UNIT_RESIDENCE, int $returnFlag = Village::RETURN_UNIT_EXISTING): ?Unit { + if ($fetchFlag == Village::FETCH_UNIT_RESIDENCE) { + $query = 'select * from village_units where residence_village_id=:id and type=:type and is_traveling=false'; + } + + $results = DB::fetch(Unit::class, $query, ['id' => $village->id, 'type' => $unitType]); + if (isset($results[0])) { + $unit = $results[0]->cast(); + } else { + return null; + } + + return $unit; } /** diff --git a/src/gemini/Controller/Village.php b/src/gemini/Controller/Village.php index 85b9205..70aa899 100644 --- a/src/gemini/Controller/Village.php +++ b/src/gemini/Controller/Village.php @@ -4,9 +4,13 @@ namespace App\gemini\Controller; use App\DB; use App\Guard; +use App\Model\Event; +use App\Model\Event\SendResources; use App\Model\Event\SendUnits; use App\Model\Event\TrainUnits; use App\Model\Event\UpgradeBuilding; +use App\Model\Unit; +use App\Model\Unit\Merchant; use App\Model\Village as Model; use App\View; use GeminiFoundation\Request; @@ -89,6 +93,26 @@ class Village $events['SendUnits'][] = DB::convertToModel(SendUnits::class, $row);; } + $eventsResourcesSendOwn = DB::query( + << $village->id] + )->fetchAll(); + + $eventsResourcesSendOther = DB::query( + << $village->id] + )->fetchAll(); + + foreach ([...$eventsResourcesSendOwn, ...$eventsResourcesSendOther] as $row) { + $events['SendResources'][] = DB::convertToModel(SendResources::class, $row);; + } + $buildings = []; foreach (Model::getBuildings($village->id, true) as $building) { $buildings[$building->type] = $building; @@ -100,6 +124,7 @@ class Village 'events' => $events, 'buildings' => $buildings, 'villages' => DB::fetch(Model::class, "select * from villages where id!=:id", ['id' => $village->id]), + 'marketplace' => $village->getBuilding($village->id, 'Marketplace'), ])); } @@ -164,4 +189,67 @@ class Village meta: "/village/{$village->x}/{$village->y}/storage/config" ); } + + // #[Route(path: '/village/{x}/{y}/send-resources', methods: ['POST'])] + public function sendResources(Request $request): Response + { + $village = Model::getByCoordinates($request->get('x'), $request->get('y')); + + $selectedResourceType = $request->get('selectedResourceType'); + if (empty($selectedResourceType)) { + return new Response(body: View::render('send-resources/01-resource-types.twig', [ + 'village' => $village, + 'marketplace' => $village->getBuilding($village->id, 'Marketplace'), + ])); + } + + if (empty($request->get('selectedVillageX'))) { + return new Response(body: View::render('send-resources/02-villages.twig', [ + 'village' => $village, + 'villages' => DB::fetch(Model::class, "select * from villages where id!=:id", ['id' => $village->id]), + 'selectedResourceType' => $selectedResourceType, + 'marketplace' => $village->getBuilding($village->id, 'Marketplace'), + ])); + } + $selectedVillage = Model::getByCoordinates($request->get('selectedVillageX'), $request->get('selectedVillageY')); + + $amount = intval($request->get('input')); + if (empty($amount)) { + return new Response(statusCode: Status::INPUT, meta: 'Amount'); + } + + $resourceCapabilities = Merchant::getResourceCapabilities($village); + $necessaryMerchants = ceil($amount / $resourceCapabilities); + $merchants = DB::fetch(Merchant::class, 'select sum(amount) from village_units where type=:type and residence_village_id=:villageId and is_traveling=false', ['villageId' => $this->source, 'type' => 'Merchant'])[0]['amount'] ?? 0; + if ($merchants === 0) { + return new Response( + statusCode: Status::REDIRECT_TEMPORARY, + meta: "/village/{$village->x}/{$village->y}/type/$selectedResourceType/village/{$selectedVillage->x}/{$selectedVillage->y}" + ); + } + + $destination = $selectedVillage; + + // event + $event = new Event(); + $event->time = (new \DateTime())->add( + \DateInterval::createFromDateString( + Unit::getTravelTime(new Merchant(), Model::getDistance($village->x, $village->y, $destination->x, $destination->y)) + . ' seconds' + ) + ); + $event->villageId = $village->id; + $sendResourcesEvent = new SendResources(); + $sendResourcesEvent->event = $event; + $sendResourcesEvent->$selectedResourceType = $amount; + $sendResourcesEvent->source = $village->id; + $sendResourcesEvent->destination = $destination->id; + $sendResourcesEvent->dbInsert(); + + + return new Response( + statusCode: Status::REDIRECT_TEMPORARY, + meta: "/village/{$village->x}/{$village->y}" + ); + } } diff --git a/src/gemini/Gemini.php b/src/gemini/Gemini.php index 507b962..4c420d4 100644 --- a/src/gemini/Gemini.php +++ b/src/gemini/Gemini.php @@ -129,6 +129,25 @@ class Gemini $response = $unitController->sendUnits($request); } + else if (preg_match('@village/(\d+)/(\d+)/send-resources@', $request->getPath(), $routeMatch)) { + $request + ->set('x', $routeMatch[1]) + ->set('y', $routeMatch[2]); + + if (preg_match('@village/(\d+)/(\d+)/send-resources/type/(\w+)$@', $request->getPath(), $routeMatch)) { + $request->set('selectedResourceType', $routeMatch[3]); + } + else if (preg_match('@village/(\d+)/(\d+)/send-resources/type/(\w+)/village/(\d+)/(\d+)$@', $request->getPath(), $routeMatch)) { + $request + ->set('selectedResourceType', $routeMatch[3]) + ->set('selectedVillageX', $routeMatch[4]) + ->set('selectedVillageY', $routeMatch[5]); + } + + $villageController = new Village(); + $response = $villageController->sendResources($request); + } + else if (preg_match('@village/(\d+)/(\d+)@', $request->getPath(), $routeMatch)) { $request ->set('x', $routeMatch[1]) diff --git a/views/gemini/send-resources/01-resource-types.twig b/views/gemini/send-resources/01-resource-types.twig new file mode 100644 index 0000000..586aa1e --- /dev/null +++ b/views/gemini/send-resources/01-resource-types.twig @@ -0,0 +1,4 @@ +Sendable Resource Capability: {{ marketplace.getResourceCapabilities(village) }} +{% for resourceType in ['wood', 'clay', 'iron', 'food'] %} +=> /village/{{ village.x }}/{{ village.y }}/send-resources/type/{{ resourceType }} {{ resourceType }} +{% endfor %} diff --git a/views/gemini/send-resources/02-villages.twig b/views/gemini/send-resources/02-villages.twig new file mode 100644 index 0000000..54ccc5c --- /dev/null +++ b/views/gemini/send-resources/02-villages.twig @@ -0,0 +1,6 @@ +{{ selectedResourceType }} +Sendable Resource Capability: {{ marketplace.getResourceCapabilities(village) }} + +{% for listVillage in villages %} +=> /village/{{ village.x }}/{{ village.y }}/send-resources/type/{{ selectedResourceType }}/village/{{ listVillage.x }}/{{ listVillage.y }} {{ listVillage.name }} - {{ listVillage.x }} x {{ listVillage.y }} +{% endfor %} diff --git a/views/gemini/send-units/02-villages.twig b/views/gemini/send-units/02-villages.twig index 80f5326..ea2741c 100644 --- a/views/gemini/send-units/02-villages.twig +++ b/views/gemini/send-units/02-villages.twig @@ -1,5 +1,5 @@ {{ selectedUnit }} {% for listVillage in villages %} -=> /village/{{ village.x }}/{{ village.y }}/send-units/type/{{ selectedUnit }}/village/{{ listVillage.x }}/{{ listVillage.y }} {{ listVillage.name }} - {{ listVillage.x }} - {{ listVillage.y }} +=> /village/{{ village.x }}/{{ village.y }}/send-units/type/{{ selectedUnit }}/village/{{ listVillage.x }}/{{ listVillage.y }} {{ listVillage.name }} - {{ listVillage.x }} x {{ listVillage.y }} {% endfor %} diff --git a/views/gemini/village.twig b/views/gemini/village.twig index d351eb6..d448020 100644 --- a/views/gemini/village.twig +++ b/views/gemini/village.twig @@ -57,6 +57,27 @@ Canceled {% endfor %} {% endif %} +{% if events['SendResources'] %} +## Send Resources +{% for event in events['SendResources'] %} +### {{ village.get(event.source).name }} -> {{ village.get(event.destination).name }} +Wood: {{ event.wood }} +Clay: {{ event.clay }} +Iron: {{ event.iron }} +Food: {{ event.food }} +Source: {{ village.get(event.source).name }} +Destination: {{ village.get(event.destination).name }} +Finished: {{ event.event.time | date('c') }} +{% if event.isCanceled %} +Canceled +{% else %} +{% if event.event.villageId == village.id %} +=> /event/{{ event.event.id }}/cancel Cancel +{% endif %} +{% endif %} +{% endfor %} +{% endif %} + # Buildings @@ -120,15 +141,11 @@ Travel Time: {{ unit.getTravelTime(unit, village.getDistance(unit.getHomeVillage {% include 'send-units/01-units.twig' %} +{% if village.canSendResources(village) %} # Send Resources -TODO -* list possible resource types -=> /village/{{ village.x }}/{{ village.y }}/send-resources/type/wood Wood -=> /village/{{ village.x }}/{{ village.y }}/send-resources/type/clay Clay -=> /village/{{ village.x }}/{{ village.y }}/send-resources/type/iron Iron -=> /village/{{ village.x }}/{{ village.y }}/send-resources/type/food Food -* list possible villages -* send INPUT request for amount +{% include 'send-resources/01-resource-types.twig' %} +{% endif %} + {% include 'footer.twig' %} -- cgit v1.2.3