工学じじいの縁側日記

工学じじいの縁側日記

引退間際の工学じじいがきままに、プログラミングやデバイス、工学について呟きます。

【pygame】pythonでヒット&ブローゲーム作る #05 正誤判定、ログの表示【簡単】

pygamepythonでヒット&ブローゲーム作る #05 正誤判定、ログの表示【簡単】

f:id:gomta777:20191115111505p:plain

前回までのまとめ

【簡単?】初心者がpython3とpygameでヒット&ブローゲーム作ってみる【習作?】 - 工学じじいの縁側日記
【簡単?】初心者がpython3とpygameでヒット&ブローゲーム作ってみる #02【習作?】 - 工学じじいの縁側日記
【簡単?】初心者がpython3とpygameでヒット&ブローゲーム作ってみる #03 画面作るよ【習作?】 - 工学じじいの縁側日記
【pygame】pythonでヒット&ブローゲーム作る #04 数字の入力【簡単】 - 工学じじいの縁側日記




前回は、数字の入力までを行えるようになりました。
今回は、出題の処理と、正誤判定、ログの判定をやっていきたいと思います。
出題と正誤判定は、テキストバージョンの関数をそのまま使います。

それでは、作っていくー(笑)



出題と正誤判定

初めにも書きましたが、出題と正誤判定はテキスト版のゲームで使用したものをそのまま使います。(使えます)

【簡単?】初心者がpython3とpygameでヒット&ブローゲーム作ってみる【習作?】 - 工学じじいの縁側日記

def has_not_duplicates(seq):
    return len(seq) == len(set(seq))

def judgement(yin, ans):
    h=0
    b=0
    for i in range(len(yin)):
        if yin[i] == ans[i]:
            h += 1
        else:
            if yin[i] in ans:
                b += 1
    return([h, b])

def make_question():
    nums = list(range(10))
    return(random.sample(nums, 4))

これらの関数を適切なタイミングで呼び出していきます。

まず出題ですが、ゲームスタートボタンを押したときに、問題が初期化されれば都合がよさそうです。また、このときに、プレイヤーの入力した数字が初期化されると、これまた都合がいいです。
すなわち、

    players_input = []  # プレイヤーの入力した数字
    question = []  # 出題された数字
(省略)
if gamebuttonrect.collidepoint(event.pos):
    if gamescene == 0: #gamesceneが0の時(タイトル画面の時)
        question = make_question()  # 問題を作成
        players_input = [-1, -1, -1, -1]  # プレイヤーの入力を初期化
        gamescene = 1  # 状態を「ゲーム中」へ変更

これで、スタートボタンを押したときに、出題(question)とプレイヤーの入力(players_input)が初期化されます。
この辺は画面に現れないので、各自print文でquestionとplayers_inputを表示してみるなど試してみてください。

正誤判定

スタートボタンが押され、状態が「ゲーム中」になると(gamescnene==1)、プレイヤーは、4桁の数字を入力して、ヒット数、ブロー数をかくにんして、それをヒントに正解になるまで繰り返し数字を入力します。

入力は、前回作成した通り、4桁の数字をを数字ボタンでを順にクリックすることで入力できます。
プレイヤーは、4桁の数字の入力が終わったら「Judgement」ボタンを押すことで、ヒット数、ブロー数の判定ができます
このとき、4ヒットが獲得できれば、クリアです。

これらの機能を実装していきます。

4桁の数字が入力されて「judgement」ボタンが押されたときの動作を考えてみます。
「judgement」ボタンが押されたときの状態は以下の3つが考えられます。

  • 「judgement」ボタンが押されたときの状態
  1. 数字が4桁入力されていない(エラー)
  2. 数字に重複がある(エラー)
  3. 数字が正しく入力されていて、正解である
  4. 数字が正しく入力されているが、正解はしていない

これらの時の表示メッセージを切り替えてみます。
「数字が正しく入力されているが、正解はしていない」ときは、ヒット数、ブロー数のログを画面右のスペースに表示します。

まずは、エラーと正解のメッセージの処理を実装していきます。

タイトルの表示の時と同じように、メッセージ用のフォントを用意します。お好みでいいですが、このフォントで、入力数字が正しくないときのエラーと、数字が重複しているエラー、クリアおめでとう(Congratulations)の3種類のメッセージを表示します。
別に3つとも別のフォントで表示できますが、その時はその都度別フォントを読み込んでください。

また、エラー状態とクリア状態を表す状態変数 input_statusを準備しておきます。

input_status = 0 # 1 入力エラー 2 重複エラー 3 正解

gamemessage = []
    mes_font = pygame.font.SysFont("yumincho", 40)
    gamemessage.append(mes_font.render("エラー:数字が正しくない", True, (255, 0, 0)))
    gamemessage.append(mes_font.render("エラー:数字が重複", True, (255, 0, 0)))
    gamemessage.append(mes_font.render("CONGRATULATIONS", True, (255, 0, 0)))

これでエラー表示の準備ができました。
次に、実際にエラーの検知と正解の判定をしていきます。
不正入力エラーと数字の重複エラーの検知、正誤判定のタイミングはすべて「judgement」ボタンを押したときです。

それぞれの条件は、

  • 不正入力エラー
    • players_inputのどこかの桁に初期値-1が残っている
  • 数字重複エラー
    • players_inputの数字に重複がある
    • 関数has_duplicates(players_input)で判定
  • 正解か不正解か
    • 関数judgement(players_input, question)で判定
    • 戻り値が[4, 0]の時が正解

正誤判定、重複判定は、テキストバージョンのものがそのまま使えます。

if gamebuttonrect.collidepoint(event.pos):
# judgementボタンが押されたときに
    if gamescene == 0:
        question = make_question()  # 問題の作成
        # print(question)  # デバッグ用に答えを表示
        players_input = [-1, -1, -1, -1]
        gamescene = 1  # ゲーム状態の初期化
        input_status = 0  # エラー状態もリセットする
    elif gamescene == 1:
        if -1 in players_input:  # 入力に初期値が残っている
            input_status = 1  # 不正入力エラー
        elif has_duplicates(players_input):  # Trueの時は数字が重複
            input_status = 2  # 数字重複エラー
        else:  # エラー以外の時は正誤判定をする
            judge = judgement(players_input, question)
    # judgeに判定結果が返される[Hit数, Blow数]
            if judge[0] == 4: # ヒット数が4なら正解
                input_status = 3   # Congratularions状態に遷移 
                gamescene = 2  # ゲームオーバー状態へ遷移

これで、エラー判定、正誤判定ができます。

まとめると

gamescene input_status 状態
0 - タイトル画面
1 0 通常の入力状態
1 1 不正入力エラー
1 2 数字重複エラー
2 3 正解でゲームオーバー

この条件で適切なメッセージを表示します。

   while running:
        screen.fill((100, 100, 100))  # 背景色で塗る
        if gamescene == 0:
            screen.blit(title, titlepos)
            screen.blit(gamebutton[0], gamebuttonrect)
        elif gamescene == 1:
            for i in range(4): # 入力された数字を表示する領域
                if players_input[i] == -1:
                    screen.blit(qimagelist[10], qbuttonrect[i])
                else:
                    screen.blit(qimagelist[players_input[i]], qbuttonrect[i])
            for i in range(5): # 入力のためのボタンとなる画像0~4
                screen.blit(imagelist[i], buttonrect[i])
            for i in range(5, 10): # 入力のためのボタンとなる画像5~9
                screen.blit(imagelist[i], buttonrect[i])
            screen.blit(gamebutton[1], gamebuttonrect)
            pygame.draw.rect(screen, (255, 0, 0), qbuttonrect[input_pos], 3)
            if input_status > 0:
                screen.blit(gamemessage[input_status-1], (80, 30))
     #エラーメッセージを表示
        elif gamescene == 2:
            screen.blit(gamemessage[input_status - 1], (100, 100))
            # 「Congratulations」表示
            screen.blit(gamebutton[2], gamebuttonrect)
        else:
            print("error")

実行してみます。

f:id:gomta777:20191122045323p:plainf:id:gomta777:20191122045327p:plainf:id:gomta777:20191122045331p:plain
図 エラーと正解メッセージの表示

「judgement」ボタンを押したときの反応を貼っておきます。

ログの表示

次に、正解を導くために肝心なヒット数、ブロー数のログを表示していきます。これがないと、正解を予測できませんね(笑)

ログ表示用のフォント(20pt)と現在まで答えた手数を表す変数turnを用意します。

    anslog = []
    log_font = pygame.font.SysFont("yumincho", 20)
    turn = 1  # 現在まで答えた手数(judgementボタンの押下数)

これを使って、judgementボタンを押すたびにログメッセージをanslogに追加してゆき、turnを+1します。

if (event.type == pygame.MOUSEBUTTONUP) and (event.button == 1):
    if gamebuttonrect.collidepoint(event.pos):
        if gamescene == 0:
            question = make_question()
            print(question)
            players_input = [-1, -1, -1, -1]
            gamescene = 1
            input_status = 0
        elif gamescene == 1:
            if -1 in players_input:
                input_status = 1
            elif has_duplicates(players_input):
                input_status = 2
            else:
                judge = judgement(players_input, question)
                # ここから追加部分
                anslog.append("%02d 回目 " % turn + str(players_input) + \
                "  " + str(judge[0]) + " HIT  " + str(judge[1]) + " BLOW")
                turn += 1
                # ここまで追加部分
                if judge[0] == 4:
                    input_status = 3
                    gamescene = 2
        elif gamescene == 2:
            gamescene = 0
        else:
            gamescene = -1

あとは適切なタイミングでログメッセージを表示してあげればよいです。

        #(省略)
        elif gamescene == 1:
            for i in range(4): # 入力された数字を表示する領域
                if players_input[i] == -1:
                    screen.blit(qimagelist[10], qbuttonrect[i])
                else:
                    screen.blit(qimagelist[players_input[i]], qbuttonrect[i])

            for i in range(5): # 入力のためのボタンとなる画像0~4
                screen.blit(imagelist[i], buttonrect[i])
            for i in range(5, 10): # 入力のためのボタンとなる画像5~9
                screen.blit(imagelist[i], buttonrect[i])
            screen.blit(gamebutton[1], gamebuttonrect)
            pygame.draw.rect(screen, (255, 0, 0), qbuttonrect[input_pos], 3)
            if input_status > 0:
                screen.blit(gamemessage[input_status-1], (80, 30))
            # ここから追加部分
            for i in range(len(anslog)):
                text = log_font.render(anslog[i], True, (255, 255, 255))
                screen.blit(text, (560, 30 * i + 100 - 3))
            # ここまで追加部分
        elif gamescene == 2:
        #(省略)

f:id:gomta777:20191122051930p:plain
図 ログの表示

今回のまとめ

今回は、「judgement」ボタンを押したときの反応を中心に進めてきました。「judgement」ボタンを押したときのエラー表示、正解の表示、入力ログの表示を実装しました。
実は、今回の実装にはちょっとした問題があります。

f:id:gomta777:20191122052229p:plain
図 表示のバグ

ログの表示を、ただ座標をずらして繰り返しで表示しているので、画面の外にどんどんはみ出ていってしまいます。また、16回ぐらいまでは表示できますが、わりと15,6回で正解するのは、難しいかもしれません。何か工夫が必要ですよね。

次回

今回のソースコード一式はgithubにもあります。
https://github.com/gomta777/hitandblow/
のjudgementが今回のものです。

次回は、ログの表示を工夫してみます。



よかったら、ブログ村↓のクリックお願いします。