/////////////////////////////////////////////////////////////////
//オセロゲーム（Othello.cs）
//原典：http://www.souzousha.iinaa.net/Source/Othello.txt
//オセロ（またはReversi）：https://en.wikipedia.org/wiki/Reversi
//【用語】
//Board - 盤
//Disks - 石
//Square - 升目
/////////////////////////////////////////////////////////////////
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.Reflection;		//Assemblyを使う為
using System.IO;	 			//StreamReader/Writerを使用するのに必要
using System.Text;	 			//Encoding.GetEncodingを使用するのに必要

namespace Othello
{
	//アプリケーションクラス
	public class App
	{
		[STAThread]
		public static void Main()
		{
			OthelloForm of = new OthelloForm();
			Application.Run(of);
		}
	}

	//Othelloクラス
	public partial class OthelloForm : Form
	{
		//ウィンドウコントロール
		Label label;									//ラベルコントロール
		RadioButton radioButton1, radioButton2;			//ラジオボタンコントロール
		Button startButton, passButton, exitButton, datButton;	//ボタンコントロール
		//表示関係
		public Image image2disp = new Bitmap(412, 412);	//表示用ビットマップ（幅412、高さ412）
		public Graphics gr_image;						//表示用ビットマップのグラフィックス
		Brush bD = new SolidBrush(Color.DarkGreen);		//ブラシ暗い緑色
		Brush bW = new SolidBrush(Color.White);			//		白色
		Brush bB = new SolidBrush(Color.Black);			//		黒色
		Brush bG = new SolidBrush(Color.Gray);			//		灰色
		Pen pB = new Pen(Color.Black, 1);				//ペン	黒色
		Pen pW = new Pen(Color.White, 1);				//		白色
		Pen pG = new Pen(Color.DarkGray, 1);			//		暗い灰色
		Pen p1 = new Pen(Color.White, 2);				//		白色（太さ2)
		Pen p2 = new Pen(Color.White, 4);				//		白色（太さ4)
		string pmpt = "先攻（黒石）、後攻（白石）を選択して、開始ボタンをクリックして下さい。";
		//オセロデータ関係
		byte[,] dataTable = new byte[8, 8];				//オセロ盤配列[y, x]（0-空、1-黒、2-白）
		int[,] EvalTable =								//評価用テーブル[y, x]（8 x 8の盤に対応）
				{{200,  6, 70, 30, 30, 70,  6, 200},	//戦略的重要度を数値で表している。
				{  6,  5,  7,  6,  6,  7,  5,   6},		//四隅が200で最も高く、四隅の一つ手前等
			 	{ 70,  7, 40, 30, 30, 40,  7,  70},		//要所が高く設定されている。
				{ 30,  6, 30,  1,  1, 30,  6,  30},		//本プログラムの最も重要なノウハウ部分。
				{ 30,  6, 30,  1,  1, 30,  6,  30},
				{ 70,  7, 40, 30, 30, 40,  7,  70},
				{  6,  5,  7,  6,  6,  7,  5,   6},
				{200,  6, 70, 30, 30, 70,  6, 200}};
		//オセロ盤の特定位置の周囲8方向を表すテーブルで第1引数は↓の方向(0 - 7）を表し、
		//第2引数（0 - 1)はy座標、x座標の差分を表している。
		int[,] vectorTable								//指定点から伸ばす方向表
			= {	{-1, -1 }, {-1, 0 }, {-1, 1},			//上（左、中、右）
				{ 0, -1 }, { 0, 1 },					//左、右
				{ 1, -1 }, { 1, 0 }, { 1, 1}};			//下（左、中、右）
		//戦績データ（Log.datを作成する為、追加）
		int Games, Wins, Loses;							//今回の勝負数、人間の勝数、人間の負数
		int GT_Games, GT_Wins, GT_Loses;				//その累積数

		//コンストラクター
		public OthelloForm()
		{
			//プログラムアイコンをフォームにつける
			Assembly myOwn = Assembly.GetEntryAssembly();
			this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);
			this.Size = new Size(560, 528);			//初期サイズ
			this.MinimumSize = new Size(530, 508);	//最小サイズ
			this.Text = "Othello";
			this.Load += Form_Load;
			this.MouseClick += MainForm_MouseClick;
		}

		/////////////////////////
		//ウィンドウイベント処理
		/////////////////////////
		//WM_CREATE処理
		private void Form_Load(object sender, EventArgs e)
		{
			//Labelの設定
			label = new Label();
			label.Location = new Point(10, 10);
			label.Width = 400;	//Othello盤面幅（400）
			label.Text = pmpt;	//プロンプト表示
			this.Controls.Add(label);
			//RadioButtonsの設定
			radioButton1 = new RadioButton();
			radioButton1.Location = new Point(10, label.Height + 10);
			radioButton1.Text = "先攻（黒）";
			this.Controls.Add(radioButton1);
			radioButton2 = new RadioButton();
			radioButton2.Location = new Point(radioButton1.Width + 10, label.Height + 10);
			radioButton2.Text = "後攻（白）";
			this.Controls.Add(radioButton2);
			//Buttons（開始、パス、終了、戦績）の設定
			startButton = new Button();
			startButton.Location = new Point(ClientSize.Width - startButton.Width - 10, 20);
			startButton.Text = "開始";
			startButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			startButton.Click += startButton_Click;
			this.Controls.Add(startButton);
			passButton = new Button();
			passButton.Location = new Point(ClientSize.Width - passButton.Width - 10, startButton.Height + 30);
			passButton.Text = "パス";
			passButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			passButton.Click += passButton_Click;
			passButton.Enabled = false;
			this.Controls.Add(passButton);
			exitButton = new Button();
			exitButton.Location = new Point(ClientSize.Width - exitButton.Width - 10, startButton.Height + passButton.Height + 40);
			exitButton.Text = "終了";
			exitButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			exitButton.Click += exitButton_Click;
			this.Controls.Add(exitButton);
			datButton = new Button();
			datButton.Location = new Point(ClientSize.Width - datButton.Width - 10, this.ClientSize.Height - datButton.Height - 10);
			datButton.Text = "戦績";
			datButton.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
			datButton.Click += datButton_Click;
			this.Controls.Add(datButton);
			//ビットマップimage2dispのGraphicsを取得
			gr_image = Graphics.FromImage(image2disp);
			gr_image.Clear(this.BackColor);
			this.Invalidate();	//フォームの再描画
			//ログファイルの読み込み
			LogRead();
		}

		//WM_L/RBUTTONDOWN処理
		private void MainForm_MouseClick(object sender, MouseEventArgs e)
		{
			//座標の指定範囲内か否かを判別し、外なら何もしない
			Rectangle rect = new Rectangle(20, 60, 400, 400);
			if(!rect.Contains(new Point(e.X, e.Y)))
				return;
			else if(!radioButton1.Checked && !radioButton2.Checked)	//ゲームが開始されていない
			{
				//ユーザーインストラクションを追加
				MessageBox.Show("ゲームは開始していません。\r\n先攻、後攻を指定して「開始」ボタンを押してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			else
			{
				//マウスポインター座標をオセロ盤座標（0 - 7 x 0 - 7）に変換
				person((e.X - 20) / 50, (e.Y - 60) / 50);
			}
		}

		//WM_PAINT処理
		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint(e);
			displayBoard();								//グリッドと盤面をimage2dispに描画
			e.Graphics.DrawImage(image2disp, 10, 50);	//ビットマップをPoint(10, 50)に表示
		}

		//WM_CLOSE処理
		protected override void OnFormClosing(FormClosingEventArgs e)
		{
			base.OnFormClosing(e);
			DialogResult dr = MessageBox.Show("終了しますか？", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
			if(dr == DialogResult.No)
			{
				e.Cancel = true;
			}
			else
			{
				LogWrite();				//勝敗データの更新
				//ビットマップimage2dispとGraphicsの開放（元は無かったので追加）
				gr_image.Dispose();		//image由来のGraphicsを開放
				image2disp.Dispose();	//imageを開放
			}
		}

		///////////////////////////
		//コントロールイベント処理
		///////////////////////////
		//「開始」ボタン処理
		private void startButton_Click(object sender, EventArgs e)
		{
			if(!radioButton1.Checked && !radioButton2.Checked)	//ゲームが開始されていない
			{
				//ユーザーインストラクションを追加
				MessageBox.Show("先攻、後攻の指定がありません。\r\n指定してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			InitTable();					//盤の初期化と石の初期配置
			this.Invalidate();				//フォームの再描画
			label.Text = "";				//ラベルの初期表示（pmpt）を消す
			startButton.Enabled = false;	//「開始」ボタンを無効化する
			passButton.Enabled = true;		//「パス」ボタンを有効化する
			radioButton1.Enabled = false;	//「先攻」を無効化する
			radioButton2.Enabled = false;	//「後攻」を無効化する
			//ユーザーインストラクションを追加
			string str =	"オセロゲームを開始します。\r\n" + 
							"あなたが手を打つには、オセロ盤上で石を置く位置をマウスでクリックしてください。\r\n" + 
							"あなたの手の後、ＰＣは自動的に石を打ちます。\r\n" +
							"あなたのご健闘を祈ります。";
			MessageBox.Show(str, "遊び方", MessageBoxButtons.OK, MessageBoxIcon.Information);
			if(radioButton2.Checked)		//後攻ならPCに先手を行わせる
				computer();
		}

		//パスボタン処理
		private void passButton_Click(object sender, EventArgs e)
		{
			//石を置ける場所があるかどうか判定し、置ける場合はパス解消。ない場合はパス。
			int diskColor = radioButton1.Checked ? 1 : 2;
			if(canPlace(diskColor))
				MessageBox.Show("置ける場所がありますのでパスできません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
			else
				computer();
		}

		//終了ボタン処理
		private void exitButton_Click(object sender, EventArgs e)
		{
			Close();
		}

		//戦績ボタン処理
		private void datButton_Click(object sender, EventArgs e)
		{
			//ユーザーインストラクション
			string str =	"【今回の戦績】\r\n" + 
							"ゲーム数\t\t：" + Games.ToString() + 
							"\r\nプレーヤー勝数\t：" + Wins.ToString() + 
							"\r\nＰＣ勝数\t\t：" + Loses.ToString() + 
							"\r\n引分数\t\t：" + (Games - Wins - Loses).ToString() + 
							"\r\n\r\n【前回迄の累計】\r\n" + 
							"ゲーム数\t\t：" + GT_Games.ToString() + 
							"\r\nプレーヤー勝数\t：" + GT_Wins.ToString() + 
							"\r\nＰＣ勝数\t\t：" + GT_Loses.ToString() +
							"\r\n引分数\t\t：" + (GT_Games - GT_Wins - GT_Loses).ToString();
			MessageBox.Show(str, "戦績の表示", MessageBoxButtons.OK, MessageBoxIcon.Information);
		}

		///////////////////////
		//オセロゲーム関連処理
		///////////////////////
		//コンピュータ側判定処理
		public void computer()
		{
			if(isOver())
				return;			//ゲーム終了なら何もしない
			int num;			//裏返すことが出来る石の数
			byte diskColor = radioButton1.Checked ? (byte)2 : (byte)1;	//先攻後攻判断
			int MaxEval = -1, Bestx = -1, Besty = -1, MaxNum = 0;		//MaxEval－最大戦略的評価値
			for(int y = 0; y < 8; y++)
			{	//オセロ盤の全桝目をチェック
				for(int x = 0; x < 8; x++)
				{
					int numTotal = 0;					//裏返すことが出来る石の数の合計
					for(int dir = 0; dir < 8; dir++)	//iは桝目(x, y)の周囲8方向
					{
						if((num = numberCount(x, y, diskColor, dir)) > 0)
							numTotal += num;			//裏返せる石の合計
					}
					if(numTotal > 0 &&					//裏返せる石があり、且つ
					(MaxEval < EvalTable[y, x] ||		//戦略評価が以前の最大値を超えるか（EvalTableは1から200）
					(MaxEval == EvalTable[y, x] && MaxNum < numTotal)))	//同じでも裏返せる石の数が多い場合
					{
						MaxEval = EvalTable[y, x];		//戦略評価を更新
						MaxNum = numTotal;				//裏返せる石の最大数更新
						Bestx = x; Besty = y;			//その升目の座標を記録
					}
				}
			}
			if (MaxEval < 0)
				MessageBox.Show("打つ場所がありません。パスします", "確認", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			else
				flipDisks(Bestx, Besty, diskColor);	//石を裏返す
		}

		//人間側判定処理
		public void person(int posx, int posy)
		{
			if(dataTable[posy, posx] != 0)			//空でない場合は何もしない
				return;
			byte diskColor = radioButton1.Checked ? (byte)1 : (byte)2;	//先攻後攻判断
			if(canPlace(posx, posy, diskColor))		//石を置くことができる場合
			{
				flipDisks(posx, posy, diskColor);	//裏返し処理
				Thread.Sleep(200);					//waitの代わり
				computer();							//コンピュータ側処理
			}
			else
				MessageBox.Show("そこには打てません。", "確認", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			isOver();		 		//ゲーム終了チェック
		}

		//石の配置初期設定
		private void InitTable()
		{
			//データテーブルを空き（0）で初期化
			for(int y = 0; y < 8; y++)
			{
				for (int x = 0; x < 8; x++)
				{
					dataTable[y, x] = 0;
				}
			}
			//中央に黒白石の初期配置
			dataTable[3, 3] = dataTable[4, 4] = 1;
			dataTable[4, 3] = dataTable[3, 4] = 2;
		}

		//空の桝目(x, y)から指定方向に裏返せる石の数を返す。置けない場合は0を返す。
		//引数：座標（x, y）、自石色（黒-1、白-2）、指定座標周囲の方向(0-7)
		public int numberCount(int x, int y, int diskColor, int dir)
		{
			if(dataTable[y, x] != 0)
				return 0;		//既に石が置かれている場合
			int oponentColor = 3 - diskColor;	//敵石の色（黒 - 1、白 - 2）
			int dx = vectorTable[dir, 1], dy = vectorTable[dir, 0];	//設定方向のx、y座標差分（vectorTable参照）
			int nextX = x + dx, nextY = y + dy;						//設定方向1個先のx、y座標
			if(nextX < 0 || nextX > 7 || nextY < 0 || nextY > 7)
				return 0;		//指定方向1個先が盤外の場合（一次チェック－無いと範囲外エラーとなる）
			int numStone = 0;	//(nextX, nextY)から指定方向へ続く敵石の数
			while(dataTable[nextY, nextX] == oponentColor)
			{
				numStone++;		//裏返すことが出来る敵石の数を増やす
				nextX += dx;	//指定方向に沿って（nextX, nextY)の桝目の先を
				nextY += dy;	//x、y座標差分を繰り返してチェック
				if(nextX < 0 || nextX > 7 || nextY < 0 || nextY > 7)
					return 0;	//盤外の場合
			}
			if(dataTable[nextY, nextX] == diskColor)	//敵石の連続が終わる座標をチェック
				return numStone;	//自石であれば裏返せる
			else
				return 0;			//それ以外（空-0）の場合
		}

		//桝目(x, y)に石を置くことができるかを判定
		public bool canPlace(int x, int y, int diskColor)
		{
			for(int dir = 0; dir < 8; dir++)
			{
				if(numberCount(x, y, diskColor, dir) > 0)
					return true;
			}
			return false;
		}

		//盤上に石を置く場所があるか判定
		public bool canPlace(int diskColor)
		{
			for(int y = 0; y < 8; y++)
			{
				for (int x = 0; x < 8; x++)
				{
					if(canPlace(x, y, diskColor))
						return true;
				}
			}
			return false;
		}

		//終了判定
		public bool isOver()
		{
			//既に試合終了しておれば何もしない
			if(startButton.Enabled == true)
				return true;;
			for(int diskColor = 1; diskColor < 3; diskColor++)
			{
				if(canPlace(diskColor))
					return false;	//敵味方一つでも石を置ける場合
			}
			//両者の石をカウントする
			int diskColor1 = 0, diskColor2 = 0;	//diskColor1が先攻（黒）、diskColor2が後攻（白）
			for(int y = 0; y < 8; y++)
			{
				for(int x = 0; x < 8; x++)
				{
					if(dataTable[y, x] == 1)
						diskColor1++;
					else if(dataTable[y, x] == 2)
						diskColor2++;
				}
			}
			//石の数で勝負判定
			if(diskColor1 == diskColor2)
				MessageBox.Show("引き分けです。", "結果", MessageBoxButtons.OK, MessageBoxIcon.Information);
			else if((diskColor1 > diskColor2 && radioButton1.Checked) || (diskColor2 > diskColor1 && radioButton2.Checked))
			{
				Wins++;		//勝数を更新
				MessageBox.Show("貴方の勝ちです。黒 = " + diskColor1 + ", 白 = " + diskColor2, "結果", MessageBoxButtons.OK, MessageBoxIcon.Information);
			}
			else
			{
				Loses++;	//負数を更新
				MessageBox.Show("ＰＣの勝ちです。黒 = " + diskColor1 + ", 白 = " + diskColor2, "結果", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			}
			Games++;						//ゲーム数を更新
			label.Text = pmpt;				//プロンプト表示
			radioButton1.Enabled = true;	//「先攻」を有効化する
			radioButton2.Enabled = true;	//「後攻」を有効化する
			radioButton1.Checked = radioButton2.Checked = false;	//先攻、後攻のチェックを外す
			startButton.Enabled = true;		//「開始」ボタンを有効化する
			passButton.Enabled = false;		//「パス」ボタンを無効化する
			return true;
	   }

		//石を裏返す
		public void flipDisks(int x, int y, byte diskColor)
		{
			byte [, ] Temp = new byte[8, 8];	//盤の状態を作業用盤コピー（注）
			//注：8方向でチェックはオリジナルで行うので、オリジナルの盤面を変えないように
			for(int i = 0; i < 8; i++)
			{
				for(int j = 0; j < 8; j++)
				{
					Temp[i, j] = dataTable[i, j];
				}
			}
			for(int dir = 0; dir < 8; dir++)	//作業用盤を8方向でチェック
			{	   
				int num = numberCount(x, y, diskColor, dir);
				if(num > 0)		//裏返すことが出来る石があるなら
				{
					int flipx = x, flipy = y;
					int dx = vectorTable[dir, 1], dy = vectorTable[dir, 0];	//指定方向（dir）のx、y差分
					for(int i = 0; i <= num; i++, flipx += dx, flipy += dy)	//裏返す桝目＋現在の位置
					{
						Temp[flipy, flipx] = diskColor;
					}
				}
			}
			for(int i = 0; i < 8; i++)			//解説：作業用盤面をデータテーブルにコピーする
			{
				for(int j = 0; j < 8; j++)
				{
					dataTable[i, j] = Temp[i, j];
				}
			}
			this.Invalidate();	//フォームの再描画
		}

		///////////////////
		//グラフィック表示
		///////////////////
		//石の表示
		private void displayStone(int x, int y, Brush b, Pen p, float R)
		{
			//クライアントエリアオフセット(10, 10)と盤面のオフセット(10, 10)で(20, 20)
			float X = x * 50 + 20, X1 = X + 1;
			float Y = y * 50 + 20, Y1 = Y + 1;
			gr_image.FillEllipse(bG, X1, Y1, R, R);	//石に厚さがあるような形で表示
			gr_image.FillEllipse(b , X, Y, R, R);
			gr_image.DrawEllipse(p , X, Y, R, R);
		}

		//盤面、桝目と石の表示
		public void displayBoard()
		{
			//(10,10)から背景(400 x 400)を暗い緑色で塗潰す
			gr_image.FillRectangle(bD, 10, 10, 400, 400);
			//太さ2の白色で内側に縦横線（格子）を引く
			for(float X = 60; X < 390; X += 50)
				gr_image.DrawLine(p1, X, 10, X, 410);
			for(float Y = 60; Y < 390; Y += 50)
				gr_image.DrawLine(p1, 10, Y, 410, Y);
			//太さ4の白色で外枠を描く
			gr_image.DrawRectangle(p2, 10, 10, 400, 400);
			//dataTable（0-空、1-黒石、2-白石）を描く
			for(int y = 0; y < 8; y++)
			{
				for (int x = 0; x < 8; x++)
				{	//白い石は大きく見えるので若干小さく表示
					if(dataTable[y, x] == 1)		//黒石
						displayStone(x, y, bB, pG, 30);
					else if(dataTable[y, x] == 2)	//白石
						displayStone(x, y, bW, pB, 29);
				}
			}
		}

		///////////////////
		//ログファイル処理
		///////////////////
		private bool LogRead()
		{
			string data = "";
		/*	出力UTF-8	：Encoding.UTF8;
			出力UTF-16LE：Encoding.Unicode;
			出力S-JIS	：GetEncoding("shift-jis")	*/
			using (StreamReader file = new StreamReader(".\\Log.dat", Encoding.UTF8, false))
			{
				//ファイルを読む
				data = file.ReadToEnd();
				if(data == "")	//ファイルが読み込めない場合
					return false;
				//data文のデータ化（GT_Games、GT_Winns、GT_Loses）
				string[] words = data.Split(',');
				if(!Int32.TryParse(words[0], out GT_Games))
					return false;
				if(!Int32.TryParse(words[1], out GT_Wins))
					return false;
				if(!Int32.TryParse(words[2], out GT_Loses))
					return false;
				// ファイルを閉じる
				file.Close();
			}
			return true;
		}

		private void LogWrite()
		{
			//累計を今回のデータで更新する
			GT_Games += Games;
			GT_Wins += Wins;;
			GT_Loses += Loses;
			string data = GT_Games.ToString() + "," + GT_Wins.ToString() + "," + GT_Loses.ToString();
		/*	出力UTF-8	：Encoding.UTF8;
			出力UTF-16LE：Encoding.Unicode;
			出力S-JIS	：GetEncoding("shift-jis")	*/
			using (StreamWriter file = new StreamWriter(".\\Log.dat", false, Encoding.UTF8))
			{
				//ファイルを書く
				file.Write(data);
				// ファイルを閉じる
				file.Close();
			}
		}
	}
}
