4 private static $sort_mode_list = ['default' => 'score', 'newcome'];
6 public function __construct() {
7 $this->dbh = new PDO('sqlite:db/score.db');
8 $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
10 $this->set_sort_mode(filter_input(INPUT_GET, 'sort'));
16 * @param string $mode 設定する表示モード
17 * 'score' - スコア順に表示(デフォルト)
19 * 存在しない表示モードが指定された場合デフォルトの表示モードが設定される
21 public function set_sort_mode($mode)
23 if ($mode !== NULL && in_array($mode, self::$sort_mode_list)) {
24 $this->sort_mode = $mode;
26 $this->sort_mode = self::$sort_mode_list['default'];
34 * @return string スコア表示モード名
36 public function get_sort_mode_name()
38 switch ($this->sort_mode) {
50 * スコア検索の絞り込み用WHERE句を取得する
52 * @return string スコア検索絞り込み用のWHERE句
54 private function get_where()
56 $last_days = filter_input(INPUT_GET, 'last_days', FILTER_VALIDATE_INT);
58 $wheres[] = "date >= datetime('now', 'localtime', '-{$last_days} days')";
61 foreach (['race_id', 'class_id', 'personality_id'] as $id_str) {
62 $id = filter_input(INPUT_GET, $id_str, FILTER_VALIDATE_INT);
64 $wheres[] = "{$id_str} = {$id}";
68 $where = isset($wheres) ? 'WHERE '.implode(' AND ', $wheres) : '';
75 * スコアソート用のORDER BY句を取得する
77 * @return string スコアソート用のORDER BY句
79 private function get_order_by()
81 switch ($this->sort_mode) {
83 $order_by = "ORDER BY score DESC";
86 $order_by = 'ORDER BY score_id DESC';
97 * @param integer $offset スコア取得開始位置
98 * @param integer $limit スコア取得最大件数
99 * @param string $where スコア検索に用いるWHERE句
100 * @param string $order_by スコアソートに用いるORDER BY句
101 * @return string スコア検索用SQLクエリ
103 private function get_search_query($offset, $limit, $where, $order_by)
108 group_concat(realm_name) AS realms_name,
110 WHEN killer = 'ripe' THEN '勝利の後引退'
111 WHEN killer = 'Seppuku' THEN '勝利の後切腹'
112 ELSE killer || 'に殺された'
121 LIMIT {$offset}, {$limit}) AS s
141 * 検索でヒットしたスコアの総件数を取得する
143 * @param string $where スコア検索に用いるWHERE句
144 * @return integer スコア総件数
146 private function get_query_data_count($where)
148 $stmt = $this->dbh->query("SELECT count(*) AS data_count from scores {$where}");
149 $res = $stmt->fetchAll(PDO::FETCH_ASSOC);
151 return intval($res[0]['data_count']);
158 * @param integer $start_num 検索するスコアの開始位置
159 * @param integer $count 検索するスコア数
161 * @return array 検索したスコアの配列と、条件に合致するスコアの総件数の連想配列
163 public function search_score($start_num, $count)
165 $where = $this->get_where();
166 $order_by = $this->get_order_by();
167 $query_sql = $this->get_search_query(intval($start_num / $count) * $count, $count, $where, $order_by);
169 $search_start_time = microtime(true);
170 $this->dbh->beginTransaction();
172 $score_stmt = $this->dbh->query($query_sql);
173 $result['scores'] = $score_stmt->fetchAll(PDO::FETCH_ASSOC);
174 $result['total_data_count'] = $this->get_query_data_count($where);
176 $this->dbh->commit();
177 $result['elapsed_time'] = microtime(true) - $search_start_time;
182 public function get_db_handle() {
186 private function update_killers_cache_table()
189 $this->dbh->beginTransaction();
191 $this->dbh->exec("DROP TABLE IF EXISTS killers_cache");
192 $this->dbh->exec(<<<EOM
198 count(*) AS killer_count_total,
199 count(killed_status = 0 OR NULL) AS killer_count_normal,
200 count(killed_status != 0 OR NULL) AS killer_count_freeze
204 WHEN killer LIKE '麻痺状態で%' THEN substr(killer, 6)
205 WHEN killer LIKE '彫像状態で%' THEN substr(killer, 6)
206 WHEN killer = 'ripe' THEN '引退'
207 WHEN killer = 'Seppuku' THEN '切腹'
211 WHEN killer LIKE '麻痺状態で%' THEN 1
212 WHEN killer LIKE '彫像状態で%' THEN 2
221 killer_count_total DESC
224 $this->dbh->commit();
227 } catch (PDOException $e) {
228 $this->dbh->rollBack();
234 public function get_killers_table()
236 //$this->update_cache_status('killers_cache', 0);
237 if (!$this->get_cache_status('killers_cache')) {
238 $this->update_killers_cache_table();
239 $this->update_cache_status('killers_cache', 1);
242 $killers = $this->dbh->query("SELECT * FROM killers_cache ORDER BY killer_count_total DESC")->fetchAll(PDO::FETCH_ASSOC);
249 * 統計情報のキャッシュテーブルを更新する
251 * 種族・職業・性格について各種統計情報を取得しキャッシュテーブルに保存する
252 * 通常の統計情報の取得はこのキャッシュテーブルから行う
254 * @return boolean 生成に成功したらTRUE、失敗したらFALSE
256 private function update_statistics_cache_tables() {
257 $statistics_list = ['race', 'class', 'personality'];
260 foreach ($statistics_list as $stat) {
261 $table_name = $stat."_statistics";
262 $this->dbh->exec("DROP TABLE IF EXISTS ".$table_name);
263 $stmt = $this->dbh->query(
265 CREATE TABLE $table_name AS
268 count(sex=1 or NULL) AS male_count,
269 count(sex=0 or NULL) AS female_count,
270 count(*) AS total_count,
271 count(winner=1 OR NULL) AS winner_count,
272 avg(score) AS average_score,
273 max(score) AS max_score
281 } catch (PDOException $e) {
290 * @param integer $sort_key_column 表示順序のキーとするカラムの名称
294 public function get_statistics_tables($sort_key_column) {
295 if (!$this->get_cache_status('statistics_cache')) {
296 $result = $this->update_statistics_cache_tables();
297 $this->update_cache_status('statistics_cache', $result ? 1 : 0);
302 $this->dbh->beginTransaction();
303 foreach ([['race', 'races'], ['class', 'classes'], ['personality', 'personalities']] as $kind) {
304 $stmt = $this->dbh->query("SELECT ${kind[0]}_id AS id, ${kind[0]}_name AS name, * FROM ${kind[0]}_statistics NATURAL JOIN ${kind[1]} ORDER BY ${sort_key_column} DESC");
305 $stat[$kind[0]] = $stmt->fetchAll(PDO::FETCH_ASSOC);
307 $this->dbh->commit();
312 public function table_exists($table_name)
314 $stmt = $this->dbh->prepare("SELECT count(*) AS table_exists FROM sqlite_master WHERE type='table' AND name=?");
315 $stmt->execute([$table_name]);
316 $res = $stmt->fetchAll(PDO::FETCH_ASSOC);
318 return intval($res[0]['table_exists']) === 1;
322 public function get_cache_status($cache_table_name)
324 if (!$this->table_exists('cache_status_table')) {
325 $this->create_cache_status_table();
328 $stmt = $this->dbh->prepare("SELECT cached FROM cache_status_table WHERE table_name=?");
329 $stmt->execute([$cache_table_name]);
330 $res = $stmt->fetchAll(PDO::FETCH_ASSOC);
332 return count($res) === 1 ? intval($res[0]['cached']) : 0;
335 public function update_cache_status($cache_table_name, $status)
337 if (!$this->table_exists('cache_status_table')) {
338 $this->create_cache_status_table();
341 $stmt = $this->dbh->prepare("INSERT OR REPLACE INTO cache_status_table VALUES(?, ?)");
342 $stmt->execute([$cache_table_name, $status]);
344 return $stmt->rowCount();
347 public function create_cache_status_table()
349 $this->dbh->exec(<<<EOM
350 CREATE TABLE cache_status_table
351 (table_name TEXT PRIMARY KEY,
357 public function register_new_score($score_data)
359 $insert_stmt = $this->dbh->prepare(
362 (version, score, name,
363 race_id, class_id, personality_id,
364 sex, level, depth, maxlv, maxdp,
365 au, turns, winner, killer)
367 :version, :score, :name,
368 race_id, class_id, personality_id,
369 :sex, :level, :depth, :maxlv, :maxdp,
370 :au, :turns, :winner, :killer
372 races, classes, personalities
374 race_name = :race AND
375 class_name = :class AND
376 personality_name = :personality
380 $realm_insert_stmt = $this->dbh->prepare(
382 INSERT INTO score_realms
394 $this->dbh->beginTransaction();
395 if ($insert_stmt->execute($score_data['character_info']) === FALSE ||
396 $insert_stmt->rowCount() !== 1) {
401 // NOTE: score_idはINTEGER PRIMARY KEYなのでROWIDを参照している
402 // したがってlastInsertIdで追加されたスコアのscore_idを取得可能
403 $score_id = $this->dbh->lastInsertId();
405 foreach ($score_data['realm_info'] as $realm_name) {
406 if ($realm_insert_stmt->execute([$score_id, $realm_name]) === FALSE ||
407 $realm_insert_stmt->rowCount() !== 1) {
413 // スコアが追加されたので死因ランキング・人気ランキングのキャッシュをクリア
414 $this->update_cache_status('killers_cache', 0);
415 $this->update_cache_status('statistics_cache', 0);
416 $this->dbh->commit();
419 } catch (PDOException $e) {