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;
}
}