10
3

More than 1 year has passed since last update.

LINEでミニ神経衰弱を作った【GAS✖️スプレッドシート 】

Last updated at Posted at 2022-07-18

はじめに

リッチメニューエディターを使えば、ボタンの数も形も関係なくリッチメニューが作成できることを知り、トランプゲームはできないかと思いつきました。

使用したもの

スプレッドシート
Google Apps Script
LINE messaging API

【ルール(通常の神経衰弱と同じ)】

  1. めくるトランプを選ぶ(リッチメニューをタップ)
  2. 2枚選び、ペアであれば「1ペア!」違えば「残念」と返ってくる
  3. 2枚同じトランプは選べない
  4. 既にペアになったトランプを選ぶと「既に選んだカード」と返ってくる
  5. 全ペア(ここでは6ペア)作れたらクリア!ランキングに登録すると順位が返ってくる
  6. 「ゲームを初めからやる」を押すとカードがシャッフルされる

準備

  • スプレッドシートの作成→中に"common", "user", "prize"の3シートを準備スクリーンショット 2022-07-18 18.59.45.png

  • 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はこのようになっています。神経衰弱qiita用commonsheet.png
※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に記録していきます。
神経衰弱userSheet.png

★既にペアになったカードかどうかの判定
ペアができた時にそのカードを「済」に変更しておく。
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+"位です!",
        }
      ]

prizeSheetはこんな感じです。神経衰弱prizeSheet.png

⑥「ゲームを初めからやる」を押すとカードがシャッフルされる

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;
}

おわりに

各部分でもっといいやり方がありそうな気がしますが、自分の実力ではこれが限界でした。
ぜひアドバイスなどあればコメントお願いします!!!!

10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3