picking up project again. Fixing rspec errors first

This commit is contained in:
2026-05-28 10:13:28 -06:00
parent eb161e0f2e
commit a1f31a89b2
5 changed files with 268 additions and 23 deletions
+104
View File
@@ -0,0 +1,104 @@
This is a list of all the commands that are available from the client to the
server along with the expected response.
# Message types
## Client
### Hello
Sent during initiali connection phase.
### Query
Query information from the server about a specific topic
### Command
Request an action to be performed on the server
## Server
### Hello
Responses and further queries during the connection phase. Example is asking
what faction they want to be.
### Update
Response to a query or a command. Examples would be responding to a query of
how much money the player has. A response updating the target positon of a ship
and it's location.
This is also used to provide updates about the game state. There are periodic
updates that define where everything should be. This is the type used there.
### Event
This is an event that the client did not create or ask for. For example, a
battle starting, a building finishing, a planet found.
### Error
A response sent when the requested action can not be completed. This is likely
to be a frequent response to things like not having enough resources.
# Full message definitions
For all communication with the server the player you need the player id
## Hello
For the hello message neither key is optional for connecting. They are both
required. If a new player is created then it does not matter what the faction
key is (as long as it is a valid faction) however when connecting an existing
player the faction key *must* match the key stored on the server or a
connection error will happen.
| Domain | Payload keys | Expected response |
|:-------|:-------------------|:----------------------------------------------------------------------|
| Player | connect: player_id | An update with payload success OR an update with a payload of faction |
| | faction: faction | An update to the player (when creating a player) that has the faction |
| | | |
| | | |
## Queries
For queries the keys just need to exist. There are not any values for the
payload keys. You can also apply as many keys in a single request as you would
like. All parts of the message should be in snake_case. All of the values for
the keys may be whatever you wish, they are discarded by the server though.
| Domain | Payload keys | Expected response |
|:-------|:-------------|--------------------------------------------------------------------|
| Game | time | The current game time. |
| | size | The current size of the game universe. |
| Player | resources | The resources the player has |
| | planets | The planets the player can see (All planets owned, and discovered) |
| | buildings | The buildings the player has |
| | ships | The ships the player has |
| | construction | The builds in progress the player has |
| | | |
| | | |
## Commands
This lists the domain and the payload keys required as well as the expected
value.
| Domain | Payload keys | Expected response |
|:-------|:----------------------------------------------------|:-----------------------------------------------|
| Game | resign | An update with payload success. |
| Player | build: type [tech, building, ship], name: item name | An update with payload success. |
| | move: type [ship], name: item\_id | An update with the new target of the item. |
| | equip: item\_id, target: item\_id | An update with the stats of the targeted item. |
| | | |
| | | |
| | | |
| | | |
| | | |
+1 -6
View File
@@ -39,12 +39,7 @@ loop do
begin
encoded_command = JSON.parse(line.chomp)
response = ''
if encoded_command['type'] == 'hello'
# Here we need to get the client information.
response = game.player_connect(client, encoded_command).to_json
else
response = game.parse_command(encoded_command).to_json
end
response = game.parse_command(client, encoded_command)
puts "Received: #{line.chomp}"
puts "Sending: #{response}"
client.puts "#{response}"
+147
View File
@@ -0,0 +1,147 @@
# Nathan Hinton 12 Jan 2026
#
# Parse a command from the client. This somehow will end up connecting with the
# game. This is because it will need to be able to communicate with the objects
# in the game and have game level access.
class CommandParser
def initialize
end
def parse(client, command)
response = {'request_id' => command['request_id']}
case command['type']
when 'hello'
parse_hello(client, command)
when 'query'
parse_query(command)
when 'command'
parse_command(command)
else
raise CommandParserError, "Unable to parse unknown type of '#{commad['type']}'"
end
return response
end
def parse_hello(client, command)
response = {'type' => command['type']}
id = command['player_id']
case command['domain']
when 'player'
hello_player(client, id, command['payload'])
else
raise CommandParserError, "Unable to parse the hello type, domain '#{command['domain']}'"
end
return response
end
def parse_query(command)
response = {'type' => command['type']}
id = command['player_id']
case command['domain']
when 'game'
query_game(id, command['payload'])
when 'player'
query_player(id, command['payload'])
else
raise CommandParserError, "Unable to parse the query type, domain '#{command['domain']}'"
end
return response
end
def parse_command(command)
response = {'type' => command['type']}
id = command['player_id']
case command['domain']
when 'game'
command_game(id, command['payload'])
when 'player'
command_player(id, command['payload'])
else
raise CommandParserError, "Unable to parse the command type, domain '#{command['domain']}'"
end
return response
end
# Perform the actions for the type of hello and domain of player.
def hello_player(client, id, payload)
response = {}
payload.keys.each do |key, value|
case key
when 'connect'
game.connect_player(client, id, value)
when 'faction'
game.set_player_faction(id, value)
else
raise CommandParserError, "Unable to parse the type hello, domain player, key '#{key}'"
end
end
return response
end
# Perform the actions for the type of query and the domain of game
def query_game(id, payload)
response = {}
payload.keys.each do |key, value|
case key
when 'time'
game.send_time(id, value)
when 'size'
game.send_size(id, value)
else
raise CommandParserError, "Unable to parse the type query, domain game, key '#{key}'"
end
end
return response
end
# Perform the actions for the type of query and the domain of player
def query_player(id, payload)
response = {}
payload.keys.each do |key, value|
case key
when 'resources'
game.get_player_resources(id, value)
when 'planets'
game.get_player_planets(id, value)
when 'buildings'
game.get_player_buildings(id, value)
when 'ships'
game.get_player_ships(id, value)
when 'construction'
game.get_player_construction(id, value)
else
raise CommandParserError, "Unable to parse the type query, domain player, key '#{key}'"
end
end
return response
end
# Perform the actions for the type command and the domain of game
def command_game(id, payload)
response = {}
payload.keys.each do |key, value|
case key
else
raise CommandParserError, "Unable to parse the type command, domain game, key '#{key}'"
end
end
return response
end
# Perform the actions for the type command and the domain of player
def command_player(id, payload)
response = {}
payload.keys.each do |key, value|
case key
else
raise CommandParserError, "Unable to parse the type command, domain player, key '#{key}'"
end
end
return response
end
end
class CommandParserError < StandardError
end
+15 -16
View File
@@ -176,19 +176,17 @@ module Game
# The entry point for all messages that arrive from a client. The
# payload must be a hash decoded from JSON and contain a `type` field.
#
# @param client [TCPSocket] The socket that the client used.
# @param command [Hash] The parsed client command
#
# @return [String] JSON-encoded response
#
def parse_command(command)
def parse_command(client, command)
return error_response('Player not registered!') unless command['player_id']
case command['type']
when 'dbg'
# Debugger - do nothing in production
{ 'type' => 'ok', 'payload' => 'debugging' }.to_json
when 'hello'
error_response('Hello command should already have been handled.')
parse_hello(client, command)
when 'query'
parse_query(command)
when 'command'
@@ -233,7 +231,18 @@ module Game
# @return [String] JSON-encoded error response
#
def error_response(request_id, msg)
{ 'type' => 'error', 'request_id', => id, 'payload' => msg }.to_json
{ 'type' => 'error', 'request_id' => id, 'payload' => msg }.to_json
end
##
# Return an update response to the client
def update_response(request_id, domain, payload)
{'type' => 'update', 'domain' => domain, 'request_id' => request_id, 'payload' => payload}.to_json
end
# Return an event response to the client
def event_response(request_id, domain, payload)
{'type' => 'event', 'domain' => domain, 'request_id' => request_id, 'payload' => payload}.to_json
end
##
@@ -367,16 +376,6 @@ module Game
@players.find { |p| p.player_id == player_id } || false
end
# --------------------------------------------------------------------
# PRIVATE HELPERS - kept simple for the test-suite
# --------------------------------------------------------------------
private
def error_response(msg)
{ 'type' => 'error', 'payload' => msg }.to_json
end
def load_players(ids)
ids.map { |id| Player.load_from_id(id) }
end
+1 -1
View File
@@ -34,7 +34,7 @@ require 'socket'
require 'json'
HOSTNAME = 'localhost'
PORT = 2000
PORT = 20001
RSpec.describe "app.rb" do
let(:client) {TCPSocket.open(HOSTNAME, PORT)}