-
Notifications
You must be signed in to change notification settings - Fork 5
/
Leaderboard.gs
218 lines (187 loc) · 4.95 KB
/
Leaderboard.gs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
const Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const LeaderboardSheet = Spreadsheet.getSheets()[0];
const QuerySheet = Spreadsheet.getSheets()[1];
const ResultSheet = Spreadsheet.getSheets()[2];
const DEFAULT_SCORE = 0; // スコアの読み取りに失敗した場合に設定する値
const TIMEZONE = "Asia/Tokyo"; // タイムスタンプのタイムゾーン
const ANONYMOUS_NAME = "[匿名]"; // 匿名ユーザーの表示名
const RECORD_LIMIT = 100; // レコードを取得できる上限数 (サーバー負荷を抑えるため)
/**
* シート「検索クエリ」のセルB2に値を設定
* @param {string} value クエリ
*/
function setSearchQuery(value)
{
QuerySheet.getRange(2, 2).setValue(value);
}
/**
* 値をnumber型に変換し、失敗した場合はDEFAULT_SCOREを返す
* @param {any} value ユーザー名
*/
function parseScore(value)
{
if (typeof(value) === "number" &&
isFinite(value))
{
return value;
}
if (typeof(value) === "string" &&
value.match(/^[+-]?(\d*[.])?\d+$/g))
{
return parseFloat(value);
}
return DEFAULT_SCORE;
}
/**
* スコアを追加
* @param {string} username ユーザー名
* @param {number} score スコア
* @param {any} data 追加データ
*/
function pushScore(username, score, data=undefined)
{
// 日本時間、現在のタイムスタンプを作成
let timestamp = Utilities.formatDate(new Date(), TIMEZONE, 'yyyy-MM-dd HH:mm:ss');
// ユーザー名の型が文字列型ではないか、
// 空の場合は匿名にする
if (typeof(username) !== "string" ||
username.length == 0)
{
username = ANONYMOUS_NAME;
}
// スコアを読み込む
score = parseScore(score);
// スコアデータをシートに追加
LeaderboardSheet.appendRow([
timestamp, username, score, JSON.stringify(data)
]);
}
/**
* 上位のスコアを取得
* @param {number} limit 取得最大個数
* @return {{ username: string, score: number }[]} スコアの一覧
*/
function getTopScores(limit = null)
{
if (typeof(limit) !== "number" ||
limit > RECORD_LIMIT)
{
limit = RECORD_LIMIT;
}
// クエリ文字列を設定
setSearchQuery(`select B, C, D where A is not null order by C desc limit ${limit}`);
let srcData = ResultSheet.getDataRange().getValues();
let labelList = srcData.shift();
let dstData = srcData;
// データの形式を変換
dstData = dstData.map(row => {
let obj = { };
row.forEach((value, i) => {
obj[labelList[i]] = value;
});
return obj;
});
// 特定のキーの型を変換
dstData = dstData.map(record => {
if ("username" in record)
{
record.username = String(record.username);
}
if ("score" in record)
{
record.score = parseScore(record.score);
}
if ("data" in record)
{
try
{
record.data = JSON.parse(record.data);
}
catch(SyntaxError)
{
record.data = undefined;
}
}
return record;
});
return dstData;
}
/**
* GETリクエスト処理
*/
function doGet(e)
{
// 取得個数
let count = null;
if ("count" in e.parameter &&
e.parameter.count.match(/^\d+$/g))
{
count = parseInt(e.parameter.count, 10);
}
// リーダーボードを取得
let leaderboard = getTopScores(count);
// リーダーボードをJSONで送信
return ContentService.createTextOutput()
.setContent(JSON.stringify(leaderboard))
.setMimeType(ContentService.MimeType.JSON);
}
/**
* POSTリクエスト処理
*/
function doPost(e)
{
// エラーメッセージ
let errorMessages = [];
// 追加データ
let parsedData = undefined;
// クエリのバリデーションと例外処理
if (!("username" in e.parameter))
{
errorMessages.push("パラメータusernameが存在しません");
}
if (!("score" in e.parameter))
{
errorMessages.push("パラメータscoreが存在しません");
}
if ("data" in e.parameter)
{
try
{
parsedData = JSON.parse(e.parameter.data);
}
catch (SyntaxError)
{
errorMessages.push("パラメータdataの文法が間違っています");
}
}
if (errorMessages.length > 0)
{
// 異常終了
throw Error(errorMessages.join("\n"));
}
// スコアを追加
pushScore(e.parameter.username, e.parameter.score, parsedData);
// 正常終了
return ContentService.createTextOutput("OK");
}
function pushScoreTest1()
{
const users = [ "太郎", "一郎", "二郎", "三郎" ];
let user = users[Math.floor(Math.random() * users.length)];
pushScore(user, Math.random() * 100);
}
function pushScoreTest2()
{
pushScore("", Math.random() * 100);
}
function pushScoreTest3()
{
const users = [ "太郎", "一郎", "二郎", "三郎" ];
let user = users[Math.floor(Math.random() * users.length)];
pushScore(user, Math.random() * 100, {hoge1:"test"});
}
function getTopScoresTest()
{
Logger.log(JSON.stringify(getTopScores()));
Logger.log(JSON.stringify(getTopScores(3)));
}