# 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] 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] 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