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