Combu Server  3.1.1
PHP API Documentation
LeaderBoard.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Combu;
4 
10 class LeaderBoard extends DataClass {
11 
12  const TABLE_NAME = "LeaderBoard";
13 
17  const VALUE_INT = 0;
21  const VALUE_FLOAT = 1;
22 
26  const ORDER_DESC = 0;
30  const ORDER_ASC = 1;
31 
35  const SCORE_UNIQUE_NONE = 0;
39  const SCORE_UNIQUE_INCREASE = 1;
43  const SCORE_UNIQUE_REPLACE_HIGHER = 2;
47  const SCORE_UNIQUE_REPLACE_ANY = 3;
48 
52  const HIGHSCORE_TOTAL = 0;
56  const HIGHSCORE_MONTH = 1;
60  const HIGHSCORE_WEEK = 2;
64  const HIGHSCORE_TODAY = 3;
65 
66  const USERSCOPE_GLOBAL = 0;
67  const USERSCOPE_FRIENDS = 1;
68 
69  public $Id = 0;
70  public $IdApp = 0;
71  public $Code = "";
72  public $Title = "";
73  public $Description = "";
74  public $UniqueRecords = self::SCORE_UNIQUE_INCREASE;
75  public $ValueType = self::VALUE_INT;
76  public $OrderType = self::ORDER_DESC;
77  public $AllowAnonymous = 0;
78 
82  public function __construct($src = null, $stripSlashes = false) {
83  global $Database;
84  if (empty($src)) {
85  return;
86  }
87  if (is_array($src)) {
88  // Load by array
89  $this->_loadByRow($src, $stripSlashes);
90  } else if (is_numeric($src)) {
91  // Load by Id
92  if (intval($src) > 0) {
93  $this->_loadFilter(self::GetTableName(__CLASS__), "Id = " . intval($src));
94  }
95  } else {
96  // Load by Code
97  $this->_loadFilter(self::GetTableName(__CLASS__), sprintf("Code = '%s'", $Database->Escape($src)));
98  }
99  }
100 
106  public function ExistsCode() {
107  global $Database;
108  $sql = "SELECT Id FROM " . self::GetTableName(__CLASS__) . " WHERE Code = '" . $Database->Escape($this->Code) . "'";
109  if ($this->Id > 0) {
110  $sql .= " AND Id <> " . $this->Id;
111  }
112  $res = $Database->Query($sql);
113  if ($res) {
114  $row = $Database->FetchAssoc($res);
115  if ($row) {
116  return TRUE;
117  }
118  }
119  return FALSE;
120  }
121 
127  public static function GetScoreTypes() {
128  return array(
129  self::SCORE_UNIQUE_NONE => "Create new score per match",
130  self::SCORE_UNIQUE_INCREASE => "Increase the same score per user",
131  self::SCORE_UNIQUE_REPLACE_HIGHER => "Replace the best score per user",
132  self::SCORE_UNIQUE_REPLACE_ANY => "Replace the same score per user"
133  );
134  }
135 
146  public static function Load ($idApp = 0, $limit = null, $offset = null, &$count = null, $returnArray = false) {
147  $where = "";
148  if ($idApp > 0) {
149  $where = sprintf("(IdApp = 0 OR IdApp = %d)", $idApp);
150  }
151  return self::_load(self::GetTableName(__CLASS__), ($returnArray ? "" : __CLASS__), $where, "IdApp, Id", $limit, $offset, $count);
152  }
153 
159  public function Save() {
160  global $Database;
161  if ($this->Id > 0) {
162  $query = sprintf("UPDATE %s SET Code = '%s', Title = '%s', Description = '%s', ValueType = %d, OrderType = %d, AllowAnonymous = %d WHERE Id = %d",
163  self::GetTableName(__CLASS__),
164  $Database->Escape($this->Code),
165  $Database->Escape($this->Title),
166  $Database->Escape($this->Description),
167  $this->ValueType,
168  $this->OrderType,
169  $this->AllowAnonymous,
170  $this->Id);
171  } else {
172  $query = sprintf("INSERT INTO %s (IdApp, Code, Title, Description, UniqueRecords, ValueType, OrderType, AllowAnonymous) VALUES (%d, '%s', '%s', '%s', %d, %d, %d, %d)",
173  self::GetTableName(__CLASS__),
174  $this->IdApp,
175  $Database->Escape($this->Code),
176  $Database->Escape($this->Title),
177  $Database->Escape($this->Description),
178  $this->UniqueRecords,
179  $this->ValueType,
180  $this->OrderType,
181  $this->AllowAnonymous);
182  }
183  if ($Database->Query($query)) {
184  if ($this->Id <= 0) {
185  $this->Id = $Database->InsertedId();
186  }
187  return TRUE;
188  }
189  return FALSE;
190  }
191 
197  public function Delete() {
198  if ($this->Id > 0) {
199  if ($this->_Delete(self::GetTableName(__CLASS__), "Id = " . $this->Id)) {
200  // Delete all the associated resources
201  $this->_Delete(self::GetTableName(LeaderBoard_User::class), "IdLeaderboard = " . $this->Id);
202  return TRUE;
203  }
204  }
205  return FALSE;
206  }
207 
211  public static function Prune() {
212  self::TruncateClass(__CLASS__);
213  LeaderBoard_User::Prune();
214  }
215 
228  public function LoadHighscore ($timeInterval, $userScope, $groupPlayer = FALSE, $sumPlayer = FALSE, $limit = NULL, $offset = NULL, &$count = NULL) {
229  global $LoggedAccount;
230  // Set the first filter on the current leaderboard
231  $where = sprintf("(IdLeaderboard = %d)", $this->Id);
232  // Filter Last Updated datetime to reflect the time interval desired
233  switch ($timeInterval) {
234  case self::HIGHSCORE_TODAY:
235  $where .= sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')",
236  date("Y-m-d"),
237  date("Y-m-d"));
238  break;
239  case self::HIGHSCORE_WEEK:
240  $where .= sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')",
241  date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 7, date("Y"))),
242  date("Y-m-d"));
243  break;
244  case self::HIGHSCORE_MONTH:
245  $where .= sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')",
246  date("Y-m-d", mktime(0, 0, 0, date("m"), 1, date("Y"))),
247  date("Y-m-t", mktime(0, 0, 0, date("m"), 1, date("Y"))));
248  break;
249  case self::HIGHSCORE_TOTAL:
250  default:
251  // No filter on date by default and for TOTAL interval
252  break;
253  }
254  switch ($userScope) {
255  case self::USERSCOPE_FRIENDS:
256  $filterId = array($LoggedAccount->Id);
257  $friends = Friend::Load($LoggedAccount->Id, FRIEND_STATE_ACCEPTED);
258  foreach ($friends as $friend) {
259  $filterId[] = $friend->IdFriend;
260  }
261  $where = sprintf("(IdAccount IN (%s))", implode(",", $filterId));
262  break;
263  }
264  // Build ORDER BY statement and get the field to update
265  $order = "";
266  $field = "";
267  $this->GetOrderBy($order, $field);
268  // Load the records from users in the leaderboard
269  if ($this->UniqueRecords == self::SCORE_UNIQUE_NONE && ($groupPlayer || $sumPlayer)) {
270  // Leaderboards with multiple scores can be grouped to load only the highest score per player
271  $where .= " GROUP BY IdAccount";
272  if ($sumPlayer) {
273  $select = "*, SUM(ValueInt) AS ValueInt, SUM(ValueFloat) AS ValueFloat";
274  } else {
275  $select = "*, MAX(ValueInt) AS ValueInt, MAX(ValueFloat) AS ValueFloat";
276  }
277  $records = self::_loadEx($select, self::GetTableName(LeaderBoard_User::class), LeaderBoard_User::class, $where, $order, $limit, $offset);
278  $recordCount = self::_loadQuery("SELECT COUNT(Id) AS CountScores FROM (SELECT Id FROM " . self::GetTableName(LeaderBoard_User::class) . " WHERE " . $where . ") T");
279  if (count($recordCount) > 0) {
280  $count = $recordCount[0]["CountScores"];
281  }
282  } else {
283  // Load all the records filtered in the time interval
284  $records = self::_load(self::GetTableName(LeaderBoard_User::class), LeaderBoard_User::class, $where, $order, $limit, $offset, $count);
285  }
286  // Finally build the highscore custom list!
287  $highscore = array();
288  $rank = $offset + 1;
289  foreach ($records as $record) {
290  if ($record->IdAccount > 0) {
291  $user = new Account($record->IdAccount);
292  } else {
293  $user = new Account();
294  $user->Username = $record->Username;
295  }
296  $highscore[] = array(
297  "Id" => $record->Id,
298  "IdLeaderboard" => $this->Id,
299  "Rank" => $rank++,
300  "User" => $user->ToJson(),
301  "Score" => $record->$field
302  );
303  }
304  return $highscore;
305  }
306 
307  public function GetOrderBy (&$order, &$field) {
308  // Build ORDER BY statement and get the field to update
309  switch ($this->ValueType) {
310  case self::VALUE_FLOAT:
311  $order = $field = "ValueFloat";
312  break;
313  case self::VALUE_INT:
314  default:
315  $order = $field = "ValueInt";
316  break;
317  }
318  // Apply Descending or Ascending to ORDER BY
319  if ($this->OrderType == self::ORDER_ASC) {
320  $order .= " ASC, IdAccount ASC";
321  } else {
322  $order .= " DESC, IdAccount ASC";
323  }
324  }
325 
326  public function LoadHighscoreForAccount ($timeInterval, $groupPlayer, $sumPlayer, $idAccount) {
327  // Set the time interval statement
328  $where_time = "";
329  switch ($timeInterval) {
330  case self::HIGHSCORE_TODAY:
331  $where_time .= sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')",
332  date("Y-m-d"),
333  date("Y-m-d"));
334  break;
335  case self::HIGHSCORE_WEEK:
336  $where_time .= sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')",
337  date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 7, date("Y"))),
338  date("Y-m-d"));
339  break;
340  case self::HIGHSCORE_MONTH:
341  $where_time .= sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')",
342  date("Y-m-d", mktime(0, 0, 0, date("m"), 1, date("Y"))),
343  date("Y-m-t", mktime(0, 0, 0, date("m"), 1, date("Y"))));
344  break;
345  case self::HIGHSCORE_TOTAL:
346  default:
347  // No filter on date by default and for TOTAL interval
348  break;
349  }
350  // Get the current score for this user
351  $where = sprintf("(IdLeaderboard = %d AND IdAccount = %d)", $this->Id, $idAccount) . $where_time;
352  $order = "";
353  $field = "";
354  $this->GetOrderBy($order, $field);
355  $records = self::_load(self::GetTableName(LeaderBoard_User::class), LeaderBoard_User::class, $where);
356  $userScore = 0;
357  if (count($records) > 0) {
358  if ($this->UniqueRecords == self::SCORE_UNIQUE_NONE && ($groupPlayer || $sumPlayer)) {
359  foreach ($records as $record) {
360  if ($sumPlayer) {
361  $userScore += $record->$field;
362  } else if ($record->$field > $userScore) {
363  $userScore = $record->$field;
364  }
365  }
366  } else {
367  $userScore = $records[0]->$field;
368  }
369  }
370  // Now count how many players with higher score
371  $where = sprintf("(IdLeaderboard = %d AND $field " . ($this->OrderType == self::ORDER_ASC ? "<" : ">") . " %f)", $this->Id, $userScore) . $where_time;
372  if ($this->UniqueRecords == self::SCORE_UNIQUE_NONE && ($groupPlayer || $sumPlayer)) {
373  $where .= " GROUP BY IdAccount";
374  if ($sumPlayer) {
375  $select = "*, SUM(ValueInt) AS ValueInt, SUM(ValueFloat) AS ValueFloat";
376  } else {
377  $select = "*, MAX(ValueInt) AS ValueInt, MAX(ValueFloat) AS ValueFloat";
378  }
379  $recordCount = self::_loadQuery("SELECT COUNT(Id) AS CountScores FROM (SELECT Id FROM " . self::GetTableName(LeaderBoard_User::class) . " WHERE " . $where . ") T");
380  if (count($recordCount) > 0) {
381  $rank = $recordCount[0]["CountScores"];
382  }
383  } else {
384  $rank = self::_count(self::GetTableName(LeaderBoard_User::class), $where);
385  }
386  $rank++; // Add "1" to the count, because the first rank is "1" and not "0"
387  // Finally count how many players have equal score
388  $where = sprintf("(IdLeaderboard = %d AND $field = %f AND IdAccount < %d)", $this->Id, $userScore, $idAccount);
389  $rank += count(self::_load(self::GetTableName(LeaderBoard_User::class), LeaderBoard_User::class, $where));
390  return array( "IdLeaderboard" => $this->Id, "CodeLeaderboard" => $this->Code, "Score" => $userScore, "Rank" => $rank );
391  }
392 
400  public function PostScore ($account, $scoreValue) {
401  // Check the value type of the leaderboard
402  // to get the field name to update and adjust score value
403  switch ($this->ValueType) {
404  case self::VALUE_FLOAT:
405  $field = "ValueFloat";
406  $scoreValue = floatval($scoreValue);
407  break;
408  case self::VALUE_INT:
409  default:
410  $field = "ValueInt";
411  $scoreValue = intval($scoreValue);
412  break;
413  }
414  // Load the current saved records for this user
415  $records = array();
416  if ($account->Id > 0) {
417  $records = LeaderBoard_User::LoadAccount($account->Id, $this->Id);
418  }
419  if (count($records) == 0) {
420  // We still haven't records, create new one
421  $newRecord = new LeaderBoard_User();
422  $newRecord->IdLeaderboard = $this->Id;
423  $newRecord->IdAccount = $account->Id;
424  $newRecord->Username = $account->Username;
425  $newRecord->$field = $scoreValue;
426  return $newRecord->Save();
427  }
428  // Ok! So we have at least one record for this user in the leaderboard...
429  // Check to see if the leaderboard requires unique records per user
430  if ($this->UniqueRecords != self::SCORE_UNIQUE_NONE) {
431  // It requires unique records, so update the current saved
432  $records[0]->Username = $account->Username;
433  switch ($this->UniqueRecords) {
434  case self::SCORE_UNIQUE_INCREASE:
435  // Increase score
436  $records[0]->$field += $scoreValue;
437  break;
438  case self::SCORE_UNIQUE_REPLACE_HIGHER:
439  // Replace score if it is highest
440  if (($this->OrderType == self::ORDER_DESC && $records[0]->$field >= $scoreValue) || ($this->OrderType == self::ORDER_ASC && $records[0]->$field <= $scoreValue)) {
441  return TRUE;
442  }
443  $records[0]->$field = $scoreValue;
444  break;
445  case self::SCORE_UNIQUE_REPLACE_ANY:
446  $records[0]->$field = $scoreValue;
447  break;
448  }
449  return $records[0]->Save();
450  }
451  // It doesn't require unique records, so create new one
452  $newRecord = new LeaderBoard_User();
453  $newRecord->IdLeaderboard = $this->Id;
454  $newRecord->IdAccount = $account->Id;
455  $newRecord->Username = $account->Username;
456  $newRecord->$field = $scoreValue;
457  return $newRecord->Save();
458  }
459 }
GetOrderBy(&$order, &$field)
LoadHighscoreForAccount($timeInterval, $groupPlayer, $sumPlayer, $idAccount)
static GetScoreTypes()
LoadHighscore($timeInterval, $userScope, $groupPlayer=FALSE, $sumPlayer=FALSE, $limit=NULL, $offset=NULL, &$count=NULL)
__construct($src=null, $stripSlashes=false)
Definition: LeaderBoard.php:82
PostScore($account, $scoreValue)
Definition: Account.php:3
static Load($idApp=0, $limit=null, $offset=null, &$count=null, $returnArray=false)