【Python/Django】数当てゲーム(Hit&Blow)を開発【前編】

Djangoでの数当てゲームの作り方の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Django を利用した「数当てゲーム」の開発について解説していきます!

この Django での「数当てゲームの開発」に関する解説は前編・後編構成となっています。この前編では、数当てゲームを開発する上でポイントとなる内容の解説や、実際の数当てゲームの Django での作り方について解説していきます。

また、下記ページの後編では数当てゲームを拡張し、ログイン機能・ランキング機能等を搭載することで、よりウェブアプリっぽいアプリに仕立てていきます。

Djangoでの数当てゲームの拡張の仕方・ポイントの解説ページアイキャッチ 【Python/Django】数当てゲーム(Hit&Blow)を拡張【後編】

題材がゲームなので作ってみて楽しいと思いますし、このページで解説する内容は、様々なゲームやアプリの開発に応用することもできると思いますので、是非ページを読み進めていっていただければと思います!

数当てゲーム

まず、今回開発するウェブアプリについて簡単に説明しておきます。

前述の通り、今回は「数当てゲーム」を開発していきます。数当てゲームとは、正解となる数字(今回は4桁の数字)を予想するゲームになります。特に、「数当てゲーム」の中でも、Hit & Blow (他にもマスターマインドや Numer0n とも呼ばれる) と呼ばれるゲームを開発していきます。

数当てゲームの説明図

このゲームでは、予想を回答した際に、単に正解・不正解という情報だけでなく、「正解への近さ」の情報が出力されるようになっています。具体的には、下記の Hit 数Blow 数が出力されるようになっています。

  • Hit 数:正解と「位置」も「数字」も合っている桁の個数
  • Blow 数:「位置」は合っていないが「数字」自体は正解に含まれている桁の個数

例えば、正解が 3024 で、それに対して回答された予想が 4321 の場合、2 に関しては正解と桁が合っているので Hit と判断され、さらに 34 に関しては桁は一致していないが数字自体は含まれているため Blow と判断されます。そのため、3024 を入力した場合は 1 Hit / 2 Blow と出力が得られることになります。この正解への近さという情報を基に、正解となる数字を予想していく、つまり、4 Hit を目指していくというのが Hit & Blow となります。

HitとBlowの説明図

「数当てゲーム」を開発する場合は、もちろん Hit 数・Blow 数の算出や、正解・不正解を判断するようなロジックも重要です。ですが、“Django でウェブアプリとして” 数当てゲームを開発する場合は、それらに加えてビュー・フォーム・テンプレート等を上手く利用してゲームを実現していくという点も重要になります。また、Django で開発することで、ウェブアプリの特徴的な機能、例えばログイン機能やランキング機能を搭載するようなことも簡単に実現可能です。

ページの冒頭でも説明したように、このページでは、まずは数当てゲームとして最低限機能するウェブアプリの開発を行っていきます。そして、後編となる下記ページで、ウェブアプリに搭載することの多い「ログイン機能」や「ランキング機能」等を搭載し、よりウェブアプリらしいアプリに仕立てていく手順を解説していきます。

Djangoでの数当てゲームの拡張の仕方・ポイントの解説ページアイキャッチ 【Python/Django】数当てゲーム(Hit&Blow)を拡張【後編】

数当てゲームの開発のポイント

まずは数当てゲームをウェブアプリとして開発するためのポイント説明していきたいと思います。

スポンサーリンク

データベースでの正解データの管理

この数当てゲームを開発する上でポイントになるのが、正解データ(正解の数字)の管理になります。

前述の通り、数当てゲームは、ユーザーが何回も試行しながら正解データを予想していくゲームになります。

ウェブアプリの場合、この試行は、ウェブアプリへの HTTP リクエストの送信によって実施されることになります。さらに、その結果(Hit 数 / Blow 数)は、ウェブアプリからの HTTP レスポンスの返却で通知されることになります。もう少し具体的に言えば、フォームから予想をボディとする HTTP リクエストを送信し、結果を記述した HTML をボディとする HTTP レスポンスが返却されることになります。

予想と結果の送受信

この複数回の HTTP リクエストと HTTP レスポンスの送受信の間、ウェブアプリは同じ正解データを管理し続け、その正解データを利用して結果を判断する必要があります。毎回異なる正解データに変化してしまうと、Hit 数 / Blow 数というヒントの意味が無くなり予想ができなくなってしまいますよね…。

正解データが変化してしまう様子

例えば、Django で開発したウェブアプリの場合、ビューとして定義した関数は HTTP リクエストを受信するたびに実行されることになります。なので、ビューの中でローカル変数を利用して正解データを管理したりすると、HTTP リクエストを受信するたびに正解データが変化してしまうことになるので注意が必要です。

ということで、数当てゲームを開発するためには、複数の HTTP リクエストを跨いで同じデータを保持し続けることが必要となります。で、こういったデータの保持を実現するのに便利なのがデータベースとなります。データベースに保存されたデータは、複数の HTTP リクエストを跨いで保持され続けますし、ウェブアプリやサーバー等を再起動しても保持され続けます。つまり、永続的なデータの管理が可能となります。そして、これにより、同じ正解データに対して何回も予想を試行することが可能なウェブアプリを実現できます。

データベースで正解を管理する様子

他にも、正解データをファイルに記録しておくような方法もありますが、Django の場合はデータベースが簡単に利用できるため、データベースで正解データを管理するようにした方が楽だと思います。

また、何回も数当てゲームをプレイできるようにするためには、ユーザーが正解したタイミングで新たな正解データを作成し、それをデータベースで管理するような処理も必要となります。ずっと同じ正解データだとゲームとして楽しくないですからね…。

ユーザー単位での正解データの管理

とりあえず、データベースで正解データを管理するようにしてウェブアプリを開発すれば、数当てゲーム自体はプレイできるようになるはずです。

ユーザー単位での正解データの管理の必要性

ただし、1つの正解データを管理するだけだとゲームが破綻する可能性があり、必要に応じてユーザー毎に正解データを管理するようにする必要があります。要はユーザーごとに独立してゲームをプレイできるようにするためには、ユーザー毎に正解データを用意し、予想した数字を送信してきたユーザーに応じて、参照する正解データを切り替えるような処理が必要となります。

正解データをユーザー単位で用意する様子

簡単に、この理由について説明しておきます。

まず、ウェブアプリは、同時に複数のユーザーから利用される場合があります。むしろ、それができることがウェブアプリの1つの特徴と言って良いと思います。数当てゲームの場合であれば、同時に複数のユーザーからゲームがプレイされる可能性があるということになります。そして、ウェブアプリで1つの正解データのみを管理する場合、各ユーザーが同じ正解データを共有し、その正解データの予想を行うことになります。

ウェブアプリで管理する正解データが1つのみである場合にゲームが破綻してしまう説明図1

この場合、特定のユーザーが正解を当てたタイミングで「正解データ」が他の正解データに切り替わることになりますので、他のユーザーが予想中の正解データが勝手にいきなり変わってしまうことになります。また、正解データを切り替えないような場合は、同じ正解データなので、正解したユーザーから正解データが漏洩してしまう可能性もあります。このようなことが起こると、ゲーム・アプリとして破綻してしまうことになります。

ウェブアプリで管理する正解データが1つのみである場合にゲームが破綻してしまう説明図2

こういったことを避けるために、ユーザー毎に別々の正解データを管理するようにし、各ユーザーが独立してゲームをプレイできるようにする必要があります。

もちろん、早押しクイズのようなウェブアプリを開発するのであれば、複数人がゲームをプレイしていたとしても、一人が正解したらすぐに次の正解データに切り替えるような作りにする必要があります。つまり、結局はどういったウェブアプリを開発したいのかによって作り方も変わるのですが、今回はユーザーごとに独立してゲームがプレイできるようにしていきたいので、ユーザー単位で異なる正解データを用意するようにしていきたいと思います。

ユーザーの識別

で、このユーザー単位での正解データの管理を実現する上で重要になるのが「ユーザーの識別」になります。ユーザー毎に正解データを管理するようにしたとしても、予想した数字を送信してきたユーザーが識別できなければ、結果を判断するために参照すべき正解データも分かりません。

ユーザーの識別の必要性を説明する図

このユーザーの識別に便利な機能が「ログイン」になります。ログイン機能を搭載しておけば、リクエストを送信してきたユーザー、すなわち、ウェブアプリを利用しているユーザーを識別することが可能となります。なので、ユーザー毎に正解データを用意し、ユーザーに応じて参照する正解データを切り替えるような処理を簡単に実現することができます。

ログイン機能によってユーザーを識別する様子

ただ、ログイン機能を搭載するのは結構大変です。そのため、今回は「セッション」を利用して簡易的なユーザーの識別を実現していきたいと思います。このセッションを利用した識別は、正確に言えばクライアント(ウェブブラウザ・デバイス)の識別となります。

セッションとは、クライアントとウェブアプリ(サーバー)との間で、ウェブアプリの利用開始から利用終了までの一連の通信を管理するための仕組みのことを言います。Django で開発したウェブアプリでは、クライアントがウェブアプリを利用開始する時にセッション ID を発行し、その ID をクライアントに対して返却することができるようになっています。そして、ウェブブラウザ等の一般的なクライアントでは、以降のウェブアプリに対する HTTP リクエスト送信時に、そのセッション ID を一緒に送信するようになっています。

セッションIDでのユーザーの識別1

このセッション ID はクライアントごとに発行されるため、このセッション ID によってクライアントを識別することができるようになります。あとは、セッション ID に紐づける形で正解データを管理するようにウェブアプリを開発すれば、クライアント単位での正解データの管理が実現できることになり、各ユーザーが独立してゲームをプレイすることができるようになります。

セッションIDでのユーザーの識別2

ただし、前述の通り、このセッションを利用した識別は、ユーザーの識別ではなくクライアント(使用しているウェブブラウザや PC 等のデバイス)の識別です。なので、同じユーザーだとしても、異なるウェブブラウザや異なる PC・スマホ等からアプリを利用した場合は、異なる利用者であると判断されてしまいます。

セッションを利用した識別のデメリットを示す図

また、ウェブブラウザではセッション ID が保存されるようになっていますが、このセッション ID が削除されたり有効期限が切れたりすることもあります(Django の設定でセッション ID の削除・有効期限切れを防ぐことも可能ですが、セキュリティリスクやウェブアプリ側の負荷が増加する可能性があります)。そして、セッション ID が削除されたり有効期限が切れたりすると、また新たにセッション ID がウェブアプリで発行されることになり、今までとは異なる利用者と判断されることになります。なので、それまで予想していた正解データでの数当てゲームが継続できなくなります。

こういった課題は、前述でも触れたログイン機能の搭載によって解決することが可能です。ログイン機能を搭載することで、真の意味でのユーザーの識別が可能となります。ただし、今回は「数当てゲームの実現」という点に焦点を当てた解説を行いたいため、簡単に実現可能なセッションでの識別を採用したいと思います。ページの冒頭でも説明したように、このログインを数当てゲームに搭載する例は後編の下記ページで解説します。

Djangoでの数当てゲームの拡張の仕方・ポイントの解説ページアイキャッチ 【Python/Django】数当てゲーム(Hit&Blow)を拡張【後編】

正解データのクライアントへの送信はダメ

また、「正解データはクライアントに送信しない」という点も、数当てゲームを開発する上での重要なポイントになります。

当たり前のように感じるかもしれませんが、これはウェブアプリを開発する上で非常に重要なポイントで、ここを注意しないとセキュリティリスクが生まれたりゲームとして成立しなくなったりしてしまいます。

今回の数当てゲームの場合であれば、前述で説明したような「複数の HTTP リクエストを跨いだ正解データの保持」は、サーバー側で正解データを保持・管理しなくても、クライアント側に正解データを送信するようにすることでも実現可能です。この場合、データベースに正解データを保存する必要が無くなるため、必要な記憶容量が削減可能です。また、データベースの利用が不要となるので実装も楽になります。こういったメリットが得られるとしても、正解データをクライアント側に送信することは NG です。

例えば、ウェブアプリからクライアントに送信する「回答の入力フォーム」に隠しフィールド(ページに表示されないフィールド)を設け、そのフィールドに正解データをセットするようにしておけば、回答の入力フォームを送信したときに自動的に正解データもウェブアプリに送信されるようになります。クライアントから正解データが送信されてくるため、ウェブアプリは正解データを管理する必要が無く、クライアントから送信されてきた正解データと回答データから結果(正解 or 不正解・Hit 数 / Blow 数)を計算すればよいだけになります。

隠しフィールドでクライアントに正解を送信する様子

また、下記ページで解説しているように、Django で開発したウェブアプリでも JavaScript を扱うことが可能で、上記のような方法で正解データをクライアント側に送信すれば、わざわざクライアントからウェブアプリに回答データを送信しなくても、ユーザーが回答フォームに値を入力したタイミングで JavaScript によって結果を判断するようなことも可能となります。

Djangoで開発するウェブアプリでのJavaScriptの扱い方の解説ページアイキャッチ 【Django入門21】JavaScriptの扱い方

つまり、クライアント側に正解データを送信すれば、実はウェブアプリの作りがシンプルになってウェブアプリが開発しやすくなりますし、通信の回数も減ってウェブアプリの負荷を減らすことも可能です。

なんですが、クライアント側に送信したデータは、基本的にはユーザーから閲覧することが可能です。例えば、前述のようにフォームに隠しフィールドを設けたとしても、ウェブブラウザでフォームの HTML のソースコードを表示してしまえば正解データをユーザーが確認することができてしまいます。

隠しフィールドの値はコードを見るとユーザーにばれてしまうことを説明する図

また、JavaScript のソースコードもウェブブラウザで確認可能で、さらにはブレークポイントを設定してスクリプトを停止し、そのタイミングの変数の中身を確認するようなこともできます。なので、JavaScript で正解データを扱うと、その正解データもユーザーにばれてしまいます。

このように、クライアントに送信したデータは基本的にはユーザーが確認可能です。なので、クライアント側でデータを管理させるようにすることでウェブアプリの開発が楽になる場合であっても、ユーザーに見られたくないデータはクライアントに送信してはダメで、機密性の高いデータはウェブアプリ側(サーバー側)のみで扱うようにウェブアプリを開発する必要があります。

今回の例では正解データが機密性の高いデータとなりますが、パスワード等も同様です。パスワードをクライアント側に送信するとパスワードが漏洩することになります。正解データの漏洩であれば笑い話で済むかもしれませんが、パスワードの漏洩は大問題となります。こういった、パスワードのような機密性の高いデータはクライアントに送信してはいけないという点は、ウェブアプリを開発する上で最重要と言っても過言ではないポイントとなりますので、是非このポイントについては覚えておいてください。

スポンサーリンク

Hit 数 / Blow 数の算出

また、今回は数当てゲームの中の Hit & Blow を開発していきますので、ユーザーからの数字の回答を受け付け、その回答と正解の数字から Hit 数や Blow 数を算出する必要があります。

ユーザーからの数字の回答の受付に関してはフォームを利用してやれば良いです。フォームに関しては下記ページで解説していますので、フォームの定義の仕方・フォームを扱うビューやテンプレートの作り方等のフォームの詳細に関しては下記ページを参考にしていただければと思います。

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

さらに、このフォームから送信されてきた数字と、データベースで管理している正解データを照合し、Hit 数と Blow 数を算出する必要があります。

ただ、どちらも算出方法は単純で、まず Hit 数に関しては、両方の同じ桁の数字を一つ一つ比較し、一致している桁数を Hit 数としてカウントすればよいだけです。

Hit数の数え方

また、Blow 数に関しては、正解 or 回答データの各桁の数字が他方側のデータに含まれている個数をカウントし、その個数から Hit 数を減算することで求めることができます(正解データの各桁の数字が重複しないことを前提とした算出方法になります)。

Blow数の数え方

具体的には、下記のようなコードで Hit 数と Blow 数は算出可能です。

Hit数とBlow数(ループ)
# answer:正解(4桁の文字列)
# guess:ユーザーの回答(4桁の文字列)

hit = 0
for a, g in zip(answer, guess):
    if a == g:
        hit += 1

blow = 0
for g in guess:
    if g in answer:
        blow += 1
blow -= hit

また、内包表記を利用すれば下記のようなコードで算出することもできます。

Hit数とBlow数(内包表記)
# answer:正解(4桁の文字列)
# guess:ユーザーの回答(4桁の文字列)

hit = sum(1 for a, g in zip(answer, guess) if a == g)
blow = sum(1 for g in guess if g in answer) - hit

以上が、数当てゲームを Django で開発する上でのポイントの説明となります。

数当てゲームの作り方

ここからは、数当てゲームの Django での作り方について、実際に数当てゲームを開発しながら解説していきたいと思います。

プロジェクトの作成・アプリの作成

Django でウェブアプリを開発する時には、最初にプロジェクトとアプリを作成する必要があります。これらに関しては、一般的なウェブアプリを作る時と同じ手順で OK です。

今回は、プロジェクト名を number_guess とし、アプリ名を game としたいと思います。まずはプロジェクト number_guess を作成するため、コンソールアプリを起動して適当なフォルダに移動後、下記のコマンドを実行してください。

% django-admin startproject number_guess

このコマンドの実行によって、コマンドを実行したフォルダに number_guess フォルダが作成されるはずなので、この number_guess フォルダに移動し、さらに下記のコマンドでアプリ game を作成してください。

% python manage.py startapp game

続いて、number_guess フォルダ内の settings.pyINSTALLED_APPS のリストの先頭に、下記のように 'game', を追加してください。

INSTALLED_APPS
INSTALLED_APPS = [
    'game',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

次に、number_guess フォルダ内の urls.py を下記のように変更し、/game/ から始まる URL のリクエストを受け取った時に game フォルダ内の urls.py が参照されるようにします。

number_guess/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('game/', include('game.urls')),
]

スポンサーリンク

モデルクラスの定義

ここからは game フォルダ内のファイルを変更し、アプリを作りこんでいきます。

まずはモデルクラスの定義を行っていきます。データベースでの正解データの管理 でも解説したように、ユーザーが何回も回答を試行できるようにするためには、正解データの永続的な保持が必要となります。そのため、データベースで正解データを管理できるよう、正解データを管理するフィールドを持つモデルクラスを定義する必要があります。

ということで、models.py を下記のように変更してください。下記で定義する Game は数当てゲームを管理するモデルクラスとなります。answer が数当てゲームの正解を管理するフィールドとなります。

models.py
from django.db import models

class Game(models.Model):
    answer = models.CharField(max_length=4) # 正解
    attempts = models.IntegerField(default=0) # 予想回数
    is_finished = models.BooleanField(default=False) # ゲームクリアフラグ
    session_id = models.CharField(max_length=40) # セッションID

ポイントは session_id フィールドになります。ユーザー単位での正解データの管理 で説明したように、各ユーザーが独立してゲームをプレイできるようにするためには、ユーザー(クライアント)を識別するための情報に紐づけてゲーム(正解の数字)を管理する必要があります。今回は、その識別するための情報としてセッション ID を採用し、この session_id フィールドを設けてゲームとセッション ID を紐づけて管理することで、各ユーザーが独立してゲームをプレイできるようにしていきます。

また、他にも、attempts でユーザーが回答した回数を管理、さらに is_finished でユーザーが既に正解を回答済み(ゲームクリア済み)であるかどうかを管理するようにしています。

フォームの定義

また、数当てゲームでは、ユーザーから予想した数を回答してもらうために、下図のような「回答用のフォーム」が必要となります。次は、このフォームを定義していきます。

数当てゲームのプレイ画面1

そのために game フォルダ内に forms.py を新規作成し、ファイルの中身を下記のように変更してください。GuessForm が回答用のフォームであり、number フィールドが予想した数を入力するフィールドになっています。number フィールドでは4桁の半角数字のみを入力可能とするため、max_lengthmin_lengthvalidators の引数指定を行っています。

forms.py
from django import forms
from django.core.validators import RegexValidator

class GuessForm(forms.Form):
    number = forms.CharField(
        max_length=4,
        min_length=4,
        validators=[
            RegexValidator(r'^[0-9]{4}$', '半角数字4桁のみ入力してください。'),
        ],
        label='予想した数字'
    )

ビューの定義

次はビューを定義していきます。今回は、ビューは関数ベースで定義していきます。

models.py で定義した Game がゲームを管理するモデルクラスとなっていますので、この Game のレコードを取得してゲームの情報(特に正解の数字や予想回数)を参照したり、ゲームのクリア状況等に応じて Game のレコードを更新したりする必要があるという点がビュー定義時のポイントになります。また、クライアントを識別するためにも、Game のレコードの session_id フィールドを上手く利用するという点も重要なポイントになります。

今回は、下記のように views.py を変更して数当てゲームを実現していきたいと思います。いろいろ定義していますが、ビューとして機能するのは index 関数のみで、その他は index 関数で数当てゲームを実現するために利用されるクラスや関数となっています。

views.py
from django.shortcuts import render
from .models import Game
from .forms import GuessForm
import random

class Guess:
    def __init__(self):
        self.game = None # プレイ中のゲーム
        self.number = None # 予想した数
        self.attempts = None # 予想回数
        self.hit = None # Hit数
        self.blow = None # Blow数

    def set_hit_blow(self):

        # ヒット数(桁まで一致している数字の個数)
        self.hit = sum(1 for a, g in zip(self.game.answer, self.number) if a == g)

        # ブロウ数(桁は一致していないが正解に含まれている数字の個数)
        self.blow = sum(1 for g in self.number if g in self.game.answer) - self.hit

def create_answer():
    # ランダムな4桁の数字(重複無し)の文字列を生成
    return ''.join(map(str, random.sample(range(10), 4)))

def index(request):
    # セッションIDに紐づくゲームの管理情報を取得 
    game = Game.objects.filter(session_id=request.session.session_key, is_finished=False).first()

    if game is None:
        # まだゲームが開始されていない場合
        if request.session.session_key is None:
            # セッションIDが送信されてきていない場合

            # セッションIDを発行
            request.session.create()

        # Gameを作成(セッションIDと紐づけ)
        game = Game.objects.create(
            answer=create_answer(), # 正解データ
            session_id=request.session.session_key # セッションID
        )

    guess = None
    if request.method == 'POST':
        # フォームからデータが送信されてきた場合 
        
        # 受信したデータからフォームを作成
        form = GuessForm(request.POST)
        if form.is_valid():
            # 受信したデータが妥当である場合

            # 予想回数をインクリメント
            game.attempts += 1

            # ユーザーが予想した数を取得
            number = form.cleaned_data['number']

            # Guessクラスのインスタンスを生成
            guess = Guess()
            guess.number = number
            guess.game = game
            guess.attempts = game.attempts

            # Hit数とBlow数を計算
            guess.set_hit_blow()

            if guess.hit == 4:
                # 予想した数が正解の場合

                # ゲーム終了フラグをセット
                game.is_finished = True

            # レコードを更新
            game.save()
    else:
        # フォームからデータが送信されてきていない場合

        # 空のフォームを生成
        form = GuessForm()


    context = {
        'game': game,
        'guess': guess,
        'form': form
    }
    return render(request, 'game/guess.html', context)

ゲームの管理

この views.py における index 関数の処理の流れを確認していきましょう!

index 関数で最初に行っているのは、クライアントから送信されてきたセッション ID に紐づく Game のレコードの取得になります。ゲームがクリアされていないレコードのみを取得するようにしています。下記の処理からも分かるように、クライアントから送信されてきたセッション ID は request.session.session_key から取得可能です。セッション ID はクライアントごとに異なるため、Game のレコードでゲームの状態を管理するようにし、さらにセッション ID と紐づけてレコードを管理するようにしておけば、ウェブアプリでクライアントごとに独立してゲームの状態を管理することができるようになります。

Gameのレコードの取得
game = Game.objects.filter(session_id=request.session.session_key, is_finished=False).first()

ただし、ゲーム開始時、具体的には、次のような場合は上記の処理でレコードの取得に失敗してしまいます。

  • 初めてウェブアプリを利用する(request.session.session_keyNone の場合)
  • ゲームクリア直後(is_finished=False を満たすレコードが存在しない場合)

このような場合は、上記の処理において gameNone となるため、必要に応じてセッション ID を発行したのちに、新たに Game のレコードを作成するようにしています。で、この Game のレコード作成時には、下記のようにセッション ID を紐づけておく必要があるところがポイントになります。また、下記で実行している create_answer は、views.py に定義した「正解データを生成する関数」となります。

Gameのレコードの作成
# Gameを作成(セッションIDと紐づけ)
game = Game.objects.create(
    answer=create_answer(), # 正解データ
    session_id=request.session.session_key # セッションID
)

さらに、リクエストのメソッドが POST の場合、つまりクライアントからデータが送信されてきている場合は、その送信されてきたデータからユーザーが予想した数字を取得し、Guessset_hit_blow メソッドで Hit 数と Blow 数の算出を行っています。この set_hit_blow での Hit 数と Blow 数の算出は、多少変数名等は異なりますが、基本的には Hit 数 / Blow 数の算出 で紹介したコードと同じとなります。

さらに、Hit 数が 4 の場合は、予想が的中してゲームクリアということになるので、この場合はゲームクリアフラグである game.is_finishedTrue にセットするようにしています。これにより、次回 index 関数が実行された際には、最初に実施する Game のレコードの取得に失敗するようになり、新たな Game のレコードが作成され、新たな正解に対してゲームが開始されることになります。

回答の管理

また、views.py で定義している Guess は、ユーザーの回答情報を管理するクラスになっています。

わざわざ回答情報を管理するクラスを定義している理由は2つで、1つはコンテキストを作りやすくするためです。クラスとして情報を1つに集約することで、コンテキストにキーを1つ追加するだけで、ページに表示したい「回答に関する情報」を全てテンプレートに渡すことができるようになります。

もう1つが、下記の後編でのコードの変更を楽にするためになります。下記の後編では、回答情報をデータベースに保存するように数当てゲームを拡張します。データベースに保存するので、回答情報をモデルクラスで管理することになります。回答情報をモデルクラスで管理するようにした場合でも、通常のクラスで回答情報を管理するようにしていればコードはほとんど変更が不要となるので、予め回答情報を管理するクラスを定義し、それを使ってビューを実装するようにしています。

Djangoでの数当てゲームの拡張の仕方・ポイントの解説ページアイキャッチ 【Python/Django】数当てゲーム(Hit&Blow)を拡張【後編】

後は、基本的にはフォームを扱うビューとして典型的な流れの処理を実施しているだけになります。フォームやビューに関して詳しく知りたいという方は下記ページを参考にしていただければと思います。

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本 Djangoのビューの機能についての解説ページアイキャッチ 【Django入門3】ビュー(View)の基本とURLのマッピング

スポンサーリンク

テンプレートの作成

ビューが完成したので、次はビューから利用されるテンプレートファイルを作成していきます。

index 関数の最後に実行している render 関数の第2引数に 'game/guess.html' を指定しているため、この render 関数実行時には下記のパスのテンプレートファイルが利用されることになります。

game/templates/game/guess.html

そのため、このパスにテンプレートファイルを作成し、index 関数が実行された際に数当てゲームプレイ用のページが表示されるようにしていきます。

まずは、game フォルダ内に templates フォルダを作成し、さらに templates フォルダ内に game フォルダを作成してください。その後、最後に作成した game フォルダ内に guess.html を作成してください。

そして、その guess.html に下記をコピペしてください。

guess.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>数当てゲーム</title>
</head>
<body>
    <h1>数当てゲーム</h1>
    {% if not game.is_finished %}
    <h2>{{ game.attempts|add:1 }}回目の予想</h2>
    <form method="post" action="{% url 'index' %}">
        {% csrf_token %}
        <table><tbody>{{ form.as_table }}</tbody></table>
        <button type="submit">回答</button>
    </form>
    {% else %}
    <p>正解です!!!</p>
    <p>もう一度プレイする場合は<a href="{% url 'index' %}">ココ</a>をクリックしてください</p>
    {% endif %}
    
    {% if guess is not None %}
    <h2>前回の予想結果</h2>
    <table>
        <thead>
            <tr>
                <th>予想回数</th><th>予想した数字</th><th>ヒット数</th><th>ブロウ数</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>{{ guess.attempts }}</td>
                <td>{{ guess.number }}</td>
                <td>{{ guess.hit }}</td>
                <td>{{ guess.blow }}</td>
            </tr>
        </tbody>
    </table>
    {% endif %}
</body>
</html>

この guess.html で実施しているのは主に下記の2つです。

  • ユーザーから予想した数字の入力受付を行うフォームの出力
  • 前回ユーザーが予想した数字の Hit 数・Blow 数を表示するテーブルの出力

ただし、Django のテンプレートタグの仕組みを利用して、game (Game のインスタンス)や guess (Guess のインスタンス)に応じて表示を切り替えるようにしています。例えば、ゲームクリア時、つまり game.is_finishedTrue の場合はフォームは表示せずに正解したことを伝えるメッセージのみを表示したり、予想が送信されていない時(ゲーム開始直後や GET メソッドの HTTP リクエストを受け取った時)、つまり guessNone の場合はテーブルは非表示にするようにしています。

あとは、基本的にはコンテキストとして渡されてきた各インスタンスのデータ属性を出力することで、Hit 数や Blow 数等をページに表示するようにしているだけです。このあたりの説明は不要だと思いますので省略します。

テンプレートに関して詳しく知りたいという方は、別途下記ページを参照していただければと思います。

Djangoのテンプレートの解説ページアイキャッチ 【Django入門4】テンプレート(Template)の基本

また、正解データのクライアントへの送信はダメ でも解説したように、正解データをテンプレートファイルに埋め込むようなことは避けましょう。今回の場合であれば、game.answer が正解データとなりますので、これをテンプレートファイルで出力するのは NG です。

ビューと URL とのマッピング

最後に、先ほど views.py に定義した index 関数と URL とのマッピングを行っていきます。これにより、数当てゲームのウェブアプリが完成することになります。

まずは、game フォルダ内に urls.py を新規作成し、下記をコピペしてください。

game/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index')
]

この game/urls.py は /game/ から始まる URL の HTTP リクエストを受け取った時に読み込まれるようになっているため(前述で変更した number_guess/urls.py で、そうなるように設定している)、上記のように game/urls.py を変更することで、URL が /game/ の時に index 関数が呼び出され、数当てゲームプレイ用のページが表示されることになります。

動作確認

以上の変更・実装により、とりあえず数当てゲームが完成したことになります。最後に、このアプリの動作確認を実施したいと思います。

まずは、number_guess フォルダ内(manage.py が存在するフォルダ内)で下記の2つのコマンドを実行してマイグレーションを実施してください。これにより、Game のテーブルがデータベース内に作成され、この Game のレコードで数当てゲームを管理することができるようになります。

% python manage.py makemigrations
% python manage.py migrate

続いて、下記コマンドを実行して開発用ウェブサーバーを起動してください。

% python manage.py runserver

開発用ウェブサーバーが起動できたら、次はウェブブラウザを起動して下記 URL を開いてください。

http://localhost:8000/game/

これにより、views.pyindex 関数が実行され、下記のようなページが表示されるはずです。まだ予想した数字は送信していないため、フォームのみが表示されているはずです。

数当てゲームのプレイ画面1

続いて、このページ内の 予想した数字 フィールドに4桁の半角数字を入力し、送信 ボタンをクリックしてください。すると、下の図のように送信した数字の Hit 数や Blow 数が表示されるはずです。

数当てゲームのプレイ画面2

また、異なる数字を入力して送信すれば、入力した数字に基づいて Hit 数や Blow 数の表示が変化するはずです。

数当てゲームのプレイ画面3

あとは、この Hit 数や Blow 数を参考にして正解の数字を予想し、正解の数字と同じ数字を回答するまでゲームを続けていくことになります。最終的に、正解の数字を回答することができれば下の図のようなページが表示されることになります。ここで、ココ の部分のリンクをクリックしたり、ページをリロードしたりすれば、新たなゲームが開始されることになり、先ほどと異なる正解の数字に対する数当てゲームをプレイすることができます。

数当てゲームのプレイ画面4

もしウェブブラウザが PC 内に複数インストールされているのであれば、別々のウェブブラウザを起動してゲームをプレイした際に、それぞれが独立して異なる正解の数字に対して数当てゲームがプレイできることを確認することもできるはずです。

異なるブラウザを利用すると異なる利用者と判断される様子

これは、ウェブブラウザ毎に異なるセッション ID がウェブアプリから発行されるようになっているためです。 このような動作から、セッション ID に応じてクライアントを識別し、各クライアントから独立してゲームがプレイできることを確認できたことにはなりますが、同じユーザーであってもクライアントが異なると別の利用者と判断されてしまう点は課題であり、これに関しては後編でログインを導入することで解決していきます。

スポンサーリンク

まとめ

このページでは、Django での数当てゲームの作り方について解説しました!

数当てゲーム自体はシンプルなゲームとなるのですが、ウェブアプリとして開発しようと思うと考慮すべき点も多くて結構難しいと感じた方も多いのではないでしょうか?

特に、今回開発したウェブアプリにおいては、データを永続的に管理するためにデータベースを利用するという点と、ユーザー毎に独立してゲームがプレイできるようにユーザーの識別を行うという点がポイントになると思います。後者の識別に関しては、セッション ID を利用することで簡易的に実現できることは覚えておくと良いと思います。

ただし、セッション ID を利用して識別を行うと、同じユーザーが操作したとしても異なるウェブブラウザを利用した場合に異なるユーザーと判断されてしまうことになります(異なるセッション ID が送信される)。

また、ゲームを実際にプレイしてみて、今まで回答した履歴が表示されないという点に不満を持った方も多いのではないかと思います。これだと、回答に対する Hit 数 / Blow 数を自身で覚えておく必要があって大変です。

このように、今回開発した数当てゲームには多くの課題が残っています。もしかしたら、上記以外の点を課題と感じた方もおられると思います。こういった課題を実際に解決していくことは、あなた自身の知識を増やし、さらに技術を向上させることに繋がりますので、是非課題の解決に挑戦してみていただければと思います。

下記の後編のページでは、その一例として、上記で挙げた課題を解決するために、数当てゲームに「ログイン機能」や「回答履歴の表示機能」の追加を行っていきます。興味があれば、是非、後編も読んでみてください!

Djangoでの数当てゲームの拡張の仕方・ポイントの解説ページアイキャッチ 【Python/Django】数当てゲーム(Hit&Blow)を拡張【後編】

同じカテゴリのページ一覧を表示