RensaYomi6ABC.java

 /**
 * $Id: A5BDA1BCA5B92852656E7361596F6D693629.txt,v 1.1.1.1 2009/06/01 12:21:04 kamoi Exp $
 * Copyright 2004 kamoland.com All rights reserved.
 */
 package com.kamoland.rensacommon.ai.decider;
 
 import com.kamoland.rensacommon.Const;
 import com.kamoland.rensacommon.Util;
 import com.kamoland.rensacommon.ai.AiBasedCom;
 import com.kamoland.rensacommon.ai.AiUtil;
 import com.kamoland.rensacommon.ai.Tumi;
 import com.kamoland.rensacommon.ai.TumiDeciderPruning;
 import com.kamoland.rensacommon.rule.PuField;
 
 /**
 * 連鎖形成読みの実装 part6
 *
 * @author kamoland.com
 * @version
 * <pre>
 * 2004.06.23 新規作成
 * </pre>
 */
 public class RensaYomi6ABC implements AiBasedCom, TumiDeciderPruning {
 
    // TumiDeciderインタフェイス用.未使用
    public long evaluateTumi(PuField beforeField, PuField afterField, Tumi fallenTumi,  byte[][] nextTumos, PuField[] enemyFields, boolean isAnyEnemyDoingRensa) {
        return 0;
    }
 
    /**
     * 動作モード
     */
    private int mode;
    /**
     * 発火候補点と見なす連結数
     */
    private int currentMinCon;
    /**
     * 現在の積み状況
     */ 
    private int currentStatus;  
    /**
     * 積み状況:危険(1手以内に発火するべき)
     */
    private static final int STATUS_RED1 = 1;
    /**
     * 積み状況:危険(2手以内に発火するべき)
     */
    private static final int STATUS_RED2 = 2;
    /**
     * 積み状況:危険(3手以内に発火するべき)
     */
    private static final int STATUS_RED3 = 3;
    /**
     * 積み状況:安全
     */
    private static final int STATUS_NORMAL = 0;
    /**
     * 積み状況の文字列表現(デバッグ用)
     */
    private final String[] statusDesc = new String[]{"n", "Red1", "Red2", "Red3"};
    /**
     * 連鎖数読みの一時保存用
     * prepareEvaluate()→evaluateTumi()間で共有する
     */
    private int rensaYomi[][] = new int[22][3];
    /**
     * 連鎖数読みの最大値の一時保存用
     * prepareEvaluate()→evaluateTumi()間で共有する
     */
    private int rensaYomiMax[] = new int[3];
 
    /**
     * デフォルトコンストラクタ
     */
    public RensaYomi6ABC() {
    }
 
    /**
     * パラメータ付きコンストラクタ
     * @param mode
     */
    public RensaYomi6ABC(int mode) {
        this.mode = mode;
    }
 
    /**
     * COMとしての表示名を取得する
     * @return COMとしての表示名
     */
    public String getDisplayName() {
        switch (mode) {
        default:
        return "RensaYomi6";
        }
    }
    
    /**
     * 説明コメントを取得する
     * @return 説明コメントを取得する
     */
    public String getComment() {
        switch (mode) {
        default:
        return "NEXT3まで読む.思考に時間がかかって,積みは遅い";
        }
    }
 
    /**
     * 積み込み時間間隔を求める
     * これが大きいほど積みが遅い
     * @return 積み込み時間間隔(msec)
     */
    public long getTumiInterval() {
        switch (mode) {
        default:
            return 45;
        }
    }
 
    /**
     * 部屋のルールを求める
     * @return
     */
    public int getRoomRule() {
        return 0;
    }
 
    /**
     * 実装している実行パスの最大番号を返す
     * 
     * @return 実行パスの最大番号
     */
    public int getMaxPass() {
        return 2;
    }
 
    /**
     * 評価処理の初期化.1手ごとに評価前に呼び出される
     * 
     * @param beforeField 自分のフィールド(NEXT積み適用前)
     * @param enemyFields 敵のフィールド
     * @param isAnyEnemyDoingRensa いずれかの敵が連鎖を発動中
     */
    public void initEvaluator(PuField beforeField, PuField[] enemyFields, boolean isAnyEnemyDoingRensa) {
        for (int j = 0; j < 3; j++) {
            for (int i = 0; i < 22; i++) {
                rensaYomi[i][j] = 0;
            }
            rensaYomiMax[j] = 0;
        }
 
        // 現在のフィールド詰まり具合を評価する
        updateCurrentStatus(beforeField, isAnyEnemyDoingRensa);
    }
 
    /**
     * 評価得点を算出する準備,枝刈りを行う
     * 
     * @param pass 実行パスの番号(0〜maxPass)
     * @param tumiIndex 着目している積み手を識別する番号 (0〜21)
     * @param beforeField 自分のフィールド(NEXT積み適用前)
     * @param afterField 自分のフィールド(NEXT積み適用後)
     * @param fallenTumi NEXT積み(重力落下後)
     * @param nextTumos ツモ色.NEXT:[0][0]〜[0][1],NEXT2:[1][0]〜[1][1]
     * @param enemyFields 敵のフィールド
     * @param isAnyEnemyDoingRensa いずれかの敵が連鎖を発動中
     * @return 有効な積みとして残すならtrue,刈るならfalse
     */
    public boolean prepareEvaluate(int pass, int tumiIndex, PuField beforeField, PuField afterField, Tumi fallenTumi, byte[][] nextTumos, PuField[] enemyFields, boolean isAnyEnemyDoingRensa) {
        boolean keepAsValidTumi = true;
        
        boolean isRensaStart = afterField.duplicate().vanish(0).any_rensa;
 
        if (pass == 0) {
            // ★ 1.窒息を予防する
            // 窒息手を棄却する
            int df = 0; 
            for (int k = 0; k < 2; k++) {
                if (fallenTumi.getI()[k] == 2 && fallenTumi.getJ()[k] <= 1 && !isRensaStart) {
                    // 窒息手
                    keepAsValidTumi = false;
                }
            }
        } else if (pass == 1) {
            // ★ 2.連鎖の誤発動を予防する
            // 誤発動する手を棄却する
            if (!isRedZone(beforeField) && !isAnyEnemyDoingRensa && isRensaStart) {
                keepAsValidTumi = false;
            }           
        } else if (pass == 2) {
            // 最大3手の読み.棄却は行わない
            
            // NEXTを置いた状態で,発火候補点で発火したときに生じる連鎖数
            // 広めの探索
            PuField firedField = new PuField();
            int con = 3;
            if (currentStatus == STATUS_RED3) {
                con = 4;
            } else if (currentStatus == STATUS_RED2 || currentStatus == STATUS_RED1) {
                con = 4;
            }
            rensaYomi[tumiIndex][0] = evaluateByRensaYomi(afterField, fallenTumi, con, firedField, false);
 
            // NEXT,NEXT2を置いた状態で,発火候補点で発火したときに生じる連鎖数
            Tumi droppedBestTumi = new Tumi();
            rensaYomi[tumiIndex][1] = evaluateNextTumoByRensaYomi(afterField, nextTumos[1], droppedBestTumi, firedField);
        
            if (currentStatus != STATUS_RED3) {
                // Red3ステータスでなければ,2手読みまでで止める
                rensaYomi[tumiIndex][2] = 0;
                
            } else {
                // NEXT,NEXT2,NEXT3を積んだ状態で,発火候補点で発火したときに生じる連鎖数
                // Redステータスの場合は,発火タイミングを決めるためにこの3手読みが必要
                PuField afterAfterField = afterField.duplicate();
                afterAfterField = AiUtil.applyTumiToField(afterAfterField, droppedBestTumi);
                afterAfterField = afterAfterField.createVanishedField();
                afterAfterField.gravitate();
                rensaYomi[tumiIndex][2] = evaluateNextTumoByRensaYomi(afterAfterField, nextTumos[2], droppedBestTumi, firedField);
            }
 
            // 最大値なら,最大値を更新する
            for (int i = 0; i < rensaYomiMax.length; i++) {
                if (rensaYomi[tumiIndex][i] > rensaYomiMax[i]) {
                    rensaYomiMax[i] = rensaYomi[tumiIndex][i];          
                }
            }
        }
        return keepAsValidTumi;
    }
 
 
    /**
     * 積みに対する評価得点を算出する
     * @param tumiIndex 着目している積み手を識別する番号 (0〜21)
     * @param beforeField 自分のフィールド(NEXT積み適用前)
     * @param afterField 自分のフィールド(NEXT積み適用後)
     * @param fallenTumi NEXT積み(重力落下後)
     * @param nextTumos ツモ色.NEXT:[0][0]〜[0][1],NEXT2:[1][0]〜[1][1]
     * @param enemyFields 敵のフィールド
     * @param isAnyEnemyDoingRensa いずれかの敵が連鎖を発動中
     */
    public long evaluateTumi(int tumiIndex, PuField beforeField, PuField afterField, Tumi fallenTumi,   byte[][] nextTumos, PuField[] enemyFields, boolean isAnyEnemyDoingRensa) {
        final int fpRange[] = new int[]{20, 800, Const.FIELD_Y * 2 + 1,  Const.FIELD_X * 2 + 1};
        int fp[] = new int[fpRange.length];     
        
        boolean isRensaStart = afterField.duplicate().vanish(0).any_rensa;
 
        int ry1 = rensaYomi[tumiIndex][0];
        int ry2 = rensaYomi[tumiIndex][1];
        int ry3 = rensaYomi[tumiIndex][2];
 
        // ★ 2.発火タイミングの判定
        if (!isRensaStart) {
            fp[0] = 1;
        } else {
            if (currentStatus == STATUS_RED1) {
                // すぐに発火する
                fp[0] = ry1;
            } else if (currentStatus == STATUS_RED2) {
                // NEXT2までの発火延長を考慮する
                if (ry1 >= rensaYomiMax[1]) {
                    fp[0] = ry1;
                } else {
                    fp[0] = 0;
                }
            } else if (currentStatus == STATUS_RED3) {
                // NEXT3までの発火延長を考慮する
                if (ry1 >= rensaYomiMax[1] && ry1 >= rensaYomiMax[2]) {
                    fp[0] = ry1;
                } else {
                    fp[0] = 0;
                }
            } else {
                fp[0] = 0;
            }
        }
        
        if (false) {
            System.out.println("ry1,ry2,ry3,fp[0]=" + ry1 + "," + ry2 + "," + ry3 + "," + fp[0] + 
                "[" + statusDesc[currentStatus] + "]");
        }
 
        // ★ 3.連鎖読みの結果
        if (currentStatus == STATUS_RED1) {
            fp[1] = ry1;
            
        } else if (currentStatus == STATUS_RED3) {
            fp[1] = ry3 + ry2 / 2 + ry1 / 4;
            
        } else {
            fp[1] = ry2 + ry1 / 2;
        }
               
        // ★ 4.できるだけ下に置く
        fp[2] = fallenTumi.getJ()[0] + fallenTumi.getJ()[1]; // [0,Const.FIELD_Y*2]
        
        // ★ 5.できるだけ端の列に置く
        fp[3] = Math.abs(fallenTumi.getI()[0] - 2) + Math.abs(fallenTumi.getI()[1] - 2); // [0,Const.FIELD_X*2]
 
        // 各ルールによる評価点を,優先順位を考慮して集計する
        long result = 0;
        long eff = 1;       
        for (int i = fp.length - 1; i >= 0; i--) {
            result += eff * fp[i];
            eff *= fpRange[i];
        }
 
        return result;
    }
    
    /**
     * COMの詰まり具合を調べて,メンバを更新する
     * @param beforeField
     * @param isAnyEnemyDoingRensa
     */
    private void updateCurrentStatus(PuField beforeField, boolean isAnyEnemyDoingRensa) {
        if (isAnyEnemyDoingRensa) {
            currentStatus = STATUS_RED1;
            
        } else if (isRedZone(beforeField)) {
            currentStatus = STATUS_RED3;
            
            if (AiUtil.countEmpty(beforeField) <= 28 ||
                beforeField.getPuyo(2, 2) > 0 ||
                beforeField.getPuyo(3, 2) > 0 ||
                beforeField.getPuyo(1, 2) > 0 ||
                beforeField.getPuyo(4, 2) > 0) {
                currentStatus = STATUS_RED2;
                
                if (AiUtil.countEmpty(beforeField) <= 24 ||
                    beforeField.getPuyo(2, 1) > 0 ||
                    beforeField.getPuyo(3, 1) > 0 ||
                    beforeField.getPuyo(1, 1) > 0 ||
                    beforeField.getPuyo(4, 1) > 0) {
 
                    currentStatus = STATUS_RED1;
                }
            }       
        } else {
            currentStatus = STATUS_NORMAL;
        }
        
        // 2,3手読みに使用する発火候補点の個数
        currentMinCon = (currentStatus == STATUS_NORMAL || currentStatus == STATUS_RED3)? 3: 4;
    }
 
    /**
     * 発火しても良い状態かを判断する
     * @param beforeField
     * @return 発火しても良いならtrue
     */ 
    private boolean isRedZone(PuField beforeField) {
        
        return (AiUtil.countEmpty(beforeField) <= 40 ||
                beforeField.getPuyo(2, 3) > 0 ||
                beforeField.getPuyo(3, 3) > 0 ||
                beforeField.getPuyo(1, 3) > 0 ||
                beforeField.getPuyo(4, 3) > 0);     
    }
 
    /**
     * 積みを,連鎖読みを使ったルールで評価する
     * @param afterField
     * @param fallenTumi
     * @param minCon
     * @param [OUT]firedField
     * @return 評価結果
     */
    private int evaluateByRensaYomi(PuField afterField, Tumi fallenTumi, int minCon, PuField firedField, boolean rensaOnly) {
 
        int cc = 0;
        if (!rensaOnly) {
            // 連結個数を求める
            // 発火候補点で発火された状況も考慮する版
            int cc1 = AiUtil.countConnected(afterField, fallenTumi.getI()[0], fallenTumi.getJ()[0], fallenTumi.getCol()[0]);
            int cc2 = AiUtil.countConnected(afterField, fallenTumi.getI()[1], fallenTumi.getJ()[1], fallenTumi.getCol()[1]);
            if (cc1 > 4) {
                // 長連規制
                cc1 = 4;
            }
            if (cc2 > 4) {
                // 長連規制
                cc2 = 4;
            }
            cc = cc1 + cc2; // [0,32] * 2 = [0,64]
        }
        
        // 発火候補点で発火した結果
        PuField[] fired = AiUtil.imagineFiredField(afterField, minCon);
 
        int maxRensa = 0;
        for (int i = 0; i < fired.length; i++) {                        
            int currentRensa = 0;
            PuField target = fired[i];
            while (target.vanish(currentRensa).any_rensa) {
                currentRensa++;
                target = target.createVanishedField();
                target.gravitate();
            }
            if (currentRensa > maxRensa) {
                maxRensa = currentRensa;
                firedField.restoreFromString(target.toString());
            }
        }
        // CPU使用率を下げる
        Util.sleep(1);
    
        return maxRensa * maxRensa + cc / 2; // [0,2^19]
    }
 
    /**
     * 所定のフィールド+ツモで連鎖読みを使ったルールを使い,評価したときの最善手の得点を求める
     * つまり,所定のフィールド+ツモでは,どれだけ良い手が見つかるのかを評価する
     * @param field
     * @param nextTumo
     * @param [OUT]resultTumiDropped
     * @param [OUT]firedField
     * @return
     */
    public int evaluateNextTumoByRensaYomi(PuField field, byte[] nextTumo, Tumi resultTumiDropped, PuField firedField) {
 
        PuField fb = field.duplicate();
 
        Tumi beforeDrop = new Tumi();
        int[] points = new int[Const.FIELD_X * 2 + (Const.FIELD_X - 1) * 2];
        PuField[] fields = new PuField[Const.FIELD_X * 2 + (Const.FIELD_X - 1) * 2];
        Tumi[] tumis = new Tumi[Const.FIELD_X * 2 + (Const.FIELD_X - 1) * 2];
        int firstTumiIndex = 0;
        int k = 0;
        final int[][] fallX = {{2, 1, 0}, {3, 4, 5}};
        
        // 縦置き
        int d = 0;
        int i = 0;
        while (d < 2) {
            int x = fallX[d][i];
            
            PuField fa = fb.duplicate();
 
            if (fa.getPuyo(x, 0) == 0 && fa.getPuyo(x, 1) == 0) {
                // 最上段が埋まっていないので落とせる列
                beforeDrop.setElement(0, x, 0, nextTumo[0]);
                beforeDrop.setElement(1, x, 1, nextTumo[1]);
                Tumi dropped = AiUtil.falldown(fa, beforeDrop);
                fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                fa.putPuyo(x, dropped.getJ()[1], dropped.getCol()[1]);
                
                // 評価得点と積み手を記録する
                fields[k] = new PuField();
                points[k] = evaluateByRensaYomi(fa, dropped, currentMinCon, fields[k], true);
                tumis[k] = dropped;
                firstTumiIndex = k;
                k++;
 
                // 上下入れ替え
                fa = fb.duplicate();
                beforeDrop.setElement(0, x, 0, nextTumo[1]);
                beforeDrop.setElement(1, x, 1, nextTumo[0]);
                dropped = AiUtil.falldown(fa, beforeDrop);
                fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                fa.putPuyo(x, dropped.getJ()[1], dropped.getCol()[1]);
                
                // 評価得点と積み手を記録する
                fields[k] = new PuField();
                points[k] = evaluateByRensaYomi(fa, dropped, currentMinCon, fields[k], true);
                tumis[k] = dropped;
                k++;
 
                i++;
 
                if (i >= fallX[d].length) {
                    i = 0;
                    d++;
                }
                
            } else {
                // 最上段が埋まっているので方向を変える
                i = 0;
                d++;
            }
        }
        // 横置き
        d = 0;
        i = 0;
        while (d < 2) {
            int x = fallX[d][i];
 
            PuField fa = fb.duplicate();
 
            if (fa.getPuyo(x, 0) == 0 && fa.getPuyo(x + 1, 0) == 0) {
                beforeDrop.setElement(0, x, 0, nextTumo[0]);
                beforeDrop.setElement(1, x + 1, 0, nextTumo[1]);
                Tumi dropped = AiUtil.falldown(fa, beforeDrop);
                fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                fa.putPuyo(x + 1, dropped.getJ()[1], dropped.getCol()[1]);
            
                // 評価得点と積み手を記録する
                fields[k] = new PuField();
                points[k] = evaluateByRensaYomi(fa, dropped, currentMinCon, fields[k], true);
                tumis[k] = dropped;
                firstTumiIndex = k;
                k++;                    
 
                // 左右入れ替え
                fa = fb.duplicate();
                beforeDrop.setElement(0, x, 0, nextTumo[1]);
                beforeDrop.setElement(1, x + 1, 0, nextTumo[0]);
                dropped = AiUtil.falldown(fa, beforeDrop);
                fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                fa.putPuyo(x + 1, dropped.getJ()[1], dropped.getCol()[1]);
            
                // 評価得点と積み手を記録する
                fields[k] = new PuField();
                points[k] = evaluateByRensaYomi(fa, dropped, currentMinCon, fields[k], true);
                tumis[k] = dropped;
                k++;
                
                i++;
 
                if (i >= fallX[d].length || (d == 1 && i >= fallX[d].length - 1)) {
                    i = 0;
                    d++;
                }
                
            } else {
                // 最上段が埋まっているので方向を変える
                i = 0;
                d++;
            }
        }   
        
        // 積み手のうち,最高得点のものを求める     
        int maxPoint = points[firstTumiIndex];
        for (i = 1; i < k; i++) {
            if (points[i] > maxPoint) {
                maxPoint = points[i];
                resultTumiDropped.setElement(0, tumis[i].getI()[0], tumis[i].getJ()[0], tumis[i].getCol()[0]);
                resultTumiDropped.setElement(1, tumis[i].getI()[1], tumis[i].getJ()[1], tumis[i].getCol()[1]);
                firedField.restoreFromString(fields[i].toString());
            }
        }       
        return maxPoint;            
    }
 }

AiUtil.java

 /**
 * $Id: A5BDA1BCA5B92852656E7361596F6D693629.txt,v 1.1.1.1 2009/06/01 12:21:04 kamoi Exp $
 * Copyright 2004 kamoland.com All rights reserved.
 */
 package com.kamoland.rensacommon.ai;
 
 import java.util.Vector;
 
 import com.kamoland.rensacommon.Const;
 import com.kamoland.rensacommon.rule.LinkedPuyo;
 import com.kamoland.rensacommon.rule.PuField;
 
 /**
 * AIロジックに提供するユーティリティクラス
 * 
 * @author kamoland.com
 * @version
 * <pre>
 * 2004.06.23 decideTumiWithPruning()におけるtumiIndex追加に対応した
 * 2004.06.11 decideTumi()で壁越え禁止処理を組み込んだ
 * 2004.06.09 リファクタリング
 * 2004.05.29 imagineFiredField()を作成した
 * 2004.05.22 評価関数の返値をint→longに変更したのに対応
 * 2004.05.04 新規作成
 * </pre>
 */
 public class AiUtil {
    /**
     * 積みを重力落下して,落下後の積み位置を取得する
     * @param field
     * @param initTumi
     * @return
     */
    public static Tumi falldown(PuField field, Tumi initTumi) {
        Tumi fallenTumi = new Tumi();
        int under;
        int upper;
        if (initTumi.getJ()[0] < initTumi.getJ()[1]) {
            under = 1;
            upper = 0;
        } else {
            under = 0;
            upper = 1;          
        }
        
        int initJ = initTumi.getJ()[under];
        int i = initTumi.getI()[under];
        int j = initJ;
        byte col = initTumi.getCol()[under];
        
        for (int j0 = initJ; j0 < Const.FIELD_Y; j0++) {
            if (field.getPuyo(i, j0) != 0) {
                break;
            }
            j = j0;
        }
        fallenTumi.setElement(under, i, j, col);
        field.putPuyo(i, j, col);
        
        initJ = initTumi.getJ()[upper];
        i = initTumi.getI()[upper];
        j = initJ;
        col = initTumi.getCol()[upper];
        
        for (int j0 = initJ; j0 < Const.FIELD_Y; j0++) {
            if (field.getPuyo(i, j0) != 0) {
                break;
            }
            j = j0;
        }
        fallenTumi.setElement(upper, i, j, col);
        
        return fallenTumi;
    }
    
    /**
     * 積みを適用後のフィールドイメージを取得する
     * @param field
     * @param tumi
     * @return
     */
    public static PuField applyTumiToField(PuField field, Tumi tumi) {
        PuField newField = field.duplicate();
        
        newField.putPuyo(tumi.getI()[0], tumi.getJ()[0], tumi.getCol()[0]);
        newField.putPuyo(tumi.getI()[1], tumi.getJ()[1], tumi.getCol()[1]);
        
        return newField;
    }
 
    /**
     * 列指定で同色を検索する
     * @param field
     * @param i
     * @param col
     * @return
     */
    public static LinkedPuyo queryByI(PuField field, int i, byte col) {
        LinkedPuyo result = new LinkedPuyo(Const.FIELD_Y);
        int num = 0;
        
        for (int j = 0; j < Const.FIELD_Y; j++) {
            if (field.getPuyo(i, j) == col) {
                result.p_i[num] = i;
                result.p_j[num] = j;
                num++;
            }
        }
        result.num = num;
        return result;
    }
    
    /**
     * 連結している同色を検索し,その個数を返す
     * 
     * @param field
     * @param i
     * @param j
     * @param col
     * @return
     */
    public static int countConnected(PuField field, int i, int j, byte col) {
        int sum = 0;
        int tl = 0;
        int tr = 0;
        int bl = 0;
        int br = 0;
        
        // 左を検索する
        if (field.getPuyo(i - 1, j) == col) {
            sum++;
            if (tl == 0 && field.getPuyo(i - 1, j - 1) == col) {
                sum++;
                tl++;
            }
            if (bl == 0 && field.getPuyo(i - 1, j + 1) == col) {
                sum++;
                bl++;
            }
            if (field.getPuyo(i - 2, j) == col) {
                sum++;
                if (field.getPuyo(i - 2, j - 1) == col) {
                    sum++;
                }
                if (field.getPuyo(i - 2, j + 1) == col) {
                    sum++;
                }
                if (field.getPuyo(i - 3, j) == col) {
                    sum++;
                }
            }
        }
        // 右を検索する
        if (field.getPuyo(i + 1, j) == col) {
            sum++;
            if (tr == 0 && field.getPuyo(i + 1, j - 1) == col) {
                sum++;
                tr++;
            }
            if (br == 0 && field.getPuyo(i + 1, j + 1) == col) {
                sum++;
                br++;
            }
            if (field.getPuyo(i + 2, j) == col) {
                sum++;
                if (field.getPuyo(i + 2, j - 1) == col) {
                    sum++;
                }
                if (field.getPuyo(i + 2, j + 1) == col) {
                    sum++;
                }
                if (field.getPuyo(i + 3, j) == col) {
                    sum++;
                }
            }
        }
        // 上を検索する
        if (field.getPuyo(i, j - 1) == col) {
            sum++;
            if (tl == 0 && field.getPuyo(i - 1, j - 1) == col) {
                sum++;
                tl++;
            }
            if (tr == 0 && field.getPuyo(i + 1, j - 1) == col) {
                sum++;
                tr++;
            }
            if (field.getPuyo(i, j - 2) == col) {
                sum++;
                if (field.getPuyo(i - 1, j - 2) == col) {
                    sum++;
                }
                if (field.getPuyo(i + 1, j - 2) == col) {
                    sum++;
                }
                if (field.getPuyo(i, j - 3) == col) {
                    sum++;
                }
            }
        }
        // 下を検索する
        if (field.getPuyo(i, j + 1) == col) {
            sum++;
            if (bl == 0 && field.getPuyo(i - 1, j + 1) == col) {
                sum++;
                bl++;
            }
            if (br == 0 && field.getPuyo(i + 1, j + 1) == col) {
                sum++;
                br++;
            }
            if (field.getPuyo(i, j + 2) == col) {
                sum++;
                if (field.getPuyo(i - 1, j + 2) == col) {
                    sum++;
                }
                if (field.getPuyo(i + 1, j + 2) == col) {
                    sum++;
                }
                if (field.getPuyo(i, j + 3) == col) {
                    sum++;
                }
            }
        }
        return sum;
    }
        
    /**
     * AI-COMフレームワークの呼び出し部分
     * AIロジッククラスを呼び出して,次の積み手を決定する
     * 
     * @param aiImpl 呼び出すべきAIロジッククラス
     * @param field 自分のフィールド
     * @param nextTumos ツモ色.NEXT:[0][0]〜[0][1],NEXT2:[1][0]〜[1][1]
     * @param myJama 自分が食らう予定のオジャマ得点
     * @param enemyFields 敵のフィールド
     * @return 次の積み手(重力落下前)
     */
    public static Tumi decideTumi(TumiDecider aiImpl, PuField field, byte[][] nextTumos, int myJama, PuField[] enemyFields) {
 
        if (aiImpl instanceof TumiDeciderPruning) {
            return decideTumiWithPruning((TumiDeciderPruning)aiImpl, field, nextTumos, myJama, enemyFields);
        }
        
        PuField fb = field.duplicate();
 
        // Tumiのとりうる組み合わせは,
        // ・縦置き X=0〜5 ×2(上下) = 12
        // ・横置き 左側のX=0〜4 ×2(左右) = 10
    
        Tumi beforeDrop = new Tumi();
        long[] points = new long[Const.FIELD_X * 2 + (Const.FIELD_X - 1) * 2];
        Tumi[] tumis = new Tumi[Const.FIELD_X * 2 + (Const.FIELD_X - 1) * 2];
        int firstTumiIndex = 0;
        int k = 0;
        int[][] fallX = {{2, 1, 0}, {3, 4, 5}};
                
        boolean isAnyEnemyDoingRensa = false;
        if (myJama > 0) {
            isAnyEnemyDoingRensa = true;
        } else {
            for (int i = 0; i < enemyFields.length; i++) {
                if (enemyFields[i].vanish(0).any_rensa) {
                    // 敵のうち1人以上が連鎖中である
                    isAnyEnemyDoingRensa = true;
                }
            }
        }
        
        // 縦置き
        int d = 0;
        int i = 0;
        while (d < 2) {
            int x = fallX[d][i];
            
            PuField fa = fb.duplicate();
 
            if (fa.getPuyo(x, 0) == 0 && fa.getPuyo(x, 1) == 0) {
                // 最上段が埋まっていないので落とせる列
                beforeDrop.setElement(0, x, 0, nextTumos[0][0]);
                beforeDrop.setElement(1, x, 1, nextTumos[0][1]);
                Tumi dropped = AiUtil.falldown(fa, beforeDrop);
                fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                fa.putPuyo(x, dropped.getJ()[1], dropped.getCol()[1]);
                
                // 評価得点と積み手を記録する
                points[k] = aiImpl.evaluateTumi(fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                tumis[k] = dropped;
                firstTumiIndex = k;
                k++;
 
                // 上下入れ替え
                fa = fb.duplicate();
                beforeDrop.setElement(0, x, 0, nextTumos[0][1]);
                beforeDrop.setElement(1, x, 1, nextTumos[0][0]);
                dropped = AiUtil.falldown(fa, beforeDrop);
                fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                fa.putPuyo(x, dropped.getJ()[1], dropped.getCol()[1]);
                
                // 評価得点と積み手を記録する
                points[k] = aiImpl.evaluateTumi(fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                tumis[k] = dropped;
                k++;
 
                i++;
 
                if (i >= fallX[d].length) {
                    i = 0;
                    d++;
                }
                
            } else {
                // 最上段が埋まっているので方向を変える
                i = 0;
                d++;
            }
        }
        // 横置き
        d = 0;
        i = 0;
        while (d < 2) {
            int x = fallX[d][i];
 
            PuField fa = fb.duplicate();
 
            if (fa.getPuyo(x, 0) == 0 && fa.getPuyo(x + 1, 0) == 0) {
                beforeDrop.setElement(0, x, 0, nextTumos[0][0]);
                beforeDrop.setElement(1, x + 1, 0, nextTumos[0][1]);
                Tumi dropped = AiUtil.falldown(fa, beforeDrop);
                fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                fa.putPuyo(x + 1, dropped.getJ()[1], dropped.getCol()[1]);
            
                // 評価得点と積み手を記録する
                points[k] = aiImpl.evaluateTumi(fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                tumis[k] = dropped;
                firstTumiIndex = k;
                k++;                    
 
                // 左右入れ替え
                fa = fb.duplicate();
                beforeDrop.setElement(0, x, 0, nextTumos[0][1]);
                beforeDrop.setElement(1, x + 1, 0, nextTumos[0][0]);
                dropped = AiUtil.falldown(fa, beforeDrop);
                fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                fa.putPuyo(x + 1, dropped.getJ()[1], dropped.getCol()[1]);
            
                // 評価得点と積み手を記録する
                points[k] = aiImpl.evaluateTumi(fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                tumis[k] = dropped;                 
                k++;
                
                i++;
 
                if (i >= fallX[d].length || (d == 1 && i >= fallX[d].length - 1)) {
                    i = 0;
                    d++;
                }
                
            } else {
                // 最上段が埋まっているので方向を変える
                i = 0;
                d++;
            }
        }   
        
        // 積み手のうち,最高得点のものを求める     
        long maxPoint = points[firstTumiIndex];
        Tumi maxPointTumi = tumis[firstTumiIndex];
        for (i = 1; i < k; i++) {
            if (points[i] > maxPoint) {
                maxPoint = points[i];
                maxPointTumi = tumis[i];
            }
        }
            
        return maxPointTumi;            
    }
    
    /**
     * AI-COMフレームワークの呼び出し部分 [枝刈り対応版]
     * AIロジッククラスを呼び出して,次の積み手を決定する
     * 
     * @param aiImpl 呼び出すべきAIロジッククラス
     * @param field 自分のフィールド
     * @param nextTumos ツモ色.NEXT:[0][0]〜[0][1],NEXT2:[1][0]〜[1][1]
     * @param myJama 自分が食らう予定のオジャマ得点
     * @param enemyFields 敵のフィールド
     * @return 次の積み手(重力落下前)
     */
    public static Tumi decideTumiWithPruning(TumiDeciderPruning aiImpl, PuField field, byte[][] nextTumos, int myJama, PuField[] enemyFields) {
 
        final int TUMI_VARIETY  = Const.FIELD_X * 2 + (Const.FIELD_X - 1) * 2;
        final int[][] fallX = {{2, 1, 0}, {3, 4, 5}};
 
        long[] points = new long[TUMI_VARIETY];
        Tumi[] tumis = new Tumi[TUMI_VARIETY];
        boolean[] rejected = new boolean[TUMI_VARIETY];
 
        int maxTumiIndex = 0;
        int tumiIndex;
        Tumi beforeDrop = new Tumi();
        
        for (int i = 0; i < TUMI_VARIETY; i++) {
            points[i] = 0;
            rejected[i] = false;
        }
 
        PuField fb = field.duplicate();
        
        boolean isAnyEnemyDoingRensa = false;
        if (myJama > 0) {
            isAnyEnemyDoingRensa = true;
        } else {
            for (int i = 0; i < enemyFields.length; i++) {
                if (enemyFields[i].vanish(0).any_rensa) {
                    // 敵のうち1人以上が連鎖中である
                    isAnyEnemyDoingRensa = true;
                }
            }
        }
        
        // AI初期化
        aiImpl.initEvaluator(fb, enemyFields, isAnyEnemyDoingRensa);
        System.out.println("initEvaluator() processed.");
        int maxPass = aiImpl.getMaxPass();
        
        for (int pass = 0; pass <= maxPass + 1; pass++) {
            tumiIndex = 0;
            boolean[] rejectedNow = new boolean[TUMI_VARIETY];
            
            // 縦置き
            int d = 0;
            int i = 0;
            while (d < 2) {
                int x = fallX[d][i];
                
                PuField fa = fb.duplicate();
    
                if (fa.getPuyo(x, 0) == 0 && fa.getPuyo(x, 1) == 0) {
                    // 最上段が埋まっていないので落とせる列
                    if (!rejected[tumiIndex]) {
                        beforeDrop.setElement(0, x, 0, nextTumos[0][0]);
                        beforeDrop.setElement(1, x, 1, nextTumos[0][1]);
                        Tumi dropped = AiUtil.falldown(fa, beforeDrop);
                        fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                        fa.putPuyo(x, dropped.getJ()[1], dropped.getCol()[1]);
                        
                        // 評価得点と積み手を記録する
                        if (pass == maxPass + 1) {
                            points[tumiIndex] = aiImpl.evaluateTumi(tumiIndex, fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                            tumis[tumiIndex] = dropped;
                        
                        } else {
                            rejectedNow[tumiIndex] = !aiImpl.prepareEvaluate(pass, tumiIndex, fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                        }
                    }
                    tumiIndex++;
    
                    // 上下入れ替え
                    if (!rejected[tumiIndex]) {
                        fa = fb.duplicate();
                        beforeDrop.setElement(0, x, 0, nextTumos[0][1]);
                        beforeDrop.setElement(1, x, 1, nextTumos[0][0]);
                        Tumi dropped = AiUtil.falldown(fa, beforeDrop);
                        fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                        fa.putPuyo(x, dropped.getJ()[1], dropped.getCol()[1]);
                        
                        // 評価得点と積み手を記録する
                        if (pass == maxPass + 1) {
                            points[tumiIndex] = aiImpl.evaluateTumi(tumiIndex, fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                            tumis[tumiIndex] = dropped;
                            
                        } else {
                            rejectedNow[tumiIndex] = !aiImpl.prepareEvaluate(pass, tumiIndex, fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                        }
                    }
                    tumiIndex++;
                    
                    // X位置を次に進める
                    i++;
                    if (i >= fallX[d].length) {
                        i = 0;
                        d++;
                    }
                    
                } else {
                    // 最上段が埋まっているので方向を変える
                    i = 0;
                    d++;
                }
            }
            // 横置き
            d = 0;
            i = 0;
            while (d < 2) {
                int x = fallX[d][i];
    
                PuField fa = fb.duplicate();
    
                if (fa.getPuyo(x, 0) == 0 && fa.getPuyo(x + 1, 0) == 0) {
                    if (!rejected[tumiIndex]) {
                        beforeDrop.setElement(0, x, 0, nextTumos[0][0]);
                        beforeDrop.setElement(1, x + 1, 0, nextTumos[0][1]);
                        Tumi dropped = AiUtil.falldown(fa, beforeDrop);
                        fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                        fa.putPuyo(x + 1, dropped.getJ()[1], dropped.getCol()[1]);
                    
                        // 評価得点と積み手を記録する
                        if (pass == maxPass + 1) {
                            points[tumiIndex] = aiImpl.evaluateTumi(tumiIndex, fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                            tumis[tumiIndex] = dropped;
                            
                        } else {
                            rejectedNow[tumiIndex] = !aiImpl.prepareEvaluate(pass, tumiIndex, fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                        }
                    }
                    tumiIndex++;                    
    
                    // 左右入れ替え
                    if (!rejected[tumiIndex]) {
                        fa = fb.duplicate();
                        beforeDrop.setElement(0, x, 0, nextTumos[0][1]);
                        beforeDrop.setElement(1, x + 1, 0, nextTumos[0][0]);
                        Tumi dropped = AiUtil.falldown(fa, beforeDrop);
                        fa.putPuyo(x, dropped.getJ()[0], dropped.getCol()[0]);
                        fa.putPuyo(x + 1, dropped.getJ()[1], dropped.getCol()[1]);
                    
                        // 評価得点と積み手を記録する
                        if (pass == maxPass + 1) {
                            points[tumiIndex] = aiImpl.evaluateTumi(tumiIndex, fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                            tumis[tumiIndex] = dropped;
                            
                        } else {
                            rejectedNow[tumiIndex] = !aiImpl.prepareEvaluate(pass, tumiIndex, fb, fa, dropped, nextTumos, enemyFields, isAnyEnemyDoingRensa);
                        }
                    }                   
                    tumiIndex++;
                    
                    // X位置を次に進める
                    i++;
                    if (i >= fallX[d].length || (d == 1 && i >= fallX[d].length - 1)) {
                        i = 0;
                        d++;
                    }
                    
                } else {
                    // 最上段が埋まっているので方向を変える
                    i = 0;
                    d++;
                }
            }
            if (pass == 0) {
                maxTumiIndex = tumiIndex - 1;
            }
            
            // 今回のパスで全ての手が枝刈りされた場合,
            // 今回のパスの枝刈りを無効にする
            boolean anyTumiNotReject = false;
            for (int k = 0; k < maxTumiIndex; k++) {
                if (!rejected[k] && !rejectedNow[k]) {
                    // 少なくとも1手はrejectされなかった
                    anyTumiNotReject = true;
                    break;
                }
            }
            if (anyTumiNotReject) {
                // rejectされなかった手があったので,実際に反映する
                for (int k = 0; k < maxTumiIndex; k++) {
                    rejected[k] = rejectedNow[k];
                }
            }
            System.out.println("pass[" + pass + "] processed.");
        }
            
        // 積み手のうち,最高得点のものを求める     
        long maxPoint = points[0];
        Tumi maxPointTumi = tumis[0];
        for (tumiIndex = 1; tumiIndex <= maxTumiIndex; tumiIndex++) {
            if (points[tumiIndex] > maxPoint) {
                maxPoint = points[tumiIndex];
                maxPointTumi = tumis[tumiIndex];
            }
        }
        return maxPointTumi;            
    }
 
    /**
     * 発火したと仮定して,発火消去後のフィールドを返す
     * 発火候補点の条件は,
     *  (A)3個以上連結している
     *  (B)上右左のいずれかが空白である (積みの到達可能位置である)
     * の両方を満たしていること
     * 
     * @param field
     * @return 発火消去後のフィールド.発火候補点の個数分(0〜22個)
     */
    public static PuField[] imagineFiredField(PuField field) {
        return imagineFiredField(field, 3);
    }
    
    /**
     * 発火したと仮定して,発火消去後のフィールドを返す
     * 発火候補点の条件は,
     *  (A)minCon個以上連結している
     *  (B)上右左のいずれかが空白である (積みの到達可能位置である)
     * の両方を満たしていること
     * 
     * @param field
     * @return 発火消去後のフィールド.発火候補点の個数分(0〜22個)
     */
    public static PuField[] imagineFiredField(PuField field, int minCon) {
 
        Vector resultTemp = new Vector();
        boolean[][] processed = new boolean[Const.FIELD_X][Const.FIELD_Y];
        
        for (int i = 0; i < Const.FIELD_X; i++) {
            for (int j = 1; j < Const.FIELD_Y; j++) {
 
                // まず,検索開始セルかどうかを判断する
                if (field.getPuyo(i, j) > 0 &&
                    (j > 1 && field.getPuyo(i, j - 1) == 0 || 
                    i > 0 && field.getPuyo(i - 1, j) == 0 ||
                    i < Const.FIELD_X - 1 && field.getPuyo(i + 1, j) == 0)) {
 
                    if (!processed[i][j]) {
                        // 処理済みでなければ
                        
                        // 連結を検索する
                        LinkedPuyo lp = field.searchLinkedPuyo(i, j);
                        for (int n = 0; n < lp.num; n++) {
                            // 処理済みとしておく
                            processed[lp.p_i[n]][lp.p_j[n]] = true;
                        }
                        
                        if (lp.num >= minCon) {
                            // 発火候補点の場合
                            PuField wf = field.duplicate();
                        
                            // 連結を消去する
                            for (int n = 0; n < lp.num; n++) {
                                wf.putPuyo(lp.p_i[n], lp.p_j[n], (byte)0);
                            }
                            // 重力落下を適用する
                            wf.gravitate();
    
                            // 結果のフィールドを,返値用に保存する
                            resultTemp.addElement(wf);
                        }   
                    }
                }       
            }
        }
 
        // 結果を配列に入れ直す
        PuField results[] = new PuField[resultTemp.size()];
        for (int k = 0; k < resultTemp.size(); k++) {
            results[k] = (PuField)resultTemp.elementAt(k);
        }
 
        return results;
    }
 
    /**
     * フィールドの空き領域の個数を数える
     * @param field
     * @return 空き領域の個数
     */
    public static int countEmpty(PuField field) {
        int c = 0;
        for (int i = 0; i < Const.FIELD_X; i++) {
            for (int j = 0; j < Const.FIELD_Y; j++) {
                if (field.getPuyo(i, j) == 0) {
                    c++;
                } else {
                    j = Const.FIELD_Y;
                }
            }
        }
        return c;
    }
 }


© 2024 KMIソフトウェア