working on reimplimenting server

This commit is contained in:
2025-12-19 19:23:54 -07:00
parent 4737531805
commit 057164122a
20 changed files with 1145 additions and 6 deletions
+2
View File
@@ -0,0 +1,2 @@
gem sinatra
gem sinatra-cross_origin
+11 -6
View File
@@ -1,5 +1,9 @@
# Goals: # Note
This is now directed at the `src` folder. The `game` folder is old code and is
kept for example stuff.
# Goals:
Make a multiplayer game that will run in the browser. This game should be a Make a multiplayer game that will run in the browser. This game should be a
planet conquest type game where you can create a lobby and then players can planet conquest type game where you can create a lobby and then players can
@@ -43,11 +47,12 @@ I do eventually want to have AI players however for now I just want human
players. Make sure that you help me to keep this easy as I am programming. players. Make sure that you help me to keep this easy as I am programming.
Here are the resources that I am thinking of for now: Here are the resources that I am thinking of for now:
- Food - Food (Local to planet)
- Metal - Energy (Local to planet)
- Fuel/energy - Metal (Global)
- Crystals - Fuel (Global)
- Galaxy Credits (Universal money) - Crystals (Global)
- Galaxy Credits (Global)
How do we have resources travel be tween planets? I am not sure yet. Maybe have How do we have resources travel be tween planets? I am not sure yet. Maybe have
+1
View File
@@ -0,0 +1 @@
--require spec_helper
+34
View File
@@ -0,0 +1,34 @@
# Nathan Hinton 2 Oct 2025
# Rake file
desc 'Run RSpec tests'
task :test do
begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
Rake::Task['spec'].invoke
rescue LoadError
puts 'Failed to load RSpec to run tests... Try installing rspec'
end
end
desc 'Run the server for clients to connect to'
task :serve do
raise NotImplementedError.new('Server task not yet implimented!')
end
namespace :doc do
desc 'Create the YARD documentation for the project'
task :yard do
res = sh 'yard doc *.rb'
sh 'yard stats *.rb --list-undoc'
end
desc 'Create code coverage'
task :coverage do
Rake::Task[:test].invoke
end
end
+47
View File
@@ -0,0 +1,47 @@
# Nathan Hinton 19 Dec 2025
require './space_object'
# This class is for buildings that are built on a planet. Currently it
# inherrits the SpaceObject class however it should probably inherrit a class
# that is for planets.
class Building<SpaceObject
attr_reader :name, :type
# Create a new building object. You can pass in a name and a type.
#
# @param player [Player] The player who owns this building
# @param name [String] The name of the building
# @param type [String] The type of the building
def initialize(player, planet, name: 'default name', type: 'Central Post')
super()
# Valiate type:
if not get_config['buildings'].include? type
raise BuildingError, "Invalid building type"
end
@player = player
@planet = planet
@name = name
@type = type
end
# Override the superclass tick method. This will add the resources that ar
# produced to the player and the planet that the building belongs to.
def tick
get_config['buildings'][@type]['produces'].each do |resource, value|
# Send some to the player (Global resoures)
if ['metal', 'credits', 'crystals'].include? resource
@player.resources[resource] += value
else
@planet.resources[resource] += value
end
end
end
end
# Error class for the buildings
class BuildingError < StandardError
end
+183
View File
@@ -0,0 +1,183 @@
# Configuration file for the game:
planets:
production:
metal: 10
credits: 10
crystals: 10
buildings:
Central Post:
description:
"The Central Post allows you to build other buildings on the planet"
cost:
metal: 100
credits: 100
crystals: 100
produces:
metal: 10
credits: 10
crystals: 10
##### ENERGY PRODUCTION #####
Nuclear Plant:
description:
"The Nuclear Plant is expensive however it will produce a constant and large amount of energy"
cost:
metal: 500
credits: 500
crystals: 500
produces:
energy: 1000
Wind Farm:
description:
"A Wind Farm will produce a variable amount of energy depending on the surface of the planet."
cost:
metal: 200
credits: 200
crystals: 200
produces:
energy: 200
Solar Farm:
description:
"A Solar Farm will produce a variable amount of energy depending on the planet environment"
cost:
metal: 200
credits: 200
crystals: 200
produces:
energy: 200
##### RESEARCH BUILDINGS #####
Civil Research:
description:
"Proviced access to research economic and morale upgrades"
cost:
metal: 1000
credits: 1000
crystals: 1000
produces:
civ research: 1000 # This is a *global* resource
Space Research:
description:
"Provides access to sensor/module and engine upgrades"
cost:
metal: 1000
credits: 1000
crystals: 1000
produces:
spa research: 1000 # This is a *global* resource
Military Research:
description:
"Provides access to weapon and shield upgrades"
cost:
metal: 1000
credits: 1000
crystals: 1000
produces:
mil research: 1000 # This is a *global* resource
Shipping Research:
description:
"Provides access to ship and efficiency upgrades"
cost:
metal: 1000
credits: 1000
crystals: 1000
produces:
shi research: 1000 # This is a *global* resource
##### RESOURCE PRODUCTION BUILDINGS #####
Refinery:
description:
"Extracts fuel from the ground. Most effective on liquid type planets."
cost:
metal: 150
credits: 150
crystals: 150
energy: 100 # This is energy per tick
produces:
fuel: 20 # This is a *global* resource
Mine:
description:
"Extracts metal from the ground. Most effective on"
cost:
metal: 150
credits: 150
crystals: 150
energy: 100 # This is energy per tick
produces:
metal: 20 # This is a *global* resource
Harvester:
description:
"Extracts crystas from the surface. Most effective on"
cost:
metal: 150
credits: 150
crystals: 150
energy: 100 # This is energy per tick
produces:
crystals: 20 # This is a *global* resource
Trade Center:
description:
"Increases the number of credits earned on this planet. Most effective on"
cost:
metal: 150
credits: 150
crystals: 150
energy: 100 # This is energy per tick
produces:
credits: 20 # This is a *global* resource
Farm:
description:
"Produces food on this planet. Required to grow the population"
cost:
metal: 150
credits: 150
crystals: 150
energy: 100 # This is energy per tick
produces:
food: 1000
##### STUFF PRODUCTION #####
Space Port:
description:
"Produces space ships"
cost:
metal: 500
credits: 500
crystals: 500
produces:
ship building power: 1000
Manufacturing Plant:
description:
"Produces modules and engines for ships"
cost:
metal: 500
credits: 500
crystals: 500
produces:
manufacturing power: 1000
Military Production:
description:
"Produces weapons and shields"
cost:
metal: 500
credits: 500
crystals: 500
produces:
military power: 1000
+133
View File
@@ -0,0 +1,133 @@
# Nathan Hinton 19 Dec 2025
require 'securerandom'
require './planet'
# This class holds a game and should be the top level object.
class Game
attr_reader :players, :width, :height, :planets, :game_id
# Create the game
def initialize(width: 0, height: 0, planets: 0, game_id: nil, name: 'default name')
@game_id = game_id || SecureRandom.hex
@game_name = name
@players = []
@planets = []
planets.times do |idx|
planet = Planet.new()
# Move to a random position
position = [rand(width), rand(height), 0]
# Ensure that the planets position is random
while @planets.map{ |pl| pl.position}.include? position
position = [rand(width), rand(height), 0]
end
planet.move(position)
# Add to the list:
@planets.append(planet)
end
@width = width
@height = height
end
# Add a player to the game
#
# @param player [Player] The player to add to the game
def add_player(player)
# Check if there is a planet free for the player:
avail_planets = []
@planets.each do |planet|
avail_planets.append(planet) if planet.owner.nil?
end
if avail_planets == []
raise GameError, "No available planets to add player to"
end
planet = avail_planets.sample
# Add the player to the game
@players.append(player)
planet.capture(player)
player.home_planet = planet
end
# Remove a player from the game
#
# @param player [Player] The player to be removed
def remove_player(player)
idx = @players.index(player)
if idx.nil?
# Raise an error that the player could not be found
raise GameError, 'Player not found to remove!'
end
@players.delete_at(idx)
end
# Checks if the given id matches a player in the game. Returns the player if
# so. otherwise raises an error.
#
# @param id [String] The id of a player
# @return Player The player matching the id
def check_id(id)
@players.each do |player|
if player.id == id
return player
end
end
raise GameError, 'Invalid player id!'
end
# Gets the planets for a player
#
# @param id [String] Players id
# @return [Array<Planet>] List of planets captured by that player
def get_captured_planets(id)
player = check_id(id)
result = []
@planets.each do |planet|
result.append(planet) if planet.owner == player
end
return result
end
# Returns a string representation of the map
# Begin no coverage
# :nocov:
def to_s()
# Create char map:
game_map = []
@height.times do
tmp = []
@width.times do
tmp.append('')
end
game_map.append(tmp)
end
# Add planets to the map:
@planets.each do |planet|
pos_x, pos_y, pos_z = planet.position
puts("x: #{pos_x}, y: #{pos_y}")
game_map[pos_y][pos_x] += 'p'
end
require 'pp'
pp(game_map)
game_map.each do |row|
row.each do |col|
if col == ''
print(' ')
else
print(col)
end
end
print("\n")
end
end
# :nocov:
# End no coverage
end
# General game errors
class GameError<StandardError
end
+63
View File
@@ -0,0 +1,63 @@
# Nathan Hinton 19 Dec 2025
# Holds a planet and planet information
require './space_object'
# Class for planets.
class Planet < SpaceObject
attr_reader :owner, :resources
# Create a planet in the game
def initialize()
super()
@buildings = []
@owner = nil
@resources = {
'food' => 0,
'energy' => 0,
'people' => 10
}
end
# Any clean up that the planet will need
def delete()
end
# Build a building on the planet
#
# @param building [Building] The building to be built
def building_build(building)
@buildings.append(building)
end
# Remove building from the planet
#
# @param building [Building] The building to be destroyed
def building_destroy(building)
index = @buildings.index(building)
if index == nil
raise PlanetError, 'Building not found to delete'
end
@buildings.delete_at(index)
end
# Capture a planet for a player
#
# @param player [Player] The player who is capturing the planet
def capture(player)
@buildings = [] # Destroy all the buildings on the planet
@owner = player
end
# Tick for the planet
def tick()
@buildings.each do |building|
building.tick
end
end
end
# Generic error for plannets
class PlanetError < StandardError
end
+43
View File
@@ -0,0 +1,43 @@
# Nathan Hinton 19 Dec 2025
require 'securerandom'
# This defines the player class
class Player
attr_reader :name, :resources, :id
attr_accessor :home_planet
# initialize a player.
#
# @param name [String] The name of the player
def initialize(name: 'test player')
@name = name
@planets = []
@fleets = []
@tech = []
@resources = {
'metal' => 0,
'crystals' => 0,
'credits' => 0
}
# This id will be used to validate the player in the game
@id = SecureRandom.hex()
@id_retrieved = false
@home_planet = nil
end
# Returns the player ID. This *should* only be called once. When called again
# it should print a warning to the log.
#
# @return [String] The ID of the player
def get_id
if @id_retrieved
# Print a warning message!
end
@id_retrieved = true
return @id
end
end
+6
View File
@@ -0,0 +1,6 @@
# Core of game
This will have the classes and a base loop however it will not do anything more
than just store stuff I think.
For now only the x and y cords are used.
+55
View File
@@ -0,0 +1,55 @@
# Nathan Hinton 19 Dec 2025
# Runs the server for the game
require 'sinatra'
require 'sinatra/cross_origin'
require 'json'
require './game'
configure do
enable :cross_origin
end
# CORS headers for all routes
before do
headers 'Access-Control-Allow-Origin' => '*', # Allow all domains (you can change '*' to a specific domain for security)
'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS', # Allow only specific methods
'Access-Control-Allow-Headers' => 'Content-Type, Authorization'
end
games = {}
# Get the server status
get '/status' do
return 'UP'
end
# Creates a new game
#
# @param name [String] Required. The name of the game
# @param width [Integer] The width of the game space
# @param height [Integer] The height of the game space
# @param planets [Integer] The number of planets to have in the game
get '/create_game' do
puts "Creating game named #{params['name']}"
name = params['name']
width = params['width'] || 10
width = width.to_i
height = params['height'] || 10
height = height.to_i
planets = params['planets'] || 20
planets = planets.to_i
games.update({name => Game.new(width: width, height: height, planets: planets, name: name)})
return 'OK'
end
# Gets games available
#
# @return [Array] list of game names
get '/list_games' do
return games.keys.to_json
end
+47
View File
@@ -0,0 +1,47 @@
# Nathan Hinton 19 Dec 2025
require 'yaml'
# Base class for any object in space. This can be a planet, asteroid, ship etc.
class SpaceObject
attr_reader :position
File.open('./config.yaml', 'r') do |fi|
@@config = YAML.safe_load(fi.read())
end
# Create the object
#
def initialize()
@position = [0, 0, 0]
end
# Delete the object. This should be overwritten by the sub objects. For
# example when an asteroid is destroyed it might want to explode and damage
# anything nearby first before being removed from the game. This is the path
# that will be used for that.
def delete()
end
# Move the object to a new position. Not everything should be movable so this
# can be disallowed by sub classes.
#
# @param position [Array<Integer>] The position that the object should be
# moved to.
def move(position)
@position = position
end
# This *should* be overridden by any object that wants to do anyting. For
# example a ship probaly would like to move when the game tick happens.
def tick()
end
# Define a method to get the config
#
# @return [Hash] A hash containing the configuration
def get_config()
return @@config
end
end
+74
View File
@@ -0,0 +1,74 @@
# Nathan Hinton 19 Dec 2025
# Defines tests for the buildings
require './building'
require './player'
require './planet'
require './space_object'
RSpec.describe Building do
describe '#initialize' do
it 'Initializes to defaults' do
building = Building.new(Player.new(), Planet.new())
expect(building.name).to eq 'default name'
expect(building.type).to eq 'Central Post'
end
it 'initializes with custom values' do
building = Building.new(Player.new(), Planet.new(), name: 'nathan house', type: 'Solar Farm')
expect(building.name).to eq 'nathan house'
expect(building.type).to eq 'Solar Farm'
end
it 'will raise error when invalid building type' do
expect{Building.new(Player.new(), Planet.new(), name: 'error', type: 'error')}.to raise_error BuildingError, "Invalid building type"
end
end
describe '#tick' do
it 'Adds resources to the player' do
player = Player.new()
planet = Planet.new()
type = 'Central Post'
building = Building.new(player, planet, type: type)
config = SpaceObject.new().get_config
metal = config['buildings'][type]['produces']['metal']
credits = config['buildings'][type]['produces']['credits']
crystals = config['buildings'][type]['produces']['crystals']
expect(player.resources).to eq({'metal' => 0, 'crystals' => 0, 'credits' => 0})
expect{building.tick}.to_not raise_error
expect(player.resources).to eq({
'metal' => metal,
'crystals' => crystals,
'credits' => credits
})
expect(planet.resources).to eq({
'food' => 0,
'energy' => 0,
'people' => 10
})
end
it 'Adds resources to the planet' do
player = Player.new()
planet = Planet.new()
type = 'Nuclear Plant'
building = Building.new(player, planet, type: type)
config = SpaceObject.new().get_config
expect(player.resources).to eq({'metal' => 0, 'crystals' => 0, 'credits' => 0})
expect{building.tick}.to_not raise_error
expect(player.resources).to eq({
'metal' => 0,
'crystals' => 0,
'credits' => 0
})
expect(planet.resources).to eq({
'food' => 0,
'energy' => 1000,
'people' => 10
})
end
end
end
+34
View File
@@ -0,0 +1,34 @@
example_id | status | run_time |
---------------------------------- | ------ | --------------- |
./spec/building_spec.rb[1:1:1] | passed | 0.00024 seconds |
./spec/building_spec.rb[1:1:2] | passed | 0.00044 seconds |
./spec/building_spec.rb[1:1:3] | passed | 0.00059 seconds |
./spec/building_spec.rb[1:2:1] | passed | 0.00024 seconds |
./spec/building_spec.rb[1:2:2] | passed | 0.00208 seconds |
./spec/game_spec.rb[1:1:1] | passed | 0.00011 seconds |
./spec/game_spec.rb[1:1:2] | passed | 0.00011 seconds |
./spec/game_spec.rb[1:1:3] | passed | 0.00011 seconds |
./spec/game_spec.rb[1:1:4] | passed | 0.00014 seconds |
./spec/game_spec.rb[1:2:1] | passed | 0.00014 seconds |
./spec/game_spec.rb[1:2:2] | passed | 0.00016 seconds |
./spec/game_spec.rb[1:3:1] | passed | 0.00019 seconds |
./spec/game_spec.rb[1:3:2] | passed | 0.00026 seconds |
./spec/game_spec.rb[1:4:1] | passed | 0.00013 seconds |
./spec/game_spec.rb[1:4:2] | passed | 0.00016 seconds |
./spec/game_spec.rb[1:5:1] | passed | 0.00016 seconds |
./spec/planet_spec.rb[1:1:1] | passed | 0.00018 seconds |
./spec/planet_spec.rb[1:1:2] | passed | 0.00013 seconds |
./spec/planet_spec.rb[1:3:1] | passed | 0.00015 seconds |
./spec/planet_spec.rb[1:4:1] | passed | 0.00018 seconds |
./spec/planet_spec.rb[1:4:2] | passed | 0.00021 seconds |
./spec/planet_spec.rb[1:5:1] | passed | 0.00168 seconds |
./spec/planet_spec.rb[1:5:2] | passed | 0.00039 seconds |
./spec/planet_spec.rb[1:6:1] | passed | 0.00873 seconds |
./spec/player_spec.rb[1:1:1] | passed | 0.00035 seconds |
./spec/player_spec.rb[1:2:1] | passed | 0.00052 seconds |
./spec/player_spec.rb[1:2:2] | passed | 0.00044 seconds |
./spec/simple_game_spec.rb[1:1] | passed | 0.00956 seconds |
./spec/space_object_spec.rb[1:1:1] | passed | 0.00012 seconds |
./spec/space_object_spec.rb[1:3:1] | passed | 0.00023 seconds |
./spec/space_object_spec.rb[1:4:1] | passed | 0.00013 seconds |
./spec/space_object_spec.rb[1:6:1] | passed | 0.00012 seconds |
+87
View File
@@ -0,0 +1,87 @@
# Nathan Hinton 19 Dec 2025
require './game'
require './player'
RSpec.describe Game do
describe '#initialize' do
it 'Creates an empty list of players' do
expect(Game.new().players).to eq []
end
it 'sets the width when passed in' do
expect(Game.new(width: 10).width).to eq 10
end
it 'sets the height when passed in' do
expect(Game.new(height: 11).height).to eq 11
end
it 'creates the specified number of planets' do
expect(Game.new(planets: 5).planets.length).to eq 5
end
end
describe '#add_player' do
it 'adds a player to the game' do
game = Game.new(planets: 1)
player = Player.new()
game.add_player(player)
expect(game.players).to eq [player]
end
it 'fails when there are no planets available for the player' do
game = Game.new(planets: 0)
player = Player.new()
expect{game.add_player(player)}.to raise_error GameError, "No available planets to add player to"
expect(game.players).to eq []
end
end
describe '#remove_player' do
it 'removes a player from the game' do
game = Game.new(planets: 1)
player = Player.new()
game.add_player(player)
expect(game.players).to eq [player]
expect(game.remove_player(player)).to eq player
expect(game.players).to eq []
end
it 'returns an error when the player is not in the game' do
game = Game.new(planets: 1)
player = Player.new()
game.add_player(player)
player2 = Player.new()
expect{game.remove_player(player2)}.to raise_error GameError, 'Player not found to remove!'
end
end
describe '#check_id' do
it 'returns true when the player id is valid for a player' do
game = Game.new(planets: 1)
player = Player.new()
game.add_player(player)
expect(game.check_id(player.get_id)).to eq player
end
it 'returns false when the player id does not match a player' do
game = Game.new(planets: 1)
player = Player.new()
game.add_player(player)
expect{game.check_id('a')}.to raise_error GameError, 'Invalid player id!'
end
end
describe '#get_captured_planets' do
it 'returns the captured planets of the player' do
game = Game.new(planets: 1)
player = Player.new()
game.add_player(player)
expect(game.get_captured_planets(player.get_id)).to eq game.instance_variable_get(:@planets)
end
end
end
+95
View File
@@ -0,0 +1,95 @@
# Nathan Hinton 19 Dec 2025
#
# Defines tests for the planets
require './planet'
require './building'
RSpec.describe Planet do
describe '#initialize' do
it 'Starts in a position' do
expect(Planet.new().instance_variable_get(:@position)).to eq [0, 0, 0]
end
it 'Creates a buildings list' do
expect(Planet.new().instance_variable_get(:@buildings)).to eq []
end
end
describe '#delete' do
end
describe '#building_build' do
it 'Adds a building to the planet building list' do
planet = Planet.new()
test_building = Building.new(Player.new(), planet)
expect{planet.building_build(test_building)}.to_not raise_error
expect(planet.instance_variable_get(:@buildings)).to eq [test_building]
end
end
describe '#building_destroy' do
it 'removes a building from the list' do
planet = Planet.new()
test_building = Building.new(Player.new(), planet)
planet.building_build(test_building)
expect(planet.instance_variable_get(:@buildings)).to eq [test_building]
expect(planet.building_destroy(test_building)).to eq test_building
expect(planet.instance_variable_get(:@buildings)).to eq []
end
it 'raises an error if the building can not be removed' do
planet = Planet.new()
test_building = Building.new(Player.new(), planet)
planet.building_build(test_building)
test_building2 = Building.new(Player.new(), planet)
expect{planet.building_destroy(test_building2)}.to raise_error PlanetError, 'Building not found to delete'
end
end
describe '#capture' do
it 'sets the planet owner' do
planet = Planet.new()
player = Player.new(name: 'test0')
expect(planet.capture(player)).to be player
expect(planet.instance_variable_get(:@owner)).to be player
end
it 'removes all buildings from the planet' do
planet = Planet.new()
player = Player.new(name: 'test0')
planet.capture(player)
test_building = Building.new(player, planet)
planet.building_build(test_building)
player1 = Player.new(name: 'test1')
expect(planet.instance_variable_get(:@buildings)).to eq [test_building]
expect(planet.capture(player1)).to be player1
expect(planet.instance_variable_get(:@owner)).to be player1
expect(planet.instance_variable_get(:@buildings)).to eq []
end
end
describe '#tick' do
it 'Sends the tick event to all buildings on the planet' do
planet = Planet.new()
test_building1 = Building.new(Player.new(), planet)
test_building2 = Building.new(Player.new(), planet)
test_building3 = Building.new(Player.new(), planet)
planet.building_build(test_building1)
planet.building_build(test_building2)
planet.building_build(test_building3)
expect(test_building1).to receive(:tick)
expect(test_building2).to receive(:tick)
expect(test_building3).to receive(:tick)
expect(planet.tick).to eq planet.instance_variable_get(:@buildings)
end
end
end
+51
View File
@@ -0,0 +1,51 @@
# Nathan Hinton 19 Dec 2025
# Test file for the player class.
require 'securerandom'
require './player'
RSpec.describe Player do
# Set the testing id
let(:id) { 'cfeab1a60864fd529cb9ce993e9c3af4' }
before(:each) do
# Make sure that we get the same ID every time.
allow(SecureRandom).to receive(:hex).and_return(id)
end
describe '#initialize' do
it 'initializes with name' do
expect(Player.new(name: 'Nathan').name).to eq 'Nathan'
end
end
describe '#get_id' do
it 'returns the id' do
player = Player.new()
expect(player.instance_variable_get(:@id_retrieved)).to be false
expect(player.get_id).to eq id
expect(player.instance_variable_get(:@id_retrieved)).to be true
end
it 'prints a warning when the id is gotten more than once' do
player = Player.new()
player.get_id
expect(player.instance_variable_get(:@id_retrieved)).to be true
expect(player.get_id).to eq id
expect(player.instance_variable_get(:@id_retrieved)).to be true
end
end
describe '#planets' do
end
describe '#fleets', pending: 'Ships not yet implimented' do
end
describe '#tech', pending: 'Tech not yet implimented' do
end
end
+23
View File
@@ -0,0 +1,23 @@
# Nathan Hinton 19 Dec 2025
require './player'
require './game'
# This tests a simple game.
RSpec.describe 'Simple game', :sim => true do
it 'Initalize a new game and add a player' do
# Setup the player
player = Player.new()
id = player.get_id
expect(player.resources).to eq({'metal' => 0, 'crystals' => 0, 'galaxy credits' => 0})
# Setup the start planet
game = Game.new(width: 5, height: 5, planets: 5)
expect(game.planets.length).to eq 5
game.add_player(player)
expect(game.get_captured_planets(id).length).to eq 1
puts(game)
end
end
+47
View File
@@ -0,0 +1,47 @@
# Nathan Hinton 19 Dec 2025
#
# Defines tests for the space object class
require './space_object'
RSpec.describe SpaceObject do
describe '#initialize' do
it 'works with no args' do
expect(SpaceObject.new().instance_variable_get(:@position)).to eq [0, 0, 0]
end
end
describe '#delete' do
end
describe '#move' do
let(:so) { SpaceObject.new() }
it 'changes the objects position and returns the new position' do
expect(so.instance_variable_get(:@position)).to eq [0, 0, 0]
expect(so.move([2, 3, 4])).to eq [2, 3, 4]
expect(so.instance_variable_get(:@position)).to eq [2, 3, 4]
end
end
describe '#position' do
let(:so) { SpaceObject.new() }
it 'returns the current position' do
so.move([1, 2, 3])
expect(so.position).to eq [1, 2, 3]
end
end
describe '#tick' do
end
describe '#get_config' do
it 'returns the correct keys' do
expect(SpaceObject.new().get_config.keys).to eq ['planets', 'buildings']
end
end
end
+109
View File
@@ -0,0 +1,109 @@
require 'simplecov'
SimpleCov.configure do
coverage_dir 'coverage'
# Start SimpleCov
SimpleCov.start
end
# 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
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.
# 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
# Exclude by default the simulate tests.
config.filter_run_excluding :sim => true
end