Calculating experience for a text-based RPG - php

OK, using the answers over at this question I was able to produce the following code to calculated the experience needed to level on a text-based RPG I am working on. I'll say from the get-go that my math skills are not great, so please "dumb it down" as much as you can.
function calcExp($L) {
if($L <= 5) {
//If Level 5 or less then just 12 exp per level
return 12*$L;
}
if($L > 1000) {
//If over level 1,000, we need to slow them down
//add an additional 500k per level required
$calc = $L - 1000;
$c2 = 500000*$calc;
return (5*$L*$L-5*$L)+$c2;
}
else {
//otherwise, calculate as follows
$exp = 5 * $L * $L - 5 * $L;
return $exp;
}
}
That returns the correct amount of experience, now I'm trying to figure out how to reverse the process so that I can checkLvl($EXPERIENCE) and have it return the level it should be at.
So then if I were to
$level = 10;
$exp = 450;
$check = checkLvl($exp);
if($check != $level) { die('Unknown Error'); }
else { echo "Success!"; }
Note: the above code is just me trying to be clear.
I'm at a loss as to how to reverse the little mess I created.

Your first five levels are only tweleve exp per level, so you could reverse and say the first 60 exp (5 * 12) should be floor($exp / 12)
Your second level range is from 6 to 1000. So if the exp is greater than 60, but less than whatever exp you need for level 1000, you'd solve $exp = 5 * $L * $L - 5 * $L for $L, which is a quadratic equation. It would look something like $L = (5 + sqrt(25 - 4 * 5 * -$exp)) / (2 * 5)
Everything else above level 1000 should solve a similar quadratic equation, $exp = (5 * $L * $L - 5 * $L) + 500000 * ($L - 1000) for $L. It would look like $L = ((5 + 500000) + sqrt((5 + 500000)^2 - 4 * 5 * (500000 - $exp))) / (2 * 5)

Related

PHP interpreter micro-optimizations in code

By stumbling on this so thread i decided to write similar test in PHP.
My test code is this:
// Slow version
$t1 = microtime(true);
for ($n = 0, $i = 0; $i < 20000000; $i++) {
$n += 2 * ($i * $i);
}
$t2 = microtime(true);
echo "n={$n}\n";
// Optimized version
$t3 = microtime(true);
for ($n = 0, $i = 0; $i < 20000000; $i++) {
$n += $i * $i;
}
$n *= 2;
$t4 = microtime(true);
echo "n={$n}\n";
$speedup = round(100 * (($t2 - $t1) - ($t4 - $t3)) / ($t2 - $t1), 0);
echo "speedup: {$speedup}%\n";
Results
in PHP 2 * ($i * $i) version runs quite similar like 2 * $i * $i,
so PHP interpreter isn't optimizing bytecode as JVM in Java
Even when I optimized code manually - I've got ~ 8% speedup, when
Java's version gets ~ 16% speedup. So PHP version gets about 1/2 speedup factor of that in Java's code.
Rationale for optimization
I will not go into many details, but ratio of multiplications in optimized and un-optimized code is ->
1 summation : 3/4
2 summations: 4/6
3 summations: 5/8
4 summations: 6/10
...
And in general:
where n is number of summations in a loop. To be formula useful to us - we need to calculate limit of it when N approaches infinity (to replicate situation that we do A LOT of summations in a loop). So :
So we get conclusion that in optimized code there must be 50% less multiplications.
Questions
Why PHP interpreter isn't applying code optimization ?
Why PHP speedup factor is just half of that in Java ?
It's time to analyze PHP opcodes which are generated by PHP interpreter. For that you need to install VLD extension and use it from command line to generate opcodes of php script at hand.
Opcode analysis
Seems that $i++ is not the same as ++$i in terms of opcodes and memory usage. Statement $i++; generates opcodes:
POST_INC ~4 !1
FREE ~4
Increases counter by 1 and saves previous value into memory slot #4. Then, because this value is never used - frees it from memory. Question - why do we need to store value if it is never used ?
Seems that indeed there is a loop penalty, so we can gain additional performance by performing loop unrolling.
Optimized test code
Changing POST_INC into ASSIGN_ADD (which don't saves additional info in memory) and performing loop unrolling, gives use such test code :
while (true) {
// Slow version
$t1 = microtime(true);
for ($n = 0, $i = 0; $i < 2000; $i+=10) {
// loop unrolling
$n += 2 * (($i+0) * ($i+0));
$n += 2 * (($i+1) * ($i+1));
$n += 2 * (($i+2) * ($i+2));
$n += 2 * (($i+3) * ($i+3));
$n += 2 * (($i+4) * ($i+4));
$n += 2 * (($i+5) * ($i+5));
$n += 2 * (($i+6) * ($i+6));
$n += 2 * (($i+7) * ($i+7));
$n += 2 * (($i+8) * ($i+8));
$n += 2 * (($i+9) * ($i+9));
}
$t2 = microtime(true);
echo "{$n}\n";
// Optimized version
$t3 = microtime(true);
for ($n = 0, $i = 0; $i < 2000; $i+=10) {
// loop unrolling
$n += ($i+0) * ($i+0);
$n += ($i+1) * ($i+1);
$n += ($i+2) * ($i+2);
$n += ($i+3) * ($i+3);
$n += ($i+4) * ($i+4);
$n += ($i+5) * ($i+5);
$n += ($i+6) * ($i+6);
$n += ($i+7) * ($i+7);
$n += ($i+8) * ($i+8);
$n += ($i+9) * ($i+9);
}
$n *= 2;
$t4 = microtime(true);
echo "{$n}\n";
$speedup = round(100 * (($t2 - $t1) - ($t4 - $t3)) / ($t2 - $t1), 0);
$table[$speedup]++;
echo "****************\n";
foreach ($table as $s => $c) {
if ($s >= 0 && $s <= 20)
echo "$s,$c\n";
}
}
Results
Script aggregates number of times CPU hit into one or other speedup value.
When CPU hits vs Speedup is drawn as a graph, we get such picture:
So it is most likely that script will get 10% speedup. This means that our optimizations resulted in +2% speedup (compared to original scripts 8%).
Expectations
I'm pretty sure that all these things i've done - could be done automatically by a PHP JIT'er. I don't think that it's hard to change automatically a pair of POST_INC/FREE opcodes into one PRE_INC opcode when generating binary executable. Also it's not a miracle that PHP JIT'er could apply loop unrolling. And this is just a start of optimizations !
Hopefully there will be a JIT'er in PHP 8.0

Expanding Round-robin tournament 1v1 to a 1v1v1v1

I am trying to expand and improve the round-robin algorithm from 1v1 group to a 1v1v1v1 group(something like free for all). I've made the function itself to do the schedule, but when I tried to expand it, some teams repetead. For example, I have 16 teams and I want to have 5 rounds, team 1 appears 7 times in 5 rounds and team2 appears 3 times in 5 rounds. I need them to appear 5 times at most.I really can't understand how I can do it. Any advice is welcomed and links.
function make_schedule(array $teams, int $rounds = null, bool $shuffle = true, int $seed = null): array
{
$teamCount = count($teams);
if($teamCount < 4) {
return [];
}
//Account for odd number of teams by adding a bye
if($teamCount % 2 === 1) {
array_push($teams, null);
$teamCount += 1;
}
if($shuffle) {
//Seed shuffle with random_int for better randomness if seed is null
srand($seed ?? random_int(PHP_INT_MIN, PHP_INT_MAX));
shuffle($teams);
} elseif(!is_null($seed)) {
//Generate friendly notice that seed is set but shuffle is set to false
trigger_error('Seed parameter has no effect when shuffle parameter is set to false');
}
$quadTeamCount = $teamCount / 4;
if($rounds === null) {
$rounds = $teamCount - 1;
}
$schedule = [];
for($round = 1; $round <= $rounds; $round += 1) {
$matchupPrev = null;
foreach($teams as $key => $team) {
if($key >= $quadTeamCount ) {
break;
}
$keyCount = $key + $quadTeamCount;
$keyCount2 = $key + $quadTeamCount + 1;
$keyCount3 = $key + $quadTeamCount + 2;
$team1 = $team;
$team2 = $teams[$keyCount];
$team3 = $teams[$keyCount2];
$team4 = $teams[$keyCount3];
//echo "<pre>Round #{$round}: {$team1} - {$team2} - {$team3} - {$team4} == KeyCount: {$keyCount} == KeyCount2: {$keyCount2} == KeyCount3: {$keyCount3}</pre>";
//Home-away swapping
$matchup = $round % 2 === 0 ? [$team1, $team2, $team3, $team4 ] : [$team2, $team1, $team4, $team3];
$schedule[$round][] = $matchup ;
}
rotate($teams);
}
return $schedule;
}
Rotate function:
function rotate(array &$items)
{
$itemCount = count($items);
if($itemCount < 3) {
return;
}
$lastIndex = $itemCount - 1;
/**
* Though not technically part of the round-robin algorithm, odd-even
* factor differentiation included to have intuitive behavior for arrays
* with an odd number of elements
*/
$factor = (int) ($itemCount % 2 === 0 ? $itemCount / 2 : ($itemCount / 2) + 1);
$topRightIndex = $factor - 1;
$topRightItem = $items[$topRightIndex];
$bottomLeftIndex = $factor;
$bottomLeftItem = $items[$bottomLeftIndex];
for($i = $topRightIndex; $i > 0; $i -= 1) {
$items[$i] = $items[$i - 1];
}
for($i = $bottomLeftIndex; $i < $lastIndex; $i += 1) {
$items[$i] = $items[$i + 1];
}
$items[1] = $bottomLeftItem;
$items[$lastIndex] = $topRightItem;
}
For example:
If I set rounds to 5, every team play 5 matches.
Array example Screenshot
Dealing with the 5th round:
Well, after I thought for a bit, maybe there isn't a way for them to play without repeatence, but if it is lowered to minumum, like every team should play 5 times only - this means once a round. That's what I meant. And what I meant under 'they repeat' is that there are like: 16 teams, 5 rounds and some teams are going like 7 times for all these rounds and other teams are going 3 times for these 5 rounds. I want to avoid this and to make every team play 5 rounds at most.
Your foreach() with the selection of the other 3 teams is wrong. One of them have to make steps with a multiple of 4. If you don't, you will select the teams at the beginning more than one and don't select the teams at the end of the array at all. This will result in wrong team matchups like this (teams are letters here):
abcd
bcde
cdef
defg
And then your break; hits.
Instead it should look something like this:
for ($i=0; $i<4; $i++) {
$matchup = array();
for ($j=0; $j<4; $j++) {
$matchup[] = $teams[4*$i+$j];
}
$schedule[$round][] = $matchup ;
}
This way you get the following pairing (again, using letters as teams):
abcd
efgh
ijkl
mnop
This algorithm will split the team list in four groups:
abcd|efgh|ijkl|mnop
Keep in mind that depending on the shuffling of the $teams array for the next round you might get the same opponent twice.
adei|klnf|gjmc|pobh
Here the teams ad, kl and op will face again.

Regular savings account interest calculation php

OK, I've had the same problem for a few weeks now and cant perfect it.
Aim
To build a regular deposit savings account system where it prints out the total balance at the current time.
Problem
The current equation I have:
If the interest is 6% with the user paying in 200 a month with compound being each month the balance would be after 6 months 1,220.61
I am getting 1217.13
I have tested different lengths of time and many different online calculators, my calculation is always less.
My code
<h2>Total Balance To Date</h2>
<?php
$p = 0; // Starting amount
$i = 0.06; // Interest rate
$c = 12; // compound frequency set to monthly
$n = 6/12; // Current time invested set to 6 months
$r = 200; // Monthly investment is 200
$x = $i / $c;
$y = pow((1 + $x), ($n * $c));
if($p!=0)
{
$vf = $p * $y + ($r * ($y - 1) / $x);
}
else
{
$vf = 1 + $y + ($r * ($y - 1) / $x);
}
?>
<p>£<?php echo round($vf, 2, PHP_ROUND_HALF_UP); ?></p> // Comes out at 1217.13
LINK to sandbox https://3v4l.org/9X7OH
Setting
q = pow(1.06 , 1.0/12) = 1.0048675505653430
and computing
200*(q+q^2+q^3+q^4+q^5+q^6) = 200*q*(q^6-1)/(q-1)
gives the result
1220.61037336530790
which is obviously what the online calculators worked with. It is slightly wrong, as for the nominal interest rate, the monthly compound factor should be
q = 1 + 0.06/12 = 1.005
resulting in a balance after 6 months of
1221.1758776293781
As you see, you got the formula almost right, it should be
$vf = $p * $y + ($r * (1 + $x) * ($y - 1) / $x);
since the rate is deposited at the start of the month, so the first rate gets compounded as r*(1+x)^6 and the last rate as r*(1+x). However, the second formula in the else branch does not make sense at all.

php, how to iterate through numbers and add as we go along and calculate new numbers

I wrote a simple code to calculate a math equation, but I would like to iterate through numbers from $start to $end. I am making a game, and this page will calculate the amount of experience it takes to reach the next level and insert it into the database. What would be the best way to iterate from $start to $end and calculate the amount of exp needed for that level?
Code:
<?php
$start = 1;
$end = 100;
$level = $start++;
$l = $level - 1;
$exp = ((40*($l * $l)) + (360 * $l));
?>
As it sits right now it calculates the first level but i cannot for the life of me figure out how to make it go through til it reaches $end.
$exp = 0;
for($level = $start; $level <= $end; $level++){
$exp += 40 * $l ** 2 + 360 * $l;
}
Actually, we can use mathematics to make this faster by generalizing the experience level required. Since your experience function is the summation of a quadratic function:
f(n)
= S[1 100] 40n^2 + 360n
= 40n (n + 1) (2n + 1) / 6 + 360n (n + 1) / 2
In PHP:
40 * $level * ($level + 1) * (2 * $level + 1) / 6 + 360 * $level * ($level + 1) / 2
Or simplify it further if you like.
This is definitely faster than calculating a loop 100 times.
If $start is not 1, simply use f(end) - f(start - 1).
You have to calculate the needed xp for every single level, so you should put the calculation code inside a loop that starts at your lowest level and proceeds until it hits the top limit / end level. You can choose from two different loop types in PHP, the for-loop and the while-loop.
Personally, I would choose the while-loop for this specific "problem", but that is a thing everyone has to decide on his own. The code for your calculator would look like this:
// Create an extra variable to store the level for which you are currently calculating the needed xp
$i = $start;
// The while-loop (do this code until the current level hits the max level as specified)
while($i <= end) {
// use $i to calculate your exp, its the current level
// Insert code here...
// then add 1 to $i and do the same again (repeat the code inside loop)
$i++;
}
Here are some links to the documentation of php:
while-loop
for-loop

Efficient PHP algorithm to multiply two 3-digit numbers in all configurations

I'm trying to get the results of multiplication of all combinations of two
3-digit numbers.
I realized that each time I execute a script that does a lot fewer operations than the number of operations in above task, the execution time always exceeds 30 second, and the script halts:
$x = 100;
$y = 100;
$z = $x * $y;
while($this->isMagicNumber($z) == false) {
if ($x + 1 < 1000) {
$x++;
} elseif ($x = 999) {
if($y + 1 < 1000) {
$y++;
}
}
$z = $x * $y;
}
This algorithm is by no means efficient, and it does not check all multiplication combinations, and it already takes too long to execute.
What would be the way to find the results of all possible multiplications of two 3-digit numbers?
Edit:
The isMagicNumber() method might, in fact, be the case why the script takes so long to execute, but, on the other hand, it doesn't seem excessively complicated:
private function isMagicNumber($number)
{
if ($number === strrev($number)) {
echo $number . ' is a Palindrome';
return true;
}
return false;
}
Edit 2
Just a reminder:
Down-voting should be reserved for extreme cases. It's not meant as a
substitute for communication and editing.
You may use this. This avoids multiplying the same two numbers twice as suggested by #KIKO Software:
$min = 100;
$max = 999;
for ($x = $min; $x <= $max; $x++) {
for ($y = $x; $y <= $max; $y++) {
$p = $x * $y;
echo "$x * $y = $p", PHP_EOL;
}
}
Output:
100 * 100 = 10000
100 * 101 = 10100
100 * 102 = 10200
...
997 * 997 = 994009
997 * 998 = 995006
997 * 999 = 996003
998 * 998 = 996004
998 * 999 = 997002
999 * 999 = 998001
UPDATE - Looking at your newly posted isMagicNumber method
Your $number === strrev($number) expression will actually never return true. As strrev returns a string and $number is a number, your identical comparison will fail. Currently you are experiencing an endless loop, that surely would take more than 30 seconds to complete. Use == instead.

Categories