//////////////////////////////////////////////////////////////////
// Class of MENACE (Machine Educable Noughts And Crosses Engine)
// for the use of Noughts and Crosses or "tic-tac-toe"
// Copyright (c) by Ysama, 2021
/////////////////////////////////////////////////////////////////

#include	<vector>
#include	<algorithm>
using namespace std;

//CBOARDクラスヘッダー
#include	"CBOARD.h"

////////////////////////
//CMENACEクラスヘッダー
////////////////////////
class CMENACE : public CBOARD {
private:						//CMENACEを承継しても利用できない
	vector <BOARD> m_BoardDB;
	vector <BOARD>::iterator m_Itr;
public:
	//関数
	bool AutoMove(char);		//自動で手を打つ
	void Register();			//盤面の登録
	void PushBack();			//一手前に戻し敗者をm_Outcomeへ代入する
	int Search(BOARD);			//盤面の検索（戻り値：0−該当なし、1−先手、2−後手）
	int BoardCmp(char*, char*);	//盤面比較（戻り値はstrcmp(char*, char*)と同じ）
	void Rotate(char*);			//盤を右に90度回転させる
	void Mirror(char*);			//盤を左右反転させる
	int DBSize();				//DBのデータ数を返す
//	void DBSort();				//DBのデータをm_Posをキーに昇順でソートする
//	void DBShow();				//DBのデータを表示する
	bool BoardWrite(char*);		//DBデータの書き込み
	bool BoardRead(char*);		//DBデータの読み込み
};

//自動で手を打つ
bool CMENACE::AutoMove(char you) {

	//変数
	BOARD work = m_Board;			//対戦盤の複写を作業用盤にする
	int* vac = CheckBoard();		//現在の空き位置配列を取得する
	int pos[10];					//m_BoardDBに該当がない空き位置配列
	pos[9] = 0;						//pos[9]に残空き位置数を入れる
	//空き位置候補に手を打った場合の評価（1手先を読む）
	for(int i  = 0, j = 0; i < vac[9]; i++) {
		work.m_Pos[vac[i]] = you;	//空き位置に自分（you）の手を打つ
		switch(Search(work)) {
		case 0:	//該当なし
			pos[j] = vac[i];		//空き位置をposに入れ
			j++;					//次のpos配列を指す
			pos[9]++;				//その数をpos[9]に記録
			break;
		case 1:	//先手勝利
			if(you == 1) {
				MakeMove(vac[i], you);	//youに有利な手を打つ
				return true;
			}						//相手有利の場合はvac[i]を候補から落とす
			break;
		case 2:	//後手勝利
			if(you == 2) {
				MakeMove(vac[i], you);	//youに有利な手を打つ
				return true;
			}						//相手有利の場合はvac[i]を候補から落とす
			break;
		case -1:	//先手敗北−先手が忌避すべき盤譜であり候補から外す（後手の盤譜ではない）
		case -2:	//後手敗北−後手が忌避すべき盤譜であり候補から外す（先手の盤譜ではない）
			break;
		}
		work.m_Pos[vac[i]] = 0;		//打った手を空き位置（0）に戻す
	}
	//以下ではvac配列から相手有利な位置を除いた空き位置（pos配列）を使う
	if(!pos[9]) {					//vac配列の全ての候補が相手有利で打つべき空き位置が
		m_Board.m_Outcome = -you;	//がない為投了→負けなのでm_Outcomeにマイナス値を代入
		return false;				//この後現在の盤面をDB登録する（2手先読みとなる）
	}
	else {
		int i = pos[rand() % pos[9]];	//m_BoardDBに該当がない空き位置を乱数で選択
		MakeMove(i, you);			//乱数手を打つ
		RecMove(i);					//打手を記録
		return true;
	}
}

//盤面の登録−m_Board.m_Outcomeは勝者（+）、敗者（-）
void CMENACE::Register() {

	//現在の盤面（m_Board）と同様のものが無ければ登録する
	if(!Search(m_Board))
		m_BoardDB.push_back(m_Board);
}

//一手前に戻し相手敗者（-）をm_Outcomeへ代入する
void CMENACE::PushBack() {

	//注意：IsOver()の結果、m_Outcomeが正の場合だけ適用する
	//AutMove()の「投了」はm_Outcomeが負となる
	m_Board.m_Pos[m_LastMove] = 0;
	if(m_Board.m_Outcome == FIRST)
		m_Board.m_Outcome = -SECOND;
	else
		m_Board.m_Outcome = -FIRST;
}

//盤面の検索（戻り値：0−該当なし、1−先手勝利、2−後手勝利、-1−先手敗北、-2−後手敗北）
//m_Board.m_Posの盤を4回右に90度回転、反転させて4回右に90度回転、
//更に先手、後手を入れ替え（評価は逆となる）て同様の検索を行う
int CMENACE::Search(BOARD work) {

	int found = 0;						//初期値(該当なし）
	//登録された盤面を検索する
	for(m_Itr = m_BoardDB.begin(); m_Itr != m_BoardDB.end(); m_Itr++) {
		for(int j = 0; j < 4; j++) {	//4回回して元に戻す
			if(!BoardCmp(work.m_Pos, m_Itr->m_Pos)) {
				found = m_Itr->m_Outcome;
				return found;
			}
			Rotate(work.m_Pos);
		}
		Mirror(work.m_Pos);				//盤面を反転する
		for(int j = 0; j < 4; j++) {	//4回回して元に戻す
			if(!BoardCmp(work.m_Pos, m_Itr->m_Pos)) {
				found = m_Itr->m_Outcome;
				return found;
			}
			Rotate(work.m_Pos);
		}
	}
	return found;
}

//盤面比較（戻り値はstrcmp(char*, char*)と同じ）
int CMENACE::BoardCmp(char* bd1, char* bd2) {

	int i;
	for(i = 0; i < 9; i++) {
		if(bd1[i] != bd2[i])
			break;
	}
	return (bd1[i] - bd2[i]);
}

//盤面を右に90度回転させる（検索効率向上目的）
//	-------		-------
//	|0|1|2|		|6|3|0|
//	-------		-------
//	|3|4|5|→	|7|4|1|
//	-------		-------
//	|6|7|8|		|8|5|2|
//	-------		-------
void CMENACE::Rotate(char* bd) {

	char c = bd[0];
	bd[0] = bd[6];
	bd[6] = bd[8];
	bd[8] = bd[2];
	bd[2] = c;
	c = bd[3];
	bd[3] = bd[7];
	bd[7] = bd[5];
	bd[5] = bd[1];
	bd[1] = c;
}

//盤を左右反転させる
//	-------		-------
//	|0|1|2|		|2|1|0|
//	-------		-------
//	|3|4|5|→	|5|4|3|
//	-------		-------
//	|6|7|8|		|8|7|6|
//	-------		-------
void CMENACE::Mirror(char* bd) {

	swap(bd[0], bd[2]);
	swap(bd[3], bd[5]);
	swap(bd[6], bd[8]);
}

//DBのデータ数を返す
int CMENACE::DBSize() {

	return m_BoardDB.size();
}

/*
//DBのデータをm_Posをキーに昇順でソートする
//BDCmpは昇順用比較関数（x < y)でTRUEとなる
bool BDCmp(const BOARD& x, const BOARD& y) {
	int i;
	for(i = 0; i < 9; i++) {
		if(x.m_Pos[i] != y.m_Pos[i])
			break;
	}
	return ((x.m_Pos[i] - y.m_Pos[i]) < 0);
}
//BDCmpを使ってsort関数を使う
void CMENACE::DBSort() {

	sort(m_BoardDB.begin(), m_BoardDB.end(), BDCmp);
}
*/
/*
//DBのデータを表示する
void CMENACE::DBShow() {

	for(m_Itr = m_BoardDB.begin(); m_Itr != m_BoardDB.end(); m_Itr++) {
		cout << "-------" << endl;
		for(int i = 0; i < 9; i++) {
			char c;
			switch(m_Itr->m_Pos[i]) {
			case 0:
				c = ' ';
				break;
			case 1:
				c = 'O';
				break;
			case 2:
				c = 'X';
				break;
			}
			cout << "|" << c;
			if((i % 3) == 2)
				cout << "|" << endl << "-------" << endl;
		}
	}
}
*/

bool CMENACE::BoardWrite(char* fn) {

	BOARD board;
	int num = DBSize();
	HANDLE hFile = CreateFile(fn, GENERIC_WRITE,
				FILE_SHARE_WRITE, NULL,
				OPEN_ALWAYS, NULL, NULL);	//ファイルを書込でオープン
	if(hFile != INVALID_HANDLE_VALUE) {		//オープンできたか
		DWORD dwWrite;
		//先ずintでデータ数を書き込む
		WriteFile(hFile, &num, sizeof(int), &dwWrite, NULL);
		m_Itr = m_BoardDB.begin();
		//次にBOARDの配列を書き込む
		for(int i = 0; i < num; i++, m_Itr++) {
			board = *m_Itr;
			WriteFile(hFile, &board, sizeof(board), &dwWrite, NULL);
		}
		CloseHandle(hFile);				//ファイルのクローズ
		return TRUE;
	}
	else
		return FALSE;
}

bool CMENACE::BoardRead(char* fn) {

	BOARD board;
	int num;
	HANDLE hFile = CreateFile(fn, GENERIC_READ,
				FILE_SHARE_READ, NULL,
				OPEN_EXISTING, NULL, NULL);	//ファイルを読込でオープン
	if(hFile != INVALID_HANDLE_VALUE) {		//オープンできたか
		m_BoardDB.clear();					//m_BoardDBのデータを空にする
		DWORD dwRead;
		//先ずint numにデータ数を読み込む
		ReadFile(hFile, &num, sizeof(int), &dwRead, NULL);
		m_Itr = m_BoardDB.begin();
		for(int i = 0; i < num; i++) {
			ReadFile(hFile, &board, sizeof(board), &dwRead, NULL);
			m_BoardDB.push_back(board);
		}
		CloseHandle(hFile);				//ファイルのクローズ
		return TRUE;
	}
	else
		return FALSE;
}
