Compare commits
14 Commits
graphql-ui
...
master
Author | SHA1 | Date | |
---|---|---|---|
915e33d183 | |||
fe5c6712d2 | |||
e9cac30467 | |||
85d73a3947 | |||
43310ecf9e | |||
beaa6ca277 | |||
8e28f3f7d7 | |||
791a24e5b3 | |||
532aae0907 | |||
dbb230123e | |||
41fa326418 | |||
b53a0e6050 | |||
ee5e3cdd3f | |||
1f4bcdae21 |
2
Makefile
2
Makefile
|
@ -7,6 +7,8 @@ run: build
|
|||
./$(NAME)
|
||||
build:
|
||||
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging
|
||||
build_entitas_logging:
|
||||
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging
|
||||
debug:
|
||||
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging Dentitas_debug_generator
|
||||
# I did not enabled --debug because it crashes.
|
||||
|
|
|
@ -16,8 +16,8 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
|
|||
|
||||
## Usage
|
||||
|
||||
export LD_LIBRARY_PATH="$(pwd)/lib/imgui-sfml"
|
||||
./core # run the server (high performance for data)
|
||||
xdg-open https://localhost:3000 # open the game UI (browser UI for simplicity)
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -36,7 +36,7 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
|
|||
|
||||
## Contributors
|
||||
|
||||
- [Arthur Poulet](https://git.sceptique.eu/Sceptique) - creator and maintainer.
|
||||
- [Arthur Poulet](https://git.sceptique.eu/Sceptique) - creator and maintainer
|
||||
|
||||
## Particular mentions
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ items:
|
|||
c_plant:
|
||||
<<: *template_default
|
||||
title: Chemical plant
|
||||
description: Series of chemical transformation machines.
|
||||
description: Transformation machines that can produce almost any kind of material via chemistery, such as super-acides, rare gaz, etc. using lots of basic materials.
|
||||
consumes:
|
||||
mineral:
|
||||
function: linear
|
||||
|
@ -84,7 +84,7 @@ items:
|
|||
l_plant:
|
||||
<<: *template_default
|
||||
title: Logistic assembly
|
||||
description: An assembly center site that make trunks, trains, planes, and civilan space transports.
|
||||
description: An assembly center site that make trunks, trains, planes, and civilan space transports. It also provide a better transportation network.
|
||||
consumes:
|
||||
alloy:
|
||||
function: linear
|
||||
|
|
|
@ -36,7 +36,7 @@ items:
|
|||
e_plant:
|
||||
<<: *template_default
|
||||
title: Energy plant
|
||||
description: Energy grid that produce energy using nuclear isotops, sun, wind, and fossil fuel.
|
||||
description: Energy grid that produce energy using nuclear isotops, sun, wind, and fossil fuel, and also can fill energy storage means with chemistery.
|
||||
prods:
|
||||
energy:
|
||||
function: linear
|
||||
|
@ -51,7 +51,7 @@ items:
|
|||
mine:
|
||||
<<: *template_default
|
||||
title: Mines
|
||||
description: Mines are polluting industry to generate tons of basic ressources required for most of the industry.
|
||||
description: Mines are a polluting industry that can generate tons of basic ressources required for most of the industry. Network of mines generate it's own cheminals.
|
||||
consumes:
|
||||
energy:
|
||||
function: linear
|
||||
|
@ -62,16 +62,20 @@ items:
|
|||
function: linear
|
||||
coefs:
|
||||
a: 10
|
||||
chemical:
|
||||
function: linear
|
||||
coefs:
|
||||
a: 0.01
|
||||
wastes:
|
||||
pollution:
|
||||
function: linear
|
||||
coefs:
|
||||
a: 1
|
||||
|
||||
farm:
|
||||
agrifood:
|
||||
<<: *template_default
|
||||
title: Farmed lands
|
||||
description: Large portions of the land space is used to produce nutrient. It growth crops, animal farms, etc. using standard mechanized tools and vehicules.
|
||||
description: Large portions of the land space is used to produce nutrient. It growth crops, animal farms, etc. using standard mechanized tools and vehicules. Food can then be distributed via a dense network of distributors.
|
||||
consumes:
|
||||
mineral:
|
||||
function: linear
|
||||
|
|
|
@ -29,7 +29,7 @@ templates:
|
|||
max:
|
||||
function: linear
|
||||
coefs:
|
||||
b: 10_000
|
||||
b: 40_000
|
||||
|
||||
items:
|
||||
|
||||
|
@ -51,12 +51,12 @@ items:
|
|||
mineral:
|
||||
function: linear
|
||||
coefs:
|
||||
a: 10000000
|
||||
a: 10000
|
||||
|
||||
f_store:
|
||||
<<: *template_default
|
||||
title: Food storage
|
||||
description: Protective centers for nutrients and food supplies
|
||||
description: Protective centers for nutrients and food supplies.
|
||||
consumes:
|
||||
energy:
|
||||
function: linear
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
energy:
|
||||
name: Energy
|
||||
description: Represents how many machines we can move at the same time.
|
||||
description: Represents how many machines we can move at the same time. Energy unit represents both the energy immediate usage and storage by technological means.
|
||||
food:
|
||||
name: Food
|
||||
description: Standard food with low technological ehancements
|
||||
description: Standard food with low technological ehancements, along with means and services to distribute it properly.
|
||||
food2:
|
||||
name: Synthetic Food
|
||||
description: Artificialy engineered very efficient food
|
||||
description: Artificialy engineered very efficient food and distribution.
|
||||
mineral:
|
||||
name: Simple Minerals
|
||||
name: Simple Minerals in a proper concentration.
|
||||
minerals2:
|
||||
name: Rare Minerals
|
||||
name: Rare Minerals.
|
||||
chemical:
|
||||
name: Chemicals
|
||||
description: Usually extracted from minerals with chemistery
|
||||
description: Materials usually extracted from minerals with chemistery, with the infrastructure for safe storage and exploitation.
|
||||
require: mineral
|
||||
alloy:
|
||||
name: Alloys
|
||||
description: Standard alloys
|
||||
description: Standards alloys for most industrial and military usages.
|
||||
require: mineral
|
||||
alloy2:
|
||||
name: Super Materials
|
||||
description: Super alloys with near-magical properties
|
||||
description: Super alloys with near-magical properties compared to modern age.
|
||||
require: mineral
|
||||
weapon:
|
||||
name: Weaponery
|
||||
description: Bombs, guns, bullets, armors, utilitary, tanks, fighters
|
||||
description: Bombs, guns, bullets, armors, utilitary, tanks, fighters, artillery, all the things that are small enough to fit in a carrier.
|
||||
require: alloy
|
||||
logistic:
|
||||
name: Logistic
|
||||
description: Civilan ships for fret and travellers
|
||||
description: Civilan ships and means of transportations for high volume fret and travellers.
|
||||
pollution:
|
||||
name: Pollution
|
||||
description: Represent the wastes of industrial activities, that is stored on the planet more or less randomly.
|
||||
description: Represent the wastes of industrial activities, that is stored on the planet more or less efficiently and have a negative impact on productivity and life.
|
||||
|
|
|
@ -36,7 +36,7 @@ Lalande
|
|||
Lampadas
|
||||
Lankiveil
|
||||
Lernaeus
|
||||
Luyten's Star
|
||||
Luyten
|
||||
Mu Arae
|
||||
Muritan
|
||||
Naraj
|
||||
|
@ -53,11 +53,11 @@ Selusa
|
|||
Sikun
|
||||
Sun
|
||||
Synchrony
|
||||
Trappist
|
||||
Tau Ceti
|
||||
Tauri
|
||||
Tegeuse
|
||||
Tleilax
|
||||
Trappist
|
||||
Tupile
|
||||
Upsilon Andromedae
|
||||
Ursae Majoris
|
||||
|
@ -65,4 +65,3 @@ Virginis
|
|||
Wasp
|
||||
Xo
|
||||
Yz Ceti
|
||||
c
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
type Query {
|
||||
empires(name: String!): String!
|
||||
|
||||
day(): Int!
|
||||
}
|
26
shard.lock
26
shard.lock
|
@ -1,28 +1,20 @@
|
|||
version: 2.0
|
||||
shards:
|
||||
backtracer:
|
||||
git: https://github.com/sija/backtracer.cr.git
|
||||
version: 1.2.1
|
||||
crsfml:
|
||||
git: https://github.com/oprypin/crsfml.git
|
||||
version: 2.5.2
|
||||
|
||||
entitas:
|
||||
git: https://github.com/spoved/entitas.cr.git
|
||||
version: 1.4.5
|
||||
|
||||
exception_page:
|
||||
git: https://github.com/crystal-loot/exception_page.git
|
||||
version: 0.2.2
|
||||
imgui:
|
||||
git: https://github.com/oprypin/crystal-imgui.git
|
||||
version: 1.87
|
||||
|
||||
graphql:
|
||||
git: https://github.com/graphql-crystal/graphql.git
|
||||
version: 0.4.0
|
||||
|
||||
kemal:
|
||||
git: https://github.com/kemalcr/kemal.git
|
||||
version: 1.2.0
|
||||
|
||||
radix:
|
||||
git: https://github.com/luislavena/radix.git
|
||||
version: 0.4.1
|
||||
imgui-sfml:
|
||||
git: https://github.com/oprypin/crystal-imgui-sfml.git
|
||||
version: 1.87
|
||||
|
||||
spoved:
|
||||
git: https://github.com/spoved/spoved.cr.git
|
||||
|
|
10
shard.yml
10
shard.yml
|
@ -15,7 +15,9 @@ license: GPLv3
|
|||
dependencies:
|
||||
entitas:
|
||||
github: spoved/entitas.cr
|
||||
graphql:
|
||||
github: graphql-crystal/graphql
|
||||
kemal:
|
||||
github: kemalcr/kemal
|
||||
crsfml:
|
||||
github: oprypin/crsfml
|
||||
imgui:
|
||||
github: oprypin/crystal-imgui
|
||||
imgui-sfml:
|
||||
github: oprypin/crystal-imgui-sfml
|
|
@ -1,4 +0,0 @@
|
|||
module TETU::API
|
||||
end
|
||||
|
||||
require "./api/*"
|
|
@ -1,21 +0,0 @@
|
|||
require "./objects"
|
||||
|
||||
module TETU::API::Definitions
|
||||
@[GraphQL::Object]
|
||||
class GameQuery < GraphQL::BaseQuery
|
||||
@[GraphQL::Field]
|
||||
def hello(name : String) : String
|
||||
"Hello, #{name}!"
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
def empire : Empire
|
||||
Empire.new
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
def planet(id : ID? = nil) : Planet
|
||||
Planet.new(id: id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,50 +0,0 @@
|
|||
class TETU::API::HttpServer
|
||||
def self.start
|
||||
spawn do
|
||||
Kemal.run do |config|
|
||||
server = config.server.not_nil!
|
||||
server.bind_tcp(
|
||||
host: ENV.fetch("HOST", "127.0.0.1"),
|
||||
port: ENV.fetch("PORT", "3000").to_i,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "./game_query"
|
||||
|
||||
schema = GraphQL::Schema.new(TETU::API::Definitions::GameQuery.new)
|
||||
|
||||
|
||||
# class CustomHandler < Kemal::Handler
|
||||
# only ["/graphql"], "POST"
|
||||
|
||||
# def call(context)
|
||||
# puts "Doing some custom stuff here"
|
||||
# spawn do
|
||||
# puts "Async stuff finished now"
|
||||
# call_next context
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# add_handler CustomHandler.new
|
||||
|
||||
module TETU::API
|
||||
PLAYER_CHANNEL = Channel(Tuple(String, Channel(String))).new
|
||||
RESPONSE_CHANNEL = Channel(String).new
|
||||
end
|
||||
|
||||
post "/graphql" do |env|
|
||||
env.response.content_type = "application/json"
|
||||
|
||||
query = env.params.json["query"].as(String)
|
||||
variables = env.params.json["variables"]?.as(Hash(String, JSON::Any)?)
|
||||
operation_name = env.params.json["operationName"]?.as(String?)
|
||||
context = TETU::API::Definitions::PlayerContext.new(TETU::API::PLAYER_CHANNEL)
|
||||
schema.execute(query, variables, operation_name, context)
|
||||
end
|
||||
|
||||
get "/" do |env|
|
||||
"<html></html>"
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
require "./objects/base"
|
||||
require "./objects/planet"
|
||||
require "./objects/all"
|
|
@ -1,48 +0,0 @@
|
|||
require "./base"
|
||||
require "./planet"
|
||||
|
||||
module TETU::API::Definitions
|
||||
@[GraphQL::Object]
|
||||
class Star < GraphQL::BaseObject
|
||||
@@i : ID = 0
|
||||
|
||||
def initialize(id : Int32? = nil)
|
||||
if id.nil?
|
||||
@id = @@i
|
||||
@@i += 1
|
||||
else
|
||||
@id = id
|
||||
end
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
getter id : ID
|
||||
|
||||
@[GraphQL::Field]
|
||||
def name : String
|
||||
"my #{@id}th star name"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module TETU::API::Definitions
|
||||
@[GraphQL::Object]
|
||||
class Empire < GraphQL::BaseObject
|
||||
@[GraphQL::Field]
|
||||
def name : String
|
||||
"Player"
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
def stars(context) : Array(Star)
|
||||
context.player_channel.send({ "stars", TETU::API::RESPONSE_CHANNEL })
|
||||
answer = TETU::API::RESPONSE_CHANNEL.receive
|
||||
answer.split(",").map { |name| Star.new }
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
def planets : Array(Planet)
|
||||
[] of Planet
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module TETU::API::Definitions
|
||||
alias ID = Int32
|
||||
|
||||
class PlayerContext < GraphQL::Context
|
||||
def initialize(@player_channel : Channel(Tuple(String, Channel(String))))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,23 +0,0 @@
|
|||
module TETU::API::Definitions
|
||||
@[GraphQL::Object]
|
||||
class Planet < GraphQL::BaseObject
|
||||
@@i : ID = 0
|
||||
|
||||
def initialize(id : Int32? = nil)
|
||||
if id.nil?
|
||||
@id = @@i
|
||||
@@i += 1
|
||||
else
|
||||
@id = id
|
||||
end
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
getter id : ID
|
||||
|
||||
@[GraphQL::Field]
|
||||
def name : String
|
||||
"my #{@id}th planet name"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,79 +18,6 @@ class TETU::Named < Entitas::Component
|
|||
end
|
||||
|
||||
require "./game/resources"
|
||||
|
||||
# TODO: why is it not a component ????? should fix that
|
||||
class TETU::InfrastructureUpgrade
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
alias Costs = Hash(Resources::Name, Float64)
|
||||
property id : String
|
||||
property costs_by_tick : Costs
|
||||
property costs_start : Costs
|
||||
property end_tick : TETU::Tick
|
||||
property current_tick : TETU::Tick
|
||||
@finished = false
|
||||
|
||||
def to_s
|
||||
"#{@id} (#{@current_tick}/#{@end_tick})"
|
||||
end
|
||||
|
||||
def finished?
|
||||
@finished
|
||||
end
|
||||
|
||||
def finish!
|
||||
@finished = true
|
||||
end
|
||||
|
||||
def initialize(@id, @costs_by_tick, @costs_start, @end_tick, @current_tick = 0i64)
|
||||
end
|
||||
|
||||
def self.from_infrastructure(id : String, tier : Int32)
|
||||
# TODO: must read the properties of the blueprints to define the costs
|
||||
free_instant(id)
|
||||
end
|
||||
|
||||
def self.free_instant(id : String)
|
||||
new(
|
||||
id: id,
|
||||
costs_by_tick: Costs.new,
|
||||
costs_start: Costs.new,
|
||||
end_tick: 0i64,
|
||||
current_tick: 0i64,
|
||||
)
|
||||
end
|
||||
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
|
||||
def self.from_blueprint(infra_id : String, tier : Number)
|
||||
blueprint = Helpers::InfrastructuresFileLoader.all[infra_id]
|
||||
total_costs = blueprint.build.costs.transform_values { |f| f.execute(tier) }
|
||||
upfront_costs = total_costs.transform_values { |v| v * blueprint.build.upfront }
|
||||
duration = blueprint.build.duration.execute(tier)
|
||||
tick_costs = total_costs.transform_values { |v| v * (1.0 - blueprint.build.upfront) / duration }
|
||||
logger.debug { "" }
|
||||
logger.debug { "> Create from blueprint" }
|
||||
upgrade = new(
|
||||
id: infra_id,
|
||||
costs_by_tick: tick_costs,
|
||||
costs_start: upfront_costs,
|
||||
end_tick: duration.to_i64,
|
||||
current_tick: 0i64,
|
||||
)
|
||||
logger.debug { {blueprint: blueprint} }
|
||||
logger.debug { {upgrade: upgrade} }
|
||||
logger.debug { "" }
|
||||
upgrade
|
||||
end
|
||||
end
|
||||
|
||||
@[Context(Game)]
|
||||
class TETU::InfrastructureUpgrades < Entitas::Component
|
||||
prop :upgrades, Array(InfrastructureUpgrade), default: Array(InfrastructureUpgrade).new
|
||||
|
||||
def to_s
|
||||
"InfrastructureUpgrades: #{upgrades.map(&.to_s)}"
|
||||
end
|
||||
end
|
||||
require "./game/infrastructure"
|
||||
|
||||
require "./game/*"
|
||||
|
|
73
src/components/game/infrastructure.cr
Normal file
73
src/components/game/infrastructure.cr
Normal file
|
@ -0,0 +1,73 @@
|
|||
# TODO: why is it not a component ????? should fix that
|
||||
class TETU::InfrastructureUpgrade
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
alias Costs = Hash(Resources::Name, Float64)
|
||||
property id : String
|
||||
property costs_by_tick : Costs
|
||||
property costs_start : Costs
|
||||
property end_tick : TETU::Tick
|
||||
property current_tick : TETU::Tick
|
||||
@finished = false
|
||||
|
||||
def to_s
|
||||
"#{@id} (#{@current_tick}/#{@end_tick})"
|
||||
end
|
||||
|
||||
def finished?
|
||||
@finished
|
||||
end
|
||||
|
||||
def finish!
|
||||
@finished = true
|
||||
end
|
||||
|
||||
def initialize(@id, @costs_by_tick, @costs_start, @end_tick, @current_tick = 0i64)
|
||||
end
|
||||
|
||||
def self.from_infrastructure(id : String, tier : Int32)
|
||||
# TODO: must read the properties of the blueprints to define the costs
|
||||
free_instant(id)
|
||||
end
|
||||
|
||||
def self.free_instant(id : String)
|
||||
new(
|
||||
id: id,
|
||||
costs_by_tick: Costs.new,
|
||||
costs_start: Costs.new,
|
||||
end_tick: 0i64,
|
||||
current_tick: 0i64,
|
||||
)
|
||||
end
|
||||
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
|
||||
def self.from_blueprint(infra_id : String, tier : Number)
|
||||
blueprint = Helpers::InfrastructuresFileLoader.all[infra_id]
|
||||
total_costs = blueprint.build.costs.transform_values { |f| f.execute(tier) }
|
||||
upfront_costs = total_costs.transform_values { |v| v * blueprint.build.upfront }
|
||||
duration = blueprint.build.duration.execute(tier)
|
||||
tick_costs = total_costs.transform_values { |v| v * (1.0 - blueprint.build.upfront) / duration }
|
||||
logger.debug { "" }
|
||||
logger.debug { "> Create from blueprint" }
|
||||
upgrade = new(
|
||||
id: infra_id,
|
||||
costs_by_tick: tick_costs,
|
||||
costs_start: upfront_costs,
|
||||
end_tick: duration.to_i64,
|
||||
current_tick: 0i64,
|
||||
)
|
||||
logger.debug { {blueprint: blueprint} }
|
||||
logger.debug { {upgrade: upgrade} }
|
||||
logger.debug { "" }
|
||||
upgrade
|
||||
end
|
||||
end
|
||||
|
||||
@[Context(Game)]
|
||||
class TETU::InfrastructureUpgrades < Entitas::Component
|
||||
prop :upgrades, Array(InfrastructureUpgrade), default: Array(InfrastructureUpgrade).new
|
||||
|
||||
def to_s
|
||||
"InfrastructureUpgrades: #{upgrades.map(&.to_s)}"
|
||||
end
|
||||
end
|
|
@ -1,15 +1,19 @@
|
|||
@[Context(Game)]
|
||||
class TETU::Population < Entitas::Component
|
||||
alias Food = Hash(String, Float64)
|
||||
DEFAULT_FOOD = { "food" => 1.0/100.0.millions }
|
||||
|
||||
prop :amount, Float64, default: 0.0
|
||||
prop :foods, Food, default: DEFAULT_FOOD
|
||||
|
||||
MIN_RANDOM_POP = 10_000.0
|
||||
MAX_RANDOM_POP = 10_000_000_000.0
|
||||
MIN_RANDOM_POP = 10.0.millions
|
||||
MAX_RANDOM_POP = 10.0.billions
|
||||
|
||||
def self.generate(entity)
|
||||
def self.generate_for(entity)
|
||||
entity.add_population amount: (MIN_RANDOM_POP..MAX_RANDOM_POP).sample.round
|
||||
end
|
||||
|
||||
def to_s
|
||||
Helpers::Numbers.humanize(number: @amount, round: 2)
|
||||
def to_s(round : Int32 = 2)
|
||||
Helpers::Numbers.humanize(number: @amount, round: round)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,6 +27,10 @@ class TETU::Resources < Entitas::Component
|
|||
def humanize(sep = "\n")
|
||||
map { |k, store| "#{k}: #{store.humanize}" }.join(sep)
|
||||
end
|
||||
|
||||
def amount_hash
|
||||
transform_values { |store| store.amount }
|
||||
end
|
||||
end
|
||||
|
||||
# alias Prod = Tuple(Name, Float64)
|
||||
|
@ -93,7 +97,7 @@ class TETU::Resources < Entitas::Component
|
|||
|
||||
def self.default
|
||||
stores = Stores.new
|
||||
stores["pollution"] = Store.new(amount: 0.0, max: 1_000_000.0)
|
||||
stores["pollution"] = Store.new(amount: 0.0, max: 1.0.millions)
|
||||
|
||||
infras = Infras.new
|
||||
|
||||
|
|
33
src/core.cr
33
src/core.cr
|
@ -1,20 +1,24 @@
|
|||
# require "log"
|
||||
require "yaml"
|
||||
|
||||
require "entitas"
|
||||
require "graphql"
|
||||
require "crsfml"
|
||||
require "imgui"
|
||||
require "imgui-sfml"
|
||||
require "spoved/logger"
|
||||
require "kemal"
|
||||
|
||||
module TETU
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
|
||||
# TODO: TO BE USED
|
||||
# TO BE USED
|
||||
module Systems
|
||||
# Log = TETU::Log.for(self)
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
end
|
||||
|
||||
# TODO: TO BE USED
|
||||
# TO BE USED
|
||||
module Components
|
||||
# Log = TETU::Log.for(self)
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
end
|
||||
|
||||
|
@ -24,9 +28,9 @@ end
|
|||
|
||||
require "./helpers/*"
|
||||
require "./core/*"
|
||||
require "./ui_service"
|
||||
require "./components"
|
||||
require "./systems"
|
||||
require "./api"
|
||||
|
||||
class TETU::EconomicSystems < Entitas::Feature
|
||||
def initialize(contexts : Contexts)
|
||||
|
@ -38,16 +42,22 @@ class TETU::EconomicSystems < Entitas::Feature
|
|||
end
|
||||
end
|
||||
|
||||
class TETU::PlayerSystems < Entitas::Feature
|
||||
class TETU::UiSystems < Entitas::Feature
|
||||
def initialize(contexts : Contexts)
|
||||
@name = "Player Systems"
|
||||
add RequestHandlerSystem.new(contexts, player_channel)
|
||||
@name = "UI Systems"
|
||||
|
||||
add UiInitSystem.new(contexts)
|
||||
add UiBackgroundSystem.new(contexts)
|
||||
add UiEmpireSystem.new(contexts)
|
||||
add UiPlanetSystem.new(contexts)
|
||||
add UiDrawSystem.new(contexts) # keep at the end
|
||||
end
|
||||
end
|
||||
|
||||
class TETU::TimeSystems < Entitas::Feature
|
||||
def initialize(contexts : Contexts)
|
||||
@name = "Time Systems"
|
||||
|
||||
add TimeSystem.new(contexts)
|
||||
end
|
||||
end
|
||||
|
@ -58,12 +68,12 @@ class TETU::MainWorld
|
|||
def start
|
||||
# get a reference to the contexts
|
||||
contexts = Contexts.shared_instance
|
||||
|
||||
# create the systems by creating individual features
|
||||
@systems = Entitas::Feature.new("systems")
|
||||
.add(TimeSystems.new(contexts))
|
||||
.add(EconomicSystems.new(contexts))
|
||||
.add(PlayerSystems.new(contexts))
|
||||
# .add(UiSystems.new(contexts))
|
||||
.add(UiSystems.new(contexts))
|
||||
@systems.init
|
||||
end
|
||||
|
||||
|
@ -71,6 +81,7 @@ class TETU::MainWorld
|
|||
# call execute on all the ExecuteSystems and
|
||||
# ReactiveSystems that were triggered last frame
|
||||
@systems.execute
|
||||
|
||||
# call cleanup on all the CleanupSystems
|
||||
@systems.cleanup
|
||||
end
|
||||
|
@ -104,11 +115,9 @@ module TETU
|
|||
t2 = Time.local
|
||||
logger.debug { "Duration: #{t2 - t1}" }
|
||||
logger.debug { "" }
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TETU::API::HttpServer.start
|
||||
TETU.main_loop
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
require "./configuration"
|
||||
|
||||
# class TETU::Window
|
||||
# @@instance = Window.new
|
||||
class TETU::Window
|
||||
@@instance = Window.new
|
||||
|
||||
# def self.instance
|
||||
# @@instance
|
||||
# end
|
||||
def self.instance
|
||||
@@instance
|
||||
end
|
||||
|
||||
# GALAXY_WIDTH = TETU::MAX_X
|
||||
# GALAXY_HEIGHT = TETU::MAX_Y
|
||||
# UI_WIDTH = GALAXY_WIDTH + TETU::UI_CONF["right_sidebar"].as_i64
|
||||
# UI_HEIGHT = GALAXY_HEIGHT
|
||||
# SQUARE_SIZE = TETU::UI_CONF["square_size"].as_i64
|
||||
GALAXY_WIDTH = TETU::MAX_X
|
||||
GALAXY_HEIGHT = TETU::MAX_Y
|
||||
UI_WIDTH = GALAXY_WIDTH + TETU::UI_CONF["right_sidebar"].as_i64
|
||||
UI_HEIGHT = GALAXY_HEIGHT
|
||||
SQUARE_SIZE = TETU::UI_CONF["square_size"].as_i64
|
||||
|
||||
# GALAXY = SF::Texture.from_file("assets/#{GALAXY_WIDTH}x#{GALAXY_HEIGHT}/galaxy.jpg")
|
||||
GALAXY = SF::Texture.from_file("assets/#{GALAXY_WIDTH}x#{GALAXY_HEIGHT}/galaxy.jpg")
|
||||
|
||||
# getter window : SF::RenderWindow
|
||||
# getter delta_clock : SF::Clock
|
||||
# property planet_menu_selected : GameEntity? = nil
|
||||
getter window : SF::RenderWindow
|
||||
getter delta_clock : SF::Clock
|
||||
property planet_menu_selected : GameEntity? = nil
|
||||
|
||||
# def initialize
|
||||
# @window = SF::RenderWindow.new(
|
||||
# SF::VideoMode.new(UI_WIDTH, UI_HEIGHT),
|
||||
# "To the End of The Universe",
|
||||
# )
|
||||
# @delta_clock = SF::Clock.new
|
||||
# end
|
||||
def initialize
|
||||
@window = SF::RenderWindow.new(
|
||||
SF::VideoMode.new(UI_WIDTH, UI_HEIGHT),
|
||||
"To the End of The Universe",
|
||||
)
|
||||
@delta_clock = SF::Clock.new
|
||||
end
|
||||
|
||||
# def [](k)
|
||||
# @data[k]
|
||||
# end
|
||||
# end
|
||||
def [](k)
|
||||
@data[k]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,3 +15,17 @@ module TETU::Helpers::Numbers
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
struct Number
|
||||
def billions
|
||||
self * TETU::Helpers::Numbers::BILLION
|
||||
end
|
||||
|
||||
def millions
|
||||
self * TETU::Helpers::Numbers::MILLION
|
||||
end
|
||||
|
||||
def thousands
|
||||
self * TETU::Helpers::Numbers::THOUSAND
|
||||
end
|
||||
end
|
||||
|
|
23
src/helpers/ratio.cr
Normal file
23
src/helpers/ratio.cr
Normal file
|
@ -0,0 +1,23 @@
|
|||
module Ratio
|
||||
alias IdValue = Hash(String, Number)
|
||||
|
||||
# minimum ratio of right/left, or nil if missing keys in right
|
||||
def self.minimum_reverse(left : IdValue, right : IdValue)
|
||||
return nil if left.keys & right.keys != left.keys
|
||||
|
||||
left.map do |id, left_value|
|
||||
right_value = right[id]
|
||||
right_value / left_value
|
||||
end.min
|
||||
end
|
||||
|
||||
# minimum ratio of left/right, or nil if missing keys in right
|
||||
def self.minimum(left : IdValue, right : IdValue)
|
||||
return nil if left.keys & right.keys != left.keys
|
||||
|
||||
left.map do |id, left_value|
|
||||
right_value = right[id]
|
||||
left_value / right_value
|
||||
end.min
|
||||
end
|
||||
end
|
|
@ -1,2 +1,2 @@
|
|||
require "./systems/*"
|
||||
require "./systems/player/*"
|
||||
require "./systems/ui/*"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require "../components"
|
||||
|
||||
class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
spoved_logger level: :debug, io: STDOUT, bind: true
|
||||
|
||||
def initialize(@contexts : Contexts)
|
||||
@time_context = @contexts.time
|
||||
|
@ -21,9 +21,14 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
|||
|
||||
producer_group = @contexts.game.get_group Entitas::Matcher.all_of(Resources, Population, ManpowerAllocation)
|
||||
producer_group.entities.each do |e|
|
||||
next if !e.resources.can_produce?
|
||||
if !e.resources.can_produce?
|
||||
logger.debug { "#{e.named} cannot produces" }
|
||||
next
|
||||
end
|
||||
logger.debug { "#{e.named} produces now" }
|
||||
|
||||
e.resources.infras.each do |infra_id, infra|
|
||||
logger.debug { "#{e.named.to_s} produces now via #{infra.id}" }
|
||||
rate = prod_rate(infra, e)
|
||||
prod_rates = infra.prods.map { |res, prod| apply_prod(infra: infra, res: res, rate: rate, prod: prod) }
|
||||
real_prod_rate = prod_rates.empty? ? rate : prod_rates.max
|
||||
|
@ -42,8 +47,16 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
|||
end
|
||||
|
||||
def prod_rate(infra : Resources::Infra, producer : GameEntity) : Float64
|
||||
return 1.0 if infra.consumes.empty?
|
||||
return 0.0 if infra.consumes.any? { |res, _value| infra.stores[res]?.nil? }
|
||||
if infra.consumes.empty?
|
||||
logger.debug { "no consumption, rate 1.0" }
|
||||
return 1.0
|
||||
end
|
||||
|
||||
if infra.consumes.any? { |res, _value| infra.stores[res]?.nil? }
|
||||
logger.debug { "missing consumable, 0.0" }
|
||||
return 0.0
|
||||
end
|
||||
|
||||
# TODO: another function for pop.amount < manpower.optimal
|
||||
allocated_manpower = producer.manpower_allocation.absolute[infra.id]
|
||||
maximal_rate =
|
||||
|
@ -54,14 +67,15 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
|||
end
|
||||
limited_rate = (infra.consumes.map { |res, value| infra.stores[res].amount / value } + [maximal_rate]).min
|
||||
# if infra.id == "mine" || true
|
||||
# logger.debug { "producer.named.name=#{producer.named.name}" }
|
||||
# logger.debug { "infra.id=#{infra.id}" }
|
||||
# logger.debug { "allocated_manpower=#{allocated_manpower}" }
|
||||
# logger.debug { "infra.manpower.optimal=#{infra.manpower.optimal}" }
|
||||
# logger.debug { "infra.manpower.min=#{infra.manpower.min} " }
|
||||
# logger.debug { "maximal_rate=#{maximal_rate} " }
|
||||
# logger.debug { "limited_rate=#{limited_rate}" }
|
||||
# logger.debug { "" }
|
||||
logger.debug { "" }
|
||||
logger.debug { "producer.named.name=#{producer.named.name}" }
|
||||
logger.debug { "infra.id=#{infra.id}" }
|
||||
logger.debug { "allocated_manpower=#{allocated_manpower}" }
|
||||
logger.debug { "infra.manpower.optimal=#{infra.manpower.optimal}" }
|
||||
logger.debug { "infra.manpower.min=#{infra.manpower.min} " }
|
||||
logger.debug { "maximal_rate=#{maximal_rate} " }
|
||||
logger.debug { "limited_rate=#{limited_rate}" }
|
||||
logger.debug { "" }
|
||||
# end
|
||||
limited_rate
|
||||
end
|
||||
|
@ -69,10 +83,10 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
|||
# returns the real production rate, limited by the storage
|
||||
# @param rate : the maximum production we should use
|
||||
def apply_prod(infra : Resources::Infra, rate : Float64, res : Resources::Name, prod : Float64) : Float64
|
||||
# logger.debug { "apply_prod wants rate=#{rate} res=#{res} prod=#{prod}" }
|
||||
logger.debug { "apply_prod wants rate=#{rate} res=#{res} prod=#{prod}" }
|
||||
store = infra.stores[res]?
|
||||
if store.nil? || rate > 0 && store.amount == store.max
|
||||
# logger.debug { "apply_prod applied rate=0.0 res=#{res} prod=#{prod}" }
|
||||
logger.debug { "max storage: apply_prod applied rate=0.0 res=#{res} prod=#{prod}" }
|
||||
return 0.0
|
||||
end
|
||||
|
||||
|
@ -88,7 +102,7 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
|||
|
||||
store.amount = new_amount
|
||||
|
||||
# logger.debug { "apply_prod applied rate=#{rate} res=#{res} prod=#{prod}" }
|
||||
logger.debug { "ok: apply_prod applied rate=#{rate} res=#{res} prod=#{prod}, store=#{new_amount}" }
|
||||
|
||||
return rate
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class TETU::GalaxyInitializerSystem
|
||||
include Entitas::Systems::InitializeSystem
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
spoved_logger level: :debug, io: STDOUT, bind: true
|
||||
|
||||
def initialize(@contexts : Contexts); end
|
||||
|
||||
|
@ -9,6 +9,7 @@ class TETU::GalaxyInitializerSystem
|
|||
EMPIRE_AMOUNT = AI_AMOUNT + 1 # add the player
|
||||
AI_MIN_PLANETS = GALAXY_CONF["ai_start_populated_bodies_amount"].as_i
|
||||
PLANET_POP_PROBA = TETU::GALAXY_CONF["populated_planets_proba"].as_f
|
||||
AI_DEBUG_0_IS_PLANET = GALAXY_CONF["ai_debug_0_is_planet"].as_bool?
|
||||
# NO_SPACE_EMPIRE_ID = 100001
|
||||
|
||||
def init
|
||||
|
@ -44,9 +45,11 @@ class TETU::GalaxyInitializerSystem
|
|||
|
||||
bodies_amount = Helpers::Planet::BODIES_STATISTICS.sample
|
||||
bodies_amount = AI_MIN_PLANETS if !empire_id.nil? && bodies_amount < AI_MIN_PLANETS
|
||||
logger.debug { "generate bodies_amount=#{bodies_amount}" }
|
||||
|
||||
bodies_amount.times.map do |index|
|
||||
body_type = Helpers::Planet::TYPES_STATISTICS.sample
|
||||
body_type = :planet if AI_DEBUG_0_IS_PLANET && index == 0
|
||||
ids_trash[body_type] += 1
|
||||
body = generate_body(star: star, index: index, body_type: body_type, ids_trash: ids_trash)
|
||||
if body_type == :asteroid_belt
|
||||
|
@ -104,19 +107,32 @@ class TETU::GalaxyInitializerSystem
|
|||
body
|
||||
end
|
||||
|
||||
DEFAULT_INFRASTRUCTURES = %w[e_store m_store f_store e_plant mine farm a_store l_store a_plant l_plant]
|
||||
DEFAULT_INFRASTRUCTURES = {
|
||||
"e_store" => 2,
|
||||
"m_store" => 2,
|
||||
"f_store" => 5,
|
||||
"e_plant" => 2,
|
||||
"mine" => 2,
|
||||
"agrifood" => 5,
|
||||
"a_store" => 1,
|
||||
"l_store" => 1,
|
||||
"a_plant" => 1,
|
||||
"l_plant" => 1,
|
||||
}
|
||||
|
||||
def populate(body)
|
||||
# logger.debug { "populate: #{body.named.name}..." }
|
||||
pop_amount = ((10_000.0)..(10_000_000_000.0)).sample
|
||||
body.add_population amount: pop_amount
|
||||
pop_amount = ((10_000.0)..(10.0.billions)).sample
|
||||
Population.generate_for(body)
|
||||
body.replace_component(Resources.default_populated)
|
||||
body.add_infrastructure_upgrades
|
||||
body.add_manpower_allocation
|
||||
body.manpower_allocation.available = body.population.amount
|
||||
DEFAULT_INFRASTRUCTURES.each do |infra_id|
|
||||
upgrade = InfrastructureUpgrade.free_instant(id: infra_id)
|
||||
body.infrastructure_upgrades.upgrades << upgrade
|
||||
DEFAULT_INFRASTRUCTURES.each do |infra_id, infra_level|
|
||||
infra_level.times do
|
||||
upgrade = InfrastructureUpgrade.free_instant(id: infra_id)
|
||||
body.infrastructure_upgrades.upgrades << upgrade
|
||||
end
|
||||
end
|
||||
# logger.debug { "populated: #{body.named.name}, now #{body.resources.to_s}, with #{body.infrastructure_upgrades.upgrades.size} upgrade to do..." }
|
||||
body
|
||||
|
|
|
@ -81,14 +81,14 @@ class TETU::InfrastructureUpgradesSystem < Entitas::ReactiveSystem
|
|||
end
|
||||
|
||||
def pay_upgrade_tick(resources : Resources, upgrade : InfrastructureUpgrade, costs : InfrastructureUpgrade::Costs)
|
||||
if costs.all? { |res, amount| resources.stores[res].amount >= amount }
|
||||
if !(missing_resource = costs.find { |res, amount| resources.stores[res].amount < amount })
|
||||
# pay the upgrade with local store
|
||||
costs.all? { |res, amount| resources.stores[res].amount -= amount }
|
||||
upgrade.current_tick += 1
|
||||
logger.debug { "paid tick upgrade" }
|
||||
else
|
||||
# if we can't pay the upgrade, we will "loose" one tick due to maintenance
|
||||
logger.debug { "cannot pay upgrade" }
|
||||
logger.debug { "cannot pay upgrade because #{missing_resource}" }
|
||||
upgrade.end_tick += 1
|
||||
end
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
class TETU::RequestHandlerSystem
|
||||
include Entitas::Systems::ExecuteSystem
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
|
||||
getter player_channel : Channel(Tuple(String, Channel(String)))
|
||||
@contexts : Contexts
|
||||
|
||||
def initialize(@contexts, @player_channel)
|
||||
end
|
||||
|
||||
def execute
|
||||
message, responder = player_channel.receive
|
||||
if message == "stars"
|
||||
stars = @contexts.game.get_group Entitas::Matcher.all_of(Named, Position, CelestialBody, PlayerOwned).none_of(StellarPosition)
|
||||
responder.send stars.entities.map { |entity| entity.named.name }.join(",")
|
||||
else
|
||||
responder.send ""
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
require "../components"
|
||||
|
||||
class TETU::PopulationGrowthSystem < Entitas::ReactiveSystem
|
||||
spoved_logger level: :info, io: STDOUT, bind: true
|
||||
spoved_logger level: :debug, io: STDOUT, bind: true
|
||||
|
||||
def initialize(@contexts : Contexts)
|
||||
@time_context = @contexts.time
|
||||
|
@ -13,15 +13,62 @@ class TETU::PopulationGrowthSystem < Entitas::ReactiveSystem
|
|||
end
|
||||
|
||||
def execute(time_entities : Array(Entitas::IEntity))
|
||||
populateds = @contexts.game.get_group Entitas::Matcher.all_of(Population)
|
||||
populateds = @contexts.game.get_group Entitas::Matcher.all_of(Population, Resources)
|
||||
populateds.entities.each do |e|
|
||||
pop_amount = e.population.amount
|
||||
# let's say every adult make 1.5 child average in its average lifespan (80 years)
|
||||
# and one tick is one day
|
||||
reproduction_rate = 1.5 * (1.0/80.0) * (1.0/365)
|
||||
new_pop_amount = pop_amount + pop_amount * reproduction_rate
|
||||
# logger.debug { "population growth: {reproduction_rate:#{reproduction_rate}} {population:#{e.population.to_s}} {bonus:#{pop_amount * reproduction_rate}}" }
|
||||
e.replace_population(amount: new_pop_amount)
|
||||
foods = e.population.foods
|
||||
pop_foods_needs = foods.transform_values do |food_amount_per_pop|
|
||||
food_amount_per_pop * pop_amount
|
||||
end
|
||||
logger.debug { "pop_foods_needs=#{pop_foods_needs}" }
|
||||
logger.debug { "resouces=#{e.resources.stores}" }
|
||||
|
||||
minimum_food_ratio = Ratio.minimum_reverse(pop_foods_needs, e.resources.stores.amount_hash) || 0.0
|
||||
logger.debug { "minimum_food_ratio=#{minimum_food_ratio}" }
|
||||
total_food_modifier = food_modifier(minimum_food_ratio)
|
||||
logger.debug { "total_food_modifier=#{total_food_modifier}" }
|
||||
|
||||
new_pop_amount = pop_amount + pop_amount * (reproduction_rate(total_food_modifier))
|
||||
logger.debug { "population growth: #{pop_amount} * #{new_pop_amount / pop_amount} => #{new_pop_amount}" }
|
||||
e.replace_population(amount: new_pop_amount, foods: foods)
|
||||
pop_foods_needs.each do |food_id, food_need|
|
||||
# only consumes the need, only "theorical availability" counts, like a richness factor
|
||||
food_consumption = minimum_food_ratio < 1.0 ? food_need * minimum_food_ratio : food_need
|
||||
logger.debug { "population food consumption: #{food_id}:#{food_need} -> consumes #{food_consumption}" }
|
||||
e.resources.stores[food_id].amount -= food_consumption
|
||||
end if minimum_food_ratio > 0.0
|
||||
end
|
||||
end
|
||||
|
||||
# we also add a modifier based on food availability
|
||||
# that can be between -5 for famine with 0 food
|
||||
# +0.0 if food required is reach no less no more
|
||||
# up to 1.0 for post-scarcity inifity food (5x food required = +0.8)
|
||||
def food_modifier(available_food_ratio : Float64)
|
||||
return -5.0 if available_food_ratio <= 0.0
|
||||
modifier = 1.0 / -available_food_ratio + 1.0
|
||||
if modifier < -5.0
|
||||
-5.0 # not worse than -5
|
||||
else
|
||||
modifier
|
||||
end
|
||||
end
|
||||
|
||||
# let's say every adult make 1.5 child average in its average lifespan (80 years)
|
||||
# and one tick is one day (3 children per couple).
|
||||
def reproduction_rate(food_modifier) : Float64
|
||||
(children_per_pop_average + food_modifier) * (1.0/pop_lifespan_average) * (1.0/365.0)
|
||||
end
|
||||
|
||||
def pop_per_food_per_tick : Float64
|
||||
100.0.millions
|
||||
end
|
||||
|
||||
def children_per_pop_average : Float64
|
||||
1.5
|
||||
end
|
||||
|
||||
def pop_lifespan_average : Float64
|
||||
80.0
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,14 +8,23 @@ class TETU::UiService::PlanetInfrastructure < TETU::UiService
|
|||
|
||||
def draw
|
||||
if ImGui.tree_node_ex("resources panel", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
||||
ImGui.text @planet.named.name
|
||||
|
||||
draw_population
|
||||
draw_resources
|
||||
ImGui.tree_pop
|
||||
end
|
||||
end
|
||||
|
||||
private def draw_resources
|
||||
ImGui.text @planet.named.name
|
||||
private def draw_population
|
||||
ImGui.text "Population:" + if @planet.has_population?
|
||||
@planet.population.to_s(round: 4)
|
||||
else
|
||||
"None"
|
||||
end
|
||||
end
|
||||
|
||||
private def draw_resources
|
||||
if @planet.has_resources?
|
||||
draw_storage
|
||||
ImGui.text ""
|
||||
|
|
Loading…
Reference in New Issue
Block a user