-
Notifications
You must be signed in to change notification settings - Fork 703
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add dependency to seedrandom * initially add module containing random/shuffle fns * align tests with other test code. * add seed to ctx (either a provided or a created one) * secret state: delete "seed" key from ctx where playerView() is used. * amend test to bring branch coverage to 100% * use object spread syntax for key deletion. * store prng state on ctx. * implement rolldie and allow evaluation. * drop any modification of and reliance on an existing ctx * inline newCtx * pass seed through separate option rather than through G * prepare code to be split into public/internal parts, commented out shuffle * split API public/internal * drop shuffle * drop TODOs * fix comments * embed random into flow, breaks a log test * fix gamelog rewind test * use es6 export keyword * add seedrandom as new global * drop console.log statements used for debugging * add random.md after first iteration * add second iteration on random.md * add second iteration over client-facing random API * allow to evaluate random die values. * add fns for all commonly used dice. * allow arbitrary die spot values. * add export random API * pass seed to flow for proper initialization * add third pass over random.md * add license headers for new files * some tweaks to random.md * rename RequestRandom to Random * pass spotvalue as arg to addrandomop and retire regex * update random.md
- Loading branch information
1 parent
f510b69
commit d296b36
Showing
18 changed files
with
345 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# Randomness and its Use in Games | ||
|
||
Many games allow moves whose outcome depends on shuffled cards or rolled dice. | ||
Take e.g. the game [Yahtzee](https://en.wikipedia.org/wiki/Yahtzee). | ||
A player rolls dice, chooses some, rolls another time, chooses some more, and does a final dice roll. | ||
Depending on the face-up sides he now must choose where he will score. | ||
|
||
This poses interesting challenges regarding the implementation. | ||
|
||
* **AI**. Randomness makes games interesting since you cannot predict the future, but it | ||
needs to be controlled in order for allowing games that can be replayed exactly (e.g. for AI purposes). | ||
|
||
* **PRNG State**. The game runs on both the server and client. | ||
All code and data on the client can be viewed and used to a player's advantage. | ||
If a client could predict the next random numbers that are to be generated, the future flow of a game stops being unpredictable. | ||
The library must not allow such a scenario. The RNG and its state must stay at the server. | ||
|
||
* **Pure Functions**. The library is built using Redux. This is important for games since each move is a [reducer](https://redux.js.org/docs/basics/Reducers.html), | ||
and thus must be pure. Calling `Math.random()` and other functions that | ||
maintain external state would make the game logic impure and not idempotent. | ||
|
||
## Using Randomness in Games | ||
|
||
[boardgame.io]() takes a rather unusual approach to randomness: It disallows getting random variables directly. | ||
Instead, a game can ask the engine to generate random numbers, and the engine will inject those into the game on the next move. | ||
|
||
```js | ||
import { Random } from 'boardgame.io/core'; | ||
|
||
const SomeGame = Game({ | ||
moves: { | ||
rollDie(G, ctx) { | ||
// G.diceValue will contain the requested | ||
// die value at the end of this move. | ||
return Random.D6(G, 'diceValue'); | ||
}, | ||
}, | ||
|
||
flow: { | ||
onMove: G => { | ||
const dice = G.diceValue; | ||
// do something... | ||
return { ...G }; | ||
}, | ||
}, | ||
// ... | ||
}); | ||
``` | ||
|
||
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. | ||
|
||
## Background | ||
|
||
There is an interesting background article by David Bau called [Random Seeds, Coded Hints, and Quintillions](http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html). | ||
Despite its age, this article gives insight on topics about randomness, like differentiating _local_ and _network_ entropy. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* Copyright 2017 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 { addrandomop, DICE, NUMBER } from './randomeval'; | ||
|
||
const SpotValue = { | ||
D4: 4, | ||
D6: 6, | ||
D8: 8, | ||
D10: 10, | ||
D12: 12, | ||
D20: 20, | ||
}; | ||
|
||
// generate functions for predefined dice values D4 - D20 | ||
const predefined = {}; | ||
for (const key in SpotValue) { | ||
const value = SpotValue[key]; | ||
predefined[key] = (G, fieldname) => { | ||
return addrandomop(G, fieldname, DICE, value); | ||
}; | ||
} | ||
|
||
export const Random = { | ||
...predefined, | ||
Die: (G, fieldname, spotvalue) => { | ||
return addrandomop(G, fieldname, DICE, spotvalue); | ||
}, | ||
Number: (G, fieldname) => { | ||
return addrandomop(G, fieldname, NUMBER); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
* Copyright 2017 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 seedrandom from 'seedrandom'; | ||
|
||
export const DICE = 'DICE'; | ||
export const NUMBER = 'NUMBER'; | ||
|
||
function getrandomfn(ctx) { | ||
let randomfn; | ||
if (ctx.prngstate === undefined) { | ||
// no call to a random function has been made. | ||
// pre-populate the state info | ||
randomfn = new seedrandom.alea(ctx.seed, { state: true }); | ||
} else { | ||
randomfn = new seedrandom.alea('', { state: ctx.prngstate }); | ||
} | ||
return randomfn; | ||
} | ||
|
||
export function randomctx(ctx) { | ||
const r = getrandomfn(ctx); | ||
const randomnumber = r(); | ||
const ctx2 = { ...ctx, prngstate: r.state() }; | ||
return { randomnumber, ctx: ctx2 }; | ||
} | ||
|
||
export function addrandomop(G, fieldname, op, ...args) { | ||
let rop = [{ op, fieldname, args }]; | ||
let _randomOps = [...(G._randomOps || []), ...rop]; | ||
return { ...G, _randomOps }; | ||
} | ||
|
||
export function evaluaterandomops(G, ctx) { | ||
let randomresults = {}; | ||
let ctx2 = ctx; | ||
|
||
// some flow tests run without a defined G | ||
if (G && G._randomOps !== undefined) { | ||
G._randomOps.forEach(r => { | ||
const { ctx: ctx3, randomnumber } = randomctx(ctx2); | ||
ctx2 = ctx3; | ||
|
||
switch (r.op) { | ||
case DICE: { | ||
const spotvalue = r.args[0]; | ||
const dievalue = Math.floor(randomnumber * spotvalue) + 1; | ||
randomresults[r.fieldname] = dievalue; | ||
break; | ||
} | ||
|
||
case NUMBER: { | ||
randomresults[r.fieldname] = randomnumber; | ||
break; | ||
} | ||
|
||
default: | ||
break; | ||
} | ||
}); | ||
} | ||
|
||
return { randomresults, ctx: ctx2 }; | ||
} | ||
|
||
export function runrandom(G, ctx) { | ||
let { randomresults, ctx: ctx2 } = evaluaterandomops(G, ctx); | ||
const G2 = { ...G, ...randomresults, _randomOps: undefined }; | ||
return { G: G2, ctx: ctx2 }; | ||
} |
Oops, something went wrong.