Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.47% covered (success)
99.47%
188 / 189
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
TimeModeController
99.46% covered (success)
99.46%
185 / 186
88.89% covered (warning)
88.89%
8 / 9
51
0.00% covered (danger)
0.00%
0 / 1
 start
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 play
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
7
 skip
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getRanksWithTsumegoCount
96.77% covered (success)
96.77%
30 / 31
0.00% covered (danger)
0.00%
0 / 1
8
 overview
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
8
 exportSessionToShow
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
5
 deduceUnlock
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
5
 deduceFinishedSession
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 result
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
1 / 1
10
1<?php
2
3App::uses('TimeModeUtil', 'Utility');
4App::uses('AppException', 'Utility');
5App::uses('Play', 'Controller/Component');
6
7class TimeModeController extends AppController
8{
9    public function start(): mixed
10    {
11        $timeMode = new TimeMode();
12        $categoryID = (int) $this->params['url']['categoryID'];
13        if (!$categoryID)
14            throw new AppException('Time mode category not specified.');
15        $rankID = (int) $this->params['url']['rankID'];
16        if (!$rankID)
17            throw new AppException('Time mode rank not specified.');
18
19        $timeMode->startTimeMode($categoryID, $rankID);
20        return $this->redirect("/timeMode/play");
21    }
22
23    public function play(): mixed
24    {
25        if (!Auth::isLoggedIn())
26            return $this->redirect('/users/login');
27
28        if (!Auth::isInTimeMode())
29        {
30            Auth::getUser()['mode'] = Constants::$TIME_MODE;
31            Auth::saveUser();
32        }
33
34        $timeMode = new TimeMode();
35
36        if (!$timeMode->currentSession)
37            return $this->redirect('/timeMode/overview');
38
39        $tsumegoID = $timeMode->prepareNextToSolve();
40        if ($timeModeSessionID = $timeMode->checkFinishSession())
41            return $this->redirect("/timeMode/result/" . $timeModeSessionID);
42        assert($tsumegoID != null);
43
44        $setConnection = ClassRegistry::init('SetConnection')->find('first', ['conditions' => ['tsumego_id' => $tsumegoID]]);
45        if (!$setConnection)
46            throw new Exception('Time mode session contains tsumego without a set connection.');
47
48        $this->set('timeMode', $timeMode);
49        $this->set('nextLink', '/timeMode/skip/');
50        $this->set('noSkipNextLink', $timeMode->currentWillBeLast() ? '/timeMode/result/' . $timeMode->currentSession['TimeModeSession']['id'] : '/timeMode/play');
51        $play  = new Play(function ($name, $value) { $this->set($name, $value); });
52        $play->play($setConnection['SetConnection']['id'], $this->params, $this->data);
53        $this->render('/Tsumegos/play');
54        return null;
55    }
56
57    public function skip(): mixed
58    {
59        $timeMode = new TimeMode();
60        $timeMode->skip();
61        return $this->play();
62    }
63
64    private function getRanksWithTsumegoCount()
65    {
66        $ranks = ClassRegistry::init('TimeModeRank')->find('all', ['order' => 'id']);
67        $rankPartOfQuery = '';
68        $count = count($ranks);
69        if ($count == 0)
70            return null;
71        if ($count == 1)
72        {
73            $result = [];
74            $rank = $ranks[0]['TimeModeRank'];
75
76            $count = Util::query("
77SELECT
78    COUNT(*) AS count
79FROM
80    tsumego
81    JOIN set_connection ON set_connection.tsumego_id=tsumego.id
82    JOIN `set` ON set_connection.set_id=`set`.id
83WHERE
84    `set`.`included_in_time_mode` = 1 AND
85    `set`.public = 1");
86
87            $rank['tsumego_count'] = $count[0]['count'];
88            $result[] = $rank;
89            return $result;
90        }
91        foreach ($ranks as $index => $rank)
92            if ($index + 1 < $count)
93                $rankPartOfQuery .= 'WHEN rating < ' . Rating::getRankMinimalRating(Rating::getRankFromReadableRank($rank['TimeModeRank']['name']) + 1) . ' THEN \'' . $rank['TimeModeRank']['id'] . '\' ';
94            else
95                $rankPartOfQuery .= 'ELSE \'' . $rank['TimeModeRank']['id'] . '\'';
96        $counts = ClassRegistry::init('Tsumego')->query("
97SELECT
98    CASE " . $rankPartOfQuery . "
99    END AS bucket,
100    COUNT(*) AS count
101FROM
102    tsumego
103    JOIN set_connection ON set_connection.tsumego_id=tsumego.id
104    JOIN `set` ON set_connection.set_id=`set`.id
105WHERE
106    `set`.`included_in_time_mode` = 1 AND
107    `set`.public = 1
108GROUP BY bucket
109ORDER BY MIN(rating);");
110
111        $countsByRankID = [];
112        foreach ($counts as $count)
113            $countsByRankID[$count[0]['bucket']] = $count[0]['count'];
114
115        $result = [];
116        foreach ($ranks as $rank)
117        {
118            $rank = $rank['TimeModeRank'];
119            if ($count = $countsByRankID[$rank['id']])
120                $rank['tsumego_count'] = $count;
121            else
122                $rank['tsumego_count'] = 0;
123            $result [] = $rank;
124        }
125        return $result;
126    }
127
128    public function overview(): mixed
129    {
130        if (!Auth::isLoggedIn())
131            return $this->redirect('/users/login');
132        $this->set('_title', 'Time Mode - Select');
133        $this->set('_page', 'time mode');
134
135        $lastTimeModeCategoryID = Auth::getUser()['last_time_mode_category_id'];
136        if (!$lastTimeModeCategoryID)
137            $lastTimeModeCategoryID = ClassRegistry::init('TimeModeCategory')->find('first', ['order' => 'id DESC'])['TimeModeCategory']['id'];
138        assert($lastTimeModeCategoryID);
139
140        $timeModeRankMap = Util::indexByID(ClassRegistry::init('TimeModeRank')->find('all', []) ?: [], 'TimeModeRank', 'name');
141
142        $timeModeStatuses = ClassRegistry::init('TimeModeSession')->find('all', [
143            'conditions' => [
144                'user_id' => Auth::getUserID(),
145                'time_mode_session_status_id' => TimeModeUtil::$SESSION_STATUS_SOLVED]]) ?: [];
146
147        $solvedMap = [];
148        foreach ($timeModeStatuses as $timeModeStatus)
149        {
150            $timeModeCategoryID = $timeModeStatus['TimeModeSession']['time_mode_category_id'];
151            $timeModeRankID = $timeModeStatus['TimeModeSession']['time_mode_rank_id'];
152            $category = &$solvedMap[$timeModeCategoryID];
153            $category[$timeModeRankID] = $timeModeRankMap[$timeModeStatus['TimeModeSession']['time_mode_rank_id']];
154            if (!isset($category['best-solved-rank']) || $category['best-solved-rank'] < $timeModeRankID)
155                $category['best-solved-rank'] = $timeModeRankID;
156        }
157
158        $this->set('lastTimeModeCategoryID', $lastTimeModeCategoryID);
159        $this->set('timeModeCategories', ClassRegistry::init('TimeModeCategory')->find('all', ['order' => 'id']));
160        $this->set('timeModeRanks', $this->getRanksWithTsumegoCount());
161        $this->set('solvedMap', $solvedMap);
162        $finishedSessionCount = ClassRegistry::init('TimeModeSession')->find('count', ['conditions' => [
163            'user_id' => Auth::getUserID(),
164            'time_mode_session_status_id !=' => TimeModeUtil::$SESSION_STATUS_IN_PROGRESS]]);
165        $this->set('hasFinishedSesssion', $finishedSessionCount > 0);
166        return null;
167    }
168
169    public function exportSessionToShow($session, $timeModeCategory, $timeModeRank): array
170    {
171        $result = [];
172        $result['id'] = $session['TimeModeSession']['id'];
173        $result['status'] = $session['TimeModeSession']['time_mode_session_status_id'] == TimeModeUtil::$SESSION_STATUS_SOLVED ? 'passed' : 'failed';
174        $result['category'] = $timeModeCategory['TimeModeCategory']['name'];
175        $result['points'] = $session['TimeModeSession']['points'];
176        $result['rank'] = $timeModeRank['TimeModeRank']['name'];
177        $date = new DateTime($session['TimeModeSession']['created']);
178        $result['created'] = $date->format('H:i d.m.Y');
179        $timeModeAttempts = ClassRegistry::init('TimeModeAttempt')->find('all', ['conditions' => ['time_mode_session_id' => $session['TimeModeSession']['id']]]) ?: [];
180        $result['attempts'] = [];
181        $solvedCount = 0;
182        foreach ($timeModeAttempts as $timeModeAttempt)
183        {
184            if ($timeModeAttempt['TimeModeAttempt']['time_mode_attempt_status_id'] == TimeModeUtil::$ATTEMPT_RESULT_SOLVED)
185                $solvedCount++;
186            $attempt = [];
187            $setConnection = ClassRegistry::init('SetConnection')->find('first', ['conditions' => ['tsumego_id' => $timeModeAttempt['TimeModeAttempt']['tsumego_id']]]);
188            $set = ClassRegistry::init('Set')->findById($setConnection['SetConnection']['set_id']);
189            $attempt['tsumego_id'] = $timeModeAttempt['TimeModeAttempt']['tsumego_id'];
190            $attempt['set'] = $set['Set']['title'] . ' ' . $set['Set']['title2'];
191            $attempt['set_order'] = $setConnection['SetConnection']['num'];
192            $seconds = $timeModeAttempt['TimeModeAttempt']['seconds'];
193            $minutes = floor($seconds / 60);
194            $seconds -= $minutes * 60;
195            $attempt['seconds'] = $minutes . ' : ' . number_format($seconds, 2);
196            $attempt['points'] = $timeModeAttempt['TimeModeAttempt']['points'];
197            $attempt['order'] = $timeModeAttempt['TimeModeAttempt']['order'];
198            $attempt['status'] = TimeModeUtil::attemptStatusName($timeModeAttempt['TimeModeAttempt']['time_mode_attempt_status_id']);
199            $result['attempts'] [] = $attempt;
200        }
201        $result['solvedCount'] = $solvedCount;
202        return $result;
203    }
204
205    public static function deduceUnlock(?array $finishedSession, array $timeModeRanks, array $timeModeCategories): ?array
206    {
207        if (!$finishedSession)
208            return null;
209
210        // the current session wasn't a success, so it logically can't unlock shit
211        if ($finishedSession['TimeModeSession']['time_mode_session_status_id'] != TimeModeUtil::$SESSION_STATUS_SOLVED)
212            return null;
213
214        // only one successful solve exists for this combination of rank and category, so it must be the one we just did
215        if (ClassRegistry::init('TimeModeSession')->find('count', [
216            'conditions' => [
217                'user_id' => Auth::getUserID(),
218                'time_mode_category_id' => $finishedSession['TimeModeSession']['time_mode_category_id'],
219                'time_mode_rank_id' => $finishedSession['TimeModeSession']['time_mode_rank_id'],
220                'time_mode_session_status_id' => TimeModeUtil::$SESSION_STATUS_SOLVED]]) != 1)
221                    return null;
222
223        $rankIndex = array_find_key($timeModeRanks, function ($timeModeRank) use ($finishedSession) { return $timeModeRank['TimeModeRank']['id'] == $finishedSession['TimeModeSession']['time_mode_rank_id']; });
224
225        if ($rankIndex == 0)
226        {
227            return null; // there is no higher rank to unlock
228        }
229
230        $unlock = [];
231        $unlock['rank'] = $timeModeRanks[$rankIndex - 1]['TimeModeRank']['name'];
232
233        $categoryIndex = array_find_key($timeModeCategories, function ($timeModeCategory) use ($finishedSession) { return $timeModeCategory['TimeModeCategory']['id'] == $finishedSession['TimeModeSession']['time_mode_category_id']; });
234        $unlock['category'] = $timeModeCategories[$categoryIndex]['TimeModeCategory']['name'];
235        return $unlock;
236    }
237
238    private function deduceFinishedSession($passedSessionID, TimeMode $timeMode): ?array
239    {
240        if ($finishedSessionID = $timeMode->checkFinishSession())
241            return ClassRegistry::init('TimeModeSession')->findById($finishedSessionID);
242
243        if ($passedSessionID)
244            if ($result = ClassRegistry::init('TimeModeSession')->find('first', ['conditions' => ['id' => $passedSessionID]]))
245                return $result;
246            else
247                throw new AppException('Time Mode Session not found.');
248        return null;
249    }
250
251    public function result($timeModeSessionID = null): mixed
252    {
253        if (!Auth::isLoggedIn())
254            return $this->redirect("/users/login");
255
256        $timeMode = new TimeMode();
257        $finishedSession = $this->deduceFinishedSession($timeModeSessionID, $timeMode);
258
259        $this->loadModel('Tsumego');
260        $this->loadModel('Set');
261        $this->loadModel('TimeModeSession');
262        $this->loadModel('SetConnection');
263        $this->set('_title', 'Time Mode - Result');
264        $this->set('_page', 'time mode');
265
266        $timeModeCategories = ClassRegistry::init('TimeModeCategory')->find('all', []);
267        $timeModeRanks = ClassRegistry::init('TimeModeRank')->find('all', ['order' => 'id DESC']);
268
269        $sessionsToShow = [];
270        foreach ($timeModeCategories as $timeModeCategory)
271            foreach ($timeModeRanks as $timeModeRank)
272            {
273                $session = ClassRegistry::init('TimeModeSession')->find('first', [
274                    'conditions' => [
275                        'user_id' => Auth::getUserID(),
276                        'time_mode_category_id' => $timeModeCategory['TimeModeCategory']['id'],
277                        'time_mode_rank_id' => $timeModeRank['TimeModeRank']['id']],
278                    'order' => 'points DESC']);
279                $categoryID = $timeModeCategory['TimeModeCategory']['id'];
280                $rankID = $timeModeRank['TimeModeRank']['id'];
281                if (isset($finishedSession)
282                    && $finishedSession['TimeModeSession']['time_mode_category_id'] == $categoryID
283                    && $finishedSession['TimeModeSession']['time_mode_rank_id'] == $rankID)
284                        $sessionsToShow[$categoryID][$rankID]['current'] = $this->exportSessionToShow($finishedSession, $timeModeCategory, $timeModeRank);
285                if (!$session || isset($finishedSession) && $session['TimeModeSession']['id'] == $finishedSession['TimeModeSession']['id'])
286                    continue;
287                $sessionsToShow[$categoryID][$rankID]['best'] = $this->exportSessionToShow($session, $timeModeCategory, $timeModeRank);
288            }
289
290        $this->set('sessionsToShow', $sessionsToShow);
291        $this->set('finishedSession', $finishedSession);
292        $this->set('rankArrowClosed', '/img/greyArrow1.png');
293        $this->set('rankArrowOpened', '/img/greyArrow2.png');
294        $this->set('unlock', self::deduceUnlock($finishedSession, $timeModeRanks, $timeModeCategories));
295        $this->set('achievementUpdates', new AchievementChecker()->checkTimeModeAchievements()->finalize()->updated);
296
297        return null;
298    }
299}