Epic Cosmos 2: Ressourcen, Gebäude, Schiffe
Kurz gesagt: Ein rundenbasiertes Spiel: Ressourcen werden in jedem Zug gefördert, Gebäude kosten Ressourcen, Minen verdoppeln die Förderung, und fehlende Ressourcen sind eine eigene Exception.
Problem
Die Welt ist bereit, aber nichts passiert
Aus der letzten Lektion hast du Sterne, Planeten und Satelliten. Aber das Spiel läuft noch nicht: Es gibt nichts, womit du bauen kannst, und nichts, worauf du warten kannst.
Die Wirtschaft von Epic Cosmos:
- Jeder Planet hat vier Ressourcen. In jedem Zug werden sie gefördert, jede mit ihrer eigenen Rate: Metall 1,0, Gas 0,5, Kristall 0,2, Uran 0,1 pro Zug.
- Gebäude kosten Ressourcen und Zeit. Die Minen und die Raffinerie verdoppeln die Rate ihrer Ressource mit jeder Stufe. Das Forschungslabor und die Werft schalten andere Dinge frei.
- Schiffe fliegen. Der Späher erkundet die Umgebung und meldet, was jeder Planet hat.
- Das Spiel läuft in Zügen. Punkte kommen von Gebäuden, Schiffen und Entdeckungen. Das Ende kommt bei einer bestimmten Zahl von Punkten oder Zügen.
Wenn die Ressourcen nicht reichen, muss das Bauen ablehnen und sagen, was fehlt. Das ist eine Aufgabe für eine eigene Exception.
Die Idee
Die Ressource
Eine Ressource hat eine Menge (amount) und eine Förderrate (harvestRate). In jedem Zug addiert Harvest() die Rate zur Menge. Eine Mine ruft Boost() auf, und die Rate verdoppelt sich.
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} muss getName() überschreiben`); }
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; }}Die vier Arten unterscheiden sich nur in Name und Rate:
class Metal extends Resource { public Metal(double amount) { super(amount, 1.0); }
@Override public String getName() { return "Metall"; }}class Metal : Resource{ public Metal(double amount) : base(amount, 1.0) { }
public override string GetName() { return "Metall"; }}class Metal(Resource): def __init__(self, amount): super().__init__(amount, 1.0)
def get_name(self): return "Metall"class Metal extends Resource { constructor(amount) { super(amount, 1.0); }
getName() { return "Metall"; }}Gas hat 0,5, Crystal 0,2, Uranium 0,1.
Genauer, für Neugierige
double kann 0,1 nicht genau speichern. Addierst du 0,1 zehnmal, bekommst du 0,9999999999999999 statt 1,0. Dann würde die Prüfung „hast du 1 Uran“ „nein“ sagen. Deshalb rundet Round jede neue Menge auf Zehntel. In echten Spielen sind die Mengen oft ganze Zahlen, zum Beispiel in Zehnteln einer Einheit, genau um solche Überraschungen zu vermeiden.
Sieh es dir an
Die Ressourcen des Planeten
Planet aus der letzten Lektion bekommt vier Ressourcen und eine Methode, die sie alle auf einmal fördert:
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("Metall %.1f, Gas %.1f, Kristall %.1f, Uran %.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 $"Metall {Metal.Amount:F1}, Gas {Gas.Amount:F1}, Kristall {Crystal.Amount:F1}, Uran {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 = [] # die Gebäude kommen später in der Lektion
@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"Metall {self._metal.amount:.1f}, Gas {self._gas.amount:.1f}, Kristall {self._crystal.amount:.1f}, Uran {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 = []; // die Gebäude kommen später in der Lektion
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 `Metall ${this.#metal.amount.toFixed(1)}, Gas ${this.#gas.amount.toFixed(1)}, Kristall ${this.#crystal.amount.toFixed(1)}, Uran ${this.#uranium.amount.toFixed(1)}`;}
printResources() { console.log(`${this.name}: ${this.resourcesText()}`);}Fünf Züge Förderung, dann Metall verdoppeln und noch ein Zug:
Planet home = new Planet("Nova Terra", 100, 0, 0, 1);for (int turn = 1; turn <= 5; turn++) { home.harvestAll();}home.printResources();home.getMetal().boost();home.harvestAll();home.printResources();Nova Terra: Metall 5.0, Gas 2.5, Kristall 1.0, Uran 0.5Nova Terra: Metall 7.0, Gas 3.0, Kristall 1.2, Uran 0.6Planet home = new Planet("Nova Terra", 100, 0, 0, 1);for (int turn = 1; turn <= 5; turn++){ home.HarvestAll();}home.PrintResources();home.Metal.Boost();home.HarvestAll();home.PrintResources();Nova Terra: Metall 5.0, Gas 2.5, Kristall 1.0, Uran 0.5Nova Terra: Metall 7.0, Gas 3.0, Kristall 1.2, Uran 0.6home = Planet("Nova Terra", 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()Nova Terra: Metall 5.0, Gas 2.5, Kristall 1.0, Uran 0.5Nova Terra: Metall 7.0, Gas 3.0, Kristall 1.2, Uran 0.6const home = new Planet("Nova Terra", 100, 0, 0, 1);for (let turn = 1; turn <= 5; turn++) { home.harvestAll();}home.printResources();home.metal.boost();home.harvestAll();home.printResources();Nova Terra: Metall 5.0, Gas 2.5, Kristall 1.0, Uran 0.5Nova Terra: Metall 7.0, Gas 3.0, Kristall 1.2, Uran 0.6Probier es aus
Wie viel Gas?
Was gibt das Programm aus?
Resource gas = new Gas(0);gas.Harvest();gas.Boost();gas.Harvest();gas.Boost();gas.Harvest();Console.WriteLine(gas.Amount + " " + gas.GetName() + ", Rate " + gas.HarvestRate);Die erste Förderung ist 0,5. Dann wird die Rate 1,0, und die zweite Förderung ist 1,0, zusammen 1,5. Dann wird die Rate 2,0, und die dritte Förderung ist 2,0, zusammen 3,5. C# gibt 2 aus, nicht 2.0: Ohne Format wird eine ganze Zahl in einem double ohne Nachkommateil angezeigt.
Die Idee
Bauen kostet etwas
Wenn die Ressourcen nicht reichen, muss das Bauen ablehnen. Der Aufrufer will wissen, was und wie viel fehlt, deshalb ist die Exception eine eigene und trägt die Zahl:
class NotEnoughResourcesException extends Exception { private double missing;
public NotEnoughResourcesException(String purpose, String resource, double missing) { super(String.format("Für %s fehlen noch %.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($"Für {purpose} fehlen noch {missing:F1} {resource}") { Missing = missing; }}class NotEnoughResourcesError(Exception): def __init__(self, purpose, resource, missing): super().__init__(f"Für {purpose} fehlen noch {missing:.1f} {resource}") self.missing = missingclass NotEnoughResourcesError extends Error { constructor(purpose, resource, missing) { super(`Für ${purpose} fehlen noch ${missing.toFixed(1)} ${resource}`); this.name = "NotEnoughResourcesError"; this.missing = missing; }}Das Bezahlen steht in Planet. Zuerst prüft es die vier Ressourcen, dann gibt es aus:
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); }}Probier es aus
Finde den Fehler
Die Kolonie hat 20 Metall und keinen Kristall. Das Forschungslabor kostet 12 Metall und 5 Kristall. Das Bauen wird abgelehnt, aber das Metall ist auf 8 gesunken. In welcher Zeile liegt das Problem?
Wähle die Zeile mit dem Fehler.
Das Metall wird gleich nach seiner eigenen Prüfung ausgegeben. Dann wirft die Kristallprüfung, aber das Metall ist schon ausgegeben, und das Labor ist nicht gebaut. Die Exception stoppt die Methode, macht aber nicht rückgängig, was vor ihr geschehen ist. Zuerst alle Prüfungen, dann das Ausgeben.
Die Idee
Das Gebäude
Jedes Gebäude hat einen Namen, eine Stufe, eine Bauzeit und einen Preis in den vier Ressourcen. Bauen und Ausbauen sind für alle Gebäude gleich, deshalb stehen sie in der abstrakten Klasse BaseBuilding:
public String getRequiredBuilding() { return null;}
public void construct(Planet target) throws NotEnoughResourcesException { if (target.findBuilding(name) != null) { throw new IllegalStateException(name + " ist schon gebaut"); } String required = getRequiredBuilding(); if (required != null && target.findBuilding(required) == null) { throw new IllegalStateException(name + " setzt " + required + " voraus"); } 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 + " ist noch nicht gebaut"); } double factor = Math.pow(2, level); planet.spend(name + " Stufe " + (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 + " ist schon gebaut"); } string required = GetRequiredBuilding(); if (required != null && target.FindBuilding(required) == null) { throw new InvalidOperationException(Name + " setzt " + required + " voraus"); } target.Spend(Name, MetalRequired, CrystalRequired, GasRequired, UraniumRequired); Planet = target; Level = 1; target.AddBuilding(this);}
public void Upgrade(){ if (Planet == null) { throw new InvalidOperationException(Name + " ist noch nicht gebaut"); } double factor = Math.Pow(2, Level); Planet.Spend(Name + " Stufe " + (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} ist schon gebaut") required = self.get_required_building() if required is not None and target.find_building(required) is None: raise RuntimeError(f"{self._name} setzt {required} voraus") 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} ist noch nicht gebaut") factor = 2 ** self._level self._planet.spend(f"{self._name} Stufe {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} ist schon gebaut`); } const required = this.getRequiredBuilding(); if (required !== null && target.findBuilding(required) === null) { throw new Error(`${this.#name} setzt ${required} voraus`); } 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} ist noch nicht gebaut`); } const factor = 2 ** this.#level; this.#planet.spend(`${this.#name} Stufe ${this.#level + 1}`, this.#metalRequired * factor, this.#crystalRequired * factor, this.#gasRequired * factor, this.#uraniumRequired * factor); this.#level++;}
applyEffect() { throw new Error(`${this.constructor.name} muss applyEffect() überschreiben`);}Constructlehnt ab, wenn das Gebäude schon da ist oder wenn das Gebäude fehlt, von dem es abhängt. Diese Ablehnungen nutzen die vorhandene Exception für den falschen Moment. Danach bezahlt es und trägt das Gebäude im Planeten ein.Upgradekostet mit jeder Stufe doppelt so viel: Stufe 2 kostet das Zweifache des Preises, Stufe 3 das Vierfache.ApplyEffectist abstrakt: Jedes Gebäude weiß selbst, was es tut, wenn es fertig ist.
Genauer, für Neugierige
GetRequiredBuilding() gibt null zurück: Standardmäßig hängt ein Gebäude von nichts ab. Eine Unterklasse, die von etwas abhängt, überschreibt es.
Die Idee
Minen und Abhängigkeiten
Die Minen unterscheiden sich nur darin, welche Ressource sie beschleunigen. Das Gemeinsame steht in 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} muss _resourceOf() überschreiben`); }
applyEffect() { this._resourceOf(this.planet).boost(); }}class MetalMine extends Mine { public MetalMine() { super("Metallmine", 1, 6, 2); }
@Override protected Resource resourceOf(Planet planet) { return planet.getMetal(); }}class MetalMine : Mine{ public MetalMine() : base("Metallmine", 1, 6, 2) { }
protected override Resource ResourceOf(Planet planet) { return planet.Metal; }}class MetalMine(Mine): def __init__(self): super().__init__("Metallmine", 1, 6, 2)
def _resource_of(self, planet): return planet.metalclass MetalMine extends Mine { constructor() { super("Metallmine", 1, 6, 2); }
_resourceOf(planet) { return planet.metal; }}Metallmine und Kristallmine kannst du sofort bauen. Das Forschungslabor setzt eine Kristallmine voraus, und die Gasraffinerie und die Werft setzen ein Forschungslabor voraus:
class ResearchLab extends BaseBuilding { public ResearchLab() { super("Forschungslabor", 0, 2, 12, 4, 0, 0); }
@Override public String getRequiredBuilding() { return "Kristallmine"; }
@Override public void applyEffect() { // Es schaltet andere Gebäude frei. Die Ressourcen ändern sich nicht. }}class ResearchLab : BaseBuilding{ public ResearchLab() : base("Forschungslabor", 0, 2, 12, 4, 0, 0) { }
public override string GetRequiredBuilding() { return "Kristallmine"; }
public override void ApplyEffect() { // Es schaltet andere Gebäude frei. Die Ressourcen ändern sich nicht. }}class ResearchLab(BaseBuilding): def __init__(self): super().__init__("Forschungslabor", 0, 2, 12, 4, 0, 0)
def get_required_building(self): return "Kristallmine"
def apply_effect(self): # Es schaltet andere Gebäude frei. Die Ressourcen ändern sich nicht. passclass ResearchLab extends BaseBuilding { constructor() { super("Forschungslabor", 0, 2, 12, 4, 0, 0); }
getRequiredBuilding() { return "Kristallmine"; }
applyEffect() { // Es schaltet andere Gebäude frei. Die Ressourcen ändern sich nicht. }}Ein Plan aus drei Gebäuden mit 10 Metall und 3 Kristall. Das Forschungslabor wird wegen der Abhängigkeit abgelehnt, die Kristallmine wegen des Metalls:
Planet home = new Planet("Nova Terra", 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() + " ist fertig."); } catch (NotEnoughResourcesException e) { System.out.println("Abgelehnt: " + e.getMessage()); } catch (IllegalStateException e) { System.out.println("Abgelehnt: " + e.getMessage()); }}home.printResources();System.out.println("Förderrate Metall: " + home.getMetal().getHarvestRate());Abgelehnt: Forschungslabor setzt Kristallmine vorausMetallmine ist fertig.Abgelehnt: Für Kristallmine fehlen noch 4.0 MetallNova Terra: Metall 4.0, Gas 0.0, Kristall 1.0, Uran 0.0Förderrate Metall: 2.0Planet home = new Planet("Nova Terra", 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 + " ist fertig."); } catch (NotEnoughResourcesException e) { Console.WriteLine("Abgelehnt: " + e.Message); } catch (InvalidOperationException e) { Console.WriteLine("Abgelehnt: " + e.Message); }}home.PrintResources();Console.WriteLine("Förderrate Metall: " + home.Metal.HarvestRate);Abgelehnt: Forschungslabor setzt Kristallmine vorausMetallmine ist fertig.Abgelehnt: Für Kristallmine fehlen noch 4.0 MetallNova Terra: Metall 4.0, Gas 0.0, Kristall 1.0, Uran 0.0Förderrate Metall: 2home = Planet("Nova Terra", 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} ist fertig.") except NotEnoughResourcesError as e: print(f"Abgelehnt: {e}") except RuntimeError as e: print(f"Abgelehnt: {e}")home.print_resources()print(f"Förderrate Metall: {home.metal.harvest_rate}")Abgelehnt: Forschungslabor setzt Kristallmine vorausMetallmine ist fertig.Abgelehnt: Für Kristallmine fehlen noch 4.0 MetallNova Terra: Metall 4.0, Gas 0.0, Kristall 1.0, Uran 0.0Förderrate Metall: 2.0const home = new Planet("Nova Terra", 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} ist fertig.`); } catch (e) { console.log(`Abgelehnt: ${e.message}`); }}home.printResources();console.log(`Förderrate Metall: ${home.metal.harvestRate}`);Abgelehnt: Forschungslabor setzt Kristallmine vorausMetallmine ist fertig.Abgelehnt: Für Kristallmine fehlen noch 4.0 MetallNova Terra: Metall 4.0, Gas 0.0, Kristall 1.0, Uran 0.0Förderrate Metall: 2Planet home = new Planet("Nova Terra", 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 + " ist fertig."); } catch (NotEnoughResourcesException e) { Console.WriteLine("Abgelehnt: " + e.Message); } catch (InvalidOperationException e) { Console.WriteLine("Abgelehnt: " + e.Message); }}home.PrintResources();Console.WriteLine("Förderrate Metall: " + home.Metal.HarvestRate);Schritt für Schritt, in Worten
- Nova Terra hat 10 Metall und 3 Kristall. Metall wird mit 1.0 pro Zug gefördert.
- Der Plan ist ein Array aus 3 Gebäuden. Jedes hat einen Preis in Metall und Kristall.
- Forschungslabor setzt Kristallmine voraus, doch das fehlt noch. Construct() wirft InvalidOperationException, bevor etwas ausgegeben wird.
- Metallmine kostet 6 Metall und 2 Kristall. Das reicht: 4 Metall und 1 Kristall bleiben übrig. ApplyEffect() verdoppelt die Förderrate von Metall.
- Kristallmine braucht 8 Metall, vorhanden sind 4. Die Prüfung wirft NotEnoughResourcesException, und nichts wird ausgegeben.
- Die Ablehnungen haben nichts ausgegeben. Nur der Preis des gebauten Gebäudes ist weg, und die Förderrate von Metall ist 2.
Die Idee
Schiffe und der Späher
SpaceShip aus der letzten Lektion bekommt einen Typ, eine Geschwindigkeit, eine Ladekapazität und eine Waffenstärke. Es startet auf dem Heimatplaneten und fliegt mit FlyTo, wie zuvor. Der Späher ist ein Schiff mit Sensoren: Explore prüft, welche Planeten in seiner Reichweite liegen, und meldet ihre Ressourcen.
class Scout extends SpaceShip { private double sensorRange;
public Scout(String name, Planet home) { super(name, "Späher", 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() + " fand " + planet.getName() + ": " + planet.resourcesText()); }}class Scout : SpaceShip{ private double sensorRange;
public Scout(string name, Planet home) : base(name, "Späher", 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 + " fand " + planet.Name + ": " + planet.ResourcesText()); }}class Scout(SpaceShip): def __init__(self, name, home): super().__init__(name, "Späher", 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} fand {planet.name}: {planet.resources_text()}")class Scout extends SpaceShip { #sensorRange;
constructor(name, home) { super(name, "Späher", 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} fand ${planet.name}: ${planet.resourcesText()}`); }}Die Reichweite wird mit dem Abstand aus Aufgabe 2 der letzten Lektion gemessen. Die Methode gibt zurück, wie viele Planeten sie gefunden hat, damit das Spiel Punkte dafür geben kann.
Sieh es dir an
Das ganze Spiel
Game hält den Heimatplaneten, die anderen Planeten, den Zug und die Punkte. Die Befehle kommen vom Spieler, einer pro Zeile. Gebäude brauchen Züge, und in jedem Zug fördert der Planet:
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("bauen")) { build(createBuilding(rest)); } else if (verb.equals("ausbauen")) { upgrade(rest); } else if (verb.equals("späher")) { buildScout(); } else if (verb.equals("erkunden")) { explore(); } else if (verb.equals("warten")) { waitTurns(1); } else { System.out.println("Unbekannter Befehl: " + command); } } catch (NotEnoughResourcesException e) { System.out.println("Abgelehnt: " + e.getMessage()); } catch (IllegalStateException e) { System.out.println("Abgelehnt: " + e.getMessage()); } catch (IllegalArgumentException e) { System.out.println("Abgelehnt: " + 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 == "bauen") { Build(CreateBuilding(rest)); } else if (verb == "ausbauen") { Upgrade(rest); } else if (verb == "späher") { BuildScout(); } else if (verb == "erkunden") { Explore(); } else if (verb == "warten") { WaitTurns(1); } else { Console.WriteLine("Unbekannter Befehl: " + command); } } catch (NotEnoughResourcesException e) { Console.WriteLine("Abgelehnt: " + e.Message); } catch (InvalidOperationException e) { Console.WriteLine("Abgelehnt: " + e.Message); } catch (ArgumentException e) { Console.WriteLine("Abgelehnt: " + e.Message); }}def handle(self, command): verb, _, rest = command.partition(" ") try: if verb == "bauen": self._build(self._create_building(rest)) elif verb == "ausbauen": self._upgrade(rest) elif verb == "späher": self._build_scout() elif verb == "erkunden": self._explore() elif verb == "warten": self._wait_turns(1) else: print(f"Unbekannter Befehl: {command}") except (NotEnoughResourcesError, RuntimeError, ValueError) as e: print(f"Abgelehnt: {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 === "bauen") { this.#build(this.#createBuilding(rest)); } else if (verb === "ausbauen") { this.#upgrade(rest); } else if (verb === "späher") { this.#buildScout(); } else if (verb === "erkunden") { this.#explore(); } else if (verb === "warten") { this.#waitTurns(1); } else { console.log(`Unbekannter Befehl: ${command}`); } } catch (e) { console.log(`Abgelehnt: ${e.message}`); }}Jedes Gebäude bringt 10 Punkte, ein Ausbau das Zehnfache der neuen Stufe, der Späher 15 und jeder gefundene Planet 5. Das Spiel endet bei 80 Punkten oder 40 Zügen. Die Schleife in Main:
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("Ende: " + game.getPoints() + " Punkte in " + game.getTurn() + " Zügen.");> bauen forschungslaborAbgelehnt: Forschungslabor setzt Kristallmine voraus Zug 0, Punkte 0 | Metall 40.0, Gas 0.0, Kristall 15.0, Uran 0.0> bauen metallmineMetallmine ist fertig. Zug 1, Punkte 10 | Metall 35.0, Gas 0.5, Kristall 13.2, Uran 0.1> bauen kristallmineKristallmine ist fertig. Zug 2, Punkte 20 | Metall 29.0, Gas 1.0, Kristall 12.4, Uran 0.2> bauen forschungslaborForschungslabor ist fertig. Zug 4, Punkte 30 | Metall 21.0, Gas 2.0, Kristall 9.2, Uran 0.4> ausbauen metallmineMetallmine ist auf Stufe 2. Zug 5, Punkte 50 | Metall 11.0, Gas 2.5, Kristall 5.6, Uran 0.5> bauen werftAbgelehnt: Für Werft fehlen noch 4.0 Metall Zug 5, Punkte 50 | Metall 11.0, Gas 2.5, Kristall 5.6, Uran 0.5> warten Zug 6, Punkte 50 | Metall 15.0, Gas 3.0, Kristall 6.0, Uran 0.6> bauen werftWerft ist fertig. Zug 9, Punkte 60 | Metall 12.0, Gas 2.5, Kristall 3.2, Uran 0.9> späherAbgelehnt: Für Späher fehlen noch 0.1 Uran Zug 9, Punkte 60 | Metall 12.0, Gas 2.5, Kristall 3.2, Uran 0.9> warten Zug 10, Punkte 60 | Metall 16.0, Gas 3.0, Kristall 3.6, Uran 1.0> späherSpäher-1 ist fertig. Zug 12, Punkte 75 | Metall 14.0, Gas 2.0, Kristall 1.4, Uran 0.2> erkundenSpäher-1 fand Kobalt: Metall 500.0, Gas 0.0, Kristall 40.0, Uran 0.0Späher-1 fand Helia: Metall 0.0, Gas 300.0, Kristall 0.0, Uran 20.0Gefundene Planeten: 2 Zug 13, Punkte 85 | Metall 18.0, Gas 2.5, Kristall 1.8, Uran 0.3Ende: 85 Punkte in 13 Zügen.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("Ende: " + game.Points + " Punkte in " + game.Turn + " Zügen.");> bauen forschungslaborAbgelehnt: Forschungslabor setzt Kristallmine voraus Zug 0, Punkte 0 | Metall 40.0, Gas 0.0, Kristall 15.0, Uran 0.0> bauen metallmineMetallmine ist fertig. Zug 1, Punkte 10 | Metall 35.0, Gas 0.5, Kristall 13.2, Uran 0.1> bauen kristallmineKristallmine ist fertig. Zug 2, Punkte 20 | Metall 29.0, Gas 1.0, Kristall 12.4, Uran 0.2> bauen forschungslaborForschungslabor ist fertig. Zug 4, Punkte 30 | Metall 21.0, Gas 2.0, Kristall 9.2, Uran 0.4> ausbauen metallmineMetallmine ist auf Stufe 2. Zug 5, Punkte 50 | Metall 11.0, Gas 2.5, Kristall 5.6, Uran 0.5> bauen werftAbgelehnt: Für Werft fehlen noch 4.0 Metall Zug 5, Punkte 50 | Metall 11.0, Gas 2.5, Kristall 5.6, Uran 0.5> warten Zug 6, Punkte 50 | Metall 15.0, Gas 3.0, Kristall 6.0, Uran 0.6> bauen werftWerft ist fertig. Zug 9, Punkte 60 | Metall 12.0, Gas 2.5, Kristall 3.2, Uran 0.9> späherAbgelehnt: Für Späher fehlen noch 0.1 Uran Zug 9, Punkte 60 | Metall 12.0, Gas 2.5, Kristall 3.2, Uran 0.9> warten Zug 10, Punkte 60 | Metall 16.0, Gas 3.0, Kristall 3.6, Uran 1.0> späherSpäher-1 ist fertig. Zug 12, Punkte 75 | Metall 14.0, Gas 2.0, Kristall 1.4, Uran 0.2> erkundenSpäher-1 fand Kobalt: Metall 500.0, Gas 0.0, Kristall 40.0, Uran 0.0Späher-1 fand Helia: Metall 0.0, Gas 300.0, Kristall 0.0, Uran 20.0Gefundene Planeten: 2 Zug 13, Punkte 85 | Metall 18.0, Gas 2.5, Kristall 1.8, Uran 0.3Ende: 85 Punkte in 13 Zügen.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"Ende: {game.points} Punkte in {game.turn} Zügen.")> bauen forschungslaborAbgelehnt: Forschungslabor setzt Kristallmine voraus Zug 0, Punkte 0 | Metall 40.0, Gas 0.0, Kristall 15.0, Uran 0.0> bauen metallmineMetallmine ist fertig. Zug 1, Punkte 10 | Metall 35.0, Gas 0.5, Kristall 13.2, Uran 0.1> bauen kristallmineKristallmine ist fertig. Zug 2, Punkte 20 | Metall 29.0, Gas 1.0, Kristall 12.4, Uran 0.2> bauen forschungslaborForschungslabor ist fertig. Zug 4, Punkte 30 | Metall 21.0, Gas 2.0, Kristall 9.2, Uran 0.4> ausbauen metallmineMetallmine ist auf Stufe 2. Zug 5, Punkte 50 | Metall 11.0, Gas 2.5, Kristall 5.6, Uran 0.5> bauen werftAbgelehnt: Für Werft fehlen noch 4.0 Metall Zug 5, Punkte 50 | Metall 11.0, Gas 2.5, Kristall 5.6, Uran 0.5> warten Zug 6, Punkte 50 | Metall 15.0, Gas 3.0, Kristall 6.0, Uran 0.6> bauen werftWerft ist fertig. Zug 9, Punkte 60 | Metall 12.0, Gas 2.5, Kristall 3.2, Uran 0.9> späherAbgelehnt: Für Späher fehlen noch 0.1 Uran Zug 9, Punkte 60 | Metall 12.0, Gas 2.5, Kristall 3.2, Uran 0.9> warten Zug 10, Punkte 60 | Metall 16.0, Gas 3.0, Kristall 3.6, Uran 1.0> späherSpäher-1 ist fertig. Zug 12, Punkte 75 | Metall 14.0, Gas 2.0, Kristall 1.4, Uran 0.2> erkundenSpäher-1 fand Kobalt: Metall 500.0, Gas 0.0, Kristall 40.0, Uran 0.0Späher-1 fand Helia: Metall 0.0, Gas 300.0, Kristall 0.0, Uran 20.0Gefundene Planeten: 2 Zug 13, Punkte 85 | Metall 18.0, Gas 2.5, Kristall 1.8, Uran 0.3Ende: 85 Punkte in 13 Zügen.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(`Ende: ${game.points} Punkte in ${game.turn} Zügen.`);rl.close();> bauen forschungslaborAbgelehnt: Forschungslabor setzt Kristallmine voraus Zug 0, Punkte 0 | Metall 40.0, Gas 0.0, Kristall 15.0, Uran 0.0> bauen metallmineMetallmine ist fertig. Zug 1, Punkte 10 | Metall 35.0, Gas 0.5, Kristall 13.2, Uran 0.1> bauen kristallmineKristallmine ist fertig. Zug 2, Punkte 20 | Metall 29.0, Gas 1.0, Kristall 12.4, Uran 0.2> bauen forschungslaborForschungslabor ist fertig. Zug 4, Punkte 30 | Metall 21.0, Gas 2.0, Kristall 9.2, Uran 0.4> ausbauen metallmineMetallmine ist auf Stufe 2. Zug 5, Punkte 50 | Metall 11.0, Gas 2.5, Kristall 5.6, Uran 0.5> bauen werftAbgelehnt: Für Werft fehlen noch 4.0 Metall Zug 5, Punkte 50 | Metall 11.0, Gas 2.5, Kristall 5.6, Uran 0.5> warten Zug 6, Punkte 50 | Metall 15.0, Gas 3.0, Kristall 6.0, Uran 0.6> bauen werftWerft ist fertig. Zug 9, Punkte 60 | Metall 12.0, Gas 2.5, Kristall 3.2, Uran 0.9> späherAbgelehnt: Für Späher fehlen noch 0.1 Uran Zug 9, Punkte 60 | Metall 12.0, Gas 2.5, Kristall 3.2, Uran 0.9> warten Zug 10, Punkte 60 | Metall 16.0, Gas 3.0, Kristall 3.6, Uran 1.0> späherSpäher-1 ist fertig. Zug 12, Punkte 75 | Metall 14.0, Gas 2.0, Kristall 1.4, Uran 0.2> erkundenSpäher-1 fand Kobalt: Metall 500.0, Gas 0.0, Kristall 40.0, Uran 0.0Späher-1 fand Helia: Metall 0.0, Gas 300.0, Kristall 0.0, Uran 20.0Gefundene Planeten: 2 Zug 13, Punkte 85 | Metall 18.0, Gas 2.5, Kristall 1.8, Uran 0.3Ende: 85 Punkte in 13 Zügen.Mit dieser Eingabe:
bauen forschungslaborbauen metallminebauen kristallminebauen forschungslaborausbauen metallminebauen werftwartenbauen werftspäherwartenspähererkundenGenauer, für Neugierige
- Ablehnungen bewegen den Zug nicht: Das Gebäude wurde nicht begonnen, deshalb kann der Spieler sofort etwas anderes versuchen.
- Forschung (die Technologien aus den Regeln) steht in der alten Aufgabe nur als Plan: eine Klasse
Researchmit Name, Stufe und Preis, mit Methoden zum Erforschen und Hochstufen. Sie und die Konflikte (Piraten, Angriff mitWeaponPower) sind der nächste Schritt, wenn du allein weitermachen willst.
Übung
Die Kolonie wächst
Löse die Aufgaben in deinem Editor, mit den Klassen aus der Lektion.
1. 100 Metall so schnell wie möglich. Nova Terra startet mit 40 Metall und 15 Kristall. Lies den Plan aus der Eingabe, ein Befehl pro Zeile, führe ihn aus und warte dann, bis das Metall 100 erreicht. Gib aus, in welchem Zug das passiert ist. Mit diesem Plan ist es Zug 21. Finde einen schnelleren.
bauen metallmineausbauen metallmineMetallmine ist fertig.Metallmine ist auf Stufe 2.100 Metall im Zug 21: Zug 21, Punkte 30 | Metall 101.0, Gas 10.5, Kristall 13.2, Uran 2.12. Ein Frachter. Schreibe CargoShip, eine Unterklasse von SpaceShip, mit einer Ladekapazität von 50. HarvestResource(Planet planet) lädt so viel vom Metall des Planeten, wie hineinpasst, aber nicht mehr, als da ist. Unload(Planet planet) lädt es wieder ab. Kobalt hat 120 Metall. Das Schiff Muli macht drei Fahrten von Nova Terra nach Kobalt und zurück.
Muli ist bei Kobalt angekommenMuli hat 50.0 Metall von Kobalt geladenMuli ist bei Nova Terra angekommenMuli hat 50.0 Metall bei Nova Terra entladenMuli ist bei Kobalt angekommenMuli hat 50.0 Metall von Kobalt geladenMuli ist bei Nova Terra angekommenMuli hat 50.0 Metall bei Nova Terra entladenMuli ist bei Kobalt angekommenMuli hat 20.0 Metall von Kobalt geladenMuli ist bei Nova Terra angekommenMuli hat 20.0 Metall bei Nova Terra entladenNova Terra: Metall 120.0, Gas 0.0, Kristall 0.0, Uran 0.0Kobalt: Metall 0.0, Gas 0.0, Kristall 0.0, Uran 0.03. Eine Simulation über 10 Züge. Ohne Spieler: Das Spiel hat eine Liste von Gebäuden (Metallmine, Kristallmine, Forschungslabor, Gasraffinerie). In jedem Schritt versucht es, das nächste zu bauen. Geht das nicht, wartet es einen Zug. Nach jedem Schritt gibt es den Zustand aus, bis die Züge 10 erreichen.
Metallmine ist fertig.Zug 1, Punkte 10 | Metall 35.0, Gas 0.5, Kristall 13.2, Uran 0.1Kristallmine ist fertig.Zug 2, Punkte 20 | Metall 29.0, Gas 1.0, Kristall 12.4, Uran 0.2Forschungslabor ist fertig.Zug 4, Punkte 30 | Metall 21.0, Gas 2.0, Kristall 9.2, Uran 0.4Gasraffinerie ist fertig.Zug 6, Punkte 40 | Metall 15.0, Gas 3.0, Kristall 7.0, Uran 0.6Zug 7, Punkte 40 | Metall 17.0, Gas 4.0, Kristall 7.4, Uran 0.7Zug 8, Punkte 40 | Metall 19.0, Gas 5.0, Kristall 7.8, Uran 0.8Zug 9, Punkte 40 | Metall 21.0, Gas 6.0, Kristall 8.2, Uran 0.9Zug 10, Punkte 40 | Metall 23.0, Gas 7.0, Kristall 8.6, Uran 1.0Weitere Aufgaben
- Eine Uranmine. Füge eine
UraniumMinehinzu, die von der Gasraffinerie abhängt. - Punkte für Ressourcen. Gib 1 Punkt für je 50 geförderte Metall.
- Piraten. Alle 10 Züge nehmen Piraten 20 % des Metalls, wenn der Planet kein Schiff mit einer Waffenstärke von mindestens 10 hat.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Typprüfungen · Exceptions · try, catch und finally · Werfen und eigene Exceptions
- Aufgaben zum Thema: Wir bauen ein Spiel