Skip to content

Commit

Permalink
Merge 96ce621 into 2595399
Browse files Browse the repository at this point in the history
  • Loading branch information
pociej committed Oct 12, 2020
2 parents 2595399 + 96ce621 commit b0fbbf7
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 43 deletions.
5 changes: 2 additions & 3 deletions docs/documentation/multiplayer.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ up pass-and-play multiplayer or for prototyping the multiplayer experience
without having to set up a server to test it.

To do this `import { Local } from 'boardgame.io/multiplayer'`,
and add `multiplayer: Local()` to the client options.
Now you can instantiate as many of these clients in your app as you like and
you will notice that they’re all kept in sync, sharing the same state.
and add `multiplayer: Local()` to the client options. Or `multiplayer: Local({ persist : true })` if you want game state to be cached using `localStorage`. Now you can instantiate as many of these clients in your app as you like and you will notice that they’re all kept in sync, sharing the same state.

<!-- tabs:start -->

Expand Down Expand Up @@ -85,6 +83,7 @@ const clients = playerIDs.map(playerID => {

#### **React**


```js
// src/App.js
Expand Down
41 changes: 11 additions & 30 deletions package-lock.json

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

15 changes: 11 additions & 4 deletions src/client/transport/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import * as ActionCreators from '../../core/action-creators';
import { InMemory } from '../../server/db/inmemory';
import { LocalStorage } from '../../server/db/localstorage';
import { Master, TransportAPI } from '../../master/master';
import { Transport, TransportOpts } from './transport';
import {
Expand Down Expand Up @@ -44,6 +45,8 @@ export function GetBotPlayer(state: State, bots: Record<PlayerID, any>) {
interface LocalMasterOpts {
game: Game;
bots: Record<PlayerID, any>;
persist: boolean;
storageKey: string;
}

/**
Expand All @@ -57,7 +60,7 @@ export class LocalMaster extends Master {
callback: (...args: any[]) => void
) => void;

constructor({ game, bots }: LocalMasterOpts) {
constructor({ game, bots, storageKey, persist }: LocalMasterOpts) {
const clientCallbacks: Record<PlayerID, (...args: any[]) => void> = {};
const initializedBots = {};

Expand Down Expand Up @@ -88,8 +91,8 @@ export class LocalMaster extends Master {
}
},
};

super(game, new InMemory(), transportAPI, false);
const storage = persist ? new LocalStorage(storageKey) : new InMemory();
super(game, storage, transportAPI, false);

this.connect = (matchID, playerID, callback) => {
clientCallbacks[playerID] = callback;
Expand Down Expand Up @@ -235,7 +238,9 @@ export class LocalTransport extends Transport {
}

const localMasters = new Map();
export function Local(opts?: Pick<LocalMasterOpts, 'bots'>) {
export function Local(
opts?: Pick<LocalMasterOpts, 'bots' | 'persist' | 'storageKey'>
) {
return (transportOpts: TransportOpts) => {
let master: LocalMaster;

Expand All @@ -245,6 +250,8 @@ export function Local(opts?: Pick<LocalMasterOpts, 'bots'>) {
master = new LocalMaster({
game: transportOpts.game,
bots: opts && opts.bots,
persist: opts && opts.persist,
storageKey: opts && opts.storageKey,
});
localMasters.set(transportOpts.gameKey, master);
}
Expand Down
4 changes: 2 additions & 2 deletions src/server/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InMemory } from './inmemory';
import { FlatFile } from './flatfile';

import { LocalStorage } from './localstorage';
const DBFromEnv = () => {
if (process.env.FLATFILE_DIR) {
return new FlatFile({
Expand All @@ -11,4 +11,4 @@ const DBFromEnv = () => {
}
};

export { InMemory, FlatFile, DBFromEnv };
export { InMemory, FlatFile, LocalStorage, DBFromEnv };
8 changes: 4 additions & 4 deletions src/server/db/inmemory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import * as StorageAPI from './base';
* InMemory data storage.
*/
export class InMemory extends StorageAPI.Sync {
private state: Map<string, State>;
private initial: Map<string, State>;
private metadata: Map<string, Server.MatchData>;
private log: Map<string, LogEntry[]>;
protected state: Map<string, State>;
protected initial: Map<string, State>;
protected metadata: Map<string, Server.MatchData>;
protected log: Map<string, LogEntry[]>;

/**
* Creates a new InMemory storage.
Expand Down
79 changes: 79 additions & 0 deletions src/server/db/localstorage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { LocalStorage } from './localstorage';
import { State, Server } from '../../types';

// this is not best place for fake string generator

const getRandomString = () =>
Math.random()
.toString(36)
.substring(7);

describe('LocaLStorage', () => {
let db: LocalStorage;

beforeAll(() => {
db = new LocalStorage(getRandomString());
db.connect();
});

// Must return undefined when no game exists.
test('must return undefined when no game exists', () => {
const { state } = db.fetch('gameID', { state: true });
expect(state).toEqual(undefined);
});

test('create game', () => {
let stateEntry: unknown = { a: 1 };

// Create game.
db.createMatch('gameID', {
metadata: {
gameName: 'tic-tac-toe',
} as Server.MatchData,
initialState: stateEntry as State,
});

// Must return created game.
const { state } = db.fetch('gameID', { state: true });
expect(state).toEqual(stateEntry);

// Fetch initial state.
const { initialState } = db.fetch('gameID', { initialState: true });
expect(initialState).toEqual(stateEntry);
});

test('listGames', () => {
let keys = db.listMatches({});
expect(keys).toEqual(['gameID']);
keys = db.listMatches({ gameName: 'tic-tac-toe' });
expect(keys).toEqual(['gameID']);
keys = db.listMatches({ gameName: 'chess' });
expect(keys).toEqual([]);
});

test('remove', () => {
// Must remove game from DB
db.wipe('gameID');
expect(db.fetch('gameID', { state: true })).toEqual({});
// Shall not return error
db.wipe('gameID');
});

test('must create new empty db if other localstorage key is used', () => {
// create another localstorage with anothr key
let db2 = new LocalStorage(getRandomString());
let stateEntry: unknown = { a: 1 };

// create game in db
db.createMatch('gameID', {
metadata: {
gameName: 'tic-tac-toe',
} as Server.MatchData,
initialState: stateEntry as State,
});

// game shouldnt be visible in db2
const { state } = db2.fetch('gameID', { state: true });
expect(state).toEqual(undefined);
});
});

0 comments on commit b0fbbf7

Please sign in to comment.