麻雀 手牌入力 + 役・符・点数計算ツール

対応している役一覧を表示する

このページでは、麻雀の点数計算を誰でも簡単に行える「手牌入力型の無料ツール」を提供しています。和了形を入力するだけで、役・符・点数を自動で計算し、結果を瞬時に表示します。面前・副露・ドラなどの条件にも対応しており、初心者がルールを覚える練習にも、上級者の実戦確認にも最適です。スマホ・PCどちらでも使いやすい設計で、友人との対局中の確認やオンライン麻雀の復習にも便利に活用できます。

対応している役一覧

本ツールでは、一般役から役満まで幅広く判定できます。

特殊形

門前系

一般役

役満

ドラ

対応している役一覧を表示する

このツールを作った理由

麻雀を始めたばかりの頃、対局のたびに点数計算で婦警さんと一緒に悩んでしまうほどの素人でした。「毎回こんなに迷うなら、いっそ自分で点数計算アプリを作ってしまおう」と思い立ち、勉強がてら開発したのがこのツールです。実際に使いながら改良を重ね、初心者でも直感的に操作できるように設計しました。

また、このツールは Pyscript を利用しており、ブラウザ上で Python を実行することで高速かつ柔軟な判定処理を実現しています。アプリのようにサクサク動くのに、インストール不要で誰でも使えるのが特徴です。

もし不具合や計算ミスなどを見つけた場合は、お気軽にご連絡ください。改善の参考にさせていただきます。

よくある質問(FAQ)

Q. スマホでも使えますか?

はい、スマホ・タブレット・PCすべてに対応しています。タッチ操作でも入力しやすいようにUIを最適化しています。

Q. 裏ドラや赤ドラにも対応していますか?

対応しています。ドラ表示牌・裏ドラ表示牌・赤ドラの有無を入力することで自動計算されます。

Q. 七対子や国士無双などの特殊形も判定できますか?

はい、特殊形にも対応しています。手牌の入力内容から自動で判定します。

Q. 点数計算が間違っている気がします。

ルールの違いや特殊なケースで誤差が出る可能性があります。不具合を見つけた場合はご連絡いただけると助かります。

Q. 競技麻雀や特殊ルールにも対応していますか?

一般的な日本麻雀ルールに基づいています。競技ルールや特殊ルールには対応していない場合があります。

麻雀 手牌入力 + 役・符・点数計算

状況









本場

鳴き面子

例:
ポン 5m5m5m
チー 3p4p5p
カン 7s7s7s7s

牌一覧

萬子:
筒子:
索子:
字牌:

入力モード

現在のモード:手牌

手牌(14枚)


ドラ表示牌

和了牌

裏ドラ表示牌

結果

from js import document from pyodide.ffi import create_proxy from collections import Counter hand = [] input_mode = "hand" dora_tile = None agari_tile = None ura_tile = None # -------------------------- # UI rendering # -------------------------- def render_hand(): div = document.getElementById("hand") div.innerHTML = "" for item in hand: span = document.createElement("span") span.className = "hand-tile" img = document.createElement("img") img.src = item["src"] # ← ここが重要! img.className = "tile-img" span.appendChild(img) div.appendChild(span) def on_tile_click(event): global hand, dora_tile, agari_tile, ura_tile, input_mode target = event.target if target.tagName.lower() == "img": target = target.parentElement tile = target.getAttribute("data-tile") src = target.getAttribute("data-src") if tile is None or src is None: return if input_mode == "hand": if len(hand) < 14: hand.append({"tile": tile, "src": src}) render_hand() elif input_mode == "dora": dora_tile = {"tile": tile, "src": src} render_dora() elif input_mode == "agari": agari_tile = {"tile": tile, "src": src} render_agari() elif input_mode == "ura": # ★★★ これが必要 ★★★ ura_tile = {"tile": tile, "src": src} render_ura() def on_clear(event): global hand hand = [] render_hand() document.getElementById("result-yaku").innerHTML = "" # -------------------------- # Tile parsing # -------------------------- def parse_tile(t): if t[-1] in ["m","p","s"]: suit = t[-1] num_char = t[0] is_red = (num_char == "0") num = 5 if is_red else int(num_char) return ("num", num, suit, is_red) else: return ("honor", t) def hand_to_internal(lst): return [parse_tile(item["tile"]) for item in lst] def normalize_for_meld(t): if t[0] == "num": # ("num", n, s, is_red) または ("num", n, s) if len(t) == 4: _, n, s, _ = t elif len(t) == 3: _, n, s = t else: raise ValueError(f"Unexpected tile format: {t}") return ("num", n, s) return t def tiles_to_counts(tiles): return Counter([normalize_for_meld(t) for t in tiles]) # -------------------------- # Naki parsing # -------------------------- def parse_naki_input(text): """ text: ポン 5m5m5m チー 3p4p5p カン 7s7s7s7s return: [ {"type":"pon","tiles":[...],"is_naki":True}, {"type":"chi","tiles":[...],"is_naki":True}, {"type":"kan","tiles":[...],"is_naki":True} ] """ lines = text.strip().split("\n") result = [] for line in lines: line = line.strip() if not line: continue parts = line.split() if len(parts) < 2: continue kind = parts[0] tiles_str = parts[1] # 2文字ずつ切る tiles = [tiles_str[i:i+2] for i in range(0, len(tiles_str), 2)] tiles = [parse_tile(t) for t in tiles] if kind in ["ポン","pon","Pon"]: result.append({"type":"pon","tiles":tiles,"is_naki":True}) elif kind in ["チー","chi","Chi"]: result.append({"type":"chi","tiles":tiles,"is_naki":True}) elif kind in ["カン","kan","Kan"]: result.append({"type":"kan","tiles":tiles,"is_naki":True}) return result # -------------------------- # Kokushi / Chiitoi # -------------------------- def is_kokushi(tiles): base = [] for t in tiles: if t[0] == "num": _,n,s,_ = t if n in [1,9]: base.append(("num",n,s,False)) else: base.append(t) if len(base) != 14: return False if len(set(base)) != 13: return False c = Counter(base) return any(v == 2 for v in c.values()) def is_chiitoi(tiles): if len(tiles) != 14: return False norm = [] for t in tiles: if t[0] == "num": _,n,s,_ = t norm.append(("num",n,s)) else: norm.append(t) c = Counter(norm) return len(c) == 7 and all(v == 2 for v in c.values()) # -------------------------- # Remove naki tiles from hand # -------------------------- def remove_naki_tiles(tiles, naki_melds): tiles_copy = tiles.copy() for meld in naki_melds: for t in meld["tiles"]: nt = normalize_for_meld(t) for i,orig in enumerate(tiles_copy): if normalize_for_meld(orig) == nt: tiles_copy.pop(i) break return tiles_copy # -------------------------- # Meld structure search (menzen part) # -------------------------- def find_meld_structure(tiles): counts = tiles_to_counts(tiles) for head in list(counts.keys()): if counts[head] >= 2: counts[head] -= 2 if counts[head] == 0: del counts[head] melds = [] if _search_melds(counts.copy(), melds): return {"head": head, "melds": melds} counts[head] = counts.get(head,0) + 2 return None def _search_melds(counts, melds): if not counts: return True tile = min(counts.keys()) cnt = counts[tile] # koutsu if cnt >= 3: counts[tile] -= 3 if counts[tile] == 0: del counts[tile] melds.append(("kotsu",[tile,tile,tile], False)) if _search_melds(counts, melds): return True melds.pop() counts[tile] = cnt # shuntsu if tile[0] == "num": _,n,s = tile t2 = ("num",n+1,s) t3 = ("num",n+2,s) if n <= 7 and counts.get(t2,0)>0 and counts.get(t3,0)>0: counts[tile] -= 1 counts[t2] -= 1 counts[t3] -= 1 if counts[tile] == 0: del counts[tile] if counts.get(t2,0)==0: counts.pop(t2,None) if counts.get(t3,0)==0: counts.pop(t3,None) melds.append(("shuntsu",[tile,t2,t3], False)) if _search_melds(counts, melds): return True melds.pop() counts[tile] = counts.get(tile,0) + 1 counts[t2] = counts.get(t2,0) + 1 counts[t3] = counts.get(t3,0) + 1 return False # -------------------------- # Meld structure with naki # -------------------------- def find_meld_structure_with_naki(tiles, naki_melds): # remove naki tiles remaining = remove_naki_tiles(tiles, naki_melds) # menzen melds base = find_meld_structure(remaining) if base is None: return None melds = base["melds"].copy() # add naki melds for nm in naki_melds: if nm["type"] == "pon": melds.append(("kotsu", nm["tiles"], True)) elif nm["type"] == "chi": melds.append(("shuntsu", nm["tiles"], True)) elif nm["type"] == "kan": melds.append(("kantsu", nm["tiles"], True)) return { "head": base["head"], "melds": melds } # -------------------------- # Machi detection # -------------------------- def detect_machi(structure, agari_tile): if agari_tile is None: return "other" head = structure["head"] melds = structure["melds"] norm_agari = normalize_for_meld(agari_tile) # tanki if norm_agari == head: return "tanki" for kind, tiles_m, is_naki in melds: norm_m = [normalize_for_meld(t) for t in tiles_m] if kind == "koutsu": if norm_agari in norm_m: return "other" elif kind == "shuntsu": if norm_agari not in norm_m: continue nums = sorted(t[1] for t in norm_m) n = norm_agari[1] # penchan if nums == [1,2,3] and n == 1: return "penchan" if nums == [7,8,9] and n == 9: return "penchan" # kanchan if n == nums[1]: return "kanchan" # ryanmen return "ryanmen" return "other" # -------------------------- # Yaku helpers # -------------------------- def is_tanyao(tiles): for t in tiles: if t[0] == "honor": return False _,n,s,_ = t if n == 1 or n == 9: return False return True def all_melds_are_shuntsu(counts): if not counts: return True tile = min(counts.keys()) if tile[0] != "num": return False _,n,s = tile t2 = ("num",n+1,s) t3 = ("num",n+2,s) if n > 7 or counts.get(t2,0)==0 or counts.get(t3,0)==0: return False counts[tile] -= 1 counts[t2] -= 1 counts[t3] -= 1 if counts[tile] == 0: del counts[tile] if counts.get(t2,0)==0: counts.pop(t2,None) if counts.get(t3,0)==0: counts.pop(t3,None) return all_melds_are_shuntsu(counts) def is_pinfu(tiles,jifu,bakaze,menzen): if not menzen: return False counts = tiles_to_counts(tiles) for tile in list(counts.keys()): if counts[tile] >= 2: if tile[0] == "honor": if tile[1] in ["P","F","C",jifu,bakaze]: continue counts[tile] -= 2 if counts[tile] == 0: del counts[tile] if all_melds_are_shuntsu(counts.copy()): return True counts[tile] = counts.get(tile,0) + 2 return False def is_toitoi(tiles): counts = tiles_to_counts(tiles) for tile in list(counts.keys()): if tile[0] == "num": _,n,s = tile t2 = ("num",n+1,s) t3 = ("num",n+2,s) if n <= 7 and counts.get(tile,0)>0 and counts.get(t2,0)>0 and counts.get(t3,0)>0: return False return True def is_sanankou(tiles): counts = tiles_to_counts(tiles) k = 0 for tile,c in counts.items(): if c >= 3: k += 1 return k >= 3 def is_ikkitsuukan(tiles): counts = tiles_to_counts(tiles) for suit in ["m","p","s"]: need = [("num",i,suit) for i in range(1,10)] if all(counts.get(t,0)>=1 for t in need): return True return False def is_sanshoku_doujun(tiles): counts = tiles_to_counts(tiles) for n in range(1,8): has_m = all(counts.get(("num",n+i,"m"),0)>=1 for i in [0,1,2]) has_p = all(counts.get(("num",n+i,"p"),0)>=1 for i in [0,1,2]) has_s = all(counts.get(("num",n+i,"s"),0)>=1 for i in [0,1,2]) if has_m and has_p and has_s: return True return False def is_chanta(structure): if not is_chanta_head(structure["head"]): return False for m in structure["melds"]: if not is_chanta_mentsu(m): return False return True def is_chinitsu(tiles): suits = set() has_h = False for t in tiles: if t[0] == "honor": has_h = True else: _,n,s,_ = t suits.add(s) return len(suits)==1 and not has_h def is_chanta_mentsu(meld): kind, tiles_m, is_naki = meld # 刻子・槓子 if kind in ["kotsu", "kantsu"]: return is_yaochu_tile(tiles_m[0]) # 順子 → 123 or 789 のみ if kind == "shuntsu": nums = sorted([t[1] for t in tiles_m]) return nums[0] == 1 or nums[0] == 7 return False def is_chanta_head(head): return is_yaochu_tile(head) def is_junchan(structure, tiles): # 字牌があれば純チャンは不成立 if has_honor(tiles): return False # チャンタの条件を満たしているか if not is_chanta(structure): return False return True def is_yaochu_tile(tile): # tile = (suit, number, ..., ...) suit = tile[0] num = tile[1] return (suit == "honor" or num in [1, 9]) def is_honitsu(tiles): suits = set() has_h = False for t in tiles: if t[0] == "honor": has_h = True else: _,n,s,_ = t suits.add(s) return len(suits)==1 and has_h def yakuhai(tiles,jifu,bakaze): counts = tiles_to_counts(tiles) res = [] for code,name in [("P","白"),("F","發"),("C","中")]: if counts.get(("honor",code),0)>=3: res.append((f"役牌:{name}",1)) for code,name in [("E","東"),("S","南"),("W","西"),("N","北")]: if counts.get(("honor",code),0)>=3: fan = 0 label = "役牌:" if code == jifu: fan += 1 label += "自風" if code == bakaze: fan += 1 label += "場風" if fan > 0: res.append((label,fan)) return res def count_red_dora(tiles): return sum(1 for t in tiles if t[0]=="num" and t[3]) # -------------------------- # 字牌があるか判定 # -------------------------- def has_honor(tiles): return any(t[0] == "honor" for t in tiles) # -------------------------- # Dora indicator # -------------------------- def next_number_tile(num): return 1 if num == 9 else num + 1 def next_wind(code): order = ["E","S","W","N"] i = order.index(code) return order[(i+1) % 4] def next_dragon(code): order = ["P","F","C"] i = order.index(code) return order[(i+1) % 3] def parse_indicator(s): s = s.strip() if not s: return None if s[-1] in ["m","p","s"]: suit = s[-1] num_char = s[0] is_red = (num_char == "0") num = 5 if is_red else int(num_char) return ("num", num, suit, False) else: return ("honor", s) def calc_dora_from_indicator(indicator, tiles): if indicator is None: return 0 if indicator[0] == "num": _,n,s,_ = indicator dora_num = next_number_tile(n) return sum(1 for t in tiles if t[0]=="num" and t[2]==s and t[1]==dora_num) else: _,code = indicator if code in ["E","S","W","N"]: d = ("honor", next_wind(code)) else: d = ("honor", next_dragon(code)) return sum(1 for t in tiles if t == d) # -------------------------- # Fu calculation # -------------------------- def fu_head(head,jifu,bakaze): if head[0] != "honor": return 0 code = head[1] if code in ["P","F","C"]: return 2 if code == jifu: return 2 if code == bakaze: return 2 return 0 def fu_koutsu(meld): kind, tiles_m, is_naki = meld tile = tiles_m[0] is_yaochu = (tile[0]=="honor" or tile[1] in [1,9]) if is_naki: return 4 if is_yaochu else 2 else: return 8 if is_yaochu else 4 def fu_kantsu(meld): kind, tiles_m, is_naki = meld tile = tiles_m[0] is_yaochu = (tile[0]=="honor" or tile[1] in [1,9]) if is_naki: return 16 if is_yaochu else 8 else: return 32 if is_yaochu else 16 def fu_shuntsu(meld): return 0 def calc_fu(tiles, jifu, bakaze, menzen, agari_type, agari_tile, structure): if is_kokushi(tiles): return 0 if is_chiitoi(tiles): return 25 if structure is None: return 0 head = structure["head"] melds = structure["melds"] if is_pinfu(tiles,jifu,bakaze,menzen) and agari_type=="tsumo": return 20 fu = 20 if agari_type == "ron" and menzen: fu += 10 if agari_type == "tsumo": fu += 2 fu += fu_head(head,jifu,bakaze) for kind,tiles_m,is_naki in melds: if kind == "koutsu": fu += fu_koutsu((kind,tiles_m,is_naki)) elif kind == "kantsu": fu += fu_kantsu((kind,tiles_m,is_naki)) else: fu += fu_shuntsu((kind,tiles_m,is_naki)) machi = detect_machi(structure, agari_tile) if machi in ["tanki","kanchan","penchan"]: fu += 2 if fu % 10 != 0: fu = fu + (10 - fu % 10) return fu # -------------------------- # Score calculation # -------------------------- def calc_base_points(fu, han): if han >= 13: return 8000 if han >= 11: return 6000 if han >= 8: return 4000 if han >= 6: return 3000 if han >= 5: return 2000 base = fu * (2 ** (han + 2)) return min(base, 2000) def round_up_100(x): return ((x + 99) // 100) * 100 def calc_score(fu, han, oya_flag, agari_type, honba): base = calc_base_points(fu, han) if oya_flag: if agari_type == "tsumo": pay = round_up_100(base * 2) total = pay * 3 + honba * 300 return f"親ツモ:{pay}オール(本場 {honba})" else: pts = round_up_100(base * 6) return f"親ロン:{pts}点(本場 {honba})" else: if agari_type == "tsumo": child = round_up_100(base) parent = round_up_100(base * 2) return f"子ツモ:親 {parent}点・子 {child}点(本場 {honba})" else: pts = round_up_100(base * 4) return f"子ロン:{pts}点(本場 {honba})" # -------------------------- # 一盃口・二盃口判定関数 # -------------------------- def count_ipeiko(structure, menzen): if not menzen: return 0 # 鳴きがあると一盃口・二盃口は不成立 melds = structure["melds"] # 順子だけ取り出して正規化 shuntsu_list = [] for kind, tiles_m, is_naki in melds: if kind == "shuntsu" and not is_naki: # 鳴き順子は対象外 norm = tuple(sorted([normalize_for_meld(t) for t in tiles_m])) shuntsu_list.append(norm) # 同じ順子の出現回数を数える c = Counter(shuntsu_list) # 2回出ている順子の数を数える pairs = sum(1 for v in c.values() if v == 2) if pairs == 2: return 2 # 二盃口 if pairs == 1: return 1 # 一盃口 return 0 # -------------------------- # 小三元判定関数 # -------------------------- def is_shousangen(structure): head = structure["head"] melds = structure["melds"] sangen = ["P", "F", "C"] # 白 發 中 # 雀頭が三元牌か? head_code = head[1] if head[0] == "honor" else None # 三元牌の刻子・槓子を数える koutsu_count = 0 for kind, tiles_m, is_naki in melds: if kind in ["kotsu", "kantsu"]: # ← "koutsu" を "kotsu" に統一済み前提 t = tiles_m[0] if t[0] == "honor" and t[1] in sangen: koutsu_count += 1 # 条件: # ・刻子/槓子が2つ # ・雀頭が残りの1つ if koutsu_count == 2 and head_code in sangen: return True return False # -------------------------- # 混老頭(ホンロウトウ) # -------------------------- def is_honroutou(tiles): for t in tiles: if t[0] == "num": _, n, s, _ = t if n not in [1, 9]: return False else: # honor は OK pass return True # -------------------------- # 三槓子(サンカンツ) # -------------------------- def is_sankantsu(structure): melds = structure["melds"] kantsu_count = sum(1 for kind, tiles_m, is_naki in melds if kind == "kantsu") return kantsu_count >= 3 # -------------------------- # 三色同刻(サンショクドウコウ) # -------------------------- def is_sanshoku_doukou(structure): melds = structure["melds"] # 刻子・槓子だけ取り出す koutsu_list = [] for kind, tiles_m, is_naki in melds: if kind in ["kotsu", "kantsu"]: t = tiles_m[0] if t[0] == "num": _, n, s = t[0], t[1], t[2] koutsu_list.append((n, s)) # 数字ごとにスーツを集める by_number = {} for n, s in koutsu_list: by_number.setdefault(n, set()).add(s) # 同じ数字で m/p/s が揃っていれば成立 for n, suits in by_number.items(): if suits == {"m", "p", "s"}: return True return False # -------------------------- # 大三元(だいさんげん) # -------------------------- def is_daisangen(structure): melds = structure["melds"] sangen = {"P", "F", "C"} found = set() for kind, tiles_m, is_naki in melds: if kind in ["kotsu", "kantsu"]: t = tiles_m[0] if t[0] == "honor" and t[1] in sangen: found.add(t[1]) return len(found) == 3 # -------------------------- # 清老頭(ちんろうとう) # -------------------------- def is_chinroutou(tiles): for t in tiles: if t[0] == "honor": return False _, n, s, _ = t if n not in [1, 9]: return False return True # -------------------------- # Yaku judgement # -------------------------- def judge_yaku(tiles, jifu, bakaze, menzen, riichi, agari_type, structure): yaku = [] # 国士無双 if is_kokushi(tiles): yaku.append(("国士無双",13)) return yaku # 七対子 if is_chiitoi(tiles): yaku.append(("七対子",2)) return yaku # 門前ツモ if menzen and agari_type=="tsumo": yaku.append(("門前ツモ",1)) # 立直 if riichi and menzen: yaku.append(("立直",1)) # 断么九 if is_tanyao(tiles): yaku.append(("断么九",1)) # 平和 if is_pinfu(tiles,jifu,bakaze,menzen): yaku.append(("平和",1)) # 一盃口・二盃口 ipei = count_ipeiko(structure, menzen) if ipei == 1: yaku.append(("一盃口", 1)) elif ipei == 2: yaku.append(("二盃口", 3)) # 小三元 if is_shousangen(structure): yaku.append(("小三元", 2)) # 混老頭 if is_honroutou(tiles): yaku.append(("混老頭", 2)) # 三槓子 if is_sankantsu(structure): yaku.append(("三槓子", 2)) # 三色同刻 if is_sanshoku_doukou(structure): yaku.append(("三色同刻", 2)) # 大三元 if is_daisangen(structure): yaku.append(("大三元", 13)) # 役満 # 清老頭 if is_chinroutou(tiles): yaku.append(("清老頭", 13)) # 役満 # 役牌 yaku.extend(yakuhai(tiles,jifu,bakaze)) # 対々和 if is_toitoi(tiles): yaku.append(("対々和",2)) # 三暗刻 if is_sanankou(tiles): yaku.append(("三暗刻",2)) # 一気通貫 if is_ikkitsuukan(tiles): yaku.append(("一気通貫",2 if menzen else 1)) # 三色同順 if is_sanshoku_doujun(tiles): yaku.append(("三色同順",2 if menzen else 1)) # 清一色 / 混一色 if is_chinitsu(tiles): yaku.append(("清一色",6 if menzen else 5)) elif is_honitsu(tiles): yaku.append(("混一色",3 if menzen else 2)) # チャンタ if is_chanta(structure): yaku.append(("チャンタ", 2 if menzen else 1)) # 純チャン if is_junchan(structure, tiles): yaku.append(("純全帯么九", 3 if menzen else 2)) # 赤ドラ red = count_red_dora(tiles) if red > 0: yaku.append((f"赤ドラ {red}", red)) if dora_tile: indicator = parse_tile(dora_tile["tile"]) dora_count = calc_dora_from_indicator(indicator, tiles) if dora_count > 0: yaku.append((f"ドラ {dora_count}", dora_count)) if riichi and ura_tile: ura_indicator = parse_tile(ura_tile["tile"]) ura_count = calc_dora_from_indicator(ura_indicator, tiles) if ura_count > 0: yaku.append((f"裏ドラ {ura_count}", ura_count)) return yaku # -------------------------- # Analyze button # -------------------------- def on_analyze(event): global dora_tile, agari_tile # -------------------------- # 手牌チェック # -------------------------- if len(hand) != 14: document.getElementById("result-yaku").innerHTML = "手牌は14枚必要です。" return # -------------------------- # 手牌(内部形式へ変換) # -------------------------- tiles = hand_to_internal(hand) # -------------------------- # ドラ表示牌(画像クリックで選択) # -------------------------- indicator = parse_tile(dora_tile["tile"]) if dora_tile else None # -------------------------- # 和了牌(画像クリックで選択) # -------------------------- agari_parsed = parse_tile(agari_tile["tile"]) if agari_tile else None # -------------------------- # UI から状況取得 # -------------------------- jifu = document.getElementById("jifu").value bakaze = document.getElementById("bakaze").value menzen = document.getElementById("menzen").checked riichi = document.getElementById("riichi").checked agari_type = document.getElementById("agari-type").value oya_flag = (document.getElementById("oya").value == "oya") honba = int(document.getElementById("honba").value or "0") # -------------------------- # 鳴き面子 # -------------------------- naki_text = document.getElementById("naki-input").value naki_melds = parse_naki_input(naki_text) # -------------------------- # 面子構造(鳴き込み) # -------------------------- structure = find_meld_structure_with_naki(tiles, naki_melds) if structure is None: document.getElementById("result-yaku").innerHTML = "和了形ではありません。" return # -------------------------- # 役判定 # -------------------------- yaku = judge_yaku(tiles, jifu, bakaze, menzen, riichi, agari_type, structure) if not yaku: document.getElementById("result-yaku").innerHTML = "役なしです。" return total_han = sum(f for _, f in yaku) # -------------------------- # 符計算(和了牌を使用) # -------------------------- fu = calc_fu(tiles, jifu, bakaze, menzen, agari_type, agari_parsed, structure) # -------------------------- # 点数計算 # -------------------------- score_text = calc_score(fu, total_han, oya_flag, agari_type, honba) # -------------------------- # HTML 出力 # -------------------------- html = f'
{fu}符 {total_han}翻
' html += "
" for name, fan in yaku: html += f'
{name}: {fan}翻
' html += "
" html += f'
{score_text}
' document.getElementById("result-yaku").innerHTML = html # -------------------------- # ドラ・アガリ・裏ドラ出力切り替え # -------------------------- def render_dora(): div = document.getElementById("dora-display") div.innerHTML = "" if dora_tile: img = document.createElement("img") img.src = dora_tile["src"] img.className = "tile-img" div.appendChild(img) def render_agari(): div = document.getElementById("agari-display") div.innerHTML = "" if agari_tile: img = document.createElement("img") img.src = agari_tile["src"] img.className = "tile-img" div.appendChild(img) def render_ura(): div = document.getElementById("ura-display") div.innerHTML = "" if ura_tile: img = document.createElement("img") img.src = ura_tile["src"] img.className = "tile-img" div.appendChild(img) # -------------------------- # 入力切替処理 # -------------------------- def set_mode_hand(event): global input_mode input_mode = "hand" document.getElementById("current-mode").innerHTML = "手牌" def set_mode_dora(event): global input_mode input_mode = "dora" document.getElementById("current-mode").innerHTML = "ドラ表示牌" def set_mode_agari(event): global input_mode input_mode = "agari" document.getElementById("current-mode").innerHTML = "和了牌" def set_mode_ura(event): global input_mode input_mode = "ura" document.getElementById("current-mode").innerHTML = "裏ドラ表示牌" # -------------------------- # Event registration # -------------------------- tile_click_proxy = create_proxy(on_tile_click) clear_proxy = create_proxy(on_clear) analyze_proxy = create_proxy(on_analyze) # 牌一覧クリック buttons = document.getElementsByClassName("tile-btn") for b in buttons: b.addEventListener("click", tile_click_proxy) # 入力モード切り替え document.getElementById("mode-hand").addEventListener("click", create_proxy(set_mode_hand)) document.getElementById("mode-dora").addEventListener("click", create_proxy(set_mode_dora)) document.getElementById("mode-agari").addEventListener("click", create_proxy(set_mode_agari)) document.getElementById("mode-ura").addEventListener("click", create_proxy(set_mode_ura)) # クリア・判定 document.getElementById("clear-hand").addEventListener("click", clear_proxy) document.getElementById("analyze").addEventListener("click", analyze_proxy) document.getElementById("clear-hand").addEventListener("click", clear_proxy) document.getElementById("analyze").addEventListener("click", analyze_proxy)