public static function generate(array $params) { $active = array_shift($params) !== 'past'; $brackets = Lib\Cache::fetch(function () use($active) { $allBrackets = Api\Bracket::getAll(); // Filter out active/completed brackets $brackets = []; foreach ($allBrackets as $bracket) { if ($active && ($bracket->state == BS_ELIMINATIONS || $bracket->state == BS_VOTING || $bracket->state == BS_NOMINATIONS)) { $bracket->title = Api\Round::getBracketTitleForActiveRound($bracket); $brackets[] = $bracket; } if (!$active && $bracket->state == BS_FINAL) { $brackets[] = $bracket; } } // Check for card images foreach ($brackets as $bracket) { if (is_readable('./images/bracket_' . $bracket->id . '_card.jpg')) { $bracket->cardImage = '/images/bracket_' . $bracket->id . '_card.jpg'; } else { $bracket->entrants = Api\Character::getRandomCharacters($bracket, 9); } } return $brackets; }, 'Controller::Brackets_displayBrackets_' . ($active ? 'active' : 'completed')); Lib\Display::addKey('page', 'brackets'); $title = $active ? 'Current Brackets' : 'Past Brackets'; Lib\Display::renderAndAddKey('content', 'bracketsView', ['brackets' => $brackets, 'title' => $title]); }
private static function _getCurrentRounds() { $retVal = null; $bracketId = Lib\Url::GetInt('bracketId', null); if ($bracketId) { $retVal = \Api\Round::getCurrentRounds($bracketId); } return $retVal; }
public static function generate(array $params) { $bracket = self::_getBracket(array_shift($params)); if ($bracket) { $stats = Api\Round::getVotingStats($bracket->id); if ($stats) { $out = new stdClass(); $out->bracket = $bracket; $out->stats = $stats; Lib\Display::renderAndAddKey('content', 'admin/stats', $out); } } }
/** * Generates a list of characters in a bracket (ordered by seed) and their performance * in said bracket */ public static function getEntrantPerformanceStats(Bracket $bracket, $force = false) { return Lib\Cache::fetchLongCache(function () use($bracket) { // Get all tourney rounds and characters for this bracket $characters = Character::queryReturnAll(['bracketId' => $bracket->id, 'seed' => ['null' => false]], ['seed' => 'asc']); $rounds = Round::queryReturnAll(['bracketId' => $bracket->id, 'final' => 1, 'tier' => ['gt' => 0]], ['id' => 'asc']); // Create a hash out of the characters $temp = []; foreach ($characters as $character) { $temp[$character->id] = $character; } $characters = $temp; // Sort the rounds out based on character for faster access later $characterRounds = []; foreach ($rounds as $round) { // Decorate the round with full character models $round->character1 = $characters[$round->character1Id]; $round->character2 = $characters[$round->character2Id]; self::_addRoundToCharacterRounds($round, $round->character1Id, $characterRounds); self::_addRoundToCharacterRounds($round, $round->character2Id, $characterRounds); } $retVal = []; foreach ($characters as $character) { $roundsForCharacter = array_values($characterRounds[$character->id]); $closestDiff = -1; $closestRound = null; $lostTo = null; $totalVotes = 0; foreach ($roundsForCharacter as $round) { // Heheheh... so gross $isCharacter1 = $round->character1Id == $character->id; $totalVotes += $isCharacter1 ? $round->character1Votes : $round->character2Votes; $diff = abs($round->character1Votes - $round->character2Votes); if ($diff < $closestDiff || $closestDiff === -1) { $closestDiff = $diff; // This case should be small enough that re-instantiating through a loop // shouldn't prove too much of a performance concern (especially since // it's generated only once per new round). Will monitor in production $closestRound = (object) ['character' => $isCharacter1 ? $round->character2 : $round->character1, 'difference' => $closestDiff, 'round' => $round]; } $lost = $isCharacter1 && $round->character1Votes < $round->character2Votes || !$isCharacter1 && $round->character2Votes < $round->character1Votes; $lostTo = $lost ? (object) ['character' => $isCharacter1 ? $round->character2 : $round->character1, 'lostBy' => $diff, 'round' => $round] : null; } $retVal[] = (object) ['character' => $character, 'closestRound' => $closestRound, 'lostTo' => $lostTo, 'totalVotes' => $totalVotes, 'group' => chr(65 + $roundsForCharacter[0]->group)]; } return $retVal; }, 'Stats::PerformanceStats_' . $bracket->id, $force); }
public static function _generateBracket(Api\Bracket $bracket) { $retVal = null; if ($bracket) { $availableEntrants = Api\Round::getRoundCountForTier($bracket, 0); // Can't have much of a bracket with only two entrants... if ($availableEntrants < 2) { $message = self::_createMessage('error', 'There are not enough entrants to generate a bracket :('); self::_main($message); } else { if (count($_POST) > 0) { $entrants = Lib\Url::Post('entrants', true); $groups = Lib\Url::Post('groups', true); if ($entrants && $groups) { // Verify that the entrants/groups combo doesn't exceed to number of available entrants if ($entrants * $groups > $availableEntrants) { $message = self::_createMessage('error', 'Cannot generate a bracket of that size'); self::_main($message); } else { $bracket->advance(); if ($bracket->createBracketFromEliminations($entrants * $groups, $groups)) { $message = self::_createMessage('success', 'Voting for bracket "' . $bracket->name . '" has successfully started!'); self::_refreshCaches($bracket); self::_main($message); } else { $message = self::_createMessage('error', 'There are not enough entrants to create a bracket of that size'); self::_main($message); } } } else { $message = self::_createMessage('error', 'There was an error starting the bracket'); self::_main($message); } } else { $out = (object) ['bracket' => $bracket, 'count' => $availableEntrants]; Lib\Display::renderAndAddKey('content', 'admin/start_bracket', $out); } } } }
public static function generate(array $params) { $user = self::_checkLogin(); self::_enableAd(); $perma = array_shift($params); $bracket = Api\Bracket::getBracketByPerma($perma); if ($bracket->start <= time() && ($bracket->state == BS_ELIMINATIONS || $bracket->state == BS_VOTING || $bracket->state == BS_WILDCARD)) { $cacheKey = 'CurrentRound_' . $bracket->id . '_' . $user->id; $out = Lib\Cache::fetch(function () use($user, $bracket) { $out = new stdClass(); $out->userId = $user->id; $out->round = Api\Round::getCurrentRounds($bracket->id); $out->title = Api\Round::getBracketTitleForActiveRound($bracket); return $out; }, $cacheKey, CACHE_MEDIUM); if ($out) { $out->bracket = $bracket; $template = $out->bracket->state == BS_ELIMINATIONS ? 'eliminations' : 'voting'; if ($bracket->state != BS_ELIMINATIONS) { $entrantSwap = Lib\TestBucket::get('entrantSwap'); if ($entrantSwap !== 'control') { foreach ($out->round as $round) { // Interesting side effect that I had not considered before: // When TestBucket initializes, it's setting the random seed for the entire RNG (duh). // That means the following random line will produce a static set of results, so the // user experience won't be wonky. if ($entrantSwap === 'flip' || $entrantSwap === 'random' && rand() % 2 === 0) { $round = self::_flipEntrants($round); } } } } Lib\Display::addKey('page', 'vote'); Lib\Display::addKey('title', $bracket->name . ' - Voting' . DEFAULT_TITLE_SUFFIX); Lib\Display::renderAndAddKey('content', $template, $out); } } }
private static function _vote($user) { $out = new stdClass(); $out->success = false; $bracketId = Lib\Url::Post('bracketId', true); $bracket = Api\Bracket::getById($bracketId); if ($bracket) { $state = $bracket ? (int) $bracket->state : null; if ($bracket->isLocked()) { $out->message = 'Voting is closed for this round. Please refresh to see the latest round.'; } else { if ($state === BS_ELIMINATIONS || $state === BS_VOTING) { if (self::_verifyAccountAge($user, $bracket)) { // Break the votes down into an array of round/character objects $votes = []; foreach ($_POST as $key => $val) { if (strpos($key, 'round:') === 0) { $key = str_replace('round:', '', $key); $obj = new stdClass(); $obj->roundId = (int) $key; $obj->characterId = (int) $val; $votes[] = $obj; } } $count = count($votes); if ($count > 0) { $query = 'INSERT INTO `votes` (`user_id`, `vote_date`, `round_id`, `character_id`, `bracket_id`) VALUES '; $params = [':userId' => $user->id, ':date' => time(), ':bracketId' => $bracketId]; $insertCount = 0; // Only run an insert for rounds that haven't been voted on $rounds = Api\Votes::getOpenRounds($user, $votes); for ($i = 0; $i < $count; $i++) { if (!isset($rounds[$votes[$i]->roundId])) { $query .= '(:userId, :date, :round' . $i . ', :character' . $i . ', :bracketId),'; $params[':round' . $i] = $votes[$i]->roundId; $params[':character' . $i] = $votes[$i]->characterId; $insertCount++; $rounds[$votes[$i]->roundId] = true; } } if ($insertCount > 0) { $query = substr($query, 0, strlen($query) - 1); if (Lib\Db::Query($query, $params)) { $out->success = true; // I am vehemently against putting markup in the controller, but there's much refactor needed to make this right // So, that's a note that it will be changed in the future $out->message = 'Your votes were successfully submitted! <a href="/results/' . $bracket->perma . '">View Results</a>'; // Oops, I did it again... if ($bracket->externalId) { $out->message .= ' or <a href="http://redd.it/' . $bracket->externalId . '" target="_blank">discuss on reddit</a>.'; } // Clear any user related caches $round = Api\Round::getById($votes[0]->roundId); Lib\Cache::Set('GetBracketRounds_' . $bracketId . '_' . $round->tier . '_' . $round->group . '_' . $user->id, false); Lib\Cache::Set('GetBracketRounds_' . $bracketId . '_' . $round->tier . '_all_' . $user->id, false); Lib\Cache::Set('CurrentRound_' . $bracketId . '_' . $user->id, false); $bracket->getVotesForUser($user, true); } else { $out->message = 'There was an unexpected error. Please try again in a few moments.'; } } else { $out->message = 'Voting for this round has closed'; $out->code = 'closed'; } } else { $out->message = 'No votes were submitted'; } } else { $out->message = 'Your reddit account is not old enough to vote in this bracket'; } } else { $out->message = 'Voting is closed on this bracket'; $out->code = 'closed'; } } } else { $out->message = 'Invalid parameters'; } return $out; }
/** * Refreshes various generic caches. This is expensive; use sparingly */ protected static function _refreshCaches(Api\Bracket $bracket = null) { Lib\Cache::setDisabled(true); // Refresh the main collections Api\Bracket::getAll(); Api\Bracket::getUserOwnedBrackets(self::$_user); \Controller\Brackets::generate(['past']); \Controller\Brackets::generate([]); // Refresh a single bracket if specified if ($bracket) { Api\Bracket::getBracketByPerma($bracket->perma); Api\Round::getCurrentRounds($bracket->id); $bracket->getResults(); } Lib\Cache::setDisabled(false); }
/** * Takes the results from the elimination rounds and creates a seeded bracket */ public function createBracketFromEliminations($entrants, $groups) { $retVal = false; if (is_numeric($entrants)) { // Generate the bracket template $seeding = self::generateSeededBracket($entrants); // Get the max vote counts for each day $result = Lib\Db::Query('SELECT COUNT(1) AS total, r.round_group FROM votes v INNER JOIN round r ON r.round_id = v.round_id WHERE v.bracket_id = :bracketId GROUP BY r.round_group', [':bracketId' => $this->id]); $groupCounts = []; $max = 0; while ($row = Lib\Db::Fetch($result)) { $votes = (int) $row->total; $groupCounts[(int) $row->round_group] = $votes; $max = $votes > $max ? $votes : $max; } $characters = []; $result = Lib\Db::Query('SELECT COUNT(1) AS total, c.*, r.round_group FROM `round` r INNER JOIN `character` c ON c.character_id = r.round_character1_id LEFT OUTER JOIN votes v ON v.character_id = c.character_id WHERE r.round_tier = 0 AND r.bracket_id = :bracketId GROUP BY c.character_id', [':bracketId' => $this->id]); // Ensure that we have characters and there are at least enough to meet the bracket constraints if ($result && $result->count >= $entrants) { while ($row = Lib\Db::Fetch($result)) { $obj = new Character($row); // Normalize the votes against the highest day of voting to ensure that seeding order is reflective of flucuations in daily voting // $obj->adjustedVotes = round(($obj->votes / $groups[$obj->group]) * $max); $obj->adjustedVotes = round((int) $row->total / $groupCounts[(int) $row->round_group] * $max); $characters[] = $obj; } // Reorder by adjusted votes usort($characters, function ($a, $b) { return $a->adjustedVotes < $b->adjustedVotes ? 1 : -1; }); // Set up the rounds $groupSplit = $entrants / $groups; for ($i = 0; $i < $entrants; $i += 2) { $round = new Round(); $round->bracketId = $this->id; $round->tier = 1; $round->order = ($i + 1) % $groupSplit; $round->group = floor($i / $groupSplit); // Get the correct character and save their seed $character1 = $characters[$seeding[$i] - 1]; $character1->seed = $seeding[$i]; $character1->sync(); $character2 = $characters[$seeding[$i + 1] - 1]; $character2->seed = $seeding[$i + 1]; $character2->sync(); $round->character1Id = $character1->id; $round->character2Id = $character2->id; $round->sync(); } // Change the state to standard bracket voting $this->state = BS_VOTING; $retVal = $this->sync(); // Force update the results cache $this->getResults(true); } } return $retVal; }
public static function generate(array $params) { Lib\Display::setLayout('landing'); Lib\Display::addKey('rounds', Api\Round::getRandomCompletedRounds(30)); Lib\Display::addKey('phrase', static::$_phrases[rand() % count(static::$_phrases)]); }