Cleaning up and writing tests
This commit is contained in:
@@ -3,3 +3,4 @@
|
|||||||
**/.yard*
|
**/.yard*
|
||||||
**/logs
|
**/logs
|
||||||
**/*tmp*
|
**/*tmp*
|
||||||
|
**/coverage
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
--require spec_helper
|
||||||
+11
-3
@@ -21,7 +21,15 @@ task :serve do
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
desc 'Create the YARD documentation for the project'
|
namespace :doc do
|
||||||
task :doc do
|
desc 'Create the YARD documentation for the project'
|
||||||
res = sh 'yard doc *.rb'
|
task :yard do
|
||||||
|
res = sh 'yard doc *.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Create code coverage'
|
||||||
|
task :coverage do
|
||||||
|
ENV['COVERAGE'] = 'true'
|
||||||
|
Rake::Task[:test].invoke
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+9
-3
@@ -1,8 +1,14 @@
|
|||||||
# Nathan Hinton 1 Oct 2025
|
# Nathan Hinton 1 Oct 2025
|
||||||
|
|
||||||
|
# This file basiacally creates a universe and runs a simulated game in it.
|
||||||
|
|
||||||
# Mostly for testing right now:
|
|
||||||
require './universe'
|
require './universe'
|
||||||
|
require './player'
|
||||||
|
|
||||||
universe = Universe.new('test universe')
|
universe = Universe.new('test universe', universe_size: 3)
|
||||||
universe.print_universe(simple: true)
|
universe.create_universe
|
||||||
|
universe.print_universe()
|
||||||
|
planets = universe.planets
|
||||||
|
|
||||||
|
player = Player.new('test player')
|
||||||
|
puts player.name
|
||||||
|
|||||||
@@ -5,11 +5,28 @@ PLANET_PRODUCTION_RESOURCES = [:ships]
|
|||||||
# This class holds information about a planet.
|
# This class holds information about a planet.
|
||||||
class Planet
|
class Planet
|
||||||
attr_reader :owner
|
attr_reader :owner
|
||||||
|
|
||||||
# Create a planet
|
# Create a planet
|
||||||
|
#
|
||||||
|
# @param random [Random] The random generator for the game (ensures seeds can be duplicated)
|
||||||
def initialize(random)
|
def initialize(random)
|
||||||
@random = random
|
@random = random
|
||||||
@prod_rate = @random.rand
|
@prod_rate = @random.rand
|
||||||
@resource = PLANET_PRODUCTION_RESOURCES[(@random.rand * PLANET_PRODUCTION_RESOURCES.length).floor]
|
@resource = PLANET_PRODUCTION_RESOURCES[(@random.rand * PLANET_PRODUCTION_RESOURCES.length).floor]
|
||||||
@owner = nil
|
@owner = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Change the ownership of a planet. Should check for things like the planet
|
||||||
|
# has no defenders left and other fun stuff (later)
|
||||||
|
#
|
||||||
|
# @param owner [Player] The owner of the planet
|
||||||
|
# @return [Boolean] If the operation was successful or not
|
||||||
|
def change_owner(owner)
|
||||||
|
# For now only unclaimed planets can be owned
|
||||||
|
if @owner.nil?
|
||||||
|
@owner = owner
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Nathan Hinton 2 Oct 2025
|
||||||
|
|
||||||
|
|
||||||
|
# Class for the player. For now does not do much.
|
||||||
|
class Player
|
||||||
|
attr_reader :name
|
||||||
|
|
||||||
|
# initializes with just the player name.
|
||||||
|
#
|
||||||
|
# @param name [String] The name of the player
|
||||||
|
def initialize(name)
|
||||||
|
@name = name
|
||||||
|
end
|
||||||
|
end
|
||||||
+7
-1
@@ -16,7 +16,6 @@ class Position
|
|||||||
# initialize with a, b, c position
|
# initialize with a, b, c position
|
||||||
raise NotImplementedError.new('(a, b, c) positioning not implimented!')
|
raise NotImplementedError.new('(a, b, c) positioning not implimented!')
|
||||||
else
|
else
|
||||||
require 'byebug';debugger
|
|
||||||
raise ArgumentError.new('Creation of a position requires two or three integer args for a position.')
|
raise ArgumentError.new('Creation of a position requires two or three integer args for a position.')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -35,7 +34,14 @@ class Position
|
|||||||
return [x_pos, y_pos]
|
return [x_pos, y_pos]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the string formatted version
|
||||||
|
#
|
||||||
|
# @return [String] Returns a string representing the point.
|
||||||
def to_s
|
def to_s
|
||||||
return "(#{@x_pos}, #{y_pos})"
|
return "(#{@x_pos}, #{y_pos})"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
return other.x_pos == @x_pos && other.y_pos == @y_pos
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
# Nathan Hinton 2 Oct 2025
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Nathan Hinton 2 Oct 2025
|
||||||
|
|
||||||
|
require './planet'
|
||||||
|
|
||||||
|
require './player'
|
||||||
|
|
||||||
|
RSpec.describe Planet do
|
||||||
|
let(:seed) { 12345 }
|
||||||
|
let(:random) { Random.new(seed) }
|
||||||
|
let(:planet) { Planet.new(random) }
|
||||||
|
let(:player_1) {Player.new('tester 01') }
|
||||||
|
let(:player_2) {Player.new('tester 02') }
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'stores the random off' do
|
||||||
|
expect(planet.instance_variable_get(:@random)).to eq(random)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a production rate' do
|
||||||
|
expect(planet.instance_variable_get(:@prod_rate)).to eq(Random.new(seed).rand)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets a resource' do
|
||||||
|
expect(planet.instance_variable_get(:@resource)).to eq(PLANET_PRODUCTION_RESOURCES[(Random.new(seed).rand * PLANET_PRODUCTION_RESOURCES.length).floor])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a nil owner' do
|
||||||
|
expect(planet.instance_variable_get(:@owner)).to eq nil
|
||||||
|
end
|
||||||
|
end # describe #initialize
|
||||||
|
|
||||||
|
describe '#change_owner' do
|
||||||
|
it 'allows an ownership change when the current owner is nil' do
|
||||||
|
expect(planet.change_owner(player_1)).to eq(true)
|
||||||
|
expect(planet.instance_variable_get(:@owner)).to eq player_1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not change when already owned' do
|
||||||
|
planet.change_owner(player_2)
|
||||||
|
expect(planet.change_owner(player_1)).to eq(false)
|
||||||
|
expect(planet.instance_variable_get(:@owner)).to eq player_2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require './position'
|
||||||
|
|
||||||
|
RSpec.describe Position do
|
||||||
|
let(:even_pair) { [4, 6] } # 4+6 = 10 → even
|
||||||
|
let(:odd_pair) { [3, 4] } # 3+4 = 7 → odd
|
||||||
|
|
||||||
|
describe 'initialisation' do
|
||||||
|
context 'with two arguments' do
|
||||||
|
it 'stores the coordinates when the sum is even' do
|
||||||
|
pos = Position.new(*even_pair)
|
||||||
|
expect(pos.x_pos).to eq(even_pair[0])
|
||||||
|
expect(pos.y_pos).to eq(even_pair[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises ArgumentError when the sum is odd' do
|
||||||
|
expect { Position.new(*odd_pair) }
|
||||||
|
.to raise_error(ArgumentError, /must sum to an even number/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with three arguments' do
|
||||||
|
it 'raises NotImplementedError' do
|
||||||
|
expect { Position.new(1, 2, 3) }
|
||||||
|
.to raise_error(NotImplementedError, /positioning not implimented/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an unsupported number of arguments' do
|
||||||
|
it 'raises ArgumentError when no arguments are supplied' do
|
||||||
|
expect { Position.new }
|
||||||
|
.to raise_error(ArgumentError, /requires two or three integer args/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises ArgumentError when too many arguments are supplied' do
|
||||||
|
expect { Position.new(1, 2, 3, 4) }
|
||||||
|
.to raise_error(ArgumentError, /requires two or three integer args/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#two_d_cartesian' do
|
||||||
|
it 'returns the original (x, y) pair' do
|
||||||
|
pos = Position.new(*even_pair)
|
||||||
|
expect(pos.two_d_cartesian).to eq(even_pair)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#two_d_hexagonal' do
|
||||||
|
it 'returns an array of nil values for a, b, c' do
|
||||||
|
pos = Position.new(*even_pair)
|
||||||
|
expect(pos.two_d_hexagonal).to eq([nil, nil, nil])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_s' do
|
||||||
|
it 'puts out the coordinates' do
|
||||||
|
expect(Position.new(*even_pair).to_s).to eq('(4, 6)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#==' do
|
||||||
|
it 'returns false when the positions differ' do
|
||||||
|
expect(Position.new(*even_pair) == Position.new(6, 8)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when the positions are the same' do
|
||||||
|
expect(Position.new(*even_pair) == Position.new(4, 6)).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+24
-31
@@ -1,17 +1,13 @@
|
|||||||
# Nathan Hinton 2 Oct 2025
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Test file for the ship object
|
require_relative '../ship' # <-- adjust to the actual path of ship.rb
|
||||||
|
|
||||||
require './ship'
|
|
||||||
|
|
||||||
RSpec.describe Ship do
|
RSpec.describe Ship do
|
||||||
let(:initial_x) { 0 }
|
let(:ship) { Ship.new(0, 0) }
|
||||||
let(:initial_y) { 0 }
|
|
||||||
subject(:ship) { Ship.new(initial_x, initial_y) }
|
|
||||||
|
|
||||||
describe '#initialize' do
|
describe '#initialize' do
|
||||||
it 'stores the given position' do
|
it 'stores the given position' do
|
||||||
expect(ship.instance_variable_get(:@position)).to eq([initial_x, initial_y])
|
expect(ship.instance_variable_get(:@position)).to eq([0, 0])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the speed to 0.1' do
|
it 'sets the speed to 0.1' do
|
||||||
@@ -24,23 +20,22 @@ RSpec.describe Ship do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#tick' do
|
describe '#tick' do
|
||||||
it 'does nothing (but does not raise an exception)' do
|
it 'does nothing (no exception) when called' do
|
||||||
expect { ship.tick }.not_to raise_error
|
expect { ship.tick }.not_to raise_error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#move' do
|
describe '#move' do
|
||||||
context 'when coordinates are valid' do
|
context 'when coordinates are valid (even sum, adjacent)' do
|
||||||
it 'accepts adjacent moves whose sum is even' do
|
it 'accepts positions that satisfy the rules' do
|
||||||
# adjacent positions that satisfy the rules
|
expect { ship.move(1, 1) }.not_to raise_error # sum 2
|
||||||
expect { ship.move(1, 1) }.not_to raise_error # sum = 2
|
expect { ship.move(2, 0) }.not_to raise_error # sum 2
|
||||||
expect { ship.move(2, 0) }.not_to raise_error # sum = 2
|
expect { ship.move(0, 2) }.not_to raise_error # sum 2
|
||||||
expect { ship.move(0, 2) }.not_to raise_error # sum = 2
|
expect { ship.move(-1, -1) }.not_to raise_error # sum -2
|
||||||
expect { ship.move(-1, -1) }.not_to raise_error # sum = -2
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the x + y sum is odd' do
|
context 'when the sum is odd' do
|
||||||
it 'raises an ArgumentError' do
|
it 'raises an ArgumentError' do
|
||||||
expect { ship.move(0, 1) }.to raise_error(
|
expect { ship.move(0, 1) }.to raise_error(
|
||||||
ArgumentError,
|
ArgumentError,
|
||||||
@@ -51,7 +46,6 @@ RSpec.describe Ship do
|
|||||||
|
|
||||||
context 'when the target is not adjacent' do
|
context 'when the target is not adjacent' do
|
||||||
it 'raises an ArgumentError' do
|
it 'raises an ArgumentError' do
|
||||||
# distance > 1: difference of sums != 2
|
|
||||||
expect { ship.move(3, 3) }.to raise_error(
|
expect { ship.move(3, 3) }.to raise_error(
|
||||||
ArgumentError,
|
ArgumentError,
|
||||||
/must be to an adjcent space/
|
/must be to an adjcent space/
|
||||||
@@ -65,23 +59,22 @@ RSpec.describe Ship do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#move_direction' do
|
describe '#move_direction' do
|
||||||
it 'accepts direction values between 0 and 5' do
|
it 'accepts direction values 0..5 and sets @moving correctly' do
|
||||||
(0..5).each do |direction|
|
mapping = {
|
||||||
expect { ship.move_direction(direction) }.not_to raise_error
|
0 => [2, 0],
|
||||||
# verify the @moving vector matches the spec
|
1 => [1, 1],
|
||||||
expected = case direction
|
2 => [-1, 1],
|
||||||
when 0 then [2, 0]
|
3 => [-2, 0],
|
||||||
when 1 then [1, 1]
|
4 => [-1, -1],
|
||||||
when 2 then [-1, 1]
|
5 => [1, -1]
|
||||||
when 3 then [-2, 0]
|
}
|
||||||
when 4 then [-1, -1]
|
mapping.each do |dir, expected|
|
||||||
when 5 then [1, -1]
|
expect { ship.move_direction(dir) }.not_to raise_error
|
||||||
end
|
|
||||||
expect(ship.instance_variable_get(:@moving)).to eq(expected)
|
expect(ship.instance_variable_get(:@moving)).to eq(expected)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an ArgumentError for invalid directions' do
|
it 'raises ArgumentError for invalid directions' do
|
||||||
expect { ship.move_direction(-1) }.to raise_error(
|
expect { ship.move_direction(-1) }.to raise_error(
|
||||||
ArgumentError,
|
ArgumentError,
|
||||||
/must be between \(0..5\)/
|
/must be between \(0..5\)/
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
||||||
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
||||||
|
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
||||||
|
# this file to always be loaded, without a need to explicitly require it in any
|
||||||
|
# files.
|
||||||
|
#
|
||||||
|
# Given that it is always loaded, you are encouraged to keep this file as
|
||||||
|
# light-weight as possible. Requiring heavyweight dependencies from this file
|
||||||
|
# will add to the boot time of your test suite on EVERY test run, even for an
|
||||||
|
# individual file that may not need all of that loaded. Instead, consider making
|
||||||
|
# a separate helper file that requires the additional dependencies and performs
|
||||||
|
# the additional setup, and require it from the spec files that actually need
|
||||||
|
# it.
|
||||||
|
#
|
||||||
|
# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
||||||
|
|
||||||
|
if ENV['COVERAGE'] == 'true'
|
||||||
|
require 'simplecov'
|
||||||
|
SimpleCov.start
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
# rspec-expectations config goes here. You can use an alternate
|
||||||
|
# assertion/expectation library such as wrong or the stdlib/minitest
|
||||||
|
# assertions if you prefer.
|
||||||
|
config.expect_with :rspec do |expectations|
|
||||||
|
# This option will default to `true` in RSpec 4. It makes the `description`
|
||||||
|
# and `failure_message` of custom matchers include text for helper methods
|
||||||
|
# defined using `chain`, e.g.:
|
||||||
|
# be_bigger_than(2).and_smaller_than(4).description
|
||||||
|
# # => "be bigger than 2 and smaller than 4"
|
||||||
|
# ...rather than:
|
||||||
|
# # => "be bigger than 2"
|
||||||
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# rspec-mocks config goes here. You can use an alternate test double
|
||||||
|
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
||||||
|
config.mock_with :rspec do |mocks|
|
||||||
|
# Prevents you from mocking or stubbing a method that does not exist on
|
||||||
|
# a real object. This is generally recommended, and will default to
|
||||||
|
# `true` in RSpec 4.
|
||||||
|
mocks.verify_partial_doubles = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
||||||
|
# have no way to turn it off -- the option exists only for backwards
|
||||||
|
# compatibility in RSpec 3). It causes shared context metadata to be
|
||||||
|
# inherited by the metadata hash of host groups and examples, rather than
|
||||||
|
# triggering implicit auto-inclusion in groups with matching metadata.
|
||||||
|
config.shared_context_metadata_behavior = :apply_to_host_groups
|
||||||
|
|
||||||
|
# The settings below are suggested to provide a good initial experience
|
||||||
|
# with RSpec, but feel free to customize to your heart's content.
|
||||||
|
=begin
|
||||||
|
# This allows you to limit a spec run to individual examples or groups
|
||||||
|
# you care about by tagging them with `:focus` metadata. When nothing
|
||||||
|
# is tagged with `:focus`, all examples get run. RSpec also provides
|
||||||
|
# aliases for `it`, `describe`, and `context` that include `:focus`
|
||||||
|
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
||||||
|
config.filter_run_when_matching :focus
|
||||||
|
|
||||||
|
# Allows RSpec to persist some state between runs in order to support
|
||||||
|
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
||||||
|
# you configure your source control system to ignore this file.
|
||||||
|
config.example_status_persistence_file_path = "spec/examples.txt"
|
||||||
|
|
||||||
|
# Limits the available syntax to the non-monkey patched syntax that is
|
||||||
|
# recommended. For more details, see:
|
||||||
|
# https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/
|
||||||
|
config.disable_monkey_patching!
|
||||||
|
|
||||||
|
# This setting enables warnings. It's recommended, but in some cases may
|
||||||
|
# be too noisy due to issues in dependencies.
|
||||||
|
config.warnings = true
|
||||||
|
|
||||||
|
# Many RSpec users commonly either run the entire suite or an individual
|
||||||
|
# file, and it's useful to allow more verbose output when running an
|
||||||
|
# individual spec file.
|
||||||
|
if config.files_to_run.one?
|
||||||
|
# Use the documentation formatter for detailed output,
|
||||||
|
# unless a formatter has already been configured
|
||||||
|
# (e.g. via a command-line flag).
|
||||||
|
config.default_formatter = "doc"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print the 10 slowest examples and example groups at the
|
||||||
|
# end of the spec run, to help surface which specs are running
|
||||||
|
# particularly slow.
|
||||||
|
config.profile_examples = 10
|
||||||
|
|
||||||
|
# Run specs in random order to surface order dependencies. If you find an
|
||||||
|
# order dependency and want to debug it, you can fix the order by providing
|
||||||
|
# the seed, which is printed after each run.
|
||||||
|
# --seed 1234
|
||||||
|
config.order = :random
|
||||||
|
|
||||||
|
# Seed global randomization in this process using the `--seed` CLI option.
|
||||||
|
# Setting this allows you to use `--seed` to deterministically reproduce
|
||||||
|
# test failures related to randomization by passing the same `--seed` value
|
||||||
|
# as the one that triggered the failure.
|
||||||
|
Kernel.srand config.seed
|
||||||
|
=end
|
||||||
|
end
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# If you are getting an error `uninitialized constant StringIO` make sure that
|
||||||
|
# your code is in an `it` block.
|
||||||
|
|
||||||
|
# Helper that captures STDOUT.
|
||||||
|
def capture_stdout
|
||||||
|
output = StringIO.new
|
||||||
|
$stdout = output
|
||||||
|
yield
|
||||||
|
output.string
|
||||||
|
ensure
|
||||||
|
$stdout = STDOUT
|
||||||
|
end
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Nathan Hinton 2 Oct 2025
|
||||||
|
|
||||||
|
|
||||||
|
require './universe_array'
|
||||||
|
|
||||||
|
RSpec.describe UniverseArray do
|
||||||
|
describe '#[]' do
|
||||||
|
it 'works with indexes' do
|
||||||
|
uni_arr = UniverseArray.new()
|
||||||
|
uni_arr[0] = []
|
||||||
|
uni_arr[0][0] = 'hi'
|
||||||
|
expect(uni_arr[0][0]).to eq 'hi'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works with positions' do
|
||||||
|
uni_arr = UniverseArray.new()
|
||||||
|
uni_arr[0] = []
|
||||||
|
uni_arr[0][0] = 'hi'
|
||||||
|
expect(uni_arr[Position.new(0, 0)]).to eq 'hi'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#[]=' do
|
||||||
|
it 'works with indexes' do
|
||||||
|
uni_arr = UniverseArray.new()
|
||||||
|
uni_arr[0] = []
|
||||||
|
uni_arr[0][0] = 'hi'
|
||||||
|
expect(uni_arr[0][0]).to eq 'hi'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works with positions' do
|
||||||
|
uni_arr = UniverseArray.new()
|
||||||
|
uni_arr[0] = []
|
||||||
|
uni_arr[Position.new(0, 0)] = 'hi'
|
||||||
|
expect(uni_arr[Position.new(0, 0)]).to eq 'hi'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require './universe'
|
||||||
|
require './planet'
|
||||||
|
require './position'
|
||||||
|
require './universe_array'
|
||||||
|
|
||||||
|
require './spec/stdout_helper'
|
||||||
|
|
||||||
|
RSpec.describe Universe do
|
||||||
|
|
||||||
|
let(:name) { 'Test Universe' }
|
||||||
|
let(:universe_size) { 3 }
|
||||||
|
let(:seed) { 12345 }
|
||||||
|
|
||||||
|
subject(:universe) do
|
||||||
|
Universe.new(
|
||||||
|
name,
|
||||||
|
universe_size: universe_size,
|
||||||
|
seed: seed,
|
||||||
|
create_chances: { planet: 1 } # force a planet at every valid spot
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'stores all expected instance variables' do
|
||||||
|
expect(universe.name).to eq(name)
|
||||||
|
expect(universe.instance_variable_get(:@universe_size)).to eq(universe_size)
|
||||||
|
expect(universe.instance_variable_get(:@seed)).to eq(seed)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a deterministic Random object when a seed is given' do
|
||||||
|
rng = universe.instance_variable_get(:@random)
|
||||||
|
expect(rng).to be_a(Random)
|
||||||
|
expect(rng.rand(100)).to eq(Random.new(seed).rand(100))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'initializes the random if no seed is passed in' do
|
||||||
|
uni = Universe.new(name)
|
||||||
|
rng = uni.instance_variable_get(:@random)
|
||||||
|
expect(rng).to be_a(Random)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#create_universe' do
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#distance' do
|
||||||
|
it 'is symmetric' do
|
||||||
|
a = Position.new(0, 0)
|
||||||
|
b = Position.new(4, 2)
|
||||||
|
expect(universe.distance(a, b)).to eq(universe.distance(b, a))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 0 when both points are identical' do
|
||||||
|
p = Position.new(1, 1)
|
||||||
|
expect(universe.distance(p, p)).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'correctly computes known hex distances' do
|
||||||
|
p0 = Position.new(0, 0)
|
||||||
|
p1 = Position.new(2, 0)
|
||||||
|
p2 = Position.new(4, 2)
|
||||||
|
p3 = Position.new(2, 2)
|
||||||
|
# (0,0) to (2,0) → 1
|
||||||
|
expect(universe.distance(p0, p1)).to eq(1)
|
||||||
|
# (0,0) to (4,2) → 3
|
||||||
|
expect(universe.distance(p0, p2)).to eq(3)
|
||||||
|
# (0,0) to (2,2) → 2
|
||||||
|
expect(universe.distance(p0, p3)).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#map generation' do
|
||||||
|
it 'creates the expected dimensions' do
|
||||||
|
universe.create_universe
|
||||||
|
map = universe.instance_variable_get(:@universe_map)
|
||||||
|
# Each valid (x,y) pair inside the radius contains a Planet
|
||||||
|
# (our stub) and every other entry is nil.
|
||||||
|
expect(map).to be_a(UniverseArray)
|
||||||
|
# Check how may nodes were created:
|
||||||
|
expect(map.flatten.compact.length).to eq(19) # 1 + 1*6 + 2*6
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'contains a planet at every valid point when create_chances is 1' do
|
||||||
|
universe.create_universe
|
||||||
|
map = universe.instance_variable_get(:@universe_map)
|
||||||
|
map.each do |row|
|
||||||
|
row.each do |idx|
|
||||||
|
# The indexes are allowed to be nil
|
||||||
|
if !idx.nil?
|
||||||
|
expect(idx).to be_a(Planet)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is deterministic when the same seed is used' do
|
||||||
|
first_universe = Universe.new(
|
||||||
|
name,
|
||||||
|
universe_size: universe_size,
|
||||||
|
seed: seed,
|
||||||
|
create_chances: { planet: 0.1 }
|
||||||
|
)
|
||||||
|
first_map = first_universe.instance_variable_get(:@universe_map)
|
||||||
|
|
||||||
|
second_universe = Universe.new(
|
||||||
|
name,
|
||||||
|
universe_size: universe_size,
|
||||||
|
seed: seed,
|
||||||
|
create_chances: { planet: 0.1 }
|
||||||
|
)
|
||||||
|
second_map = second_universe.instance_variable_get(:@universe_map)
|
||||||
|
|
||||||
|
expect(first_map).to eq(second_map)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#print_universe' do
|
||||||
|
it 'prints the center point as “O”' do
|
||||||
|
out = capture_stdout do
|
||||||
|
universe.create_universe
|
||||||
|
universe.print_universe
|
||||||
|
end
|
||||||
|
expect(out).to include('O')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can print blank spaces for nothing' do
|
||||||
|
out = capture_stdout do
|
||||||
|
uni = Universe.new(name, create_chances: {planet: 0.0})
|
||||||
|
uni.create_universe
|
||||||
|
uni.print_universe
|
||||||
|
end
|
||||||
|
expect(out).to include('O')
|
||||||
|
expect(out).to include('- -')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#planets' do
|
||||||
|
it 'lists all of the generated planets' do
|
||||||
|
universe.create_universe
|
||||||
|
expect(universe.planets.length).to eq(19)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+73
-16
@@ -48,9 +48,10 @@ require './universe_array'
|
|||||||
# @param universe_size [Integer] Controlls the size of the generated universe. This is a radius from the center point.
|
# @param universe_size [Integer] Controlls the size of the generated universe. This is a radius from the center point.
|
||||||
class Universe
|
class Universe
|
||||||
# Setup logger for this class:
|
# Setup logger for this class:
|
||||||
sh 'mkdir -p logs'
|
|
||||||
@@logger = Logger.new('logs/universe.log')
|
@@logger = Logger.new('logs/universe.log')
|
||||||
|
|
||||||
|
attr_reader :name
|
||||||
|
|
||||||
def initialize(name, universe_size: 4, create_chances: {planet: 1}, seed: nil)
|
def initialize(name, universe_size: 4, create_chances: {planet: 1}, seed: nil)
|
||||||
|
|
||||||
@name = name
|
@name = name
|
||||||
@@ -69,12 +70,15 @@ class Universe
|
|||||||
@seed = seed
|
@seed = seed
|
||||||
@random = Random.new(@seed)
|
@random = Random.new(@seed)
|
||||||
end
|
end
|
||||||
|
end # Initialize
|
||||||
|
|
||||||
|
|
||||||
|
def create_universe()
|
||||||
# Set center of universe
|
# Set center of universe
|
||||||
@center = Position.new(@universe_size + 2, @universe_size)
|
@center = Position.new(@universe_size + 2, @universe_size)
|
||||||
|
|
||||||
@@logger.info "Generating Universe '#{@name}' with seed #{@seed}"
|
@@logger.info "Generating Universe '#{@name}' with seed #{@seed}"
|
||||||
puts "Center is #{@center}"
|
@@logger.info "Center is #{@center}"
|
||||||
@universe_map = UniverseArray.new()
|
@universe_map = UniverseArray.new()
|
||||||
# Generate the universe. First generate the x values:
|
# Generate the universe. First generate the x values:
|
||||||
(0 .. (@universe_size * 2)).each do |y_pos|
|
(0 .. (@universe_size * 2)).each do |y_pos|
|
||||||
@@ -90,16 +94,21 @@ class Universe
|
|||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Skip if it is the center
|
||||||
|
if position == @center
|
||||||
|
end
|
||||||
|
|
||||||
# Get what the planet should be:
|
# Get what the planet should be:
|
||||||
spot = nil
|
spot = nil
|
||||||
if @random.rand < @create_chances[:planet]
|
if @random.rand < @create_chances[:planet]
|
||||||
spot = Planet.new(@random)
|
spot = Planet.new(@random)
|
||||||
end
|
end
|
||||||
@@logger.debug "Creating something at (#{position.x_pos} #{position.y_pos})"
|
@@logger.debug "Creating something at (#{position})"
|
||||||
@universe_map[position.y_pos][position.x_pos] = spot
|
@universe_map[position] = spot
|
||||||
end # Create y points
|
end # Create y points
|
||||||
end # Create x points
|
end # Create x points
|
||||||
end # Function initialize
|
# Remove any empty nodes:
|
||||||
|
end # Function create_universe
|
||||||
|
|
||||||
# Returns the distance between points. This is returned as a number which
|
# Returns the distance between points. This is returned as a number which
|
||||||
# indicates how far you have to go to get to a point. This is actually
|
# indicates how far you have to go to get to a point. This is actually
|
||||||
@@ -110,14 +119,10 @@ class Universe
|
|||||||
# @return [Integer] The distance between points in the universe.
|
# @return [Integer] The distance between points in the universe.
|
||||||
def distance(position_a, position_b)
|
def distance(position_a, position_b)
|
||||||
result = 0 # We can just add the abs diff of each direction.
|
result = 0 # We can just add the abs diff of each direction.
|
||||||
|
require 'byebug';debugger if position_a.nil?
|
||||||
dist_x = (position_b.x_pos - position_a.x_pos).abs
|
dist_x = (position_b.x_pos - position_a.x_pos).abs
|
||||||
dist_y = (position_b.y_pos - position_a.y_pos).abs
|
dist_y = (position_b.y_pos - position_a.y_pos).abs
|
||||||
|
|
||||||
# Sanity check, our coordinates should add to be even:
|
|
||||||
if (dist_x + dist_y).odd?
|
|
||||||
raise RangeError.new('When calculating the distance we calculated a non even number. This is not allowed!')
|
|
||||||
end
|
|
||||||
|
|
||||||
res = (dist_x / 2) + dist_y
|
res = (dist_x / 2) + dist_y
|
||||||
if dist_x >= dist_y
|
if dist_x >= dist_y
|
||||||
res = (dist_x + dist_y) / 2
|
res = (dist_x + dist_y) / 2
|
||||||
@@ -128,18 +133,31 @@ class Universe
|
|||||||
|
|
||||||
# Prints the universe to the screen.
|
# Prints the universe to the screen.
|
||||||
def print_universe(simple: false)
|
def print_universe(simple: false)
|
||||||
(0 .. (@universe_size * 2)).each do |y_pos|
|
(0 .. (@universe_map.length)).each do |y_pos|
|
||||||
# Print empty space before chart (for angles)
|
# Print empty space before chart (for angles)
|
||||||
nextline = ''
|
if @universe_map[y_pos].nil?
|
||||||
|
next
|
||||||
|
end
|
||||||
|
if @universe_map[y_pos] == []
|
||||||
|
puts '_' * (4 * @universe_size * 2)
|
||||||
|
next
|
||||||
|
end
|
||||||
|
nextline = '|'
|
||||||
if y_pos < @universe_size
|
if y_pos < @universe_size
|
||||||
nextline += ' ' * ((@universe_size - y_pos).abs - 1)
|
nextline += ' ' * ((@universe_size - y_pos).abs - 1)
|
||||||
else
|
else
|
||||||
nextline += ' ' * ((@universe_size - y_pos).abs)
|
nextline += ' ' * ((@universe_size - y_pos).abs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if @universe_size.odd?
|
||||||
|
nextline += ' '
|
||||||
|
end
|
||||||
|
|
||||||
# Print more empty space for the odd places (provides offset)
|
# Print more empty space for the odd places (provides offset)
|
||||||
if y_pos.odd?
|
if y_pos.odd?
|
||||||
print ' '
|
print '| '
|
||||||
|
else
|
||||||
|
print '|'
|
||||||
end
|
end
|
||||||
|
|
||||||
#Actually loop through everything
|
#Actually loop through everything
|
||||||
@@ -156,9 +174,9 @@ class Universe
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Print what we are:
|
# Print what we are:
|
||||||
value = @universe_map[position.y_pos][position.x_pos]
|
value = @universe_map[position]
|
||||||
nextline += ' '
|
nextline += ' '
|
||||||
if [x_pos, y_pos] == @center
|
if position == @center
|
||||||
print 'O'
|
print 'O'
|
||||||
elsif value.class == Planet
|
elsif value.class == Planet
|
||||||
print 'P'
|
print 'P'
|
||||||
@@ -184,6 +202,32 @@ class Universe
|
|||||||
end # For each row
|
end # For each row
|
||||||
end # Function print_universe
|
end # Function print_universe
|
||||||
|
|
||||||
|
# Returns all of the planets contained in the universe:
|
||||||
|
#
|
||||||
|
# @return [Array<Planet>]
|
||||||
|
def planets
|
||||||
|
result = []
|
||||||
|
@universe_map.flatten.each do |item|
|
||||||
|
result.push(item) if item.is_a? Planet
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
################################################################################
|
||||||
|
################################################################################
|
||||||
|
################################################################################
|
||||||
|
################################################################################
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
################### UNIVERSE #####################
|
################### UNIVERSE #####################
|
||||||
##################################################
|
##################################################
|
||||||
@@ -195,8 +239,9 @@ class Universe
|
|||||||
#
|
#
|
||||||
# @param player [Player] The player who is asking for the position
|
# @param player [Player] The player who is asking for the position
|
||||||
# @param position [Position] The position that is being requested
|
# @param position [Position] The position that is being requested
|
||||||
|
# @return [Planet, nil] Returns the thing at that point.
|
||||||
def get_position(player, position)
|
def get_position(player, position)
|
||||||
|
return @universe_map[position]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets a position as explored for a player. This should be called as ships
|
# Sets a position as explored for a player. This should be called as ships
|
||||||
@@ -242,6 +287,12 @@ class Universe
|
|||||||
def remove_planet(planet, position)
|
def remove_planet(planet, position)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Change the ownership of a planet
|
||||||
|
#
|
||||||
|
# @param planet [Planet] The planet to be modified
|
||||||
|
# @param player [Player] The player who should now own the planet
|
||||||
|
# @param position [Position] The position of the planet
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
##################### PLAYER #####################
|
##################### PLAYER #####################
|
||||||
##################################################
|
##################################################
|
||||||
@@ -251,6 +302,12 @@ class Universe
|
|||||||
# @param player [Player] The player who should be added to the universe
|
# @param player [Player] The player who should be added to the universe
|
||||||
def add_player(player)
|
def add_player(player)
|
||||||
@player_list.push(player)
|
@player_list.push(player)
|
||||||
|
# Give the player a planet
|
||||||
|
free_planets = []
|
||||||
|
planets.each do |planet|
|
||||||
|
free_planets.push(planet) if planet.owner.nil?
|
||||||
|
end
|
||||||
|
|
||||||
return 'Added player to universe'
|
return 'Added player to universe'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,19 @@ require './position'
|
|||||||
# indexing as well as point type indexing
|
# indexing as well as point type indexing
|
||||||
class UniverseArray < Array
|
class UniverseArray < Array
|
||||||
def initialize
|
def initialize
|
||||||
super
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](index, *rest)
|
def [](index, *rest)
|
||||||
if index.is_a?(Position)
|
if index.is_a?(Position)
|
||||||
return self[position.y_pos][position.x_pos]
|
return self[index.y_pos][index.x_pos]
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def []=(index, *rest)
|
||||||
|
if index.is_a?(Position)
|
||||||
|
return self[index.y_pos][index.x_pos] = rest[0]
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user