非エンジニアがChatGPTと麻雀ゲームを作ってみた

ブログサムネ 麻雀

ChatGPTを使っていて、そういえばゲームとか作らせることができるらしいと聞いたので、やってみました。

麻雀ゲームと書いてますが、半荘戦などをやる麻雀のゲームはすでにたくさん良いものがあるので、自分の学習補助になるような内容のものを想定しています。

中略

いきなりコードを出してくれた!

麻雀とは?とか教えなくていいところが良い〜

Phythonで書いてくれたので、Phythonが動かせる環境があれば遊べそう。

Phythonというのはプログラミング言語のひとつで、書きやすく読みやすくライブラリがたくさんあるのが特徴らしい。

環境構築とかわからないよ〜と思っていて調べたら、Google Colabというものを使えばブラウザ上で実行できるらしいとわかりました。

Google Colab の準備 - python.jp
まずは、Colabを使えるように準備しましょう。 準備といっても、次の2ステップだけで、簡単に完了します。 Googleアカウントを用意し、ログインする Colabにアクセスし、プログラムを書き込むノートブックを作成する Googleアカウ...

こちらのサイトを参考に、出してくれたコードを貼り付けて実行してみました!

アガリ形って思ってたのと違う!(牌姿を出して欲しかった)とか、ドラがあってリーチは30符とは限らないだろ、とかツッコミどころはあるけど、一旦無視して実行!

できた!!!!! あまりに簡単! 思いついてから15分くらい?

でもこのゲーム…… 残念ながら全然面白くありません。
UIがあったり牌姿が入ったり演出があったとしてもそんなに面白くなさそうなので、企画が悪そうです。

ということで、少し真面目に企画を考えてみました。

字が汚いのはご愛嬌。
最終的に時間で加算されるボーナスを作ったりして数問やっての合計スコアを競うゲームなんかいいんじゃないかなと思ってます。

これを言語化してChatGPTに伝えます。(役とかはおいおいで、まずは骨組みを作ってみる。)

↓長いので読み飛ばしてください。

import random

# ターツの候補とそれに対応する符
tarts_choices = [
    {"name": "刻子(中張牌)", "fu": 4},
    {"name": "刻子(ヤオ九字牌)", "fu": 8},
    {"name": "順子", "fu": 0},
]

# 雀頭の候補
head_choices = [
    {"name": "雀頭(役牌)", "fu": 2},
    {"name": "雀頭(役なし)", "fu": 0},
]

# 待ちの候補とそれに対応する符
waiting_choices = [
    {"name": "リャンメン", "fu": 0},
    {"name": "カンチャン", "fu": 2},
    {"name": "ペンチャン", "fu": 2},
]

# ツモかロンかの選択肢(符)
agari_choices = [
    {"name": "ツモ", "fu": 2},
    {"name": "ロン", "fu": 0},
]

# 符の目標をランダムに設定
def display_goal_fu():
    goal_fu = random.choice([20, 30, 40, 50])
    print(f"目標の符は {goal_fu} です。")
    return goal_fu

# 雀頭を選ぶフェーズ
def select_head():
    print("最初に雀頭を選んでください:")
    for idx, head in enumerate(head_choices, 1):
        print(f"{idx}: {head['name']} (符: {head['fu']})")

    try:
        selected_idx = int(input("番号を入力してください(1~2): ")) - 1
        selected_head = head_choices[selected_idx]
        return selected_head["fu"]
    except (ValueError, IndexError):
        print("無効な入力です。再度入力してください。")
        return select_head()

# ターツを選ぶフェーズ
def select_tarts():
    total_fu = select_head()  # 最初に雀頭を選ぶ
    for i in range(3):  # 残りの3メンツを選ぶ
        print(f"第 {i+1} メンツを選んでください:")
        choices = random.sample(tarts_choices, 3)
        for idx, tart in enumerate(choices, 1):
            print(f"{idx}: {tart['name']} (符: {tart['fu']})")

        try:
            selected_idx = int(input("番号を入力してください(1~3): ")) - 1
            selected_tart = choices[selected_idx]
            total_fu += selected_tart["fu"]
        except (ValueError, IndexError):
            print("無効な入力です。再度入力してください。")
            continue
    return total_fu

# 待ちを選ぶフェーズ
def select_waiting():
    print("待ちの形を選んでください:")
    for idx, waiting in enumerate(waiting_choices, 1):
        print(f"{idx}: {waiting['name']} (符: {waiting['fu']})")

    try:
        selected_idx = int(input("番号を入力してください(1~3): ")) - 1
        selected_waiting = waiting_choices[selected_idx]
        return selected_waiting["fu"]
    except (ValueError, IndexError):
        print("無効な入力です。再度入力してください。")
        return select_waiting()

# アガリ形を選ぶフェーズ
def select_agari():
    print("アガリの方法を選んでください:")
    for idx, agari in enumerate(agari_choices, 1):
        print(f"{idx}: {agari['name']} (符: {agari['fu']})")

    try:
        selected_idx = int(input("番号を入力してください(1~2): ")) - 1
        selected_agari = agari_choices[selected_idx]
        return selected_agari["fu"]
    except (ValueError, IndexError):
        print("無効な入力です。再度入力してください。")
        return select_agari()

# 符の計算
def calculate_final_fu(total_fu):
    # 1の位を切り上げる
    final_fu = (total_fu + 9) // 10 * 10
    return final_fu

# ゲームのメインロジック
def mahjong_fu_game():
    while True:
        goal_fu = display_goal_fu()
        total_fu = select_tarts()  # ターツ選択での符を合計
        total_fu += select_waiting()  # 待ち選択で符を加算
        total_fu += select_agari()  # アガリ方法の符を加算
        final_fu = calculate_final_fu(total_fu)

        print(f"最終的な符は {final_fu} です。")

        if final_fu == goal_fu:
            print("成功です!目標の符と一致しました!")
        else:
            print(f"失敗です。目標の符は {goal_fu} でしたが、あなたの符は {final_fu} でした。")

        replay = input("もう一度遊びますか?(はい/いいえ): ").strip().lower()
        if replay != "はい":
            print("ゲームを終了します。ありがとうございました!")
            break

# ゲーム開始
mahjong_fu_game()

雀頭とターツ、せめて「234m」みたいにして欲しかったんだけど、まあ一旦置いておいて……

実行してみた↓

(選択の数字が入れ替わっていることに気付かず、メンツ選択で「1」を連打してミスっています!)

そういえばピンフツモを教えるの忘れたな〜と思っていたら「あなたの符は10符でした」と表示されて、そんなことないよ!となりました。
というわけで上記2点を修正指示します。

こう書くと副底ムシして0符になっちゃったので、あとで直しました。

できた!(たぶん)

これに肉付けをして画像を使ったりするともっとゲームっぽくなるんだろうな〜
(どうやるのかはわからない)
というところで一定の満足を得たので筆を置きます。

読んでいただきありがとうございました。


この荒削りすぎるゲームで遊んでみたい方は、下記のソースをコピペしてみてください。
(ロンが0符だったりしたのでちょっと修正しています。)

import random

# ターツの候補とそれに対応する符
tarts_choices = [
    {"name": "刻子(中張牌)", "fu": 4},
    {"name": "刻子(ヤオ九字牌)", "fu": 8},
    {"name": "順子", "fu": 0},
]

# 雀頭の候補
head_choices = [
    {"name": "雀頭(役牌)", "fu": 2},
    {"name": "雀頭(役なし)", "fu": 0},
]

# 待ちの候補とそれに対応する符
waiting_choices = [
    {"name": "リャンメン", "fu": 0},
    {"name": "カンチャン", "fu": 2},
    {"name": "ペンチャン", "fu": 2},
]

# ツモかロンかの選択肢(符)
agari_choices = [
    {"name": "ツモ", "fu": 2},
    {"name": "ロン", "fu": 10},  # ロンは10符
]

# 符の目標をランダムに設定
def display_goal_fu():
    goal_fu = random.choice([20, 30, 40, 50])
    print(f"目標の符は {goal_fu} です。")
    return goal_fu

# 雀頭を選ぶフェーズ
def select_head():
    print("最初に雀頭を選んでください:")
    for idx, head in enumerate(head_choices, 1):
        print(f"{idx}: {head['name']} (符: {head['fu']})")

    try:
        selected_idx = int(input("番号を入力してください(1~2): ")) - 1
        selected_head = head_choices[selected_idx]
        return selected_head
    except (ValueError, IndexError):
        print("無効な入力です。再度入力してください。")
        return select_head()

# ターツを選ぶフェーズ
def select_tarts():
    total_fu = 20  # 最初に副底の20符を加算
    selected_tarts = []  # 選ばれたターツを保存

    head = select_head()  # 最初に雀頭を選ぶ
    total_fu += head["fu"]

    for i in range(3):  # 残りの3メンツを選ぶ
        print(f"第 {i+1} メンツを選んでください:")
        choices = random.sample(tarts_choices, 3)
        for idx, tart in enumerate(choices, 1):
            print(f"{idx}: {tart['name']} (符: {tart['fu']})")

        try:
            selected_idx = int(input("番号を入力してください(1~3): ")) - 1
            selected_tart = choices[selected_idx]
            selected_tarts.append(selected_tart)
            total_fu += selected_tart["fu"]
        except (ValueError, IndexError):
            print("無効な入力です。再度入力してください。")
            continue

    return total_fu, selected_tarts, head

# 待ちを選ぶフェーズ
def select_waiting():
    print("待ちの形を選んでください:")
    for idx, waiting in enumerate(waiting_choices, 1):
        print(f"{idx}: {waiting['name']} (符: {waiting['fu']})")

    try:
        selected_idx = int(input("番号を入力してください(1~3): ")) - 1
        selected_waiting = waiting_choices[selected_idx]
        return selected_waiting
    except (ValueError, IndexError):
        print("無効な入力です。再度入力してください。")
        return select_waiting()

# アガリ形を選ぶフェーズ
def select_agari():
    print("アガリの方法を選んでください:")
    for idx, agari in enumerate(agari_choices, 1):
        print(f"{idx}: {agari['name']} (符: {agari['fu']})")

    try:
        selected_idx = int(input("番号を入力してください(1~2): ")) - 1
        selected_agari = agari_choices[selected_idx]
        return selected_agari
    except (ValueError, IndexError):
        print("無効な入力です。再度入力してください。")
        return select_agari()

# ピンフツモのチェック
def check_pinfu(head, tarts, waiting, agari):
    # 雀頭が役なし、すべてのターツが順子、待ちがリャンメン、アガリがツモ
    if head["fu"] == 0 and all(tart["name"] == "順子" for tart in tarts) and waiting["name"] == "リャンメン" and agari["name"] == "ツモ":
        return True
    return False

# 符の計算
def calculate_final_fu(total_fu, head, tarts, waiting, agari):
    # ピンフツモなら符を20にする
    if check_pinfu(head, tarts, waiting, agari):
        print("ピンフツモです。符は 20 となります。")
        return 20, 20

    # 通常の符計算
    final_fu = total_fu
    rounded_fu = (final_fu + 9) // 10 * 10  # 1の位を切り上げる
    return final_fu, rounded_fu

# ゲームのメインロジック
def mahjong_fu_game():
    while True:
        goal_fu = display_goal_fu()
        total_fu, selected_tarts, selected_head = select_tarts()  # ターツ選択での符を合計
        selected_waiting = select_waiting()  # 待ち選択で符を加算
        total_fu += selected_waiting["fu"]
        selected_agari = select_agari()  # アガリ方法の符を加算
        total_fu += selected_agari["fu"]

        final_fu, rounded_fu = calculate_final_fu(total_fu, selected_head, selected_tarts, selected_waiting, selected_agari)

        print(f"切り上げ前の符は {final_fu} です。")
        print(f"最終的な符は {rounded_fu} です。")

        if rounded_fu == goal_fu:
            print("成功です!目標の符と一致しました!")
        else:
            print(f"失敗です。目標の符は {goal_fu} でしたが、あなたの符は {rounded_fu} でした。")

        replay = input("もう一度遊びますか?(はい/いいえ): ").strip().lower()
        if replay != "はい":
            print("ゲームを終了します。ありがとうございました!")
            break

# ゲーム開始
mahjong_fu_game()

ゲームっぽくするためにもっとこうした方がいいかな〜という思い付き

  • UIを作って操作をボタンにする
  • ターツが牌姿の画像で表示されるようにする
  • 3択の段階では符は表示されず、時間経過で浮かび上がってくるようにする
  • 選択したターツの牌姿画像がUI上に並んでいくようにする(手牌を作ってる感)
  • 得点の概念を入れる。正解で加点。
  • 1ターツ選択n秒より少なければ少ないほど加点、多ければ多いほど減点する
    (上限あり。時間目一杯でも正解していればある程度得点が付くようにする。)
  • n秒までのカウントダウンと上限までのカウントアップをターツ選択中に都度表示するようにし、選択した時点で「+n pt」「-n pt」と加点・減点演出が表示されるようにする
  • 不正解はその問題のタイムボーナスが全部無効になる。
  • 3問とか5問とかの規定問題数をクリアした時点での得点を記録して、ハイスコアを競うようにする。
  • 役の概念を入れ、役なしではロンの選択肢が出ないようにする
  • 選択肢に鳴きターツを入れる。(役なしの時は、アガリ形はハイテイツモということにする)
  • 選択肢に槓子を入れる。(60符とか必要な時に出てくる)

コメント

タイトルとURLをコピーしました