From eb161e0f2e179d3b252be644e268dadcd3c255cb Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 12 Jan 2026 11:36:21 -0700 Subject: [PATCH] wrote basic building stuff, need to impliment more in the player. --- lib/game/building.rb | 134 +++++++++++++ lib/game/buildings.yaml | 163 --------------- lib/game/data/buildings.yaml | 328 +++++++++++++++++++++++++++++++ lib/game/{ => data}/modules.yaml | 0 lib/game/{ => data}/ships.yaml | 0 lib/game/game.rb | 15 +- lib/game/planet.rb | 1 - lib/game/ship.rb | 3 +- spec/app_spec.rb | 77 ++++++++ spec/game/building_spec.rb | 252 ++++++++++++++++++++++++ spec/game/game_spec.rb | 4 +- spec/game/planet_spec.rb | 7 + spec/game/player_spec.rb | 4 +- spec/game/ship_spec.rb | 2 + 14 files changed, 815 insertions(+), 175 deletions(-) create mode 100644 lib/game/building.rb delete mode 100644 lib/game/buildings.yaml create mode 100644 lib/game/data/buildings.yaml rename lib/game/{ => data}/modules.yaml (100%) rename lib/game/{ => data}/ships.yaml (100%) create mode 100644 spec/app_spec.rb create mode 100644 spec/game/building_spec.rb diff --git a/lib/game/building.rb b/lib/game/building.rb new file mode 100644 index 0000000..ab2fa86 --- /dev/null +++ b/lib/game/building.rb @@ -0,0 +1,134 @@ +# Nathan Hinton Jan 2026 +# Game file for the buildings + + +require 'securerandom' + +# Load the ships into a constant: +require 'yaml' +BUILDING_DATA = File.open('lib/game/data/buildings.yaml', 'r') do |fi| + YAML.safe_load(fi.read()) +end + + +class Building + attr_reader :name + + # Create a building. Requires a player and a planet to construct on. + # + # @param player [Player] The player who owns the building. + # @param planet [Planet] The planet the building should be built on. + # @param name [String] The name of the building to be built. + def initialize(player, planet, name) + @player = player + @planet = planet + @name = name + @building_exist_time = 0 + @building_last_production_time = 0 + # Return error if the building does not exist + @building = BUILDING_DATA[name] + if @building.nil? + raise BuildingError, "Invalid building name `#{name}`" + end + + # Check the player owns the planet + if planet.owner != player + raise BuildingError, 'Player does not own planet!' + end + + # Check the player has the resources + resources = ['metal', 'crystals', 'fuel', 'credits'] + resources.each do |resource| + if @player.player_resources[resource] < @building['build_cost'][resource] + raise BuildingError, "Player does not have enough #{resource}!" + end + end + + resources.each do |resource| + @player.player_resources[resource] -= @building['build_cost'][resource] + end + + # Check that the player owns the needed buildings to construct: + player_buildings = @player.all_buildings.map{|building| building.name} + @building['required_buildings'].each do |required_building| + unless player_buildings.include? required_building + raise BuildingError, "Requires `#{required_building}` first" + end + end + + # Check that the player owns the needed tech to construct: + player_tech = @player.all_tech.map{|tech| tech.name} + @building['required_tech'].each do |required_tech| + unless player_tech.include? required_tech + raise BuildingError, "Requires the tech of `#{required_tech}` first" + end + end + end + + # This is in charge of doing things like adding to the turn build power and + # providing resources from buildings. + def update(delta_time) + @building_exist_time += delta_time + # Incriment internal time count: + # Do not produce resources if the building is not finished + if @building_exist_time < @building['build_cost']['time'] + return # Exit + end + + # Do not produce resources if there are not enough resources for upkeep + resources = ['metal', 'crystals', 'fuel', 'credits'] + resources.each do |resource| + if @player.player_resources[resource * delta_time] < @building['upkeep'][resource * delta_time] + raise BuildingWarning, "Player does not have enough #{resource} to produce from #{@name}!" + end + end + + resources.each do |resource| + @player.player_resources[resource * delta_time] -= @building['upkeep'][resource * delta_time] + end + @building_last_production_time += delta_time + + # If the building is a resource production building we need to add the resources: + if @building['classification'] == 'mining' + if @building_last_production_time >= 1 + @building_last_production_time -= 1 + resources.each do |resource| + # This resource production should jump + @player.player_resources[resource] += @building['produces'][resource] + end + end + elsif @building['classification'] == 'production' + @player.ship_build_power += @building['produces']['ship_build_power'] + @player.research_tech_build_power += @building['produces']['research_tech_build_power'] + @player.military_tech_build_power += @building['produces']['military_tech_build_power'] + else + raise BuildingError, 'Unknown classification for building type. Trying to add resources etc...' + end + + end + + # Save a player to a file + def save_to_file + File.open("game_data/#{self.planet_id}.save", 'wb') do |fi| + # Load resources for the player: + fi.write(Marshal.dump(self)) + end + return self.planet_id + end + + # Load a player from a file + def self.load_from_save(fname) + File.open("game_data/#{fname}_ship_.save", 'rb') do |fi| + # Load resources for the player: + Marshal.load(fi.read) + end + end +end + +# Error class for the buildings +class BuildingError < StandardError +end + +# Warning class for the buildings +class BuildingWarning < StandardError +end diff --git a/lib/game/buildings.yaml b/lib/game/buildings.yaml deleted file mode 100644 index 1909a03..0000000 --- a/lib/game/buildings.yaml +++ /dev/null @@ -1,163 +0,0 @@ -# Nathan Hinton 2 Jan 2025 Buildings for the game -# - -# Production buildings: -Shipyard Level 1: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Shipyard Level 2: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Space Construction Station Level 1: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Space Construction Station Level 2: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Research Factory Level 1: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Research Factory Level 2: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Shipyard Level 1: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Shipyard Level 1: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Shipyard Level 1: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] - -Shipyard Level 1: - classification: production - faction: good - production: # This is what is required to produce the building: - metal: - crystals: - fuel: - credits: - time: - upkeep: - metal: - crystals: - fuel: - credits: - base_stats: [] diff --git a/lib/game/data/buildings.yaml b/lib/game/data/buildings.yaml new file mode 100644 index 0000000..bff0be3 --- /dev/null +++ b/lib/game/data/buildings.yaml @@ -0,0 +1,328 @@ +# Nathan Hinton 2 Jan 2025 Buildings for the game +# + +# Production buildings: +Shipyard Level 1: + classification: production + faction: good + build_cost: # This is what is required to produce the building: + metal: 1,000 + crystals: 1,000 + fuel: 1,000 + credits: 1,000 + time: 1,000 + upkeep: + metal: 100 + crystals: 100 + fuel: 100 + credits: 100 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + ship_build_power: 100 + research_tech_build_power: 0 + military_tech_build_power: 0 + required_tech: [] + required_buildings: [] + +Shipyard Level 2: + classification: production + faction: good + build_cost: # This is what is required to produce the building: + metal: 2,000 + crystals: 2,000 + fuel: 2,000 + credits: 2,000 + time: 2,000 + upkeep: + metal: 200 + crystals: 200 + fuel: 200 + credits: 200 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + ship_build_power: 200 + research_tech_build_power: 0 + military_tech_build_power: 0 + required_tech: [] + required_buildings: [Shipyard Level 1] + +Space Construction Station Level 1: + classification: production + faction: good + build_cost: # This is what is required to produce the building: + metal: 2,000 + crystals: 2,000 + fuel: 2,000 + credits: 2,000 + time: 2,000 + upkeep: + metal: 200 + crystals: 200 + fuel: 200 + credits: 200 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + ship_build_power: 200 + research_tech_build_power: 0 + military_tech_build_power: 0 + required_tech: [Anti Gravity Thrusters 1] + required_buildings: [Shipyard Level 1] + +Space Construction Station Level 2: + classification: production + faction: good + build_cost: # This is what is required to produce the building: + metal: 3,000 + crystals: 3,000 + fuel: 3,000 + credits: 3,000 + time: 3,000 + upkeep: + metal: 300 + crystals: 300 + fuel: 300 + credits: 300 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + ship_build_power: 300 + research_tech_build_power: 0 + military_tech_build_power: 0 + required_tech: [Anti Gravity Thrusters 2] + required_buildings: [Space Construction Station Level 1, Shipyard Level 2] + +Research Factory Level 1: + classification: production + faction: good + build_cost: # This is what is required to produce the building: + metal: 1,000 + crystals: 1,000 + fuel: 1,000 + credits: 1,000 + time: 1,000 + upkeep: + metal: 100 + crystals: 100 + fuel: 100 + credits: 100 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + ship_build_power: 0 + research_tech_build_power: 100 + military_tech_build_power: 0 + required_tech: [] + required_buildings: [] + +Research Factory Level 2: + classification: production + faction: good + build_cost: # This is what is required to produce the building: + metal: 2,000 + crystals: 2,000 + fuel: 2,000 + credits: 2,000 + time: 2,000 + upkeep: + metal: 200 + crystals: 200 + fuel: 200 + credits: 200 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + ship_build_power: 0 + research_tech_build_power: 200 + military_tech_build_power: 0 + required_tech: [] + required_buildings: [Research Factory Level 1] + +Military Factory Level 1: + classification: production + faction: good + build_cost: # This is what is required to produce the building: + metal: 2,000 + crystals: 2,000 + fuel: 2,000 + credits: 2,000 + time: 2,000 + upkeep: + metal: 200 + crystals: 200 + fuel: 200 + credits: 200 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + ship_build_power: 0 + research_tech_build_power: 0 + military_tech_build_power: 100 + required_tech: [] + required_buildings: [Research Factory Level 1] + +Military Factory Level 2: + classification: production + faction: good + build_cost: # This is what is required to produce the building: + metal: 3,000 + crystals: 3,000 + fuel: 3,000 + credits: 3,000 + time: 3,000 + upkeep: + metal: 300 + crystals: 300 + fuel: 300 + credits: 300 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + ship_build_power: 0 + research_tech_build_power: 0 + military_tech_build_power: 200 + required_tech: [] + required_buildings: [Military Factory Level 1] + +# Harvesting + +# Each building will produce the primary resource at %60, a secondary resource +# at %24, a terciary resource at %10 and a final resource at %6. Here is the +# rotation of how they are related: + +# metal -> crystal -> fuel -> credits + + +Metal Mines 1: + classification: mining + faction: good + build_cost: # This is what is required to produce the building: + metal: 1,000 + crystals: 1,000 + fuel: 1,000 + credits: 1,000 + time: 1,000 + upkeep: + metal: 100 + crystals: 100 + fuel: 100 + credits: 100 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + metal: 600 + crystals: 240 + fuel: 100 + credits: 60 + required_tech: [] + required_buildings: [] + +Crystal Collector 1: + classification: mining + faction: good + build_cost: # This is what is required to produce the building: + metal: 1,000 + crystals: 1,000 + fuel: 1,000 + credits: 1,000 + time: 1,000 + upkeep: + metal: 100 + crystals: 100 + fuel: 100 + credits: 100 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + metal: 60 + crystals: 600 + fuel: 240 + credits: 100 + required_tech: [] + required_buildings: [] + +Fuel Refinery 1: + classification: mining + faction: good + build_cost: # This is what is required to produce the building: + metal: 1,000 + crystals: 1,000 + fuel: 1,000 + credits: 1,000 + time: 1,000 + upkeep: + metal: 100 + crystals: 100 + fuel: 100 + credits: 100 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + metal: 100 + crystals: 60 + fuel: 600 + credits: 240 + required_tech: [] + required_buildings: [] + +Credit Center 1: + classification: mining + faction: good + build_cost: # This is what is required to produce the building: + metal: 1,000 + crystals: 1,000 + fuel: 1,000 + credits: 1,000 + time: 1,000 + upkeep: + metal: 100 + crystals: 100 + fuel: 100 + credits: 100 + base_stats: + hp: 1,000 + shield: 1,000 + armor: 30 + def: 30 + produces: + metal: 240 + crystals: 100 + fuel: 60 + credits: 600 + required_tech: [] + required_buildings: [] diff --git a/lib/game/modules.yaml b/lib/game/data/modules.yaml similarity index 100% rename from lib/game/modules.yaml rename to lib/game/data/modules.yaml diff --git a/lib/game/ships.yaml b/lib/game/data/ships.yaml similarity index 100% rename from lib/game/ships.yaml rename to lib/game/data/ships.yaml diff --git a/lib/game/game.rb b/lib/game/game.rb index 149cdee..22bcb56 100644 --- a/lib/game/game.rb +++ b/lib/game/game.rb @@ -104,6 +104,7 @@ module Game # @param client [TCPSocket] The client socket # @param command [Hash] JSON parsed command from the client. # @option command [String] :player_id Required, The player ID + # @option command [String] :faction Required, The faction the player should join as. # # @return [Hash] status hash that will be converted to JSON before being # sent back to the client @@ -231,10 +232,9 @@ module Game # @param msg [String] The error message # @return [String] JSON-encoded error response # - def error_response(msg) - { 'type' => 'error', 'payload' => msg }.to_json + def error_response(request_id, msg) + { 'type' => 'error', 'request_id', => id, 'payload' => msg }.to_json end - alias error_response error_response ## # Return a JSON object that tells a client about the current @@ -274,6 +274,9 @@ module Game delta_time * (player_count / GAME_SPEED_FACTOR) @game_time += delta_time + # Order here is important, We must update the buildings first (To + # accrew resources for this delta) Because planets hold the buildings + # they must be first. @planets.each do |planet| planet.update(delta_time) end @@ -282,9 +285,9 @@ module Game ship.update(delta_time) end - #@players.each do |player| - # player.update(delta_time) - #end + @players.each do |player| + player.update(delta_time) + end # Perform game saves (Or not for now...) if (@game_time) > next_save diff --git a/lib/game/planet.rb b/lib/game/planet.rb index 161e8d3..f958a66 100644 --- a/lib/game/planet.rb +++ b/lib/game/planet.rb @@ -42,7 +42,6 @@ class Planet # Function to return info to the client about a planet def info_update - raise NotImplementedError, "Need to write tests for this function" return { 'id' => @planet_id, 'position' => @position diff --git a/lib/game/ship.rb b/lib/game/ship.rb index f62a603..e57ac22 100644 --- a/lib/game/ship.rb +++ b/lib/game/ship.rb @@ -5,7 +5,7 @@ require 'securerandom' # Load the ships into a constant: require 'yaml' -SHIP_DATA = File.open('lib/game/ships.yaml', 'r') do |fi| +SHIP_DATA = File.open('lib/game/data/ships.yaml', 'r') do |fi| YAML.safe_load(fi.read()) end @@ -50,6 +50,7 @@ class Ship raise ShipError, "Player needs the building of '#{building_needed}' to build this ship" end end + return ship end # Teleport the ship to a given position. Really only should be used for admin diff --git a/spec/app_spec.rb b/spec/app_spec.rb new file mode 100644 index 0000000..87d2fb6 --- /dev/null +++ b/spec/app_spec.rb @@ -0,0 +1,77 @@ +# Nathan Hinton 11 Jan 2026 + +# Test file for the main game. This is testing that the client/server responses +# are performed correctly. +# +# Here is how the tests are structured: There are several fields that need to +# be filled in a request to the server. These fields are: type, domain, +# request_id, payload, and player_id. In these tests the requrest_id will be +# randomized and checked that the response matches to that id. The client may +# use these id's however they wish. If there is an id passed in with a request, +# the matching response will have the same id. +# +# The first describe level is the 'type' field. +# The second describe level is the 'domain' field. +# The third describe level is the 'payload' field. +# +# These tests are not intended to show *every* possible configuration and +# combination of commands. They are simplified commands that will test the +# basic workings of the server. They should be used as a guide for constructing +# commands sent to the server though and are helpful in defining the interface +# that we will use. +# +# (Applicable to the second half of the file) +# Note that the update, event and error types are meant to come *only* from +# the server and not from the client. This means that those responses are +# tested using other commands to create them. These are more 'behavioral' +# tests than just demonstrations of how to construct requests and the +# possible combinations of options. +# +# +# + +require 'socket' +require 'json' + +HOSTNAME = 'localhost' +PORT = 2000 + +RSpec.describe "app.rb" do + let(:client) {TCPSocket.open(HOSTNAME, PORT)} + it 'responds with an error when a non JSON request is sent' do + client.puts 'this is non json test data' + expect(JSON.parse(client.gets)).to eq({'type' => 'error', 'domain' => 'error', 'payload' => 'Server unable to decode JSON'}) + end + + describe 'type: hello' do + describe 'domain: player' do + describe 'payload: player_id, faction' do + it 'Connects the player:' do + client.puts({'type' => 'hello', 'domain' => 'player', 'payload' => {'player_id' => 'test_player', 'faction' => 'good'}}.to_json) + expect(JSON.parse(client.gets)).to eq('') + end + end + end + end + + describe 'type: query' do + end + + describe 'type: command' do + end + + # Note that the update, event and error types are meant to come *only* from + # the server and not from the client. This means that those responses are + # tested using other commands to create them. These are more 'behavioral' + # tests than just demonstrations of how to construct requests and the + # possible combinations of options. + + describe 'type: update' do + end + + describe 'type: event' do + end + + describe 'type: error' do + end +end diff --git a/spec/game/building_spec.rb b/spec/game/building_spec.rb new file mode 100644 index 0000000..e52ce8a --- /dev/null +++ b/spec/game/building_spec.rb @@ -0,0 +1,252 @@ +# Nathan Hinton Jan 8 2026 +# Test file for the buildings in the game + +require 'game/building' + +RSpec.describe Building do + let(:player) {instance_double( + 'Player', + ship_build_power: 0, + research_tech_build_power: 0, + military_tech_build_power: 0, + player_resources: { + 'metal' => 20_000, + 'crystals' => 20_000, + 'fuel' => 20_000, + 'credits' => 20_000}, + all_buildings: [], + all_tech: [] + )} + + let(:planet) {instance_double( + 'Planet', + owner: player + )} + + let(:anti_gravity_thrusters_1) {instance_double('Tech', + name: 'Anti Gravity Thrusters 1' + )} + let(:anti_gravity_thrusters_2) {instance_double('Tech', + name: 'Anti Gravity Thrusters 2' + )} + + + before(:all) do + end + + describe '#initialize & #validate_building' do + it 'Can construct any of level one mining buildings' do + expect{Building.new(player, planet, 'Metal Mines 1')}.to_not raise_error + end + + it 'Can construct basic factory buildings' do + expect{Building.new(player, planet, 'Shipyard Level 1')}.to_not raise_error + end + + it 'Can construct second level factory buildings' do + allow(player).to receive(:all_buildings).and_return([Building.new(player, planet, 'Shipyard Level 1')]) + expect{Building.new(player, planet, 'Shipyard Level 2')}.to_not raise_error + end + + it 'Can construct third level (dependency check) buildings' do + allow(player).to receive(:all_tech).and_return([anti_gravity_thrusters_1, anti_gravity_thrusters_2]) # Required tech for buildings + allow(player).to receive(:all_buildings).and_return([Building.new(player, planet, 'Shipyard Level 1')]) # Building the second level requires the first first :) + allow(player).to receive(:all_buildings).and_return([Building.new(player, planet, 'Shipyard Level 2'), Building.new(player, planet, 'Space Construction Station Level 1')]) + expect{Building.new(player, planet, 'Space Construction Station Level 2')}.to_not raise_error + end + + it 'Raises an error when trying to build a building that does not have the required buildings' do + expect{Building.new(player, planet, 'Shipyard Level 2')}.to raise_error(BuildingError, 'Requires `Shipyard Level 1` first') + end + + it 'Raises an error when trying to build a building that does not have the required tech' do + allow(player).to receive(:all_buildings).and_return([Building.new(player, planet, 'Shipyard Level 1')]) # Building the second level requires the first first :) + expect{Building.new(player, planet, 'Space Construction Station Level 1')}.to raise_error(BuildingError, 'Requires the tech of `Anti Gravity Thrusters 1` first') + end + + it 'Raises an error when the player does not own the planet' do + allow(planet).to receive(:owner).and_return(nil) + expect{Building.new(player, planet, 'Shipyard Level 1')}.to raise_error(BuildingError, 'Player does not own planet!') + end + + it 'Raises an error if the player does not have the required credits' do + allow(player).to receive(:player_resources).and_return( + { + 'metal' => 20_000, + 'crystals' => 20_000, + 'fuel' => 20_000, + 'credits' => 0} + ) + expect{Building.new(player, planet, 'Shipyard Level 1')}.to raise_error(BuildingError, 'Player does not have enough credits!') + end + + it 'Raises an error if the player does not have enough metal' do + allow(player).to receive(:player_resources).and_return( + { + 'metal' => 0, + 'crystals' => 20_000, + 'fuel' => 20_000, + 'credits' => 20_000} + ) + expect{Building.new(player, planet, 'Shipyard Level 1')}.to raise_error(BuildingError, 'Player does not have enough metal!') + end + + it 'Raises an error if the player does not have enough fuel' do + allow(player).to receive(:player_resources).and_return( + { + 'metal' => 20_000, + 'crystals' => 20_000, + 'fuel' => 0, + 'credits' => 20_000} + ) + expect{Building.new(player, planet, 'Shipyard Level 1')}.to raise_error(BuildingError, 'Player does not have enough fuel!') + end + + it 'Raises an error if the player does not have enough crystals' do + allow(player).to receive(:player_resources).and_return( + { + 'metal' => 20_000, + 'crystals' => 0, + 'fuel' => 20_000, + 'credits' => 20_000} + ) + expect{Building.new(player, planet, 'Shipyard Level 1')}.to raise_error(BuildingError, 'Player does not have enough crystals!') + end + + it 'Raises an error if the building does not exist' do + expect{Building.new(player, planet, 'billy bob joe')}.to raise_error(BuildingError, 'Invalid building name `billy bob joe`') + end + end + + describe '#update/#produce_resources' do + + before(:each) do + allow(player).to receive(:ship_build_power=) + allow(player).to receive(:research_tech_build_power=) + allow(player).to receive(:military_tech_build_power=) + end + + describe 'Harvesting building produces resources' do + it 'Metal Mines' do + building = Building.new(player, planet, 'Metal Mines 1') + # Ensure the building is built: + building.instance_variable_set(:@building_exist_time, 10000) + building.update(1.0) + expect(player.player_resources).to eq( + { + 'metal' => 19_500, + 'crystals' => 19_140, + 'fuel' => 19_000, + 'credits' => 18_960 + }) + end + it 'Crystal Collector' do + building = Building.new(player, planet, 'Crystal Collector 1') + # Ensure the building is built: + building.instance_variable_set(:@building_exist_time, 10000) + building.update(1.0) + expect(player.player_resources).to eq( + { + 'crystals' => 19_500, + 'fuel' => 19_140, + 'credits' => 19_000, + 'metal' => 18_960 + }) + end + it 'Fuel Refinery' do + building = Building.new(player, planet, 'Fuel Refinery 1') + # Ensure the building is built: + building.instance_variable_set(:@building_exist_time, 10000) + building.update(1.0) + expect(player.player_resources).to eq( + { + 'fuel' => 19_500, + 'credits' => 19_140, + 'metal' => 19_000, + 'crystals' => 18_960 + }) + end + it 'Credit Center' do + building = Building.new(player, planet, 'Credit Center 1') + # Ensure the building is built: + building.instance_variable_set(:@building_exist_time, 10000) + building.update(1.0) + expect(player.player_resources).to eq( + { + 'credits' => 19_500, + 'metal' => 19_140, + 'crystals' => 19_000, + 'fuel' => 18_960 + }) + end + end + + it 'Does not produce when there are not enough resources for upkeep' do + start_resources = { + 'metal' => 1_010, + 'crystals' => 1_020, + 'fuel' => 1_030, + 'credits' => 1_040} + + allow(player).to receive(:player_resources).and_return(start_resources) + building = Building.new(player, planet, 'Credit Center 1') + building.instance_variable_set(:@building_exist_time, 10000) + expect{building.update(1.0)}.to raise_error(BuildingWarning, 'Player does not have enough metal to produce from Credit Center 1!') + expect(player.player_resources).to eq(start_resources) + end + + it 'does not produce when it is not yet finished constructing' do + building = Building.new(player, planet, 'Metal Mines 1') + building.update(1.0) + expect(player.player_resources).to eq( + { + 'metal' => 19_000, + 'crystals' => 19_000, + 'fuel' => 19_000, + 'credits' => 19_000 + }) + end + + it 'Having shipyard buildings allows the production of ships' do + building = Building.new(player, planet, 'Shipyard Level 1') + building.instance_variable_set(:@building_exist_time, 10000) + building.update(1.0) + expect(player).to have_received(:ship_build_power=).with(100) + end + + it 'Having research tech buildings allows the production of tech' do + building = Building.new(player, planet, 'Research Factory Level 1') + building.instance_variable_set(:@building_exist_time, 10000) + building.update(1.0) + expect(player).to have_received(:research_tech_build_power=).with(100) + end + + it 'Having military tech buildings allows the production of military tech' do + allow(player).to receive(:all_buildings).and_return([Building.new(player, planet, 'Research Factory Level 1')]) + building = Building.new(player, planet, 'Military Factory Level 1') + building.instance_variable_set(:@building_exist_time, 10000) + building.update(1.0) + expect(player).to have_received(:military_tech_build_power=).with(100) + end + + it 'Is in production for the right amount of time' do + building = Building.new(player, planet, 'Metal Mines 1') + building.update(1000.0) + expect(player.player_resources).to eq( + { + 'metal' => 19_000, + 'crystals' => 19_000, + 'fuel' => 19_000, + 'credits' => 19_000 + }) + building.update(1.0) + expect(player.player_resources).to eq( + { + 'metal' => 19_500, + 'crystals' => 19_140, + 'fuel' => 19_000, + 'credits' => 18_960 + }) + end + end +end diff --git a/spec/game/game_spec.rb b/spec/game/game_spec.rb index a69f44b..0ad7bb9 100644 --- a/spec/game/game_spec.rb +++ b/spec/game/game_spec.rb @@ -70,7 +70,7 @@ module Game it 're-connects the player and returns success' do cmd = { 'payload' => { 'player_id' => player_id, 'faction' => 'good' } } - expect(game.player_connect(client_socket, cmd)).to eq('status' => 'success') + expect(game.player_connect(client_socket, cmd)).to eq({"payload"=>"success", "type"=>"update"}) expect(existing_player).to have_received(:connect).with(client_socket) end end @@ -103,7 +103,7 @@ module Game it 'creates a new player, connects them, and adds them to #players' do cmd = { 'payload' => { 'player_id' => player_id, 'faction' => 'evil'} } response = game.player_connect(client_socket, cmd) - expect(response).to eq('status' => 'success') + expect(response).to eq({"payload"=>"success", "type"=>"update"}) expect(game.players).to include(an_object_having_attributes(player_id:)) end end diff --git a/spec/game/planet_spec.rb b/spec/game/planet_spec.rb index 5112f09..9f1ef9e 100644 --- a/spec/game/planet_spec.rb +++ b/spec/game/planet_spec.rb @@ -61,6 +61,13 @@ RSpec.describe Planet do end end + describe '#info_update' do + it 'returns the correct data about the planet to the player' do + data = planet.info_update + expect(data.keys).to eq(['id', 'position']) + end + end + describe '#save_to_file' do it 'saves the planet to a file' do expect{planet.save_to_file}.to_not raise_error diff --git a/spec/game/player_spec.rb b/spec/game/player_spec.rb index ac74e6d..99312d5 100644 --- a/spec/game/player_spec.rb +++ b/spec/game/player_spec.rb @@ -166,7 +166,7 @@ RSpec.describe Player do before do subject.create_new(credits, metal, crystals, fuel) - subject.player_planets << double('planet') + subject.player_planets << double('planet', info_update: 'test_data') subject.player_ships << double('ship') end @@ -177,7 +177,7 @@ RSpec.describe Player do 'request_id' => request_id, 'payload' => { 'resources' => subject.player_resources, - 'planets' => subject.player_planets, + 'planets' => ['test_data'], 'ships' => subject.player_ships } } diff --git a/spec/game/ship_spec.rb b/spec/game/ship_spec.rb index a7beb6a..77ea732 100644 --- a/spec/game/ship_spec.rb +++ b/spec/game/ship_spec.rb @@ -10,7 +10,9 @@ RSpec.describe Ship do expect(ship.owner).to eq 'nathan' expect(ship.position).to eq [0, 2, 4] end + end + describe '#validate_creation' do it 'Fails to create a ship when the name is invalid' do expect{Ship.new('nathan', 0, 2, 4, 'billy bob joe')}.to raise_error ShipError, "invalid ship name 'billy bob joe'" end