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
|
return (!!GAMES[gameId]) || false
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGame(id, puzzle, players, sockets, evtInfos) {
|
function createGame(id, rng, puzzle, players, sockets, evtInfos) {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
|
rng: rng,
|
||||||
puzzle: puzzle,
|
puzzle: puzzle,
|
||||||
players: players,
|
players: players,
|
||||||
sockets: sockets,
|
sockets: sockets,
|
||||||
|
|
@ -31,8 +32,8 @@ function createPlayer(id, ts) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function newGame({id, puzzle, players, sockets, evtInfos}) {
|
function newGame({id, rng, puzzle, players, sockets, evtInfos}) {
|
||||||
const game = createGame(id, puzzle, players, sockets, evtInfos)
|
const game = createGame(id, rng, puzzle, players, sockets, evtInfos)
|
||||||
setGame(id, game)
|
setGame(id, game)
|
||||||
return 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
|
// get a unique id
|
||||||
export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2)
|
export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2)
|
||||||
|
|
||||||
// get a random int between min and max (inclusive)
|
// 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
|
// 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) => {
|
export const throttle = (fn, delay) => {
|
||||||
let canCall = true
|
let canCall = true
|
||||||
|
|
@ -21,11 +30,14 @@ export const throttle = (fn, delay) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// return a shuffled (shallow) copy of the given array
|
// return a shuffled (shallow) copy of the given array
|
||||||
export const shuffle = (array) => {
|
export const shuffle = (
|
||||||
|
/** @type Rng */ rng,
|
||||||
|
array
|
||||||
|
) => {
|
||||||
let arr = array.slice()
|
let arr = array.slice()
|
||||||
for (let i = 0; i <= arr.length - 2; i++)
|
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];
|
const tmp = arr[i];
|
||||||
arr[i] = arr[j];
|
arr[i] = arr[j];
|
||||||
arr[j] = tmp;
|
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 minVx = -10
|
||||||
let deltaVx = 20
|
let deltaVx = 20
|
||||||
|
|
@ -17,22 +18,22 @@ const explosionDividerFactor = 10
|
||||||
const nBombs = 1
|
const nBombs = 1
|
||||||
const percentChanceNewBomb = 5
|
const percentChanceNewBomb = 5
|
||||||
|
|
||||||
function color() {
|
function color(/** @type Rng */ rng) {
|
||||||
const r = Util.randomInt(0, 255)
|
const r = Util.randomInt(rng, 0, 255)
|
||||||
const g = Util.randomInt(0, 255)
|
const g = Util.randomInt(rng, 0, 255)
|
||||||
const b = Util.randomInt(0, 255)
|
const b = Util.randomInt(rng, 0, 255)
|
||||||
return 'rgba(' + r + ',' + g + ',' + b + ', 0.8)'
|
return 'rgba(' + r + ',' + g + ',' + b + ', 0.8)'
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Bomb. Or firework.
|
// A Bomb. Or firework.
|
||||||
class Bomb {
|
class Bomb {
|
||||||
constructor() {
|
constructor(/** @type Rng */ rng) {
|
||||||
this.radius = bombRadius
|
this.radius = bombRadius
|
||||||
this.previousRadius = bombRadius
|
this.previousRadius = bombRadius
|
||||||
this.explodingDuration = explodingDuration
|
this.explodingDuration = explodingDuration
|
||||||
this.hasExploded = false
|
this.hasExploded = false
|
||||||
this.alive = true
|
this.alive = true
|
||||||
this.color = color()
|
this.color = color(rng)
|
||||||
|
|
||||||
this.px = (window.innerWidth / 4) + (Math.random() * window.innerWidth / 2)
|
this.px = (window.innerWidth / 4) + (Math.random() * window.innerWidth / 2)
|
||||||
this.py = window.innerHeight
|
this.py = window.innerHeight
|
||||||
|
|
@ -124,8 +125,9 @@ class Particle {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Controller {
|
class Controller {
|
||||||
constructor(canvas) {
|
constructor(canvas, /** @type Rng */ rng) {
|
||||||
this.canvas = canvas
|
this.canvas = canvas
|
||||||
|
this.rng = rng
|
||||||
this.ctx = this.canvas.getContext('2d')
|
this.ctx = this.canvas.getContext('2d')
|
||||||
this.resize()
|
this.resize()
|
||||||
|
|
||||||
|
|
@ -161,13 +163,13 @@ class Controller {
|
||||||
this.particles = []
|
this.particles = []
|
||||||
|
|
||||||
for (let i = 0; i < nBombs; i++) {
|
for (let i = 0; i < nBombs; i++) {
|
||||||
this.readyBombs.push(new Bomb())
|
this.readyBombs.push(new Bomb(this.rng))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
if (Math.random() * 100 < percentChanceNewBomb) {
|
if (Math.random() * 100 < percentChanceNewBomb) {
|
||||||
this.readyBombs.push(new Bomb())
|
this.readyBombs.push(new Bomb(this.rng))
|
||||||
}
|
}
|
||||||
|
|
||||||
const aliveBombs = []
|
const aliveBombs = []
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import Util from './../common/Util.js'
|
||||||
import PuzzleGraphics from './PuzzleGraphics.js'
|
import PuzzleGraphics from './PuzzleGraphics.js'
|
||||||
import Game from './Game.js'
|
import Game from './Game.js'
|
||||||
import fireworksController from './Fireworks.js'
|
import fireworksController from './Fireworks.js'
|
||||||
|
import { Rng } from '../common/Rng.js'
|
||||||
|
|
||||||
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
|
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
|
||||||
if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS 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)
|
const game = await Communication.connect(gameId, CLIENT_ID)
|
||||||
|
game.rng.obj = Rng.unserialize(game.rng.obj)
|
||||||
Game.newGame(game)
|
Game.newGame(game)
|
||||||
|
|
||||||
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(game.puzzle)
|
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(game.puzzle)
|
||||||
|
|
@ -338,7 +340,7 @@ async function main() {
|
||||||
let finished = longFinished ? true : false
|
let finished = longFinished ? true : false
|
||||||
const justFinished = () => !!(finished && !longFinished)
|
const justFinished = () => !!(finished && !longFinished)
|
||||||
|
|
||||||
const fireworks = new fireworksController(canvas)
|
const fireworks = new fireworksController(canvas, game.rng.obj)
|
||||||
fireworks.init(canvas)
|
fireworks.init(canvas)
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d')
|
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",
|
"multer": "^1.4.2",
|
||||||
"twing": "^5.0.2",
|
"twing": "^5.0.2",
|
||||||
"ws": "^7.3.1"
|
"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 { createPuzzle } from './Puzzle.js'
|
||||||
import GameCommon from './../common/GameCommon.js'
|
import GameCommon from './../common/GameCommon.js'
|
||||||
import Util from './../common/Util.js'
|
import Util from './../common/Util.js'
|
||||||
|
import { Rng } from '../common/Rng.js'
|
||||||
|
|
||||||
const DATA_DIR = './../data'
|
const DATA_DIR = './../data'
|
||||||
|
|
||||||
|
|
@ -28,6 +29,10 @@ function loadAllGames() {
|
||||||
}
|
}
|
||||||
GameCommon.newGame({
|
GameCommon.newGame({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
|
rng: {
|
||||||
|
type: game.rng ? game.rng.type : '_fake_',
|
||||||
|
obj: game.rng ? Rng.unserialize(game.rng.obj) : new Rng(),
|
||||||
|
},
|
||||||
puzzle: game.puzzle,
|
puzzle: game.puzzle,
|
||||||
players: game.players,
|
players: game.players,
|
||||||
sockets: [],
|
sockets: [],
|
||||||
|
|
@ -38,9 +43,14 @@ function loadAllGames() {
|
||||||
|
|
||||||
const changedGames = {}
|
const changedGames = {}
|
||||||
async function createGame(gameId, targetTiles, image) {
|
async function createGame(gameId, targetTiles, image) {
|
||||||
|
const rng = new Rng(gameId);
|
||||||
GameCommon.newGame({
|
GameCommon.newGame({
|
||||||
id: gameId,
|
id: gameId,
|
||||||
puzzle: await createPuzzle(targetTiles, image),
|
rng: {
|
||||||
|
type: 'Rng',
|
||||||
|
obj: rng,
|
||||||
|
},
|
||||||
|
puzzle: await createPuzzle(rng, targetTiles, image),
|
||||||
players: {},
|
players: {},
|
||||||
sockets: [],
|
sockets: [],
|
||||||
evtInfos: {},
|
evtInfos: {},
|
||||||
|
|
@ -70,6 +80,10 @@ function persistChangedGames() {
|
||||||
delete changedGames[game.id]
|
delete changedGames[game.id]
|
||||||
fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({
|
fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
|
rng: {
|
||||||
|
type: game.rng.type,
|
||||||
|
obj: Rng.serialize(game.rng.obj),
|
||||||
|
},
|
||||||
puzzle: game.puzzle,
|
puzzle: game.puzzle,
|
||||||
players: game.players,
|
players: game.players,
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import sizeOf from 'image-size'
|
import sizeOf from 'image-size'
|
||||||
import Util from './../common/Util.js'
|
import Util from './../common/Util.js'
|
||||||
import exif from 'exif'
|
import exif from 'exif'
|
||||||
|
import { Rng } from '../common/Rng.js'
|
||||||
|
|
||||||
// cut size of each puzzle tile in the
|
// cut size of each puzzle tile in the
|
||||||
// final resized version of the puzzle image
|
// 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 imagePath = image.file
|
||||||
const imageUrl = image.url
|
const imageUrl = image.url
|
||||||
|
|
||||||
|
|
@ -48,7 +53,7 @@ async function createPuzzle(targetTiles, image) {
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
for (let i = 0; i < tiles.length; i++) {
|
||||||
tiles[i] = { idx: i }
|
tiles[i] = { idx: i }
|
||||||
}
|
}
|
||||||
const shapes = determinePuzzleTileShapes(info)
|
const shapes = determinePuzzleTileShapes(rng, info)
|
||||||
|
|
||||||
let positions = new Array(info.tiles)
|
let positions = new Array(info.tiles)
|
||||||
for (let tile of tiles) {
|
for (let tile of tiles) {
|
||||||
|
|
@ -100,7 +105,7 @@ async function createPuzzle(targetTiles, image) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// then shuffle the positions
|
// then shuffle the positions
|
||||||
positions = Util.shuffle(positions)
|
positions = Util.shuffle(rng, positions)
|
||||||
|
|
||||||
tiles = tiles.map(tile => {
|
tiles = tiles.map(tile => {
|
||||||
return Util.encodeTile({
|
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 tabs = [-1, 1]
|
||||||
|
|
||||||
const shapes = new Array(info.tiles)
|
const shapes = new Array(info.tiles)
|
||||||
|
|
@ -175,9 +183,9 @@ function determinePuzzleTileShapes(info) {
|
||||||
let coord = Util.coordByTileIdx(info, i)
|
let coord = Util.coordByTileIdx(info, i)
|
||||||
shapes[i] = {
|
shapes[i] = {
|
||||||
top: coord.y === 0 ? 0 : shapes[i - info.tilesX].bottom * -1,
|
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,
|
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)
|
return shapes.map(Util.encodeShape)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import Game from './Game.js'
|
||||||
import twing from 'twing'
|
import twing from 'twing'
|
||||||
import bodyParser from 'body-parser'
|
import bodyParser from 'body-parser'
|
||||||
import v8 from 'v8'
|
import v8 from 'v8'
|
||||||
|
import { Rng } from '../common/Rng.js'
|
||||||
|
|
||||||
const allImages = () => [
|
const allImages = () => [
|
||||||
...fs.readdirSync('./../data/uploads/').map(f => ({
|
...fs.readdirSync('./../data/uploads/').map(f => ({
|
||||||
|
|
@ -130,9 +131,14 @@ wss.on('message', async ({socket, data}) => {
|
||||||
Game.addPlayer(gameId, clientId)
|
Game.addPlayer(gameId, clientId)
|
||||||
Game.addSocket(gameId, socket)
|
Game.addSocket(gameId, socket)
|
||||||
const game = Game.get(gameId)
|
const game = Game.get(gameId)
|
||||||
|
console.log(gameId, game)
|
||||||
notify(
|
notify(
|
||||||
[Protocol.EV_SERVER_INIT, {
|
[Protocol.EV_SERVER_INIT, {
|
||||||
id: game.id,
|
id: game.id,
|
||||||
|
rng: {
|
||||||
|
type: game.rng.type,
|
||||||
|
obj: Rng.serialize(game.rng.obj),
|
||||||
|
},
|
||||||
puzzle: game.puzzle,
|
puzzle: game.puzzle,
|
||||||
players: game.players,
|
players: game.players,
|
||||||
sockets: [],
|
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