Skip to content

Commit

Permalink
Server-side array shuffling. (#116)
Browse files Browse the repository at this point in the history
* deterministic array shuffling

* use fast-shuffle for faster shuffle

* fix rollup config
  • Loading branch information
philihp authored and nicolodavis committed Feb 20, 2018
1 parent 1a3ced2 commit 45599e5
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 1 deletion.
23 changes: 23 additions & 0 deletions docs/random.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ const SomeGame = Game({
This will place a request to a D6 dice roll inside `G`.
While processing the move, the request gets evaluated and the result placed into `diceValue`, where it can be used.

### Shuffles

To simulate a shuffled deck of cards, sometimes it might be simplest to draw a card from a random index in an array. This can get confusing if your game moves
include a move for peeking at the top card which is later rendered moot by
another move which shuffles the deck. To simplify modeling this, you might want
to represent your deck as an array. Use `Random.Shuffle` to perform a shallow
shuffle of the elements.

```js
import { Random } from 'boardgame.io/core';

const SomeGame = Game({
setup: () => ({
deck: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
}),
moves: {
shuffleDeck(G) {
return Random.Shuffle(G, 'deck');
},
},
});
```

## Seed

The library uses a `seed` in `ctx` that is stripped before it
Expand Down
2 changes: 1 addition & 1 deletion docs/react/boardgameio.min.js

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions examples/modules/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import tic_tac_toe from './tic-tac-toe';
import chess from './chess';
import phases from './phases';
import liars_dice from './liars-dice';
import shuffle from './shuffle';

const routes = [
{
Expand All @@ -28,6 +29,10 @@ const routes = [
name: 'Liars Dice',
routes: liars_dice.routes,
},
{
name: 'Shuffle',
routes: shuffle.routes,
},
];

export default routes;
18 changes: 18 additions & 0 deletions examples/modules/shuffle/components/board.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2018 The boardgame.io Authors.
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import React from 'react';
import PropTypes from 'prop-types';

const Board = ({ G }) => <pre>{JSON.stringify(G, null, 2)}</pre>;

Board.propTypes = {
G: PropTypes.any.isRequired,
};

export default Board;
26 changes: 26 additions & 0 deletions examples/modules/shuffle/components/view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2018 The boardgame.io Authors.
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import React from 'react';
import { Client } from 'boardgame.io/client';
import Game from '../game';
import Board from './board';

const App = Client({
game: Game,
numPlayers: 1,
board: Board,
});

const SingleView = () => (
<div style={{ padding: 50 }}>
<App gameID="Shuffle" />
</div>
);

export default SingleView;
23 changes: 23 additions & 0 deletions examples/modules/shuffle/game.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2018 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import { Game, Random } from 'boardgame.io/core';

const Shuffle = Game({
name: 'shuffle',

setup: () => ({
deck: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
}),

moves: {
shuffle: G => Random.Shuffle(G, 'deck'),
},
});

export default Shuffle;
14 changes: 14 additions & 0 deletions examples/modules/shuffle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2018 The boardgame.io Authors.
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import routes from './routes';

// Any other additional setup for this this module
export default {
routes,
};
19 changes: 19 additions & 0 deletions examples/modules/shuffle/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2018 The boardgame.io Authors.
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import View from './components/view';

const routes = [
{
path: '/shuffle/cards',
text: 'Shuffle Cards',
component: View,
},
];

export default routes;
5 changes: 5 additions & 0 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
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"webpack": "^3.5.5"
},
"dependencies": {
"fast-shuffle": "^1.0.4",
"koa": "^2.3.0",
"koa-router": "^7.2.1",
"koa-socket": "^4.4.0",
Expand Down
3 changes: 3 additions & 0 deletions rollup.npm.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const globals = {
'react-json-view': 'ReactJson',
mousetrap: 'Mousetrap',
'socket.io-client': 'io',
'fast-shuffle': 'shuffle',
};

export default [
Expand Down Expand Up @@ -60,6 +61,8 @@ export default [

{
input: 'packages/core.js',
external: ['fast-shuffle'],
globals: { 'fast-shuffle': 'shuffle' },
output: { file: 'dist/core.js', format: 'umd' },
name: 'Core',
plugins: plugins,
Expand Down
12 changes: 12 additions & 0 deletions src/core/random.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
*/

import { alea } from './random.alea';
import shuffle from 'fast-shuffle';

export const DICE = 'DICE';
export const NUMBER = 'NUMBER';
export const SHUFFLE = 'SHUFFLE';

function getrandomfn(ctx) {
let randomfn;
Expand Down Expand Up @@ -66,6 +68,12 @@ export function evaluaterandomops(G, ctx) {
break;
}

case SHUFFLE: {
const rng = alea(randomnumber);
G[r.fieldname] = shuffle(G[r.fieldname], rng);
break;
}

default:
break;
}
Expand Down Expand Up @@ -115,4 +123,8 @@ export const Random = {
Number: (G, fieldname) => {
return addrandomop(G, fieldname, NUMBER);
},

Shuffle: (G, fieldname) => {
return addrandomop(G, fieldname, SHUFFLE);
},
};
15 changes: 15 additions & 0 deletions src/core/random.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,18 @@ test('Random.Number', () => {
expect(G3.field1).toBeLessThanOrEqual(1);
expect(G3._randomOps).toBeUndefined();
});

test('Random.Shuffle', () => {
const initialTiles = ['A', 'B', 'C', 'D', 'E'];
let ctx = { random: { seed: 'some_predetermined_seed' } };
let G = { tiles: initialTiles };

// random event - shuffle tiles order
let G2 = Random.Shuffle(G, 'tiles');

let { G: G3, ctx: ctx2 } = RunRandom(G2, ctx);
expect(G3.tiles.length).toEqual(initialTiles.length);
expect(G3.tiles).toEqual(expect.arrayContaining(initialTiles));
expect(G3.tiles.sort()).toEqual(initialTiles);
expect(ctx).not.toMatchObject(ctx2);
});

0 comments on commit 45599e5

Please sign in to comment.