Hall of Famer Posted September 7, 2011 Share Posted September 7, 2011 Well I was trying to design a random encounter event system, but got myself into a big issue with PHP random number generator(rand or mt_rand). A simple code for how these functions work is: $num = mt_rand(0,15) if($num < 5){ // codes to execute } else{ // codes to execute } The codes look nice, but it becomes a problem when multiple events with certain probability to occur need to be programmed. Consider there are 5 colors to select for a new automobile(black, while, yellow, red and blue), the probability for each of the color to be chosen is (30%, 30%, 20%, 10%, 10%), how am I supposed to write such a script in PHP efficiently? I know its possible to do it like this, but... $num = mt_rand(0,99) if($num < 30){ $color = "black"; } else if($num >= 30 and $num <60 ){ $color = "white"; } else if($num >= 60 and $num <80 ){ $color = "yellow"; } else if($num >= 80 and $num <90 ){ $color = "red"; } else{ $color = "blue"; } See the problem with this script? Yes, it is long and tedious, and more importantly inflexible. For a PHP random encounter script, the user may not know how many random events can occur(except for the fact that their probability values do add up to 1). It is also a common practice to retrieve random event names, probabilities and other properties from mysql with mysql_fetch_array() and loop. I do not know how to write such a program at this moment, can anyone of you please give a try and lemme know there is a way to handle multiple random events with different probability to occur easily? Thanks. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/ Share on other sites More sharing options...
xyph Posted September 7, 2011 Share Posted September 7, 2011 Here's a object-oriented solution. If you're confused and would like comments, I'll be happy to add them <?php $color = new probability(); $color->addEvent( 'black', 30 ); $color->addEvent( 'white', 30 ); $color->addEvent( 'yellow', 20 ); $color->addEvent( 'red', 10 ); $color->addEvent( 'blue', 10 ); echo 'Single random event: '.$color->randomEvent(1).'<br>'; echo 'Multiple random events: '. implode(', ', $color->randomEvent(5)); class probability { private $events = array(); public function addEvent( $value, $probability ) { $this->events[$value] = $probability; } public function removeEvent( $value ) { if( array_key_exists($value, $this->events) ) unset( $this->events[$value] ); } public function randomEvent( $num ) { $events = array_reverse( $this->buildTable(), TRUE ); $sum = array_sum($this->events); if( $num == 1 ) { $rand = mt_rand( 1, $sum ); foreach( $events as $key => $event ) if( $event <= $rand ) return $key; } else { $return = array(); for( $i = 0; $i < $num; $i++ ) { $rand = mt_rand( 1, $sum ); foreach( $events as $key => $event ) if( $event <= $rand ) { $return[] = $key; break; } } return $return; } } private function buildTable() { $events = $this->events; $total = 0; foreach( $events as &$event ) { $prev = $event; $event = $total; $total += $prev; } return $events; } } ?> Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1266510 Share on other sites More sharing options...
Hall of Famer Posted September 7, 2011 Author Share Posted September 7, 2011 Thanks a lot, this looks like a powerful script indeed. Would you mind adding a few comments in your code explaining how a random number is generated and how the script determines if the condition matches for a certain instance(such as black or white color)? Also what if the data for object's name and probability properties are retrieved from a mysql table's two columns called 'name', 'probability'? I am fine with OOP, but I dunno how to write OOP code together with Mysql integration. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1266538 Share on other sites More sharing options...
xyph Posted September 7, 2011 Share Posted September 7, 2011 Do you understand general class structure? If they're retrieved from MySQL, I'd suggest simply using a while loop to add them to the class, like this <?php $sql = new mysqli('localhost','root','','db'); $prob = new probability(); $query = 'SELECT `name`, `probability` FROM `probability'; if( ($result = $sql->query($query)) === FALSE ) echo 'Query error'; else { while( $row = $result->fetch_assoc() ) $prob->addEvent( $row['name'], $row['probability'] ); $result->free(); } echo '2 random colors: ' . implode(', ', $prob->randomEvent(2)); class probability { // This will hold our possible outcomes along with thier probabilities. // I store them with the key being the name of teh event, and the value // it's probability to show up. $this->events['name'] = probability private $events = array(); // This function will add a new event to the array. I didn't include a check // to make sure a duplicate exists, so as it is, if you call // $this->addEvent( 'blue', 20 ); // followed by // $this->addEvent( 'blue', 30 ); // 'blue' will now have a value of 30. public function addEvent( $value, $probability ) { $this->events[$value] = $probability; } // Simple function to remove an event. I don't think the array_key_exists() // check is needed, but it's there. public function removeEvent( $value ) { if( array_key_exists($value, $this->events) ) unset( $this->events[$value] ); } // Here's the meaty part. I'll comment this line-by-line public function randomEvent( $num = 1 ) { // Generate a reverse list of events, using our class method below to change // the individual probabilities into comparable numbers. $events = array_reverse( $this->buildTable(), TRUE ); // This is the total sum of all the probabilities. This gives us our max range // when we generate our random number $sum = array_sum($this->events); // If only 1 result is needed, we want to return a string if( $num == 1 ) { // Generate the random number between 1 and the sum of all probabilities $rand = mt_rand( 1, $sum ); // Loop through probabilities, greatest to lowest. foreach( $events as $event => $probability ) // If the probability is less than or equal to our random number, // it must be the value we want, because we go highest to lowest. // By returning the value, the function ceases to execute. if( $probability <= $rand ) return $event; // If more than 1 result is needed, we'll build an array. } else { // Set up an empty array to hold the values. $return = array(); // Loop through the following code $num times for( $i = 0; $i < $num; $i++ ) { // Generate our random number INSIDE the loop, so it gets changed // every time $rand = mt_rand( 1, $sum ); // Again, loop through each event foreach( $events as $event => $probability ) // Again, check if it's less than or equal to the value we want if( $probability <= $rand ) { // If so, add a new array entry. Since we don't need to loop // through any more events until we generate a new random number, // we use break to escape out of the foreach. $return[] = $event; break; } } // Return the array generated return $return; } } // Here's where we modify the events array to become more useable. We do this on the // fly, rather than when we add an event because it makes removing events a much easier // process. This method will add each probability to the sum of the ones before it. I // will show you below why I chose to do this. private function buildTable() { // Create a local copy of the events array. We can change this all we want without // modifying data that other class methods might still need. $events = $this->events; // This will hold the running total of the probabilities $total = 0; // Loop through each probability. The &$event will apply any changes made in the // loop to the $events array. foreach( $events as &$probability ) { // Hold the original probability in a temporary array $original = $probability; // Set the probability to the runnig total of all probabilities before it $probability = $total; // Add the oringial probability to the running total $total += $original; } // Return the momdified array return $events; } /* To give you a visual of what this function does... here's a print_r() of the array before and after the foreach loop BEFORE Array ( [black] => 30 [white] => 30 [yellow] => 20 [red] => 10 [blue] => 10 ) AFTER Array ( [black] => 0 [white] => 30 [yellow] => 60 [red] => 80 [blue] => 90 ) As you can see, the sum of the BEFORE array is 100, so the randomEvent method will generate a random number between 1 and 100 (say, 36), flip the AFTER array, and go through it, top to bottom, until it finds a value less than or equal to 36. Blue is 90, so it skips. Red is 80, so it skips. Yellow, 60, skip. White is 30, which is less than 36, so return it! */ } ?> SQL Dump used CREATE TABLE IF NOT EXISTS `probability` ( `name` varchar(10) NOT NULL, `probability` int(2) NOT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1; INSERT INTO `probability` (`name`, `probability`) VALUES ('black', 30), ('white', 30), ('yellow', 20), ('red', 10), ('blue', 10); Hope this helps. If you want me to get into more detail anywhere, let me know. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1266575 Share on other sites More sharing options...
Hall of Famer Posted September 7, 2011 Author Share Posted September 7, 2011 This is detailed enough, thank you so much. And yeah I do understand basic class structure, just never got it to work with mysql since I learned PHP classes/objects from books that do not introduce Mysql together with OOP. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1266601 Share on other sites More sharing options...
xyph Posted September 7, 2011 Share Posted September 7, 2011 Glad I could help. Please mark as solved. Integrating MySQL with objects is no different than integrating MySQL with any other part of PHP. You simply build your methods to accept values that your MySQL class of choice will return. In this case, I simply used PHP's mysqli class to turn my results into arrays, which my method was more than happy to accept. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1266604 Share on other sites More sharing options...
Hall of Famer Posted September 8, 2011 Author Share Posted September 8, 2011 I see, thanks again. I wonder if the class mysqli can identify a table's prefix though, as I browsed through the manual of mysqli class from php.net. It seems that the four arguments passed into mysqli class are 'hostname', 'username', 'password', 'database', what should I do with database tables with a prefix? Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1266705 Share on other sites More sharing options...
xyph Posted September 8, 2011 Share Posted September 8, 2011 Using the class is very similar to using it in procedural style. There are a few slight differences. Procedural <?php $table_prefix = 'blah_'; $link = mysqli_connect('localhost','root','','db'); $query = 'SELECT `name`, `probability` FROM `'.$table_prefix.'probability`'; if( ($result = mysqli_query($link, $query)) === FALSE ) echo 'Query error'; else { if( mysqli_num_rows($result) < 1 ) echo 'No rows found'; else { while( $row = mysqli_fetch_assoc($result) ) echo $row['name'].' - '.$row['probability'].'<br>'; } } ?> Object-Oriented <?php $table_prefix = 'blah_'; $link = new mysqli( 'localhost', 'root', '', 'db' ); $query = 'SELECT `name`, `probability` FROM `'.$table_prefix.'probability`'; if( ($result = $link->query($query)) === FALSE ) echo 'Query error'; else { if( $result->num_rows < 1 ) echo 'No rows found'; else { while( $row = $result->fetch_assoc() ) echo $row['name'].' - '.$row['probability'].'<br>'; } } ?> If you want to automate the process, you'll have to design your own class that extends the MySQLi class. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1266708 Share on other sites More sharing options...
Hall of Famer Posted September 8, 2011 Author Share Posted September 8, 2011 I see, that makes sense. I should write an inheritance class like this then? Class mysqli_extra extends mysqli{ var $prefix; // more lines of codes in this child class, the prefix name can be retrieved from config file. } Also a question that seems irrelevant to this topic. Why dont you use mysql_fetch_object in the OOP approach? Is it because there is no easy way to iterate an object like what we can normally do with an array(by using foreach). Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1266717 Share on other sites More sharing options...
xyph Posted September 8, 2011 Share Posted September 8, 2011 Arrays still have their place in OOP. I usually use fetch_assoc because I don't need an object to simply display the data. If, on the other hand, you have a group of functions that you commonly apply to data retrieved from the database, then using fetch_object with the 'class_name' argument is a great idea. The method you suggested is fine. Personally, I have a database class that uses an instance of the MySQLi class, rather than extending it. This is mostly because I use an interface for my database classes, so I have a common format regardless of the database used. I've found simply creating an instance of the existing object easier Quick example of what I mean class database { private $sql, $prefix, $debug = FALSE; public function __construct( $host, $user, $pass, $db, $prefix ) { $this->sql = new mysqli($host,$user,$pass,$db); $this->prefix = $prefix; } public function select( $table, array $columns, $where = FALSE, $limit = FALSE, $order = FALSE ) { $query = 'SELECT `' . implode('`,`',$columns) . ' FROM `'.$this->prefix.$table.'`' . ( $where ? ' WHERE '.$where : '' ) . ( $limit ? ' LIMIT '.$limit : '' ) . ( $order ? ' ORDER BY '.$order : '' ); if( $this->debug ) echo $query; if( ($r = $this->sql->query($query)) === FALSE ) return FALSE; elseif ( $r->num_rows < 1 ) return array(); else { $return = array(); while( $d = $r->fetch_assoc ) $return[] = $d; return $return; } } .... Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1267045 Share on other sites More sharing options...
Hall of Famer Posted September 9, 2011 Author Share Posted September 9, 2011 I see, this makes perfect sense to me. It seems that you have a select function in it, and it is quite flexible to use. Do you also have update and delete functions for the class database? From my experience, its quite annoyingly tedious to write multiple lines of mysql query commands in a PHP codes. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1267280 Share on other sites More sharing options...
xyph Posted September 9, 2011 Share Posted September 9, 2011 Yes, I have updates, deletes, and complex selects like nested selects and joins. Generally, these can change form script to script slightly, as sometimes it's hard to write a single method that can handle 4 joins, and redundant to have that kind of method in a simple script. The key is being able to port the script to any supported database without changing any code beyond a config variable that sets it. It's getting pretty abstract, but it's something to think about as you progress. I doubt you'll need anything like this until you start building larger projects. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1267438 Share on other sites More sharing options...
Hall of Famer Posted September 10, 2011 Author Share Posted September 10, 2011 Well actually I do need a more efficient and convenient way to select/insert/update database info. Some of my script files has tens of lines of "SELECT from db.tablename where columname = '$name'". It is getting quite tedious and annoying, it would be nice if you could provide me with an easier approach with or w/o OOP. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1267547 Share on other sites More sharing options...
xyph Posted September 10, 2011 Share Posted September 10, 2011 You'll have to be more specific. The example I posted above will do a great basic query, assuming you have sanitized the values before-hand, if needed. What kind of query were you looking at simplifying? Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1267579 Share on other sites More sharing options...
Hall of Famer Posted September 11, 2011 Author Share Posted September 11, 2011 Well here are some examples: $query = "SELECT * FROM {$prefix}items WHERE itemname = '{$itemname}'"; $result = mysql_query($query); $row = mysql_fetch_array($result); The above code uses three lines to retrieve database info from a table and sent it to an array called $row for future use. It may look fine with only one query being run, but it can be rather annoying if I have to write 2 or 3+ times of such code over and over again, especially in the script file of admin control panel. Similarly, the one below updates database info, and I believe it can be simplified. mysql_query("UPDATE {$prefix}items SET category='{$category}' , itemname='{$itemname}' , shop='{$shop}' , price='{$price}' , consumable='{$consumable}' WHERE id='{$id}'"); Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1267951 Share on other sites More sharing options...
xyph Posted September 11, 2011 Share Posted September 11, 2011 Of course. With OOP is my preferred method. Do you prefer a generic or customizable sanitize function? Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1268039 Share on other sites More sharing options...
Hall of Famer Posted September 11, 2011 Author Share Posted September 11, 2011 Well that is unnecessary, I am perfectly fine with OOP, and I think I can transform an OOP code back to procedural if necessary. Quote Link to comment https://forums.phpfreaks.com/topic/246610-probility-in-php/#findComment-1268119 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.