407 lines
13 KiB
Ruby
407 lines
13 KiB
Ruby
# Nathan Hinton 1 Oct 2025
|
|
|
|
|
|
require 'logger'
|
|
|
|
require './planet'
|
|
require './star'
|
|
require './position'
|
|
require './universe_array'
|
|
|
|
|
|
# Universe class. Holds a universe and provides the api for it. Universes are
|
|
# created on a 2d grid. I would like it to be hexagonal movement type so that
|
|
# it feels less like a grid and more like a circle. This means that we will
|
|
# have some interesting point systems. For now I am thinking that we will have
|
|
# three directions, a, b, c which will corespond to moving along one of the
|
|
# lines to another system. Here is an ascii art:
|
|
#
|
|
#
|
|
# (0, 0, 1) (0, 1, 0)
|
|
# \ /
|
|
# \ /
|
|
# \ /
|
|
# (+c) (+b)
|
|
# \ /
|
|
# \ /
|
|
# \ /
|
|
# (-1, 0, 0) <--- (-a) --- (0, 0, 0) --- (+a) ---> (1, 0, 0)
|
|
# / \
|
|
# / \
|
|
# /
|
|
# (-b) (-c)
|
|
# / \
|
|
# / \
|
|
# / \
|
|
# (0, -1, 0) (0, 0, -1)
|
|
#
|
|
#
|
|
# This means that we are adding vector together. I do not think that this will
|
|
# work super well though with a system that is other than 2D. I think that will
|
|
# be fine.
|
|
#
|
|
# After thinking some more I will have the universe class hold everything in
|
|
# the universe. It will be in charge of holding all of the ships and
|
|
# planets. Then the Player class will be connected to the ships and planets
|
|
# that they own/control
|
|
#
|
|
# @param name [String] The name of the game
|
|
# @param universe_size [Integer] Controlls the size of the generated universe. This is a radius from the center point.
|
|
class Universe
|
|
# Setup logger for this class:
|
|
@@logger = Logger.new('logs/universe.log')
|
|
|
|
attr_reader :name
|
|
|
|
def initialize(name, universe_size: 4, create_chances: {planet: 1}, seed: nil)
|
|
|
|
@name = name
|
|
@universe_size = universe_size
|
|
@create_chances = create_chances
|
|
|
|
# Setup internal lists:
|
|
@player_list = []
|
|
@planet_list = []
|
|
@ship_list = []
|
|
|
|
# initizlize RNG
|
|
if seed.nil?
|
|
@random = Random.new()
|
|
@seed = @random.seed
|
|
else
|
|
@seed = seed
|
|
@random = Random.new(@seed)
|
|
end
|
|
end # Initialize
|
|
|
|
|
|
# Create the universe. This will setup all of the planets and a blank slate
|
|
# for the universe.
|
|
def create_universe()
|
|
# Set center of universe
|
|
@center = Position.new(@universe_size + 2, @universe_size)
|
|
|
|
@@logger.info "Generating Universe '#{@name}' with seed #{@seed}"
|
|
@@logger.info "Center is #{@center}"
|
|
@universe_map = UniverseArray.new()
|
|
# Generate the universe. First generate the x values:
|
|
(0 .. (@universe_size * 2)).each do |y_pos|
|
|
@universe_map[y_pos] = []
|
|
(0 .. (@universe_size * 2 + 2)).each do |x_pos|
|
|
# Make the grid work right, x is actually skipping every other.
|
|
x_pos *= 2
|
|
x_pos += 1 if y_pos.odd?
|
|
position = Position.new(x_pos, y_pos)
|
|
|
|
# Skip if the node is too far
|
|
if distance(@center, position) >= @universe_size
|
|
next
|
|
end
|
|
|
|
# Skip if it is the center
|
|
if position == @center
|
|
@universe_map[position] = Star.new()
|
|
next
|
|
end
|
|
|
|
# Get what the planet should be:
|
|
spot = nil
|
|
if @random.rand < @create_chances[:planet]
|
|
spot = Planet.new(@random)
|
|
@planet_list.push(spot)
|
|
#else
|
|
# spot = ''
|
|
end
|
|
@@logger.debug "Creating something at (#{position})"
|
|
@universe_map[position] = spot
|
|
end # Create y points
|
|
end # Create x points
|
|
# Remove any empty nodes:
|
|
end # Function create_universe
|
|
|
|
# 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
|
|
# supurisingly hard...
|
|
#
|
|
# @param position_a [Position] A start point in the universe
|
|
# @param position_b [Position] Another point in the universe
|
|
# @return [Integer] The distance between points in the universe.
|
|
def distance(position_a, position_b)
|
|
result = 0 # We can just add the abs diff of each direction.
|
|
dist_x = (position_b.x_pos - position_a.x_pos).abs
|
|
dist_y = (position_b.y_pos - position_a.y_pos).abs
|
|
|
|
res = (dist_x / 2) + dist_y
|
|
if dist_x >= dist_y
|
|
res = (dist_x + dist_y) / 2
|
|
end
|
|
@@logger.debug "Distance: #{position_a} and #{position_b} are #{res} units away"
|
|
return res
|
|
end # Function distance
|
|
|
|
# Prints the universe to the screen.
|
|
def print_universe(simple: false)
|
|
(0 .. (@universe_map.length)).each do |y_pos|
|
|
# Print empty space before chart (for angles)
|
|
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
|
|
nextline += ' ' * ((@universe_size - y_pos).abs - 1)
|
|
else
|
|
nextline += ' ' * ((@universe_size - y_pos).abs)
|
|
end
|
|
|
|
if @universe_size.odd?
|
|
nextline += ' '
|
|
end
|
|
|
|
# Print more empty space for the odd places (provides offset)
|
|
if y_pos.odd?
|
|
print '| '
|
|
else
|
|
print '|'
|
|
end
|
|
|
|
#Actually loop through everything
|
|
(0 .. (@universe_size * 2)).each do |x_pos|
|
|
# Make the grid work right, x is actually skipping every other.
|
|
x_pos *= 2
|
|
x_pos += 1 if y_pos.odd?
|
|
|
|
position = Position.new(x_pos, y_pos)
|
|
# Print empty space if we are outside the universe (For items)
|
|
if distance(@center, position) >= @universe_size
|
|
print ' '
|
|
next
|
|
end
|
|
|
|
# Print what we are:
|
|
value = @universe_map[position]
|
|
nextline += ' '
|
|
if value.is_a?(Star)
|
|
print 'O'
|
|
elsif value.is_a?(Planet)
|
|
print 'P'
|
|
else
|
|
print ' '
|
|
end
|
|
|
|
# Calculate what we should print on the next line and if we should print dashes
|
|
if y_pos < @universe_size
|
|
nextline += '/ \\'
|
|
end
|
|
if distance(@center, Position.new(x_pos + 2, y_pos)) < @universe_size
|
|
print '---'
|
|
if y_pos >= @universe_size
|
|
nextline += '\\ /'
|
|
end
|
|
end
|
|
end # For each col in row
|
|
puts ''
|
|
if (y_pos + 1) / 2 < @universe_size && !simple
|
|
puts nextline
|
|
end
|
|
end # For each row
|
|
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
|
|
|
|
# Runs a tick of the game. Passes it on to all of the sub objects
|
|
def tick
|
|
# Incriment planets
|
|
@planet_list.each do |planet|
|
|
planet.tick
|
|
end
|
|
|
|
# Incriment ships
|
|
@ship_list.each do |ship|
|
|
ship.tick
|
|
end
|
|
end
|
|
|
|
|
|
|
|
################################################################################
|
|
################################################################################
|
|
################################################################################
|
|
################################################################################
|
|
################################################################################
|
|
################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
##################################################
|
|
################### UNIVERSE #####################
|
|
##################################################
|
|
|
|
# This is where API stuff for the universe should go
|
|
|
|
# Gets a position from the universe for a player. Eventually this will take
|
|
# into account that the player can not actually see the whole universe
|
|
#
|
|
# @param player [Player] The player who is asking for the position
|
|
# @param position [Position] The position that is being requested
|
|
# @return [Planet, nil] Returns the thing at that point.
|
|
def get_position(player, position)
|
|
return @universe_map[position]
|
|
end
|
|
|
|
# Sets a position as explored for a player. This should be called as ships
|
|
# move around and explore the universe.
|
|
#
|
|
# @param player [Player] The player who should be set
|
|
# @param position [Position] The position that is being modified
|
|
def explore_position(player, position)
|
|
end
|
|
|
|
|
|
##################################################
|
|
##################### SHIPS ######################
|
|
##################################################
|
|
|
|
# Create a ship in the universe. This ship is registered to a specific player
|
|
#
|
|
# @param player [Player] The player who owns the ship
|
|
# @param position [Position] The position that the ship should be created at
|
|
def create_ship(player, position)
|
|
return false unless validate_player(player)
|
|
end
|
|
alias_method :add_ship, :create_ship
|
|
|
|
# Move a player's ship to a given position
|
|
#
|
|
# @param player [Player] The player whose ship should be moved
|
|
# @param ship [Ship] The ship that should be moved
|
|
# @param position [Position] The position the ship should be moved to.
|
|
def move_ship(player, ship, position)
|
|
return false unless validate_player(player)
|
|
# Ensure the ship exists before moving it.
|
|
end
|
|
|
|
# Remove a ship from the game
|
|
# @param player [Player] The player who owns the ship to be removed
|
|
# @param ship [Ship] The ship to be removed
|
|
def remove_ship(player, ship)
|
|
return false unless validate_player(player)
|
|
end
|
|
|
|
|
|
##################################################
|
|
##################### PLANETS ####################
|
|
##################################################
|
|
|
|
# As all planets are created when the galaxy is generated there are no create
|
|
# planet functions. It is possible that we want to remove them though
|
|
|
|
# Remove a planet from the universe
|
|
#
|
|
# @param planet [Planet] The planet that should be removed
|
|
# @param position [Position] The position that it should be removed from
|
|
def remove_planet(planet, position)
|
|
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 #####################
|
|
##################################################
|
|
|
|
# Add a player to the player list in the universe
|
|
#
|
|
# @param player [Player] The player who should be added to the universe
|
|
def add_player(player)
|
|
# Give the player a planet
|
|
free_planets = []
|
|
planets.each do |planet|
|
|
free_planets.push(planet) if planet.owner.nil?
|
|
end
|
|
if free_planets == []
|
|
@@logger.debug 'Failed to find an unoccopuied planet for new player!'
|
|
return false
|
|
end
|
|
home_planet = free_planets.sample(random: @random)
|
|
# Can;t figure out how to test this...
|
|
# if !home_planet.change_owner(player)
|
|
# raise Exception.new('Failed to allocate a new player a home planet!')
|
|
# end
|
|
home_planet.change_owner(player)
|
|
@player_list.push(player)
|
|
@@logger.debug "Added player '#{player.name}' with home planet #{home_planet}"
|
|
return true
|
|
end
|
|
|
|
# Remove a player from the list in this universe
|
|
#
|
|
# @param player [Player] The player that should be removed from the player list.
|
|
def remove_player(player)
|
|
if @player_list.delete(player).nil?
|
|
@@logger.debug 'Failed to remove player! Does not exist in this universe! Returning true anyway though!'
|
|
end
|
|
@@logger.debug "Removed player '#{player.name}' from universe"
|
|
return true
|
|
end
|
|
|
|
# Validates if a player exists and is in a good state in the server
|
|
#
|
|
# @param player [Player] The player to validate
|
|
def validate_player(player)
|
|
# check if in player list:
|
|
unless @player_list.include?(player)
|
|
@@logger.warn "Player '#{player}' Tried to perform an action but is an invalid player!"
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
# Returns a list of ships owned by the player
|
|
#
|
|
# @param player [Player] The player who wants to get their ships.
|
|
# @return [Array<Ship>]
|
|
def get_player_ships(player)
|
|
return [] unless validate_player(player)
|
|
result = []
|
|
@ship_list.each do |ship|
|
|
if ship.owner == player
|
|
result.push(ship)
|
|
end
|
|
end
|
|
return result
|
|
end # Function get_player_ships
|
|
|
|
# Returns a list of planets owned by the player
|
|
#
|
|
# @param player [Player] The player who wants to get their planets.
|
|
def get_player_planets(player)
|
|
return [] unless validate_player(player)
|
|
result = []
|
|
@planet_list.each do |planet|
|
|
if planet.owner == player
|
|
result.push(planet)
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
end # Class Universe
|