diff --git a/spec/command_spec.cr b/spec/command_spec.cr index b0441e2..57bb5bc 100644 --- a/spec/command_spec.cr +++ b/spec/command_spec.cr @@ -18,13 +18,26 @@ module Game::TestCommand ) map.set DepositTile.new( Point.new(1, 1), - Deposit.new(Resource::Type::Crystals, 100) + Deposit.new(Resource::Type::Crystals, 1000, 500) + ) + map.set DepositTile.new( + Point.new(1, 0), + Deposit.new(Resource::Type::Crystals, 1000, 0) ) map.set BuildingTile.new( Point.new(0, 1), Building.new( Building::Type::CrystalMiner, - mining: Mining.new(20, Resource.new(Resource::Type::Crystals, 40)) + mining: Mining.new( + ts: 20, + resource: Resource.new(Resource::Type::Crystals, 40), + input: ResourceBag.new + ), + restoration: Mining.new( + ts: 20, + resource: Resource.new(Resource::Type::Crystals, 40), + input: ResourceBag.new({Resource::Type::Crystals => 5}) + ) ) ) map @@ -65,10 +78,27 @@ module Game::TestCommand it "should complete mining command" do world = World.new create_map_with_resource - command = MineCommand.new Point.new(0, 1) + command = MineCommand.new Point.new(0, 1), once: true world.push command world.run 20 + # Check world resources world.resources[Resource::Type::Crystals].should eq 40 + # Check tile deposit + tile = world.map.get(1, 1).as(DepositTile) + tile.dep.cur.should eq 460 + end + + it "should complete restore command" do + world = World.new create_map_with_resource + world.resources.inc(Resource::Type::Crystals, 20) + command = RestoreCommand.new Point.new(0, 1), once: true + world.push command + world.run 20 + # Check world resources + world.resources[Resource::Type::Crystals].should eq 15 + # Check tile deposit + tile = world.map.get(1, 0).as(DepositTile) + tile.dep.cur.should eq 40 end end end diff --git a/spec/resources_spec.cr b/spec/resources_spec.cr index 9d7bf88..976078c 100644 --- a/spec/resources_spec.cr +++ b/spec/resources_spec.cr @@ -25,5 +25,29 @@ module Game::TestResourceBag res = Res.new res.has({ResType::Crystals => 50}).should be_false end + + it "should inc single value" do + res = Res.new ({ResType::Crystals => 10}) + res.inc ResType::Crystals, 5 + res[ResType::Crystals].should eq 15 + end + + it "should inc resource" do + res = Res.new ({ResType::Crystals => 10}) + res.inc Resource.new(ResType::Crystals, 5) + res[ResType::Crystals].should eq 15 + end + + it "should inc hash" do + res = Res.new ({ResType::Crystals => 10}) + res.inc ({ResType::Crystals => 5}) + res[ResType::Crystals].should eq 15 + end + + it "should inc other bag" do + res = Res.new ({ResType::Crystals => 10}) + res.inc Res.new({ResType::Crystals => 5}) + res[ResType::Crystals].should eq 15 + end end end diff --git a/src/game/building.cr b/src/game/building.cr index 5abd976..9600b02 100644 --- a/src/game/building.cr +++ b/src/game/building.cr @@ -9,11 +9,12 @@ module Game end class Mining - def initialize(@ts : TimeSpan, @resource : Resource) + def initialize(@ts : TimeSpan, @resource : Resource, @input : ResourceBag) end getter ts getter resource + getter input end class Construction diff --git a/src/game/building_factory.cr b/src/game/building_factory.cr index 3e73bd0..dc19081 100644 --- a/src/game/building_factory.cr +++ b/src/game/building_factory.cr @@ -11,7 +11,8 @@ module Game Building.new Building::Type::CrystalMiner, **{ mining: Mining.new( ts: 20, - resource: Resource.new(Resource::Type::Crystals, 40) + resource: Resource.new(Resource::Type::Crystals, 40), + input: ResourceBag.new ), } ) @@ -27,7 +28,8 @@ module Game ), restoration: Mining.new( ts: 30, - resource: Resource.new(Resource::Type::Crystals, 20) + resource: Resource.new(Resource::Type::Crystals, 20), + input: ResourceBag.new ), } ) diff --git a/src/game/command.cr b/src/game/command.cr index 22fe8b0..672aa5e 100644 --- a/src/game/command.cr +++ b/src/game/command.cr @@ -36,7 +36,7 @@ module Game class MineCommand < Command @holded : Resource? = nil - def initialize(@point : Point) + def initialize(@point : Point, *, @once = false) end def desc : String @@ -63,7 +63,9 @@ module Game holded = @holded.as(Resource) world.resources.inc(holded) end - world.push(MineCommand.new(@point)) + if !@once + world.push(MineCommand.new(@point)) + end end private def nearest_deposit(world : World, res_type : Resource::Type) : DepositTile? @@ -74,6 +76,53 @@ module Game end end + class RestoreCommand < Command + @holded : Resource? = nil + @deposit_tile : DepositTile? = nil + + def initialize(@point : Point, *, @once = false) + end + + def desc : String + if @holded + sprintf "Restore %s from %d,%d", @holded.type, @point.x, @point.y + else + sprintf "Wait for resources at %d,%d", @point.x, @point.y + end + end + + def start(world : World) : TimeSpan + tile = world.map.get(@point).as(BuildingTile) + building = tile.building + restoration = building.restoration.as(Mining) + if !world.resources.has(restoration.input) + return restoration.ts + end + @deposit_tile = nearest_deposit(world, restoration.resource.type) + if @deposit_tile + world.resources.dec restoration.input + @holded = restoration.resource + end + restoration.ts + end + + def finish(world : World) + if @deposit_tile && @holded + @deposit_tile.as(DepositTile).dep.inc(@holded.as(Resource)) + end + if !@once + world.push(RestoreCommand.new(@point)) + end + end + + private def nearest_deposit(world : World, res_type : Resource::Type) : DepositTile? + tile = world.map.nearest_tile @point do |tile| + tile.is_a?(DepositTile) && tile.dep.type == res_type && tile.dep.cur == 0 + end + tile.as?(DepositTile) + end + end + # class BuildCrystalHarvesterCommand < Command # BUILD_TIME = 30 diff --git a/src/game/deposit.cr b/src/game/deposit.cr index 05ce8e3..03318a7 100644 --- a/src/game/deposit.cr +++ b/src/game/deposit.cr @@ -1,4 +1,5 @@ class Game::Deposit + @cap : Capacity = 0 @cur : Capacity = 0 def initialize(@type : Resource::Type, @cap : Capacity) diff --git a/src/game/resources.cr b/src/game/resources.cr index b20b077..a2163e7 100644 --- a/src/game/resources.cr +++ b/src/game/resources.cr @@ -26,15 +26,12 @@ class Game::ResourceBag @values = ResourceHash.new Resource::Type.each { |t, v| @values[t] = 0 } if vals.is_a?(ResourceHash) - vals.each do |i| - t, v = i - @values[t] = v - end + add_amounts vals end end def [](res_type : Resource::Type) - @values.fetch(res_type, 0) + @values[res_type] end def has(res_type : Resource::Type, value : Capacity) : Bool @@ -42,38 +39,68 @@ class Game::ResourceBag end def has(res : Resource) : Bool - has(res.type, res.amount) + has res.type, res.amount end def has(vs : ResourceHash) : Bool - vs.reduce true do |acc, entry| - t = entry[0] - v = entry[1] - acc && @values[t] >= v - end + has_amounts vs end def has(vs : self) : Bool - has vs.to_hash + has_amounts vs.@values end def inc(res_type : Resource::Type, value : Capacity) - new_value = @values[res_type] + value - if new_value < 0 - raise NotEnoughtResources.new - end - @values[res_type] = new_value + validate_add_amounts ({res_type => value}) + @values[res_type] += value end def inc(res : Resource) - inc(res.type, res.amount) + inc res.type, res.amount + end + + def inc(vs : ResourceHash) + validate_add_amounts vs + add_amounts vs + end + + def inc(other : self) + inc other.@values end def dec(res_type : Resource::Type, value : Capacity) - inc(res_type, -value) + inc res_type, -value end - def to_hash : ResourceHash - @values.clone + def dec(other : self) + inverted = other.@values.transform_values { |v| -v } + inc inverted + end + + private def can_add_amounts(vs : ResourceHash) + vs.reduce true do |acc, entry| + res_type, amount = entry + acc && @values[res_type] + amount >= 0 + end + end + + private def validate_add_amounts(vs : ResourceHash) + if !can_add_amounts(vs) + raise NotEnoughtResources.new + end + end + + private def add_amounts(vs : ResourceHash) + vs.each do |entry| + res_type, amount = entry + @values[res_type] += amount + end + end + + private def has_amounts(vs : ResourceHash) + vs.reduce true do |acc, entry| + res_type, amount = entry + acc && @values[res_type] >= amount + end end end