2
3

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.

ボードゲームをWEB化してみたシリーズ<THE GAME>

Posted at

あけましておめでとうございます。
毎年ながら、相当暇なときにしか記事を書いていませんが、今年も例の如くネタを提供していければと思います。

今回は、以前、リアルボードゲームでいくつか興味を持ったゲームを、コロナのリモート時代にオンラインで楽しめるように、Herokuを使ってPythonで作ってみました。

実は、すでに10種類ぐらい作っているので、徐々に公開していければと思います。
(なお、これはあくまでも個人の趣味であり、完全にゲームを再現できているわけではありません。)

まずはじめに

職場でボードゲームをしていたが、諸事情と、コロナ禍になり直接会ってゲームすることが難しくなりました。ただ、その中でもボードゲームをやりたいなー。と思い、WEBでできればみんなで楽しめるんじゃないか?!と思い、関係者限定公開で開発しました。
「THE GAME」については、ゲームはとてもシンプルで、一人からでも楽しめるので、最初に作ってみました。

動作環境

Heroku + Flask with Python + JQuery(Ajax) + Redis

「THE GAME」とは

実際にゲームをお持ちでない方は、下記を参考にしてみてください。
https://bodoge.hoobby.net/games/the-game

仕組み

基本構成としては、Flaskをベースとしたアプリケーションですが、REST API設計にしました。
ゲーム画面はHTML/JS/CSSで構成しますが、すべての操作はAjaxで、サーバ側のREST APIを呼び出し、その結果を加工して使うようにしています。
Redisは、ゲーム中のゲームマスターと、クライアントの紐付け管理や、ゲームの進行内容を管理するために使用しています
※細かい技術については、様々な記事からのピックアップなので省略します

ゲームの分析

まずはゲームを分析・理解し、どのように仕組み化するかを考えます。

ゲームの全容

  • テーブル上には、山場と、4つのカード置き場
  • カードは2から99までの1組(計98枚のカード)

ゲームのルール

  • 最初にプレイヤーに配る手札の数は、人数により調整
  • 最初に4つのカード置き場には、1→99というカードと、100→2というカードを2枚ずつ、合計4枚配置
  • プレイヤーは2枚以上カードを出す必要がある(2枚以上なら手札分を全部おいてもよし)
  • 配置した4枚のカードには、1→99の場合には、直前に出されているカードより大きい数字のカードを置く
  • 100→2の場合には、直前に出されているカードより小さい数字のカードを置く
  • プレイヤーのターンが終わったら、出したカード分を山札から補充する
  • 特殊ルール「10もどし」:これは直前に出されているカードより10小さい/大きいカードを出すことが可能

勝利条件

勝利条件は下記の通りありますが、これは枚数を見て判断すればいいので、機能は実装しません

  • 9枚以下なら「勝利」
  • 0枚なら「完全勝利」
  • 10枚以上なら「敗北」

最重要ルール

このゲームは、協力ゲームであり、みんなで協力して手持ちのカードなるべく出せるようにすることが大事です。
しかし、ゲームの最大のルール。出すカードの数字などを発言してはいけません。
「この列にはこれ以上出さないで」とか、「この列を進めちゃってもいい?」などでゲームを進めることが大事です。
コミュニケーションが大事なので、この辺の機能も実装はしません。

実装部分

まずはゲームはじめ

ここではゲームの初期化をしています。
最初に、2から99までの98枚のカードデータを作ります。
playerにはプレイヤー数分のデータが入っています。
プレイヤー数に応じて、最初に作った98枚のカードデータを書くユーザ6枚になるように各プレイヤーの配列に設定しています。
最後に、場のカードを設定しています。high->low/low->highの通りに対して、更に1を2つ、100を2つで計4つ用意します。

    game['stocks'] = list(range(2, 99))

    # playerids = [player['playerid'] for player in game['players']]
    routelist = copy.copy(game['players'])
    random.shuffle(routelist)
    game['routelist'] = routelist

    players = game['players']

    for player in players:
        player['holdcards'] = []
        while len(player['holdcards']) < 6:
            player['holdcards'].append(game['stocks'].pop(random.randint(0, len(game['stocks']) - 1)))

    game['hightolow'] = []
    game['lowtohigh'] = []
    game['hightolow'].append([100])
    game['hightolow'].append([100])
    game['lowtohigh'].append([1])
    game['lowtohigh'].append([1])

次に、プレイヤーのターンエンド

game['routeidx']とはプレイヤーの順番を示し、これを1つ動かします
その上で、プレイヤーに対し、手札が6枚になるように補充しています

    game['routeidx'] = (game['routeidx'] + 1) % len(game['players'])

    players = game['players']

    # refresh holdcards for all members
    for player in players:
        while len(player['holdcards']) < 6:
            if len(game['stocks']) > 0:
                player['holdcards'].append(game['stocks'].pop(random.randint(0, len(game['stocks']) - 1)))
            else:
                break

カードを配置する処理

この処理が、このプログラム最長の処理になります。
カードをどの位置に配置し、それが妥当であるのかを判定する処理です。
この処理の中で、「10もどし」の判定も入れています。

そして、実は「10もどし」以外に、オリジナルルールを2つ組み込んでいます。

  • 直前のカードがゾロ目なら、手持ちのゾロ目を出せる
  • 直前のカードが10単位のカードの場合、手持ちの10単位のカードはどんな数字でも出せる
    if lineid in [0, 1]:
        highToLow = game['hightolow'][lineid]
        # 100 -> 2
        if highToLow[-1] > cardnum:
            # highToLow.append(cardnum)
            isHit = True
        if (highToLow[-1] + 10) == cardnum and isHit == False:
            # highToLow.append(cardnum)
            isHit = True
        if game['rule'] == 'original' and isHit == False:
            if len(str(cardnum)) > 1 and len(str(highToLow[-1])) > 1:
                cardnum_str = str(cardnum)
                latest_str = str(highToLow[-1])
                if cardnum_str[0] == cardnum_str[1] and latest_str[0] == latest_str[1]:
                    # highToLow.append(cardnum)
                    isHit = True
            if highToLow[-1] % 10 == cardnum % 10:
                # highToLow.append(cardnum)
                isHit = True
        if isHit == False:
            return 'Error1'
        else:
            highToLow.append(cardnum)
    elif lineid in [2, 3]:
        lowToHigh = game['lowtohigh'][lineid%2]
        # 1 -> 99
        if lowToHigh[-1] < cardnum:
            # lowToHigh.append(cardnum)
            isHit = True
        if (lowToHigh[-1] - 10) == cardnum and isHit == False:
            # lowToHigh.append(cardnum)
            isHit = True
        if game['rule'] == 'original' and isHit == False:
            if len(str(cardnum)) > 1 and len(str(lowToHigh[-1])) > 1:
                cardnum_str = str(cardnum)
                latest_str = str(lowToHigh[-1])
                if cardnum_str[0] == cardnum_str[1] and latest_str[0] == latest_str[1]:
                    # lowToHigh.append(cardnum)
                    isHit = True
            if lowToHigh[-1] % 10 == cardnum % 10:
                # lowToHigh.append(cardnum)
                isHit = True
        if isHit == False:
            return 'Error2'
        else:
            lowToHigh.append(cardnum)
    else:
        return 'Error'

最後に

と言うことで、部分的にソースの共有だけさせていただきました。
まぁ、他にも時間をかけて実装を見直すこともできるとは思いますが、効率よく、最低限の要件を実現したところ、こんな感じになりました。

ゲームを試してみたい方へ

https://yanyan-the-game.herokuapp.com/
こちらにアクセスして、以下の通りゲームを進めてください

  1. 「ニックネーム」を入力する
  2. 「ゲームを作る」ボタンを押す
  3. この時点で一人で遊ぶ場合は「ゲームを始める」ボタンを押す。複数人で遊びたい場合は、「ゲームを作る」ボタンの下にあるリンクをメンバーに共有する
  4. 共有された人は、「ニックネーム」を入力後、「ゲームに参加する」ボタンを押す
  5. 必要な人数が揃ったら「ゲームを始める」ボタンを押す

注意事項:ゲームが始まったら、F5キーや、ブラウザの再読み込みは行わないでください。行った場合は、最初からやり直してください。

2
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?