Trying to figure out how to store some useful data for chess games programming.
I decided to store rays emitted by on-board chessmen in a Raycaster; This question is about the implementation of this structure.
TL;DR (for chess gamers only...)
first of all, I have identified three kinds of rays :
Ray.NORMAL or Ray.FULL: they are emitted by all chessmen but not pawns, in an iterative way (rook, bishop, queen) or not (knight and king)
Ray.CAPTURE: emitted only by pawns, ahead left and /or ahead right captures
Ray.OFFSET: emitted by pawns when moving forward, and kings/rooks for castling
Thus a ray is defined like this :
class Ray {
constructor (owner, kind) {
this.owner = owner // chessman which emits ray
this.kind = kind
this.ref = null
this.sequence = []
}
// is computed afetr construction
expand (ref, sequence) {
this.ref = ref // starting ref (origin of the ray)
this.sequence = sequence // array of refs
}
// is called when ray is inserted into raycaster
interact (otherRay) {
// to be implemented
}
}
Rays also have two special compound properties :
shadowing { ray: null, idx: -1 }
crossing { ray: null, idx: -1 }
which denote where this ray instance may be shadowed by another chessman, and where another ray is crossing it, to detect passability and interference (for castling)
THE PROBLEM:
How to store efficiently rays in the RayCaster?
In a way that optimizes operations such as:
adding a newly computed ray, computing interactions with previously stored ones, at a mo,opmal cost?
determining from a given starting ref, all targeted tiles/ref?
determining easily which rays target a given ref, to compute pression balance on this tile?
PROPOSED SOLUTIONS / ALTERNATIVES
single array of rays : worst case 64 * 63 elements, costful for seeking a ray and compute interactions
Map of arrays : Map.set(startingRef, [list_of_emtted_rays_from_startingRef])
Map of arrays : Map.set(endingRef, [list_of_targetinhg_rays_to_endingRef])
and maybe a good candidate :
maintain 2 maps of arrays, one for emitted, and one for targeting
class RayCaster {
constructor() {
this.startings = new Map()
this.endings = new Map()
}
cast(board) { ...iterate through board and casts individual rays }
add(ray) { ... }
getRefsAttackedBy(ref) { ... }
getRefsAttacking(ref) { ... }
}
So what are your feelings about this data structure (the RayCaster)?
finally since Maps are time constant access, I have considered a double-maps implementation :
constructor() {
this.startings = new Map()
this.endings = new Map()
this.counters = { rays: 0, interactions: 0 }
}
Each map is keyed by board refs, from "a1" to "h8"
casts(board) {
this.counters = {
rays: this.raysFrom(board),
interactions: this.computeInteractions()
}
}
Adding rays is straighforward :
raysFrom(board) {
let counter = 0;
board.traverse((ref, chessman) => {
let rays = chessman.cast(ref)
for(let ray of rays) {
this.add(ray)
}
counter += rays.length
})
return counter
}
And a a simple ray :
add (ray) {
let skey = ray.ref
let sRays = this.startings.get(sKey)
if(sRays.indexOf(ray) === -1) {
sRays.push(ray)
}
ray.traverse((seqIdx, seqRef) => {
let seqKey = seqRef.key
let eRays = this.endings.get(seqKey)
if (eRays.indexOf(ray) === -1) {
eRays.push(ray)
}
})
}
Computing ray interactions (crossing and shading) is more complicated :
computeInteractions() {
let counter = 0
// consider all starting rays
for (let {sRef, sRays} of this.startings) {
for (let sRay of sRays) {
sRay.traverse((seqIdx, seqRef) => {
// consider all possible intersections
// into the endings map
let eRays = this.endings.get(seqRef.ref)
for(let eRay of eRays) {
// ensure that rays are different
if (sRay !== eRay) {
sRay.interact(eRay)
eRay.interact(sRay)
}
}
})
}
}
return counter
}
The rest of the work is just determining in the ray class how two rays could interact (crossing or shading)
Thanks for advice, best regards !
Related
I'm following an exercise's tutorial but I cannot understand the logic.
The initial setup is a 3x3 board made of a 2D array. Each nested array will have 3 elements, and they will be objects with two properties: x and y, defining their position in the array. (x represent the index and y the position of each element in the nested array so it will be 0,1 or 2 since each nested array has 3 elements).
we define an object we will be using: it will be named player and, for now, will have x and y properties set to the following values:
const player = {
x: 1,
y: 1
};
So now the object is placed in the middle of our 3x3 board with position {1,1}
Our next logical step is to start writing some code and to define a function that will allow us, whenever called, depending on the given command (go left or go right) to change the position of the player on the board. To be able to affect the player’s properties and its position, our function has to have access to the player object. Also, we have to set some condition in order not to allow our player to leave the board. And that's the condition I do not understand:
if (thePlayer.y >= 0 && thePlayer.y < 2) {
if (command === 'l') {
thePlayer.y--;
} else {
thePlayer.y++;
}
console.log(`Player has position: x=${thePlayer.x}, y=${thePlayer.y}`);
} else {
console.log("You can't place player outside of the board!");
}
}
Why the first 'if' statement says >=0 and not >0 ?
if the value of y was 0, the player would be on the left side of the board, and then still able to move left because equal to zero and then would go out of the board.
What makes me totally blank is the following sentence:
Now, if our player’s property “y” is lower than 0, we’ll get a notification that the player left the board, and that is not allowed.
The exercise is right but I would like to understand!
This logic is wrong for me also, You will not be able to move 'player' when it will be on Y = 0, which is still correct position. I guess the better and clear solution will be first check the command and then check if command is valid or not, smthg like this:
const moveHandlers = {
'l': () => thePlayer.y--,
'r': () => thePlayer.y++,
}
const validCommands = Object.keys(moveHandlers);
if (!validCommands.includes(command)) {
throw new Error('Invalid command');
}
const invalidLeftMove = command === 'l' && thePlayer.y === 0;
const invalidRightMove = command === 'r' && thePlayer.y === 2;
if (invalidLeftMove || invalidRightMove) {
throw new Error("You can't place player outside of the board!");
}
moveHandlers[command]();
I am creating an endless runner game in which a character on a skateboard is static at (0,0,0) and the scenery moves towards that point and is reset to the "back" when it is off screen.
The aim of the game is to dodge cars coming towards you and collect objects that move slower than the cars towards the character, both gaining you points. I am using THREE.Raycaster to detect collision between the skateboard and the cars/objects (different collision scenarios for the cars & objects) which I have got working well.
The Problem
I am detecting collision between the skateboard and the objects because the skateboard extends further than the character in the -z direction (where the objects are coming from) and so it is first to "hit" an object. The skateboard is a group of objects (Still a THREE.Object3D and not a THREE.Group(), but they function the same) containing for example 4 wheels, two trucks, etc... and so I cannot use the main SkateBoardGroup object to detect collision as it is not a Mesh and does not have vertices, which are what determines where the rays are cast from.
The closest Mesh to the objects is the SkateBoardBaseRight Mesh, which is a child of the SkateBoardBase object (containing 3 Meshes), which itself is a child of the main SkateBoardGroup object. I am currently accessing this Mesh with the following code and raycasting from that Mesh:
objectMesh = SkateBoardGroup.children[0].children[2];
I want to be able to raycast from the top level SkateBoardGroup as there are times in which the skateboard rotates and so the SkateBoardBaseRight mesh is not closest to the object.
What I want
I would appreciate an explanation and/or code example of how I can raycast from the THREE.Object3D SkateBoardGroup rather than having to dive into the children of an object.
Setting the raycaster.intersetObjects to recursive is not what I'm looking for as that is for the objects the skateboard collides with.
Here is my raycasting code:
I am calling this function twice, once for the cars and once for the other objects
originObject is the SkateBoardGroup, collidedObjects is the array containing either the cars or the other objects, and doesCollidedObjectsEndGame is a boolean flag to decide which type of object is it (car or other object)
let cooldown = false; // cooldown reset when try-again clicked
let cooldown2 = false;
function detectCollisionRaycaster(originObject, collidedObjects,
doesCollidedObjectsEndGame) {
let objectMesh = originObject.children[0].children[2];
let originPoint = originObject.position.clone();
if (cooldown === false) {
for (var vertexIndex = 0; vertexIndex < objectMesh.geometry.vertices.length; vertexIndex++) {
var localVertex = objectMesh.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( objectMesh.matrix );
var directionVector = globalVertex.sub( objectMesh.position );
var raycaster = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = raycaster.intersectObjects( collidedObjects, true );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) {
if (doesCollidedObjectsEndGame) {
// car objects collided
gameIsOver = true;
cooldown = true;
} else {
if (cooldown2 === false) {
cooldown2 = true;
// other objects collided
setTimeout(function(){
cooldown2 = false;
}, 100);
}
}
}
}
}
}
I am combining many long arrays (80 arrays of 80,000 elements each, eventually more) into several single arrays (8 arrays of 1,600,000 elements each) to be uploaded as attributes in a Three.js BufferGeometry. I struggled to get the process efficient enough not to freeze the browser. I'm past that point, but it is still painfully slow. Is there any approach that might speed this up - optimizations I should consider? I tried using push.apply, which sped the process considerably, but eventually exceeded the call stack. I'm currently working with concat, but wonder if converting the process to strings or another data structure, and then back again might help? Other ideas? I'm all ears.
Here are the code blocks in question:
var motifMinBufferSize = 80000;
function setMinimumBufferSize( pointCloudAttributeArray, itemSize, fillValue ) {
// buffers cannot be resized once they've been sent to the graphics card, so I am emulating resizing by setting a minimum buffer size that exceeds the number of vertex positions in the largest known point cloud.
supplementalArray.fill( fillValue );
var fullArray = pointCloudAttributeArray.concat( supplementalArray );
return fullArray;
}
function flattenVertexArray( array ) {
var flattenedArray = [];
for ( var i = 0; i < array.length; i++ ) {
flattenedArray.push( array[i].x, array[i].y, array[i].z );
}
return flattenedArray;
}
function concatArrays( gridArray, motifArray ) {
var newGridArray = [];
newGridArray = gridArray.concat( motifArray );
return newGridArray;
}
function compileGridOfPointCloudAttributes( ... ) {
...code to compile the attributes for a BufferGeometry representing a grid of point clouds...
// Skipping ahead in the function:
for ( var i = 0; i < 80; i++ ) {
...
// I have 8 of these attributes that gradually accumulate representing 80,000 values each for 80 different particle clouds:
var position = flattenVertexArray( motif.position );
var aGridPosition = flattenVertexArray ( motif.aGridPosition );
pointCloudGridAttributes.aPointCloudIDPerVertex = concatArrays( pointCloudGridAttributes.aMotifIDPerVertex, setMinimumBufferSize( motif.aPointCloudIDPerVertex, 1, 0 ) );
pointCloudGridAttributes.position = concatArrays( pointCloudGridAttributes.position, setMinimumBufferSize( position, 3, gridDimensions.gridWidth ) );
pointCloudGridAttributes.aGridPosition = concatArrays( pointCloudAttributes.aGridPosition, setMinimumBufferSize( motif.aGridPosition, 1, 0 ) );
...continue like this for 5 more attributes...
}
}
Context:
I'm making a visualization with Three.js composed of 80 or so particle clouds, each with a unique number of points (50,000+ points per cloud) and all composed into a single BufferGeometry for efficient rendering. I periodically swap out on point cloud for another, but learned that the buffers in a buffer geometry are not resizable once they are implemented, so I now have a a fixed, oversized section of the array dedicated to each point cloud.
Playing around with point-free style javascript for fun.
Say I am coding the video game Diablo, and I am modeling enemies using complex nested types like this but deeper and more complicated:
{ name: "badguy1", stats: { health: 10: strength: 42 }, pos: {x: 100, y: 101 } }
So I have a list of all my enemies. I want to do damage to all the enemies within a certain radius
function isInRange(radius, point) { return point.x^2 + point.y^2 >= radius^2; }
function fireDamage(health) { return health - 10; }
var newEnemies = enemies.filter(isInRange).map(fireDamage);
this of course doesn't type check - my combinators take primitives, so i need to map and filter "down another level". I don't want to obscure the filter/map business logic pipeline. I know lenses can help me but lets say I am in a browser, as this is of course trivial with mutable structures. How do I do it?
Is your question is about how to use lenses in Javascript? If so, I may be able to help. Have you checked out the Ramda.js library? It's a terrific way to write functional JS. Let's start by looking at your enemy model:
/* -- data model -- */
let enemyModel = {
name: "badguy1",
stats: {
health: 10,
strength: 42
},
pos: {
x: 100,
y: 101
}
};
Lens: In order to construct a lens you need a getter method and a setter method for your specific object -- in your case the "enemy". Here's how you could construct those by hand.
Method 1: Create your own getters and setters
const getHealth = path(['stats', 'health']);
const setHealth = assocPath(['stats', 'health']);
const healthLens = lens(getHealth, setHealth);
Method 2: Ramda's expedient convenience-lens for Objects
const healthLens = lensPath(['stats', 'health']);
Once you've created the lens, it's time to use it. Ramda offers 3 functions for using lenses: view(..), set(..), and over(..).
view(healthLens)(enemyModel); // 10
set(healthLens, 15)(enemyModel); // changes health from 10 to 15
over(healthLens, fireDamage)(enemyModel); // reduces enemyModel's health property by 10
Since you're applying the fireDamage(..) function to an enemy's health, you'll want to use over(..). Also, since your position coordinates are nested within the enemyModel, you're going to want to use a lens to access those as well. Let's create one and refactor isInRange(..) while we're at it.
As a reference, here's the origin fn:
// NOTE: not sure if this works as you intended it to...
function isInRange(radius, point) {
return point.x^2 + point.y^2 >= radius^2; // maybe try Math.pow(..)
}
Here's a functional approach:
/* -- helper functions -- */
const square = x => x * x;
const gteRadSquared = radius => flip(gte)(square(radius));
let sumPointSquared = point => converge(
add,
[compose(square, prop('x')),
compose(square, prop('y'))]
)(point);
sumPointSquared = curry(sumPointSquared); // allows for "partial application" of fn arguments
/* -- refactored fn -- */
let isInRange = (radius, point) => compose(
gteRadSquared(radius),
sumPointSquared
)(point);
isInRange = curry(isInRange);
Here's what that would look like when dealing with a collection of enemyModels:
/* -- lenses -- */
const xLens = lensPath(['pos', 'x']);
const yLens = lensPath(['pos', 'y']);
const ptLens = lens(prop('pos'), assoc('pos'));
// since idk where 'radius' is coming from I'll hard-code it
let radius = 12;
const filterInRange = rad => filter(
over(ptLens, isInRange(rad)) // using 'ptLens' bc isInRange(..) takes 'radius' and a 'point'
);
const mapFireDamage = map(
over(healthLens, fireDamage) // using 'healthLens' bc fireDamage(..) takes 'health'
);
let newEnemies = compose(
mapFireDamage,
filterInRange(radius)
)(enemies);
I hope this helps illustrate how useful lenses can be. While there are many helper functions, I think the end piece of code is super semantic!
Lastly, I'm just flooding my scope with these functions from Ramda to make this example more readable. I'm using ES6 deconstruction to accomplish this. Here's how:
const {
add,
assocPath,
compose,
converge,
curry,
filter,
flip,
gte,
lens,
lensPath,
map,
over,
set,
path,
prop,
view
} = R;
// code goes below...
Try it out in jsBin! They offer Ramda support.
Read my article on lenses. It answers your question exactly the way you worded it. Seriously, I'm not even joking. Here's a code snippet from my post:
fireBreath :: Point -> StateT Game IO ()
fireBreath target = do
lift $ putStrLn "*rawr*"
units.traversed.(around target 1.0).health -= 3
I'm writing a 2D gravity simulation game and I'm trying to add save/load functionality. In the game I store all of the current planets in an array. Each planet is represented by a Body object which contains the coordinates, mass, and motion vector of the planet. It also stores an array of the last 100 coordinates of the planet in order to draw the planet's trail on the screen.
I want to use JSON.stringify() to serialize the planets array. I'd like to save the first attributes of each planet (mass, location, motion) but I don't need to save the last 100 coordinates (the trail array). I don't want to completely delete the coordinates otherwise the trails will disappear from the screen. Can I stringify only a portion of each object? If not, can I remove that portion of the JSON string after it's been encoded? Or should I move the coordinates elsewhere during the save process then copy them back into each planet once it's been saved?
In modern web browsers you can use Array#map.
var serialized = JSON.stringify(planets.map(function(planet){
return {
mass: planet.mass,
location: planet.location,
motion: planet.motion
};
}));
Or, the equivalent using a for loop.
try it this way
var saved = JSON.stringify( {mass:body.mass,location:body.location,motion:body.motion} );
it shall give you just the three parts as a json string.
A bit more extended you could provide your body class such an export function.
For example:
Bodyclass.export = function( toexport ) {
if ( undefined === toexport || toexport.constructor != Array ) {
var toexport = [ 'mass', 'location', 'motion' ];
}
var export = {};
for ( var i = 0; i < toexport; i++) {
export[ toexport[ i ] ] = this[ toexport[ i ] ];
]
}
var saved = JSON.stringify( body.export() );
The best would be to create both a serialization and deserialization method. This will allow you to create the most efficient storage format while still allowing you to reconstruct as much of the objects as you deem necessary.
You can use export/import, save/restore, serialize/deserialize terminology, whichever you see fit.
Having methods like this will increase you maintainability in the long run as well.
You can use second parameter of JSON.stringify (replacer)
const planet = {
name: "Pluto",
lastCoords: [[0, 0], [1,1,]]
}
const json = JSON.stringify(planet, (key, value) => key === "lastCoords" ? undefined : value)
// json === {"name":"Pluto"}