Skip to content

Commit

Permalink
Merge 48df54e into 9f6f80f
Browse files Browse the repository at this point in the history
  • Loading branch information
evandroabukamel committed Oct 28, 2020
2 parents 9f6f80f + 48df54e commit 864798b
Show file tree
Hide file tree
Showing 11 changed files with 686 additions and 33 deletions.
27 changes: 25 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -160,6 +160,7 @@
"koa-router": "^7.2.1",
"koa-socket-2": "^1.0.17",
"lru-cache": "^4.1.1",
"p-queue": "^6.6.2",
"prop-types": "^15.5.10",
"react-cookies": "^0.1.0",
"redux": "^4.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/server.ts
Expand Up @@ -7,7 +7,7 @@
*/

import { Server } from '../src/server';
import { FlatFile } from '../src/server/db';
import { FlatFile, InMemory } from '../src/server/db';
import { SocketIO } from '../src/server/transport/socketio';

export { Server, FlatFile, SocketIO };
export { Server, FlatFile, InMemory, SocketIO };
2 changes: 1 addition & 1 deletion src/client/transport/socketio.ts
Expand Up @@ -36,7 +36,7 @@ type SocketIOTransportOpts = TransportOpts &
*/
export class SocketIOTransport extends Transport {
server: string;
socket;
socket: SocketIOClient.Socket;
socketOpts;
callback: () => void;
matchDataCallback: MetadataCallback;
Expand Down
2 changes: 1 addition & 1 deletion src/core/game.ts
Expand Up @@ -105,6 +105,6 @@ export function ProcessGameConfig(game: Game | ProcessedGame): ProcessedGame {
};
}

function IsLongFormMove(move: Move): move is LongFormMove {
export function IsLongFormMove(move: Move): move is LongFormMove {
return move instanceof Object && (move as LongFormMove).move !== undefined;
}
172 changes: 161 additions & 11 deletions src/master/master.test.ts
Expand Up @@ -17,9 +17,10 @@ import {
isActionFromAuthenticPlayer,
} from './master';
import { error } from '../core/logger';
import { Server, State } from '../types';
import { Server, State, Ctx, LogEntry } from '../types';
import * as StorageAPI from '../server/db/base';
import * as dateMock from 'jest-date-mock';
import { PlayerView } from '../core/player-view';

jest.mock('../core/logger', () => ({
info: jest.fn(),
Expand All @@ -30,9 +31,62 @@ beforeEach(() => {
dateMock.clear();
});

class InMemoryAsync extends InMemory {
type() {
return StorageAPI.Type.ASYNC;
class InMemoryAsync extends StorageAPI.Async {
db: InMemory;

constructor() {
super();
this.db = new InMemory();
}

async connect() {
await this.sleep(300);
}

private sleep(interval: number): Promise<void> {
return new Promise(resolve => void setTimeout(resolve, interval));
}

/**
* @param id
* @param opts
* @override
*/
async createMatch(id: string, opts: StorageAPI.CreateMatchOpts) {
await this.sleep(300);
this.db.createMatch(id, opts);
}

async setMetadata(matchID: string, metadata: Server.MatchData) {
await this.sleep(300);
this.db.setMetadata(matchID, metadata);
}

async setState(matchID: string, state: State, deltalog?: LogEntry[]) {
await this.sleep(300);
this.db.setState(matchID, state, deltalog);
}

async fetch<O extends StorageAPI.FetchOpts>(
matchID: string,
opts: O
): Promise<StorageAPI.FetchResult<O>> {
await this.sleep(300);
return this.db.fetch(matchID, opts);
}

async wipe(matchID: string) {
await this.sleep(300);
this.db.wipe(matchID);
}

/**
* @param opts
* @override
*/
async listMatches(opts?: StorageAPI.ListMatchesOpts): Promise<string[]> {
await this.sleep(300);
return this.db.listMatches(opts);
}
}

Expand Down Expand Up @@ -178,22 +232,24 @@ describe('update', () => {
await master.onUpdate(action, 100, 'matchID', '1');
expect(sendAll).not.toHaveBeenCalled();
expect(error).toHaveBeenCalledWith(
`invalid stateID, was=[100], expected=[1]`
`invalid stateID, was=[100], expected=[1] - playerID=[1] - action[endTurn]`
);
});

test('invalid playerID', async () => {
await master.onUpdate(action, 1, 'matchID', '100');
await master.onUpdate(ActionCreators.makeMove('move'), 1, 'matchID', '100');
expect(sendAll).not.toHaveBeenCalled();
expect(error).toHaveBeenCalledWith(`player not active - playerID=[100]`);
expect(error).toHaveBeenCalledWith(
`player not active - playerID=[100] - action[move]`
);
});

test('invalid move', async () => {
await master.onUpdate(ActionCreators.makeMove('move'), 1, 'matchID', '1');
expect(sendAll).not.toHaveBeenCalled();
expect(error).toHaveBeenCalledWith(
`move not processed - canPlayerMakeMove=false, playerID=[1]`
`move not processed - canPlayerMakeMove=false - playerID=[1] - action[move]`
);
});

Expand All @@ -202,6 +258,98 @@ describe('update', () => {
expect(sendAll).toHaveBeenCalled();
});

test('allow execution of moves with ignoreStaleStateID truthy', async () => {
const game = {
setup: () => {
const G = {
players: {
'0': {
cards: ['card3'],
},
'1': {
cards: [],
},
},
cards: ['card0', 'card1', 'card2'],
discardedCards: [],
};
return G;
},
playerView: PlayerView.STRIP_SECRETS,
turn: {
activePlayers: { currentPlayer: { stage: 'A' } },
stages: {
A: {
moves: {
A: (G, ctx: Ctx) => {
const card = G.players[ctx.playerID].cards.shift();
G.discardedCards.push(card);
},
B: {
move: (G, ctx: Ctx) => {
const card = G.cards.pop();
G.players[ctx.playerID].cards.push(card);
},
ignoreStaleStateID: true,
},
},
},
},
},
};

const send = jest.fn();
const master = new Master(
game,
new InMemory(),
TransportAPI(send, sendAll)
);

const setActivePlayers = ActionCreators.gameEvent(
'setActivePlayers',
[{ all: 'A' }],
'0'
);
const actionA = ActionCreators.makeMove('A', null, '0');
const actionB = ActionCreators.makeMove('B', null, '1');
const actionC = ActionCreators.makeMove('B', null, '0');

// test: simultaneous moves
await master.onSync('matchID', '0', 2);
await master.onUpdate(actionA, 0, 'matchID', '0');
await master.onUpdate(setActivePlayers, 1, 'matchID', '0');
await Promise.all([
master.onUpdate(actionB, 2, 'matchID', '1'),
master.onUpdate(actionC, 2, 'matchID', '0'),
]);
await Promise.all([
master.onSync('matchID', '0', 2),
master.onSync('matchID', '1', 2),
]);

const G_player0 = sendAllReturn('0').args[1].G;
const G_player1 = sendAllReturn('1').args[1].G;

expect(G_player0).toMatchObject({
players: {
'0': {
cards: ['card1'],
},
},
cards: ['card0'],
discardedCards: ['card3'],
});
expect(G_player1).toMatchObject({
players: {
'1': {
cards: ['card2'],
},
},
cards: ['card0'],
discardedCards: ['card3'],
});
});

describe('undo / redo', () => {
test('player 0 can undo', async () => {
await master.onUpdate(ActionCreators.undo(), 2, 'matchID', '0');
Expand Down Expand Up @@ -245,7 +393,9 @@ describe('update', () => {
await master.onUpdate(event, 5, 'matchID', '0');
event = ActionCreators.gameEvent('endTurn');
await master.onUpdate(event, 6, 'matchID', '0');
expect(error).toHaveBeenCalledWith(`game over - matchID=[matchID]`);
expect(error).toHaveBeenCalledWith(
`game over - matchID=[matchID] - playerID=[0] - action[endTurn]`
);
});

test('writes gameover to metadata', async () => {
Expand Down Expand Up @@ -286,7 +436,7 @@ describe('update', () => {
const gameOverArg = 'gameOverArg';
const event = ActionCreators.gameEvent('endGame', gameOverArg);
await masterWithMetadata.onUpdate(event, 0, id, '0');
const { metadata } = db.fetch(id, { metadata: true });
const { metadata } = await db.fetch(id, { metadata: true });
expect(metadata.gameover).toEqual(gameOverArg);
});

Expand All @@ -308,7 +458,7 @@ describe('update', () => {
dateMock.advanceTo(updatedAt);
const event = ActionCreators.gameEvent('endTurn', null, '0');
await masterWithMetadata.onUpdate(event, 0, id, '0');
const { metadata } = db.fetch(id, { metadata: true });
const { metadata } = await db.fetch(id, { metadata: true });
expect(metadata.updatedAt).toEqual(updatedAt.getTime());
});

Expand Down Expand Up @@ -343,7 +493,7 @@ describe('update', () => {
await masterWithoutMetadata.onUpdate(event, 0, id, '0');
// Confirm the turn ended.
let metadata: undefined | Server.MatchData;
({ state, metadata } = db.fetch(id, { state: true, metadata: true }));
({ state, metadata } = await db.fetch(id, { state: true, metadata: true }));
expect(state.ctx.turn).toBe(2);
expect(metadata).toBeUndefined();
});
Expand Down

0 comments on commit 864798b

Please sign in to comment.