An API to store tic-tac-toe game state and let two players compete across the internet. It allows players to register as users of the API and play against other registered users.
The API does not currently validate game states.
Verb | URI Pattern | Controller#Action |
---|---|---|
POST | /sign-up |
users#signup |
POST | /sign-in |
users#signin |
DELETE | /sign-out/:id |
users#signout |
PATCH | /change-password/:id |
users#changepw |
GET | /games |
games#index |
POST | /games |
games#create |
GET | /games/:id |
games#show |
PATCH | /games/:id |
games#update |
GET | /games/:id/watch |
games#watch |
All data returned from API actions is formatted as JSON.
Summary:
Request | Response | |||
---|---|---|---|---|
Verb | URI | body | Status | body |
POST | `/sign-up` | credentials | 201, Created | user |
400 Bad Request | empty | |||
POST | `/sign-in` | credentials | 200 OK | user w/token |
401 Unauthorized | empty | |||
DELETE | `/sign-out/:id` | empty | 201 Created | empty |
401 Unauthorized | empty | |||
PATCH | `/change-password/:id` | passwords | 204 No Content | user w/token |
400 Bad Request | empty |
The create
action expects a POST of credentials
identifying a new user to
create, e.g. using getFormFields
:
<form>
<input name="credentials[email]" type="text" value="[email protected]">
<input name="credentials[password]" type="password" value="an example password">
<input name="credentials[password_confirmation]" type="password" value="an example password">
</form>
or using JSON
:
{
"credentials": {
"email": "[email protected]",
"password": "an example password",
"password_confirmation": "an example password"
}
}
The password_confirmation
field is optional.
If the request is successful, the response will have an HTTP Status of 201,
Created, and the body will be JSON containing the id
and email
of the new
user, e.g.:
{
"user": {
"id": 1,
"email": "[email protected]"
}
}
If the request is unsuccessful, the response will have an HTTP Status of 400 Bad Request, and the response body will be empty.
The signin
action expects a POST with credentials
identifying a previously
registered user, e.g.:
<form>
<input name="credentials[email]" type="text" value="[email protected]">
<input name="credentials[password]" type="password" value="an example password">
</form>
or:
{
"credentials": {
"email": "[email protected]",
"password": "an example password"
}
}
If the request is successful, the response will have an HTTP Status of 200 OK,
and the body will be JSON containing the user's id
, email
, and the token
used to authenticate other requests, e.g.:
{
"user": {
"id": 1,
"email": "[email protected]",
"token": "an example authentication token"
}
}
If the request is unsuccessful, the response will have an HTTP Status of 401 Unauthorized, and the response body will be empty.
The signout
actions is a DELETE specifying the id
of the user to sign out.
If the request is successful the response will have an HTTP status of 204 No Content.
If the request is unsuccessful, the response will have a status of 401 Unauthorized.
The changepw
action expects a PATCH of passwords
specifying the old
and
new
.
If the request is successful the response will have an HTTP status of 204 No Content.
If the request is unsuccessful the reponse will have an HTTP status of 400 Bad Request.
The sign-out
and change-password
requests must include a valid HTTP header
Authorization: Token token=<token>
or they will be rejected with a status of
401 Unauthorized.
All games action requests must include a valid HTTP header Authorization: Token token=<token>
or they will be rejected with a status of 401 Unauthorized.
All of the game actions, except for watch
, follow the RESTful style.
Games are associated with users, player_x
and player_o
.
Actions, other than update, will only retrieve a game if the user associated
with the Authorization
header is one of those two users.
If this requirement is unmet, the response will be 404 Not Found, except for
the index action which will return an empty games array.
Summary:
Request | Response | |||
---|---|---|---|---|
Verb | URI | body | Status | body |
GET | `/games[?over=]` | n/a | 200, OK | games found |
The optional `over` query parameter restricts the response to games with a matching `over` property. | 200, OK | empty games | ||
The default is to retrieve all games associated with the user.. | 401 Unauthorized | empty | ||
POST | `/games` | n/a | 201, Created | game created |
401 Unauthorized | empty | |||
400 Bad Request | errors | |||
GET | `/games/:id` | n/a | 200, OK | game found |
401 Unauthorized | empty | |||
404 Not Found | empty | |||
PATCH | `/games/:id` | empty | 200, OK | game joined |
400 Bad Request | errors | |||
400 Bad Request | empty | |||
PATCH | `/games/:id` | game delta | 200, OK | game updated |
400 Bad Request | errors | |||
404 Not Found | empty |
The index
action is a GET that retrieves all the games associated with a
user.
The response body will contain JSON containing an array of games, e.g.:
{
"games": [
{
"id": 1,
"cells": ["o","x","o","x","o","x","o","x","o"],
"over": true,
"player_x": {
"id": 1,
"email": "[email protected]"
},
"player_o": {
"id": 3,
"email": "[email protected]"
}
},
{
"id": 2,
"cells": ["","","","","","","","",""],
"over": false,
"player_x": {
"id": 3,
"email": "[email protected]"
},
"player_o": {
"id": 1,
"email": "[email protected]"
}
}
]
}
If the over
query parameter is specified the results will be restricted
accordingly.
If there are no games associated with the user, the response body will contain an empty games array, e.g.:
{
"games": [
]
}
The create
action expects a POST with an empty body (e.g ''
or '{}'
if
JSON).
If the request is successful, the response will have an HTTP Status of 201
Created, and the body will contain JSON of the created game with player_x
set
to the user calling create
, e.g.:
{
"game": {
"id": 3,
"cells": ["","","","","","","","",""],
"over": false,
"player_x": {
"id": 1,
"email": "[email protected]"
},
"player_o": null
}
}
If the request is unsuccessful, the response will have an HTTP Status of 400 Bad Request, and the response body will be JSON describing the errors.
The show
action is a GET specifing the id
of the game to retrieve.
If the request is successful the status will be 200, OK, and the response body
will contain JSON for the game requested, e.g.:
{
"game": {
"id": 1,
"cells": ["o","x","o","x","o","x","o","x","o"],
"over": true,
"player_x": {
"id": 1,
"email": "[email protected]"
},
"player_o": {
"id": 3,
"email": "[email protected]"
}
}
}
This update
action expects an empty (e.g ''
or '{}'
if JSON) PATCH to
join an existing game.
If the request is successful, the response will have an HTTP Status of 200 OK, and the body will be JSON containing the game joined, e.g.:
{
"game": {
"id": 1,
"cells": ["","","","","","","","",""],
"over":false,
"player_x": {
"id": 1,
"email": "[email protected]"
},
"player_o": {
"id": 3,
"email":
"[email protected]"
}
}
}
If the request is unsuccessful, the response will have an HTTP Status of 400 Bad Request, and the response body will be empty (game cannot be joined, player_o already set or user making request is player_x) or JSON describing the errors.
This update
action expects a PATCH with changes to to an existing game,
e.g.:
<form>
<input name="game[cell][index]" type="text" value="0">
<input name="game[cell][value]" type="text" value="x">
<input name="game[over]" type="text" value="false">
</form>
{
"game": {
"cell": {
"index": 0,
"value": "x"
},
"over": false
}
}
If the request is successful, the response will have an HTTP Status of 200 OK, and the body will be JSON containing the modified game, e.g.:
{
"game": {
"id": 1,
"cells": ["x","","","","","","","",""],
"over":false,
"player_x": {
"id": 1,
"email": "[email protected]"
},
"player_o": {
"id": 3,
"email":
"[email protected]"
}
}
}
If the request is unsuccessful, the response will have an HTTP Status of 400 Bad Request, and the response body will be JSON describing the errors.
The watch
action is handled differently than all the others. Because watch
implements a streaming source of data, we'll use a wrapper around the html5
object EventSource to handle the events sent.
You can find the wrapper here.
The wrapper is also available from the deployed app at the path
/js/resource-watcher-0.1.0.js
.
The events that watch
implements let you know when a game has been updated.
By using this interface you can write code that lets a player see another's move
almost as it happens.
Updates to the game from one player's browser are sent to the other's browser.
You create a watcher object using the resourceWatcher function. This function takes two parameters, the watch url and a configuration object which must contain the Authorization token, and may contain an optional timeout in seconds, e.g.:
let gameWatcher = resourceWatcher('<server>/games/:id/watch', {
Authorization: 'Token token=<token>'[,
timeout: <timeout>]
});
By default, watching a game times-out after 120 seconds.
You should add a handler for change
and error
events.
The error events are not the most informative.
The change event may pass to your handler an object with a root key of "timeout"
or "heartbeat".
Otherwise, it will pass an object with a root key of "game". Each key in this object will contain an array of length 2. The first element of such an array will contain the value for that key before the update. The last element will contain the value after the update. The code example that follows shows handling the most important case, a change to the game board.
gameWatcher.on('change', function (data) {
console.log(data);
if (data.game && data.game.cells) {
const diff = changes => {
let before = changes[0];
let after = changes[1];
for (let i = 0; i < after.length; i++) {
if (before[i] !== after[i]) {
return {
index: i,
value: after[i],
};
}
}
return { index: -1, value: '' };
};
let cell = diff(data.game.cells);
$('#watch-index').val(cell.index);
$('#watch-value').val(cell.value);
} else if (data.timeout) { //not an error
gameWatcher.close();
}
});
gameWatcher.on('error', function (e) {
console.error('an error has occurred with the stream', e);
});
- All content is licensed under a CCBYNCSA 4.0 license.
- All software code is licensed under GNU GPLv3. For commercial use or alternative licensing, please contact [email protected].