雀魂の簡略化ルールによる麻雀和了判定

雀魂という麻将ゲームに興味を持ったユーザーが、複雑なルールを簡略化して新しい麻将を作りました。この新しいルールでは、1から9までの数値の牌を使用し、各数値の牌は4枚ずつ存在します。手札は14枚で構成され、和了の条件は以下の通りです。
  • 14枚の牌の中に2枚同じ数値の牌(雀頭)が含まれていること。
  • 雀頭を除いた残りの12枚の牌が、4つの順子または刻子で構成されていること。順子とは連続する3つの数値の組み合わせ(例:234, 567)であり、刻子とは同じ数値の3つの組み合わせ(例:111, 777)です。
ユーザーはすでに13枚の牌を持っています。残りの23枚の中から1枚を引いて和了となる場合の数値を求めることになりました。

入力

入力は1行で、13個の数字がスペース区切りで与えられます。各数字は1から9の間で、同じ数字は最大4回しか出現しません。

出力

出力は1行で、和了となる可能性のある数字を昇順でスペース区切りで出力します。複数の数字が該当する場合はそれらを全て出力します。どの数字でも和了にならない場合は0を出力します。

入力例1:

1 1 1 2 2 2 5 5 5 6 6 6 9

出力例1:

9

入力例2:

1 1 1 1 2 2 3 3 5 6 7 8 9

出力例2:

4 7

入力例3:

1 1 1 2 2 2 3 3 3 5 7 7 9

出力例3:

0

解法

問題を解決するために、再帰を使って全通りの組み合わせを試す方法を採用しました。雀頭、刻子、順子のいずれかとして使用可能な数字をリストに入れて、それぞれのケースについて再帰的に調査を行います。
import java.util.*;

public class MahjongSolver {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        List<Integer> hand = new ArrayList<>();
        for (int i = 0; i < 13; i++) {
            hand.add(scanner.nextInt());
        }

        StringBuilder result = new StringBuilder();
        for (int tile = 1; tile <= 9; tile++) {
            List<Integer> potentialHand = new ArrayList<>(hand);
            potentialHand.add(tile);
            Collections.sort(potentialHand);
            if (canWin(potentialHand, false)) {
                result.append(tile).append(" ");
            }
        }

        if (result.length() == 0) {
            System.out.println("0");
        } else {
            System.out.println(result.toString().trim());
        }
    }

    private static boolean canWin(List<Integer> tiles, boolean hasPair) {
        if (tiles.isEmpty()) {
            return true;
        }

        if (!hasPair && isPair(tiles.get(0), tiles.get(1))) {
            List<Integer> remainingTiles = removeTiles(tiles, 0, 1);
            if (canWin(remainingTiles, true)) {
                return true;
            }
        }

        if (tiles.size() >= 3) {
            if (isTriplet(tiles.get(0), tiles.get(1), tiles.get(2))) {
                List<Integer> remainingTiles = removeTiles(tiles, 0, 1, 2);
                if (canWin(remainingTiles, hasPair)) {
                    return true;
                }
            }

            if (isSequence(tiles.get(0), tiles.get(1), tiles.get(2))) {
                List<Integer> remainingTiles = removeTiles(tiles, 0, 1, 2);
                if (canWin(remainingTiles, hasPair)) {
                    return true;
                }
            }
        }

        return false;
    }

    private static boolean isPair(int a, int b) {
        return a == b;
    }

    private static boolean isTriplet(int a, int b, int c) {
        return a == b && b == c;
    }

    private static boolean isSequence(int a, int b, int c) {
        return a + 1 == b && b + 1 == c;
    }

    private static List<Integer> removeTiles(List<Integer> tiles, int... indices) {
        List<Integer> remainingTiles = new ArrayList<>(tiles);
        Arrays.sort(indices);
        for (int i = indices.length - 1; i >= 0; i--) {
            remainingTiles.remove(indices[i]);
        }
        return remainingTiles;
    }
}

タグ: Java Algorithm Recursion mahjong

8月13日 17:18 投稿