picking up project again. Fixing rspec errors first
This commit is contained in:
+104
@@ -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
@@ -39,12 +39,7 @@ loop do
|
|||||||
begin
|
begin
|
||||||
encoded_command = JSON.parse(line.chomp)
|
encoded_command = JSON.parse(line.chomp)
|
||||||
response = ''
|
response = ''
|
||||||
if encoded_command['type'] == 'hello'
|
response = game.parse_command(client, encoded_command)
|
||||||
# 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
|
|
||||||
puts "Received: #{line.chomp}"
|
puts "Received: #{line.chomp}"
|
||||||
puts "Sending: #{response}"
|
puts "Sending: #{response}"
|
||||||
client.puts "#{response}"
|
client.puts "#{response}"
|
||||||
|
|||||||
@@ -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
@@ -176,19 +176,17 @@ module Game
|
|||||||
# The entry point for all messages that arrive from a client. The
|
# 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.
|
# 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
|
# @param command [Hash] The parsed client command
|
||||||
#
|
#
|
||||||
# @return [String] JSON-encoded response
|
# @return [String] JSON-encoded response
|
||||||
#
|
#
|
||||||
def parse_command(command)
|
def parse_command(client, command)
|
||||||
return error_response('Player not registered!') unless command['player_id']
|
return error_response('Player not registered!') unless command['player_id']
|
||||||
|
|
||||||
case command['type']
|
case command['type']
|
||||||
when 'dbg'
|
|
||||||
# Debugger - do nothing in production
|
|
||||||
{ 'type' => 'ok', 'payload' => 'debugging' }.to_json
|
|
||||||
when 'hello'
|
when 'hello'
|
||||||
error_response('Hello command should already have been handled.')
|
parse_hello(client, command)
|
||||||
when 'query'
|
when 'query'
|
||||||
parse_query(command)
|
parse_query(command)
|
||||||
when 'command'
|
when 'command'
|
||||||
@@ -233,7 +231,18 @@ module Game
|
|||||||
# @return [String] JSON-encoded error response
|
# @return [String] JSON-encoded error response
|
||||||
#
|
#
|
||||||
def error_response(request_id, msg)
|
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
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -367,16 +376,6 @@ module Game
|
|||||||
@players.find { |p| p.player_id == player_id } || false
|
@players.find { |p| p.player_id == player_id } || false
|
||||||
end
|
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)
|
def load_players(ids)
|
||||||
ids.map { |id| Player.load_from_id(id) }
|
ids.map { |id| Player.load_from_id(id) }
|
||||||
end
|
end
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ require 'socket'
|
|||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
HOSTNAME = 'localhost'
|
HOSTNAME = 'localhost'
|
||||||
PORT = 2000
|
PORT = 20001
|
||||||
|
|
||||||
RSpec.describe "app.rb" do
|
RSpec.describe "app.rb" do
|
||||||
let(:client) {TCPSocket.open(HOSTNAME, PORT)}
|
let(:client) {TCPSocket.open(HOSTNAME, PORT)}
|
||||||
|
|||||||
Reference in New Issue
Block a user