忍者ブログ

PPM - Python Program Magazine

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

[PR]

×

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


ねこばかり

天秤にねこを乗せてみた

右の天秤に隠れているねこを当てるゲームです。


遊び方

下に居るねこを左のねこタワーに乗せて「計測開始」ボタンを押すと天秤が傾きます。
重いと下がり軽いと上がるので傾き加減で予想して右側のねこを当ててください。

プログラムの説明

 このプログラムはGUIをつかっています。GUIは標準ライブラリのtkinterです。
 画像を使ったプログラムを試したくて作ってみました。
 昔VC#で作ったもののリメイクなのですが、やはりGUIはVC#の方が作りやすいですね。

プログラム

ダウンロード

nekobakari.py

from email import message
from itertools import count
import random
from re import X
from struct import pack
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
from PIL import Image, ImageTk
import data

# 答え
ans = 0
# カウンタ
counter = 0
# 入力値
neko_weight = 0

def initialize():
    '''
    初期化
    '''
    global ans
    global counter
    global neko_weight

    # 答え
    max = 2**7
    print(f"{max}")
    ans = random.randint(1, max)
    print(f"{ans}")
    # カウンタ
    counter = 0
    # 入力値
    neko_weight = 0
    return

def initialize_neko():
    '''
    ねこ画像初期化
    '''
    for index, item in enumerate(data.item_info):
        item['enable'] = True

    canvas.lift('base')
    canvas.lift('tower')
    canvas.lift('neko')
    return

def show_answerneko():
    '''
    解答表示
    '''
    for index, item in enumerate(data.item_info):
        if item['enable'] == False:
            canvas.lift(answerneko_id[index], towerId[1])
    return

def move_neko(event):
    '''
    ねこ移動
    '''
    global neko_id
    global neko_weight
    global towerneko_id
    global towerId
    # マウスカーソルの位置に一番近い図形のIDのタプルを取得
    closest_ids = canvas.find_closest(event.x, event.y)
    index = neko_id.index(closest_ids[0])
    print(f'id : {closest_ids}')
    print(f'neko_id {index}')
    data.item_info[index]['enable'] = False
    event.widget.lift(towerneko_id[index], towerId[0])
    event.widget.lift(base_id[index], neko_id[index])
    neko_weight += data.item_info[index]['weight']
    print(f'neko_weight {neko_weight}')
    return

def return_neko(event):
    '''
    ねこ移動
    '''
    global base_id
    global neko_id
    global neko_weight
    global towerneko_id
    global towerId
    # マウスカーソルの位置に一番近い図形のIDのタプルを取得
    closest_ids = canvas.find_closest(event.x, event.y)
    index = towerneko_id.index(closest_ids[0])
    print(f'id : {closest_ids}')
    print(f'towerneko_id {index}')
    data.item_info[index]['enable'] = True
    event.widget.lower(towerneko_id[index], towerId[0])
    event.widget.lift(neko_id[index], base_id[index])
    neko_weight -= data.item_info[index]['weight']
    print(f'neko_weight {neko_weight}')
    return

def move_tower(diff):
    '''
    ねこタワー移動
    '''
    canvas.move(towerId[0], 0, -diff)
    canvas.move('towerneko', 0, -diff)
    canvas.move(towerId[1], 0, diff)
    canvas.move('answerneko', 0, diff)
    return

def return_tower(diff):
    '''
    ねこタワー正位置へ
    '''
    canvas.move(towerId[0], 0, diff)
    canvas.move('towerneko', 0, diff)
    canvas.move(towerId[1], 0, -diff)
    canvas.move('answerneko', 0, -diff)
    return

def check_weight():
    '''
    計測実行
    '''
    global canvas
    global counter
    global neko_weight
    global ans
    counter += 1
    diff = ans - neko_weight
    print(f"counter={counter} neko_weight={neko_weight} answer={ans}")
    if diff == 0:
        show_answerneko()
        messagebox.showinfo('終了', f'{counter}回目です。正解!')
        initialize()
        initialize_neko()
    else:
        move_tower(diff)
        if diff > 0:
            message = '軽い'
        else:
            message = '重い'
        messagebox.showinfo('判定', f'{counter}回目です。{message}')
        return_tower(diff)
    return


#version = tk.Tcl().eval('info patchlevel')

root = tk.Tk()
root.title(f'ねこばかり')
root.geometry('480x480')
#root.geometry('640x480')

# 画像表示
neko = []
for index, fname in enumerate(data.item_info):
    if index > 0:
        data.item_info[index]['x'] = data.item_info[index-1]['x'] + data.item_info[index-1]['width'] + 4
        data.item_info[index]['y'] = data.item_info[index-1]['y'] + 6

    img = Image.open(fname['file'])
    size = [fname['width'], fname['height']] 
    img = img.resize(size=size)
    img = ImageTk.PhotoImage(img)
    neko.append(img)

tower = []
for index, fname in enumerate(data.tower_info):
    img = Image.open(fname['file'])
    img = ImageTk.PhotoImage(img)
    tower.append(img)

# Canvasの作成
canvas = tk.Canvas(bg='wheat1', width=476, height=476)
canvas.place(x=0, y=0)

base_id = []
for index, item in enumerate(data.item_info):
    base_id.append(canvas.create_rectangle( 
                        item['x'], item['y'], 
                        item['x']+item['width'], 
                        item['y']+item['height'],  
                        fill='wheat2', width = 0, tag="base"))

neko_id = []
for index, pic in enumerate(neko):
    neko_id.append(canvas.create_image(
                        data.item_info[index]['x'], 
                        data.item_info[index]['y'], 
                        image=pic, anchor=tk.NW, tag='neko'))
canvas.tag_bind('neko', '<ButtonPress>', move_neko)

towerneko_id = []
for index, pic in enumerate(neko):
    towerneko_id.append(canvas.create_image(
                        data.item_info[index]['cagex'], 
                        data.item_info[index]['cagey'], 
                        image=pic, anchor=tk.NW, tag='towerneko'))
canvas.tag_bind('towerneko', '<ButtonPress>', return_neko)

answerneko_id = []
for index, pic in enumerate(neko):
    answerneko_id.append(canvas.create_image(
                        data.item_info[index]['cagex'] + 240,
                        data.item_info[index]['cagey'], 
                        image=pic, anchor=tk.NW, tag='answerneko'))

towerId = []
for index, pic in enumerate(tower):
    towerId.append(canvas.create_image(
                        data.tower_info[index]['x'], 
                        data.tower_info[index]['y'], 
                        image=pic, anchor=tk.NW, tag='tower'))

canvas.grid(row=0, column=0, rowspan=7)

btn = tk.Button(root,
            text='計測開始',
            command=check_weight)
btn.grid(row=3,column=0)

initialize()
root.mainloop()

data.py

itemarea_left = (480-346)/2
itemarea_top = 480-64-8
item_info = [
    {
        'enable':True, 
        'weight':64,
        'file':'./app1/neko11.png', 
        'width':64, 
        'height':64, 
        'cagex':120-64-2, 
        'cagey':300-64,
        'home_x':itemarea_left, 
        'home_y':itemarea_top, 
        'x':itemarea_left, 
        'y':itemarea_top, 
    },{
        'enable':True, 
        'weight':32,
        'file':'./app1/neko21.png', 
        'width':58, 
        'height':58, 
        'cagex':120+2,    
        'cagey':300-64-58/2-4,
        'home_x':itemarea_left, 
        'home_y':itemarea_top, 
        'x':itemarea_left, 
        'y':itemarea_top, 
    },{
        'enable':True, 
        'weight':16,
        'file':'./app1/neko31.png', 
        'width':52, 
        'height':52, 
        'cagex':120-52-2, 
        'cagey':300-64-52-4,
        'home_x':itemarea_left, 
        'home_y':itemarea_top, 
        'x':itemarea_left, 
        'y':itemarea_top, 
    },{
        'enable':True, 
        'weight':8,
        'file':'./app1/neko41.png', 
        'width':46, 
        'height':46, 
        'cagex':120+2,    
        'cagey':300-64-58/2-46-4*2,
        'home_x':itemarea_left, 
        'home_y':itemarea_top, 
        'x':itemarea_left, 
        'y':itemarea_top, 
    },{
        'enable':True, 
        'weight':4,
        'file':'./app1/neko51.png', 
        'width':40, 
        'height':40, 
        'cagex':120-40-2, 
        'cagey':300-64-52-40-4*2,
        'home_x':itemarea_left, 
        'home_y':itemarea_top, 
        'x':itemarea_left, 
        'y':itemarea_top, 
    },{
        'enable':True, 
        'weight':2,
        'file':'./app1/neko61.png', 
        'width':34, 
        'height':34, 
        'cagex':120+2,    
        'cagey':300-64-58/2-46-34-4*3,
        'home_x':itemarea_left, 
        'home_y':itemarea_top, 
        'x':itemarea_left, 
        'y':itemarea_top, 
    },{
        'enable':True, 
        'weight':1,
        'file':'./app1/neko71.png', 
        'width':28, 
        'height':28, 
        'cagex':120-28-2, 
        'cagey':300-64-52-40-28-4*3,
        'home_x':itemarea_left, 
        'home_y':itemarea_top, 
        'x':itemarea_left, 
        'y':itemarea_top, 
    },
]

towerarea_left = 53
towerarea_top = 80
tower_info = [
        {
            'file':'./app1/tower01.png',
            'home_x':towerarea_left,
            'home_y':towerarea_top,
            'x':towerarea_left,
            'y':towerarea_top,
        },
        {
            'file':'./app1/tower02.png',
            'home_x':towerarea_left+240, #293,
            'home_y':towerarea_top,
            'x':towerarea_left+240,
            'y':towerarea_top,
        },
]

画像データ

拍手[0回]

PR

フラッシュ暗算

計算能力を鍛えましょう

足し算なんて簡単さ

 1秒に1個ずつ表示される1桁の数字10個を足し算します。
 これ、毎日やると計算能力鍛えられそうですね。
 もう少し凝って、桁数や時間、出題数を変えられるよう改造する余地があります。


プログラムの説明

 このプログラムはGUIをつかっています。GUIは標準ライブラリのtkinterです。
 ウィジェットの配置にはgridを使用しています。
 after()メソッドでfunc_clear()とfunc_interval()を交互に呼び出しています。そうしないと同じ数字が続いたときにわからなくなるためです。

プログラム

ダウンロード

import tkinter as tk
import random

# 問題
mondai = []
# 出題番号
number = 0
# 状態
state = 0

# ガイド文字列
guide_message = '開始ボタンを押すとスタートします'

def func_clear():
    '''
    消去処理
    '''
    # 表示消去
    global lbl
    lbl['text'] = ''
    # 次の出題の表示設定
    global number
    number += 1
    root.after(500, func_interval)
    return

def show_addend():
    '''
    問題の表示
    '''
    global lbl
    global mondai
    global number
    lbl['text'] = str(mondai[number])
    global root
    root.after(500, func_clear)
    return

def func_interval():
    '''
    定期処理
    '''
    global number
    global mondai
    if number < len(mondai):
        # 出題番号の値表示
        show_addend()
        return

    # 定期処理開始
    global root
    global btnStart
    btnStart['state'] = tk.NORMAL
    btnStart['text'] = '答え'
    lblStart.config(state=tk.NORMAL)
    return

def satrt_anzan():
    '''
    ハンドラ関数
    '''
    global btnStart
    global mondai
    global number
    global state
    if state == 0:
        state = 1
        # 押されたボタンの無効化
        btnStart.config(state=tk.DISABLED)
        lblStart.config(state=tk.DISABLED)
        # 変数初期化
        number = 0
        # 問題作成
        mondai = []
        for no in range(10):
            mondai.append(random.randint(1, 9))
        # 最初の値表示
        show_addend()
        return

    state = 0
    # 最後まで表示した
    lbl['text'] = f'答え={sum(mondai)}'
    btnStart['text'] = '開始'
    number = 0
    return

'''
メイン処理
'''
# トップレベルウインドウの生成
root = tk.Tk()
root.title('Flash Anzan')
root.geometry('320x240')

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

# Buttonウィジェットの生成と配置
btnStart = tk.Button(root, text='開始', font=('System', 20), command=satrt_anzan)
# 各列の割合を指定
root.columnconfigure(0, weight=1)
# 各行の割合を指定
root.rowconfigure(0, weight=3)
root.rowconfigure(1, weight=1)
root.rowconfigure(2, weight=1)

# grid関数で配置
lbl.grid(column=0, row=0, columnspan=1)
btnStart.grid(column=0, row=1, columnspan=1)
lblStart.grid(column=0, row=2, columnspan=1)

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

拍手[0回]


ヒット・アンド・ブロー

答えが何か当ててごらん

 数あてゲームです。
 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回]