11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LINE DCAdvent Calendar 2023

Day 7

柿ピーの比率当てクイズゲームをLINEボットでつくった話

Last updated at Posted at 2023-12-07

はじめに

LINE DC Advent Calendar 2023 の7日目の記事です。

柿ピーの画像を見て「柿の種」と「ピーナッツ」の比率を当てるクイズゲームチャットボットを作ったので、その紹介をします。

亀田製菓の柿の種の「柿の種:ピーナッツ」比率は、以前は6:4でしたが、現在は7:3だそうです (参考)
数値を知ったら「そうなんだー」って感じですが、食べる時に何対何なのか考えたことはありますか?

これをゲームにしてみたら面白そうだと思い、この比率当てゲームを考えました。

作ったもの

こんな感じです。

RPReplay_Final1701952797.gif

ソースコード

構成

バックエンドはAWS Lambda、DynamoDB、S3で作ってます。

image.png

デプロイにはServerless Frameworkを使いました。
OpenCVを使っているので、コンテナイメージでLambda 関数をデプロイしてます。

解説

柿ピー画像生成部

このゲームの核となる、柿ピー画像生成部についてです。

まず、柿の種とピーナッツ画像を用意します。実際にそれぞれ写真を撮って切り抜きました。
kakinotane_photo.png peanut_photo.png

これらの画像を使用し、OpenCVで任意の比率の柿ピー画像を作ります。

1000✕1000の白画像の上に、埋め尽くすように、かつ、場所・角度をランダムさせて配置します。

kakinotane_image.py
import cv2
import numpy as np
import random


# 画像にシャドウを追加する関数
def add_shadow(image):
    alpha = image[:, :, 3]/255
    alpha = alpha[:, :, np.newaxis]  # alphaを3次元配列に変換
    shadow = cv2.GaussianBlur(image[:, :, :3], (15, 15), 0)
    image[:, :, :3] = alpha * image[:, :, :3] + (1 - alpha) * shadow
    return image

# 画像をランダムに回転させる関数
def rotate_image(image):
    angle = random.randint(0,360)
    angle_rad = angle/180.0*np.pi
    h, w = image.shape[:2]

    # 回転後の画像サイズを計算
    w_rot = int(np.round(h*np.absolute(np.sin(angle_rad))+w*np.absolute(np.cos(angle_rad))))
    h_rot = int(np.round(h*np.absolute(np.cos(angle_rad))+w*np.absolute(np.sin(angle_rad))))
    size_rot = (w_rot, h_rot)

    # 元画像の中心を軸に回転する
    center = (w/2, h/2)
    scale = 1.0
    rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)

    # 平行移動を加える (rotation + translation)
    affine_matrix = rotation_matrix.copy()
    affine_matrix[0][2] = affine_matrix[0][2] -w/2 + w_rot/2
    affine_matrix[1][2] = affine_matrix[1][2] -h/2 + h_rot/2

    rotated_image = cv2.warpAffine(image, affine_matrix, size_rot)
    return rotated_image

# 透過画像を背景に配置する関数
def place_image(background, img, x, y):
    #img = rotate_image(img)  # 画像回転
    h, w, _ = img.shape
    alpha = img[:, :, 3]/255
    for c in range(3):
        background[y:y+h, x:x+w, c] = alpha * img[:, :, c] + (1 - alpha) * background[y:y+h, x:x+w, c]
    return background

# 画像をランダムに配置する関数
def place_images_randomly(background, img, n):
    bh, bw, _ = background.shape
    for _ in range(n):
        img_rot = rotate_image(img)
        h_rot, w_rot, _ = img_rot.shape
        x = random.randint(0, bw - w_rot)
        y = random.randint(0, bh - h_rot)
        background = place_image(background, img_rot, x, y)
    return background


def create_kakinotane_image(ratio: float,
                            output_file_name: str = "output.jpg",
                            kakinotane_img_file_name: str = "assets/kakinotane_photo.png",
                            peanut_img_file_name: str = "assets/peanut_photo.png",
                            ):
    # 画像の読み込み
    img_a = cv2.imread(kakinotane_img_file_name, cv2.IMREAD_UNCHANGED)
    img_b = cv2.imread(peanut_img_file_name, cv2.IMREAD_UNCHANGED)

    # 画像のリサイズ
    img_a = cv2.resize(img_a, (img_a.shape[1] // 4, img_a.shape[0] // 4))
    img_b = cv2.resize(img_b, (img_b.shape[1] // 4, img_b.shape[0] // 4))

    # 白背景画像の作成
    background = np.ones((1000, 1000, 3), np.uint8) * 255

    # 配置する回数を計算
    n_total = (background.shape[0]//img_a.shape[0]) * (background.shape[1]//img_a.shape[1])

    # 画像aとbの配置比率を設定
    ratio_a = ratio
    ratio_b = 1 - ratio_a

    # 配置する回数を計算
    n_a = round(n_total * ratio_a)
    n_b = n_total - n_a

    # 画像をランダムに配置
    for i in range(3):
        # N回する。
        background = place_images_randomly(background, img_a, n_a)
        background = place_images_randomly(background, img_b, n_b)

    cv2.imwrite(output_file_name, background)

柿の種 : ピーナッツ = 7 : 3で生成したものがこちら。

image.png

生成した画像を、パブリックアクセスを許可しているS3バケットにアップロードし、そのURLをLINEに返します。

選択肢の作成

難易度ごとに、3択をランダムに作ります。

パラメータ

  • step : 数値のスケール (10%ごと・5%ごとなど)
  • select_range : 数値のレンジ (中央の値からどれだけ離れた値をあと2つ作るか)
def create_selections(step: int, select_range: int = 20) -> tuple:
    percent_1 = random.randrange(MIN_PERCENT + step * 2, MAX_PERCENT - step * 2, step)

    if percent_1 + select_range >= MAX_PERCENT:
        percent_2 = percent_1 + random.randrange(0, MAX_PERCENT - percent_1, step) + step
    else:
        percent_2 = percent_1 + random.randrange(0, select_range, step) + step

    if percent_1 - select_range <= MIN_PERCENT:
        percent_3 = percent_1 - random.randrange(0, percent_1 - MIN_PERCENT, step) - step
    else:
        percent_3 = percent_1 - random.randrange(0, select_range, step) - step

    return percent_1, percent_2, percent_3

クイックリプライにて難易度 (初級・中級・上級) を選んで、その難易度に応じた選択肢を3つ作ります。

そのうち、正解とする選択肢をランダムで選んで、その比率に応じた柿ピー画像を生成します。

# select level
if message_text == "初級":
    step = 10
    sct_range = 20
elif message_text == "中級":
    step = 5
    sct_range = 15
elif message_text == "上級":
    step = 1
    sct_range = 10
else:
    step = 5
    sct_range = 15

# create selections
percent_1, percent_2, percent_3 = create_selections(step, sct_range)

correct_percent = random.choice([percent_1, percent_2, percent_3])

正解の選択肢と画像はユーザーごとに持っており、正解はDynamoDBに格納して答え合わせします。

dynamodb.py
def put_items(table_name: str, items: List[dict]):
    """
    Put items from DynamoDB table
    """
    table = boto3.resource("dynamodb").Table(table_name)

    with table.batch_writer() as writer:
        try:
            for item in items:
                _params = dict()
                _params["Item"] = json.loads(json.dumps(item), parse_float=Decimal)

                table.put_item(**_params)
        except botocore.exceptions.ClientError as e:
            if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
                # 条件NG
                pass
            else:
                raise
# put database
data = {
    "user_id": event.source.user_id,
    "correct_ans": correct_ans,
}
dynamodb.put_item(table_name, data)

その他のコードはリポジトリをご覧ください。

まとめ

画像生成AIが大流行りしている中、世界初の柿ピー画像生成AI(?)を作ってクイズゲームにしてみました。

ボットはこちらです。ぜひ遊んでみてください。

11
1
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
11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?