OVERVIEW
This is the design for a two-player peer-to-peer card game application.
This application does only two things:
- It presents a graphical representation of a
bunch of piles of cards, including a draw pile, your hand, a play
area, and a discard pile. It allows you to move cards from any pile to
any other pile.
- It keeps track of which cards your opponent has
looked at, and which cards are truly secret. It will inform you if your
opponent stacks his deck or peeks at your hand.
That's it. This application does not enforce the rules of any card game,
and it cannot play any card game.
CARD PILES OR ZONES
A "zone" is an ordered list of cards. At all times, each card in the
game is in exactly one zone.
Each player has the following zones:
- library / draw pile.
This zone is private. Neither player can look at the contents or order
of either player's library without asking the other player for
permission.
- sideboard.
This zone is semi-private. The contents are known to the zone's owner,
but not to the other player.
- hand.
This zone is semi-private. When a card is moved to a player's hand,
it should be revealed to that player. Neither player can look at the
contents or order of the other player's hand without asking for
permission.
- discard pile.
This zone is public. When a card is moved to a public zone, it should
be revealed to both players.
- removed from game.
Public. Just another discard pile.
- play area.
This zone is semi-public. When a card is moved to a play area, it can
be revealed to both players, which represents playing the card face-up.
Or it can be unrevealed, which represents playing the card face-down.
Each card in a play area has associated (x, y) coordinates. Also, any
number of (key, value) pairs can be associated with the card.
RESTRICTIONS
No player is allowed to modify his opponent's library. (He cannot move
cards out of or in to that library. If both players were allowed to
modify the same library at the same time, then the peers could become
out-of-sync in a way that would make it impossible to ever shuffle that
library again.)
There are no other restrictions on card movement. All other moves are
technically allowed, even moves that would break the rules of some card
games. (For example, the protocol allows me to move cards from my
opponent's hand to my hand. Fortunately, all card movements are
announced to both players, so you can disconnect from players who make
illegal moves.)
HOW IT WORKS
At the start of a game, each card in each player's deck is represented
by a unique string, and that string encrypted by both players. Then,
each double-encrypted string is assigned a unique card ID (cid).
Then, the zones are initialized: Each player's card IDs are split up
into that player's library and sideboard zones, and all other zones
are emptied.
At all times, each player knows the exact ordered list of cids in each
zone. However, for a player to find out what card is represented by a
particular cid, the player must ask the other player to decrypt the
cid's double-encrypted string.
Here's an example: Let's say that cid 123 is at the top of my library,
and I want to draw a card. I'll send my opponent a message saying that
I want to draw a card. He will remove 123 from the list of cids in my
library, and add 123 to the list of cids in my hand. And I'll do the
same. But what card did I actually draw? Neither of us knows.
Fortunately, my opponent acknowledges that I now have the right to know
what cid 123 represents. So he decrypts that card's double-encrypted
string. The result is a single-encrypted string that has no meaning to
him. He sends the single-encrypted string to me, and I decrypt it, and
now only I know what's in my hand.
Now I want to play cid 123. So I decrypt the card's double-encrypted
string, and get a string that is only encrypted by my opponent. I send
it to him, and he decrypts it, and now I can move the card to a public
zone.
This scheme is known as Mental Poker. It requires a commutative
encryption algorithm. This prototcol was designed to use the RSA
Modulo a Prime algorithm, but clients may be implemented using other
encryption schemes.
DIRTY SHUFFLING
A library can be "clean" or "dirty". A library is clean if no card in
the library has been revealed to either player. Otherwise, it's dirty.
(To make a library dirty, either peek at the top card without drawing
it, or move a card from some other zone to your library.)
To shuffle a clean library, just put the list of cids in some
random order, and send the new list to your opponent.
To shuffle a dirty library, the cards need to be re-encrypyed by each
player, like they were a the start of the game.
PROTOCOL
Two players connect over a network. They can use a direct TCP/IP
connection. If both players are behind firewalls, then they can connect
through an instant messaging service. (Jabber seems appropriate for
this game.)
The players send messages to each other. The messages are not RPCs.
They do not have to be answered or acknowledged. Each player must
always be ready to receive and process a message from the other player,
regardless of whose turn it is or anything else that's going on in the
game.
Each message has a name and a series of arguments. An argument is a
name and a value. The value can be any standard type, such as an
integer, a string, or an array of strings.
CARD DESCRIPTIONS
A card description, as sent in the "set-deck" message, is a 32 byte string.
The first byte is a random byte, which distinguishes this individual
card from other copies of this card in the deck. (The high bit must be
off. A deck is limited to 128 copies of any single card.)
The second byte is the full length of the card name, even if the full
length does not fit in this card description. (The length is the number
of bytes in the name in UTF8 format.)
The next 30 bytes are the card name in UTF8 format. The card name is
truncated if necessary, and padded with random bytes if necessary.
Then, the 32 bytes are encrypted.
MESSAGES
hello
The first message sent by each peer to the other. The
arguments should contain the data that will be necessary for further
communication, such as the version of this protocol that the peer
supports, and any known extensions.
identify value:%d
Each peer sends a non-negative random integer to the other
peer. The peer that sends the lower number will be identified as pid 0,
and the other will be pid 1. If there is a tie, then each peer should
send a new value.
set-player-name name:%s
Tells the opponent this player's name.
set-player-value key:%s value:%d
Associates a (key, value) pair with the player.
set-deck cards:{%s %s %s ...} kid:%d text:%s
Sends this player's deck as a list of single-encrypyed
card descriptions. (This message can only be used during game-setup. It
cannot be used while a game is in progress.) For player 0's deck:
cards[0] becomes cid 1 and is put at the bottom of the library.
cards[1] becomes cid 2 and is second from the bottom, and so on. The
last card ends up at the top of the library and has the highest cid.
For player 1's deck, add 5000 to each cid.
Note that the library is initially dirty. So, both the sender and
the receiver should treat this message like an
"encrypt-request" message: The sender's GUI should "lock" the
library, and the receiver should respond with an "encrypt"
message.
kid identifies the sender's key used on the library. It has no meaning to the receiver during the game.
text is a hexadecimal 256 bit prime number, with the high bit on.
Both clients will use this prime number when encrypting cards in this
deck with the RSA Modulo a Prime algorithm.
set-sideboard cards:{%s %s %s ...} kid:%d
Sends this player's sideboard, just like the set-deck message.
cards[0] becomes the next cid after the highest cid in the player's deck,
and is put at the bottom of his sideboard zone. And so on.
encrypt-request zone:%d
Requests that the opponent send an "encrypt"
message for the sender's library (or other zone).
After sending this message, the GUI
should "lock" the library. The player should not be allowed to modify
his library until the "encrypt-library" message has been received.
encrypt pid:%d zone:%d kid:%d value:%d cards:{%s ...} kids:(%d ...)
Tells that the sender has re-encrypted and re-assigned the
cards in the given player's library (or other zone).
value is 0 if the zone was not shuffled, and 1 if it was shuffled.
The cid at the bottom of the library becomes cards[0], the cid second
from the bottom becomes cards[1], and so on. The local (receiver's) key
used on cards[x] is kids[x].
kid identifies the new sender's key used on the library.
shuffle zone:%d cids:(%d %d %d ...)
Tells the opponent that this is the new order for the
cards in the sender's library (or other zone).
If this method is used to shuffle a
dirty library, then the game should inform the player that the library
is not truly shuffled.
message text:%s
Sends a chat message to the opponent. The text is whatever was typed in the chat box (in UTF-8).
move-card cid:%d pid:%d zone:%d x:%d y:%d loc:%d card:%s
Tells that a card has been moved to a new zone.
cid identifies the card.
(pid, zone) identifies the card's new zone.
If the new zone is a playarea, then:
(x, y) are the card's location in the playarea.
loc is 0 if the card is face-up, and 1 if face-down.
Else:
loc is 0 for the top of the new zone, and 1 for the bottom.
card,
an optional parameter, is the card description, decrypted by the sender
(but still encrypted by the receiver). It is important to specify this
parameter when moving a card from your hand to a public zone. However,
this parameter is unnecessary when moving a card between public zones.
move-card-request cid:%d pid:%d zone:%d x:%d y:%d loc:%d card:%s
Tells the opponent that this player wants to move a card,
but that the card has not yet been moved. Typically, this message is
used to move a card from the player's library, such as to draw a card.
The card parameter must be specified when the new zone is public, but it should not be specified when drawing a card.
To process this message, the opponent should move the card in its own game state, and then send a "move-card" message.
rearrange-zone cid:%d pid:%d zone:%d value:%d
Tells that the sender has moved a card within its zone.
(pid, zone) are technically unnecessary, since the
receiver knows cid's current zone, but those arguments can be
used to verify that the clients are in sync.
value is the card's new location in the zone. If the card was
moved closer to the bottom, then it pushes other cards up, and if it
was moved closer to the top, then it pushes other cards down.
set-card-value cid:%d key:%s value:%d
Sets a (key, value) pair associated with a card in a
playarea. A card's (key, value) pairs are discarded when the card is
moved out of the playarea.
send-ping
Requests that the opponent send a "return-ping" message.
return-ping
Tells that the client has received a "send-ping" message.
reveal-request pid:%d zone:%d size:%d
Tells the opponent to send a "reveal" message for the top "size" cards of the given player's given zone (usually his library).
reveal cids:{%d ...} cards:{%s ...}
This message may be sent at any time for any reason, but
it will typically be used when a player needs to look at the top cards
of some library, or look at the other player's hand. For each cid, this
contains the card description, decrypted by the sender, but still
encrypted by the receiver.
show-zone zone:%d size:%d
Tells the opponent to pop-up a dialog box displaying the top of this
player's library or hand. The dialog box will be filled with the label "Unknown
Card" unless the client sends the appropriate "reveal" message first.
select-request options:{%s ...} kid:%d value:%d
Requests that the opponent select one of the encrypted "option" strings. The client should inform the user that a random event had been requested, and then send a "select" message. When the client receives the "reveal-key" message, it can inform the user of the result of the random event.
kid is a cookie used to match the "select-request", "select", and "reveal-key" messages.
If value is 0, then it's a die-roll. Each option is two bytes: The first byte is a number from 1 to n, and the second byte is the same as the first byte, which acts as a checksum after the bytes are decrypted. The options are in random order. When the client receives the "reveal-key" message, it can verify that the correct options were available.
If value is 1, then it's a coin-flip. The options are "HEAD" and "TAIL", in random order.
If value is 2, then it's the purpose is to show a random card from the sender's hand. The options are the cids from the sender's hand, as 4-byte little-endian integers, encrypted and shuffled. When the sender receives the corresponding "select" message, it should decrypt the cid, and then send a "reveal" message before it sends the "reveal-key" message.
select option:%s kid:%d
Response to a "select-request" message.
reveal-key kid:%d key:%s
Reveals the encryption key that was used in the corresponding "select" message. When a client sends or receives this message, it should tell the user the result of the random event.
define-card name:%s pow:%s color:%d
Defines a card in the card database. The card name may then be used in "set-deck" messages, and, more likely, "add-card" messages.
add-card name:%s zone:%d
Assigns a new cid to a new card. The cid is the next highest number after the sender's deck, sideboard, and added cards. The card is added to the top of the specified zone. name is the unencrypted regular-length card name.
set-phase, next-turn, loaded-new-deck, inform-sideboard, etc.
Inform the other player that something interesting has happened.
request-new-game value:%d
If value is 1, then the player wants to start a new game. If
it's 0, then the player has cancelled his request to reset the game.
When a player receives value:1 while he wants to reset the game, or
when he sends value:1 while the other player wants to reset the game,
then he should reset his game state, and assume that the other player
is doing the same. To reset the game, throw away all cards in all
zones, all encryption keys and cid mappings, and all deck lists. Load
a new deck, encrypt it with a new key, and send it to the other
player. Wait for the other player to send his new deck, and then the
new game can begin.