[PYTHON] Algorithme de jugement d'achèvement de Mahjong

Algorithme de jugement d'achèvement de Mahjong

Ce n'est pas un algorithme pour trouver le nombre d'écoutes, qui vérifie si l'achèvement de 14 travaux manuels est terminé.

formulaire de données

ONE-HOT est utilisé pour les données des tuiles J'ai choisi ONE-HOT car il est plus facile de juger la tête et la gravure si la somme des séquences ONE-HOT est prise dans le sens des rangées.

# ONE-Artisanat d'expression à chaud
[
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
]

#Prenez la somme dans le sens de la ligne
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 1, 1, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0]
#3 pièces ou plus seront utilisées pour juger de la gravure
# [1, 1, 1]Pliez-le et utilisez 3 parties ou plus pour juger Junko.
#Il y a des avantages tels que (je pense)

Méthodes d'inspection

  1. Inspectez tous les modèles de «tête» (boucle la plus externe)
  2. Inspectez tous les motifs de "gravure" avec le reste après avoir retiré la "tête" (boucle intérieure)
  3. Inspectez «Junko» avec le reste après avoir retiré la «tête» et la «gravure» inspectées

Vérifiez si l'achèvement est terminé par la procédure ci-dessus.

Code source


import itertools
import multiprocessing
import numpy as np
import os
import sys
import time

# m1-m9, p1-p9, s1-s9, dw, dg, dr, we, ww, ws, wn
#Trois éléments = Dragon
#Vent = Vent
tileKeyIndex = [
    "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9", 
    "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", 
    "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", 
    "dw", "dg", "dr",
    "we", "ww", "ws", "wn", 
]

MTileBits = [
    1, 1, 1, 1, 1, 1, 1, 1, 1, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0,
    0, 0, 0, 0
]

PTileBits = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0,
    0, 0, 0, 0
]

STileBits = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 
    0, 0, 0,
    0, 0, 0, 0
]

DTileBits = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    1, 1, 1,
    0, 0, 0, 0
]

WTileBits = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0,
    1, 1, 1, 1
]

KokusiBits = [
    1, 0, 0, 0, 0, 0, 0, 0, 1, 
    1, 0, 0, 0, 0, 0, 0, 0, 1, 
    1, 0, 0, 0, 0, 0, 0, 0, 1, 
    1, 1, 1,
    1, 1, 1, 1
]

KokusiBits = np.array(KokusiBits)

# m1m2m3m4m5m6m7m8m9s1s2s3wnwn
def parseTehai(s):
    if len(s) != 28:
        print("error in {}, len(s)={}".format(sys._getframe().f_code.co_name, len(s)))
        sys.exit()
    tileMatrix = np.zeros((14, len(tileKeyIndex)))
    for i in range(14):
        pos = i * 2
        idx = tileKeyIndex.index(s[pos:pos + 2])
        tileMatrix[i][idx] = 1
    return tileMatrix

def isShuntsuCompleted(tileMatrix):
    indexes = []
    for tbits in [MTileBits, PTileBits, STileBits]:
        target = tileMatrix * tbits
        while True:
            targetB = (target != 0).astype(int) # [1, 1, 2]Convertissez tout en 1 pour éviter un tel tableau
            b = np.convolve(targetB, [1, 1, 1], mode="valid")
            if np.max(b) != 3:
                break
            idxs = np.where(b == 3)[0]
            idx = idxs[0]
            target[idx:idx + 3] -= 1 #Retirer la tuile inspectée
            indexes = indexes + list(np.arange(idx, idx + 3, 1))
    return indexes

def isCompleted(tileMatrix):
    rowSum = np.sum(tileMatrix, axis=0)
    headerIdxs = np.where(rowSum >= 2)[0]
    atama, kotsu, shuntsu = [], [], []
    
    #Cheet Jouets
    if len(headerIdxs) == 7:
        return 1, list(headerIdxs) * 2, [], []
    
    #Kokushi
    kokusiCheck = (rowSum != 0).astype(int)
    if np.sum(kokusiCheck * KokusiBits) == 13 and np.sum(rowSum * KokusiBits) == 14:
        return 1, np.where(np.array(KokusiBits) == 1)[0], [], []
    
    #Fixer la tête
    #Tous les motifs de gravure sont imprimés à l'avance et Junko est inspecté en fixant chaque motif.
    for hidx in headerIdxs:
        #Faites une copie pour ne pas manipuler le tableau d'origine
        calcBuffer = np.array(rowSum)
        
        #Débarrassez-vous de la tête
        calcBuffer[hidx] -= 2
        
        #Détecter toutes les gravures possibles
        kotsuPos = np.where(calcBuffer >= 3)[0]
        
        #Une seule des gravures détectées est valide, seuls deux des graveurs détectés sont valides ... Créez tous les motifs de toutes les gravures détectées
        kotsuPatterns = []
        for i in range(len(kotsuPos)):
            comb = list(itertools.combinations(kotsuPos, i + 1))
            kotsuPatterns = kotsuPatterns + comb
        #Ajouter un motif pour lequel aucune gravure n'est valide
        kotsuPatterns.append(None)
        
        for kotsuIndexes in kotsuPatterns:
            #Faites une copie pour ne pas manipuler le tableau d'origine
            calcBuffer2 = np.array(calcBuffer)
            if isinstance(kotsuIndexes, type(None)):
                pass
            else:
                #Retirer la gravure
                for kidx in kotsuIndexes:
                    calcBuffer2[kidx] -= 3
            #Junko
            shuntsuIndexes = isShuntsuCompleted(calcBuffer2)
            for idx in shuntsuIndexes:
                #Débarrassez-vous de Junko
                calcBuffer2[idx] -= 1
            
            #S'il ne reste plus de tuiles après avoir retiré la tête, la gravure et le Junko, c'est terminé.
            #print("np.sum(calcBuffer)", np.sum(calcBuffer2))
            if np.sum(calcBuffer2) == 0:
                atama.append(np.full(2, hidx))
                kotsu.append(kotsuIndexes)
                shuntsu.append(shuntsuIndexes)
    
    return len(atama), atama, kotsu, shuntsu

def Test1():
    #2333345677778
    #2333344567888
    #2345666777888
    #3344455566777
    #2223344455677
    #1112345556677
    #4556677888999
    
    #En attente de 1425869
    #En attente de 14725869
    #En attente de 1245678
    #En attente de 36258
    #En attente de 6257
    #En attente de 672583
    #En attente de 789436
    
    #tileMatrix = parseTehai("m1m2m3m4m5m6m7m8m9s1s2s3wnwn")
    #tileMatrix = parseTehai("wewewewwwwwwwswswsm9m9m9s1s1")
    #tileMatrix = parseTehai("s2s3s3s3s3s4s5s6s7s7s7s7s8s9") # s1, s2, s4, s5, s6, s8, s9
    #tileMatrix = parseTehai("m2m3m3m3m3m4m4m5m6m7m8m8m8m1") # 
    #tileMatrix = parseTehai("m2m3m4m5m6m6m6m7m7m7m8m8m8?")
    #tileMatrix = parseTehai("m3m3m4m4m4m5m5m5m6m6m7m7m7?")
    #tileMatrix = parseTehai("p2p2p2p3p3p4p4p4p5p5p6p7p7?")
    #tileMatrix = parseTehai("p1p1p1p2p3p4p5p5p5p6p6p7p7?")
    #tileMatrix = parseTehai("p4p5p5p6p6p7p7p8p8p8p9p9p9?")
    tileMatrix = parseTehai("m1m9p1p9s1s9wewswwwndwdgdrm1")
    completeCount, atama, kotsu, shuntsu = isCompleted(tileMatrix)
    if completeCount > 0:
        print("OK")
        print(atama)
        print(kotsu)
        print(shuntsu)
    else:
        print("NG")

def tileMatrixToTehaiString(tileMatrix):
    s = ""
    for r in tileMatrix:
        idx = np.where(r == 1)[0][0]
        s += tileKeyIndex[idx]
    return s

def appendFile(fileName, data):
    with open(fileName, mode="a") as f:
        f.write(data + "\n")

def TenhohTestSub(args):
    seed = time.time()
    seed = int((seed - int(seed)) * 10000000)
    np.random.seed(seed)
    instanceId, tryCount = args
    size = len(tileKeyIndex)
    allTile = []
    for i in range(size):
        tmp = [0] * size
        tmp[i] = 1
        for n in range(4):
            allTile.append(tmp)
    for i in range(tryCount):
        np.random.shuffle(allTile)
        tiles = np.array(allTile[:14])
        completeCount, atama, kotsu, shuntsu = isCompleted(tiles)
        if completeCount > 0:
            tehaiStr = tileMatrixToTehaiString(tiles)
            appendFile("tenhoh_{}.txt".format(instanceId), tehaiStr)

def TenhohTest():
    #TenhohTestSub(1, 400000)
    tryCount = 1000000
    
    args = []
    for i in range(4):
        args.append([i, tryCount])
    
    with multiprocessing.Pool(4) as p:
        p.map(TenhohTestSub, args)

def main():
    #Test1()
    TenhohTest()

if __name__ == "__main__":
    main()
# python main.py

Comment utiliser le code source

def main():
    #Test1()
    TenhohTest()

Test1 () inspecte le score préparé manuellement dans le code source. Dans TenhohTest (), 4 cœurs sont utilisés pour créer aléatoirement un score 1 million de fois par cœur, et s'il s'agit d'une forme gagnante, un enregistrement est pris. L'enregistrement est enregistré avec un numéro pour chaque cœur tel que "tenhoh_0.txt". ..

Est-ce Tenwa en utilisant le programme de conversion d'image ajouté ci-dessous? Vous pouvez visualiser le score que vous avez réalisé.

Programme d'imagerie pour texte syllabaire

Nous publierons un programme de texte image, son utilisation sera décrite plus loin.


import PIL.Image
import os
import sys

tileKeyIndex = [
    "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9", 
    "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", 
    "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", 
    "dw", "dg", "dr",
    "we", "ww", "ws", "wn", 
]

haiImageNames = [
    "p_ms1_1.gif", "p_ms2_1.gif", "p_ms3_1.gif", "p_ms4_1.gif", "p_ms5_1.gif", "p_ms6_1.gif", "p_ms7_1.gif", "p_ms8_1.gif", "p_ms9_1.gif", 
    "p_ps1_1.gif", "p_ps2_1.gif", "p_ps3_1.gif", "p_ps4_1.gif", "p_ps5_1.gif", "p_ps6_1.gif", "p_ps7_1.gif", "p_ps8_1.gif", "p_ps9_1.gif", 
    "p_ss1_1.gif", "p_ss2_1.gif", "p_ss3_1.gif", "p_ss4_1.gif", "p_ss5_1.gif", "p_ss6_1.gif", "p_ss7_1.gif", "p_ss8_1.gif", "p_ss9_1.gif", 
    "p_no_1.gif", "p_ji_h_1.gif", "p_ji_c_1.gif",
    "p_ji_e_1.gif", "p_ji_w_1.gif", "p_ji_s_1.gif", "p_ji_n_1.gif", 
]

def parseTehai(s):
    if len(s) != 28:
        print("error in {}, len(s)={}".format(sys._getframe().f_code.co_name, len(s)))
        sys.exit()
    indexes, tehai = [], []
    for i in range(14):
        pos = i * 2
        idx = tileKeyIndex.index(s[pos:pos + 2])
        indexes.append(idx)
        tehai.append(s[pos:pos + 2])
    return indexes, tehai

def enumFile():
    files = []
    for v in os.listdir("./"):
        if os.path.isfile(v) and v.startswith("tenhoh_"):
            files.append(v)
    return files

def readFile(fileName):
    with open(fileName, "r") as f:
        return f.read()

def tileIndexesToImage(indexes):
    images = []
    for idx in indexes:
        imageFile = os.path.join("./images", haiImageNames[idx])
        im = PIL.Image.open(imageFile)
        images.append(im)
    imageWidth = 0
    maxHeight = 0
    for im in images:
        imageWidth += im.width
        if im.height > maxHeight:
            maxHeight = im.height
    dst = PIL.Image.new('RGB', (imageWidth, maxHeight))
    for i, im in enumerate(images):
        dst.paste(im, (im.width * i, 0))
    return dst

def main():
    files = enumFile()
    for f in files:
        lines = readFile(f).split("\n")
        basename = os.path.basename(f)
        basename, _ = os.path.splitext(basename)
        for j, l in enumerate(lines):
            if len(l) < 28:
                continue
            indexes, tehai = parseTehai(l)
            indexes = sorted(indexes)
            image = tileIndexesToImage(indexes)
            destFile = "{}_{:03d}.png ".format(basename, j)
            destFile = os.path.join("./dest", destFile)
            image.save(destFile)

if __name__ == "__main__":
    main()
# https://mj-king.net/sozai/
# python tehai_2_image.py

Comment utiliser le programme d'imagerie

Le fichier "tenhoh _ ???. Txt" dans le même dossier est lu automatiquement et l'image est sortie vers ./dest en fonction de l'image en ./images.

** m7s5p2p6s7p4m6p7s6p5m5p2p5p6 ** ↓ tenhoh_2_000.png Triez et convertissez l'image de cette manière.

Téléchargez et agrandissez l'image du royaume de Mahjong

Décompressez les données d'image téléchargées depuis «Manko 2», «Tsutsuko 2», «Ryoko 2» et «Character 2» dans ./images

画像フォルダ構成.PNG

La structure des dossiers ressemble à ceci, D: \ tmp est le dossier du programme.

Créer un dossier dest

フォルダ構成.PNG

Créer un dossier pour la sortie à l'avance

Courir

python tehai_2_image.py

Si elle est exécutée normalement, la partition imagée sera sortie vers ./dest.

c'est tout.

Recommended Posts

Algorithme de jugement d'achèvement de Mahjong