はじめに
リッチメニューエディターを使えば、ボタンの数も形も関係なくリッチメニューが作成できることを知り、トランプゲームはできないかと思いつきました。
使用したもの
スプレッドシート
Google Apps Script
LINE messaging API
【ルール(通常の神経衰弱と同じ)】
- めくるトランプを選ぶ(リッチメニューをタップ)
- 2枚選び、ペアであれば「1ペア!」違えば「残念」と返ってくる
- 2枚同じトランプは選べない
- 既にペアになったトランプを選ぶと「既に選んだカード」と返ってくる
- 全ペア(ここでは6ペア)作れたらクリア!ランキングに登録すると順位が返ってくる
- 「ゲームを初めからやる」を押すとカードがシャッフルされる
準備
-
LINEアカウントの作成
-
トランプ表面(今回はハートのJQK、クローバーのJQK)の画像のダウンロード
(URLはGithubPagesで画像のホスティングで取得)
ルールに従って実装
まずはuserからアクションがあった時にオリジナルのスプレッドシートをコピーして、userごとのスプレッドシートを作成する。
データベースをユーザーごとに用意するを参考にしました。
①めくるトランプを選ぶ
リッチメニューエディターでトランプごとに領域を区切り、順番に番号を振る。
選ばれたカードの画像をLINEで返す。
const number = Number(json.events[0].message.text);
// 返す画像URL
image = commonSheet.getRange(number + 1, 3).getValue();
commonSheetはこのようになっています。
※H列ではなくK列を使用していることには特に意味はありません。
②-④カードを選んだ時の挙動
2枚選ぶということは1枚目と2枚目の挙動が異なるので、スプレッドシート上で今1枚目なのか2枚目なのかを確認する。
(私はuserSheetに「奇数」「偶数」を表示するセルを作りました。)
- 奇数(1枚目を引いた)の場合
if (userSheet.getRange("G2").getValue() === "奇数") {
messages = [
{
type: "image",
originalContentUrl: image,
previewImageUrl: image,
},
{
type: "text",
text: "次の1枚を選んでください。",
},
];
userSheet.getRange("G2").setValue("偶数");
- 偶数(2枚目を引いた)の場合
奇数の時よりやや複雑になります。- 1枚目と同じカードを引いている場合
- 1枚目と違うカードを引き、絵柄がそろった場合
- これでゲームクリアの場合「クリアです」「○回でクリアできました」「ランキングに登録しますか?」と返す
- クリアではない場合「1ペア成立!」と返す
- 1枚目と2枚目の絵柄が違う場合
1枚目と2枚目のカードの見比べができるように選んだカードをuserSheetに記録していきます。
★既にペアになったカードかどうかの判定
ペアができた時にそのカードを「済」に変更しておく。
commonSheetD列(例えばB2)が=COUNTIF(B2:B13,B2)になっており、ペアになったカードの相方であればD列が1になるため、1になったD列と同じ行のB列を「済」に変更する。
commonSheet.getRange(number + 1, 2).setValue("済");
for (let i = 1; i < 13; i++) {
if(commonSheet.getRange(i + 1, 4).getValue() == 1){
commonSheet.getRange(i + 1, 2).setValue("済")
}
var card = commonSheet.getRange(number + 1, 2).getValue();
if (card === "済") {
messages = [
{
type: "text",
text: "既にペアになったカードです。",
},
];
}
(コードは全体コード↓参照。)
⑤ランキングに登録すると順位が返ってくる
オリジナルのスプレッドシートのprizeSheetから順位を引っ張ってきます。
var prizelastrow = prizeSheet
.getRange(1, 1)
.getNextDataCell(SpreadsheetApp.Direction.DOWN)
.getRow();
prizeSheet.getRange(prizelastrow+1, 1).setValue(json.events[0].postback.data)
prizeSheet.getRange("C1").setValue(json.events[0].postback.data)
var prize = prizeSheet.getRange("C2").getValue()
messages =[
{
type: "text",
text: "あなたの順位は"+prize+"位です!",
}
]
⑥「ゲームを初めからやる」を押すとカードがシャッフルされる
commonSheetにMath.random関数を用いて、カードの順番を変える。
前回選んだカードの記録を全て消す。
そろったペアの数を0にする。
if (usermessage == "ゲームを初めからやる") {
for (let i = 1; i < 13; i++) {
x = Math.random();
commonSheet.getRange(i + 1, 6).setValue(x);
}
for (let i = 1; i < 13; i++) {
commonSheet
.getRange(i + 1, 2)
.setValue(commonSheet.getRange(i + 1, 11).getValue());
}
userSheet.getRange("D3:D302").clear();
userSheet.getRange("G4").setValue(0);
userSheet.getRange("G2").setValue("奇数");
messages =[{ type:"text",text:"スタートです!"}]
}
以上がポイントです。
全体
var CHANNEL_ACCESS_TOKEN ="アクセストークン";
var line_endpoint = "https://api.line.me/v2/bot/message/reply";
var line_endpoint_profile = "https://api.line.me/v2/bot/profile";
var messages;
var image;
const prizeSheet = SpreadsheetApp.openById("originalsheetのID").getSheetByName('prize')
function doPost(e) {
var json = JSON.parse(e.postData.contents);
var reply_token = json.events[0].replyToken;
if (typeof reply_token === "undefined") {
return;
}
if (json.events[0].type == "message") {
var user_id = json.events[0].source.userId;
var SpreadSheet = getSpreadSheet(user_id);
const commonSheet = SpreadSheet.getSheetByName("common");
const userSheet = SpreadSheet.getSheetByName("user");
const usermessage = json.events[0].message.text;
if (usermessage == "ゲームを初めからやる") {
for (let i = 1; i < 13; i++) {
x = Math.random();
commonSheet.getRange(i + 1, 6).setValue(x);
}
for (let i = 1; i < 13; i++) {
commonSheet
.getRange(i + 1, 2)
.setValue(commonSheet.getRange(i + 1, 11).getValue());
}
userSheet.getRange("D3:D302").clear();
userSheet.getRange("G4").setValue(0);
userSheet.getRange("G2").setValue("奇数");
messages =[{ type:"text",text:"スタートです!"}]
} else {
const number = Number(json.events[0].message.text);
// 返す画像URL
image = commonSheet.getRange(number + 1, 3).getValue();
var lastRow = userSheet
.getRange(1, 4)
.getNextDataCell(SpreadsheetApp.Direction.DOWN)
.getRow();
// 何を引いたか
var card = commonSheet.getRange(number + 1, 2).getValue();
if (card === "済") {
messages = [
{
type: "text",
text: "既にペアになったカードです。",
},
];
} else {
userSheet.getRange(lastRow + 1, 4).setValue(card);
userSheet.getRange(lastRow + 1, 5).setValue(number);
userSheet.getRange("A9").setValue(userSheet.getRange("G2").getValue());
if (userSheet.getRange("G2").getValue() === "奇数") {
messages = [
{
type: "image",
originalContentUrl: image,
previewImageUrl: image,
},
{
type: "text",
text: "次の1枚を選んでください。",
},
];
userSheet.getRange("G2").setValue("偶数");
} else {
var last = userSheet.getRange(lastRow, 4).getValue();
var lastnumber = userSheet.getRange(lastRow, 5).getValue();
var now = userSheet.getRange(lastRow + 1, 4).getValue();
if (lastnumber === number) {
messages = [
{
type: "text",
text: "同じカードは選べません。",
},
];
} else if (last === now) {
var pair = userSheet.getRange("G4").getValue();
var newpair = pair + 1;
var clearnumber = userSheet.getRange("G8").getValue();
userSheet.getRange("G4").setValue(newpair);
if (newpair == 6) {
messages = [
{
type: "image",
originalContentUrl: image,
previewImageUrl: image,
},
{
type: "text",
text: "クリアです!!",
},
{
type: "text",
text: clearnumber + "回でクリアできました。",
},
{
type: "text",
text: "ランキングに登録しますか?",
quickReply: {
items: [
{
type: "action",
action: {
type: "postback",
label: "登録する",
text: "ランキングに登録しました。",
data: clearnumber,
},
},
{
type: "action",
action: {
type: "message",
label: "しない",
text: "登録せずに記録を破棄します。"
}
},
],
},
},
];
} else {
messages = [
{
type: "image",
originalContentUrl: image,
previewImageUrl: image,
},
{
type: "text",
text: "1ペア成立!!",
},
];
commonSheet.getRange(number + 1, 2).setValue("済");
for (let i = 1; i < 13; i++) {
if(commonSheet.getRange(i + 1, 4).getValue() == 1){
commonSheet.getRange(i + 1, 2).setValue("済")
}
}
userSheet.getRange("G2").setValue("奇数");
}
} else if (lastRow == 299) {
messages = [{ type: "text", text: "ゲームオーバーです。" }];
userSheet.getRange("D3:D302").clear();
userSheet.getRange("G4").setValue(0);
userSheet.getRange("G2").setValue("奇数");
for (let i = 1; i < 13; i++) {
commonSheet
.getRange(i + 1, 2)
.setValue(commonSheet.getRange(i + 1, 11).getValue());
}
} else {
messages = [
{
type: "image",
originalContentUrl: image,
previewImageUrl: image,
},
{
type: "text",
text: "残念。頑張って!!",
},
];
userSheet.getRange("G2").setValue("奇数");
}
}
}
}
}else if(json.events[0].type == "postback"){
var prizelastrow = prizeSheet
.getRange(1, 1)
.getNextDataCell(SpreadsheetApp.Direction.DOWN)
.getRow();
prizeSheet.getRange(prizelastrow+1, 1).setValue(json.events[0].postback.data)
prizeSheet.getRange("C1").setValue(json.events[0].postback.data)
var prize = prizeSheet.getRange("C2").getValue()
messages =[
{
type: "text",
text: "あなたの順位は"+prize+"位です!",
}
]
}
// bagSheet.getRange(3,1,100).clear()
UrlFetchApp.fetch(line_endpoint, {
headers: {
"Content-Type": "application/json; charset=UTF-8",
Authorization: "Bearer " + CHANNEL_ACCESS_TOKEN,
},
method: "post",
payload: JSON.stringify({
replyToken: reply_token,
messages: messages,
}),
});
return ContentService.createTextOutput(
JSON.stringify({ content: "post ok" })
).setMimeType(ContentService.MimeType.JSON);
}
function getSpreadSheet(user_id) {
var sid = PropertiesService.getScriptProperties().getProperty(user_id);
if (sid == null) {
return createSpreadSheet(user_id);
} else {
try {
return SpreadsheetApp.openById(sid);
} catch (e) {
return createSpreadSheet(user_id);
}
}
}
function createSpreadSheet(user_id) {
var ori_sheet = SpreadsheetApp.openById(
"originalSheetのID"
);
var spreadSheet = ori_sheet.copy(getUserDisplayName(user_id));
PropertiesService.getScriptProperties().setProperty(
user_id,
spreadSheet.getId()
);
var file = DriveApp.getFileById(spreadSheet.getId());
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
// logSheet.getRange("A2").setValue("createsheet")
return spreadSheet;
}
function getUserDisplayName(user_id) {
var res = UrlFetchApp.fetch(line_endpoint_profile + "/" + user_id, {
headers: {
"Content-Type": "application/json; charset=UTF-8",
Authorization: "Bearer " + CHANNEL_ACCESS_TOKEN,
},
method: "get",
});
return JSON.parse(res).displayName;
}
おわりに
各部分でもっといいやり方がありそうな気がしますが、自分の実力ではこれが限界でした。
ぜひアドバイスなどあればコメントお願いします!!!!