Efficient Leveling System - php

I'm making a leveling system based on experience you have on the site. I already have all the experience stuff figured out, and how I want to do the leveling, but I need a more efficient way to do it. I know this would probably would be achieved using an array, but I don't really know how to go about doing that. Enough blabbering, though, this is what I'm trying to do...
Level 1 will be anything under 150 experience
Then I'm going to multiply that by 1.5, so
Level 2 will be anything under 225
Level 3 will be anything under 337.5 and so on. This is the inefficient way that I was going to do.
if($xp < 150){
$level = "1";
}elseif($xp < 225){
$level = "2";
}elseif($xp < 337.5){
$level = "3";
}
I could use a variable for the number and multiple by 1.5 ($number*1.5), but like I said before I don't really know how that'd work.
*Some more info..
I have a session file included on every page and I have queries that would check every time there is new experience earned and this code would be used to update the level on the database automatically.

Try
$level = min(max((int)(log($xp / 100, 1.5) + 1), 1), $maxLevel);
That takes the logarithm base 1.5 of xp/100 (i.e. the number of times you'd have to multiply 100 by to get $xp), then adds one (since log($x, 1.5) is less than one if $x is less than 1.5). The min(max(..., minLevel), maxLevel) construct lets you clamp the level to lie between 1 and $maxLevel, also avoiding any issues with negative levels (if $xp is sufficiently less than 150).

Here's how I did my level system. It's a little more advanced featuring skill points..
<?php
$level_up = ($level + 1);
if ($exp >= $max_exp)
{
$sql = "UPDATE users SET level=(level + 1) , max_exp=(exp * 1.05) , skill_points=(skill_points + 3) WHERE id='".$id."' LIMIT 1";
$res = mysql_query($sql);
if ($exp >= $max_exp)
echo '<div class="Leveled">' . 'You sucessfully leveled up to ' . $level_up . '!' . ' As a reward you were given 3 skill points!' . '</div>';
}
else
{
}
?>

I'd agree. either objects or arrays would be best.
Something like:
$array = array(
array(
'xp_required' => 150,
'level' => 1
),
array(
'xp_required' => 225,
'level' => 2
),
array(
'xp_required' => 337.5,
'level' => 3
)
);
$current_xp = 155;
foreach( $array as $reqs ){
if( $current_xp > $reqs['xp_required'] ){
$level = $reqs['level'];
}
}
echo $level';

well at the moment once you grab the users exp you can have that code block as a function to constantly check for variations in exp levels, but use a ranged statement
if($xp < 150){
$level = "1";
}elseif($xp > 150 && $xp < 225 ){
$level = "2";
}elseif($xp > 225 && $xp < 337.5){
$level = "3";
}

Related

PHP - Avoid typing much lines [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
So I want to avoid typing 9999 possible outcomes doing something like this:
if($experience==50) {echo 'level 2'; }
if($experience==300) {echo 'level 3'; }
if($experience==700) {echo 'level 4'; }
if($experience==1000) {echo 'level 5'; }
Is there a way to do it? So yes i hope somebody could help me!
EDIT: The max level is 99 so i have also to check
if($level == 1 && $experience >= 50) { echo 'level 2';}
Short answer: You can't.
Long answer: You must create the experience map somewhere. In code or in a settings file/database. The system can't magical know how to map the experience to levels without you telling it how to.
What you can do however is to create a formula on how the levels are linked to experiences then it's just a small calculation and you are done.
Agreed to #Ztripez HellströmYou's answer. You need some kind of math formula, I would do something like:
$level = 1;
// level up after every 50 * $currentLevel xp gained
if ($experience >= 50 && $experience % (50*$level) == 0 && $level <= 99) {
$level++;
}
echo 'Level: ' . $level;
So above would result in the following:
$level = 1;
$experience = 50;
After if condition, $level = 2
$level = 2;
$experience = 95;
After if condition, $level = 2 //not increased
$level = 2;
$experience = 100;
After if condition, 100 % (50*2) == 0 ? true - $level = 3
etc...
Note: make sure to increase XP points by multiples of 5 so you dont go over the threshold. Or increase it by multiples of 50 or multiples of 10.
Typically, EXP-level thresholds are calculated on some kind of formula. Often this formula is exponential in nature, so you might have something like:
$base = 50;
$growth = 1.2;
$maxlevel = 100;
$levelthresholds = array();
for( $i=0; $i<$maxlevel; $i++) $levelthresholds[$i] = $base*$i*pow($growth,$i-1);
// the above is a formula I've used in many places, it works quite well
The base rate would be your EXP for Level 2, then the growth is how much you multiply the base rate by to get the next level. With 1.2, you'd get:
Level 1: 0
Level 2: 50
Level 3: 120
Level 4: 216
Level 5: 345
...
You can adapt your $base and $growth rates as desired. You can also use something like using a lower $base, rounding the result and then multiplying by 100 to get nice, rounded numbers.
The point here is to determine a formula for EXP, as this greatly reduces the amount of code you have to write. Once done you can do this:
for( $level = 0; $level < $maxlevel; $level++) {
if( $levelthresholds[$level] > $experience) break;
}
$level++; // result should be 1-based

Defining percentage for random number

My rand(0,1) php function returns me the 0 and 1 randomly when I call it.
Can I define something in php, so that it makes 30% numbers will be 0 and 70% numbers will be 1 for the random calls? Does php have any built in function for this?
Sure.
$rand = (float)rand()/(float)getrandmax();
if ($rand < 0.3)
$result = 0;
else
$result = 1;
You can deal with arbitrary results and weights, too.
$weights = array(0 => 0.3, 1 => 0.2, 2 => 0.5);
$rand = (float)rand()/(float)getrandmax();
foreach ($weights as $value => $weight) {
if ($rand < $weight) {
$result = $value;
break;
}
$rand -= $weight;
}
You can do something like this:
$rand = (rand(0,9) > 6 ? 1 : 0)
rand(0,9) will produce a random number between 0 and 9, and whenever that randomly generated number is greater than 6 (which should be nearly 70% time), it will give you 1 otherwise 0...
Obviously, it seems to be the easiest solution to me, but definitely, it wont give you 1 exactly 70% times, but should be quite near to do that, if done correctly.
But, I doubt that any solution based on rand will give you 1 exactly 70% times...
Generate a new random value between 1 and 100. If the value falls below 30, then use 0, and 1 otherwise:
$probability = rand(1, 100);
if ($probability < 30) {
echo 0;
} else {
echo 1;
}
To test this theory, consider the following loop:
$arr = array();
for ($i=0; $i < 10000; $i++) {
$rand = rand(0, 1);
$probability = rand(1, 100);
if ($probability < 30) {
$arr[] = 0;
} else {
$arr[] = 1;
}
}
$c = array_count_values($arr);
echo "0 = " . $c['0'] / 10000 * 100;
echo "1 = " . $c['1'] / 10000 * 100;
Output:
0 = 29.33
1 = 70.67
Create an array with 70% 1 and 30% 0s. Then random sort it. Then start picking numbers from the beginning of the array to the end :)
$num_array = array();
for($i = 0; $i < 3; $i++) $num_array[$i] = 0;
for($i = 0; $i < 7; $i++) $num_array[$i] = 1;
shuffle($num_array);
Pros:
You'll get exactly 30% 0 and 70% 1 for any such array.
Cons: Might take longer computation time than a rand() only solution to create the initial array.
I searched for an answer to my question and this was the topic I found.
But it didn't answered my question, so I had to figure it out myself, and I did :).
I figured out that maybe this will help someone else as well.
It's regarding what you asked, but for more usage.
Basically, I use it as a "power" calculator for a random generated item (let's say a weapon). The item has a "min power" and a "max power" value in the db. And I wanted to have 80% chances to have the "power" value closer to the lower 80% of the max possible power for the item, and 20% for the highest 20% possible max power (that are stored in the db).
So, to do this I did the following:
$min = 1; // this value is normally taken from the db
$max = 30; // this value is normally taken from the db
$total_possibilities = ($max - $min) + 1;
$rand = random_int(1, 100);
if ($rand <= 80) { // 80% chances
$new_max = $max - ($total_possibilities * 0.20); // remove 20% from the max value, so you can get a number only from the lowest 80%
$new_rand = random_int($min, $new_max);
} elseif ($rand <= 100) { // 20% chances
$new_min = $min + ($total_possibilities * 0.80); // add 80% for the min value, so you can get a number only from the highest 20%
$new_rand = random_int($new_min, $max);
}
echo $new_rand; // this will be the final item power
The only problem you can have, is if the initial $min and $max variables are the same (or obviously, if the $max is bigger than the $min). This will throw an error since the random works like this ($min, $max), not the other way around.
This code can be very easily changed to have more percentages for different purposes, instead of 80% and 20% to put 40%, 40% and 20% (or whatever you need). I think the code is pretty much easy to read and understand.
Sorry if this is not helpful, but I hope it is :).
It can't do any harm either way ;).

Calculating Highly divisible triangular number with PHP

I am trying to resolve project euler problem no 12 with PHP but it is taking too much time to process. I came across with similar processing problems of PHP while solving previous problems and I had to solve them in C++ just to test whether my approach is correct or not.
I want to know whether there is something wrong with my approach or somehow I can do something to make processing fast. Here is the code of my solution which works well for the triangle having 360 divisors. The link of problem is http://projecteuler.net/problem=12 and here is my code
<?php
set_time_limit(0);
ini_set('memory_limit', '1G');
$triangles = array(0);
$count = 1;
$numOfDivisiors = 0;
$lastTriangle = 0;
while($numOfDivisiors < 500){
$triangle = (int) $lastTriangle + (int) $count;
$factors = getFactors($triangle);
//$triangles[] = array('triangle' => $triangle, 'factors' => $factors, 'factorsCount' => count($factors));
$triangles[] = array('triangle' => $triangle, 'factorsCount' => count($factors));
$lastTriangle = $triangle;
$numOfDivisiors = count($factors);
$count++;
//echo '<pre>'; print_r(array('triangle' => $triangle, 'factorsCount' => count($factors), 'count' => $count)); echo '</pre>';
}
echo $numOfDivisiors; exit;
/**
for($i = 0 ; $i < 10 ; $i++){
}
**/
//echo '<pre>'; print_r($triangles); exit;
function getFactors($number){
$factors = array();
$break = false;
$count = 1;
while($break != true){
$remainder = $number % $count;
if($remainder == 0){
$factors[] = $count;
}
//echo $count." ".$number; exit;
if($count == $number){
$break = true;
}
$count++;
}
return $factors;
}
?>
use some maths.
triangle numbers can be generated by
n(n+1) /2
and that if you can find the prime factors, Adding 1 to their powers and multiplying together gives the number of divisors.
My PHP solution takes around 4 seconds and i think i can speed that up also
There are several ways to speed up your solution. The first one I'd point you at is the following:
if a * b = c then both a and b are factors of c
One of a and b will be <= to the square root of c
this should help speed up your solution

Check through an array and identify matching numbers

I am working on a scratch card game where by a user purchases a scratch card from me/my store and can then redeem said card by scratching off numbers and matching 3 pictures to win a prize
Like physical scratch cards I want the prizes to be pre configured so that I know when a prize will be won, but obviously the users dont, this is so I can limit the prizes, so it is not true random, but will be random in terms of who wins the prize..
I know how I will setup the prizes but I need to check each data value in an array to ensure that there is not 3 matching numbers, unless they have actually won..
So far I have this (I will neaten it up and shorten the code down soon)
$numbers = array(
0 => rand(0,9),
1 => rand(0,9),
2 => rand(0,9),
3 => rand(0,9),
4 => rand(0,9),
5 => rand(0,9),
6 => rand(0,9),
7 => rand(0,9),
8 => rand(0,9)
);
Which just randomly generates 9 numbers and
echo "<table>";
$i=0;
for($x=0;$x<3;$x++){
echo "<tr>";
for($y=0;$y<3;$y++){
echo "<td>" . $numbers[$i] . "</td>";
$i++;
}
echo "</tr>";
}
Sorts them into a 3x3 table
I know there is the command in_array() which will sort through the array looking for certain values but I dont want to run an endless loop checking each value to see if I get a match, because it's labor intensive and would be messy as well.
I'm looking for a suggestion anyone might have so I can sort through the array and see if any 3 numbers exist, if they do, and they're not suppost to (ie the user hasn't actually won a prize) then one should be changed to a new number.
So thank you in advance for your help guys I look forward to your suggestions
Luke
**** EDIT **
+More info
To pick the winners I will use a database query such as this
$stock = $conn -> query("SELECT DISTINCT * FROM codes WHERE Available = 1 AND 0 = FLOOR(RAND()*Chance) ORDER BY RAND()");
Each code will have a set chance to win and if it matches up it will be shown, depending on the code won a certain prize will show
$stock = $conn -> query("SELECT DISTINCT * FROM codes WHERE Available = 1 AND Win = 1);
'Win' will be a table in the database which each time someone plays the game it goes down by 1
So each code will have the table Win which when it hits 1 it will pop out the in the next game
These are two ways I can think of doing it which I think will work, both ways roughly allow me to control when and if someone wins but the second solution is obviously a more accurate way of doing it
how about not changing a number if a triple one occurs, but preventing a triple one occurs.
function createScratchRange()
{
$result = array();
$options = array_merge(range(0,9), range(0,9));
for ($i = 0; $i < 9; $i++)
{
$key = array_rand($options, 1);
$result[] = $options[$key];
unset($options[$key]);
}
return $result;
}
and you might want to be able to create winners:
function createWinner()
{
$winningNumber = rand(0,9);
$result = array();
$possibleNumbers = range(0,9);
unset($possibleNumbers[$winningNumber]);
$options = array_merge($possibleNumbers,$possibleNumbers);
for ($i = 0; $i < 9; $i++)
{
$key = array_rand($options, 1);
$result[] = $options[$key];
unset($options[$key]);
}
// looks random now.. but no winning numbers... Lets just stick them in there.
$keys = array_rand($result, 3);
foreach($keys as $key)
{
$result[$key] = $winningNumber;
}
return $result;
}
and for testing:
var_dump(createScratchRange()); // this will never give 3 identical numbers in the array.
var_dump(createWinner()); // this will ALWAYS give 3 identical numbers, but never 2 sets of 3 identical numbers

Prioritized Random Selection

I need to design an algorithm that displays ads on a website based on imprints. i record each imprint in DB. Cannot make use of mysql rand() limit 1 in my query as it will be uncontrolled query. i have to make sure that if an ad is selected its not only just an ad with lowest imprints but there is some randomness to it. Also New Ads get a biased selection some way.
In order to achieve this i have managed to write a function but somewhere I am not satisfied with it. Please go through it and let me know if it could done any better.
function getRandomAd($result){
while($row = mysql_fetch_assoc($result)){
$rows[] = $row;
}
$filteredRow = array();
foreach($rows as $row){
if($row['imprints'] == 0 ){
$row['piority'] = 10;
$filteredRow[] = $row;
}
else if($row['imprints'] >= 0 and $row['imprints'] <= 5){
$row['piority'] = 8;
$filteredRow[] = $row;
}
else if($row['imprints'] >= 5 and $row['imprints'] <= 15){
$row['piority'] = 6;
$filteredRow[] = $row;
}
else if($row['imprints'] >= 15 and $row['imprints'] <= 30){
$row['piority'] = 4;
$filteredRow[] = $row;
}
else if($row['imprints'] >= 30 and $row['imprints'] <= 60) {
$row['piority'] = 2;
$filteredRow[] = $row;
}
else{
$row['piority'] = 0;
$filteredRow[] = $row;
}
foreach($filteredRow as $row){
if($row['piority'] == 10 or $row['piority'] == 8) $high[] = $row;
if($row['piority'] == 6 or $row['piority'] == 4) $medium[] = $row;
if($row['piority'] == 2 or $row['piority'] == 0) $low[] = $row;
}
$countHigh = count($high);
$countMed = count($medium);
$countLow = count($low);
if($countHigh > $countLow and $countHigh > $countMed)$rowReturned = $high;
if($countMed > $countLow and $countMed > $countHigh) $rowReturned = $medium;
if($countLow > $countMed and $countLow > $countHigh) $rowReturned = $low;
}
return $rowReturned[array_rand($rowReturned,1)];
}
Returning all the rows and using PHP to sort and determine which row to spit out is terribly inefficient. This would be better handled on the database end than in PHP.
Also, your current set of if statements overlap with the >= operators.
Finally, I know you didn't do this but just a general note: you should almost never use ORDER BY RAND() with MySQL because the performance is terrible. This is because when you use a function return value to order results MySQL can't use an index for the order operation.
Here's a better solution ...
Generate a weighted random number in PHP that we can use as a condition for our query
Write a query that will return only one result based on the weighted random number
So, lets generate a random integer weighted towards the lower end of the imprint spectrum:
$max = 60;
$random = floor($max - mt_rand(mt_rand(1, $max), $max));
And write a query that uses the weighted random number to pick a single result from the table:
$query = '
SELECT id, advert_txt
FROM table
WHERE imprints >= '.$random.'
ORDER BY imprints ASC
LIMIT 1
';
Note that you would normally want to escape a PHP variable in a query like this (or preferably use prepared statements with PDO), but since we know the variable is an integer in this case it's okay.
This solution will appropriately weight returned results based on their respective imprint counts while maintaining MySQL's ability to use indexes to select the record, which is of paramount importance if you have a large number of rows in the table.
UPDATE
I would add that you could tweak the random number generation to your own needs. With this particular solution you'll never get back a row with >= 60 imprints. The solution remains sound though, because it's trivial to tweak how you determine the condition for the query. For example, maybe you would first run a query to get the MAX(imprints) from the database and use that value for your $max when determining the random number.
And second, just to demonstrate the behavior of the random number generation specified above, if you run it 10,000 times using a $max = 10 you'll get results similar to this:
0: 2966
1: 1965
2: 1420
3: 1101
4: 820
5: 621
6: 457
7: 333
8: 210
9: 107
UPDATE 2
In response to your comment for further clarification ...
In my original update I'm trying to say that if you just hard-code in the max value as 60, the original code will never return a row where imprints >= 60. This means that if you just use that code on it's own, it will work fine for a while but eventually (assuming you increment the imprints column each time you display an advertisement) all your records will have been imprinted 60+ times and you won't get any results back.
So to avoid such an issue the entire process would be something like this:
Query the table once to get the highest imprints column value to use for your maximum random value range like so: SELECT MAX(imprints) AS max FROM my_table;
Use the max value you retrieved in step one in the $max variable when generating your random weighted number.
Update the table by incrementing the imprints column up for the row you retrieved in step 2.
function getRandomAd($result) {
$imprint_weights = array(
array(
'min' => 0,
'max' => 0,
'weight' => 10
),
array(
'min' => 1,
'max' => 5,
'weight' => 8
),
array(
'min' => 6,
'max' => 15,
'weight' => 6
),
array(
'min' => 16,
'max' => 30,
'weight' => 4
),
array(
'min' => 31,
'max' => 60,
'weight' => 2
)
);
$weighted = array();
foreach ($result as $row) {
$imprints = $row['imprints'];
$imprint_weight = 1;
foreach ($imprint_weights as $match) {
if ($imprints >= $match['min'] && $imprints <= $match['max']) {
$imprint_weight = $match['weight'];
}
}
$weighted = array_merge($weighted, array_fill(0, $imprint_weight, $row));
}
return $weighted[array_rand($weighted)];
}
$data = array();
for ($x = 1; $x <= 100; $x++) {
$data[] = array(
'id' => $x,
'imprints' => rand(0, 65)
);
}
print_r(getRandomAd($data));
This provides an alternative way of doing what you want. The benifit here being that you can more easily change how an amount of imprints affect the weight of an ad, and also easily add more parameters (like age) to influence the selection of an ad.

Categories