📄 form.cs
字号:
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System;
class TicTacToe : Form
{
#region Fields
public static readonly int Computer = -1;
public static readonly int Human = 1;
Board GameBoard;
SquareControl[,] aSquares;
private int CurrentPlayer; //Computer or human?
private int FirstPlayer; //Who gets first move?
private int ComputerFigure; //Does computer use X or O?
//worker thread for AI
Thread CalculateComputerMoveThread;
//Size of the board in one dimension
int iBoardSize;
//Used for sizing SquareControls
int iSquareSize;
//Used for mutually exclusive option storage
MenuItemInt miiFirstPlayer;
MenuItemInt miiComputerFigure;
//MenuItems to deactivate during game play
MenuItemInt[] amiiMenuItemsToDeactivate;
#endregion
public static void Main()
{
//TicTacToe form = new TicTacToe(3);
Application.Run(new TicTacToe(3));
}
public TicTacToe(int Size)
{
iBoardSize = Size;
//
// Create Menus
//
//Main menu
this.Menu = new MainMenu();
//File menu
MenuItem File = new MenuItem("&File");
MenuItem NewGame = new MenuItem("&NewGame", new EventHandler(OnFileNewGameClick),
Shortcut.CtrlN);
this.Menu.MenuItems.Add(File);
this.Menu.MenuItems[0].MenuItems.Add(NewGame);
//Options menu
MenuItem Options = new MenuItem("&Options");
MenuItemInt ComputerFirst = new MenuItemInt(TicTacToe.Computer,"Computer plays first",
new EventHandler(OnOptionsFirstPlayerClick));
MenuItemInt HumanFirst = new MenuItemInt(TicTacToe.Human, "Human plays first",
new EventHandler(OnOptionsFirstPlayerClick));
MenuItem Spacer = new MenuItem("-");
MenuItemInt ComputerX = new MenuItemInt(Board.X, "Computer plays X",
new EventHandler(OnOptionsComputerFigureClick));
MenuItemInt ComputerO = new MenuItemInt(Board.O, "Computer plays O",
new EventHandler(OnOptionsComputerFigureClick));
ComputerFirst.RadioCheck = true;
HumanFirst.RadioCheck = true;
ComputerX.RadioCheck = true;
ComputerO.RadioCheck = true;
this.Menu.MenuItems.Add(Options);
this.Menu.MenuItems[1].MenuItems.Add(ComputerFirst);
this.Menu.MenuItems[1].MenuItems.Add(HumanFirst);
this.Menu.MenuItems[1].MenuItems.Add(Spacer);
this.Menu.MenuItems[1].MenuItems.Add(ComputerX);
this.Menu.MenuItems[1].MenuItems.Add(ComputerO);
amiiMenuItemsToDeactivate = new MenuItemInt[] { ComputerX, ComputerO, ComputerFirst, HumanFirst };
//
// Set default options
//
FirstPlayer = TicTacToe.Human;
HumanFirst.Checked = true;
ComputerFigure = Board.X;
ComputerX.Checked = true;
CurrentPlayer = FirstPlayer;
miiFirstPlayer = HumanFirst;
miiComputerFigure = ComputerX;
//
//Create the board
//
aSquares = new SquareControl[iBoardSize, iBoardSize];
GameBoard = new Board(iBoardSize);
int i, j;
for(i = 0; i < iBoardSize; i++)
{
for (j = 0; j < iBoardSize; j++)
{
aSquares[i, j] = new SquareControl(i, j);
aSquares[i, j].Click += new EventHandler(SquareControl_Click);
aSquares[i, j].Parent = this;
}
}
//
//Sizing and positioning business
//
ClientSize = new Size(500, 500);
SizeAndPositionControls();
//avoid exceptions due to controls with zero size
this.MinimumSize = new Size(200, 225);
}
//==================================================================================
// Gameplay code
//==================================================================================
private void StartGame()
{
this.Reset();
//disable options MenuItems
foreach (MenuItemInt mii in amiiMenuItemsToDeactivate)
mii.Enabled = false;
//Make first computer move if necessary.
//Otherwise, just return and wait for user input.
if (CurrentPlayer == Computer)
{
CalculateComputerMove();
}
return;
}
//Apply a move to the GameBoard and the appropriate SquareControl
private void MakeMove(Move move)
{
//make move on the Board
GameBoard.MakeMove(CurrentPlayer, move);
//update SquareControls
if (CurrentPlayer == TicTacToe.Computer)
aSquares[move.iCol, move.iRow].iContents = ComputerFigure;
else
aSquares[move.iCol, move.iRow].iContents = -ComputerFigure;
aSquares[move.iCol, move.iRow].Invalidate();
//Check for endgame, switch players
GameBoard.CheckBoard();
CurrentPlayer = -CurrentPlayer;
//If game is over, update the form text
if (GameBoard.BoardState != GameState.InProgress)
{
UpdateStatus();
}
}
//update form text and re-enable options MenuItems
private void UpdateStatus()
{
foreach (MenuItemInt mii in amiiMenuItemsToDeactivate)
mii.Enabled = true;
if (this.GameBoard.BoardState == GameState.HumanWins)
this.Text = "You win!";
else if (this.GameBoard.BoardState == GameState.ComputerWins)
this.Text = "Computer wins!";
else if (this.GameBoard.BoardState == GameState.Draw)
this.Text = "Draw";
return;
}
//get ready for another round
private void Reset()
{
CurrentPlayer = FirstPlayer;
int i, j;
for (i = 0; i < iBoardSize; i++)
{
for (j = 0; j < iBoardSize; j++)
{
this.aSquares[i, j].iContents = Board.Empty;
this.aSquares[i, j].Invalidate();
this.GameBoard.aiBoard[i, j] = Board.Empty;
}
}
this.GameBoard.BoardState = GameState.InProgress;
this.GameBoard.iEmptySquares = iBoardSize * iBoardSize;
this.Text = "";
}
//==================================================================================
// Code related to making computer moves
//==================================================================================
//used for callback from the worker thread
delegate void MakeComputerMoveDelegate(Move move);
//Spawn a new thread and kickoff lookahead
private void CalculateComputerMove()
{
CalculateComputerMoveThread = new Thread(new ThreadStart(MakeComputerMove));
CalculateComputerMoveThread.IsBackground = true;
CalculateComputerMoveThread.Priority = ThreadPriority.Lowest;
CalculateComputerMoveThread.Name = "AI worker thread";
CalculateComputerMoveThread.Start();
}
//Entry point for worker thread
private void MakeComputerMove()
{
//TODO: initialize alpha and beta
int alpha = -2;
int beta = 2;
Move move = GetBestMove(this.GameBoard, this.CurrentPlayer, alpha, beta);
Object[] args = { move };
MakeComputerMoveDelegate MakeMoveDelegate = new MakeComputerMoveDelegate(this.MakeMove);
this.BeginInvoke(MakeMoveDelegate, args);
}
//Game AI code. Standard minimax search with alpha-beta pruning. Calculated in worker thread.
private Move GetBestMove(Board board, int CurrentPlayer, int alpha, int beta)
{
Move BestMove = null;
int iPossibleMoves = board.iEmptySquares;
//Start at a random square, so that if two or more moves
//are of equal rank, one of them will be chosen at random.
Random rand = new Random();
int i = rand.Next(board.iBoardSize);
int j = rand.Next(board.iBoardSize);
while (iPossibleMoves > 0)
{
//Loop through board.aiBoard to find next available move
do
{
if (i < iBoardSize - 1)
{
i++;
}
else if (j < iBoardSize - 1)
{
i = 0; j++;
}
else
{
i = 0; j = 0;
}
} while (board.aiBoard[i, j] != Board.Empty) ;
Move NewMove = new Move(i, j);
iPossibleMoves--;
//Make Move
Board NewBoard = new Board(board);
NewBoard.MakeMove(CurrentPlayer, NewMove);
NewBoard.CheckBoard();
if (NewBoard.BoardState == GameState.InProgress)
{
Move tempMove = GetBestMove(NewBoard, -CurrentPlayer, alpha, beta);
NewMove.iRank = tempMove.iRank;
}
else
{
//Assign a rank
if(NewBoard.BoardState == GameState.Draw)
NewMove.iRank = 0;
else
{
if (NewBoard.BoardState == GameState.ComputerWins)
NewMove.iRank = -1;
else
{
if (NewBoard.BoardState == GameState.HumanWins)
NewMove.iRank = 1;
}
}
}
//Is NewMove the best move encountered at this level so far?
if (BestMove == null ||
(CurrentPlayer == TicTacToe.Computer && NewMove.iRank < BestMove.iRank) ||
(CurrentPlayer == TicTacToe.Human && NewMove.iRank > BestMove.iRank))
{
BestMove = NewMove;
}
//Update alpha and beta, if necessary.
if (CurrentPlayer == TicTacToe.Computer && BestMove.iRank < beta)
beta = BestMove.iRank;
if (CurrentPlayer == TicTacToe.Human && BestMove.iRank > alpha)
alpha = BestMove.iRank;
//Check for pruning condition.
if (alpha > beta)
{
//prune this branch
iPossibleMoves = 0;
}
}
return BestMove;
}
//==================================================================================
// SquareControl Event handlers
//==================================================================================
private void SquareControl_Click(object Sender, EventArgs ea)
{
SquareControl Square = (SquareControl)Sender;
if (Square.iContents == Board.Empty && this.GameBoard.BoardState == GameState.InProgress
&& CurrentPlayer == TicTacToe.Human)
{
//Make the move
Move move = new Move(Square.iCol, Square.iRow);
MakeMove(move);
if (this.GameBoard.BoardState == GameState.InProgress)
CalculateComputerMove();
}
return;
}
//==================================================================================
// Menu event handlers
//==================================================================================
private void OnFileNewGameClick(object sender, EventArgs ea)
{
StartGame();
}
private void OnOptionsComputerFigureClick(object sender, EventArgs ea)
{
miiComputerFigure.Checked = false;
miiComputerFigure = (MenuItemInt)sender;
miiComputerFigure.Checked = true;
ComputerFigure = miiComputerFigure.iValue;
}
private void OnOptionsFirstPlayerClick(object sender, EventArgs ea)
{
miiFirstPlayer.Checked = false;
miiFirstPlayer = (MenuItemInt)sender;
miiFirstPlayer.Checked = true;
FirstPlayer = miiFirstPlayer.iValue;
}
//==================================================================================
// Methods related to displaying of the form
//==================================================================================
protected override void OnResize(EventArgs ea)
{
//Ensure that the client area is always square
int iSize = Math.Min(ClientSize.Height, ClientSize.Width);
ClientSize = new Size(iSize, iSize);
//resize and reposition child controls
SizeAndPositionControls();
this.Invalidate();
}
private void SizeAndPositionControls()
{
iSquareSize = (this.ClientSize.Width / iBoardSize) - (3 * (iBoardSize - 1) / iBoardSize);
int i, j;
for (i = 0; i < iBoardSize; i++)
{
for (j = 0; j < iBoardSize; j++)
{
aSquares[i, j].Size = new Size(iSquareSize, iSquareSize);
aSquares[i, j].Location = new Point(iSquareSize * i + 3 * i,
iSquareSize * j + 3 * j);
}
}
}
protected override void OnPaint(PaintEventArgs pea)
{
base.OnPaint(pea);
Graphics g = pea.Graphics;
Pen pen = new Pen(new SolidBrush(Color.Black), 10f);
int i;
for (i = 1; i < iBoardSize - 2; i++)
{
g.DrawLine(pen, new Point(i * iSquareSize + (i-1) * 3, 0),
new Point(i * iSquareSize + (i-1) * 3, this.ClientSize.Height));
}
}
}
//Custom MenuItem class used for mutually excluse options
class MenuItemInt : MenuItem
{
public int iValue;
public MenuItemInt(int integer, string str, EventHandler eh)
: base(str, eh)
{
iValue = integer;
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -