i am making a virtual Hama beads tool online. (its a kids toy where you put plastic beads on a matrix and iron them to make them stick together and form a solid shape)like this one below (real hama)
So far i managed to make this work (you can try it out there : https://static.club1.fr/alixturcq/hama%20dev/ ) but i would like to make it more elaborate and have the beads on the edges more realistic with round corners where there are no neighbors (virtual hama)
This would mean having a special image for each of theses neighbor cases like this (neighbor_cases)
So each bead is an object, and i have added a "neighbor" parameter to display the correct image.
Every object is automatically placed on the matrix at start but "isOn" is false, so nothing is displayed. When I click on the matrix, I change the 'isOn' boolean to true and the hama bead displays.
The objects are stored in a 1D array called hamas[], but are being displayed as a 2D array, so i can manipulate it as 2D.
class hama {
constructor(isOn, posx, posy, color, neighbor) {
this.posx = posx;
this.posy = posy;
this.color = color;
this.isOn = isOn;
this.neighbor = neighbor;
this.variation = int(random(0,6));
map
}
display() {
if (this.isOn == true) {
if (ironed == true) {
image(hamas_img[this.color][this.neighbor], this.posx, this.posy, CELL_SIZE, CELL_SIZE);
}
else {
image(ironed_hamas_img[this.color][this.neighbor], this.posx, this.posy, CELL_SIZE, CELL_SIZE);
}
}
}
}
Every time I place a bead i need the program to check every objects if they are On, and also what is the 'isOn' state of each of the neighbors in the matrix.
So far I've done this in pseudo-code but I hope there are smarter ways.
// creating an array of neighbor indexes (clockwise around the object)
checkAround[(+1); (width+1); (width); (width-1); (-1); (-width-1); (-width); (-width+1)]
// creating an empty string that is going to be the neighbor code
neighborCode= "";
// check around object to get neighborstate
for (int i=0; i<checkAround.count; i++){
if (hammas[hammaIndex+checkAround[i]].isOn==true){
neighborCode += "O"
}
else {
neighborCode += "X"
}
}
Then I get neighbourCode strings looking like "XOOXOXOO" and map all these code to appropriate neighbour state number and display the correct image.
So this is rather twisted, I was hoping someone could help me find a smarter solution !
And also I have to find a way to avoid checking non existing indexes on the edges of the matrix, but this should not be to hard to find.
Thanks in advance
Al
What you are looking for is edge detection on a pixel array and has been solved many times before. You can solve it yourself and it's fun to figure out yourself.
When a problem exists in 2D space, it often helps to think in 2D.
But your data is structured in 1D (which is fine, and the convention)
Each pixel/bead has a position in the 1D array -let's call that i- and a position in 2D space, defined by x and y.
The relationship between those is like this: i = x + y * w
( Where w is the width of the image)
With this information you can traverse the 1D array through x and y quite easily and decide for each pixel what its neighbours are doing.
example:
for(var x = 0; x < width; x++){
for(var y = 0; y < height; y++){
var i = x + y * width; // id of the current pixel being checked.
// Calculate neighbour ids:
var iNorth = x + (y - 1) * width;
var iSouth = x + (y + 1) * width;
var iEast = x + 1 + y * width;
var iWest = x - 1 + y * width;
var iNorthWest = (x - 1) + (y - 1) * width; // haha Kanye
// etc.. you get the point
// Now encode all those vars in one single byte
// Use that byte to choose the right image for the bead at position I
// Don't forget to deal with your edge cases
}
}
You can optimize later. (eg. You don't have to update the whole image each time you change one pixel, only its neighbors are affected.)
Your "OXOOXOOO" string is a perfectly acceptable way of storing the different states of a bead. A more nerdy approach would be to just store bits in a 8 bit byte.(because each pixel has only 8 neighbours..)
Those bytes can be stored in a buffer with the same exact structure as your actual pixel data.
I think this should get you started without spoiling too much of the fun?
Related
I have a polar graph (see image) with 120 different points. I want to make it so if the user clicks or hovers on one of the points, the coordinate of that point is displayed. I have an array called pointCoordinates that stores each canvas coordinate of each points like this:
[[x1, y1], [x2, y2] ... [x120, y120]]
This is how I am capturing mouse coordinates (which I might later change to click):
document.onmousemove = function(e) {
var x = e.clientX;
var y = e.clientY;
}
I was originally planning to use a formula to check if the mouse is in a certain region (using the distance formula) or simplifying it all into a circle. Either way, this will require me to have 120 different if statements to check for this. I feel like this is inefficient and probably slow. Are there other methods for doing this?
Edit:
To provide more information, these points will NOT be draggable. I am planning to display something like a tooltip near the point that was clicked where the polar coordinates of the point will be shown.
Edit 2:
After using the code posted below and drawing a rectangle in the "clickable" spot on the map, I get this image. I do not want the click detection to be perfect, but this is pretty far off after pi/3. Any ideas how to fix this? I used this code to generate the black spots:
for(var x = 0; x < WIDTH*2/3; x++){
for(var y = 0; y < HEIGHT; y++){
var mp = realToPolar(x, y);//converts canvas x and y into polar
if(checkRadialDistance(mp[0], mp[1])){ //returns true if in bounds
ctx.fillRect(x, y, 1, 1);
}
}
}
Playing around with the constants still generates the same pattern, just of different thicknesses. checkRadialDistance is just the renamed checkr function that inside calls checkrt.
JSBIN Keep in mind, width of screen has to be greater than height for this to work properly.
The image generated by mt-rt. I later made a minor edit, so that whole circle is covered when theta = 0.
EDIT: My (accepted) answer was bad. This corrects it:
This assumes r to be 1 to 5. Convert mouse cartesian mx,my to polar mr,mt. First check if mr is close to 1 of the 5 radii. Function checkr does that. If it is close, then check if mt is close to 1 of the 24 thetas. Function checkt does that. A complication is that the atan2 function is not continuous at pi radians which is where points are at, so make the discontinuity at -pi/24 radians where there are no points.
A "close" value is pi/24 since the arc distance between two adjacent points at r=1 will be pi/12.
var del = 1*Math.PI/24*.7; // for example
function xy2rt(xy) { // to polar cordinates
var rt = [];
rt.push(Math.sqrt(xy[0]*xy[0]+xy[1]*xy[1])); // r
var zatan = Math.atan2(xy[1], xy[0]);
// make the discontinuity at -pi/24
if (zatan < -Math.PI/24) zatan += 2*Math.PI;
rt.push(zatan); // theta
return rt;
}
function checkr() { // check radial distance
for (var pr=1; pr<=5; pr+=1) { // 5 radii
if (Math.abs(mr-pr) < del) { checkt(pr); break; }
}
}
function checkt(pr) { // check theta
var pt;
for (var ipt=0; ipt<24; ipt+=1) { // 24 thetas
pt = ipt / 24 * 2 * Math.PI;
if (Math.abs(mt-pt) < del/pr) {
// is close -- do whatever
break;
}
}
}
My problem was when checking the arc distance, I was using mr and pr whereas only pr should be used. The OP found my error by processing every pixel on the canvas and found there was a problem. I also processed every pixel and this image shows the routines to be correct now. The black is where the routines determine that the pixel is close to one of the 120 points.
EDIT: Faster processing
There are a lot of Math.* functions being executed. Although I haven't timed anything, I think this has to be much faster.
1) The x,y coordintates of the 120 points are stored in arrays.
2) Instead of getting polar mr, mt, pr, and pt, use vector processing.
Here is the derivation of arcd, the arc distance using vectors.
sint = sin(theta) = (M cross P)/mr/pr (cross product Mouse X Point)
cost = cos(theta) = (M dot P)/mr/pr (dot product Mouse . Point)
sint will be used to get arc distance, but sint goes to zero at theta=+-pi as well as theta=0, so:
mdotp will be used to determine if theta is near zero and not +-pi
arcd = pr*theta
arcd = pr*sin(theta) (good approximation for small theta)
arcd = pr*abs(M cross P)/mr/mp (from above)
if ardd < del, check if mdotp > 0.
Here are the load-xy-arrays and the new checkr and checkt routines.
apx=[], apy=[]; // the saved x,y of the 120 points
function loadapxapy() { // load arrays of px, py
var itheta, theta
for (var pr=1; pr<=5; pr+=1) { // 2-dimension arrays
apx[pr] = []; apy[pr] = []; // 5 arrays, 1 for each pr
for (itheta=0; itheta<24; itheta+=1) { // 24 x's and y's
theta = Math.PI*itheta/12;
apx[pr][itheta] = pr*Math.cos(theta);
apy[pr][itheta] = pr*Math.sin(theta);
}
}
}
function checkr() { // check radial distance
var mr = Math.sqrt(mx*mx+my*my); // mouse r
for (var pr=1; pr<=5; pr+=1) { // check 1 to 5
if (Math.abs(mr-pr) < del) { // mouser - pointr
checkt(mr, pr); // if close, check thetas
}
}
}
function checkt(mr, pr) { // check thetas
var px, py, sint, mdotp, arcd;
for (var itheta=0; itheta<24; itheta+=1) { // check 24
px = apx[pr][itheta]; // get saved x
py = apy[pr][itheta]; // and y
// This arcd is derived from vector processing
// At least this doesn't use the accursed "atan"!
sint = Math.abs(mx*py-my*px)/mr/pr; // sine
arcd = pr*sint; // arc distance
if (arcd<del) { // arc distance check
mdotp = (mx*px+my*py); // final check
if (mdotp > 0) { // to see if theta is near zero and not +-pi
setpixelxy([mx, my]); // or whatever..
}
}
}
}
i am making a webgl application i want to know is there any way of making objects(3D models made using blender) inside canvas clickable. So that when i click on them a pop up comes containing data.
I know (and have used) two major approaches.
The first one is to allocate a separate framebuffer and render interactive object to it with different colours. Then, upon a mouse event, you read a pixel corresponding to mouse position and find an object corresponding to the colour just read. For exapmle, it may look somewhat like this.
Textured and shaded scene:
Rendered for hit testing:
This approach is interesting due to it's simplicity. But it has some performance challenges and major ones among them are rendering the scene twice and reading pixel data back (its slow and synchronous). The first one was easy: just keep a dirty flag for the framebuffer and redraw it only upon a event and only if the flag is set (then of course reset it). The second one I've tackled by reading and caching from the framebuffer big chunks of pixels:
getPixel: function (x, y) {
var screenSize = this._screen.getCssSize();
x = x * HIT_TEST_BUFFER_SIZE[0] / screenSize[0] | 0;
y = y * HIT_TEST_BUFFER_SIZE[1] / screenSize[1] | 0;
var rx = x >> PIXEL_CACHE_BUCKET_IDX_SHIFT,
ry = y >> PIXEL_CACHE_BUCKET_IDX_SHIFT,
pixelCache = this._pixelCache,
bucket = pixelCache[[rx, ry]];
if (!bucket) {
this._framebuffer.bind();
bucket = pixelCache[[rx, ry]] = new Uint8Array(
4 * PIXEL_CACHE_BUCKET_SIZE[0] * PIXEL_CACHE_BUCKET_SIZE[1]
);
var gl = this._gl;
gl.readPixels(
rx << PIXEL_CACHE_BUCKET_IDX_SHIFT,
ry << PIXEL_CACHE_BUCKET_IDX_SHIFT,
PIXEL_CACHE_BUCKET_SIZE[0],
PIXEL_CACHE_BUCKET_SIZE[1],
gl.RGBA,
gl.UNSIGNED_BYTE,
bucket
);
this._framebuffer.unbind();
}
var bucketOffset = 4 * (
(y - ry * PIXEL_CACHE_BUCKET_SIZE[1]) * PIXEL_CACHE_BUCKET_SIZE[0] +
x - rx * PIXEL_CACHE_BUCKET_SIZE[0]
);
return bucket.subarray(bucketOffset, bucketOffset + 3);
}
The second major approach would be casting a ray to the scene. You take mouse position, construct a ray with it and cast it from a camera position into a scene to find which object it intersects with. That object would be the one mouse cursor pointing to. There is actually a decent implementation of that approach in Three.js, you can use it or take it as a reference to implement your own algorithm. The main challenge with that approach would be algorithmic complexity of searching an object the ray intersects with. It can be tackled with spacial indices built upon you scene.
Canvas is a simple graphics API. It draws pixels very well and nothing more. There are ways to 'fake' event handlers via mouse positions, but that takes more work. Basically you will register the location of mouse click, than match that up with the position of your 3D models to see if you have a match. You will not be able to attach event handlers directly to the 3d blender objects in canvas. In Scalable Vector Graphics (SVG) that would work fine. Just not in canvas.
function handleMouseDown(e) {
// tell the browser we'll handle this event
e.preventDefault();
e.stopPropagation();
// save the mouse position
// in case this becomes a drag operation
lastX = parseInt(e.clientX - offsetX);
lastY = parseInt(e.clientY - offsetY);
// hit all existing FrameControlPt of your blender objects
var hit = -1;
for (var i = 0; i < FrameControlPt.length; i++) {
var location = FrameControlPt[i];
var dx = lastX - location.x;
var dy = lastY - location.y;
if (dx * dx + dy * dy < stdRadius * stdRadius) {
hit = i;
}
}
// hit all existing buttons in the canvas
for (var i = 0; i < btn.length; i++) {
if ((lastX < (btn[i].x + btn[i].width)) &&
(lastX > btn[i].x) &&
(lastY < (btn[i].y + btn[i].height)) &&
(lastY > btn[i].y)) {
console.log("Button #" + (i + 1) + " has been clicked!!");
// execute button function
btn[i].action(); // execute a custom function
}
}
// if hit then set the isDown flag to start a drag
if (hit < 0) {
drawAll();
} else {
draggingCircle = FrameControlPt[hit];
isDown = true;
}
}
Obviously you'd have to handleMouseUp(event).. in this example, I was allowing the users to drag and drop elements within the canvas. You'd have to adjust your events to match your intended usage.
Code extract from this sample.
I'm building a turn based HTML game based on a 2D square grid. Each grid square could take a variable number of movement points to cross (IE: 1 MP for roads, 1.5 MP for grasslands, 2 MP for forests, etc). When the user clicks on a unit I want to determine all possible movable spaces with said unit's allotted movement points so that I can highlight them and make them clickable.
Is there a free library available to do this? I've seen a few pathing algorithms but nothing about determining movable area. How do other game developers handle this problem? I'm open to both vanilla JS and JQuery solutions.
Well, I decided to try and attack this myself. I've never been great at these sorts of algorithms so I'm sure there's a more efficient way to handle it than what I've done. However, for my purposes it runs quickly enough and is very simple and easy to understand.
In case it's helpful to anyone else looking to do the same, I've included the code below. This is an updated version of my original answer, which I modified to also store the path taken so that you can show the units moving through the correct spaces. This answer uses JQuery in the lower examples, but only in a few places; you can easily enough replace them with vanilla JS. And the first block of code, containing the actual path/area finding functionality, is pure JS.
<script>
var possibleMovementAreaArray = new Array(); // This array will hold our allowable movement tiles. Your other functions can access this after running possibleMovementArea().
function possibleMovementArea(unitIndex) {
// I'm storing each unit in my game in an array. So I pass in the index of the unit I want to determine the movement area for.
var x = unitList[unitIndex][10]; // x coordinate on the playgrid
var y = unitList[unitIndex][11]; // y coordinate on the playgrid
var mp = unitList[unitIndex][15]; // number of movement points
possibleMovementAreaArray.length = 0; // Clear our array so previous runs don't interfere.
findPossibleMovement(x, y, mp);
}
function findPossibleMovement(x, y, mp, prevStepX, prevStepY) {
// This is a recursive function; something I'm not normally too good at.
for (var d=1; d<=4; d++) {
// We run through each of the four cardinal directions. Bump this to 8 and add 4 more cases to include corners.
if (d == 1) {
// Check Up
var newX = x;
var newY = y - 1;
} else if (d == 2) {
// Check Down
var newX = x;
var newY = y + 1;
} else if (d == 3) {
// Check Left
var newX = x - 1;
var newY = y;
} else if (d == 4) {
// Check Right
var newX = x + 1;
var newY = y;
}
// Check to see if this square is occupied by another unit. Two units cannot occupy the same space.
spaceOccupied = false;
for (var j=1; j<=numUnits; j++) {
if (unitList[j][10] == newX && unitList[j][11] == newY)
spaceOccupied = true;
}
if (!spaceOccupied) {
// Modify this for loop as needed for your usage. I have a 2D array called mainMap that holds the ID of a type of terrain for each tile.
// I then have an array called terList that holds all the details for each type of terrain, such as movement points needed to get past.
// This for loop is just looking up the ID of the terrain for use later. Sort of like a "SELECT * FROM terrainInfo WHERE ID=terrainOfCurrentTile".
for (var j=1; j<=numTerrains; j++) {
if (newX > 0 && newX <= mapWidth && newY > 0 && newY <= mapHeight && terList[j][1] == mainMap[newX][newY])
break; // After finding the index of terList break out of the loop so j represents the correct index.
}
if (j <= numTerrains) { // Run if an actual terrain is found. No terrain is found if the search runs off the sides of the map.
var newMp = mp - terList[j][7]; // Decrement the movement points for this particular path.
if (newMp >= 0) { // Only continue if there were enough movement points to move to this square.
// Check to see if this square is already logged. For both efficiency and simplicity we only want each square logged once.
var newIndex = possibleMovementAreaArray.length
var alreadyLogged = false
if (possibleMovementAreaArray.length > 0) {
for (var j=0; j<possibleMovementAreaArray.length; j++) {
if (possibleMovementAreaArray[j][1] == newX && possibleMovementAreaArray[j][2] == newY) {
alreadyLogged = true;
var alreadyLoggedIndex = j;
}
}
}
if (!alreadyLogged) {
// This adds a row to the array and records the x and y coordinates of this tile as movable
possibleMovementAreaArray[newIndex] = new Array(6);
possibleMovementAreaArray[newIndex][1] = newX;
possibleMovementAreaArray[newIndex][2] = newY;
possibleMovementAreaArray[newIndex][3] = prevStepX; // This tracks the x coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][4] = prevStepY; // This tracks the y coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][5] = newMp; // Records remaining MP after the previous steps have been taken.
}
if (alreadyLogged && newMp > possibleMovementAreaArray[alreadyLoggedIndex][5]) {
// If this tile was already logged, but there was less MP remaining on that attempt, then this one is more efficient. Update the old path with this one.
possibleMovementAreaArray[alreadyLoggedIndex][3] = prevStepX;
possibleMovementAreaArray[alreadyLoggedIndex][4] = prevStepY;
possibleMovementAreaArray[alreadyLoggedIndex][5] = newMp;
}
if (newMp > 0) {
// Now update the list of previous steps to include this tile. This list will be passed along to the next call of this function, thus building a path.
if (prevStepX == '') {
var newPrevStepX = [newX];
var newPrevStepY = [newY];
} else {
// This code is required to make a full copy of the array holding the existing list of steps. If you use a simple equals then you just create a reference and
// subsequent calls are all updating the same array which creates a chaotic mess. This way we store a separate array for each possible path.
var newPrevStepX = prevStepX.slice();
newPrevStepX.push(newX);
var newPrevStepY = prevStepY.slice();
newPrevStepY.push(newY);
}
// If there are still movement points remaining, check and see where we could move next.
findPossibleMovement(newX, newY, newMp, newPrevStepX, newPrevStepY);
}
}
}
}
}
}
</script>
After running the above, you can then loop through the array to find all usable tiles. Here is how I did it:
<script>
// Shows the movement area based on the currently selected unit.
function showMovement() {
var newHTML = "";
curAction = "move";
possibleMovementArea(curUnit); // See above code
for (x=0; x<possibleMovementAreaArray.length; x++) {
// Loop over the array and do something with each tile. In this case I'm creating an overlay that I'll fade in and out.
var tileLeft = (possibleMovementAreaArray[x][1] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
var tileTop = (possibleMovementAreaArray[x][2] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
newHTML = newHTML + "<img id='path_" + possibleMovementAreaArray[x][1] + "_" + possibleMovementAreaArray[x][2] + "' onClick='mapClk(" + possibleMovementAreaArray[x][1] + ", " + possibleMovementAreaArray[x][2] + ", 0);' src='imgs/path.png' class='mapTile' style='left:" + tileLeft + "px; top:" + tileTop + "px;'>";
}
$("#movementDiv").html(newHTML); // Add all those images into a preexisting div.
$("#movementDiv").css("opacity", "0.5"); // Fade the div to 50%
$("#movementDiv").show(); // Make the div visible.
startFading(); // Run a routine to fade the div in and out.
}
</script>
Since we determined the path, we can easily show movement as well by looping through the stored information:
<script>
for (j=0; j<possibleMovementAreaArray[areaIndex][3].length; j++) {
// This loop moves the unit img to each tile on its way to its destination. The final destination tile is not included.
var animSpeed = 150; // Time in ms that it takes to move each square.
var animEase = "linear"; // We want movement to remain a constant speed through each square in this case.
var targetLeft = (possibleMovementAreaArray[areaIndex][3][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new horizonal position.
var targetTop = (possibleMovementAreaArray[areaIndex][4][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new vertical position.
$("#char_"+curUnit).animate({"left":targetLeft, "top":targetTop}, animSpeed, animEase); // Do the animation. Subsequent animations get queued.
}
// Now we need to move to that last tile.
newLeft = (x-1) * mapTileSize;
newTop = (y-1) * mapTileSize;
$("#char_"+curUnit).animate({"left":newLeft, "top":newTop}, 400, "easeOutCubic"); // Slow unit at the end of journey for aesthetic purposes.
$("#char_"+curUnit).addClass("unitMoved", 250); // Turns the image grayscale so it can easily be seen that it has already moved.
</script>
Hopefully this is helpful to others.
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
Did you ever played the "Tank wars" game?
I'm programming this game with JavaScript + Canvas (for a personal challenge), and what I need is an algorithm for generating that random green land every time I start the game, but I'm not too good at maths, so I can't do it myself.
I don't want someone to give me the code, I only want the idea for the algorithm.
Thanks!
(9 segments)
Fiddle demo
(7 segments)
The main generation function look like this:
var numOfSegments = 9; // split horizontal space
var segment = canvas.width / numOfSegments; // calc width of each segment
var points = [], calcedPoints;
var variations = 0.22; // adjust this: lower = less variations
var i;
//produce some random heights across the canvas
for(i=0; i < numOfSegments + 1; i++) {
points.push(segment * i);
points.push(canvas.height / 2.8 + canvas.height * variations * Math.random());
}
//render the landscape
ctx.beginPath();
ctx.moveTo(canvas.width, canvas.height);
ctx.lineTo(0, canvas.height);
calcedPoints = ctx.curve(points); // see below
ctx.closePath();
ctx.fillStyle = 'green';
ctx.fill();
The curve() function is a separate function which generate a cardinal spline. In here you can modify it to also store tension values to make more spikes. You can also used the generated points as a basis for where and at what angle the tanks will move at.
The function for cardinal spline:
CanvasRenderingContext2D.prototype.curve = function(pts, tension, numOfSegments) {
tension = (tension != 'undefined') ? tension : 0.5;
numOfSegments = numOfSegments ? numOfSegments : 16;
var _pts = [], res = [], t, i, l, r = 0,
x, y, t1x, t2x, t1y, t2y,
c1, c2, c3, c4, st, st2, st3, st23, st32;
_pts = pts.concat();
_pts.unshift(pts[1]);
_pts.unshift(pts[0]);
_pts.push(pts[pts.length - 2]);
_pts.push(pts[pts.length - 1]);
l = (_pts.length - 4);
for (i = 2; i < l; i+=2) {
//overrides and modifies tension for each segment.
tension = 1 * Math.random() - 0.3;
for (t = 0; t <= numOfSegments; t++) {
t1x = (_pts[i+2] - _pts[i-2]) * tension;
t2x = (_pts[i+4] - _pts[i]) * tension;
t1y = (_pts[i+3] - _pts[i-1]) * tension;
t2y = (_pts[i+5] - _pts[i+1]) * tension;
st = t / numOfSegments;
st2 = st * st;
st3 = st2 * st;
st23 = st3 * 2;
st32 = st2 * 3;
c1 = st23 - st32 + 1;
c2 = -(st23) + st32;
c3 = st3 - 2 * st2 + st;
c4 = st3 - st2;
x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;
res[r++] = x;
res[r++] = y;
} //for t
} //for i
l = res.length;
for(i=0;i<l;i+=2) this.lineTo(res[i], res[i+1]);
return res; //return calculated points
}
Look into perlin noise generation, this in combination with a good smoothing algorithm can produce some pretty good terrain, and is fairly quick. There is a reference version of the code kicking around the net somewhere, which should provide you with a fairly hefty headstart
First you need a point that is random y (between 55,65); got x=0
So this is the origin point for the green, lets keep it as x1,y1 (x1 always 0).
Then you need a random integer between 30 to 40. This is x2. And a random y which is in the range y1 + 8 to y1 + 20.
Then x3 and y3 on same principle (lets call it formula type 1)
Now you need to first get a random either -1 or 1, this will be directions of y4. So y4 can go higher than y3 or lower ... this will be formula type 2.
You need to keep a max and min y for a new y, if it crosses that then go the other way -> this will be a correction type formula 3.
Xn keeps increasing till its >= width of board.
Join the lines in a eclipses ... and looks like web searches is the way to go !
I am sure there are a lot of coded libraries that you could use to make this easy. But if you are trying to code this by yourself, here is my idea.
You need to define terrain from everything else. So every part of your environment is a cluster for example. You need to define how are separated these clusters, by nodes(points) for example.
You can create a polygon from a sequence of points, and this polygon can become whatever you want, in this case terrain.
See that on the image you passed, there are peaks, those are the nodes (points). Remember to define also nodes on the borders of your environment.
There are surely a novel, written algorithms, either fractal as #DesertIvy pointed out or others, maybe there are libraries as well, but if you want toi generate what is in the image, it can be pretty straightforward, since it is just (slightly curved) lines between points. If you do it in phases, not trying to be correct at once, it is easy:
Split x region of your game screen into sections (with some minimal and maximal width) using random (you may be slightly off in last section, but it does not matter as much, I think). Remember the x-es where sections meet (including the ones at game screen border)
Prepare some data structure to include y-s as well, on previously remembered x-s. Start with leftmost.y = 0, slope = Math.random()-0.5;.
Generate each next undefined y beginning with 1: right.y = left.y + slope * (right.x-left.x); as well as update slope after each y: slope += Math.random()-0.5;. Do not bother, for the moment, if it all fits into game screen.
If you want arcs, you can generate "curviness" parameter for each section randomly which represent how much the middle of the line is bumped compared to straight lines.
Fit the ys into the game screen: first find maximal and minimal generated y (mingeny, maxgeny) (you can track this while generating in point 4). Choose where the max and min y in game screen (minscry, maxscry) (say at the top fourth and at the bottom fourth). Then transform generated ys so that it spans between minscry and maxscry: for every point, do apoint.y = minscry + (maxscry-minscry)/(maxgeny-mingeny)*(apoint.y-mingeny).
Now use lines between [x,y] points as a terrain, if you want to use "curviness", than add curvemodifier to y for any particular x in a section between leftx and rightx. The arc need not to be a circle: I would suggest a parabola or cosine which are easy to produce: var middle = (left.x+right.x)/2; var excess = (x-left)/(middle-left); and then either var curvemodifier = curviness * (1-excess*excess); or var curvemodifier = curviness * Math.cos(Math.PI/2*excess).
Wow...At one point I was totally addicted to tank wars.
Since you are on a learning adventure...
You might also learn about the context.globalCompositeOperation.
This canvas operation will let you grab an image of actual grass and composite it into your game.
You can randomize the grass appearance by changing the x/y of your drawImage();
Yes, the actual grass would probably be too distracting to include in your finished game, but learning about compositing would be valuable knowledge to have.
...and +1 for the question: Good for you in challenging yourself !
I am creating a new "whack-a-mole" style game where the children have to hit the correct numbers in accordance to the question. So far it is going really well, I have a timer, count the right and wrong answers and when the game is started I have a number of divs called "characters" that appear in the container randomly at set times.
The problem I am having is that because it is completely random, sometimes the "characters" appear overlapped with one another. Is there a way to organize them so that they appear in set places in the container and don't overlap when they appear.
Here I have the code that maps the divs to the container..
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function scramble() {
var children = $('#container').children();
var randomId = randomFromTo(1, children.length);
moveRandom('char' + randomId);
}
function moveRandom(id) {
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var pad = parseInt($('#container').css('padding-top').replace('px', ''));
var bHeight = $('#' + id).height();
var bWidth = $('#' + id).width();
maxY = cPos.top + cHeight - bHeight - pad;
maxX = cPos.left + cWidth - bWidth - pad;
minY = cPos.top + pad;
minX = cPos.left + pad;
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX);
$('#' + id).css({
top: newY,
left: newX
}).fadeIn(100, function () {
setTimeout(function () {
$('#' + id).fadeOut(100);
window.cont++;
}, 1000);
});
I have a fiddle if it helps.. http://jsfiddle.net/pUwKb/8/
As #aug suggests, you should know where you cannot place things at draw-time, and only place them at valid positions. The easiest way to do this is to keep currently-occupied positions handy to check them against proposed locations.
I suggest something like
// locations of current divs; elements like {x: 10, y: 40}
var boxes = [];
// p point; b box top-left corner; w and h width and height
function inside(p, w, h, b) {
return (p.x >= b.x) && (p.y >= b.y) && (p.x < b.x + w) && (p.y < b.y + h);
}
// a and b box top-left corners; w and h width and height; m is margin
function overlaps(a, b, w, h, m) {
var corners = [a, {x:a.x+w, y:a.y}, {x:a.x, y:a.y+h}, {x:a.x+w, y:a.y+h}];
var bWithMargins = {x:b.x-m, y:b.y-m};
for (var i=0; i<corners.length; i++) {
if (inside(corners[i], bWithMargins, w+2*m, h+2*m) return true;
}
return false;
}
// when placing a new piece
var box;
while (box === undefined) {
box = createRandomPosition(); // returns something like {x: 15, y: 92}
for (var i=0; i<boxes.length; i++) {
if (overlaps(box, boxes[i], boxwidth, boxheight, margin)) {
box = undefined;
break;
}
}
}
boxes.push(box);
Warning: untested code, beware the typos.
The basic idea you will have to implement is that when a random coordinate is chosen, theoretically you SHOULD know the boundaries of what is not permissible and your program should know not to choose those places (whether you find an algorithm or way of simply disregarding those ranges or your program constantly checks to make sure that the number chosen isn't within the boundary is up to you. the latter is easier to implement but is a bad way of going about it simply because you are entirely relying on chance).
Let's say for example coordinate 50, 70 is selected. If the picture is 50x50 in size, the range of what is allowed would exclude not only the dimensions of the picture, but also 50px in all directions of the picture so that no overlap may occur.
Hope this helps. If I have time, I might try to code an example but I hope this answers the conceptual aspect of the question if that is what you were having trouble with.
Oh and btw forgot to say really great job on this program. It looks awesome :)
You can approach this problem in at least two ways (these two are popped up in my head).
How about to create a 2 dimensional grid segmentation based on the number of questions, the sizes of the question panel and an array holding the position of each question coordinates and then on each time frame to position randomly these panels on one of the allowed coordinates.
Note: read this article for further information: http://eloquentjavascript.net/chapter8.html
The second approach follow the same principle, but this time to check if the panel overlap the existing panel before you place it on the canvas.
var _grids;
var GRID_SIZE = 20 //a constant holding the panel size;
function createGrids() {
_grids = new Array();
for (var i = 0; i< stage.stageWidth / GRID_SIZE; i++) {
_grids[i] = new Array();
for (var j = 0; j< stage.stageHeight / GRID_SIZE; j++) {
_grids[i][j] = new Array();
}
}
}
Then on a separate function to create the collision check. I've created a gist for collision check in Actionscript, but you can use the same principle in Javascript too. I've created this gist for inspirational purposes.
Just use a random number which is based on the width of your board and then modulo with the height...
You get a cell which is where you can put the mole.
For the positions the x and y should never change as you have 9 spots lets say where the mole could pop up.
x x x
x x x
x x x
Each cell would be sized based on % rather then pixels and would allow re sizing the screen
1%3 = 1 (x)
3%3 = 0 (y)
Then no overlap is possible.
Once the mole is positioned it can be show or hidden or moved etc based on some extended logic if required.
If want to keep things your way and you just need a quick re-position algorithm... just set the NE to the SW if the X + width >= x of the character you want to check by setting the x = y+height of the item which overlaps. You could also enforce that logic in the drawing routine by caching the last x and ensuring the random number was not < last + width of the item.
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX); if(newX > lastX + characterWidth){ /*needful*/}
There could still however be overlap...
If you wanted to totally eliminate it you would need to keep track of state such as where each x was and then iterate that list to find a new position or position them first and then all them to move about randomly without intersecting which would would be able to control with just padding from that point.
Overall I think it would be easier to just keep X starting at 0 and then and then increment until you are at a X + character width > greater then the width of the board. Then just increase Y by character height and Set X = 0 or character width or some other offset.
newX = 0; newX += characterWidth; if(newX + chracterWidth > boardWidth) newX=0; newY+= characterHeight;
That results in no overlap and having nothing to iterate or keep track of additional to what you do now, the only downside is the pattern of the displayed characters being 'checker board style' or right next to each other (with possible random spacing in between horizontal and vertical placement e.g. you could adjust the padding randomly if you wanted too)
It's the whole random thing in the first place that adds the complexity.
AND I updated your fiddle to prove I eliminated the random and stopped the overlap :)
http://jsfiddle.net/pUwKb/51/