I am working on a text based (console) WW2 strategy game, set on a 2d square grid map. I want a method to calculate line of sight from one tile on the map to another. I've used this Java example to base my code off, this is what I've written:
public function plotLine($x0, $y0, $x1, $y1, $size)
{
$arr = $this->getEmptyMap($size);
$xDist = abs($x1 - $x0);
$yDist = -abs($y1 - $y0);
if($x0 < $x1) {
$xStep = 1;
} else {
$xStep = -1;
}
if($y0 < $y1) {
$yStep = 1;
} else {
$yStep = -1;
}
$plotError = $xDist + $yDist;
$arr[$x0][$y0] = 1;
while($x0 != $x1 || $y0 != $y1) {
// if(2 * $plotError > $yDist) {
// // Horizontal step
// $plotError += $yDist;
// $x0 += $xStep;
// }
// if(2 * $plotError < $xDist) {
// // Vertical step
// $plotError += $xDist;
// $y0 += $yStep;
// }
if(2 * $plotError - $yDist > $xDist - 2 * $plotError) {
// Horizontal step
$plotError += $yDist;
$x0 += $xStep;
} else {
// Vertical step
$plotError += $xDist;
$y0 += $yStep;
}
$arr[$x0][$y0] = 1;
}
$this->line = $arr;
}
Note: getEmptyMap just fills a multidimensional array with 0's.
Testresult using (0, 0, 4, 4, 4) as input:
1100
0110
0011
0001
I've tried to ways of mapping the line: one is the normal implementation that Franz D. used (currently commented out in my example above), the other is the modified implementation that Franz D. showed. Neither is giving me the result I'm looking for; a kind of "anti-aliasing". When a solder would look from 0,0 at 2,2 and there would be buildings at 1,2 and 2,1, whatever is at 2,2 should be blocked from sight. The commented out implementation would completely ignore the buildings, the modification does "hit" 2,1 but not 1,2. How would I adjust my code to "hit" both underneath the line and above the line?
The 'problem' you are facing happens because of the special edge case when looking at an exact diagonal. There are 'only' two (simple) possibilities of handling this:
1) A diagonal increments a horizontal and vertical tile at the same time. In your game, this would mean that units would be able to look at the diagonals, even if the cardinal directions would be blocked.
2) Choose between giving precedence to the horizontal tiles or the vertical tiles and only ever incrementing one of the two. This is the algorithm of Franz D. that you ended up writing and placing in your post. Here, the if-statement is true for diagonals, which means that the result will be:
1100
0110
0011
0001
If you want the verticals to have precedence, you can alter it to:
...
if(2 * $plotError - $yDist < $xDist - 2 * $plotError) {
// Vertical step
$plotError += $xDist;
$y0 += $yStep;
} else {
// Horizontal step
$plotError += $yDist;
$x0 += $xStep;
}
...
Note that both the bodies of the if/else are swapped, and the > was changed into < in the condition.
Now, the result will be:
1000
1100
0110
0011
If you want a unit to only be able to look to the diagonals if there is nothing blocking éither of the adjacent cardinals, the simplest solution would be to use both of above variants of the algorithm, and combine their results into one single array of tiles.
One final note: if you are only interested in the coordinates and not in their values (as seems to be the case for the use case you describe), it might be more (memory)efficient to use a simple array of extracted (x, y) coordinates instead of a two-dimensional array of the complete map which you afterwards loop over to extract all (x, y) coordinates where the result is a 1.
Good luck with the game!
Related
My question is how could I replace those if's with math formula?
if ($l <= 3500)
{
$min = 100;
}
elseif ($l <= 4000)
{
$min = 120;
}
elseif ($l <= 4500)
{
$min = 140;
}
elseif ($l <= 5000)
{
$min = 160;
}
As you see this is raising 20 for every 500 levels.
As you see this is raising 20 for every 500 levels.
Well, that's your formula right there.
$min = 100 + ceil(($l-3500)/500) * 20;
We start with 100, our base value and add that to the rest of the calculation.
$l starts with 3500 less.
We ceil() the result since we only want to jump when we pass the whole value.
We multiply that by 20.
If we want to address the case where $l is less than 3500 and set 100 as the minimum value, we also need to asset that $l-3500 is more than zero. We can do this as such:
$min = 100 + ceil(max(0,$l-3500)/500) * 20;
How did I get there?
What we're actually doing is plotting a line. Like you said yourself we go a constant amount for every constant amount. We have something called linear progression here.
Great, so we recognized the problem we're facing. We have an imaginary line to plot and we want integer values. What next? Well, let's see where the line starts?
In your case the answer is pretty straightforward.
if ($l <= 3500) {
$min = 100;
}
That's our starting point. So we know the point (3500,100) is on our line. This means the result starts at 100 and the origin starts at 3500.
We know that our formula is in the form of 100+<something>. What is that something?
Like you said, for every 500 levels you're raising 20. So we know we move 20/500 for every 1 level (because well, if we multiply that by 500 we get our original rule). We also know (from before) that we start from 3500.
Now, we might be tempted to use $min = 100 + ($l-3500) * (20/500); and that's almost right. The only problem here is that you only want integer values. This is why we ceil the value of level/500 to only get whole steps.
I tried to keep this with as little math terminology as possible, you can check the wikipedia page if you want things more formal. If you'd like any clarification - let me know
Here is my approach about this problem. It's not better than a single-line formula, but for sake of being modifiable, I generally decide this kind of solutions:
$min = 100;
for($i=3500; $i<=5000; $i+=500)
{
if($l <= $i) break;
$min += 20;
}
//Now $min has got desired value.
You can express the function as follows:
f(x) := a * x + b
The inclination of the line is calculated as:
a := 20 / 500
To find b you need to extrapolate a value that's on the line; in this case, that could be 3500 (x) and 120 (f(x)). That works out to be -40.
So the function has become:
f(x) := (20 / 500) * x - 40
There are two special cases:
Left of 3500 the value of f(x) must remain 100, even though f(x) is less.
The inclination is not continuous but discrete.
Both cases applied:
$min = max(100, ceil($l / 500) * 20 - 40)
I have a list of values which should be plotted to a map with a certain color.
The plotting to the map is already done, but I need to figure out a way to map the value n to a color that represents its value.
An example and my solution so far is to normalize the values based on the min and max and then assign them to hex color 0 for the lowest and 255 for the highest. This of course limits my self to the grey scale. Here is the code:
$color = ($value / $max) * 255 // (min is zero)
But how to do this if the values should go from blue to red for instance?
Is there any common libraries or tools that can solve this? So far I haven't been able to locate any.
There might be libs to do that. However let's get a short warm up into the general principles. In general you have following options:
A predefined color index, e.g. $coloridx=array(0=>'#FFFFFF',1=>'#FFEE00',...);
Any algorithm, e.g. linear gradient, which is basically an iterations based adaption of all three RGB channels (R = red, G = green, B = blue).
A combination of both, which usually puts the result of any complex algorithm to the color index and then goes from there.
If you include algorithms in your considerations you must understand that there is no true or false. It all depends on what you would like to implement. There might be occasions where it makes sense to render variations of green into n=0..10 and then have red to black in everything beyond n>10. Caps and multipliers help to set accents. Things like that.
One way of implementing a linear gradient would be:
function lineargradient($ra,$ga,$ba,$rz,$gz,$bz,$iterationnr) {
$colorindex = array();
for($iterationc=1; $iterationc<=$iterationnr; $iterationc++) {
$iterationdiff = $iterationnr-$iterationc;
$colorindex[] = '#'.
dechex(intval((($ra*$iterationc)+($rz*$iterationdiff))/$iterationnr)).
dechex(intval((($ga*$iterationc)+($gz*$iterationdiff))/$iterationnr)).
dechex(intval((($ba*$iterationc)+($bz*$iterationdiff))/$iterationnr));
}
return $colorindex;
}
$colorindex = lineargradient(
100, 0, 0, // rgb of the start color
0, 255, 255, // rgb of the end color
256 // number of colors in your linear gradient
);
$color = $colorindex[$value];
I UPDATED the code to add dechex, which feeds back on the comments.
Colors values are representations. Numeric colors as well as hexadecimal colors. A "not grayscale" color contains at least 2 different informations: Red value, green value or blue values may be different. Performing operation on its representation gives wrong result. The 'Mapping" must then be performed on each pieces of information. You need to extract red, green and blue values, perform the mapping seperatly, then build the representation of the result color. Here is a quick helper that use a "min color" and "max color", performs mapping on red, green and blue values, according to the "n" value you need to work with, then return the result color in hexadecimal string. It works for any colors or gray scale color as well.
function linear_color($from, $to, $ratio) {
// normalize ralio
$ratio = $ratio<0?0:($ratio>1?1:$ratio);
// unsure colors are numeric values
if(!is_numeric($from))$from=hexdec($from);
if(!is_numeric($to))$to=hexdec($to);
$rf = 0xFF & ($from >> 0x10);
$gf = 0xFF & ($from >> 0x8);
$bf = 0xFF & $from;
$rt = 0xFF & ($to >> 0x10);
$gt = 0xFF & ($to >> 0x8);
$bt = 0xFF & $to;
return str_pad( dechex(($bf + (($bt-$bf)*$ratio)) + ($gf + (($gt-$gf)*$ratio) << 0x8) + ($rf + (($rt-$rf)*$ratio) << 0x10)), 6,'0',STR_PAD_LEFT);
}
Just specify 2 colors as numeric value or hexadecimal string (without hash!) like this :
$color_from = hexdec('c2c2c2');
$color_to = hexdec('1eb02b');
for($i=-0.2; $i<=1.3; $i+=0.04){
echo '<div style="background-color: #';
echo linear_color($color_from, $color_to, $i);
echo '">';
echo 'Result color when n = <strong>'.$i.'</strong>';
echo '</div>';
}
The other answer was very usefull. I decided to use JS as it reduces server load. The requirements also changed. The bar has to go from Red to white in the middle and then from white to yellow. And if the value is 0 it should be black. Here is my code for anyone who ever encounters a similar situation.
var normalize_base = 2*255;
var no_con_color = "black";
function decimalToHex(d, padding) {
var hex = Number(d).toString(16);
padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;
while (hex.length < padding) {
hex = "0" + hex;
}
return hex;
}
function normalize(max, min, value)
{
var normalized = (value - min) / (max - min) ;
return Math.floor(normalized * normalize_base);
}
function value2Color(value)
{
if(value <= 0 || value == undefined)
{
return no_con_color;
}
var g = 0;
var b = 0;
var r = 0;
if(value < 255)
{
r = 255;
g = value;
b = value;
} else {
r = (2*255) - value;
g = 255;
b = (2*255) - value;
}
return "#" + decimalToHex(r) + decimalToHex(g) + decimalToHex(b);
}
There is I2UI for this.
<div data-i2="color:['#404040','#00FF21']">
<span data-i2="rate:1">A</span>
<span data-i2="rate:2">B</span>
<span data-i2="rate:3">C</span>
<span data-i2="rate:4">D</span>
<span data-i2="rate:5">E</span>
</div>
There is a color range: from "Gray" - to "Green". The span element that has lowest rate value get the "Gray" color, the element with the biggest rate get "Green" color.
Thus, the span that are between edges get the color that has direct ratio to its rate.
Also, call JavaScript i2.emph() after the previous HTML have been loaded.
See demo
This is one-function-only code to convert any number from any given range (let's say [0,20]) to particular color where 0 is red, 10 is yellow, 20 is green. You can use any colors and even use combination of 4 colors, so it's red - yellow - green - blue.
See the end of the gist file, where this function is used.
Gist with the code:
Click here
I'm completely stuck with something that i believe is relatively simple to solve:
In PHP I am drawing filled rectangles on top of an image (using GD) - I want a small gap between each of them - which is specified in the code (gap is the same for each box) - I loop around until i've hit the max amount of rectangles on a row (e.g max columns) - and i've managed the horizontal gap offset but for the life of me can't work out the vertical gap maths.
Here is my code in it's nasty entirety:
http://pastebin.com/MHUqi0tG
But specifically PHP accepts two sets of coordinates to make a rectangle - two for the top left (x and y) and two for the bottom right (x and y).
Here is my code for that in particular:
$left_wall_of_box_x = ( $current_col * $box_size ) + $origin_x ;
if($last_block_x != $origin_x){
$left_wall_of_box_x = $last_block_x + $gap;
}
/*verticals coord 1*/
$left_wall_of_box_y = ( $current_row * $box_size ) + $origin_y;
$right_wall_x = $left_wall_of_box_x + $box_size ;
$right_wall_y = $left_wall_of_box_y + $box_size;
imagefilledrectangle($im, $left_wall_of_box_x, $left_wall_of_box_y, $right_wall_x, $right_wall_y, $red);
$gap = 2;
$origin_x & origin_y = the place i start drawing boxes from.
Output at the moment is nicely separated boxes on the horizontal axis but vertically they are hitting each other/merging together.
Any help obviously greatly appreciated - Tearing hair out on this.
Thanks
The mistake is here:
$left_wall_of_box_y = ( $current_row * $box_size ) + $origin_y;
Should be:
$left_wall_of_box_y = ( $current_row * ($box_size + $gap_vertical)) + $origin_y;
But your code is fragile and hard to read. It's fragile as you're making your calculations more be dependent on variables that represent subtly different things, and as there's more variables, it's harder to fit in your head - which is partly why you didn't spot the error.
So, rather than doing this:
while($x <= $total_boxes_to_draw)
{
if($current_col >= $cols)
{
//reset to start of row
}
}
Write your code like this.
$finished = false;
for ($y=0; ($y<$rows) && ($finished==false) ; $y++)
{
$yPosition = $origin_y + $y * ($box_size + $spacing_vertical);
for ($x=0 ; $x<$cols && ($finished==false) ; $x++)
{
$xPosition = $origin_x + $x * ($box_size + $spacing_horizontal);
//draw box at $xPosition, $yPosition
$boxesDrawn++;
if ($boxesDrawn >= $total_boxes_to_draw){
$finished = true;
}
}
}
You are adding a gap horizontally, but you also have to add it vertically.
A simple fix is subtracting the gap from the height of the rectangles you fill:
imagefilledrectangle($im, $left_wall_of_box_x, $left_wall_of_box_y,
$right_wall_x, $right_wall_y - gap, $red);
I have a system of equations of grade 1 to resolve in PHP.
There are more equations than variables but there aren't less equations than variables.
The system would look like bellow. n equations, m variables, variables are x[i] where 'i' takes values from 1 to m. The system may have a solution or not.
m may be maximum 100 and n maximum ~5000 (thousands).
I will have to resolve like a few thousands of these systems of equations. Speed may be a problem but I'm looking for an algorithm written in PHP for now.
a[1][1] * x[1] + a[1][2] * x[2] + ... + a[1][m] * x[m] = number 1
a[2][1] * x[1] + a[2][2] * x[2] + ... + a[2][m] * x[m] = number 2
...
a[n][1] * x[1] + a[n][2] * x[2] + ... + a[n][m] * x[m] = number n
There is Cramer Rule which may do it. I could make 1 square matrix of coefficients, resolve the system with Cramer Rule (by calculating matrices' determinants) and than I should check the values in the unused equations.
I believe I could try Cramer by myself but I'm looking for a better solution.
This is a problem of Computational Science,
http://en.wikipedia.org/wiki/Computational_science#Numerical_simulations
I know there are some complex algorithms to solve my problem but I can't tell which one would do it and which is the best for my case. An algorithm would use me better than just the theory with the demonstration.
My question is, does anybody know a class, script, code of some sort written in PHP to resolve a system of linear equations of grade 1 ?
Alternatively I could try an API or a Web Service, best to be free, a paid one would do it too.
Thank you
I needed exactly this, but I couldn't find determinant function, so I made one myself. And the Cramer rule function too. Maybe it'll help someone.
/**
* $matrix must be 2-dimensional n x n array in following format
* $matrix = array(array(1,2,3),array(1,2,3),array(1,2,3))
*/
function determinant($matrix = array()) {
// dimension control - n x n
foreach ($matrix as $row) {
if (sizeof($matrix) != sizeof($row)) {
return false;
}
}
// count 1x1 and 2x2 manually - rest by recursive function
$dimension = sizeof($matrix);
if ($dimension == 1) {
return $matrix[0][0];
}
if ($dimension == 2) {
return ($matrix[0][0] * $matrix[1][1] - $matrix[0][1] * $matrix[1][0]);
}
// cycles for submatrixes calculations
$sum = 0;
for ($i = 0; $i < $dimension; $i++) {
// for each "$i", you will create a smaller matrix based on the original matrix
// by removing the first row and the "i"th column.
$smallMatrix = array();
for ($j = 0; $j < $dimension - 1; $j++) {
$smallMatrix[$j] = array();
for ($k = 0; $k < $dimension; $k++) {
if ($k < $i) $smallMatrix[$j][$k] = $matrix[$j + 1][$k];
if ($k > $i) $smallMatrix[$j][$k - 1] = $matrix[$j + 1][$k];
}
}
// after creating the smaller matrix, multiply the "i"th element in the first
// row by the determinant of the smaller matrix.
// odd position is plus, even is minus - the index from 0 so it's oppositely
if ($i % 2 == 0){
$sum += $matrix[0][$i] * determinant($smallMatrix);
} else {
$sum -= $matrix[0][$i] * determinant($smallMatrix);
}
}
return $sum;
}
/**
* left side of equations - parameters:
* $leftMatrix must be 2-dimensional n x n array in following format
* $leftMatrix = array(array(1,2,3),array(1,2,3),array(1,2,3))
* right side of equations - results:
* $rightMatrix must be in format
* $rightMatrix = array(1,2,3);
*/
function equationSystem($leftMatrix = array(), $rightMatrix = array()) {
// matrixes and dimension check
if (!is_array($leftMatrix) || !is_array($rightMatrix)) {
return false;
}
if (sizeof($leftMatrix) != sizeof($rightMatrix)) {
return false;
}
$M = determinant($leftMatrix);
if (!$M) {
return false;
}
$x = array();
foreach ($rightMatrix as $rk => $rv) {
$xMatrix = $leftMatrix;
foreach ($rightMatrix as $rMk => $rMv) {
$xMatrix[$rMk][$rk] = $rMv;
}
$x[$rk] = determinant($xMatrix) / $M;
}
return $x;
}
Wikipedia should have pseudocode for reducing the matrix representing your equations to reduced row echelon form. Once the matrix is in that form, you can walk through the rows to find a solution.
There's an unmaintained PEAR package which may save you the effort of writing the code.
Another question is whether you are looking mostly at "wide" systems (more variables than equations, which usually have many possible solutions) or "narrow" systems (more equations than variables, which usually have no solutions), since the best strategy depends on which case you are in — and narrow systems may benefit from using a linear regression technique such as least squares instead.
This package uses Gaussian Elimination. I found that it executes fast for larger matrices (i.e. more variables/equations).
There is a truly excellent package based on JAMA here: http://www.phpmath.com/build02/JAMA/docs/index.php
I've used it for simple linear right the way to highly complex Multiple Linear Regression (writing my own Backwards Stepwise MLR functions on top of that). Very comprehensive and will hopefully do what you need.
Speed could be considered an issue, for sure. But works a treat and matched SPSS when I cross referenced results on the BSMLR calculations.
I am working on a personal project in which IQ ranges will be randomly assignes to fake characters. This asignment will be random, yet realistic, so IQ ranges must be distributed along a bell curve. There are 3 range categories: low, normal, and high. The half of the fake characters will fall within normal, but about 25% will either fall into the low or high range.
How can I code this?
It might look long and complicated (and was written procedural for PHP4) but I used to use the following for generating non-linear random distributions:
function random_0_1()
{
// returns random number using mt_rand() with a flat distribution from 0 to 1 inclusive
//
return (float) mt_rand() / (float) mt_getrandmax() ;
}
function random_PN()
{
// returns random number using mt_rand() with a flat distribution from -1 to 1 inclusive
//
return (2.0 * random_0_1()) - 1.0 ;
}
function gauss()
{
static $useExists = false ;
static $useValue ;
if ($useExists) {
// Use value from a previous call to this function
//
$useExists = false ;
return $useValue ;
} else {
// Polar form of the Box-Muller transformation
//
$w = 2.0 ;
while (($w >= 1.0) || ($w == 0.0)) {
$x = random_PN() ;
$y = random_PN() ;
$w = ($x * $x) + ($y * $y) ;
}
$w = sqrt((-2.0 * log($w)) / $w) ;
// Set value for next call to this function
//
$useValue = $y * $w ;
$useExists = true ;
return $x * $w ;
}
}
function gauss_ms( $mean,
$stddev )
{
// Adjust our gaussian random to fit the mean and standard deviation
// The division by 4 is an arbitrary value to help fit the distribution
// within our required range, and gives a best fit for $stddev = 1.0
//
return gauss() * ($stddev/4) + $mean;
}
function gaussianWeightedRnd( $LowValue,
$maxRand,
$mean=0.0,
$stddev=2.0 )
{
// Adjust a gaussian random value to fit within our specified range
// by 'trimming' the extreme values as the distribution curve
// approaches +/- infinity
$rand_val = $LowValue + $maxRand ;
while (($rand_val < $LowValue) || ($rand_val >= ($LowValue + $maxRand))) {
$rand_val = floor(gauss_ms($mean,$stddev) * $maxRand) + $LowValue ;
$rand_val = ($rand_val + $maxRand) / 2 ;
}
return $rand_val ;
}
function bellWeightedRnd( $LowValue,
$maxRand )
{
return gaussianWeightedRnd( $LowValue, $maxRand, 0.0, 1.0 ) ;
}
For the simple bell distribution, just call bellWeightedRnd() with the min and max values; for a more sophisticated distribution, gaussianWeightedRnd() allows you to specify the mean and stdev for your distribution as well.
The gaussian bell curve is well suited to IQ distribution, although I also have similar routines for alternative distribution curves such as poisson, gamma, logarithmic, &c.
first assume you have 3 function to provide high medium and low IQs, then simply
function randomIQ(){
$dice = rand(1,100);
if($dice <= 25) $iq = low_iq();
elseif($dice <= 75) $iq = medium_iq();
else $iq = high_iq();
return $iq;
}
You could randomize multiple 'dice', random number from each adding up to the highest point. This will generate a normal distribution (approximately).
Using the link that ithcy posted I created the following function:
function RandomIQ()
{
return round((rand(-1000,1000) + rand(-1000,1000) + rand(-1000,1000))/100,0) * 2 + 100;
}
It's a little messy but some quick checking gives it a mean of approximately 100 and a roughly Normal Distribution. It should fall in line with the information that I got from this site.