Customize chances of picking element randomly - javascript

I have two defined objects: x and y
If I do following, chances of getting either x or y are equal – 1 of 2:
var primary = [x, y];
var secondary = primary[Math.floor(Math.random() * primary.length)];
This would take a 1 of 3 (smaller) chances of getting y:
var primary = [x, x, y];
// secondary unchanged
etc.
But I believe, this is bad practice because if I'd wanted to set infinitesimal chances (e.g. 1 of 1e9) of getting y, I would have to do something extremely wasteful like this:
var primary = new Array();
for (i = 1e9 - 1; i--; i) primary.push(x);
primary.push(y);
var secondary = primary[Math.floor(Math.random() * primary.length)];
Is there a better way to do this in JavaScript?

Without digging too much into ECMAScript specification and its actual implementations, Math.random() appears to produce a number from the range 0..1 in a smooth fashion. This means that the number is 50% likely to be less than 0.5, 25% likely to be less than 0.25, 10% likely to be less than 0.1, etc.
To get x 17% of the time (and, conversely, y 83% of the time), one could use the corresponding number to be a gateway for Math.random()’s results:
const x = "x";
const y = "y";
function getRandom() {
return Math.random() < 0.17 ? x : y;
}
const value = getRandom();
// 17% "x", 83% "y"
This works fine for two values, but working with lists of 3+ elements would require different thinking.

Related

How to represent this vector algebra for 2d point calculation using comma notation in a typical programming language?

I reached out for help recently on math.stackexchange.com with a question about 2 dimensional algebra. The answer was promptly provided but it's in mathematical notation unfamiliar to me and the person giving the answer has stopped responding to my questions. While I am extremely grateful to BStar for providing this information, he/she has stopped replying both on the site and the chat, and doesn't seem interested in helping me understand it to the point that I could write programming code to calculate the desired point P. I respect that, but it leaves me stuck for now. Could someone help me convert this sequence of steps into a programming language such as Javascript? (I am actually working in PHP, but Javascript would be more convenient to represent in a runnable Snippet on stackoverflow .. I'm happy with any current language that I can translate into PHP).
The post is at https://math.stackexchange.com/questions/4110517/trig-101-calculate-coords-of-point-p-such-that-it-is-distance-n-from-line-ab-an/4110550?noredirect=1#comment8504010_4110550
The answer given is in Latex but here's a screenshot of it:
The latest description of the process by the author BStar: "Here is the process: First calculate cos B and use arccos to get B. Second calculate tanθ to get θ with arctan by using |BP| is the same from two triangles. Knowing these, we can get vectors BA’ and B’P, thus vectors OA and OP. We get θ to grt vector BA’ in this case, not the other way around. "
I can follow up until step (5) where the comma notation comes in, i.e. k = (-xb, -yb)(xc - xb, yc - yb) / ac. This seems to make k a two dimensional vector but I don't think I ever worked with this notation. Later, k is used in step (6) and (6a) to calculate theta, appearing both in the numerator and denominator of a fraction. I have no idea how to expand this to get an actual value for theta.
(Edit Note: The author BStar assumed point A is at the origin, so (xa, ya) = (0, 0) but I cannot make that assumption in the real world. Thus the vector BA in Step 1 is actually (xa - xb, ya - yb) and his formula for k shown above is actually k = (xa - xb, ya - yb)(xc - xb, yc - yb) / ac. This expansion needs to be carried through the calculation but it's not a major change.)
If we were to frame this in Javascript, I could lay out a framework of what is known at the beginning of the calculation. It's not productive to represent every single step of the mathematical proof given by BStar, but I'm not sure exactly what steps can be left as processes in the mathematical proof and what steps need expounding in code.
/* Known points - A, B, C */
var xa = 10, ya = 10;
var xb = 100, yb = 500;
var xc = 700, yc = 400;
/* Known lengths m and n (distance perpendicularly from AB and AC) */
var m = 30;
var n = 50;
/* Point we want to calculate, P */
var px = 0, py = 0;
/* Calculation goes here - some Javascript notes:
* var a = Math.sin(angInRadians);
* var b = Math.asin(opposite / hypotenuse);
* var c = Math.pow(number, 2); // square a number
* var d = Math.sqrt(number);
*/
/* Print the result */
console.log('Result: P (' + px + ', ' + py + ')');
How would one express the maths from the diagram in the programming snippet above?
I think I can get you to the angle of B but I'm not very good with math and get lost with all those variables. If you are stuck at figuring out the angle try this and see if it does what you want. It seems to do what step 5 is asking but double check my work.
let pointA = {x: 100, y: 0};
let pointB = {x: 20, y: 20};
let pointC = {x: 0, y: 100};
let distBA_x = pointB.x - pointA.x;
let distBA_y = pointB.y - pointA.y;
//let BA_a = Math.sqrt(distBA_x*distBA_x + distBA_y*distBA_y);
let distBC_x = pointB.x - pointC.x;
let distBC_y = pointB.y - pointC.y;
//let BC_c = Math.sqrt(distBC_x*distBC_x + distBC_y*distBC_y);
var angle = Math.atan2(distBA_x * distBC_y - distBA_y * distBC_x, distBA_x * distBC_x + distBA_y * distBC_y);
if(angle < 0) {angle = angle * -1;}
var degree_angle = angle * (180 / Math.PI);
console.log(degree_angle)
I've laid it out on a canvas so you can see it visually and change the parameters. Hope it helps. Here's the Codepen https://codepen.io/jfirestorm44/pen/RwKdpRw
BA • BC is a "dot product" between two vectors. The result is a single number: It's the sum of the products of vector components. If the vectors are (x1,y1) and (x2,y2) the dot product is x1x2+y1y2.
Assuming you don't have a library for vector calculations and don't want to create one, the code for computing k would be:
k = (-xb*(xc - xb)-yb*(yc - yb)) / ac

Getting two points on the edges of a rectangle

I have a rectangle and would like to:
Get a random point on one (any) of the sides.
Get a random point on one (except for the previously picked) side.
My initial approach is to create arrays for each possible side.
var arr:Array = [[{x:0,y:0}, // Top
{x:width,y:0}], //
[{x:width,y:0}, // Right
{x:width,y:height}], //
[{x:width,y:height}, // Bottom
{x:0,y:height}], //
[{x:0,y:height}, // Left
{x:0,y:0}]]; //
Then, I get the sides.
rand is an instance of Rand and has the methods:
.next() which provides a random number between 0 and 1
.between(x,y) which returns a random number between x and y.
var firstSide:Array = arr[rand.next() * arr.length];
var secondSide:Array;
do {
secondSide = arr[rand.next() * arr.length];
} while(secondSide.equals(firstSide));
Finally, I calculate my points.
var pointOnFirstSide:Object = {x:rand.between(firstSide[0].x, firstSide[1].x),
y:rand.between(firstSide[0].y, firstSide[1].y};
var pointOnSecondSide:Object = {x:rand.between(secondSide[0].x, secondSide[1].x),
y:rand.between(secondSide[0].y, secondSide[1].y};
I don't think this is the most efficient way to solve this.
How would you do it?
Assuming we have the following interfaces and types:
interface Rand {
next(): number;
between(x: number, y: number): number;
}
interface Point {
x: number;
y: number;
}
type PointPair = readonly [Point, Point];
and taking you at your word in the comment that the procedure is: first randomly pick two sides, and then pick random points on those sides... first let's see what's involved in picking two sides at random:
const s1 = Math.floor(rand.between(0, arr.length));
const s2 = (Math.floor(rand.between(1, arr.length)) + s1) % arr.length;
s1 and s2 represent the indices of arr that we are choosing. The first one chooses a whole number between 0 and one less than the length of the array. We do this by picking a real number (okay, floating point number, whatever) between 0 and the length of the array, and then taking the floor of that real number. Since the length is 4, what we are doing is picking a real number uniformly between 0 and 4. One quarter of those numbers are between 0 and 1, another quarter between 1 and 2, another quarter between 2 and 3, and the last quarter are between 3 and 4. That means you have a 25% chance of choosing each of 0, 1, 2 and 3. (The chance of choosing 4 is essentially 0, or perhaps exactly 0 if rand is implemented in the normal way which excludes the upper bound).
For s2 we now pick a number uniformly between 1 and the length of the array. In this case, we are picking 1, 2, or 3 with a 33% chance each. We add that number to s1 and then take the remainder when dividing by 4. Think of what we are doing as starting on the first side s1, and then moving either 1, 2, or 3 sides (say) clockwise to pick the next side. This completely eliminates the possibility of choosing the same side twice.
Now let's see what's involved in randomly picking a point on a line segment (which can be defined as a PointPair, corresponding to the two ends p1 and p2 of the line segment) given a Rand instance:
function randomPointOnSide([p1, p2]: PointPair, rand: Rand): Point {
const frac = rand.next(); // between 0 and 1
return { x: (p2.x - p1.x) * frac + p1.x, y: (p2.y - p1.y) * frac + p1.y };
}
Here what we do is pick a single random number frac, representing how far along the way from p1 to p2 we want to go. If frac is 0, we pick p1. If frac is 1, we pick p2. If frac is 0.5, we pick halfway between p1 and p2. The general formula for this is a linear interpolation between p1 and p2 given frac.
Hopefully between the two of those, you can implement the algorithm you're looking for. Good luck!
Link to code
jcalz already gave an excellent answer. Here is an alternate version for the variant I asked about in the comments: When you want your points uniformly chosen over two sides of the perimeter, so that if your w : h ratio was 4 : 1, the first point is four times as likely to lie on a horizontal side as a vertical one. (This means that the chance of hitting two opposite long sides is 24/45; two opposite short side, 1/45; and one of each, 20/45 -- by a simple but slightly tedious calculation.)
const rand = {
next: () => Math. random (),
between: (lo, hi) => lo + (hi - lo) * Math .random (),
}
const vertices = (w, h) => [ {x: 0, y: h}, {x: w, y: h}, {x: w, y: 0}, {x: 0, y: 0} ]
const edges = ([v1, v2, v3, v4]) => [ [v1, v2], [v2, v3], [v3, v4], [v4, v1] ]
const randomPoint = ([v1, v2], rand) => ({
x: v1 .x + rand .next () * (v2 .x - v1 .x),
y: v1 .y + rand .next () * (v2 .y - v1 .y),
})
const getIndex = (w, h, x) => x < w ? 0 : x < w + h ? 1 : x < w + h + w ? 2 : 3
const twoPoints = (w, h, rand) => {
const es = edges (vertices (w, h) )
const perimeter = 2 * w + 2 * h
const r1 = rand .between (0, perimeter)
const idx1 = getIndex (w, h, r1)
const r2 = (
rand. between (0, perimeter - (idx1 % 2 == 0 ? w : h)) +
Math .ceil ((idx1 + 1) / 2) * w + Math .floor ((idx1 + 1) / 2) * h
) % perimeter
const idx2 = getIndex (w, h, r2)
return {p1: randomPoint (es [idx1], rand), p2: randomPoint (es [idx2], rand)}
}
console .log (
// Ten random pairs on rectangle with width 5 and height 2
Array (10) .fill () .map (() => twoPoints (5, 2, rand))
)
The only complicated bit in there is the calculation of r2. We calculate a random number between 0 and the total length of the remaining three sides, by adding all four sides together and subtracting off the length of the current side, width if idx is even, height if it's odd. Then we add it to the total length of the sides up to and including the index (where the ceil and floor calls simply count the number of horizontal and vertical sides, these values multiplied by the width and height, respectively, and added together) and finally take a floating-point modulus of the result with the perimeter. This is the same technique as in jcalz's answer, but made more complex by dealing with side lengths rather than simple counts.
I didn't make rand an instance of any class or interface, and in fact didn't do any Typescript here, but you can add that yourself easily enough.

Smoothing out values of an array

If I had an array of numbers such as [3, 5, 0, 8, 4, 2, 6], is there a way to “smooth out” the values so they’re closer to each other and display less variance?
I’ve looked into windowing the data using something called the Gaussian function for a 1-dimensional case, which is my array, but am having trouble implementing it. This thread seems to solve exactly what I need but I don’t understand how user naschilling (second post) came up with the Gaussian matrix values.
Context: I’m working on a music waveform generator (borrowing from SoundCloud’s design) that maps the amplitude of the song at time t to a corresponding bar height. Unfortunately there’s a lot of noise, and it looks particularly ugly when the program maps a tiny amplitude which results in a sudden decrease in height. I basically want to smooth out the bar heights so they aren’t so varied.
The language I'm using is Javascript.
EDIT: Sorry, let me be more specific about "smoothing out" the values. According to the thread linked above, a user took an array
[10.00, 13.00, 7.00, 11.00, 12.00, 9.00, 6.00, 5.00]
and used a Gaussian function to map it to
[ 8.35, 9.35, 8.59, 8.98, 9.63, 7.94, 5.78, 7.32]
Notice how the numbers are much closer to each other.
EDIT 2: It worked! Thanks to user Awal Garg's algorithm, here are the results:
No smoothing
Some smoothing
Maximum smoothing
EDIT 3: Here's my final code in JS. I tweaked it so that the first and last elements of the array were able to find its neighbors by wrapping around the array, rather than calling itself.
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function smooth(values, alpha) {
var weighted = average(values) * alpha;
var smoothed = [];
for (var i in values) {
var curr = values[i];
var prev = smoothed[i - 1] || values[values.length - 1];
var next = curr || values[0];
var improved = Number(this.average([weighted, prev, curr, next]).toFixed(2));
smoothed.push(improved);
}
return smoothed;
}
function average(data) {
var sum = data.reduce(function(sum, value) {
return sum + value;
}, 0);
var avg = sum / data.length;
return avg;
}
smooth(array, 0.85);
Interesting question!
The algorithm to smooth out the values obviously could vary a lot, but here is my take:
"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function avg (v) {
return v.reduce((a,b) => a+b, 0)/v.length;
}
function smoothOut (vector, variance) {
var t_avg = avg(vector)*variance;
var ret = Array(vector.length);
for (var i = 0; i < vector.length; i++) {
(function () {
var prev = i>0 ? ret[i-1] : vector[i];
var next = i<vector.length ? vector[i] : vector[i-1];
ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
})();
}
return ret;
}
function display (x, y) {
console.clear();
console.assert(x.length === y.length);
x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}
display(array, smoothOut(array, 0.85));
NOTE: It uses some ES6 features like fat-arrow functions and template strings. Firefox 35+ and Chrome 45+ should work fine. Please use the babel repl otherwise.
My method basically computes the average of all the elements in the array in advance, and uses that as a major factor to compute the new value along with the current element value, the one prior to it, and the one after it. I am also using the prior value as the one newly computed and not the one from the original array. Feel free to experiment and modify according to your needs. You can also pass in a "variance" parameter to control the difference between the elements. Lowering it will bring the elements much closer to each other since it decreases the value of the average.
A slight variation to loosen out the smoothing would be this:
"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function avg (v) {
return v.reduce((a,b) => a+b, 0)/v.length;
}
function smoothOut (vector, variance) {
var t_avg = avg(vector)*variance;
var ret = Array(vector.length);
for (var i = 0; i < vector.length; i++) {
(function () {
var prev = i>0 ? ret[i-1] : vector[i];
var next = i<vector.length ? vector[i] : vector[i-1];
ret[i] = avg([t_avg, prev, vector[i], next]);
})();
}
return ret;
}
function display (x, y) {
console.clear();
console.assert(x.length === y.length);
x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}
display(array, smoothOut(array, 0.85));
which doesn't take the averaged value as a major factor.
Feel free to experiment, hope that helps!
The technique you describe sounds like a 1D version of a Gaussian blur. Multiply the values of the 1D Gaussian array times the given window within the array and sum the result. For example
Assuming a Gaussian array {.242, .399, .242}
To calculate the new value at position n of the input array - multiply the values at n-1, n, and n+1 of the input array by those in (1) and sum the result. eg for [3, 5, 0, 8, 4, 2, 6], n = 1:
n1 = 0.242 * 3 + 0.399 * 5 + 0.242 * 0 = 2.721
You can alter the variance of the Gaussian to increase or reduce the affect of the blur.
i stumbled upon this post having the same problem with trying to achieve smooth circular waves from fft averages.
i've tried normalizing, smoothing and wildest math to spread the dynamic of an array of averages between 0 and 1. it is of course possible but the sharp increases in averaged values remain a bother that basically makes these values unfeasable for direct display.
instead i use the fft average to increase amplitude, frequency and wavelength of a separately structured clean sine.
imagine a sine curve across the screen that moves right to left at a given speed(frequency) times the current average and has an amplitude of current average times whatever will then be mapped to 0,1 in order to eventually determine 'the wave's' z.
the function for calculating size, color, shift of elements or whatever visualizes 'the wave' will have to be based on distance from center and some array that holds values for each distance, e.g. a certain number of average values.
that very same array can instead be fed with values from a sine - that is influenced by the fft averages - which themselves thus need no smoothing and can remain unaltered.
the effect is pleasingly clean sine waves appearing to be driven by the 'energy' of the sound.
like this - where 'rings' is an array that a distance function uses to read 'z' values of 'the wave's x,y positions.
const wave = {
y: height / 2,
length: 0.02,
amplitude: 30,
frequency: 0.5
}
//var increment = wave.frequency;
var increment = 0;
function sinewave(length,amplitude,frequency) {
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo(0, height / 2);
for (let i = 0; i < width; i+=cellSize) {
//ctx.lineTo(i, wave.y + Math.sin(i * wave.length + increment) * wave.amplitude)
ctx.lineTo(i, wave.y + Math.sin(i * length + increment) * amplitude);
rings.push( map( Math.sin(i * length + increment) * amplitude,0,20,0.1,1) );
rings.shift();
}
ctx.stroke();
increment += frequency;
}
the function is called each frame (from draw) with the current average fft value driving the sine function like this - assuming that value is mapped to 0,1:
sinewave(0.006,averg*20,averg*0.3)
allowing fluctuating values to determine wavelength or frequency can have some visually appealing effect. however, the movement of 'the wave' will never seem natural.
i've accomplished a near enough result in my case.
for making the sine appear to be driven by each 'beat' you'd need beat detection to determine the exact tempo of 'the sound' that 'the wave' is supposed to visualize.
continuous averaging of distance between larger peaks in the lower range of fft spectrum might work there with setting a semi fixed frequency - with edm...
i know, the question was about smoothing array values.
forgive me for changing the subject. i just thought that the objective 'sound wave' is an interesting one that could be achieved differently.
and just so this is complete here's a bit that simply draws circles for each fft and assign colour according to volume.
with linewidths relative to total radius and sum of volumes this is quite nice:
//col generator
function getCol(n,m,f){
var a = (PIx5*n)/(3*m) + PIdiv2;
var r = map(sin(a),-1,1,0,255);
var g = map(sin(a - PIx2/3),-1,1,0,255);
var b = map(sin(a - PIx4/3),-1,1,0,255);
return ("rgba(" + r + "," + g + "," + b + "," + f + ")");
}
//draw circles for each fft with linewidth and colour relative to value
function drawCircles(arr){
var nC = 20; //number of elem from array we want to use
var cAv = 0;
var cAvsum = 0;
//get the sum of all values so we can map a single value with regard to this
for(var i = 0; i< nC; i++){
cAvsum += arr[i];
}
cAv = cAvsum/nC;
var lastwidth = 0;
//draw a circle for each elem from array
//compute linewith a fraction of width relative to value of elem vs. sum of elems
for(var i = 0; i< nC; i++){
ctx.beginPath();
var radius = lastwidth;//map(arr[i]*2,0,255,0,i*300);
//use a small col generator to assign col - map value to spectrum
ctx.strokeStyle = getCol(map(arr[i],0,255,0,1280),1280,0.05);
//map elem value as fraction of elem sum to linewidth/total width of outer circle
ctx.lineWidth = map(arr[i],0,cAvsum,0,width);
//draw
ctx.arc(centerX, centerY, radius, 0, Math.PI*2, false);
ctx.stroke();
//add current radius and linewidth to lastwidth
var lastwidth = radius + ctx.lineWidth/2;
}
}
codepen here: https://codepen.io/sumoclub/full/QWBwzaZ
always happy about suggestions.

JS canvas implementation of Julia set

The problem is currently solved. In case some one wants to see the colored fractal, the code is here.
Here is the previous problem:
Nonetheless the algorithm is straight forward, I seems to have a small error (some fractals are drawing correctly and some are not). You can quickly check it in jsFiddle that c = -1, 1/4 the fractal is drawing correctly but if I will take c = i; the image is totally wrong.
Here is implementation.
HTML
<canvas id="a" width="400" height="400"></canvas>
JS
function point(pos, canvas){
canvas.fillRect(pos[0], pos[1], 1, 1); // there is no drawpoint in JS, so I simulate it
}
function conversion(x, y, width, R){ // transformation from canvas coordinates to XY plane
var m = R / width;
var x1 = m * (2 * x - width);
var y2 = m * (width - 2 * y);
return [x1, y2];
}
function f(z, c){ // calculate the value of the function with complex arguments.
return [z[0]*z[0] - z[1] * z[1] + c[0], 2 * z[0] * z[1] + c[1]];
}
function abs(z){ // absolute value of a complex number
return Math.sqrt(z[0]*z[0] + z[1]*z[1]);
}
function init(){
var length = 400,
width = 400,
c = [-1, 0], // all complex number are in the form of [x, y] which means x + i*y
maxIterate = 100,
R = (1 + Math.sqrt(1+4*abs(c))) / 2,
z;
var canvas = document.getElementById('a').getContext("2d");
var flag;
for (var x = 0; x < width; x++){
for (var y = 0; y < length; y++){ // for every point in the canvas plane
flag = true;
z = conversion(x, y, width, R); // convert it to XY plane
for (var i = 0; i < maxIterate; i++){ // I know I can change it to while and remove this flag.
z = f(z, c);
if (abs(z) > R){ // if during every one of the iterations we have value bigger then R, do not draw this point.
flag = false;
break;
}
}
// if the
if (flag) point([x, y], canvas);
}
}
}
Also it took me few minutes to write it, I spent much more time trying to find why does not it work for all the cases. Any idea where I screwed up?
Good news! (or bad news)
You're implementation is completely. correct. Unfortunately, with c = [0, 1], the Julia set has very few points. I believe it is measure zero (unlike say, the Mandelbrot set). So the probability of a random point being in that Julia set is 0.
If you reduce your iterations to 15 (JSFiddle), you can see the fractal. One hundred iterations is more "accurate", but as the number of iterations increase, the chance that a point on your 400 x 400 grid will be included in your fractal approximation decreases to zero.
Often, you will see the Julia fractal will multiple colors, where the color indicates how quickly it diverges (or does not diverge at all), like in this Flash demonstration. This allows the Julia fractal to be somewhat visible even in cases like c = i.
Your choices are
(1) Reduce your # of iterations, possibly depending on c.
(2) Increase the size of your sampling (and your canvas), possibly depending on c.
(3) Color the points of your canvas according to the iteration # at which R was exceeded.
The last option will give you the most robust result.

Algorithm for random land in a "Tank Wars" game [closed]

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 !

Categories