Epic Cosmos 2: ресурси, сгради, кораби
Проблем
Светът е готов, но нищо не става
От миналия урок имаш звезди, планети и спътници. Но играта още не върви: няма с какво да строиш и няма за какво да чакаш.
Икономиката на Epic Cosmos:
- Всяка планета има четири ресурса. Всеки ход те се добиват със своя скорост: метал 1,0, газ 0,5, кристал 0,2, уран 0,1 на ход.
- Сградите струват ресурси и време. Мините и рафинерията удвояват добива на своя ресурс на всяко ниво. Лабораторията и корабостроителницата отключват други неща.
- Корабите летят. Скаутът проучва околността и докладва какво има на всяка планета.
- Играта е на ходове. Точките идват от сгради, кораби и открития. Краят е при определен брой точки или ходове.
Ако ресурсите не стигат, строежът трябва да откаже и да каже какво липсва. Това е работа за свое изключение.
Идеята
Ресурсът
Ресурсът има количество (amount) и скорост на добив (harvest_rate). Всеки ход harvest() добавя скоростта към количеството. Мината вика boost() и скоростта се удвоява.
abstract class Resource { private double amount; private double harvestRate;
public Resource(double amount, double harvestRate) { this.amount = amount; this.harvestRate = harvestRate; }
public abstract String getName();
public double getAmount() { return amount; }
public double getHarvestRate() { return harvestRate; }
public void harvest() { amount = round(amount + harvestRate); }
public void boost() { harvestRate = harvestRate * 2; }
public void add(double quantity) { amount = round(amount + quantity); }
public void spend(double quantity) { amount = round(amount - quantity); }
private static double round(double value) { return Math.round(value * 10) / 10.0; }}abstract class Resource{ public double Amount { get; private set; } public double HarvestRate { get; private set; }
public Resource(double amount, double harvestRate) { Amount = amount; HarvestRate = harvestRate; }
public abstract string GetName();
public void Harvest() { Amount = Round(Amount + HarvestRate); }
public void Boost() { HarvestRate = HarvestRate * 2; }
public void Add(double quantity) { Amount = Round(Amount + quantity); }
public void Spend(double quantity) { Amount = Round(Amount - quantity); }
private static double Round(double value) { return Math.Round(value * 10) / 10; }}class Resource(ABC): def __init__(self, amount, harvest_rate): self._amount = amount self._harvest_rate = harvest_rate
@abstractmethod def get_name(self): pass
@property def amount(self): return self._amount
@property def harvest_rate(self): return self._harvest_rate
def harvest(self): self._amount = round(self._amount + self._harvest_rate, 1)
def boost(self): self._harvest_rate = self._harvest_rate * 2
def add(self, quantity): self._amount = round(self._amount + quantity, 1)
def spend(self, quantity): self._amount = round(self._amount - quantity, 1)class Resource { #amount; #harvestRate;
constructor(amount, harvestRate) { this.#amount = amount; this.#harvestRate = harvestRate; }
getName() { throw new Error(`${this.constructor.name} трябва да напише getName()`); }
get amount() { return this.#amount; }
get harvestRate() { return this.#harvestRate; }
harvest() { this.#amount = Resource.#round(this.#amount + this.#harvestRate); }
boost() { this.#harvestRate = this.#harvestRate * 2; }
add(quantity) { this.#amount = Resource.#round(this.#amount + quantity); }
spend(quantity) { this.#amount = Resource.#round(this.#amount - quantity); }
static #round(value) { return Math.round(value * 10) / 10; }}Четирите вида се различават само по име и по скорост:
class Metal extends Resource { public Metal(double amount) { super(amount, 1.0); }
@Override public String getName() { return "метал"; }}class Metal : Resource{ public Metal(double amount) : base(amount, 1.0) { }
public override string GetName() { return "метал"; }}class Metal(Resource): def __init__(self, amount): super().__init__(amount, 1.0)
def get_name(self): return "метал"class Metal extends Resource { constructor(amount) { super(amount, 1.0); }
getName() { return "метал"; }}Gas е с 0,5, Crystal с 0,2, Uranium с 0,1.
По-точно, за любопитните
float не може да пази 0,1 точно. Ако събереш 0,1 десет пъти, получаваш 0,9999999999999999, а не 1,0. Тогава проверката „имаш ли 1 уран“ би казала „не“. Затова round(…, 1) закръгля всяко ново количество до десети. В истински игри количествата често са цели числа, например в десети от единицата, точно за да няма такива изненади.
total = 0for i in range(10): total += 0.1print(total)0.9999999999999999round(x, 1) е вградената функция на Python за закръгляне до един знак след точката, затова отделен метод не трябва.Виж го
Ресурсите на планетата
Planet от миналия урок получава четири ресурса и метод, който ги добива наведнъж:
private Resource metal = new Metal(0);private Resource gas = new Gas(0);private Resource crystal = new Crystal(0);private Resource uranium = new Uranium(0);
public Resource getMetal() { return metal;}
public Resource getGas() { return gas;}
public Resource getCrystal() { return crystal;}
public Resource getUranium() { return uranium;}
public void harvestAll() { metal.harvest(); gas.harvest(); crystal.harvest(); uranium.harvest();}
public String resourcesText() { return String.format("метал %.1f, газ %.1f, кристал %.1f, уран %.1f", metal.getAmount(), gas.getAmount(), crystal.getAmount(), uranium.getAmount());}
public void printResources() { System.out.println(getName() + ": " + resourcesText());}public Resource Metal { get; } = new Metal(0);public Resource Gas { get; } = new Gas(0);public Resource Crystal { get; } = new Crystal(0);public Resource Uranium { get; } = new Uranium(0);
public void HarvestAll(){ Metal.Harvest(); Gas.Harvest(); Crystal.Harvest(); Uranium.Harvest();}
public string ResourcesText(){ return $"метал {Metal.Amount:F1}, газ {Gas.Amount:F1}, кристал {Crystal.Amount:F1}, уран {Uranium.Amount:F1}";}
public void PrintResources(){ Console.WriteLine(Name + ": " + ResourcesText());}def __init__(self, name, coordinate_x, coordinate_y, coordinate_z, mass): super().__init__(name, coordinate_x, coordinate_y, coordinate_z, mass) self._metal = Metal(0) self._gas = Gas(0) self._crystal = Crystal(0) self._uranium = Uranium(0) self._buildings = [] # сградите идват по-нататък в урока
@propertydef metal(self): return self._metal
@propertydef gas(self): return self._gas
@propertydef crystal(self): return self._crystal
@propertydef uranium(self): return self._uranium
def harvest_all(self): self._metal.harvest() self._gas.harvest() self._crystal.harvest() self._uranium.harvest()
def resources_text(self): return f"метал {self._metal.amount:.1f}, газ {self._gas.amount:.1f}, кристал {self._crystal.amount:.1f}, уран {self._uranium.amount:.1f}"
def print_resources(self): print(f"{self.name}: {self.resources_text()}")#metal = new Metal(0);#gas = new Gas(0);#crystal = new Crystal(0);#uranium = new Uranium(0);#buildings = []; // сградите идват по-нататък в урока
get metal() { return this.#metal;}
get gas() { return this.#gas;}
get crystal() { return this.#crystal;}
get uranium() { return this.#uranium;}
harvestAll() { this.#metal.harvest(); this.#gas.harvest(); this.#crystal.harvest(); this.#uranium.harvest();}
resourcesText() { return `метал ${this.#metal.amount.toFixed(1)}, газ ${this.#gas.amount.toFixed(1)}, кристал ${this.#crystal.amount.toFixed(1)}, уран ${this.#uranium.amount.toFixed(1)}`;}
printResources() { console.log(`${this.name}: ${this.resourcesText()}`);}Ресурсите се създават в __init__ на планетата. Тук __init__ е нужен, затова той вика родителя със super().__init__(...) и после добавя своето.
Пет хода добив, после удвояване на метала и още един ход:
Planet home = new Planet("Нова Терра", 100, 0, 0, 1);for (int turn = 1; turn <= 5; turn++) { home.harvestAll();}home.printResources();home.getMetal().boost();home.harvestAll();home.printResources();Нова Терра: метал 5.0, газ 2.5, кристал 1.0, уран 0.5Нова Терра: метал 7.0, газ 3.0, кристал 1.2, уран 0.6Planet home = new Planet("Нова Терра", 100, 0, 0, 1);for (int turn = 1; turn <= 5; turn++){ home.HarvestAll();}home.PrintResources();home.Metal.Boost();home.HarvestAll();home.PrintResources();Нова Терра: метал 5.0, газ 2.5, кристал 1.0, уран 0.5Нова Терра: метал 7.0, газ 3.0, кристал 1.2, уран 0.6home = Planet("Нова Терра", 100, 0, 0, 1)for turn in range(1, 6): home.harvest_all()home.print_resources()home.metal.boost()home.harvest_all()home.print_resources()Нова Терра: метал 5.0, газ 2.5, кристал 1.0, уран 0.5Нова Терра: метал 7.0, газ 3.0, кристал 1.2, уран 0.6const home = new Planet("Нова Терра", 100, 0, 0, 1);for (let turn = 1; turn <= 5; turn++) { home.harvestAll();}home.printResources();home.metal.boost();home.harvestAll();home.printResources();Нова Терра: метал 5.0, газ 2.5, кристал 1.0, уран 0.5Нова Терра: метал 7.0, газ 3.0, кристал 1.2, уран 0.6Пробвай
Колко газ?
Какво ще отпечата програмата?
gas = Gas(0)gas.harvest()gas.boost()gas.harvest()gas.boost()gas.harvest()print(f"{gas.amount} {gas.get_name()}, добив {gas.harvest_rate}")Първият добив е 0,5. После скоростта става 1,0 и вторият добив е 1,0, общо 1,5. После скоростта става 2,0 и третият добив е 2,0, общо 3,5. Python печата 2.0: скоростта е дробно число (float), защото е тръгнала от 0,5.
Идеята
Строежът струва
Когато ресурсите не стигат, строежът трябва да откаже. Викащият иска да знае какво и колко липсва, затова изключението е свое и носи числото:
class NotEnoughResourcesException extends Exception { private double missing;
public NotEnoughResourcesException(String purpose, String resource, double missing) { super(String.format("за %s трябват още %.1f %s", purpose, missing, resource)); this.missing = missing; }
public double getMissing() { return missing; }}class NotEnoughResourcesException : Exception{ public double Missing { get; }
public NotEnoughResourcesException(string purpose, string resource, double missing) : base($"за {purpose} трябват още {missing:F1} {resource}") { Missing = missing; }}class NotEnoughResourcesError(Exception): def __init__(self, purpose, resource, missing): super().__init__(f"за {purpose} трябват още {missing:.1f} {resource}") self.missing = missingclass NotEnoughResourcesError extends Error { constructor(purpose, resource, missing) { super(`за ${purpose} трябват още ${missing.toFixed(1)} ${resource}`); this.name = "NotEnoughResourcesError"; this.missing = missing; }}Плащането е в Planet. Първо проверява четирите ресурса, после харчи:
public void spend(String purpose, double metalNeeded, double crystalNeeded, double gasNeeded, double uraniumNeeded) throws NotEnoughResourcesException { check(purpose, metal, metalNeeded); check(purpose, crystal, crystalNeeded); check(purpose, gas, gasNeeded); check(purpose, uranium, uraniumNeeded); metal.spend(metalNeeded); crystal.spend(crystalNeeded); gas.spend(gasNeeded); uranium.spend(uraniumNeeded);}
private void check(String purpose, Resource resource, double needed) throws NotEnoughResourcesException { if (resource.getAmount() < needed) { throw new NotEnoughResourcesException(purpose, resource.getName(), needed - resource.getAmount()); }}public void Spend(string purpose, double metalNeeded, double crystalNeeded, double gasNeeded, double uraniumNeeded){ Check(purpose, Metal, metalNeeded); Check(purpose, Crystal, crystalNeeded); Check(purpose, Gas, gasNeeded); Check(purpose, Uranium, uraniumNeeded); Metal.Spend(metalNeeded); Crystal.Spend(crystalNeeded); Gas.Spend(gasNeeded); Uranium.Spend(uraniumNeeded);}
private void Check(string purpose, Resource resource, double needed){ if (resource.Amount < needed) { throw new NotEnoughResourcesException(purpose, resource.GetName(), needed - resource.Amount); }}def spend(self, purpose, metal_needed, crystal_needed, gas_needed, uranium_needed): self._check(purpose, self._metal, metal_needed) self._check(purpose, self._crystal, crystal_needed) self._check(purpose, self._gas, gas_needed) self._check(purpose, self._uranium, uranium_needed) self._metal.spend(metal_needed) self._crystal.spend(crystal_needed) self._gas.spend(gas_needed) self._uranium.spend(uranium_needed)
def _check(self, purpose, resource, needed): if resource.amount < needed: raise NotEnoughResourcesError(purpose, resource.get_name(), needed - resource.amount)spend(purpose, metalNeeded, crystalNeeded, gasNeeded, uraniumNeeded) { this.#check(purpose, this.#metal, metalNeeded); this.#check(purpose, this.#crystal, crystalNeeded); this.#check(purpose, this.#gas, gasNeeded); this.#check(purpose, this.#uranium, uraniumNeeded); this.#metal.spend(metalNeeded); this.#crystal.spend(crystalNeeded); this.#gas.spend(gasNeeded); this.#uranium.spend(uraniumNeeded);}
#check(purpose, resource, needed) { if (resource.amount < needed) { throw new NotEnoughResourcesError(purpose, resource.getName(), needed - resource.amount); }}Пробвай
Намери грешката
Колонията има 20 метал и никакъв кристал. Лабораторията струва 12 метал и 5 кристал. Строежът е отказан, но металът е станал 8. На кой ред е проблемът?
Избери реда с грешката.
Металът се харчи веднага след своята проверка. После проверката на кристала хвърля, но металът вече е похарчен, а лабораторията не е построена. Изключението спира метода, но не връща назад направеното преди него. Първо всички проверки, после харченето.
Идеята
Сградата
Всяка сграда има име, ниво, време за строеж и цена в четирите ресурса. Строежът и надграждането са общи за всички сгради, затова са в абстрактния BaseBuilding:
public String getRequiredBuilding() { return null;}
public void construct(Planet target) throws NotEnoughResourcesException { if (target.findBuilding(name) != null) { throw new IllegalStateException(name + " вече е построена"); } String required = getRequiredBuilding(); if (required != null && target.findBuilding(required) == null) { throw new IllegalStateException(name + " иска първо " + required); } target.spend(name, metalRequired, crystalRequired, gasRequired, uraniumRequired); planet = target; level = 1; target.addBuilding(this);}
public void upgrade() throws NotEnoughResourcesException { if (planet == null) { throw new IllegalStateException(name + " още не е построена"); } double factor = Math.pow(2, level); planet.spend(name + " ниво " + (level + 1), metalRequired * factor, crystalRequired * factor, gasRequired * factor, uraniumRequired * factor); level++;}
public abstract void applyEffect();public virtual string GetRequiredBuilding(){ return null;}
public void Construct(Planet target){ if (target.FindBuilding(Name) != null) { throw new InvalidOperationException(Name + " вече е построена"); } string required = GetRequiredBuilding(); if (required != null && target.FindBuilding(required) == null) { throw new InvalidOperationException(Name + " иска първо " + required); } target.Spend(Name, MetalRequired, CrystalRequired, GasRequired, UraniumRequired); Planet = target; Level = 1; target.AddBuilding(this);}
public void Upgrade(){ if (Planet == null) { throw new InvalidOperationException(Name + " още не е построена"); } double factor = Math.Pow(2, Level); Planet.Spend(Name + " ниво " + (Level + 1), MetalRequired * factor, CrystalRequired * factor, GasRequired * factor, UraniumRequired * factor); Level++;}
public abstract void ApplyEffect();def get_required_building(self): return None
def construct(self, target): if target.find_building(self._name) is not None: raise RuntimeError(f"{self._name} вече е построена") required = self.get_required_building() if required is not None and target.find_building(required) is None: raise RuntimeError(f"{self._name} иска първо {required}") target.spend(self._name, self._metal_required, self._crystal_required, self._gas_required, self._uranium_required) self._planet = target self._level = 1 target.add_building(self)
def upgrade(self): if self._planet is None: raise RuntimeError(f"{self._name} още не е построена") factor = 2 ** self._level self._planet.spend(f"{self._name} ниво {self._level + 1}", self._metal_required * factor, self._crystal_required * factor, self._gas_required * factor, self._uranium_required * factor) self._level += 1
@abstractmethoddef apply_effect(self): passgetRequiredBuilding() { return null;}
construct(target) { if (target.findBuilding(this.#name) !== null) { throw new Error(`${this.#name} вече е построена`); } const required = this.getRequiredBuilding(); if (required !== null && target.findBuilding(required) === null) { throw new Error(`${this.#name} иска първо ${required}`); } target.spend(this.#name, this.#metalRequired, this.#crystalRequired, this.#gasRequired, this.#uraniumRequired); this.#planet = target; this.#level = 1; target.addBuilding(this);}
upgrade() { if (this.#planet === null) { throw new Error(`${this.#name} още не е построена`); } const factor = 2 ** this.#level; this.#planet.spend(`${this.#name} ниво ${this.#level + 1}`, this.#metalRequired * factor, this.#crystalRequired * factor, this.#gasRequired * factor, this.#uraniumRequired * factor); this.#level++;}
applyEffect() { throw new Error(`${this.constructor.name} трябва да напише applyEffect()`);}constructотказва, ако сградата вече я има или ако липсва сградата, от която зависи. Тези откази са с готовото изключение за неподходящ момент (RuntimeError). После плаща и записва сградата в планетата.upgradeструва двойно повече на всяко ниво: ниво 2 е 2 пъти цената, ниво 3 е 4 пъти.apply_effectе абстрактен: всяка сграда сама знае какво прави, когато е готова.
По-точно, за любопитните
get_required_building() връща None: по подразбиране сградата не зависи от нищо. Наследникът, който зависи, го презаписва.
Идеята
Мини и зависимости
Мините се различават само по това кой ресурс ускоряват. Общото е в Mine:
abstract class Mine extends BaseBuilding { public Mine(String name, int constructionTime, double metalRequired, double crystalRequired) { super(name, 0, constructionTime, metalRequired, crystalRequired, 0, 0); }
protected abstract Resource resourceOf(Planet planet);
@Override public void applyEffect() { resourceOf(getPlanet()).boost(); }}abstract class Mine : BaseBuilding{ public Mine(string name, int constructionTime, double metalRequired, double crystalRequired) : base(name, 0, constructionTime, metalRequired, crystalRequired, 0, 0) { }
protected abstract Resource ResourceOf(Planet planet);
public override void ApplyEffect() { ResourceOf(Planet).Boost(); }}class Mine(BaseBuilding): def __init__(self, name, construction_time, metal_required, crystal_required): super().__init__(name, 0, construction_time, metal_required, crystal_required, 0, 0)
@abstractmethod def _resource_of(self, planet): pass
def apply_effect(self): self._resource_of(self.planet).boost()class Mine extends BaseBuilding { constructor(name, constructionTime, metalRequired, crystalRequired) { super(name, 0, constructionTime, metalRequired, crystalRequired, 0, 0); }
_resourceOf(planet) { throw new Error(`${this.constructor.name} трябва да напише _resourceOf()`); }
applyEffect() { this._resourceOf(this.planet).boost(); }}class MetalMine extends Mine { public MetalMine() { super("Метална мина", 1, 6, 2); }
@Override protected Resource resourceOf(Planet planet) { return planet.getMetal(); }}class MetalMine : Mine{ public MetalMine() : base("Метална мина", 1, 6, 2) { }
protected override Resource ResourceOf(Planet planet) { return planet.Metal; }}class MetalMine(Mine): def __init__(self): super().__init__("Метална мина", 1, 6, 2)
def _resource_of(self, planet): return planet.metalclass MetalMine extends Mine { constructor() { super("Метална мина", 1, 6, 2); }
_resourceOf(planet) { return planet.metal; }}Метална и Кристална мина се строят веднага. Лабораторията иска Кристална мина, а Газовата рафинерия и Корабостроителницата искат Лаборатория:
class ResearchLab extends BaseBuilding { public ResearchLab() { super("Лаборатория", 0, 2, 12, 4, 0, 0); }
@Override public String getRequiredBuilding() { return "Кристална мина"; }
@Override public void applyEffect() { // Отключва други сгради, а ресурсите не се променят. }}class ResearchLab : BaseBuilding{ public ResearchLab() : base("Лаборатория", 0, 2, 12, 4, 0, 0) { }
public override string GetRequiredBuilding() { return "Кристална мина"; }
public override void ApplyEffect() { // Отключва други сгради, а ресурсите не се променят. }}class ResearchLab(BaseBuilding): def __init__(self): super().__init__("Лаборатория", 0, 2, 12, 4, 0, 0)
def get_required_building(self): return "Кристална мина"
def apply_effect(self): # Отключва други сгради, а ресурсите не се променят. passclass ResearchLab extends BaseBuilding { constructor() { super("Лаборатория", 0, 2, 12, 4, 0, 0); }
getRequiredBuilding() { return "Кристална мина"; }
applyEffect() { // Отключва други сгради, а ресурсите не се променят. }}План от три сгради с 10 метал и 3 кристал. Лабораторията е отказана заради зависимостта, Кристалната мина заради метала:
Planet home = new Planet("Нова Терра", 100, 0, 0, 1);home.getMetal().add(10);home.getCrystal().add(3);BaseBuilding[] plan = { new ResearchLab(), new MetalMine(), new CrystalMine() };for (BaseBuilding building : plan) { try { building.construct(home); building.applyEffect(); System.out.println(building.getName() + " е готова."); } catch (NotEnoughResourcesException e) { System.out.println("Отказано: " + e.getMessage()); } catch (IllegalStateException e) { System.out.println("Отказано: " + e.getMessage()); }}home.printResources();System.out.println("Добив на метал: " + home.getMetal().getHarvestRate());Отказано: Лаборатория иска първо Кристална минаМетална мина е готова.Отказано: за Кристална мина трябват още 4.0 металНова Терра: метал 4.0, газ 0.0, кристал 1.0, уран 0.0Добив на метал: 2.0Planet home = new Planet("Нова Терра", 100, 0, 0, 1);home.Metal.Add(10);home.Crystal.Add(3);BaseBuilding[] plan = { new ResearchLab(), new MetalMine(), new CrystalMine() };foreach (BaseBuilding building in plan){ try { building.Construct(home); building.ApplyEffect(); Console.WriteLine(building.Name + " е готова."); } catch (NotEnoughResourcesException e) { Console.WriteLine("Отказано: " + e.Message); } catch (InvalidOperationException e) { Console.WriteLine("Отказано: " + e.Message); }}home.PrintResources();Console.WriteLine("Добив на метал: " + home.Metal.HarvestRate);Отказано: Лаборатория иска първо Кристална минаМетална мина е готова.Отказано: за Кристална мина трябват още 4.0 металНова Терра: метал 4.0, газ 0.0, кристал 1.0, уран 0.0Добив на метал: 2home = Planet("Нова Терра", 100, 0, 0, 1)home.metal.add(10)home.crystal.add(3)plan = [ResearchLab(), MetalMine(), CrystalMine()]for building in plan: try: building.construct(home) building.apply_effect() print(f"{building.name} е готова.") except NotEnoughResourcesError as e: print(f"Отказано: {e}") except RuntimeError as e: print(f"Отказано: {e}")home.print_resources()print(f"Добив на метал: {home.metal.harvest_rate}")Отказано: Лаборатория иска първо Кристална минаМетална мина е готова.Отказано: за Кристална мина трябват още 4.0 металНова Терра: метал 4.0, газ 0.0, кристал 1.0, уран 0.0Добив на метал: 2.0const home = new Planet("Нова Терра", 100, 0, 0, 1);home.metal.add(10);home.crystal.add(3);const plan = [new ResearchLab(), new MetalMine(), new CrystalMine()];for (const building of plan) { try { building.construct(home); building.applyEffect(); console.log(`${building.name} е готова.`); } catch (e) { console.log(`Отказано: ${e.message}`); }}home.printResources();console.log(`Добив на метал: ${home.metal.harvestRate}`);Отказано: Лаборатория иска първо Кристална минаМетална мина е готова.Отказано: за Кристална мина трябват още 4.0 металНова Терра: метал 4.0, газ 0.0, кристал 1.0, уран 0.0Добив на метал: 2home = Planet("Нова Терра", 100, 0, 0, 1)home.metal.add(10)home.crystal.add(3)plan = [ResearchLab(), MetalMine(), CrystalMine()]for building in plan: try: building.construct(home) building.apply_effect() print(f"{building.name} е готова.") except NotEnoughResourcesError as e: print(f"Отказано: {e}") except RuntimeError as e: print(f"Отказано: {e}")home.print_resources()print(f"Добив на метал: {home.metal.harvest_rate}")Стъпка по стъпка, с думи
- На Нова Терра има 10 метал и 3 кристал. Металът се добива с 1.0 на ход.
- Планът е списък от 3 сгради. Всяка има цена в метал и кристал.
- Лаборатория иска Кристална мина, а такава още няма. construct() хвърля RuntimeError, преди да похарчи нещо.
- Метална мина струва 6 метал и 2 кристал. Стигат: остават 4 метал и 1 кристал. apply_effect() удвоява добива на метал.
- Кристална мина иска 8 метал, а има 4. Проверката хвърля NotEnoughResourcesError и нищо не е похарчено.
- Отказите не похарчиха нищо. Остава цената само на построената сграда, а добивът на метал е 2.0.
Идеята
Кораби и скаут
SpaceShip от миналия урок получава тип, скорост, товароносимост и сила на оръжията. Започва от родната планета и лети с fly_to, както преди. Скаутът е кораб със сензори: explore проверява кои планети са в обсега му и докладва ресурсите им.
class Scout extends SpaceShip { private double sensorRange;
public Scout(String name, Planet home) { super(name, "Скаут", 40, 0, 0, home); sensorRange = 100; }
public int explore(Planet[] planets) { int found = 0; for (Planet planet : planets) { double dx = planet.getCoordinateX() - getCoordinateX(); double dy = planet.getCoordinateY() - getCoordinateY(); double dz = planet.getCoordinateZ() - getCoordinateZ(); if (Math.sqrt(dx * dx + dy * dy + dz * dz) <= sensorRange) { reportResources(planet); found++; } } return found; }
public void reportResources(Planet planet) { System.out.println(getName() + " откри " + planet.getName() + ": " + planet.resourcesText()); }}class Scout : SpaceShip{ private double sensorRange;
public Scout(string name, Planet home) : base(name, "Скаут", 40, 0, 0, home) { sensorRange = 100; }
public int Explore(Planet[] planets) { int found = 0; foreach (Planet planet in planets) { double dx = planet.CoordinateX - CoordinateX; double dy = planet.CoordinateY - CoordinateY; double dz = planet.CoordinateZ - CoordinateZ; if (Math.Sqrt(dx * dx + dy * dy + dz * dz) <= sensorRange) { ReportResources(planet); found++; } } return found; }
public void ReportResources(Planet planet) { Console.WriteLine(Name + " откри " + planet.Name + ": " + planet.ResourcesText()); }}class Scout(SpaceShip): def __init__(self, name, home): super().__init__(name, "Скаут", 40, 0, 0, home) self._sensor_range = 100
def explore(self, planets): found = 0 for planet in planets: dx = planet.coordinate_x - self.coordinate_x dy = planet.coordinate_y - self.coordinate_y dz = planet.coordinate_z - self.coordinate_z if math.sqrt(dx * dx + dy * dy + dz * dz) <= self._sensor_range: self.report_resources(planet) found += 1 return found
def report_resources(self, planet): print(f"{self.name} откри {planet.name}: {planet.resources_text()}")class Scout extends SpaceShip { #sensorRange;
constructor(name, home) { super(name, "Скаут", 40, 0, 0, home); this.#sensorRange = 100; }
explore(planets) { let found = 0; for (const planet of planets) { const dx = planet.coordinateX - this.coordinateX; const dy = planet.coordinateY - this.coordinateY; const dz = planet.coordinateZ - this.coordinateZ; if (Math.sqrt(dx * dx + dy * dy + dz * dz) <= this.#sensorRange) { this.reportResources(planet); found++; } } return found; }
reportResources(planet) { console.log(`${this.name} откри ${planet.name}: ${planet.resourcesText()}`); }}Обсегът се мери с разстоянието от упражнение 2 в миналия урок. Методът връща колко планети е открил, за да може играта да даде точки за тях.
Виж го
Цялата игра
Game пази родната планета, другите планети, хода и точките. Командите идват от играча, по една на ред. Сградите отнемат ходове, а през всеки ход планетата добива:
public void handle(String command) { int space = command.indexOf(' '); String verb = space == -1 ? command : command.substring(0, space); String rest = space == -1 ? "" : command.substring(space + 1); try { if (verb.equals("строй")) { build(createBuilding(rest)); } else if (verb.equals("надгради")) { upgrade(rest); } else if (verb.equals("скаут")) { buildScout(); } else if (verb.equals("проучи")) { explore(); } else if (verb.equals("чакай")) { waitTurns(1); } else { System.out.println("Непозната команда: " + command); } } catch (NotEnoughResourcesException e) { System.out.println("Отказано: " + e.getMessage()); } catch (IllegalStateException e) { System.out.println("Отказано: " + e.getMessage()); } catch (IllegalArgumentException e) { System.out.println("Отказано: " + e.getMessage()); }}public void Handle(string command){ int space = command.IndexOf(' '); string verb = space == -1 ? command : command.Substring(0, space); string rest = space == -1 ? "" : command.Substring(space + 1); try { if (verb == "строй") { Build(CreateBuilding(rest)); } else if (verb == "надгради") { Upgrade(rest); } else if (verb == "скаут") { BuildScout(); } else if (verb == "проучи") { Explore(); } else if (verb == "чакай") { WaitTurns(1); } else { Console.WriteLine("Непозната команда: " + command); } } catch (NotEnoughResourcesException e) { Console.WriteLine("Отказано: " + e.Message); } catch (InvalidOperationException e) { Console.WriteLine("Отказано: " + e.Message); } catch (ArgumentException e) { Console.WriteLine("Отказано: " + e.Message); }}def handle(self, command): verb, _, rest = command.partition(" ") try: if verb == "строй": self._build(self._create_building(rest)) elif verb == "надгради": self._upgrade(rest) elif verb == "скаут": self._build_scout() elif verb == "проучи": self._explore() elif verb == "чакай": self._wait_turns(1) else: print(f"Непозната команда: {command}") except (NotEnoughResourcesError, RuntimeError, ValueError) as e: print(f"Отказано: {e}")handle(command) { const space = command.indexOf(" "); const verb = space === -1 ? command : command.slice(0, space); const rest = space === -1 ? "" : command.slice(space + 1); try { if (verb === "строй") { this.#build(this.#createBuilding(rest)); } else if (verb === "надгради") { this.#upgrade(rest); } else if (verb === "скаут") { this.#buildScout(); } else if (verb === "проучи") { this.#explore(); } else if (verb === "чакай") { this.#waitTurns(1); } else { console.log(`Непозната команда: ${command}`); } } catch (e) { console.log(`Отказано: ${e.message}`); }}partition(" ") разделя текста при първия интервал и връща три части: преди интервала, самия интервал и след него. Ако интервал няма, третата част е празна. Средната не ни трябва, затова е _. Всички откази се хващат с едно except и кортеж от видовете.
Всяка сграда носи 10 точки, надграждането 10 по новото ниво, скаутът 15, а всяка открита планета 5. Играта свършва при 80 точки или 40 хода. Цикълът на програмата:
Game game = new Game(home, others, 80, 40);Scanner in = new Scanner(System.in);while (!game.isOver() && in.hasNextLine()) { String command = in.nextLine(); System.out.println("> " + command); game.handle(command); System.out.println(" " + game.status());}System.out.println("Край: " + game.getPoints() + " точки за " + game.getTurn() + " хода.");> строй лабораторияОтказано: Лаборатория иска първо Кристална мина ход 0, точки 0 | метал 40.0, газ 0.0, кристал 15.0, уран 0.0> строй метална минаМетална мина е готова. ход 1, точки 10 | метал 35.0, газ 0.5, кристал 13.2, уран 0.1> строй кристална минаКристална мина е готова. ход 2, точки 20 | метал 29.0, газ 1.0, кристал 12.4, уран 0.2> строй лабораторияЛаборатория е готова. ход 4, точки 30 | метал 21.0, газ 2.0, кристал 9.2, уран 0.4> надгради метална минаМетална мина е на ниво 2. ход 5, точки 50 | метал 11.0, газ 2.5, кристал 5.6, уран 0.5> строй корабостроителницаОтказано: за Корабостроителница трябват още 4.0 метал ход 5, точки 50 | метал 11.0, газ 2.5, кристал 5.6, уран 0.5> чакай ход 6, точки 50 | метал 15.0, газ 3.0, кристал 6.0, уран 0.6> строй корабостроителницаКорабостроителница е готова. ход 9, точки 60 | метал 12.0, газ 2.5, кристал 3.2, уран 0.9> скаутОтказано: за скаут трябват още 0.1 уран ход 9, точки 60 | метал 12.0, газ 2.5, кристал 3.2, уран 0.9> чакай ход 10, точки 60 | метал 16.0, газ 3.0, кристал 3.6, уран 1.0> скаутСкаут-1 е готов. ход 12, точки 75 | метал 14.0, газ 2.0, кристал 1.4, уран 0.2> проучиСкаут-1 откри Кобалт: метал 500.0, газ 0.0, кристал 40.0, уран 0.0Скаут-1 откри Хелия: метал 0.0, газ 300.0, кристал 0.0, уран 20.0Открити планети: 2 ход 13, точки 85 | метал 18.0, газ 2.5, кристал 1.8, уран 0.3Край: 85 точки за 13 хода.Game game = new Game(home, others, 80, 40);while (!game.IsOver()){ string command = Console.ReadLine(); if (command == null) { break; } Console.WriteLine("> " + command); game.Handle(command); Console.WriteLine(" " + game.Status());}Console.WriteLine("Край: " + game.Points + " точки за " + game.Turn + " хода.");> строй лабораторияОтказано: Лаборатория иска първо Кристална мина ход 0, точки 0 | метал 40.0, газ 0.0, кристал 15.0, уран 0.0> строй метална минаМетална мина е готова. ход 1, точки 10 | метал 35.0, газ 0.5, кристал 13.2, уран 0.1> строй кристална минаКристална мина е готова. ход 2, точки 20 | метал 29.0, газ 1.0, кристал 12.4, уран 0.2> строй лабораторияЛаборатория е готова. ход 4, точки 30 | метал 21.0, газ 2.0, кристал 9.2, уран 0.4> надгради метална минаМетална мина е на ниво 2. ход 5, точки 50 | метал 11.0, газ 2.5, кристал 5.6, уран 0.5> строй корабостроителницаОтказано: за Корабостроителница трябват още 4.0 метал ход 5, точки 50 | метал 11.0, газ 2.5, кристал 5.6, уран 0.5> чакай ход 6, точки 50 | метал 15.0, газ 3.0, кристал 6.0, уран 0.6> строй корабостроителницаКорабостроителница е готова. ход 9, точки 60 | метал 12.0, газ 2.5, кристал 3.2, уран 0.9> скаутОтказано: за скаут трябват още 0.1 уран ход 9, точки 60 | метал 12.0, газ 2.5, кристал 3.2, уран 0.9> чакай ход 10, точки 60 | метал 16.0, газ 3.0, кристал 3.6, уран 1.0> скаутСкаут-1 е готов. ход 12, точки 75 | метал 14.0, газ 2.0, кристал 1.4, уран 0.2> проучиСкаут-1 откри Кобалт: метал 500.0, газ 0.0, кристал 40.0, уран 0.0Скаут-1 откри Хелия: метал 0.0, газ 300.0, кристал 0.0, уран 20.0Открити планети: 2 ход 13, точки 85 | метал 18.0, газ 2.5, кристал 1.8, уран 0.3Край: 85 точки за 13 хода.game = Game(home, others, 80, 40)while not game.is_over(): try: command = input() except EOFError: break print(f"> {command}") game.handle(command) print(f" {game.status()}")print(f"Край: {game.points} точки за {game.turn} хода.")> строй лабораторияОтказано: Лаборатория иска първо Кристална мина ход 0, точки 0 | метал 40.0, газ 0.0, кристал 15.0, уран 0.0> строй метална минаМетална мина е готова. ход 1, точки 10 | метал 35.0, газ 0.5, кристал 13.2, уран 0.1> строй кристална минаКристална мина е готова. ход 2, точки 20 | метал 29.0, газ 1.0, кристал 12.4, уран 0.2> строй лабораторияЛаборатория е готова. ход 4, точки 30 | метал 21.0, газ 2.0, кристал 9.2, уран 0.4> надгради метална минаМетална мина е на ниво 2. ход 5, точки 50 | метал 11.0, газ 2.5, кристал 5.6, уран 0.5> строй корабостроителницаОтказано: за Корабостроителница трябват още 4.0 метал ход 5, точки 50 | метал 11.0, газ 2.5, кристал 5.6, уран 0.5> чакай ход 6, точки 50 | метал 15.0, газ 3.0, кристал 6.0, уран 0.6> строй корабостроителницаКорабостроителница е готова. ход 9, точки 60 | метал 12.0, газ 2.5, кристал 3.2, уран 0.9> скаутОтказано: за скаут трябват още 0.1 уран ход 9, точки 60 | метал 12.0, газ 2.5, кристал 3.2, уран 0.9> чакай ход 10, точки 60 | метал 16.0, газ 3.0, кристал 3.6, уран 1.0> скаутСкаут-1 е готов. ход 12, точки 75 | метал 14.0, газ 2.0, кристал 1.4, уран 0.2> проучиСкаут-1 откри Кобалт: метал 500.0, газ 0.0, кристал 40.0, уран 0.0Скаут-1 откри Хелия: метал 0.0, газ 300.0, кристал 0.0, уран 20.0Открити планети: 2 ход 13, точки 85 | метал 18.0, газ 2.5, кристал 1.8, уран 0.3Край: 85 точки за 13 хода.const game = new Game(home, others, 80, 40);while (!game.isOver()) { const command = await ask(""); if (command === undefined) { break; } console.log(`> ${command}`); game.handle(command); console.log(` ${game.status()}`);}console.log(`Край: ${game.points} точки за ${game.turn} хода.`);rl.close();> строй лабораторияОтказано: Лаборатория иска първо Кристална мина ход 0, точки 0 | метал 40.0, газ 0.0, кристал 15.0, уран 0.0> строй метална минаМетална мина е готова. ход 1, точки 10 | метал 35.0, газ 0.5, кристал 13.2, уран 0.1> строй кристална минаКристална мина е готова. ход 2, точки 20 | метал 29.0, газ 1.0, кристал 12.4, уран 0.2> строй лабораторияЛаборатория е готова. ход 4, точки 30 | метал 21.0, газ 2.0, кристал 9.2, уран 0.4> надгради метална минаМетална мина е на ниво 2. ход 5, точки 50 | метал 11.0, газ 2.5, кристал 5.6, уран 0.5> строй корабостроителницаОтказано: за Корабостроителница трябват още 4.0 метал ход 5, точки 50 | метал 11.0, газ 2.5, кристал 5.6, уран 0.5> чакай ход 6, точки 50 | метал 15.0, газ 3.0, кристал 6.0, уран 0.6> строй корабостроителницаКорабостроителница е готова. ход 9, точки 60 | метал 12.0, газ 2.5, кристал 3.2, уран 0.9> скаутОтказано: за скаут трябват още 0.1 уран ход 9, точки 60 | метал 12.0, газ 2.5, кристал 3.2, уран 0.9> чакай ход 10, точки 60 | метал 16.0, газ 3.0, кристал 3.6, уран 1.0> скаутСкаут-1 е готов. ход 12, точки 75 | метал 14.0, газ 2.0, кристал 1.4, уран 0.2> проучиСкаут-1 откри Кобалт: метал 500.0, газ 0.0, кристал 40.0, уран 0.0Скаут-1 откри Хелия: метал 0.0, газ 300.0, кристал 0.0, уран 20.0Открити планети: 2 ход 13, точки 85 | метал 18.0, газ 2.5, кристал 1.8, уран 0.3Край: 85 точки за 13 хода.input() хвърля EOFError, когато входът свърши. Тогава break спира цикъла.С този вход:
строй лабораториястрой метална минастрой кристална минастрой лабораториянадгради метална минастрой корабостроителницачакайстрой корабостроителницаскаутчакайскаутпроучиПо-точно, за любопитните
- Отказите не местят хода: сградата не е започната, затова играчът може веднага да опита друго.
- Проучванията (технологиите от правилата) са в старото задание само като план: клас
Researchс име, ниво и цена, с методи за проучване и вдигане на ниво. Те и конфликтите (пирати, атака сweapon_power) са следващата стъпка, ако искаш да продължиш сам.
Упражнение
Колонията расте
Реши задачите в редактора си, с класовете от урока.
1. 100 метал възможно най-бързо. Нова Терра започва с 40 метал и 15 кристал. Прочети плана от входа, команда на ред, изпълни го и после чакай, докато металът стигне 100. Отпечатай на кой ход е станало. С този план е на ход 21. Намери по-бърз.
строй метална минанадгради метална минаМетална мина е готова.Метална мина е на ниво 2.100 метал на ход 21: ход 21, точки 30 | метал 101.0, газ 10.5, кристал 13.2, уран 2.12. Товарен кораб. Напиши CargoShip, наследник на SpaceShip, с товароносимост 50. harvest_resource(planet) товари от метала на планетата колкото се побере, но не повече от наличното. unload(planet) го разтоварва. На Кобалт има 120 метал. Корабът Мулето прави три курса Нова Терра – Кобалт – Нова Терра.
Мулето пристигна при КобалтМулето натовари 50.0 метал от КобалтМулето пристигна при Нова ТерраМулето разтовари 50.0 метал на Нова ТерраМулето пристигна при КобалтМулето натовари 50.0 метал от КобалтМулето пристигна при Нова ТерраМулето разтовари 50.0 метал на Нова ТерраМулето пристигна при КобалтМулето натовари 20.0 метал от КобалтМулето пристигна при Нова ТерраМулето разтовари 20.0 метал на Нова ТерраНова Терра: метал 120.0, газ 0.0, кристал 0.0, уран 0.0Кобалт: метал 0.0, газ 0.0, кристал 0.0, уран 0.03. Симулация на 10 хода. Без играч: играта има поредица сгради (Метална мина, Кристална мина, Лаборатория, Газова рафинерия). На всяка стъпка опитва да построи следващата. Ако не става, чака един ход. След всяка стъпка печата състоянието, докато ходовете станат 10.
Метална мина е готова.ход 1, точки 10 | метал 35.0, газ 0.5, кристал 13.2, уран 0.1Кристална мина е готова.ход 2, точки 20 | метал 29.0, газ 1.0, кристал 12.4, уран 0.2Лаборатория е готова.ход 4, точки 30 | метал 21.0, газ 2.0, кристал 9.2, уран 0.4Газова рафинерия е готова.ход 6, точки 40 | метал 15.0, газ 3.0, кристал 7.0, уран 0.6ход 7, точки 40 | метал 17.0, газ 4.0, кристал 7.4, уран 0.7ход 8, точки 40 | метал 19.0, газ 5.0, кристал 7.8, уран 0.8ход 9, точки 40 | метал 21.0, газ 6.0, кристал 8.2, уран 0.9ход 10, точки 40 | метал 23.0, газ 7.0, кристал 8.6, уран 1.0Още задачи
- Уранова мина. Добави
UraniumMine, която зависи от Газовата рафинерия. - Точки за ресурси. Давай по 1 точка за всеки 50 добити метала.
- Пирати. На всеки 10 хода пирати отнемат 20% от метала, ако на планетата няма кораб със сила на оръжията поне 10.
Тест