RetroShamBo

Paper, Scissors, Rock the old-school way

Introduction

This document describes a protocol used to allow a distributed online �paper scissors rock� tournament. It is intended to be implemented by clients with minimal computing resources, for example vintage 8-bit computers.

Tournament Structure

A tournament consists of multiple games, each game being played between two clients over 100 turns. On each turn, each client selects (�throws�) one of Paper, Scissors, or Rock. If both clients throw the same, then the game is scored as a draw, and neither client receives any points. Otherwise a winner is determined as follows:
Paper beats Rock
Rock beats Scissors
Scissors beats Paper

The winner of a turn will receive 1 point.

After each game of 100 turns, clients will be assigned new opponents and a new game will commence. After each client has played a game against each other client, the tournament will be over, and the client that scored the most points (i.e. won the most throws) across all games will be declared the winner of the tournament.

Protocol Overview

Multiple clients communicate with a single server over UDP. Clients use an ephemeral port, the server always uses UDP port 9461

The client sends a �Connect� message to the server indicating it is ready to start playing.

The server responds with a �Connect Response� indicating how many clients are already connected, and how long till the next game is scheduled to start.

The server will then periodically ping all connected clients, to detect clients which drop out prior to the start of a game.

Just before game time, the server will pair up clients. Clients are not given any indication of which opponent they have been paired with for the game about to be played.

After the throws are sent for the last game, the server will send a �Game Statues Response� with a non-zero game state code, indicating the game is over. The server will periodically resend this message until either the client responds with a �Game Over ACK� message, or the server has resent the message 10 times without response and times out the client.

After each game, the client should reconnect if it wants to play another game.

Retries

Client should send Connect Requests at approximately 1 second intervals until a Connect Response is received.

During games, the server will send �Request Throw� messages approximately every 1 second until a throw for the expected hand is received, or too many errors/timouts occur. Therefore the client does NOT need to handle timeouts & retries separately, however it must be able to handle the case where it receives multiple �Request Throw� messages for the same turn.

Message Structure

In each packet, the byte at offset 0×00 is the �command byte�, indicating what type of command this packet encodes.

The remainder of the packet is command data, that varies for each type of command.

Command Byte Command Description
0×00 Connect Request Sent by client to serverData = string identifying the client type
0×01 Connect Response sent by server to clientData =

0×01..0×02 = number of clients connected

0×03..0×04 = seconds till next game starts

0×05.. end = welcome banner (string)

0×02 Ping Request sent in either directionany data following the command byte is ignored
0×03 Ping Response sent in either directionany data following the command byte is ignored
0×04 Throw Request sent by server to clientData =

0×01..0×02 = throw number

0×03..0×04 = total throws in game

0×05..0×06 = your current score

0×07..0×08 = opponents current score

0×09 = opponents last throw (0×20 on first turn)

0x0A = your result last throw (0×20 on first turn)

0×05 Throw Response sent by client to server0x01..0×02 = throw number

0×03 = your throw

0×06 Game Status Request sent by client to server.no data is required
0×07 Game Status Response sent by server to clientData =

0×01..0×02 = current throw number

0×03..0×04 = total throws in game

0×05..0×06 = your current score

0×07..0×08 = opponents current score

0×09 = game state

0×08 Game Over ACK sent by client to server after receiving a �Game Status Response� indicating the game is complete. No data is required.
0xFF Error sent by server if a bogus request was receivedData=

0×01 error code

0×02..0×08 first 8 bytes of request generating with errors (padded with nulls)

0×09..end – a free text string describing the error

Encoding

All numbers are in network byte order (big end first)

strings:
ASCII encoded,null terminated
max length=255 bytes

throws are encoded as:
0×20=nil (ASCII space)
0×50 = Paper (ASCII �P�)
0×53 = Scissors (ASCII �S�)
0×52 = Rock (ASCII �R�)

Results are encoded as:
0×20=nil (ASCII space)
0×57= you won (ASCII �W�)
0x4C= you lost (ASCII �L�)
0×44= draw (ASCII �D�)

Error Codes:

INVALID_COMMAND_BYTE=0×80
INVALID_IDENTIFIER=0×81
SEQUENCE_ERROR=0×82
DATA_ERROR=0×83
SYSTEM_ERROR=0xFF

Game State encoded as:

0×00 = in play
0×01 = completed
0×02 = opponent dropped out

The Sandpit

There is a ‘sandpit’ test server listening on port 9461 of jamtronix.com. A client connecting to this port will immediately start playing a single 100 turn game against a very brain-dead bot.

Here’s a sample RetroShamBo client (in ruby). Save this to rsb_client.rb, and then run with the command line “rsb_client.rb jamtronix.com 9461″

# —� 8< —� cut here — >8 —-

#!/usr/bin/ruby
require 'socket'
require 'timeout'

SERVER_PORT=9461

def log_msg(msg)
  puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} #{msg}"
end

if ARGV.length==0 || ARGV[0][0]==?- then
	puts "useage: #{$0} <servername> [port]"
	exit
end
address=ARGV[0]
port=ARGV[1]
port=SERVER_PORT if port.nil?

socket=UDPSocket.open

##
# sign on
##

connected=false
connect_packet="\000psr client"
loop do
	break if connected
	begin
		log_msg("connecting to #{address}:#{port}")
		timeout(2) do
			socket.connect(address,port)
			socket.send(connect_packet,0)

			data,addr_info=socket.recvfrom(4096)
			if data[0]==0x01
				connected=true
				banner = data[0x05,0x100]
				log_msg("connected: #{banner}")
				break
			end
		end
	rescue Exception=>e
		puts e
		sleep (1)
	end
end

##
# handle messages
##

loop do
	data,addr_info=socket.recvfrom(4096)
	case data[0]

	when 0x02 #ping
		msg = data[0x01,0x40]
		log_msg("ping: #{msg}")
		socket.send("\003Pong!",0)
	when 0x04 #throw request
		(command_byte,throw_number,total_throws,your_score,opponent_score,opponent_last_throw,result_last_throw)=data.unpack("CnnnnCC")
		log_msg("THROW #{throw_number} - US=#{your_score}, THEM=#{opponent_score}, THEY THREW=#{opponent_last_throw.chr}, RESULT=#{result_last_throw.chr}")
		this_throw="PSR"[rand(3)]
		log_msg("SENDING #{this_throw.chr}")
		response=[0x05,throw_number,this_throw].pack("CnC")
		socket.send(response,0)
	when 0x07 #game status respoonse
		(command_byte,throw_number,total_throws,your_score,opponent_score,game_state)=data.unpack("CnnnnC")
		log_msg("GAME STATUS - THROW #{throw_number}/#{total_throws} - US=#{your_score}, THEM=#{opponent_score}, STATE = #{"%02x" % game_state}")
		break
	when 0xff #error
		(command_byte,error_code,original_request,error_description)=data.unpack("CCa8A*")
		log_msg("error - #{error_description}")
	else
		log_msg("unexpected message (type=#{"%02x" % data[0]}) : #{data.inspect}")
	end
end

# —� 8< —� cut here — >8 —-

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>