忍者ブログ

PPM - Python Program Magazine

Pythonのプログラムを公開するだけのブログ

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

ヒット・アンド・ブロー

答えが何か当ててごらん

 数あてゲームです。
 0~9の数字から4つを選び、答えを当てます。
 各数字は1回しか使用できません。
 数字と場所が当たっていた場合はHit、数字があっていて場所が違っていた場合はBlowとしてカウントします。
 HitとBlowの数字を頼りに答えを当てて下さい。

何で作ったの?

 新しい環境でプログラムを作る際、いつも数当てのハイ・ロー・ゲームから始めます。
 その次に作るのがこのヒット・アンド・ブロー・ゲームです。単純なわりに結構遊べるというのがその理由です。

プログラムの説明

 このプログラムはGUIをつかっています。GUIは標準ライブラリのtkinterです。
 ウィジェットの配置にはgridを使用しています。
 作る際に苦労したのは、複数ボタンから一つのハンドラを呼び出すようにした部分です。最終的にbindでハンドラを指定するようにしました。しかし、ボタンの2度押しがないようにボタンの無効化をしたのですが、bindを使った場合はハンドラが呼び出されてしまいます。そこで、ハンドラの最初でstateを判定してDISABLEのボタンは処理しないようにしています。

プログラム

ダウンロード
# Hit and Blow
# 2022/03/10
from functools import cache
from itertools import count
from multiprocessing.connection import wait
import numbers
from sre_parse import State
import this
import tkinter as tk
import random
from tkinter import N, messagebox

# アイテムの種類
ITEM_MAX = 10

# 入力するアイテムの数
ITEM_COUNT = 4

# 縦に並べるボタン数
BUTTON_Y = 2

# 横に並べるボタン数
BUTTON_X = int(ITEM_MAX / BUTTON_Y)

# 改行文字
CR = '\r\n'

# 入力表示用
numbers = []
# 答え
ans = []
# 入力回数カウンタ
counter = 1
# 入力アイテム数
item_counter = 0

# 初期化関数
def initialize():
    global numbers
    global ans
    global counter
    global item_counter
    global lbl

    # 入力されたアイテム
    numbers = []
    # 答え
    ans = list(range(ITEM_MAX))
    random.shuffle(ans)
    # カウンタ
    counter = 0
    # 入力アイテム数
    item_counter = 0
    # 初期表示
    lbl['text'] = (' ' * 22 + CR) * 3

# 改行してカウンタを表示
def disp_counter():
    global counter
    global lbl
    counter += 1
    lbl['text'] += f' \r\n{counter:2}:'

# ハンドラ関数
def input_item(event):
    global numbers
    global ans
    global counter
    global item_counter
    global lbl

    # 無効化されたボタンは処理しない
    if event.widget.cget('state') == tk.DISABLED:
        return
    
    # ボタンの数字を取得
    no = int(event.widget.cget("text"))
    # 入力されたアイテムを保存
    numbers.append(no)
    # 押されたボタンの無効化
    event.widget.config(state=tk.DISABLED)

    # 入力アイテムの表示
    lbl['text'] += str(no)
    
    # 入力アイテム数が規定数になるのを待つ
    item_counter += 1
    if item_counter < ITEM_COUNT:
        return

    # Hit/Blow 判定
    hit = 0
    blow = 0
    for idx1 in range(ITEM_COUNT):
        try:
            idx2 = numbers.index(ans[idx1])
        except:
            continue

        if idx1 == idx2:
            hit += 1
        else:
            blow += 1
 
    # 一番古い行を削除
    tmp = lbl['text']
    pos = tmp.find(CR)
    if pos >= 0:
        lbl['text'] = tmp[pos+len(CR):]

    # Hti/Blowを表示して改行
    lbl['text'] += f' H={hit} B={blow}'

    # ボタンを有効にする
    for idx in range(ITEM_COUNT):
        tenkey[numbers[idx]]['state'] = tk.NORMAL

    # 入力アイテムをクリア
    numbers = []
    item_counter = 0

    # クリア判定
    if hit == ITEM_COUNT:
        messagebox.showinfo('', f'{counter}回で正解です!')
        initialize()
    # 改行してカウンタを表示
    disp_counter()

# トップレベルウインドウの生成
root = tk.Tk()
root.title('Hit and Blow')
root.geometry('240x320')

#Labelウィジェットの生成
lbl = tk.Label(root, text='', font=('System', 20), justify='left')

# Buttonウィジェットの生成
tenkey = []
for num in range(ITEM_MAX):
    tenkey.append(tk.Button(root, text=str(num), font=('System', 20)))
    tenkey[num].bind('<ButtonPress>', input_item)

# 各行の割合を指定
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=0)
root.rowconfigure(2, weight=0)

# 各列の割合を指定
for idx in range(int(ITEM_MAX / 2)):
    root.columnconfigure(idx, weight=1)

# grid関数で配置
lbl.grid(column=0, row=0, columnspan=int(ITEM_MAX/2))
for idx in range(ITEM_MAX):
    posx = idx % BUTTON_X
    posy = idx // BUTTON_X + 1
    tenkey[idx].grid(column=posx, row=posy, 
                    sticky=tk.NSEW, padx=5, pady=5)

# 変数初期化
initialize() 
# 改行してカウンタを表示
disp_counter()

# トップレベルウインドウの表示
root.mainloop()

'''
実行イメージ
 1:0123 H=0 B=0
 2:4567 H=0 B=2
 3:6789 H=0 B=4
 4:9786 H=2 B=2
 5:987
[1][2][3][4][5] 
[5][6][7][8][9]
'''

拍手[0回]

PR