My PHP array issue - php

This will be a pretty strange question, but bear with me.
I'm coding a browser based game, and each player has an amount of guards, each with 100 health. Every time they are shot, the guards lose health. If all the guards are dead, the player takes the health instead.
The shot damage always overlaps as well, so if player has 3 guards, and the top guard has 60 health, a shot of 100 will kill guard 3 and leave guard 2 with 60 health.
i use a php array to sort this, and it works, except when it comes down to the players health. It doesn't calculate correctly, eg all a players guards are dead and the player has 60 health left. He is shot for 100, but instead of dying the health loops round so he has some other number health instead of -40.
$p_bg = array(); // players guard count
$p_bg[0] = $rs[bgs_hp2]; // only the top guard hp is saved (bgs_hp2)
$p_hp = $rs[hp2]; // players health
$dmg = 80 // shot damage
$x = 0;
while($x < $rs[BGs2]) { $p_bg[$x] = 100; $x++; }
// As long as there's still damage to take and bgs to take it:
while($dmg && !empty($p_bg)) {
$soak = min($p_bg[0], $dmg); // not more than the first bg can take
$p_bg[0] -= $soak; // remove hps from the first bg
$dmg -= $soak; // deduct from the amount of damage to tage
if ($p_bg[0] == 0) {
// bodyguard dead, remove him from the array
array_shift($p_bg);
}
}
// If there's any damage left over, it goes to hp
$p_hp = $p_hp - $dmg;

Without knowing the contents of $rs or knowing what the constants bgs_hp2, hp2, and BGs2 are, it's hard to say, but it seems like the problem lies around this combination of lines:
$p_bg = array(); // Create an empty array
$p_bg[0] = $rs[bgs_hp2]; // Populate the first index, possibly null?
$x = 0;
while($x < $rs[BGs2]) { $p_bg[$x] = 100; $x++; }
I'd suspect BGs2 is the bodyguard count for the player? It seems that you're setting the top bodyguard's health to 100 each time this code is hit. Maybe it would be more clear if it was rearranged as follows:
$p_bg = array($rs[bgs_hp2]);
for ($x = 0; $x < $rs[BGs2]; $x++) { $p_bg[] = 100; }
Other than that, log out your variables (using print_r($var) or var_dump($var) as necessary) to see the actual data you're executing on. Good luck!

Related

Generating groups of random numbers

Quick background on myself. This is my first time coding in PHP. I have a computer information systems degree, learned C++, VB, Cobol and Java in college (about 15 years ago), but have not really used it since. Some stuff is coming back to me as I learn this.
I'm attempting to simulate the opening of randomized packs of cards for a trading card game. The end result would be printing out 4 pages. Each page will list 12 card numbers, and all of the information on the card.
Here's what I was planning on doing:
Step One:
Generate 180 random numbers from different ranges. Each number represents a card in the game.
range 1 = 35-74 (116 numbers)
range 2 = 75-106 (46 numbers)
range 3 = 107-134 (16 numbers, no duplicates)
range 4 = 135-142 (2 numbers, no duplicates)
Step Two:
Take the 180 numbers and break them down into 90 pairs.
Step Three:
From the 90 pairs, break them down to 4 sets of 6 pairs.
Step Four:
From the 4 sets of 6 pairs, list the card information for each number and make 4 printable pages, 1 for each set.
I've gotten as far as creating the 180 random numbers. I'm still working on getting the unique numbers. I've tried creating arrays for the numbers I need, but none of them work. Here's the last working code I have, which will generate the 180 numbers I need, however, range 3 and 4 need to be fixed to not allow duplicates.
The way i currently have this coded, it just displays the numbers on the screen. Should I be storing them in an array? Am I just completely tackling the wrong way?
<?php
// generate 116 common cards
echo "Commons: " . '<br />';
for ($commonfeed = 0; $commonfeed < 116; $commonfeed++) {
echo mt_rand(35, 74). '<br />';
}
// generate 46 uncommon cards
echo "Uncommons: " . '<br />';
for ($uncommonfeed = 0; $uncommonfeed < 46; $uncommonfeed++) {
echo mt_rand(75, 106). '<br />';
}
// generate 16 rare cards
echo "Rares: " . '<br />';
for ($rarefeed = 0; $rarefeed < 16; $rarefeed++) {
echo mt_rand(107, 134). '<br />';
}
// generate 2 super rare cards
echo "Super Rares: " . '<br />';
for ($superrarefeed = 0; $superrarefeed < 2; $superrarefeed++) {
echo mt_rand(135, 142). '<br />';
}
?>
Here's a solution you might try:
$cards = array();
// get cards per range
for($i = 0; $i < 116; $i++) {
// range 1:
$cards[] = mt_rand(35, 74);
// for the fun, let's also do range 2:
if($i < 46) {
$cards[] = mt_rand(75, 106);
}
}
// range 3: (and range 4)
$rare = array();
$superrare = array();
for ($i = 107; $i <= 134; $i++) {
$rare[] = $i;
// range 4:
if ($i <= 114) {
$superrare[] = $i + 28;
}
}
shuffle($rare);
shuffle($superrare); // not the best choice of randomness (check: http://stackoverflow.com/questions/5694319/how-random-is-phps-shuffle-function)
$cards = array_merge($cards, array_slice($rare, 0, 16));
$cards = array_merge($cards, array_slice($superrare, 0, 2));
// shuffle everything one more time since cards have been added randomly
// to the deck
shuffle($cards);
// now when we have everything in $cards - 180 of random cards, we can
// pack them
$pack1 = array_slice($cards, 0, 90);
$pack2 = array_slice($cards, 90, 90);
// always picking first n cards because they are all shuffled
$player1Drafted = array_slice($pack1, 0, 48);
$player2Drafted = array_slice($pack2, 0, 48);
// print it all
print_r(array('Player1' => $player1Drafted,
'Player2' => $player2Drafted));
In the end, I'm not entirely sure i guessed the drafting process OK, but it seemed to me that randomization was the biggest issue and that I solved it OK. Again, if you think that shuffle is not good enough, it can be done differently, but that's another story ;)
Since you are learning to code (again), I will answer using a few pointers instead of just generating some working code.
To start off with the last question: yes, i would be storing everything in an array. This way you can split "processing" code from "output" code.
Are you tackling this the wrong way? Hard to say, depends on all game mechanics and such. But to start easy: yes, it is a good start.
Using array_unique you can make an array unique, which you can use while generating the rare and superrare cards.
Onto the game mechanics: Are you sure you always want to give someone 16 rare cards and 2 super rare cards? What you could do, is create the total "deck of cards" up front, and then select the number of cards you would like:
$number_of_cards = 5;
$deck = [1,1,1,1,2,2,2,2,3,3,4,4,100,101,102];
shuffle($deck); // shuffle the cards
$selected = array_slice($deck, 0, $number_of_cards); // select the amount of cards
You can even just use this using strings instead of integers.

Coordinate (x,y) list to be sort with a spiral algorithm

I have a list of coordinate to be sorted with a spiral algorithm. My need is to start on the middle of the area and "touch" any coordinate.
To simplify this is the representation of the (unsorted) list of coordinates (x,y marked with a "dot" on following image).
CSV list of coordinates is available here.
X increase from left to right
Y increases from TOP to BOTTOM
Every coordinate is not adjacent to the following one but are instead distanciated by 1 or 2 dice (or more in certain case).
Starting from the center of the area, I need to touch any coordinate with a spiral movement:
to parse each coordinate I've drafted this PHP algorithm:
//$missing is an associative array having as key the coordinate "x,y" to be touched
$direction = 'top';
$distance = 1;
$next = '128,127'; //starting coordinate
$sequence = array(
$next;
)
unset($missing[$next]);
reset($missing);
$loopcount = 0;
while ($missing) {
for ($loop = 1; $loop <= 2; $loop++) {
for ($d = 1; $d <= $distance; $d++) {
list($x,$y) = explode(",", $next);
if ($direction == 'top') $next = ($x) . "," . ($y - 1);
elseif ($direction == 'right') $next = ($x + 1) . "," . ($y);
elseif ($direction == 'bottom') $next = ($x) . "," . ($y + 1);
elseif ($direction == 'left') $next = ($x - 1) . "," . ($y);
if ($missing[$next]) {
unset($missing[$next]); //missing is reduced every time that I pass over a coordinate to be touched
$sequence[] = $next;
}
}
if ($direction == 'top') $direction = 'right';
elseif ($direction == 'right') $direction = 'bottom';
elseif ($direction == 'bottom') $direction = 'left';
elseif ($direction == 'left') $direction = 'top';
}
$distance++;
}
but as coordinate are not equidistant from each other, I obtain this output:
As is clearly visible, the movement in the middle is correct whereas and accordingly with the coordinate position, at a certain instant the jump between each coordinate are not anymore coherent.
How can I modify my code to obtain an approach like this one, instead?
To simplify/reduce the problem: Imagine that dots on shown above image are cities that the salesman have to visit cirurarly. Starting from the "city" in the middle of the area, the next cities to be visited are the ones located near the starting point and located on North, East, Soutch and West of the starting point. The salesman cannot visit any further city unless all the adjacent cities in the round of the starting point hadn't been visited. All the cities must be visited only one time.
Algorithm design
First, free your mind and don't think of a spiral! :-) Then, let's formulate the algorithms constraints (let's use the salesman's perspective):
I am currently in a city and am looking where to go next. I'll have to find a city:
where I have not been before
that is as close to the center as possible (to keep spiraling)
that is as close as possible to my current city
Now, given these three constraints you can create a deterministic algorithm that creates a spiral (well at least for the given example it should, you probably can create cases that require more effort).
Implementation
First, because we can walk in any direction, lets generally use the Euclidean distance to compute distances.
Then to find the next city to visit:
$nextCost = INF;
$nextCity = null;
foreach ($notVisited as $otherCity) {
$cost = distance($current_city, $other_city) + distance($other_city, $centerCity);
if ($cost < $nextCost) {
$nextCost = $cost;
$nextCity = $otherCity;
}
}
// goto: $nextCity
Just repeat this until there are no more cities to visit.
To understand how it works, consider the following picture:
I am currently at the yellow circle and we'll assume the spiral up to this point is correct. Now compare the length of the yellow, pink and blue lines. The length of those lines is basically what we compute using the distance functions. You will find that in every case, the next correct city has the smallest distance (well, at least as long as we have as many points everywhere, you probably can easily come up with a counter-example).
This should get you started to implement a solution for your problem.
(Correctness) Optimization
With the current design, you will have to compare the current city to all remaining cities in each iteration. However, some cities are not of interest and even in the wrong direction. You can further optimize the correctness of the algorithm by excluding some cities from the search space before entering the foreach loop shown above. Consider this picture:
You will not want to go to those cities now (to keep spiraling, you shouldn't go backwards), so don't even take their distance into account. Albeit this is a little more complicated to figure out, if your data points are not as evenly distributed as in your provided example, this optimization should provide you a healthy spiral for more disturbed datasets.
Update: Correctness
Today it suddenly struck me and I rethought the proposed solution. I noticed a case where relying on the two euclidean distances might yield unwanted behavior:
It is easily possible to construct a case where the blue line is definitely shorter than the yellow one and thus gets preferred. However, that would break the spiral movement. To eliminate such cases we can make use of the travel direction. Consider the following image (I apologize for the hand-drawn angles):
The key idea is to compute the angle between the previous travel direction and the new travel direction. We are currently at the yellow dot and need to decide where to go next. Knowing the previous dot, we can obtain a vector representing the previous direction of the movement (e.g. the pink line).
Next, we compute the vector to each city we consider and compute the angle to the previous movement vector. If that vector is <= 180 deg (case 1 in the image), then the direction is ok, otherwise not (case 2 in the image).
// initially, you will need to set $prevCity manually
$prevCity = null;
$nextCost = INF;
$nextCity = null;
foreach ($notVisited as $otherCity) {
// ensure correct travel direction
$angle = angle(vectorBetween($prevCity, $currentCity), vectorBetween($currentCity, $otherCity));
if ($angle > 180) {
continue;
}
// find closest city
$cost = distance($current_city, $other_city) + distance($other_city, $centerCity);
if ($cost < $nextCost) {
$nextCost = $cost;
$nextCity = $otherCity;
}
}
$prevCity = $currentCity;
// goto: $nextCity
Pay attention to compute the angle and vectors correctly. If you need help on that, I can elaborate further or simply ask a new question.
The problem seems to be in the if-conditional when you missing traverse a co-ordinate, I.e because of rounding of the corners. A else conditional with a reverse to the previous calculation of the co-ordinate would fix it.

Still got negative values even I set already less than or equals to 0

I am creating a PLayer 1 vs Player 2 simple system and the system limit only till 30 rounds if 30 rounds is done and both player are still alive then it is called a draw. If player 1 gets 0 or less than 0 before 30 rounds and player 2 is still alive then player 2 wins the games and etc...
Problem is why I am still having negative values whats wrong with my code? I already set an if statement in there. Any idea will be a big help for me and I am open for improvements since I am still a beginner programmer THANK YOU.
<?php
//Player 1
$p1Health = 100;
$p1Attack = 5;
$p1Speed = 3;
//Player 2
$p2Health = 70;
$p2Attack = 8;
$p2Speed = 5;
//Greater speed attack first
$speed1=0;
$speed2=0;
echo '<td>'.$p1Health.'</td><td>'.$p1Attack.'</td><td>'.$p1Speed.'</td>';
echo '<td>'.$p2Health.'</td><td>'.$p2Attack.'</td><td>'.$p2Speed.'</td>';
//Compare speed
if($p1Speed<$p2Speed){
$speed1=1; //start first
$speed2=0;
}
else {
$speed1=0; //start first
$speed2=1;
}
$rounds = 30; //maximum rounds
$count = 0;
while($count<=30){
if($p1Health<=0 || $p2Health<=0){ //if any of the players health is equal or below zero loop stop and declare winner
break;
}
else if($speed1==1){
$p2Health = $p2Health - $p1Attack;
echo 'Player 2 damaged by '.$p1Attack.' points.Health points left: '.$p2Health.'<br>';
//turn to other player to attack
$speed1=0;
$speed2=1;
}
else if($speed2==1){
$p1Health = $p1Health - $p2Attack;
echo 'Player 1 damaged by '.$p2Attack.' points.Health points left: '.$p1Health.'<br>';
//turn to other player to attack
$speed1=1;
$speed2=0;
}
$count++;
}
if($p1Health>0 && $p2Health<=0){
echo 'Player 1 wins the battle';
}
else if($p2Health>0 && $p1Health<=0){
echo 'Player 2 wins the battle';
}
else if($p1Health>0 && $p2Health>0){
echo 'Battle draw';
}
?>
I don't know if my code is right but this is based for my understanding any idea to improve this will be really a big help for me.
Player 1 starts with 100 health. After each attack from player 2, that goes down by 8. After the 12th attack, player 1 will have 4 health. On the 13th attack, that value is reduced by another 8, yielding −4.
You'll see this phenomenon any time one player's attack strength doesn't evenly divide the other's health.
If you don't want the value to go below zero, even after an attack, then check for that and fix it:
$p1Health = $p1Health - $p2Attack;
if ($p1Health < 0)
$p1Health = 0;
This is because the values can still go negative in, say, execution number 20 of the loop. The loop would still execute again to iteration number 21 where your if conditional would break.
You might consider using the health values in the while condition like:
while ($count < $rounds && p1Health > 0 && p2Health > 0) {
And then eliminate the first conditional in the loop that checks for health values.

Calculating Odds Throttles in PHP

In PHP, if I build a factory inspection program with a conveyor belt where the popdown listbox on the form is 100% (inspect nothing -- let all go through) to 0% (inspect everything), what is the function to calculate when one of the widgets is due for inspection?
A little extra info -- the label says "Let [x%] widgets go through without inspection".
More than this, how can we test your algorithm to prove it's correct? For instance, a value of 100%, run 99999 times, should show no inspections. A value of 99%, run 99999 times, should maybe show one inspection in a blue moon if run repeatedly. A value of 0%, run 99999 times, should show all 99999 widgets being sent to inspection.
EDIT: A coworker says I'm getting odds and probability mixed up here. She thinks I'm describing probability?
Anyway, I tried this code as a test, but it doesn't do anything except at the 100, and 50 through 1 marks. However, the 1-49 act like the 50, while the 51 through 100 act like 100.
<?php
$nChance = # $argv[1];
$nChance = intval($nChance);
for ($i = 1; $i <= 999999; $i++) {
$nTest = rand(1,floor(100/$nChance));
if (!($nTest == 1)) {
die("INSPECT! i($i) rand($nTest) chance($nChance)\n");
}
}
I then tried this variation and that failed too because it simply said INSPECT all the time.
<?php
$nChance = # $argv[1];
$nChance = intval($nChance);
for ($i = 1; $i <= 999999; $i++) {
$nTest = rand(0,100);
if (!($nTest < $nChance)) {
die("INSPECT! i($i) rand($nTest) chance($nChance)\n");
}
}
$nChance = 5; // % chance of inspection
$x = 1; // number of seconds between products passing inspection point
while ( isLineRunning() )
{
if( mt_rand(1,100) <= $nChance )
{
echo "INSPECT ITEM!";
}
sleep( $x );
};
this would check every 'x' seconds while the line is running.
Your colleague is kind of correct but for your purposes you are making a decision based on whether a random number is less than or equal to (in this case) 5 which if truly random (which its not when generated like this) should deliver a 1 in 20 chance you select any given item.

Calculate average without being thrown by strays

I am trying to calculate an average without being thrown off by a small set of far off numbers (ie, 1,2,1,2,3,4,50) the single 50 will throw off the entire average.
If I have a list of numbers like so:
19,20,21,21,22,30,60,60
The average is 31
The median is 30
The mode is 21 & 60 (averaged to 40.5)
But anyone can see that the majority is in the range 19-22 (5 in, 3 out) and if you get the average of just the major range it's 20.6 (a big difference than any of the numbers above)
I am thinking that you can get this like so:
c+d-r
Where c is the count of a numbers, d is the distinct values, and r is the range. Then you can apply this to all the possble ranges, and the highest score is the omptimal range to get an average from.
For example 19,20,21,21,22 would be 5 numbers, 4 distinct values, and the range is 3 (22 - 19). If you plug this into my equation you get 5+4-3=6
If you applied this to the entire number list it would be 8+6-41=-27
I think this works pretty good, but I have to create a huge loop to test against all possible ranges. In just my small example there are 21 possible ranges:
19-19, 19-20, 19-21, 19-22, 19-30, 19-60, 20-20, 20-21, 20-22, 20-30, 20-60, 21-21, 21-22, 21-30, 21-60, 22-22, 22-30, 22-60, 30-30, 30-60, 60-60
I am wondering if there is a more efficient way to get an average like this.
Or if someone has a better algorithm all together?
You might get some use out of standard deviation here, which basically measures how concentrated the data points are. You can define an outlier as anything more than 1 standard deviation (or whatever other number suits you) from the average, throw them out, and calculate a new average that doesn't include them.
Here's a pretty naive implementation that you could fix up for your own needs. I purposely kept it pretty verbose. It's based on the five-number-summary often used to figure these things out.
function get_median($arr) {
sort($arr);
$c = count($arr) - 1;
if ($c%2) {
$b = round($c/2);
$a = $b-1;
return ($arr[$b] + $arr[$a]) / 2 ;
} else {
return $arr[($c/2)];
}
}
function get_five_number_summary($arr) {
sort($arr);
$c = count($arr) - 1;
$fns = array();
if ($c%2) {
$b = round($c/2);
$a = $b-1;
$lower_quartile = array_slice($arr, 1, $a-1);
$upper_quartile = array_slice($arr, $b+1, count($lower_quartile));
$fns = array($arr[0], get_median($lower_quartile), get_median($arr), get_median($upper_quartile), $arr[$c-1]);
return $fns;
}
else {
$b = round($c/2);
$a = $b-1;
$lower_quartile = array_slice($arr, 1, $a);
$upper_quartile = array_slice($arr, $b+1, count($lower_quartile));
$fns = array($arr[0], get_median($lower_quartile), get_median($arr), get_median($upper_quartile), $arr[$c-1]);
return $fns;
}
}
function find_outliers($arr) {
$fns = get_five_number_summary($arr);
$interquartile_range = $fns[3] - $fns[1];
$low = $fns[1] - $interquartile_range;
$high = $fns[3] + $interquartile_range;
foreach ($arr as $v) {
if ($v > $high || $v < $low)
echo "$v is an outlier<br>";
}
}
//$numbers = array( 19,20,21,21,22,30,60 ); // 60 is an outlier
$numbers = array( 1,230,239,331,340,800); // 1 is an outlier, 800 is an outlier
find_outliers($numbers);
Note that this method, albeit much simpler to implement than standard deviation, will not find the two 60 outliers in your example, but it works pretty well. Use the code for whatever, hopefully it's useful!
To see how the algorithm works and how I implemented it, go to: http://www.mathwords.com/o/outlier.htm
This, of course, doesn't calculate the final average, but it's kind of trivial after you run find_outliers() :P
Why don't you use the median? It's not 30, it's 21.5.
You could put the values into an array, sort the array, and then find the median, which is usually a better number than the average anyway because it discounts outliers automatically, giving them no more weight than any other number.
You might sort your numbers, choose your preferred subrange (e.g., the middle 90%), and take the mean of that.
There is no one true answer to your question, because there are always going to be distributions that will give you a funny answer (e.g., consider a biased bi-modal distribution). This is why may statistics are often presented using box-and-whisker diagrams showing mean, median, quartiles, and outliers.

Categories