# Nathan Hinton 20 Dec 2025 # The main game server. This will handle the clients and the game in one go require 'async' require 'async/websocket' require 'json' require 'yaml' require './game' class GameServer def initialize(conf_path) @clients = {} File.open(conf_path, 'r') do |fi| world_config = YAML.safe_load(fi.read) end puts world_config @world = World.new( width: world_config['width'], height: world_config['height'], planets: world_config['planets'], game_id: world_config['game_id'], name: world_config['name'] ) end def start(endpoint) Async do |task| Async::WebSocket::Server.open(endpoint) do |connection| task.async do handle_connection(connection) end end task.async {simulation_loop} end end def handle_connection(ws) player = nil while message == ws.read data = JSON.parse(message) response = nil case data['type'] when 'hello' player = handle_hello(ws, data) when 'querey' response = handle_query(ws, data) when 'command' response = handle_command(ws, data) else response = send_error('UNKNOWN_TYPE', 'Unknown message type') end ws.write(JSON.dump(response)) if response end rescue => e puts "Connection error: #{e.message}" ensure disconnect(player) end def handle_hello(ws, data) token = data.dig("payload", "auth_token") player = authenticate(token) @clients[player.id] = ws ws.write( JSON.dump( { type: "update", domain: "all", payload: { player_id: player_id, server_time: @world.time, game_speed: @world.game_speed player: player.snapshot } } ) ) return player end def handle_query(player, data) domain = data["domain"] payload = case domain when "player" player.snapshot when "ship" ship_id = data.dig("payload", "ship_id") @world.ship(ship_id)&.snapshot else return error("UNKNOWN_DOMAIN", "Unknown query domain") end { type: "update", domain: domain, payload: payload } end def handle_command(player, data) domain = data["domain"] payload = data["payload"] case domain when "ship" validate_and_queue_ship_command(player, payload) else return error("UNKNOWN_DOMAIN", "Unknown command domain") end { type: "update", domain: "command", payload: { status: "accepted" } } rescue ValidationError => e error("INVALID_COMMAND", e.message) end def validate_and_queue_ship_command(player, payload) ship = @world.ship(payload["ship_id"]) raise ValidationError, "Ship not found" unless ship raise ValidationError, "Not your ship" unless ship.owner == player ship.set_target(payload["target"]) end def broadcast_event(type, domain, payload) message = JSON.dump({ type: type, domain: domain, payload: payload }) @clients.values.each do |ws| ws.write(message) end end def simulation_loop last = Time.now loop do now = Time.now delta_real = now - last delta_sim = delta_real * @world.game_speed @world.advance(delta_sim) last = now sleep 0.05 # 20 tps end end def error(code, message) { type: "error", domain: "system", payload: { code: code, message: message } } end end