Refactoring: buildings

This commit is contained in:
Anton Vakhrushev 2019-10-11 18:02:22 +03:00
parent 28c51ba518
commit 94caa93d7c
10 changed files with 255 additions and 21 deletions

21
spec/building_spec.cr Normal file
View File

@ -0,0 +1,21 @@
require "./spec_helper"
describe Game::Building do
it "should create storehouse" do
bg = Game::Building.new Game::Building::Type::Storehouse, "Storehouse", storage: 100
bg.storage.should eq 100
end
it "should create crystal miner" do
bg = Game::Building.new Game::Building::Type::CrystalMiner, "Cryslal Miner", **{
production: Game::Production.new(
ts: 20,
input: Game::Resources.new,
output: Game::Resources.new({
Game::Resources::Type::Crystals => 100,
})
),
}
bg.production.as(Game::Production).ts.should eq 20
end
end

18
spec/command_spec.cr Normal file
View File

@ -0,0 +1,18 @@
require "./spec_helper"
describe Game::Command do
it "should complete build command" do
world = Game::World.new create_map_2x2
point = Game::Point.new 1, 0
building = Game::Building.new Game::Building::Type::StartPoint, "Dummy", **{
construction: Game::Construction.free 10,
}
command = Game::BuildCommand.new point, building
world.push command
tile = world.map.get point
tile.should be_a(Game::ConstructionSiteTile)
world.run 10
tile = world.map.get point
tile.should be_a(Game::BuildingTile)
end
end

View File

@ -1,8 +1,29 @@
require "./spec_helper" require "./spec_helper"
describe Game::Resources do module Test::GameResources
alias Res = Game::Resources
alias ResType = Game::Resources::Type
describe Game::Resources do
it "should be created from hash" do it "should be created from hash" do
res = Game::Resources.new({Game::Resources::Type::Crystals => 100}) res = Res.new({ResType::Crystals => 100})
res[Game::Resources::Type::Crystals].should eq 100 res[ResType::Crystals].should eq 100
end
it "should check single type" do
res = Res.new({ResType::Crystals => 100})
res.has(ResType::Crystals, 100).should be_true
end
it "should check resource bag" do
res = Res.new({ResType::Crystals => 100})
res.has({ResType::Crystals => 50}).should be_true
res.has({ResType::Crystals => 150}).should be_false
end
it "should check empty value" do
res = Res.new
res.has({ResType::Crystals => 50}).should be_false
end
end end
end end

74
src/game/building.cr Normal file
View File

@ -0,0 +1,74 @@
module Game
struct Production
def initialize(@ts : TimeSpan, @input : Resources, @output : Resources)
end
getter ts
getter input
getter output
end
struct Restoration
def initialize(@ts : TimeSpan, @type : Resources::Type, @cap : Capacity)
end
getter ts
getter type
getter cap
end
struct Construction
def initialize(@ts : TimeSpan, @cost : Resources, @requirements : Array(Building::Type))
end
getter ts
getter cost
getter requirements
def self.immediatly
Construction.new 0, Resources.new, [] of Building::Type
end
def self.free(ts : TimeSpan)
Construction.new ts, Resources.new, [] of Building::Type
end
end
class Building
enum Role
Storehouse
end
enum Type
StartPoint
Storehouse
CrystalMiner
CrystalRestorer
end
def initialize(
@type : Type,
@name : String,
*,
roles : Array(Role) | Nil = nil,
construction : Construction | Nil = nil,
production : Production | Nil = nil,
restoration : Restoration | Nil = nil,
storage : Capacity | Nil = nil
)
@roles = roles.nil? ? Array(Role).new : roles
@construction = construction.nil? ? Construction.immediatly : construction.as(Construction)
@production = production
@restoration = restoration
@storage = storage.nil? ? 0 : storage
end
getter type
getter name
getter roles
getter construction
getter production
getter restoration
getter storage
end
end

41
src/game/building_list.cr Normal file
View File

@ -0,0 +1,41 @@
module Game
class BuildingList
def initialize
@items = [] of NamedTuple(t: Building::Type, b: Building)
add(
Building.new Building::Type::StartPoint, "Start Point", storage: 100
)
add(
Building.new Building::Type::CrystalMiner, "Crystal Miner", **{
production: Production.new(
ts: 20,
input: Resources.new,
output: Resources.new({
Resources::Type::Crystals => 100,
})
),
}
)
add(
Building.new Building::Type::CrystalRestorer, **{
cost: Resources.new({
Resources::Type::Crystals => 100,
}),
restoration: Restoration.new(
ts: 30,
type: Resources::Type::Crystals,
cap: 50
),
}
)
end
private def add(building : Building)
t = building.type
@items << {t: t, b: building}
end
end
end

View File

@ -2,18 +2,36 @@ require "./tile"
module Game module Game
abstract class Command abstract class Command
abstract def start(world : World) : Int32 abstract def start(world : World) : TimeSpan
abstract def finish(world : World) abstract def finish(world : World)
abstract def desc : String abstract def desc : String
end end
class BuildCommand < Command
def initialize(@point : Point, @building : Building)
end
def desc : String
sprintf "Building '%s'", building.name
end
def start(world : World) : TimeSpan
world.map.set(ConstructionSiteTile.new(@point))
@building.construction.ts
end
def finish(world : World)
world.map.set(BuildingTile.new(@point, @building))
end
end
class BuildCrystalHarvesterCommand < Command class BuildCrystalHarvesterCommand < Command
BUILD_TIME = 30 BUILD_TIME = 30
def initialize(@point : Point) def initialize(@point : Point)
end end
def start(world : World) : Int32 def start(world : World) : TimeSpan
tile = world.map.get(@point) tile = world.map.get(@point)
if !tile.can_build? if !tile.can_build?
raise InvalidPlaceForBuilding.new raise InvalidPlaceForBuilding.new
@ -41,7 +59,7 @@ module Game
@value = 0 @value = 0
end end
def start(world : World) : Int32 def start(world : World) : TimeSpan
deposit_tile = nearest_deposit(world) deposit_tile = nearest_deposit(world)
stock_tile = nearest_stock(world) stock_tile = nearest_stock(world)
if deposit_tile && stock_tile if deposit_tile && stock_tile
@ -83,7 +101,7 @@ module Game
def initialize(@point : Point) def initialize(@point : Point)
end end
def start(world : World) : Int32 def start(world : World) : TimeSpan
tile = world.map.get(@point) tile = world.map.get(@point)
if !tile.can_build? if !tile.can_build?
raise InvalidPlaceForBuilding.new raise InvalidPlaceForBuilding.new
@ -113,7 +131,7 @@ module Game
def initialize(@point : Point) def initialize(@point : Point)
end end
def start(world : World) : Int32 def start(world : World) : TimeSpan
@target_tile = nearest_deposit(world) @target_tile = nearest_deposit(world)
if @target_tile if @target_tile
dist = @point.distance(@target_tile.as(Tile).point) dist = @point.distance(@target_tile.as(Tile).point)
@ -148,7 +166,7 @@ module Game
def initialize(@point : Point) def initialize(@point : Point)
end end
def start(world : World) : Int32 def start(world : World) : TimeSpan
tile = world.map.get(@point) tile = world.map.get(@point)
if !tile.can_build? if !tile.can_build?
raise InvalidPlaceForBuilding.new raise InvalidPlaceForBuilding.new
@ -178,7 +196,7 @@ module Game
@can_terr = false @can_terr = false
end end
def start(world : World) : Int32 def start(world : World) : TimeSpan
if world.resources.has(Resources::Type::Crystals, CRYSTAL_REQUIRED) if world.resources.has(Resources::Type::Crystals, CRYSTAL_REQUIRED)
world.resources.dec(Resources::Type::Crystals, CRYSTAL_REQUIRED) world.resources.dec(Resources::Type::Crystals, CRYSTAL_REQUIRED)
@can_terr = true @can_terr = true

5
src/game/game.cr Normal file
View File

@ -0,0 +1,5 @@
module Game
alias TimePoint = Int64
alias TimeSpan = Int32 | Int64
alias Capacity = Int32
end

View File

@ -8,12 +8,17 @@ class Game::Resources
alias Capacity = Int32 alias Capacity = Int32
def initialize alias ResourceBag = Hash(Type, Capacity)
@values = {} of Type => Capacity
end
def initialize(vals : Hash(Type, Capacity)) def initialize(vals : ResourceBag | Nil = nil)
@values = vals.clone @values = {} of Type => Capacity
Type.each { |t, v| @values[t] = 0 }
if vals.is_a?(ResourceBag)
vals.each do |i|
t, v = i
@values[t] = v
end
end
end end
def [](t : Type) def [](t : Type)
@ -21,7 +26,15 @@ class Game::Resources
end end
def has(t : Type, value : Capacity) : Bool def has(t : Type, value : Capacity) : Bool
@values.fetch(t, 0) >= value @values[t] >= value
end
def has(vs : ResourceBag) : Bool
vs.reduce true do |acc, entry|
t = entry[0]
v = entry[1]
acc && @values[t] >= v
end
end end
def inc(t : Type, value : Capacity) def inc(t : Type, value : Capacity)
@ -32,6 +45,14 @@ class Game::Resources
@values[t] = new_value @values[t] = new_value
end end
def inc?(vs : ResourceBag) : Bool
false unless has(vs)
vs.each do |t, c|
@values[t] = @values[t] + c
end
true
end
def dec(t : Type, value : Capacity) def dec(t : Type, value : Capacity)
inc(t, -value) inc(t, -value)
end end

View File

@ -7,6 +7,7 @@ module Game
Plateau Plateau
Terraformer Terraformer
Warehouse Warehouse
Building
end end
abstract class Tile abstract class Tile
@ -20,9 +21,12 @@ module Game
getter cap getter cap
getter cur getter cur
abstract def letter : Char
abstract def has_role(role : TileRole) : Bool abstract def has_role(role : TileRole) : Bool
def letter : Char
' '
end
def withdraw(value) def withdraw(value)
if value >= @cur if value >= @cur
wd = @cur wd = @cur
@ -118,4 +122,15 @@ module Game
role == TileRole::Terraformer role == TileRole::Terraformer
end end
end end
class BuildingTile < Tile
def initialize(@point : Point, @building : Building)
end
def has_role(role : TileRole) : Bool
role == TileRole::Building
end
getter building
end
end end

View File

@ -1,9 +1,9 @@
require "./resources" require "./resources"
class Game::World class Game::World
property ts : Int64 property ts : TimePoint
def initialize(@map : Map, @ts = 0_i64) def initialize(@map : Map, @ts : TimePoint = 0_i64)
@start_ts = @ts @start_ts = @ts
@resources = Resources.new @resources = Resources.new
@queue = Queue.new @queue = Queue.new
@ -23,7 +23,7 @@ class Game::World
@queue.push(done_at, command) @queue.push(done_at, command)
end end
def run(ts : Int64) def run(ts : TimePoint)
loop do loop do
item = @queue.pop(ts) item = @queue.pop(ts)
if item.nil? if item.nil?