// 1. Drawing private void drawDbg(gamestate state, Font font, String text, SolidBrush brush) { int x = state.Column; int y = state.Row; int xx = 8 + x * state.squares[x, y].SizeH; int yy = 8 + y * state.squares[x, y].SizeV; String score = ""; if (Math.Abs(state.AIValue) == gameboard.WINNING_SCORE) { if (state.AIValue == -gameboard.WINNING_SCORE) { score = "WIN 2"; } else { score = "WIN 1"; } } else { score = state.AIValue.ToString(); } device.DrawString(score + text, font, brush, xx, yy); dbgStateArray[x, y] = state; }
//Constructors public gamestate(gamestate ParentState, int column, int sizeW, int sizeH) { // Set parent state parent = ParentState; //Set ID id = parent.ID; id += "-"; id += column.ToString(); // Set squares squares = new Square[sizeW, sizeH]; for (int x = 0; x < sizeW; x++) { for (int y = 0; y < sizeH; y++) { squares[x, y] = new Square(60, ParentState.squares[x, y].Owner); } } // Set columns used columnused = column; rowused = 0; while (rowused < sizeH) { if (squares[columnused, rowused].Owner != 0) { break; } rowused++; } rowused--; } // Used to set a state based on a parent
public void DumpState(gamestate state) { Bitmap bmp = new Bitmap(widthPixels + 2 * this.spacer, heightPixels + 2 * this.spacer); Graphics device = Graphics.FromImage(bmp); draw(device); gamestate currState = state; Font font = new Font("Arial", 30); String fileName = ""; SolidBrush sb1 = new SolidBrush(Color.Red); SolidBrush sb2 = new SolidBrush(Color.Blue); int cnt = 1; while (currState != null) { currState = currState.Parent; cnt++; } currState = state; while (currState != null) { //device.FillEllipse(SquareBrush, , , this.squares[x, y].SizeH - 16, this.squares[x, y].SizeV - 16); int x = currState.Column; int y = 0; while (y < sizeHeight) { if (currState.squares[x, y].Owner != 0) { break; } y++; } int xx = this.Spacer + 8 + x * this.squares[x, y].SizeH; int yy = this.Spacer + 8 + y * this.squares[x, y].SizeV; SolidBrush sb; if (currState.squares[x, y].Owner == 1) { sb = sb1; } else { sb = sb2; } device.DrawString((cnt - 1).ToString(), font, sb, xx, yy); fileName += currState.Column.ToString(); currState = currState.Parent; cnt--; } bmp.Save("A" + fileName + ".png"); bmp.Dispose(); }
//1.2 Supportive function that clears all AIStates. public void ClearAIStates(gamestate state) { for (int i = 0; i < state.children.Count; i++) { ClearAIStates(state.children[i]); } state.children.Clear(); }
//C1. Supportive function that checks all moves in a gamestate // public List<int> GenerateListOfMoves(gamestate StateOfBoard) // { // return list; //} //C2. Supportive function that returns the first empty gamestate on a given depth //C3. Supportive function that places the token in the new Gamestate public void PlaceToken(gamestate Board, int Column, int Player) { for (int i = sizeHeight - 1; i >= 0; i--) { if (Board.squares[Column, i].Owner == 0) { Board.squares[Column, i].Owner = Player; break; } } }
private void cbDebug_CheckedChanged(object sender, EventArgs e) { if (cbDebug.Checked) { currentDbgState = gameboard.AIRootState; if (dbgStateArray == null) { dbgStateArray = new gamestate[gameboard.Width, gameboard.Height]; } } drawall(); }
//=============================// // AI Methods // //=============================// //1. The general movement function, just places the square and times the thinking time. public void placeSquareAI(Label label1, int ColumnUsed, int MaxDepth) { //Start timer Stopwatch Timer = new Stopwatch(); Timer.Start(); //Check if moves can be made if (checkFull()) { MessageBox.Show("Table is full!"); } else { // Clear created gamestates if (AIRootState != null) { ClearAIStates(AIRootState); } //determine column // Create gamestate AIRootState = new gamestate(squares, ColumnUsed, sizeWidth, sizeHeight); // Determine AI move AIMove AI = new AIMove(); AI = RecursiveMiniMaxAI(0, MaxDepth, AIRootState, 2); //Recursive Mini Max Move //Place token int row = placeTokenAI(AI.Move); //Check win if (checkWin(AI.Move, row, activeplayer, squares, 3)) { gamewon(); } //Change player activeplayer = 1; } Timer.Stop(); label1.Text = "Time elapsed: " + Timer.ElapsedMilliseconds.ToString() + " ms"; Timer.Reset(); }
private void pnlMain_MouseClick(object sender, MouseEventArgs e) { if (gameboard == null) { return; } decimal X = (e.X) / gameboard.squares[0, 0].SizeH; decimal Y = (e.Y) / gameboard.squares[0, 0].SizeV; X = Math.Floor(X); Y = Math.Floor(Y); int xSquare = Convert.ToInt32(X); int ySquare = Convert.ToInt32(Y); if (cbDebug.Checked) { if (dbgStateArray[xSquare, ySquare] != null) { currentDbgState = dbgStateArray[xSquare, ySquare]; } } else if (gameboard.gameRun) { //Check Correct click if (gameboard.CheckCorrectClick(xSquare)) { gameboard.placeSquare(xSquare, ySquare, gameboard.activePlayer); //Player 2 moves (AI move) if (gameboard.activePlayer == 2) { gameboard.placeSquareAI(label1, xSquare, (int)nudMaxDepth.Value); } button2.Enabled = true; } } drawall(); }
//C4. Evaluates the board for a given gamestate public int EvaluateBoard(gamestate Board, int ColumnUsed) { //Determine the highest placed fiche and call checkwin on that fiche for (int y = 0; y < sizeHeight; y++) { if (Board.squares[ColumnUsed, y].Owner != 0) { int score = GetScore(ColumnUsed, y, Board.squares[ColumnUsed, y].Owner, Board.squares); if (Board.squares[ColumnUsed, y].Owner == 1) { return(score); } else { return(-score); } } } return(0); }
//C. AI movement three, recursive minimax public AIMove RecursiveMiniMaxAI(int Depth, int MaxDepth, gamestate StateOfBoard, int Player) { AIMove ReturnMove = new AIMove(); //BestScore & Move AIMove BestMove = new AIMove(-1, 0); if (Player == 2) // Minimizing { BestMove.Score = 99999; } else { BestMove.Score = -99999; } if (Depth == MaxDepth) //We are at the bottom of the search space and evaluate this level { ReturnMove.Score = EvaluateBoard(StateOfBoard, StateOfBoard.Column); ReturnMove.Move = StateOfBoard.Column; } else { int score = EvaluateBoard(StateOfBoard, StateOfBoard.Column); if (Math.Abs(score) == WINNING_SCORE) { ReturnMove.Score = score; ReturnMove.Move = StateOfBoard.Column; } else { //Check for possible moves List <int> MoveList = new List <int>(); List <AIMove> EqualGoodReturns = new List <AIMove>(); for (int column = 0; column < squares.GetLength(0); column++) { if (StateOfBoard.squares[column, 0].Owner == 0) { MoveList.Add(column); } } //Execute a minimax on each child for (int k = 0; k < MoveList.Count; k++) { //Make a move and create a new gamestate gamestate newState = new gamestate(StateOfBoard, MoveList[k], sizeWidth, sizeHeight); StateOfBoard.children.Add(newState); //Place a token in the new Gamestate PlaceToken(newState, MoveList[k], Player); //Call minimax on the child ReturnMove = RecursiveMiniMaxAI(Depth + 1, MaxDepth, newState, Player == 1 ? 2 : 1); newState.AIValue = ReturnMove.Score; if (Player == 2) // Minimizing { if (ReturnMove.Score < BestMove.Score) { EqualGoodReturns.Clear(); EqualGoodReturns.Add(new AIMove(MoveList[k], ReturnMove.Score)); BestMove.Score = ReturnMove.Score; BestMove.Move = MoveList[k]; //Changed this from ReturnMove } else if (ReturnMove.Score == BestMove.Score) { EqualGoodReturns.Add(new AIMove(MoveList[k], ReturnMove.Score)); } } else { //Evaluate return if (ReturnMove.Score > BestMove.Score) { EqualGoodReturns.Clear(); EqualGoodReturns.Add(new AIMove(MoveList[k], ReturnMove.Score)); BestMove.Score = ReturnMove.Score; BestMove.Move = MoveList[k]; //Changed this from ReturnMove } else if (ReturnMove.Score == BestMove.Score) { EqualGoodReturns.Add(new AIMove(MoveList[k], ReturnMove.Score)); } } } if (EqualGoodReturns.Count == 1 || EqualGoodReturns.Count == 0) { ReturnMove = BestMove; } else { Random rnd = new Random(); ReturnMove = EqualGoodReturns[rnd.Next(EqualGoodReturns.Count)]; } } } //string Data2; //Data2 = ReturnMove.Score.ToString(); //Data2 += "/"; //Data2 += ReturnMove.Move.ToString(); //Trace.WriteLine("ReturnScore & Move: " + Data2 + " || At Depth " + Depth.ToString()); if (writeDbg) { if (Depth == MaxDepth && ReturnMove.Score != 0) { //for (int i = 0; i < Depth; i++) // dbgText += "\t"; gamestate currState = StateOfBoard; while (currState != null) { dbgText += "AI[" + currState.AIValue + "] COL[" + currState.Column + "]\t"; currState = currState.Parent; } //dbgText += Environment.NewLine; dbgText += "P:" + Player.ToString() + " Col:" + StateOfBoard.Column.ToString() + " BestMove:" + ReturnMove.Move.ToString() + " BestScore:" + ReturnMove.Score.ToString() + Environment.NewLine; if (ReturnMove.Score > 100 && false) { DumpState(StateOfBoard); } } } return(ReturnMove); }