Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
95.02% |
191 / 201 |
|
71.43% |
10 / 14 |
CRAP | |
0.00% |
0 / 1 |
| PlayResultProcessorComponent | |
94.79% |
182 / 192 |
|
71.43% |
10 / 14 |
69.67 | |
0.00% |
0 / 1 |
| checkPreviousPlay | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
7 | |||
| checkPreviousPlayAndGetResult | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
| getNewStatus | |
85.71% |
18 / 21 |
|
0.00% |
0 / 1 |
13.49 | |||
| updateTsumegoStatus | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
5 | |||
| checkAddFavorite | |
73.33% |
11 / 15 |
|
0.00% |
0 / 1 |
5.47 | |||
| checkRemoveFavorite | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
4.18 | |||
| updateTsumegoAttempt | |
100.00% |
28 / 28 |
|
100.00% |
1 / 1 |
7 | |||
| processRatingChangeStep | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
| processRatingChange | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
5 | |||
| processDamage | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
| processXpChange | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
| processErrorAchievement | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
3 | |||
| processUnsortedStuff | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
4.01 | |||
| checkMisplay | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | App::uses('TsumegoStatus', 'Model'); |
| 4 | App::uses('SetConnection', 'Model'); |
| 5 | App::uses('Rating', 'Utility'); |
| 6 | App::uses('Util', 'Utility'); |
| 7 | App::uses('Decoder', 'Utility'); |
| 8 | App::uses('HeroPowers', 'Utility'); |
| 9 | App::uses('TsumegoXPAndRating', 'Utility'); |
| 10 | App::uses('Level', 'Utility'); |
| 11 | App::uses('Progress', 'Utility'); |
| 12 | |
| 13 | class PlayResultProcessorComponent extends Component |
| 14 | { |
| 15 | public $components = ['Session']; |
| 16 | |
| 17 | public function checkPreviousPlay($timeModeComponent): void |
| 18 | { |
| 19 | $this->checkAddFavorite(); |
| 20 | $this->checkRemoveFavorite(); |
| 21 | |
| 22 | $previousTsumegoID = Util::clearNumericCookie('previousTsumegoID'); |
| 23 | if (!$previousTsumegoID) |
| 24 | return; |
| 25 | |
| 26 | $previousTsumego = ClassRegistry::init('Tsumego')->findById($previousTsumegoID); |
| 27 | if (!$previousTsumego) |
| 28 | return; |
| 29 | |
| 30 | $result = $this->checkPreviousPlayAndGetResult($previousTsumego); |
| 31 | |
| 32 | $previousTsumegoStatus = ClassRegistry::init('TsumegoStatus')->find('first', [ |
| 33 | 'conditions' => [ |
| 34 | 'tsumego_id' => (int) $previousTsumego['Tsumego']['id'], |
| 35 | 'user_id' => (int) Auth::getUserID(), |
| 36 | ], |
| 37 | ]); |
| 38 | |
| 39 | $this->updateTsumegoStatus($previousTsumego, $result, $previousTsumegoStatus); |
| 40 | |
| 41 | if (!isset($result['solved'])) |
| 42 | return; |
| 43 | if (HeroPowers::getSprintRemainingSeconds() > 0) |
| 44 | $result['xp-modifier'] = ($result['xp-modifier'] ?: 1) * Constants::$SPRINT_MULTIPLIER; |
| 45 | |
| 46 | $previousStatusValue = $previousTsumegoStatus ? $previousTsumegoStatus['TsumegoStatus']['status'] : 'N'; |
| 47 | |
| 48 | // I need to save the original tsumego rating I calculated XP change for |
| 49 | // this is to avoid that rating gets changed, and the XP change calculation would |
| 50 | // be based on the changed rating, and would slightly differ from the promised change |
| 51 | $originalTsumegoRating = $previousTsumego['Tsumego']['rating']; |
| 52 | |
| 53 | $this->processRatingChange($previousTsumego, $result, $previousStatusValue); |
| 54 | $this->processDamage($result, $previousStatusValue); |
| 55 | $timeModeComponent->processPlayResult($previousTsumego, $result); |
| 56 | $this->processXpChange($previousTsumego, $result, $previousStatusValue, $originalTsumegoRating); |
| 57 | $this->updateTsumegoAttempt($previousTsumego, $result, $previousStatusValue); |
| 58 | $this->processErrorAchievement($result); |
| 59 | $this->processUnsortedStuff($previousTsumego, $result); |
| 60 | } |
| 61 | |
| 62 | public function checkPreviousPlayAndGetResult(&$previousTsumego): array |
| 63 | { |
| 64 | $result = []; |
| 65 | if ($misplays = $this->checkMisplay()) |
| 66 | { |
| 67 | $result['solved'] = false; |
| 68 | $result['misplays'] = $misplays; |
| 69 | } |
| 70 | if (Decoder::decodeSuccess($previousTsumego['Tsumego']['id'])) |
| 71 | $result['solved'] = true; |
| 72 | |
| 73 | return $result; |
| 74 | } |
| 75 | |
| 76 | private function getNewStatus($solved, $currentStatus, &$result) |
| 77 | { |
| 78 | if ($solved) |
| 79 | { |
| 80 | if ($currentStatus == 'W') // half xp state |
| 81 | {$result['xp-modifier'] = ($result['xp-modifier'] ?: 1) * Constants::$SECOND_SOLVE_XP_MULTIPLIER; |
| 82 | return 'C'; // double solved |
| 83 | } |
| 84 | if ($currentStatus == 'G') |
| 85 | { |
| 86 | $result['xp-modifier'] = ($result['xp-modifier'] ?: 1) * Constants::$GOLDEN_TSUMEGO_XP_MULTIPLIER; |
| 87 | return 'S'; |
| 88 | } |
| 89 | if ($currentStatus == 'V' || $currentStatus == 'N') |
| 90 | return 'S'; |
| 91 | return $currentStatus; // failed can't be unfailed by solving, user has to wait until next day or rejuvenation |
| 92 | } |
| 93 | |
| 94 | // not solved from now |
| 95 | if ($currentStatus == 'V') // if it was just visited so far (so we don't overwrite solved) |
| 96 | {if (Auth::getUser()['damage'] >= Util::getHealthBasedOnLevel(Auth::getUser()['level'])) |
| 97 | return 'F'; // only mark as failed when the user has no hearts left |
| 98 | return $currentStatus; |
| 99 | } |
| 100 | if ($currentStatus == 'W') |
| 101 | { |
| 102 | if (Auth::getUser()['damage'] >= Util::getHealthBasedOnLevel(Auth::getUser()['level'])) |
| 103 | return 'X'; // only mark as 'stale failed' when the user has no hearts left |
| 104 | return $currentStatus; |
| 105 | } |
| 106 | if ($currentStatus == 'G') |
| 107 | return 'V'; // failed golden tsumego |
| 108 | return $currentStatus; |
| 109 | } |
| 110 | |
| 111 | private function updateTsumegoStatus(array $previousTsumego, array &$result, ?array $previousTsumegoStatus): void |
| 112 | { |
| 113 | if ($previousTsumegoStatus == null) |
| 114 | { |
| 115 | $previousTsumegoStatus['TsumegoStatus'] = []; |
| 116 | $previousTsumegoStatus['TsumegoStatus']['user_id'] = Auth::getUserID(); |
| 117 | $previousTsumegoStatus['TsumegoStatus']['tsumego_id'] = $previousTsumego['Tsumego']['id']; |
| 118 | $previousTsumegoStatus['TsumegoStatus']['status'] = 'V'; |
| 119 | } |
| 120 | $_COOKIE['previousTsumegoBuffer'] = $previousTsumegoStatus['TsumegoStatus']['status']; |
| 121 | |
| 122 | if (isset($result['solved'])) |
| 123 | { |
| 124 | $newStatus = $this->getNewStatus($result['solved'], $previousTsumegoStatus['TsumegoStatus']['status'], $result); |
| 125 | if (TsumegoUtil::isSolvedStatus($newStatus) && !TsumegoUtil::isSolvedStatus($previousTsumegoStatus['TsumegoStatus']['status'])) |
| 126 | Auth::getUser()['solved'] = Auth::getUser()['solved'] + 1; |
| 127 | $previousTsumegoStatus['TsumegoStatus']['status'] = $newStatus; |
| 128 | } |
| 129 | $previousTsumegoStatus['TsumegoStatus']['created'] = date('Y-m-d H:i:s'); |
| 130 | ClassRegistry::init('TsumegoStatus')->save($previousTsumegoStatus); |
| 131 | } |
| 132 | |
| 133 | private function checkAddFavorite(): void |
| 134 | { |
| 135 | if (!Auth::isLoggedIn()) |
| 136 | return; |
| 137 | |
| 138 | $tsumegoID = Util::clearCookie('add_favorite'); |
| 139 | if (empty($tsumegoID)) |
| 140 | return; |
| 141 | |
| 142 | $favorite = ClassRegistry::init('Favorite')->find('first', ['conditions' => ['user_id' => Auth::getUserID(), 'tsumego_id' => $tsumegoID]]); |
| 143 | if ($favorite) |
| 144 | return; |
| 145 | |
| 146 | try |
| 147 | { |
| 148 | $favorite = []; |
| 149 | $favorite['user_id'] = Auth::getUserID(); |
| 150 | $favorite['tsumego_id'] = $tsumegoID; |
| 151 | ClassRegistry::init('Favorite')->create(); |
| 152 | ClassRegistry::init('Favorite')->save($favorite); |
| 153 | } |
| 154 | catch (Exception $e) |
| 155 | { |
| 156 | throw new Exception('Tsumego id = ' . $tsumegoID, 0, $e); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | private function checkRemoveFavorite(): void |
| 161 | { |
| 162 | if (!Auth::isLoggedIn()) |
| 163 | return; |
| 164 | |
| 165 | $tsumegoID = Util::clearCookie('remove_favorite'); |
| 166 | if (empty($tsumegoID)) |
| 167 | return; |
| 168 | |
| 169 | $favorite = ClassRegistry::init('Favorite')->find('first', ['conditions' => ['user_id' => Auth::getUserID(), 'tsumego_id' => $tsumegoID]]); |
| 170 | if (!$favorite) |
| 171 | return; |
| 172 | ClassRegistry::init('Favorite')->delete($favorite['Favorite']['id']); |
| 173 | } |
| 174 | |
| 175 | private function updateTsumegoAttempt(array $previousTsumego, array $result, $previousTsumegoStatus): void |
| 176 | { |
| 177 | if (Auth::isInTimeMode()) |
| 178 | return; |
| 179 | if (TsumegoUtil::isRecentlySolved($previousTsumegoStatus)) |
| 180 | return; |
| 181 | $lastTsumegoAttempt = ClassRegistry::init('TsumegoAttempt')->find( |
| 182 | 'first', |
| 183 | ['conditions' |
| 184 | => ['user_id' => Auth::getUserID(), |
| 185 | 'tsumego_id' => $previousTsumego['Tsumego']['id']], |
| 186 | 'order' => 'id DESC'] |
| 187 | ); |
| 188 | |
| 189 | // only not solved ones are updated (misplays get accumulated) |
| 190 | if (!$lastTsumegoAttempt || $lastTsumegoAttempt['TsumegoAttempt']['solved']) |
| 191 | { |
| 192 | $tsumegoAttempt = []; |
| 193 | $tsumegoAttempt['TsumegoAttempt']['user_id'] = Auth::getUserID(); |
| 194 | $tsumegoAttempt['TsumegoAttempt']['tsumego_id'] = $previousTsumego['Tsumego']['id']; |
| 195 | $tsumegoAttempt['TsumegoAttempt']['seconds'] = 0; |
| 196 | $tsumegoAttempt['TsumegoAttempt']['solved'] = $result['solved']; |
| 197 | $tsumegoAttempt['TsumegoAttempt']['tsumego_rating'] = $previousTsumego['Tsumego']['rating']; |
| 198 | $tsumegoAttempt['TsumegoAttempt']['misplays'] = 0; |
| 199 | } |
| 200 | else |
| 201 | $tsumegoAttempt = $lastTsumegoAttempt; |
| 202 | |
| 203 | $tsumegoAttempt['TsumegoAttempt']['user_rating'] = Auth::getUser()['rating']; |
| 204 | $tsumegoAttempt['TsumegoAttempt']['gain'] = $result['xp-gained'] ?: 0; |
| 205 | $tsumegoAttempt['TsumegoAttempt']['seconds'] += Decoder::decodeSeconds($previousTsumego); |
| 206 | $tsumegoAttempt['TsumegoAttempt']['solved'] = $result['solved']; |
| 207 | $tsumegoAttempt['TsumegoAttempt']['tsumego_rating'] = $previousTsumego['Tsumego']['rating']; |
| 208 | $tsumegoAttempt['TsumegoAttempt']['misplays'] += $result['misplays'] ?: 0; |
| 209 | $tsumegoAttempt['TsumegoAttempt']['created'] = date('Y-m-d H:i:s'); |
| 210 | ClassRegistry::init('TsumegoAttempt')->save($tsumegoAttempt); |
| 211 | } |
| 212 | |
| 213 | private static function processRatingChangeStep(float &$userRating, float &$tsumegoRating, bool $isWin): void |
| 214 | { |
| 215 | $userRatingDelta = Rating::calculateRatingChange($userRating, $tsumegoRating, $isWin ? 1 : 0, Constants::$PLAYER_RATING_CALCULATION_MODIFIER); |
| 216 | $tsumegoRatingDelta = Rating::calculateRatingChange($tsumegoRating, $userRating, $isWin ? 0 : 1, Constants::$TSUMEGO_RATING_CALCULATION_MODIFIER); |
| 217 | $userRating += $userRatingDelta; |
| 218 | $tsumegoRating += $tsumegoRatingDelta; |
| 219 | } |
| 220 | |
| 221 | private function processRatingChange(array &$previousTsumego, array $result, string $previousTsumegoStatus): void |
| 222 | { |
| 223 | if (!Auth::ratingisGainedInCurrentMode()) |
| 224 | return; |
| 225 | if (!Level::XPAndRatingIsGainedInTsumegoStatus($previousTsumegoStatus)) |
| 226 | return; |
| 227 | $userRating = (float) Auth::getUser()['rating']; |
| 228 | $tsumegoRating = (float) $previousTsumego['Tsumego']['rating']; |
| 229 | |
| 230 | //process misplays first |
| 231 | for ($i = 0; $i < $result['misplays']; $i++) |
| 232 | self::processRatingChangeStep($userRating, $tsumegoRating, false); |
| 233 | |
| 234 | // lastly process the solve |
| 235 | if ($result['solved']) |
| 236 | self::processRatingChangeStep($userRating, $tsumegoRating, true); |
| 237 | |
| 238 | Auth::getUser()['rating'] = $userRating; |
| 239 | Auth::saveUser(); |
| 240 | |
| 241 | $previousTsumego['Tsumego']['rating'] = Util::clampOptional( |
| 242 | $tsumegoRating, |
| 243 | $previousTsumego['Tsumego']['minimum_rating'], |
| 244 | $previousTsumego['Tsumego']['maximum_rating']); |
| 245 | $previousTsumego['Tsumego']['activity_value']++; |
| 246 | ClassRegistry::init('Tsumego')->save($previousTsumego); |
| 247 | } |
| 248 | |
| 249 | private function processDamage(array $result, $previousStatusValue): void |
| 250 | { |
| 251 | if (!$result['misplays']) |
| 252 | return; |
| 253 | if (!Auth::isInLevelMode()) |
| 254 | return; |
| 255 | if (TsumegoUtil::isRecentlySolved($previousStatusValue)) |
| 256 | return; |
| 257 | Auth::getUser()['damage'] += $result['misplays']; |
| 258 | Auth::saveUser(); |
| 259 | } |
| 260 | |
| 261 | private function processXpChange(array $previousTsumego, array &$result, string $previousTsumegoStatus, $originalTsumegoRating): void |
| 262 | { |
| 263 | if (!Auth::XPisGainedInCurrentMode()) |
| 264 | return; |
| 265 | if (!Level::XPAndRatingIsGainedInTsumegoStatus($previousTsumegoStatus)) |
| 266 | return; |
| 267 | if (!$result['solved']) |
| 268 | return; |
| 269 | |
| 270 | $multiplier = ($result['xp-modifier'] ?: 1); |
| 271 | $multiplier *= TsumegoXPAndRating::getProgressDeletionMultiplier(TsumegoUtil::getProgressDeletionCount($previousTsumego['Tsumego'])); |
| 272 | |
| 273 | $user = & Auth::getUser(); |
| 274 | $result['xp-gained'] = Rating::ratingToXP($originalTsumegoRating, $multiplier); |
| 275 | Level::addXPAsResultOfTsumegoSolving($user, $result['xp-gained']); |
| 276 | } |
| 277 | |
| 278 | private function processErrorAchievement(array $result): void |
| 279 | { |
| 280 | $achievementCondition = ClassRegistry::init('AchievementCondition')->find('first', [ |
| 281 | 'order' => 'value DESC', |
| 282 | 'conditions' => [ |
| 283 | 'user_id' => Auth::getUserID(), |
| 284 | 'category' => 'err', |
| 285 | ], |
| 286 | ]); |
| 287 | if (!$achievementCondition) |
| 288 | { |
| 289 | $achievementCondition = []; |
| 290 | ClassRegistry::init('AchievementCondition')->create(); |
| 291 | } |
| 292 | $achievementCondition['AchievementCondition']['category'] = 'err'; |
| 293 | $achievementCondition['AchievementCondition']['user_id'] = Auth::getUserID(); |
| 294 | if ($result['solved']) |
| 295 | $achievementCondition['AchievementCondition']['value']++; |
| 296 | else |
| 297 | $achievementCondition['AchievementCondition']['value'] = 0; |
| 298 | ClassRegistry::init('AchievementCondition')->save($achievementCondition); |
| 299 | } |
| 300 | |
| 301 | private function processUnsortedStuff(array $previousTsumego, array $result): void |
| 302 | { |
| 303 | if (!$result['solved']) |
| 304 | return; |
| 305 | |
| 306 | $solvedTsumegoRank = Rating::getReadableRankFromRating($previousTsumego['Tsumego']['rating']); |
| 307 | AppController::saveDanSolveCondition($solvedTsumegoRank, $previousTsumego['Tsumego']['id']); |
| 308 | AppController::updateGems($solvedTsumegoRank); |
| 309 | if ($_COOKIE['sprint'] == 1) |
| 310 | AppController::updateSprintCondition(true); |
| 311 | else |
| 312 | AppController::updateSprintCondition(); |
| 313 | if ($_COOKIE['type'] == 'g') |
| 314 | AppController::updateGoldenCondition(true); |
| 315 | |
| 316 | Util::clearCookie('sequence'); |
| 317 | Util::clearCookie('type'); |
| 318 | } |
| 319 | |
| 320 | /* @return The number of misplays and consumes the misplays cookie in the process */ |
| 321 | private function checkMisplay(): int |
| 322 | { |
| 323 | return (int) Util::clearCookie('misplays'); |
| 324 | } |
| 325 | } |