I have a question that I have been stuck on for several hours now. I have played around with numerous types of for() and while() loops. I put them in different locations with different variables and ran different things, nothing worked..
My question:
Why is my program giving all users below the first one the same level? You can clearly see in the picture that Nicolas has much more XP. (5,000 xp is level 20, and if "Nic5" was first in the database then it would change the "Skill Level" to 20.
I know that the returned variable $lvl isn't changing for each player that loads and this is why each player is getting the first player's level.
Can anybody help me with this please?
Notes:
0 = a column that holds experience for a players skill level.
class calculatelevel:
class calculatelevel {
function level($skillnum)
{
$host = "*";
$user = "*";
$pass = "*";
$db = "*";
$con = new mysqli($host, $user, $pass, $db) or die($con->error);
$res = $con->query("SELECT `0` FROM hiscores");
$max = 99;
while($row = $res->fetch_assoc()) {
$xp = $row['0'];
// Find the appropriate level
for ($lvl = 1; $lvl < $max; $lvl++) //this for loop runs 99 times
{
if ($xp < $this->experience($lvl))//if players xp in skill is less than experience(level 1-99)
{
// Level found
$lvl -= 1;
break;
}
}
}
return $lvl;
}
public function experience($lvl)
{
$xp = 0;
for($x = 1; $x < $lvl; $x++)
{
$xp += floor($x + 300 * pow(2, ($x / 7)));
}
return floor($xp / 4);
}
}
Method that writes database information to page.
if($res->num_rows > 0) {
echo "<table>
<tr>
<td>Rank</td>
<td>Username</td>
<td>Skill Level</td>
<td>Total Exp</td> </tr>";
while($row = $res->fetch_assoc()) {
echo 'ran';
$calc = new calculatelevel();
$level = $calc->level(0);
echo '<tr>
<td>'.($count+1).'</td>
<td>'. htmlspecialchars($row['username']) .'</td>
<td>'.number_format($level).'</td>
<td>'.number_format($row['0']).'</td>
</tr>';
$count++;
}
}
Your while loop and for loop are structured incorrectly. You are looking at only the first row and returning the level for that row every time. You break the for loop when you find the level for that row's player, then immediately return that level. Result: it looks like everyone has the same level.
EDIT: Okay, here are a few more thoughts.
First, a column named 0 is asking for trouble, as Mike W pointed out. You say 0 is a table, but if that's the case, your SELECT statement doesn't make sense. The first thing I would try is changing the column name to something that isn't a number, like xp.
Second, you really should make only one database connection and use it throughout the entire request, if possible. Opening a new connection each time a particular function runs will tie up a server quickly.
Third, the obvious problem in your current code is this:
function level($skillnum) {
// other code here....
// Okay, you load a row's data into $row, with the idea that you will repeat this.
while($row = $res->fetch_assoc()) {
// $xp is set to the experience points for the user you just loaded
$xp = $row['0'];
// You now look at each level, starting at 1, to see if the
// user's xp is greater than the cutoff for that level
for ($lvl = 1; $lvl < $max; $lvl++) //this for loop runs 99 times
{
// If the user's xp is less than the cutoff...
if ($xp < $this->experience($lvl))//if players xp in skill is less than experience(level 1-99)
{
// ... then you go back down one level...
// Level found
$lvl -= 1;
// ... and quit the for loop!
break;
}
}
// okay, you're out of the for loop, so you go back to the while loop...
// a new row is loaded...
// and $xp and $lvl are both overwritten with that user's values
}
// So, the while loop has run once for each player...
// ... but you are only returning one player's level!
return $lvl;
}
Also, you have defined calculatelevel::level to require a parameter $skillnum, but you never use it in the code posted here.
I suspect there is another glitch in your real code causing it to return the first player's level, rather than the last player's level. It could be a problem with the 0 column name; that really should change.
Related
I created a function inside a longer plug-in for shopware, which is supposed to create a random number for every row in the database that has a "NULL" value in the "vouchercode" column. Right now I replaced the for-loop condition with a fixed number, because I wanted to make sure the problem doesn't occur because of the for-loop condition.
The problem is, that the for-loop just has effect on the database once.
For instance: I have this table 's_plugin_tnev'. Inside of that table are 6 rows. 4 of these have "NULL" as value inside of the vouchercode column.
So as far as I understand my code. It should loop 5 times through the same table and every time update one of those "NULL"-value columns, meanwhile after every loop one of those "NULL"-value columns should be filled with a random number and therefore no longer be SELECTed nor UPDATEd by this for-loop.
Though as mentioned earlier this doesn't happen. The for loop just works once apparently.
Here is my code snippet:
public function generateCode()
{
//Repeat action 5 times
for($i = 0; $i <= 4; $i++)
{
$rand = 0;
//Creates 16 times a number and add it to the var
for ($i = 0; $i<15; $i++)
{
$rand .= mt_rand(0,9);
}
//On Checkoutcomplete add $rand to database table
$addInt = "UPDATE s_plugin_tnev SET vouchercode = $rand
WHERE vouchercode IS NULL
LIMIT 1";
$connect = Shopware()->Db()->query($addInt);
}
}
As you can see I use the DBAL Framework, because this is the best supported way by Shopware.
My idea would be that the mistake has something to do with the $connect variable or that DBAL is not communicating fast enough with the Database.
Maybe someone has more experience with DBAL and could help me out.
Thanks in advance,
Max K
You have two for loops with $i, so on your first iteration, at the end the $i value is 15 and the first loop is executed only once.
Try this instead :
public function generateCode()
{
//Repeat action 5 times
for($i = 0; $i <= 4; $i++)
{
$rand = 0;
//Creates 16 times a number and add it to the var
for ($j = 0; $j<15; $j++) // $j NOT $i <---
{
$rand .= mt_rand(0,9);
}
//On Checkoutcomplete add $rand to database table
$addInt = "UPDATE s_plugin_tnev SET vouchercode = $rand
WHERE vouchercode IS NULL
LIMIT 1";
$connect = Shopware()->Db()->query($addInt);
}
}
$st = $this->db->prepare("SELECT * FROM invoices WHERE group_id=?");
$st->execute(array($id));
if($st->rowCount() >= 1){
foreach ($st as $row) {
$counter = $row["paymentAmount"];
$start = 1;
for($start; $start < $st->rowCount(); $start++) {
$counter = $counter + $row["paymentAmount"];
}
}
It actually print out $row["paymentAmount"] + $row["paymentAmount"] and so on, depending on how many $row["paymentAmount"] there is. But the problem is that the last output from $row["paymentAmount"] is 2500.
There is:
10000
10000
2500
And the result is: 7500
I want it to be: 22500
And if the last result is 3000 it shall be 23000. So what I simply need is this code to take every row from the database, just not the latest one.
Edit: I want it outside of the SQL query
You don't need PHP logic for something like this. The functionality is built right into SQL.
SELECT SUM(paymentAmount) FROM invoices WHERE group_id=?
You should let your database handle the sum unless you have a legitimate reason why it needs to be handled in PHP. The database is more efficient with this type of operation and you avoid a loop in PHP.
SELECT SUM(paymentAmount) AS TotalPaymentAmount FROM invoices WHERE group_id = ?
You can then change your PHP to return just one row:
$row = $st->fetch();
echo $row["TotalPaymentAmount"];
If you need to do this calculation outside of SQL, just change your loop:
if($st->rowCount() >= 1){
//init the counter to 0 before you loop through your rows
$counter = 0;
//the foreach will iterate over your result set and add the paymentAmount to $counter.
foreach ($st as $row) {
$counter += $row["paymentAmount"];
}
//echo results outside of the loop
echo $counter;
}
If you need to code this outside SQL on purpose (e.g. because you need to do further processing for each row), then I'd code this as follows:
if ($st->rowCount() >= 1) {
$counter = 0;
foreach ($st as $row) {
$counter += $row["paymentAmount"];
}
}
On the page I'm creating there is graphic representation of pigeon-holes with five compartments for new comments. If there is new unread comment it should show up as graphic in one random compartment. But it won't happen if all of the 5 are
already occupied. So if someone writes new comment I like the code to check for if there is already five of them taken, and if not, than to randomly occupied one of the remaining ones. So "new_c" stands for number of
unread (new) comments, and c1-c5 stands for compartments. Value 0 means empty compartment for each "c". I was trying to make code that would first separate new_c from rest of the array after knowing the number is smaller than 5,
so I'm only working with 5 compartments. And than to determine which one is/are empty. And then randomly choose one empty to occupy. It's not really working as it is I probably am not using that array_keys properly as c2 is changed to some other value than 0 but still is being echoed, there is probably also a better/more efficient way to achieve that. I will really appreciate some input.
<?php
$tucQuery = "SELECT new_c,c1,c2,c3,c4,c5 FROM users WHERE id = '{$author_id}' LIMIT 1";
$result_tuc = mysqli_query($connection, $tucQuery);
$tucArray = mysqli_fetch_assoc($result_tuc);
mysqli_free_result($result_tuc);
$new_c = $tucArray[0];
if($new_c < 5){
$new_array = array_keys((array_slice($tucArray,1)), 0);
$rand_zero = array_rand($new_array, 1);
echo $rand_zero + 1;
}
?>
Bellow code works but it doesn't seem to be efficient and there is probably a better way.
if($new_c < 5){
$empties = array();
if($tucArray['c1'] == 0){
$empties[] = 1;
}
if($tucArray['c2'] == 0){
$empties[] = 2;
}
if($tucArray['c3'] == 0){
$empties[] = 3;
}
if($tucArray['c4'] == 0){
$empties[] = 4;
}
if($tucArray['c5'] == 0){
$empties[] = 5;
}
print_r($empties);
$rand_zero = array_rand((array_flip($empties)), 1);
echo $rand_zero;
}
I've tried looking for a solution in other questions asked before (as always), but I can't seem to wrap my head around this.
See, I want to get a number of unique IDs (in random order) from one table and store them in an array without echoing them. Then I want to use that array variable in a loop, so that I can increment the key with every pass, and set another variable to that array variable. Confusing? I think looking at the code will make it more clear.
The problem is I can't seem to store the values that I've queried into an array for later use in the code. I pasted the pertinent part of the code with my spots of trouble indicated by comment /* */ tags.
Any help is appreciated.
<?php
include ('parse_functions.php');
if ($fetch['use_rand']=='yes')
{ $loop = 5;
$concept = $fetch['concept'];
$countRandom = "SELECT exID FROM examples WHERE concept='$concept' ORDER BY RAND()";
$askForRandom = mysql_query($countRandom) or die(mysql_error());
/* HERE I NEED TO STORE RANDOM KEYS (exID) INTO AN ARRAY */ }
else
{ if (!empty($fetch['ex5'])) { $loop = 5; }
elseif (!empty($fetch['ex4'])) { $loop = 4; }
elseif (!empty($fetch['ex3'])) { $loop = 3; }
elseif (!empty($fetch['ex2'])) { $loop = 2; }
elseif (!empty($fetch['ex1'])) { $loop = 1; }
else { $loop = 0; }
}
if ($loop!==0)
{
echo '<div id="examples">' . "\n";
echo '<table class="showExample" cellspacing="0" cellpadding="0" border="0" align="center">' . "\n";
$turns = 1;
do {
if ($fetch['use_rand']=='no')
{ $exID = $fetch['ex'.$turns.'']; }
else
{ $exID = /* THIS IS WHERE I WILL USE "RANDOM VARIABLE" */; }
$askExamples = "SELECT * FROM examples WHERE exID='$exID'";
$getExamples = mysql_query($askExamples) or die(mysql_error());
$sortExamples = mysql_fetch_assoc($getExamples);
echo '<tr>' . "\n";
// ...and so on
If all you need to do is store the info in an array, here's an easy way about it.
/* code
to open
db here
*/
/* assuming you have a value for $concept */
$countRandom = "SELECT exID FROM examples WHERE concept='$concept' ORDER BY RAND()";
$askForRandom = mysql_query($countRandom) or die(mysql_error());
$Element = 0; //Array elements start at ZERO. So this is to intialise it.
while ($Fields = mysql_fetch_array($askforRandom)) //As long as there are records, get them.
{
//Records are retrieved one at a time. So store each one's exID in the array element
$Data[$Element] = $Fields["exID"];
//Soon after storing one, increment the value of the $Element variable so it is ready for the next one.
$Element++
}
/* now you have the data in the array. So you can do what you like with it. */
hope it helps
NOTE: when reading the array, make sure you put the condition to check that your counter is LESS than $Element. This is because after reading the last record, $Element is incremented by one. Hope this is clear.
Why not just grab all the right information in one query from the start along the lines of something like this:
select
a.exID,
examples.someField,
examples.someOtherField
from
examples
join
(
SELECT
exID
FROM
examples
WHERE
concept='$concept'
ORDER BY
RAND()
limit 5
) a
on a.exID=examples.exID
Then you just just pop them into an array (or better yet object) that has all the pertinent information in one row each time.
I have a game script thing set up, and when it creates a new character I want it to find an empty address for that players house.
The two relevant table fields it inserts are 'city' and 'number'. The 'city' is a random number out of 10, and the 'number' can be 1-250.
What it needs to do though is make sure there's not already an entry with the 2 random numbers it finds in the 'HOUSES' table, and if there is, then change the numbers. Repeat until it finds an 'address' not in use, then insert it.
I have a method set up to do this, but I know it's shoddy- there's probably some more logical and easier way. Any ideas?
UPDATE
Here's my current code:
$found = 0;
while ($found == 0) {
$num = (rand()%250)+1; $city = (rand()%10)+1;
$sql_result2 = mysql_query("SELECT * FROM houses WHERE city='$city' AND number='$num'", $db);
if (mysql_num_rows($sql_result2) == 0) { $found = 1; }
}
You can either do this in PHP as you do or by using a MySQL trigger.
If you stick to the PHP way, then instead of generating a number every time, do something like this
$found = 0;
$cityarr = array();
$numberarr = array();
//create the cityarr
for($i=1; $i<=10;$i++)
$cityarr[] = i;
//create the numberarr
for($i=1; $i<=250;$i++)
$numberarr[] = i;
//shuffle the arrays
shuffle($cityarr);
shuffle($numberarr);
//iterate until you find n unused one
foreach($cityarr as $city) {
foreach($numberarr as $num) {
$sql_result2 = mysql_query("SELECT * FROM houses
WHERE city='$city' AND number='$num'", $db);
if (mysql_num_rows($sql_result2) == 0) {
$found = 1;
break;
}
}
if($found) break;
}
this way you don't check the same value more than once, and you still check randomly.
But you should really consider fetching all your records before the loops, so you only have one query. That would also increase the performance a lot.
like
$taken = array();
for($i=1; $i<=10;$i++)
$taken[i] = array();
$records = mysql_query("SELECT * FROM houses", $db);
while($rec = mysql_fetch_assoc($records)) {
$taken[$rec['city']][] = $rec['number'];
}
for($i=1; $i<=10;$i++)
$cityarr[] = i;
for($i=1; $i<=250;$i++)
$numberarr[] = i;
foreach($cityarr as $city) {
foreach($numberarr as $num) {
if(in_array($num, $taken[]) {
$cityNotTaken = $city;
$numberNotTaken = $number;
$found = 1;
break;
}
}
if($found) break;
}
echo 'City ' . $cityNotTaken . ' number ' . $numberNotTaken . ' is not taken!';
I would go with this method :-)
Doing it the way you say can cause problems when there is only a couple (or even 1 left). It could take ages for the script to find an empty house.
What I recommend doing is insert all 2500 records in the database (combo 1-10 with 1-250) and mark with it if it's empty or not (or create a combo table with user <> house) and match it on that.
With MySQL you can select a random entry from the database witch is empty within no-time!
Because it's only 2500 records, you can do ORDER BY RAND() LIMIT 1 to get a random row. I don't recommend this when you have much more records.