[Python] ファイル削除ツール アップデートv0.0.2 処理完了表示

[macOS Catalina 10.15.7]

結構重宝しているファイル削除ツールをアップデートしました。

削除するファイル数が多いと処理完了のタイミングがわからないので、GUI上にメッセージを表示すると同時にデスクトップ通知を送るようにしました。

簡単なツールやスクレイピング関連アプリはこれまで通りPythonのtkinterで、 MySQL関連など本格的に仕上げたい場合はJavaのSwingあるいはJavaFXで作成するつもりです。

tkinterで作成するGUIは今ひとつモダンな感じがしませんが、ツール類はともかくスクレイピングのコードをJavaで書くのはかなりの難行と思われるため大人しくtkinterにしておきます。

import glob,os,shutil,sys
import tkinter as tk
import tkinter.font as font
from tkinter import ttk

def list_text():
    # フレーム内全ウィジェットのinfoを取得
    children = frame.winfo_children()
    text_list = [entry for entry in children if type(entry)==tk.Text]
    print(f'textのみ抽出\n{text_list}\n')

    return text_list

class FrameB(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.configure(background = '#FFFACD')
        self.grid(row=1,column=1, sticky=tk.NSEW, padx=5, pady=5)

# Macのデスクトップ通知
def notification1():
    os.system("osascript -e 'display notification \"フォルダ削除\n処理完了\"'")

def notification2():
    os.system("osascript -e 'display notification \"ファイル削除\n処理完了\"'")

# 色
# '#98fb98','#8b0000','#808000','#FF7F50','#00FA9A','#9370DB','#40E0D0','#FFFACD'

root = tk.Tk()
root.title("FILE REMOVER v0.0.2")
root.geometry("420x200")
root.configure(bg='#FFFACD')

# 設定
my_font = font.Font(root,family="System",size=18,weight="normal")
my_font2 = font.Font(root,family="System",size=16,weight="normal")

# フレームの作成・配置
frame = tk.Frame(root,background = '#FFFACD')
frame.grid(row=0,column=0, sticky=tk.NSEW, padx=5, pady=10)
frameB = FrameB(master=frame)

# ラベルの作成・配置
label = tk.Label(frame,text='PATH',background = '#FFFACD',foreground = '#8b0000',font=my_font)
label.grid(row=0, column=0)

# パス入力エントリの作成・配置
entry = tk.Entry(frame,width=25,background = '#98fb98',foreground = '#8b0000',font=my_font)
entry.grid(row=0,column=1,sticky=tk.W)

# 実行ボタンの作成・配置
var_act = tk.IntVar()
btn = tk.Button(frame, text="削除",command=lambda:var_act.set(1),width=2,font=my_font)
btn.grid(row=0,column=2,padx=2)

# ラジオボタンの作成・配置 FrameBにパックして左に詰め込む
var = tk.IntVar()
rb1 = ttk.Radiobutton(frameB,text='フォルダ',value=1,variable=var)
rb1.pack(padx=30,side=tk.LEFT)

rb2 = ttk.Radiobutton(frameB,text='ファイル',value=2,variable=var)
rb2.pack(padx=30,side=tk.LEFT)

# メッセージボックスの作成・配置
msg_box = tk.Text(frame,width=25,height=5,background = '#98fb98',foreground = '#8b0000',font=my_font)
msg_box.grid(row=2,column=1)

# クリアボタンの作成・配置
btn2 = tk.Button(frame, text="クリア",command=lambda:[entry.delete(0,tk.END),text_list[0].delete('1.0',tk.END)],width=2,font=my_font)
btn2.grid(row=1,column=2,padx=2)

text_list = list_text()
print(text_list)

for i in range(100): # 100回処理可能
    # フォルダパスの入力を待機
    btn.wait_variable(var_act)

    # 入力したフォルダパスを取得
    dirpath = entry.get()

    # ラジオボタンのvalueを取得
    v = var.get()

    if v == 1:
        path = dirpath + '/*' # 直下のフォルダを削除
        for f in glob.glob(path,recursive=True):
            if os.path.isdir(f):
                shutil.rmtree(f)
        text_list[0].delete('1.0',tk.END)
        text_list[0].insert(tk.END,dirpath+'\n')
        text_list[0].insert(tk.END,'フォルダ削除完了')
        notification1()
    else:
        path = dirpath + '/**' # 再帰的処理により下層ファイルも削除
        for f in glob.glob(path, recursive=True):
            if os.path.isfile(f):
                os.remove(f)
        text_list[0].delete('1.0',tk.END)
        text_list[0].insert(tk.END,dirpath+'\n')
        text_list[0].insert(tk.END,'ファイル削除完了')
        notification2()

root.mainloop()

[Python] 185 麻雀アプリ 09 アプリのアイコン設定

2021年3月20日作成

今日はコーディングで疲れたため、残りはグラフィック作業に切り替えました。

麻雀牌画像のフリー素材からアイコンを作成し、コードで設定しました。

画像は正方形でないと比率がおかしくなります。背景は透明が望ましいです。

初めはGIMPで作ろうとしましたが、いつもの如く全く手が動かず。

最近インストールしたAdobe XDなら直感的操作で10分程度でした。

# rootフレームの設定

root = tk.Tk()
root.title("麻雀ゲーム")
root.geometry("1200x450")
root.configure(bg=th)
img = tk.Image("photo", file="イーソウ正方形透明.png")
root.tk.call('wm','iconphoto', root._w, img)

[Python] 184 麻雀アプリ 08 GUI入力待ちの実装

2021年3月20日作成

ターミナルではinput関数で入力待ちにできましたが、GUI上ではどうすればいいのか分かりませんでした。

調べるとwait_variableなどの待機関数を使うと入力待ちにできることが判明しました。
この段階に入ってくると、ネット検索よりもtkinterのドキュメントを読む方が早いのかもしれません。

tkinterは調べるのが大変な反面、やれないことはまずないという信頼感を抱かせるツールです。

ところでコーディングの量が自分レベルでは多くなり、学習成果を都度ブログにアップする余裕がなくなってきました。500行を超えるコードは久しぶりです。

まだ序盤の序盤なので自家鳴き限定ルールでも最終的にどれくらいのボリュームになるのか見当がつかないです。

コードの階層が多くなってきたため集中して思考の深みに入らないとコーディングが進まない位で、お手軽プログラムの域を超えています。

果たしてAIの検討までたどり着けるのか不安になってきました。

[Python] 183 麻雀アプリ 07 ミニ麻雀ゲームのGUI化

2021年3月19日作成

開発を進めているミニ麻雀ゲームですが、ターミナルに表示された手牌や河が読みにくいのでGUI化してみました。

打牌の入力は今のところターミナルからです。 当然ですが完成品では他家の手牌は見えなくします。

これで大分プレイしやすくなりました。記号ではなく絵柄にすればもっとやりやすくなりそうです。

[Python] 182 麻雀アプリ 06 自家のみポン可能なミニ麻雀ゲーム

2021年3月18日作成

全家に配牌して、自家のみポンできるミニ麻雀ゲームのコードです。

他家の捨て牌は埋牌後の右端の牌なのでまず和了れません。ここまで書いて結構くたびれました。

鳴いた後に下家の番になるようにするところや、ポンして出来る刻子の扱いに悩まされました。

次はチーやリーチができるようにして、さらに他家も自動的に鳴けるようにしたいです。

コード例
クラスは省略し本文のみ。自家は親番鳴いた牌はリストの左端に2次元リストとして表示。

taku = Taku()
yama = taku.yama

print(f'初期状態の山\n{yama}')
print(f'山の枚数 {len(yama)}\n')

janshi1 = Janshi('自家')
janshi2 = Janshi('下家')
janshi3 = Janshi('対面')
janshi4 = Janshi('上家')
janshi_l = [janshi1,janshi2,janshi3,janshi4]

# 配牌
for j in janshi_l:
    j.haipai()
    print(f'janshi{(janshi_l.index(j))+1} 手牌\n{j.tehai}')

del yama[0:52]
print(f'配牌後の山\n{yama}')
print(f'配牌後 山の枚数 {len(yama)}\n')

# 埋牌
for j in janshi_l:
    j.riipai()
    print(f'janshi{(janshi_l.index(j))+1} 埋牌後手牌\n{j.tehai_all}\n')

# 順番に打牌
meld_l = [0]
countdown = 70
for i in range(18):
    for j in janshi_l:
        if meld_l[-1] == 1 and j == (janshi1 or janshi3 or janshi4):
           pass
        else:
            tsumohai = j.tsumo(yama)
            countdown -= 1
            print(f'残り {countdown}枚')
            print(f'janshi{(janshi_l.index(j))+1} ツモ牌 {tsumohai}')
            j.riipai()

            if j == janshi1:
                print(f'janshi{(janshi_l.index(j))+1} ツモ後手牌\n{j.tehai_all}\n')

            print(f'janshi{(janshi_l.index(j))+1} 向聴数 {j.shanten_calc(j.tehai_all)}')

            if int(j.shanten_calc(j.tehai)) < 0:
                j.hand_result = j.keisan(j.tehai,tsumohai)
                try:
                    j.print_hand_result(j.hand_result)
                except:
                    print('役なしのため和了れません')
                else:
                    print('和了!')
                    sys.exit()
            
            if j == janshi1:
                print('捨て牌を選んでください')
                dahai = input()
                j.dahai(dahai)
            else:
                print(f'janshi{(janshi_l.index(j))+1} 打牌 {j.tehai_all[-1]}')
                j.dahai(j.tehai_all[-1])

                print(f'鳴く場合は牌を入力してください')
                get_hai = input()
            
                if get_hai == '':
                    meld_l.append(0)
                    pass
                elif get_hai != j.sutehai[-1]:
                    print('入力ミスです。もう一度入力してください')
                    get_hai = input()
                else:
                    meld_l.append(1)
                    janshi1.pon(get_hai)
                    print('捨て牌を選んでください')
                    dahai = input()
                    janshi1.dahai(dahai)
                    print(janshi1.tehai)

            print(f'janshi{(janshi_l.index(j))+1} 打牌後手牌\n{j.tehai_all}')
            print(f'janshi{(janshi_l.index(j))+1} 河\n{j.sutehai}\n')

        if countdown == 0:
            print('流局です!')
            sys.exit()
--------------------------------------------------

  出力
--------------------------------------------------
<略>
  残り 14枚
janshi4 ツモ牌 z3
フリー牌 ['m2', 'm6', 'm9', 'p1', 'p1', 'p2', 'p6', 'p6', 'p6', 'p8', 'p9', 's1', 's2', 'z3']
鳴き牌 []
全手牌 ['m2', 'm6', 'm9', 'p1', 'p1', 'p2', 'p6', 'p6', 'p6', 'p8', 'p9', 's1', 's2', 'z3']
janshi4 向聴数 3
janshi4 打牌 z3
鳴く場合は牌を入力してください
z3
捨て牌を選んでください
m8

janshi4 打牌後手牌
['m2', 'm6', 'm9', 'p1', 'p1', 'p2', 'p6', 'p6', 'p6', 'p8', 'p9', 's1', 's2']
janshi4 河
['z5', 'z2', 'z2', 'z2', 'z5', 'z7', 's8', 'z4', 's8', 's6', 's4', 's3', 's2', 'z3']
  <略>
  残り 6枚
janshi1 ツモ牌 m8
フリー牌 ['m2', 'm3', 'm4', 'm6', 'm6', 'm7', 'm7', 's4', 's5', 's6', 'm8']
鳴き牌 [['z3', 'z3', 'z3']]
全手牌 [['z3', 'z3', 'z3'], 'm2', 'm3', 'm4', 'm6', 'm6', 'm7', 'm7', 'm8', 's4', 's5', 's6']
janshi1 ツモ後手牌
[['z3', 'z3', 'z3'], 'm2', 'm3', 'm4', 'm6', 'm6', 'm7', 'm7', 'm8', 's4', 's5', 's6']

janshi1 向聴数 0
捨て牌を選んでください
m7
janshi1 打牌後手牌
[['z3', 'z3', 'z3'], 'm2', 'm3', 'm4', 'm6', 'm6', 'm7', 'm8', 's4', 's5', 's6']
janshi1 河
['z1', 'p1', 's9', 'm9', 'p8', 'p1', 'p4', 'p4', 's5', 'm3', 'p7', 'p7', 'z5', 'm1', 'm8', 's8', 'm7']
  <略>

[Python] 181 麻雀アプリ 05 mahjongライブラリの日本語化検討

2021年3月17日作成

役名の日本語化が進められているようですが、今のところうまく機能しません。

ソースコードを確認したところ、locale.textreporterやhand_calculating.hand_responseが未完成に見えます。 だからなのか、通常のセットアップではlocaleフォルダがインストール対象に入っていません。

オリジナルを保存しておいて自分なりに手を加えてみたものの、小手先では無理な印象です。 スキルがあれば開発に参加したいのですが残念です。

ここは一旦棚上げにして次に進みます。

[Python] 180 麻雀アプリ 04 ミニ麻雀ゲーム修正

2021年3月16日作成

前回のコードに修正を入れました。

下の方の独立した関数をクラスに組み込みました。

次は他家の河を表示して鳴けるようにしようと考えています。

import random,sys
from mahjong.hand_calculating.hand import HandCalculator
from mahjong.tile import TilesConverter
from mahjong.hand_calculating.hand_config import HandConfig, OptionalRules
from mahjong.meld import Meld
from mahjong.constants import EAST, SOUTH, WEST, NORTH
from mahjong.shanten import Shanten

calculator = HandCalculator()

class Taku:
    def __init__(self):
        # 萬子:m,筒子:p,索子:s,字牌:z
        # shurui = ['m','p','s','z']
        # num = [9,9,9,7]

        # このゲームは萬子と筒子のみ
        shurui = ['m','p']
        num = [9,9]

        self.yama = []
        for s,n in zip(shurui,num):
            for i in range(1,n+1):
                hai = s + str(i)
                self.yama.append(hai)
        
        self.yama = self.yama * 4
        random.shuffle(self.yama)

class Janshi:
    def __init__(self,position):
        self.position = position
        self.tehai = []
        self.sutehai = []

    def haipai(self, yama):
        self.tehai =  yama[0:13]
        del yama[0:13]
        return self.tehai

    def ripai(self):
        self.tehai = sorted(self.tehai)
        man = [hai for hai in self.tehai if 'm' in hai]
        pin = [hai for hai in self.tehai if 'p' in hai]
        sou = [hai for hai in self.tehai if 's' in hai]
        zi = [hai for hai in self.tehai if 'z' in hai]
        self.tehai = man + pin + sou + zi

    def tsumo(self, yama):
        hai = yama[0]
        del yama[0]
        self.tehai.append(hai)
        return hai

    def dahai(self,hai):
        hai_i = self.tehai.index(hai)
        del self.tehai[hai_i]
        self.sutehai.append(hai)
        return hai

    def shanten_calc(self, tehai,position):
        tiles_man = [hai[1] for hai in self.tehai if 'm' in hai]
        tiles_pin = [hai[1] for hai in self.tehai if 'p' in hai]
        tiles_sou = [hai[1] for hai in self.tehai if 's' in hai]
        tiles_honors = [hai[1] for hai in self.tehai if 'z' in hai]

        # Shantenのインスタンス生成
        shanten = Shanten()
        tiles = TilesConverter.string_to_34_array(man=tiles_man, pin=tiles_pin, sou=tiles_sou, honors=tiles_honors)

        return str(shanten.calculate_shanten(tiles))

    def keisan(self,tehai,tsumohai,position):
        tiles_man = [hai[1] for hai in self.tehai if 'm' in hai]
        tiles_pin = [hai[1] for hai in self.tehai if 'p' in hai]
        tiles_sou = [hai[1] for hai in self.tehai if 's' in hai]
        tiles_honors = [hai[1] for hai in self.tehai if 'z' in hai]

        tsumohai_l = [tsumohai]
        print(tsumohai_l)
        end_man = [hai[1] for hai in tsumohai_l if 'm' in hai]
        end_pin = [hai[1] for hai in tsumohai_l if 'p' in hai]
        end_sou = [hai[1] for hai in tsumohai_l if 's' in hai]
        end_honors = [hai[1] for hai in tsumohai_l if 'z' in hai]

        # 和了牌姿
        tiles = TilesConverter.string_to_136_array(man=tiles_man, pin=tiles_pin, sou=tiles_sou, honors=tiles_honors)
        # 和了牌(最後のツモ牌)
        win_tile = TilesConverter.string_to_136_array(man=end_man, pin=end_pin, sou=end_sou, honors=end_honors)[0]
        # 鳴きなし
        melds = None
        # ドラなし
        dora_indicators = None
        # オプションなし
        config = None
        # 計算
        result = calculator.estimate_hand_value(tiles, win_tile, melds, dora_indicators, config)
        return result

    # 結果出力
    def print_hand_result(self,result):
        # 翻数, 符数
        print(self.result.han, self.result.fu)
        # 点数(親(放銃者),子)
        print(self.result.cost['main'], self.result.cost['additional'])
        # 役
        print(self.result.yaku)
        # 符数内容
        for fu_item in self.result.fu_details:
            print(fu_item)

taku = Taku()
yama = taku.yama
print(f'枚数 {len(yama)}')

print(f'初期状態の山\n{yama}\n')

janshi1 = Janshi('自家')
janshi1.haipai(yama)
print(f'自家手牌\n{janshi1.tehai}\n')

janshi1.ripai()
print(f'埋牌後自家手牌\n{janshi1.tehai}\n')

for i in range(18):
    position = '自家'
    print(f'{i+1}順目')
    tsumohai = janshi1.tsumo(yama)
    print(f'ツモ牌 {tsumohai}')
    janshi1.ripai()
    print(f'ツモ後手牌\n{janshi1.tehai}\n')
    print(f'向聴数 {janshi1.shanten_calc(janshi1.tehai,position)}\n')

    if int(janshi1.shanten_calc(janshi1.tehai,position)) < 0:
        janshi1.result = janshi1.keisan(janshi1.tehai,tsumohai,position)
        try:
            janshi1.print_hand_result(janshi1.result)
        except:
            print('役なしのため和了れません')
        else:
            print('和了!')
            sys.exit()

    print('捨て牌を選んでください')
    dahai = input()
    janshi1.dahai(dahai)

    print(f'打牌 {dahai}')
    print(f'打牌後手牌\n{janshi1.tehai}\n')
    print(f'捨て牌\n{janshi1.sutehai}\n')

[Python] 179 麻雀アプリ 03 鳴きなしツモ和了りのみの麻雀ゲーム

2021年3月16日作成

鳴きやドラのない単純な麻雀ゲームを作成しました。手牌のリストから牌を頭に浮かべてプレイします。


自分しか和了れないので、動作確認程度で動かしてみてください。


和了りやすくするため、索子と字牌を除いた72枚構成にしました。点数計算は自動です。

コード例
点数がロン和了りになっていますがそのままにしています。

import random,sys
from mahjong.hand_calculating.hand import HandCalculator
from mahjong.tile import TilesConverter
from mahjong.hand_calculating.hand_config import HandConfig, OptionalRules
from mahjong.meld import Meld
from mahjong.constants import EAST, SOUTH, WEST, NORTH
from mahjong.shanten import Shanten

calculator = HandCalculator()

class Taku:
    def __init__(self):
        # 萬子:m,筒子:p,索子:s,字牌:z
        # shurui = ['m','p','s','z']
        # num = [9,9,9,7]

        # このゲームは萬子と筒子のみ
        shurui = ['m','p']
        num = [9,9]

        self.yama = []
        for s,n in zip(shurui,num):
            for i in range(1,n+1):
                hai = s + str(i)
                self.yama.append(hai)
        
        self.yama = self.yama * 4
        random.shuffle(self.yama)

class Janshi:
    def __init__(self,position):
        self.position = position
        self.tehai = []
        self.sutehai = []

    def haipai(self, yama):
        self.tehai =  yama[0:13]
        del yama[0:13]
        return self.tehai

    def ripai(self):
        self.tehai = sorted(self.tehai)
        man = [hai for hai in self.tehai if 'm' in hai]
        pin = [hai for hai in self.tehai if 'p' in hai]
        sou = [hai for hai in self.tehai if 's' in hai]
        zi = [hai for hai in self.tehai if 'z' in hai]
        self.tehai = man + pin + sou + zi

    def tsumo(self, yama):
        hai = yama[0]
        del yama[0]
        self.tehai.append(hai)
        return hai

    def dahai(self,hai):
        hai_i = self.tehai.index(hai)
        del self.tehai[hai_i]
        self.sutehai.append(hai)
        return hai

    def shanten(self, tehai):
        self.tehai = sorted(self.tehai)
        tiles_man = [hai[1] for hai in self.tehai if 'm' in hai]
        tiles_pin = [hai[1] for hai in self.tehai if 'p' in hai]
        tiles_sou = [hai[1] for hai in self.tehai if 's' in hai]
        tiles_honors = [hai[1] for hai in self.tehai if 'z' in hai]

        # Shantenのインスタンス生成
        shanten = Shanten()
        tiles = TilesConverter.string_to_34_array(man=tiles_man, pin=tiles_pin, sou=tiles_sou, honors=tiles_honors)

        return str(shanten.calculate_shanten(tiles))

def keisan(tehai,tsumohai):
    tiles_man = [hai[1] for hai in tehai if 'm' in hai]
    tiles_pin = [hai[1] for hai in tehai if 'p' in hai]
    tiles_sou = [hai[1] for hai in tehai if 's' in hai]
    tiles_honors = [hai[1] for hai in tehai if 'z' in hai]

    tsumohai_l = [tsumohai]
    print(tsumohai_l)
    end_man = [hai[1] for hai in tsumohai_l if 'm' in hai]
    end_pin = [hai[1] for hai in tsumohai_l if 'p' in hai]
    end_sou = [hai[1] for hai in tsumohai_l if 's' in hai]
    end_honors = [hai[1] for hai in tsumohai_l if 'z' in hai]

    # 和了牌姿
    tiles = TilesConverter.string_to_136_array(man=tiles_man, pin=tiles_pin, sou=tiles_sou, honors=tiles_honors)
    # 和了牌(最後のツモ牌)
    win_tile = TilesConverter.string_to_136_array(man=end_man, pin=end_pin, sou=end_sou, honors=end_honors)[0]
    # 鳴きなし
    melds = None
    # ドラなし
    dora_indicators = None
    # オプションなし
    config = None
    # 計算
    result = calculator.estimate_hand_value(tiles, win_tile, melds, dora_indicators, config)
    return result

# 結果出力
def print_hand_result(hand_result):
     # 翻数, 符数
     print(hand_result.han, hand_result.fu)
     # 点数(親(放銃者),子)
     print(hand_result.cost['main'], result.cost['additional'])
     # 役
     print(hand_result.yaku)
     # 符数内容
     for fu_item in hand_result.fu_details:
          print(fu_item)

taku = Taku()
yama = taku.yama
print(f'枚数 {len(yama)}')

print(f'初期状態の山\n{yama}\n')

janshi1 = Janshi('自家')
janshi1.haipai(yama)
print(f'自家手牌\n{janshi1.tehai}\n')

janshi1.ripai()
print(f'埋牌後自家手牌\n{janshi1.tehai}\n')

for i in range(18):
    print(f'{i+1}順目 手牌')
    tsumohai = janshi1.tsumo(yama)
    print(f'ツモ牌 {tsumohai}')
    janshi1.ripai()
    print(f'ツモ後手牌\n{janshi1.tehai}\n')
    print(f'向聴数 {janshi1.shanten(janshi1.tehai)}\n')

    if int(janshi1.shanten(janshi1.tehai)) < 0:
        print('和了!')
        result = keisan(janshi1.tehai,tsumohai)
        print_hand_result(result)
        sys.exit()

    print('捨て牌を選んでください')
    dahai = input()
    dahai = janshi1.dahai(dahai)

    print(f'打牌 {dahai}')
    print(f'打牌後手牌\n{janshi1.tehai}\n')
    print(f'捨て牌\n{janshi1.sutehai}\n')
--------------------------------------------------

  出力
--------------------------------------------------
枚数 72
初期状態の山
['m2', 'p1', 'm5', 'm9', 'm4', 'p3', 'm3', 'm7', 'p5', 'p7', 'p6', 'm6', 'p2', 'p1', 'p9', 'p4', 'p1', 'm7', 'p8', 'p4', 'p6', 'p9', 'm9', 'm7', 'm2', 'm5', 'p2', 'p7', 'm1', 'm4', 'm3', 'm5', 'm3', 'm8', 'p8', 'm5', 'm8', 'm1', 'm1', 'm6', 'm3', 'p3', 'p7', 'p8', 'm4', 'p3', 'p2', 'm9', 'p7', 'm9', 'p9', 'm6', 'm4', 'm8', 'm7', 'm8', 'p1', 'm6', 'p5', 'p8', 'm2', 'p2', 'p5', 'p9', 'p3', 'p6', 'p5', 'm1', 'p6', 'p4', 'm2', 'p4']

自家手牌
['m2', 'p1', 'm5', 'm9', 'm4', 'p3', 'm3', 'm7', 'p5', 'p7', 'p6', 'm6', 'p2']

埋牌後自家手牌
['m2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm9', 'p1', 'p2', 'p3', 'p5', 'p6', 'p7']

1順目 手牌
ツモ牌 p1
ツモ後手牌
['m2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm9', 'p1', 'p1', 'p2', 'p3', 'p5', 'p6', 'p7']
向聴数 0
捨て牌を選んでください
m9
打牌 m9
打牌後手牌
['m2', 'm3', 'm4', 'm5', 'm6', 'm7', 'p1', 'p1', 'p2', 'p3', 'p5', 'p6', 'p7']
捨て牌
['m9']

2順目 手牌
ツモ牌 p9
ツモ後手牌
['m2', 'm3', 'm4', 'm5', 'm6', 'm7', 'p1', 'p1', 'p2', 'p3', 'p5', 'p6', 'p7', 'p9']
向聴数 0
捨て牌を選んでください
p9
打牌 p9
打牌後手牌
['m2', 'm3', 'm4', 'm5', 'm6', 'm7', 'p1', 'p1', 'p2', 'p3', 'p5', 'p6', 'p7']
捨て牌
['m9', 'p9']

3順目 手牌
ツモ牌 p4
ツモ後手牌
['m2', 'm3', 'm4', 'm5', 'm6', 'm7', 'p1', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7']
向聴数 -1
和了!
['p4']
1 30
1000 0
[Pinfu]
{'fu': 30, 'reason': 'base'}
--------------------------------------------------

[Python] 178 麻雀アプリ 02 点数計算ライブラリmahjong

2021年3月16日作成

mahjongという点数計算ライブラリを使って九連宝燈の点数計算をさせてみました。

これはかなり便利です。

import random
from mahjong.hand_calculating.hand import HandCalculator
from mahjong.tile import TilesConverter
from mahjong.hand_calculating.hand_config import HandConfig, OptionalRules
from mahjong.meld import Meld
from mahjong.constants import EAST, SOUTH, WEST, NORTH

calculator = HandCalculator()

class Taku:
    def __init__(self):
        # 萬子:m,筒子:p,索子:s,字牌:z
        shurui = ['m','p','s','z']
        num = [9,9,9,7]

        self.yama = []
        for s,n in zip(shurui,num):
            for i in range(1,n+1):
                hai = s + str(i)
                self.yama.append(hai)
        
        self.yama = self.yama * 4
        random.shuffle(self.yama)

#結果出力
def print_hand_result(hand_result):
     #翻数, 符数
     print(hand_result.han, hand_result.fu)
     #点数(親(放銃者),子)
     print(hand_result.cost['main'], result.cost['additional'])
     #役
     print(hand_result.yaku)
     #符数内容
     for fu_item in hand_result.fu_details:
          print(fu_item)

taku = Taku()
print(f'枚数 {len(taku.yama)}')
# print(taku.yama)

# 中略

# 九連宝燈で和了
my_tehai = ['m1','m1','m1','m2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm9']

tiles_man = [hai[1] for hai in my_tehai if 'm' in hai]
print(tiles_man)
tiles_pin = [hai[1] for hai in my_tehai if 'p' in hai]
print(tiles_pin)
tiles_sou = [hai[1] for hai in my_tehai if 's' in hai]
print(tiles_sou)
tiles_honors = [hai[1] for hai in my_tehai if 'z' in hai]
print(tiles_honors)

# 和了牌姿
tiles = TilesConverter.string_to_136_array(man=tiles_man, pin=tiles_pin, sou=tiles_sou, honors=tiles_honors)
#和了牌(萬子の1)
win_tile = TilesConverter.string_to_136_array(man='1')[0]
#鳴きなし
melds = None
#ドラなし
dora_indicators = None
#オプションなし
config = None
#計算
result = calculator.estimate_hand_value(tiles, win_tile, melds, dora_indicators, config)
print_hand_result(result)
--------------------------------------------------

  出力
--------------------------------------------------
枚数 136
['1', '1', '1', '2', '3', '4', '5', '6', '7', '8', '9', '9', '9', '9']
[]
[]
[]
13 40
32000 0
[Chuuren Poutou]
{'fu': 30, 'reason': 'base'}
{'fu': 8, 'reason': 'closed_terminal_pon'}
--------------------------------------------------

[Python] 177 麻雀アプリ 01 山の作成

2021年3月16日作成

前から興味のあった麻雀AI作成に取り組んでみます。頓挫必至かと思いますが、取りあえずやってみようかと。

2019年にMicrosoft社の麻雀AIが天鳳10段を達成しています。CUIベースでルールを間違えずに打ち切れるレベルが最初の目標です。

AIに関しては麻雀がテーマでないとモチベーションが上がらないため、少しやる気になった今こそ見切り発車します。

ネット情報を参考にまずは山の作成から。コード例では牌136枚を作成しシャッフルする字牌は風牌と三元牌に分けています。

import random

class Taku:
    def __init__(self):
        # 萬子:m、筒子:p、索子:s、風牌:w、三元牌:z
        shurui = ['m','p','s','w','z']
        num = [9,9,9,4,3]
        self.yama = []
        for s,n in zip(shurui,num):
            for i in range(1,n+1):
                hai = s + str(i)
                self.yama.append(hai)
        
        self.yama = self.yama * 4
        random.shuffle(self.yama)

taku = Taku()
print(f'枚数 {len(taku.yama)}')
print(taku.yama)
--------------------------------------------------
  出力
--------------------------------------------------
枚数 136
['s3', 'm9', 's2', 'm4', 'p4', 'z1', 'm7', 's5', 'p3', 's3', 's5', 's2', 's4', 's8', 'z3', 's1', 's5', 'p8', 'p1', 's1', 's4', 'm7', 'w2', 'z3', 's6', 'p7', 's9', 'p9', 'p1', 'm1', 'w1', 'p4', 'z2', 'm9', 'z2', 'z2', 'm4', 'p8', 'p9', 'z1', 'p6', 'w4', 's1', 'p5', 'p2', 'p6', 'm2', 'w3', 's7', 'm2', 'm7', 'p9', 's8', 'w3', 'p3', 'z3', 's4', 'm2', 's8', 'm5', 's5', 'p2', 's9', 'w2', 's9', 's7', 'w2', 'm8', 'w1', 'm8', 'm9', 's6', 'p2', 'm2', 'm5', 's9', 'p7', 's3', 'p1', 'p5', 'p6', 'm6', 'm4', 's6', 'm5', 'm3', 'w4', 'm6', 'p3', 'w4', 'p1', 'p4', 'm7', 'z2', 'm1', 'w4', 'p4', 's2', 'm3', 'm6', 's4', 'm6', 'm8', 'p5', 'z1', 'z3', 'p2', 's7', 'w2', 'w3', 'p7', 's8', 's6', 'm1', 'z1', 'm4', 'm3', 'p9', 'p8', 'm3', 's1', 'w3', 'm8', 'p7', 'm5', 'p5', 's3', 'm9', 's2', 's7', 'w1', 'm1', 'p8', 'w1', 'p6', 'p3']
--------------------------------------------------

参考記事