rng class..
This commit is contained in:
parent
3ff375dbb4
commit
cbc2b88f47
12 changed files with 4762 additions and 25 deletions
|
|
@ -7,9 +7,10 @@ function exists(gameId) {
|
|||
return (!!GAMES[gameId]) || false
|
||||
}
|
||||
|
||||
function createGame(id, puzzle, players, sockets, evtInfos) {
|
||||
function createGame(id, rng, puzzle, players, sockets, evtInfos) {
|
||||
return {
|
||||
id: id,
|
||||
rng: rng,
|
||||
puzzle: puzzle,
|
||||
players: players,
|
||||
sockets: sockets,
|
||||
|
|
@ -31,8 +32,8 @@ function createPlayer(id, ts) {
|
|||
}
|
||||
}
|
||||
|
||||
function newGame({id, puzzle, players, sockets, evtInfos}) {
|
||||
const game = createGame(id, puzzle, players, sockets, evtInfos)
|
||||
function newGame({id, rng, puzzle, players, sockets, evtInfos}) {
|
||||
const game = createGame(id, rng, puzzle, players, sockets, evtInfos)
|
||||
setGame(id, game)
|
||||
return game
|
||||
}
|
||||
|
|
|
|||
27
common/Rng.js
Normal file
27
common/Rng.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
export class Rng {
|
||||
constructor(seed) {
|
||||
this.rand_high = seed || 0xDEADC0DE
|
||||
this.rand_low = seed ^ 0x49616E42
|
||||
}
|
||||
|
||||
random (min, max) {
|
||||
this.rand_high = ((this.rand_high << 16) + (this.rand_high >> 16) + this.rand_low) & 0xffffffff;
|
||||
this.rand_low = (this.rand_low + this.rand_high) & 0xffffffff;
|
||||
var n = (this.rand_high >>> 0) / 0xffffffff;
|
||||
return (min + n * (max-min+1))|0;
|
||||
}
|
||||
|
||||
static serialize (rng) {
|
||||
return {
|
||||
rand_high: rng.rand_high,
|
||||
rand_low: rng.rand_low
|
||||
}
|
||||
}
|
||||
|
||||
static unserialize (rngSerialized) {
|
||||
const rng = new Rng(0)
|
||||
rng.rand_high = rngSerialized.rand_high
|
||||
rng.rand_low = rngSerialized.rand_low
|
||||
return rng
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,20 @@
|
|||
import { Rng } from './Rng.js'
|
||||
|
||||
// get a unique id
|
||||
export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2)
|
||||
|
||||
// get a random int between min and max (inclusive)
|
||||
export const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
|
||||
export const randomInt = (
|
||||
/** @type Rng */ rng,
|
||||
min,
|
||||
max
|
||||
) => rng.random(min, max)
|
||||
|
||||
// get one random item from the given array
|
||||
export const choice = (array) => array[randomInt(0, array.length - 1)]
|
||||
export const choice = (
|
||||
/** @type Rng */ rng,
|
||||
array
|
||||
) => array[randomInt(rng, 0, array.length - 1)]
|
||||
|
||||
export const throttle = (fn, delay) => {
|
||||
let canCall = true
|
||||
|
|
@ -21,11 +30,14 @@ export const throttle = (fn, delay) => {
|
|||
}
|
||||
|
||||
// return a shuffled (shallow) copy of the given array
|
||||
export const shuffle = (array) => {
|
||||
export const shuffle = (
|
||||
/** @type Rng */ rng,
|
||||
array
|
||||
) => {
|
||||
let arr = array.slice()
|
||||
for (let i = 0; i <= arr.length - 2; i++)
|
||||
{
|
||||
const j = randomInt(i, arr.length -1);
|
||||
const j = randomInt(rng, i, arr.length -1);
|
||||
const tmp = arr[i];
|
||||
arr[i] = arr[j];
|
||||
arr[j] = tmp;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Util from './../common/Util.js'
|
||||
import { Rng } from '../common/Rng.js'
|
||||
import Util from '../common/Util.js'
|
||||
|
||||
let minVx = -10
|
||||
let deltaVx = 20
|
||||
|
|
@ -17,22 +18,22 @@ const explosionDividerFactor = 10
|
|||
const nBombs = 1
|
||||
const percentChanceNewBomb = 5
|
||||
|
||||
function color() {
|
||||
const r = Util.randomInt(0, 255)
|
||||
const g = Util.randomInt(0, 255)
|
||||
const b = Util.randomInt(0, 255)
|
||||
function color(/** @type Rng */ rng) {
|
||||
const r = Util.randomInt(rng, 0, 255)
|
||||
const g = Util.randomInt(rng, 0, 255)
|
||||
const b = Util.randomInt(rng, 0, 255)
|
||||
return 'rgba(' + r + ',' + g + ',' + b + ', 0.8)'
|
||||
}
|
||||
|
||||
// A Bomb. Or firework.
|
||||
class Bomb {
|
||||
constructor() {
|
||||
constructor(/** @type Rng */ rng) {
|
||||
this.radius = bombRadius
|
||||
this.previousRadius = bombRadius
|
||||
this.explodingDuration = explodingDuration
|
||||
this.hasExploded = false
|
||||
this.alive = true
|
||||
this.color = color()
|
||||
this.color = color(rng)
|
||||
|
||||
this.px = (window.innerWidth / 4) + (Math.random() * window.innerWidth / 2)
|
||||
this.py = window.innerHeight
|
||||
|
|
@ -124,8 +125,9 @@ class Particle {
|
|||
}
|
||||
|
||||
class Controller {
|
||||
constructor(canvas) {
|
||||
constructor(canvas, /** @type Rng */ rng) {
|
||||
this.canvas = canvas
|
||||
this.rng = rng
|
||||
this.ctx = this.canvas.getContext('2d')
|
||||
this.resize()
|
||||
|
||||
|
|
@ -161,13 +163,13 @@ class Controller {
|
|||
this.particles = []
|
||||
|
||||
for (let i = 0; i < nBombs; i++) {
|
||||
this.readyBombs.push(new Bomb())
|
||||
this.readyBombs.push(new Bomb(this.rng))
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (Math.random() * 100 < percentChanceNewBomb) {
|
||||
this.readyBombs.push(new Bomb())
|
||||
this.readyBombs.push(new Bomb(this.rng))
|
||||
}
|
||||
|
||||
const aliveBombs = []
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import Util from './../common/Util.js'
|
|||
import PuzzleGraphics from './PuzzleGraphics.js'
|
||||
import Game from './Game.js'
|
||||
import fireworksController from './Fireworks.js'
|
||||
import { Rng } from '../common/Rng.js'
|
||||
|
||||
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
|
||||
if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS not set ]'
|
||||
|
|
@ -324,6 +325,7 @@ async function main() {
|
|||
}
|
||||
|
||||
const game = await Communication.connect(gameId, CLIENT_ID)
|
||||
game.rng.obj = Rng.unserialize(game.rng.obj)
|
||||
Game.newGame(game)
|
||||
|
||||
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(game.puzzle)
|
||||
|
|
@ -338,7 +340,7 @@ async function main() {
|
|||
let finished = longFinished ? true : false
|
||||
const justFinished = () => !!(finished && !longFinished)
|
||||
|
||||
const fireworks = new fireworksController(canvas)
|
||||
const fireworks = new fireworksController(canvas, game.rng.obj)
|
||||
fireworks.init(canvas)
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
|
|
|||
4636
package-lock.json
generated
4636
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,5 +8,8 @@
|
|||
"multer": "^1.4.2",
|
||||
"twing": "^5.0.2",
|
||||
"ws": "^7.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.6.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
scripts/tests
Executable file
3
scripts/tests
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
node --experimental-vm-modules node_modules/.bin/jest
|
||||
|
|
@ -2,6 +2,7 @@ import fs from 'fs'
|
|||
import { createPuzzle } from './Puzzle.js'
|
||||
import GameCommon from './../common/GameCommon.js'
|
||||
import Util from './../common/Util.js'
|
||||
import { Rng } from '../common/Rng.js'
|
||||
|
||||
const DATA_DIR = './../data'
|
||||
|
||||
|
|
@ -28,6 +29,10 @@ function loadAllGames() {
|
|||
}
|
||||
GameCommon.newGame({
|
||||
id: game.id,
|
||||
rng: {
|
||||
type: game.rng ? game.rng.type : '_fake_',
|
||||
obj: game.rng ? Rng.unserialize(game.rng.obj) : new Rng(),
|
||||
},
|
||||
puzzle: game.puzzle,
|
||||
players: game.players,
|
||||
sockets: [],
|
||||
|
|
@ -38,9 +43,14 @@ function loadAllGames() {
|
|||
|
||||
const changedGames = {}
|
||||
async function createGame(gameId, targetTiles, image) {
|
||||
const rng = new Rng(gameId);
|
||||
GameCommon.newGame({
|
||||
id: gameId,
|
||||
puzzle: await createPuzzle(targetTiles, image),
|
||||
rng: {
|
||||
type: 'Rng',
|
||||
obj: rng,
|
||||
},
|
||||
puzzle: await createPuzzle(rng, targetTiles, image),
|
||||
players: {},
|
||||
sockets: [],
|
||||
evtInfos: {},
|
||||
|
|
@ -70,6 +80,10 @@ function persistChangedGames() {
|
|||
delete changedGames[game.id]
|
||||
fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({
|
||||
id: game.id,
|
||||
rng: {
|
||||
type: game.rng.type,
|
||||
obj: Rng.serialize(game.rng.obj),
|
||||
},
|
||||
puzzle: game.puzzle,
|
||||
players: game.players,
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import sizeOf from 'image-size'
|
||||
import Util from './../common/Util.js'
|
||||
import exif from 'exif'
|
||||
import { Rng } from '../common/Rng.js'
|
||||
|
||||
// cut size of each puzzle tile in the
|
||||
// final resized version of the puzzle image
|
||||
|
|
@ -34,7 +35,11 @@ async function getExifOrientation(imagePath) {
|
|||
})
|
||||
}
|
||||
|
||||
async function createPuzzle(targetTiles, image) {
|
||||
async function createPuzzle(
|
||||
/** @type Rng */ rng,
|
||||
targetTiles,
|
||||
image
|
||||
) {
|
||||
const imagePath = image.file
|
||||
const imageUrl = image.url
|
||||
|
||||
|
|
@ -48,7 +53,7 @@ async function createPuzzle(targetTiles, image) {
|
|||
for (let i = 0; i < tiles.length; i++) {
|
||||
tiles[i] = { idx: i }
|
||||
}
|
||||
const shapes = determinePuzzleTileShapes(info)
|
||||
const shapes = determinePuzzleTileShapes(rng, info)
|
||||
|
||||
let positions = new Array(info.tiles)
|
||||
for (let tile of tiles) {
|
||||
|
|
@ -100,7 +105,7 @@ async function createPuzzle(targetTiles, image) {
|
|||
}
|
||||
|
||||
// then shuffle the positions
|
||||
positions = Util.shuffle(positions)
|
||||
positions = Util.shuffle(rng, positions)
|
||||
|
||||
tiles = tiles.map(tile => {
|
||||
return Util.encodeTile({
|
||||
|
|
@ -167,7 +172,10 @@ async function createPuzzle(targetTiles, image) {
|
|||
}
|
||||
}
|
||||
|
||||
function determinePuzzleTileShapes(info) {
|
||||
function determinePuzzleTileShapes(
|
||||
/** @type Rng */ rng,
|
||||
info
|
||||
) {
|
||||
const tabs = [-1, 1]
|
||||
|
||||
const shapes = new Array(info.tiles)
|
||||
|
|
@ -175,9 +183,9 @@ function determinePuzzleTileShapes(info) {
|
|||
let coord = Util.coordByTileIdx(info, i)
|
||||
shapes[i] = {
|
||||
top: coord.y === 0 ? 0 : shapes[i - info.tilesX].bottom * -1,
|
||||
right: coord.x === info.tilesX - 1 ? 0 : Util.choice(tabs),
|
||||
right: coord.x === info.tilesX - 1 ? 0 : Util.choice(rng, tabs),
|
||||
left: coord.x === 0 ? 0 : shapes[i - 1].right * -1,
|
||||
bottom: coord.y === info.tilesY - 1 ? 0 : Util.choice(tabs),
|
||||
bottom: coord.y === info.tilesY - 1 ? 0 : Util.choice(rng, tabs),
|
||||
}
|
||||
}
|
||||
return shapes.map(Util.encodeShape)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import Game from './Game.js'
|
|||
import twing from 'twing'
|
||||
import bodyParser from 'body-parser'
|
||||
import v8 from 'v8'
|
||||
import { Rng } from '../common/Rng.js'
|
||||
|
||||
const allImages = () => [
|
||||
...fs.readdirSync('./../data/uploads/').map(f => ({
|
||||
|
|
@ -130,9 +131,14 @@ wss.on('message', async ({socket, data}) => {
|
|||
Game.addPlayer(gameId, clientId)
|
||||
Game.addSocket(gameId, socket)
|
||||
const game = Game.get(gameId)
|
||||
console.log(gameId, game)
|
||||
notify(
|
||||
[Protocol.EV_SERVER_INIT, {
|
||||
id: game.id,
|
||||
rng: {
|
||||
type: game.rng.type,
|
||||
obj: Rng.serialize(game.rng.obj),
|
||||
},
|
||||
puzzle: game.puzzle,
|
||||
players: game.players,
|
||||
sockets: [],
|
||||
|
|
|
|||
23
tests/Rng.test.js
Normal file
23
tests/Rng.test.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Rng } from '../common/Rng'
|
||||
|
||||
test('random should give same results', () => {
|
||||
const rng = new Rng(1337)
|
||||
const rng2 = new Rng(1337)
|
||||
for (let i = 0; i < 100; i++) {
|
||||
expect(rng.random()).toBe(rng2.random())
|
||||
}
|
||||
})
|
||||
|
||||
test('should be serializable/deserializable', () => {
|
||||
const rng = new Rng(1337)
|
||||
// do some randoms, so that it doesnt start at 'after init'
|
||||
for (let i = 0; i < 100; i++) {
|
||||
rng.random()
|
||||
}
|
||||
|
||||
const rng2 = Rng.unserialize(Rng.serialize(rng))
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
expect(rng.random()).toBe(rng2.random())
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue