忍者ブログ

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回]


AndroidでPython

AndroidでPythonが使えるようにします。
ここでは、Playストアからインストール可能な pyroid 3 を紹介します。 

まずはPlayストアを開き、pyroid 3 を検索します。
インストールボタンを押すとインストールされます。

インストール後、アイコンからPyroid 3 を起動します。
Pyroid 3 を最初に起動したときは以下の画面が表示されるので"LET'S START"をタップします。

質問が表示されるので選択して"CONTINUE"をタップします。
ここでは小規模プロジェクトを選択しています。

使用目的を聞いているようなのでホビーとしています。
"CONTINUE"をタップすると次の画面に進みます。

カスタマイズ項目が表示されるので好きな機能をONにします。

ここでは、自動保存と、タブボタンでタブではなくスペースを入力する設定をONにしました。
ダークテーマはお好みで。
"CONTINUE"をタップすると次の画面に進みます。

どんなプログラムを作るのか聞かれたのでGUIアプリを選択しました。
"CONTINUE"をタップすると次の画面に進みます。

この画面はどうすれば良いのか迷ったのですが、よく見ると有料版の広告でした。
右上のXをタップするとようやくメインの画面が表示されます。

以降は起動するとこの画面が表示されます。


白い部分をタップするとキーボードが表示されるので、プログラムを入力します。

画面右側にある黄色の丸に横向き三角のボタンをタップするとプログラムが実行できます。

終了するときは画面左上の←をタップするか戻るボタンでエディタの画面に戻ります。

PPMのプログラムはテキストファイルを用意しているので、ダウンロードして読み込むと使えます。
PPMのサイトをブラウザで表示し、プログラムの部分に「ダウンロード」があるので長押ししてメニューを表示させます。

「リンクをダウンロード」でスマホ内にプログラムファイルを保存します。

Pyroid3の画面上部にあるファイルアイコンで保存したプログラムファイルを読み込みます。

三角ボタンをタップして実行します。

終了は戻るボタンです。

Windowsで作ったプログラムがAndroidで動いて感動です。

拍手[0回]


ダイス

さあ、ゲームを始めよう

 テーブルゲームで遊ぶとき必要になるのがサイコロです。
 普通サイコロというと正六面体のものを指します。1から6までの値を同じ確率で発生させるために使うものですよね。
 ゲームによっては他のサイコロを使うこともあります。物理的に存在するのは正4面体、正8面体、正12面体、正20面体ぐらいでしょうか。確率を調整した10面体や100面体のサイコロも見たことがありますが、100面体は面が多すぎてまるでゴルフボールのようでした。
 いろんな種類のきれいなサイコロを集めるのも楽しいのですが、任意のサイコロを必要な数振ることができると便利そうです。
 ということで、サイコロを作ってみました。

どんなサイコロを何個使う?

 1D6と書いて6面体のサイコロを1つ振るという意味になります。Dの前が個数、後ろがサイコロの種類です。
 実際のサイコロとは違い、物理的な形状に制約がないため、2から100までのサイコロを自由に作成することができます。
 個数は1個から10個までです。

プログラムの説明

 このプログラムはGUIをつかっています。GUIは標準ライブラリのtkinterです。
 文字の表示はLabelウィジェット、数字の選択はComboboxウィジェット、ボタンはButtonウィジェットです。
 ウィジェットの配置にはgridを使用しています。
 プログラム自体は何の難しいこともしていませんが、ウィジェットの配置と、サイコロの出目をどう見せるかは工夫した部分です。
 3D6はRPGのキャラクタメイキングで使われますが、期待値が11.5で最大値の18と最小値の3が出にくくなります。つまり大体同じような値となり、大きく外れた値は運が良いまたは悪い時にしか出ないようになります。
 これを合計値だけで出してしまうと確立に細工していないか?と勘繰られてしまいます。そうならないよう、すべての出目とその合計値を出力するようにしました。

プログラム

ダウンロード

from logging import root
from struct import pack
from os import system
from tkinter import ttk
import tkinter as tk
import random

def roll_dice():
    '''
    サイコロを振る
    '''
    count = int(cb_count.get())     # サイコロの数を取得
    dice = int(cb_dice.get())       # サイコロの種類を取得
    total = 0               # 合計値
    history = []            # サイコロの出目
    for no in range(count):
        d = random.randint(1, dice)
        total += d
        history.append(d)
    lbl_result['text'] = f'{total}\r\n{history}'
    return

'''
ウインドウを作成
サイコロの個数選択と結果表示、サイコロを振るボタンを配置する
'''
root = tk.Tk()
root.title('ダイス')
root.geometry('240x320')

guidemsg = tk.Label(root,
            text='サイコロの数と種類を選択してください')
count_nums = tuple(no for no in range(1, 11))
cb_count = ttk.Combobox(root,
            width=6,
            state='readonly',
            values=count_nums)
cb_count.current(0)
lbl_d = tk.Label(root,
            text='D')
dice_kind = tuple(no for no in range(2, 101))
cb_dice = ttk.Combobox(root,
            width=6,
            state='readonly',
            values=dice_kind)
cb_dice.current(4)
lbl_result = tk.Label(root,
            text='\r\n[ ]',
            font=('system', 20))
btn = tk.Button(root,
            text='サイコロを振る',
            command=roll_dice)

# 各行の割合を指定
weit = [1,1,3,1]
for no in range(4):
    root.rowconfigure(no, weight=weit[no])

# 各列の割合を指定
for no in range(3):
    root.columnconfigure(no, weight=1)

# grid関数で配置
guidemsg.grid(column=0, row=0, columnspan=3)
cb_count.grid(column=0, row=1, sticky=tk.E)
lbl_d.grid(column=1, row=1)
cb_dice.grid(column=2, row=1, sticky=tk.W)
lbl_result.grid(column=0, row=2, columnspan=3)
btn.grid(column=0, row=3, columnspan=3)

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回]


        
  • 1
  • 2