/////////////////////////////////////////////////////////////////////////////////////////
//阿弥陀籤ゲーム（GhostLeg.cs）
//あみだくじ：https://ja.wikipedia.org/wiki/%E3%81%82%E3%81%BF%E3%81%A0%E3%81%8F%E3%81%98
/////////////////////////////////////////////////////////////////////////////////////////
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;		//Assemblyを使う為
using System.Resources;			//GhostLeg.resourcesを使う為

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

	//GhostLegFormクラス
	public partial class GhostLegForm : Form
	{
		//アミダクジゲーム関連フィールド
		private Amidakuji adk = new Amidakuji();		//Amidakujiクラスインスタンス
		private int numPlayers = 0;						//縦棒数
		private int numSteps = 0;						//段数
		private int numWin = 0;							//当たりの縦棒
		private bool selecting = false;					//横棒処理中フラグ
		private int BarS = -1, BarE = -1, StepSel = -1;	//夫々横棒の始点、終点、その段数（-1は未選択を意味する）
		//操作用ウィンドウコントロール
		Label label;									//ラベルコントロール
		Button initButton, startButton, goalButton, exitButton;	//ボタンコントロール
		//ゲーム用ウィンドウコントロール（注：複数の当たりが必要な場合はRadioButtonから変更してください。）
		Button[] Players;								//縦棒の競技者
		RadioButton[] Goals;							//縦棒のゴール（当たりは一つだけ）
		//表示関係
		private Image image2disp = new Bitmap(32, 32);	//表示用ビットマップ（初期値：幅32 x 1、高さ32 x 1）
		private Graphics gr_image;						//表示用ビットマップのグラフィックス
		private Image[] GLimg = new Image[9];			//アミダクジのビットマップデータ
		//UIプロンプト
		private string pmpt1 = "「サイズ設定」ボタンで縦棒数と段数を設定してください。";
		private string pmpt2 = "「開始」ボタンを押し、隣接する縦棒をマウスクリックして横棒を引いてください。";
		private string pmpt3 = "横棒を引き終えたら、「ゴール設定」ボタンを押してゴールを設定してください。";
		private string pmpt4 = "各縦棒のボタンを押して当落を確認してください。";

		//コンストラクター
		public GhostLegForm()
		{
			//プログラムアイコンをフォームにつける
			Assembly myOwn = Assembly.GetEntryAssembly();
			this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);
			//アミダクジビットマップをImage配列（GLimg）に読み込む
			ResourceManager rm = new ResourceManager("GhostLeg", myOwn);	//ResourceManagerインスタンスの作成
			for(int i = 0; i < 9; i++)
			{
				GLimg[i] = (Image)rm.GetObject("GhostLeg" + i.ToString());
			}
			//アミダクジフォームの設定
			this.Size = new Size(380, 400);			//初期サイズ（4列 X 8段）
			this.MinimumSize = new Size(380, 400);	//最小サイズ（4列 X 8段）
			this.Text = "Ghost Leg（あみだくじ）";
			this.Load += Form_Load;
			this.MouseClick += MainForm_MouseClick;
		}

		/////////////////////////
		//ウィンドウイベント処理
		/////////////////////////
		//WM_CREATE処理
		private void Form_Load(object sender, EventArgs e)
		{
			//Labelの設定
			label = new Label();
			label.BorderStyle = BorderStyle.Fixed3D;
			label.AutoSize = false;
			label.Width = 260;
			label.Height = 32;
			label.Location = new Point(10, this.ClientSize.Height - label.Height - 10);
			label.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
			label.Text = pmpt1;	//プロンプト表示
			this.Controls.Add(label);
			initButton = new Button();
			initButton.Location = new Point(ClientSize.Width - initButton.Width - 10, 10);
			initButton.Text = "サイズ設定";
			initButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			initButton.Click += initButton_Click;
			this.Controls.Add(initButton);
			startButton = new Button();
			startButton.Location = new Point(ClientSize.Width - startButton.Width - 10, initButton.Height + 20);
			startButton.Text = "開始";
			startButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			startButton.Click += startButton_Click;
			startButton.Visible = false;
			this.Controls.Add(startButton);
			goalButton = new Button();
			goalButton.Location = new Point(ClientSize.Width - goalButton.Width - 10, initButton.Height + startButton.Height + 30);
			goalButton.Text = "ゴール設定";
			goalButton.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
			goalButton.Click += goalButton_Click;
			goalButton.Visible = false;
			this.Controls.Add(goalButton);
			exitButton = new Button();
			exitButton.Location = new Point(ClientSize.Width - exitButton.Width - 10, this.ClientSize.Height - exitButton.Height - 15);
			exitButton.Text = "終了";
			exitButton.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
			exitButton.Click += exitButton_Click;
			this.Controls.Add(exitButton);
			//ビットマップimage2dispのGraphicsを取得
			gr_image = Graphics.FromImage(image2disp);
		}

		//WM_L/RBUTTONDOWN処理
		private void MainForm_MouseClick(object sender, MouseEventArgs e)
		{
			//横棒選択中でなければ何もしない
			if(!selecting)
				return;
			//座標の指定範囲内か否かを判別し、外なら何もしない
			Rectangle rect = new Rectangle(10, 30, numPlayers * 32, numSteps * 32);
			if(!rect.Contains(new Point(e.X, e.Y)))
				return;
			else	//BarS（始点）、BarE（終点）、 StepSel（その段数）
			{
				if(BarS == -1)					//全く選択されていない状態
				{
					BarS = (e.X - 10) / 32;		//始点x座標をBarSに設定
					StepSel = (e.Y - 30) / 32;	//始点y座標をStepSelに設定
					//始点のGLimgを選択状態に変更する
					gr_image.DrawImage(GLimg[3], BarS * 32, StepSel * 32);
					this.Invalidate();
				}
				else							//始点が選択されている状態
				{
					BarE = (e.X - 10) / 32;		//終点x座標をBarEに設定
					if(StepSel != (e.Y - 30) / 32)
					{							//異なる段数がクリックされた場合
						MessageBox.Show("始点と終点の段が異なります。", "不正な選択", MessageBoxButtons.OK, MessageBoxIcon.Error);
					}
					else if((BarS == BarE) || ((BarS - BarE) > 1) || ((BarE - BarS) > 1))
					{
						MessageBox.Show("始点と終点が同じか、または離れすぎています。", "不正な選択", MessageBoxButtons.OK, MessageBoxIcon.Error);
					}
					else
					{
						if(BarE > BarS)
						{
							if(!adk.StepL2R(BarS, StepSel))
							{
								MessageBox.Show("この位置は既に横棒が引かれています。", "不正な選択", MessageBoxButtons.OK, MessageBoxIcon.Error);
							}
						}
						else
						{
							if(!adk.StepR2L(BarS, StepSel))
							{
								MessageBox.Show("この位置は既に横棒が引かれています。", "不正な選択", MessageBoxButtons.OK, MessageBoxIcon.Error);
							}
						}
					}
					BarS = -1;
					BarE = -1;
					StepSel = -1;
					DrawGhostLeg();	//アミダクジを描画する
				}
			}
		}

		//WM_PAINT処理
		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint(e);
			e.Graphics.DrawImage(image2disp, 10, 30);	//ビットマップを表示
		}

		//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
			{
				//アミダクジImage配列を開放する
				for(int i = 0; i < 9; i++)
				{
					GLimg[i].Dispose();
				}
				//ビットマップimage2dispとGraphicsの開放
				gr_image.Dispose();		//image由来のGraphicsを開放
				image2disp.Dispose();	//imageを開放
			}
		}

		///////////////////////////
		//コントロールイベント処理
		///////////////////////////
		//「サイズ設定」ボタン処理
		private void initButton_Click(object sender, EventArgs e)
		{
			//入力ダイアログによる入力
			InputDlg inpDlg = new InputDlg();
			inpDlg.ShowDialog();
			if(inpDlg.m_Bars * inpDlg.m_Steps == 0)
			{
				MessageBox.Show("縦棒数と段数が入力されませんでした。", "確認", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				return;
			}
			else
			{
				//既存のimage由来のGraphicsとimageを開放
				gr_image.Dispose();
				image2disp.Dispose();
				//既存の縦棒関連コントロールの解放
				if(Players != null)
				{
					for(int i = numPlayers - 1; i >= 0; i--)
					{
						this.Controls.Remove(Players[i]);
						Players[i].Dispose();
						this.Controls.Remove(Goals[i]);
						Goals[i].Dispose();
					}
				}
				numPlayers = inpDlg.m_Bars;
				numSteps = inpDlg.m_Steps;
			}
			inpDlg.Dispose();	//入力ダイアログの解放
			//Amidakujiクラスの初期化
			adk.Generate(numPlayers, numSteps);
			//ウィンドウサイズの再設定
			this.Height = 144 + numSteps * 32;
			//ビットマップimage2dispとGraphicsの再設定
			image2disp = new Bitmap(numPlayers *32, numSteps * 32);
			gr_image = Graphics.FromImage(image2disp);
			gr_image.Clear(this.BackColor);
			this.Invalidate();	//フォームの再描画
			//プロンプト変更
			label.Text = pmpt2;
			//「サイズ設定」ボタンを隠し、「開始」ボタンを表示
			initButton.Visible = false;
			startButton.Visible = true;
		}

		//「開始」ボタン処理
		private void startButton_Click(object sender, EventArgs e)
		{
			//縦棒関連コントロールの再設定
			Players = new Button[numPlayers];
			Goals = new RadioButton[numPlayers];
			for(int i = 0; i < numPlayers; i++)
			{
				Players[i] = new Button();
				Players[i].Location = new Point(10 + i * 32, 10);
				Players[i].Size = new Size(32, 20);
				Players[i].Text = i.ToString();
				Players[i].Click += PlayerButton_Click;
				Players[i].Enabled = false;
				this.Controls.Add(Players[i]);
				Goals[i] = new RadioButton();
				Goals[i].CheckAlign = ContentAlignment.MiddleCenter;
				Goals[i].Location = new Point(10 + i * 32, numSteps * 32 + 30);
				Goals[i].Size = new Size(32, 20);
				Goals[i].AutoCheck = false;
				this.Controls.Add(Goals[i]);
			}
			//アミダクジの描画
			DrawGhostLeg();
			//「サイズ設定」ボタンを隠し、「開始」ボタンを表示
			initButton.Visible = false;
			startButton.Visible = true;
			//プロンプト変更
			label.Text = pmpt3;
			//「開始」ボタンを隠し、「ゴール設定」ボタンを表示
			startButton.Visible = false;
			goalButton.Visible = true;
			//横棒選択フラグの設定
			selecting = true;
			//フォームの再描画
			this.Invalidate();
		}

		//「Goal設定」ボタン処理
		private void goalButton_Click(object sender, EventArgs e)
		{
			if(BarS != -1)		//横棒選択の途中で「Goal設定」に遷移した場合の処理
			{
				BarS = -1;
				BarE = -1;
				StepSel = -1;
				DrawGhostLeg();	//アミダクジを描画する
			}
			//横棒選択フラグの解除
			selecting = false;
			//乱数でゴールを設定
			Random rand = new Random((int) DateTime.Now.Ticks & 0x0000FFFF);	//乱数の初期化
			numWin = rand.Next(numPlayers);
			Goals[numWin].Checked = true;
			for(int i = 0; i < numPlayers; i++)
				Players[i].Enabled = true;
			//プロンプト変更
			label.Text = pmpt4;
			//「ゴール設定」ボタンを隠し、「サイズ設定」ボタンを表示
			initButton.Visible = true;
			goalButton.Visible = false;
		}

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

		//縦棒ボタン処理
		private void PlayerButton_Click(object sender, EventArgs e)
		{
			int result;	//各縦棒のゴール番号用
			for(int i = 0; i < numPlayers; i++)
			{
				if((Button)sender == Players[i])
				{
					if((result = adk.CheckResult(i)) == numWin)
					{
						DrawGhostLeg();
						MessageBox.Show("Congratulations!\r\n" + i.ToString() + "番が当たりました!!!", "祝－当選", MessageBoxButtons.OK, MessageBoxIcon.Information);
						label.Text = pmpt1;	//プロンプト初期化
					}
					else
					{
						DrawGhostLeg();
						MessageBox.Show("残念！\r\n縦棒" + i.ToString() + "番の結果は" + result.ToString() + "番でした。", "結果発表", MessageBoxButtons.OK, MessageBoxIcon.Stop);
					}
				}
			}
		}

		//アミダクジ描画処理
		private void DrawGhostLeg(bool all = true, int col = 0, int row = 0)
		//all - (true)アミダクジ全体を描画する、(false)特定の位置（col, row）を描画する
		{
			if(all)				//アミダクジ全体を描画
			{
				for(int i = 0; i < numPlayers; i++)
				{
					for(int j = 0; j < numSteps; j++)
					{
						gr_image.DrawImage(GLimg[adk.GetData(i, j)], i * 32, j * 32);
					}
				}
			}
			else				//アミダクジの指定位置を描画
			{
				gr_image.DrawImage(GLimg[adk.GetData(col, row)], col * 32, row * 32);
			}
			this.Invalidate();	//フォームの再描画
		}
	}

	///////////////////////////
	//アミダクジ入力ダイアログ
	///////////////////////////
	class InputDlg : Form
	{
		//ダイアログのフィールド
		public int m_Bars;
		public int m_Steps;
		//縦棒数、段数の最小値、最大値の変更は此処で行う。
		private int BarMin = 4, BarMax = 8, StepMin = 8, StepMax = 16;

		//ダイアログのコントロール
		Button BtnOK, BtnCancel;
		Label BarLabel, StepLabel;
		ComboBox BarBox, StepBox;

		public InputDlg()
		{
			//ダイアログの属性設定
			this.Text = "アミダクジ入力ダイアログ";
			this.ClientSize = new Size(240, 120);
			this.MaximizeBox = false;		// 最大化ボタン
			this.MinimizeBox = false;		// 最小化ボタン
			this.ShowInTaskbar = false;		//タスクバー上非表示
			this.FormBorderStyle = FormBorderStyle.FixedDialog;		// 境界のスタイル
			this.StartPosition = FormStartPosition.CenterParent;	//親ダイアログ中央に配置
			this.Load += Form_Load;
		}

		//OnLoad処理
		private void Form_Load(object sender, EventArgs e)
		{
			//コントロールの属性設定
			BtnOK = new Button();
			BtnOK.Location = new Point(20, ClientSize.Height - BtnOK.Height - 10);
			BtnOK.Text = "OK";
			BtnOK.AutoSize = true;
			BtnOK.Click += new EventHandler(OnOK_Click);
			this.Controls.Add(BtnOK);

			BtnCancel = new Button();
			BtnCancel.Location = new Point(ClientSize.Width - BtnCancel.Width - 20, ClientSize.Height - BtnCancel.Height - 10);
			BtnCancel.Text = "Cancel";
			BtnCancel.AutoSize = true;
			BtnCancel.Click += new EventHandler(OnCancel_Click);
			this.Controls.Add(BtnCancel);

			BarLabel = new Label();
			BarLabel.Text = "縦棒数";
			BarLabel.Width = 80;
			BarLabel.Location = new Point(10, 10);
			this.Controls.Add(BarLabel);

			StepLabel = new Label();
			StepLabel.Text = "段数";
			StepLabel.Width = 80;
			StepLabel.Location = new Point(10, BarLabel.Height + 20);
			this.Controls.Add(StepLabel);

			BarBox = new ComboBox();
			BarBox.DropDownStyle = ComboBoxStyle.DropDownList;
			BarBox.Location = new Point(BarLabel.Width + 20, 10);
			BarBox.MaxDropDownItems = 5;
			for(int i = BarMin; i <= BarMax; i++)
				BarBox.Items.Add(i.ToString());
			BarBox.SelectedIndex = 0;
			this.Controls.Add(BarBox);

			StepBox = new ComboBox();
			StepBox.DropDownStyle = ComboBoxStyle.DropDownList;
			StepBox.Location = new Point(StepLabel.Width + 20, BarLabel.Height + 20);
			StepBox.MaxDropDownItems = 9;
			for(int i = StepMin; i <= StepMax; i++)
				StepBox.Items.Add(i.ToString());
			StepBox.SelectedIndex = 0;
			this.Controls.Add(StepBox);
		}

		//OKボタン処理
		protected void OnOK_Click(object sender, EventArgs e)
		{
			if(!Int32.TryParse((string)BarBox.SelectedItem, out m_Bars))
				MessageBox.Show("縦棒数の取得に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
			if(!Int32.TryParse((string)StepBox.SelectedItem, out m_Steps))
				MessageBox.Show("段数の取得に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
			Close();
		}

		//Cancelボタン処理
		protected void OnCancel_Click(object sender, EventArgs e)
		{
			m_Steps = m_Bars = 0;
			Close();
		}
	}

	///////////////////
	//アミダクジクラス
	///////////////////
	public class Amidakuji
	{
		//Amidakujiクラスフィールド
		public int Bars {get; set;}			//縦棒数－アミダクジ配列の列数（横棒数）
		public int Steps {get; set;}		//アミダクジの段数－アミダクジ配列の行数
		public int Goal {get; set;}			//当たりの列
		private int[,] Data = null;			//アミダクジデータ配列
		//コンストラクター
		public Amidakuji()					//引数無し
		{
			Init();
		}
		public Amidakuji(int col, int row)	//縦棒数と段数を指定
		{
			Init();
			Generate(col, row);
		}
		//初期化メソッド
		public void Init()
		{
			Bars = 1;						//アミダクジの縦棒数初期値
			Steps = 16;						//アミダクジの段数（横棒数）初期値
			Data = null;					//アミダクジデータ配列
		}
		//アミダクジ配列生成メソッド
		public void Generate(int col, int row)
		{
			Bars = col;
			Steps = row;
			if(Data != null)
				Data = null;				//アミダクジデータ配列の再初期化
			Data = new int[Bars, Steps];	//アミダクジデータ配列の設定
			for(int i = 0; i < Bars; i++)
				for(int j = 0; j < Steps; j++)
					Data[i, j] = 0;			//アミダクジデータ配列データの初期化
		}
		//アミダクジデータ配列データの取得
		public int GetData(int col, int row)
		{
			return Data[col, row];
		}
		//アミダクジに横棒を左から右に引く
		public bool StepL2R(int s, int row)	//s列と(s + 1)列の間に横棒を引く
		{
			if(row < 0 || row >= Steps)		//縦の範囲外
				return false;
			if(s < 0 ||						//横の範囲外
				s >= Bars - 1)				//一番右の列の右に横棒は引けない
				return false;
			if(Data[s, row] != 0)			//既に横棒が引かれている
				return false;
			Data[s, row] = 1;				//横棒の左半分
			Data[s + 1, row] = 2;			//横棒の右半分
			return true;
		}
		//アミダクジに横棒を右から左に引く
		public bool StepR2L(int s, int row)	//s列と(s - 1)列の間に横棒を引く
		{
			if(row < 0 || row >= Steps)		//縦の範囲外
				return false;
			if(s <= 0 ||					//一番左の列の左に横棒は引けない
				s >= Bars)					//横の範囲外
				return false;
			if(Data[s, row] != 0)			//既に横棒が引かれている
				return false;
			Data[s, row] = 2;				//横棒の左半分
			Data[s - 1, row] = 1;			//横棒の右半分
			return true;
		}
		//縦棒のアミダクジの結果を返す
		public int CheckResult(int c)
		{
			int result = c;
			//アミダクジ全体で赤棒を元に戻す
			for(int i = 0; i < Bars; i++)
			{
				for(int j = 0; j < Steps; j++)
				{
					switch(Data[i, j])
					{
						case 4:
							Data[i, j] = 0;
							break;
						case 5:
						case 8:
							Data[i, j] = 1;
							break;
						case 6:
						case 7:
							Data[i, j] = 2;
							break;
						default:
							break;
					}
				}
			}
			//経路を赤棒で示す
			for(int i = 0; i < Steps; i++)
			{
				switch(Data[result, i])
				{
					case 0:
						Data[result, i] = 4;
						break;
					case 1:
						Data[result, i] = 5;
						result++;
						Data[result, i] = 6;
						break;
					case 2:
						Data[result, i] = 7;
						result--;
						Data[result, i] = 8;
						break;
					default:
						break;
				}
			}
			return result;
		}
	}
}
