blog.waterlow.work

Ruby, Rails, js, etc...

【Java】【OOP】【HitAndBlow】そこそこ戦略的に動くAIつくった!

以前作ってみたのですが、今回作ったのはその焼き直し?です。

慣れないものを作るとコメント書かないわメソッド変数名はぐちゃぐちゃだわでかなりひどいのですが、自戒の意味で貼り付けておくことにしておきます。

良かったとこ
・とりあえず動くものができた
・前よりも実行時間がかなり短くなった。
 →順列を数え上げるアルゴリズムを変えたため?
 →呼び出し階層を浅く単純にしたため?

悪かたっとこ
・クラス図さぼった
・メソッド名・変数名が長い悪い
・コードコメントがない
・テストコードない

やること
・クラス図&リファクタリングを同時進行で
・以前のコードも整理して変更点を比較
・2014年もがんばる


2014/1/2追記
朝起きてみてコード直したら2/3ぐらいまで量が減ったので速やかに差し替えました。

package play.ai;

import globalfunction.Permutation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import play.HitAndBlow;
import play.HitBlowResult;
import play.Player;

public class Ai extends Player {
	private static ArrayList<String> remainCodeForAnswer;
	private static final Permutation P = new Permutation(HitAndBlow.CHAR_USED_IN_THIS_GAME, 4);
	{
		P.startPermutation();
		remainCodeForAnswer = new ArrayList<>(P.result);
	}


	// 1つの結果を入れると残りの手を求める(戻り値:ArrayList<String>)
	private static ArrayList<String> renewRemainCode(ArrayList<String> remainCodeForAnswer, HitBlowResult result) {
		ArrayList<String> returnList = new ArrayList<>();
		for (String s : remainCodeForAnswer) {
			HitBlowResult resultTmp = HitBlowResult.answerHitBlowResult(s, result.getCode());
			if (result.equals(resultTmp)) returnList.add(s);
		}
		return returnList;
	}

	// ある文字列を宣言したときに考えられる結果と正解の可能性のある文字列を返却する(戻り値:ArrayList<ArrayList<String>>)
	private static HashMap<String, ArrayList<ArrayList<String>>> getAllRemainNum(ArrayList<String> remainCodeForAnswer) {

		HashMap<String, ArrayList<ArrayList<String>>> bbbbb = new HashMap<>();

		for (String s : P.result) {
			// sに対応する各場合の残りの可能性
			ArrayList<ArrayList<String>> tmpLists = new ArrayList<>();
			// hitCountは0から4を動く
			for (int hitCount = 0; hitCount <= HitAndBlow.CODE_LENGTH; hitCount++) {
				// blowCountは0から4-hitCountを動く
				for (int blowCount = 0; blowCount + hitCount <= HitAndBlow.CODE_LENGTH; blowCount++) {
					// 3hit1blowははじく
					if (hitCount == HitAndBlow.CODE_LENGTH - 1 && blowCount == 1) continue;
					// 結果のインスタンスを作る
					HitBlowResult hbresult = new HitBlowResult(s, hitCount, blowCount);
					tmpLists.add(renewRemainCode(remainCodeForAnswer, hbresult));
				}
			}
			bbbbb.put(s, tmpLists);
		}
		return bbbbb;
	}

	// 期待値を求める
	private static HashMap<String, Double> getAllKitaichi(ArrayList<String> remainCodeForAnswer) {
		HashMap<String, ArrayList<ArrayList<String>>> map = getAllRemainNum(remainCodeForAnswer);
		HashMap<String, Double> returnMap = new HashMap<>();
		for (String key : map.keySet()) {
			int sum = 0;
			int Denominator = 0;
			for (ArrayList<String> list : map.get(key) /* ←ArrayList<ArrayList<String>> */) {
				if (list.size() > 0) {
					sum += list.size() * list.size();
					Denominator += list.size();
				}
			}
			if (Denominator > 0) {
				double ave = (double) sum / Denominator;
				returnMap.put(key, ave);
			}
		}
		return returnMap;
	}

	// 期待値最少の文字列を求める
	private static ArrayList<String> getMinumumKitaichi(ArrayList<String> remainCodeForAnswer) {
		HashMap<String, Double> tmpMap = getAllKitaichi(remainCodeForAnswer);
		List<Map.Entry<String, Double>> entries = new ArrayList<Map.Entry<String, Double>>(tmpMap.entrySet());
		Collections.sort(entries, new Comparator<Map.Entry<String, Double>>() {
			@Override
			public int compare(Entry<String, Double> entry1, Entry<String, Double> entry2) {
				return ((Double) entry1.getValue()).compareTo((Double) entry2.getValue());
			}
		});
		ArrayList<String> returnList = new ArrayList<>();
		double previousValue = 0;
		double currentValue = 0;
		for (Map.Entry<String, Double> entry : entries) {
			returnList.add(entry.getKey());
			previousValue = currentValue;
			currentValue = entry.getValue();
			if(previousValue != 0 && currentValue != previousValue) break;
		}
		return returnList;
	}

	/**
	 * @Override
	 * @return String
	 * 1手目には必ず"0123"を、次の手からは宣言したときに一番残りの手を絞れる可能性の
	 * 高いものを宣言する。
	 */
	public String input() {
		// 1手目には必ず"0123"を宣言する。
		if (resultLog.isEmpty()) return "0123";
		// 2手目以降はそれまでの結果から次に宣言する手を決める。
		else{
			// 前回の結果を得て正解の可能性のある文字列のリストを更新する。
			remainCodeForAnswer = renewRemainCode(remainCodeForAnswer, resultLog.pop());
			// 期待値(宣言したときのremainCodeForAnswer.size())が最小になる文字列をすべて集める。
			ArrayList<String> tmp = new ArrayList<>(getMinumumKitaichi(remainCodeForAnswer));
			// 期待値最少かつ正解の可能性のある文字列を格納するためのArrayListを作成
			ArrayList<String> tmp2 = new ArrayList<>();
			for (String s : remainCodeForAnswer){
				// 全ての残り文字列に対して期待値が最小であればtmp2に入れる
				if(tmp.contains(s)) tmp2.add(s);
			}
			// 期待値最少かつ正解の可能性のある文字列が無ければ期待値最少のものを宣言する。
			if(tmp2.isEmpty()) return tmp.get(0);
			else return tmp2.get(0);
		}
	}
}