using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace Connect4 { public partial class Form1 : Form { private Graphics MainDisplay; private Bitmap MainBitmap; GameGrid.Gridvalues[] GridColour = new GameGrid.Gridvalues[3]; private ColEntry ColEntry1; private const int RowMax = 6; private const int ColMax = 7; private const int XStart = 30; private const int YStart = 80; private const int DiscScale = 50; private const int DiscSpace = 65; private const int VeryBad = -90000; private const int VeryGood = 90000; private GameGrid Grid = new GameGrid(RowMax, ColMax); int colNumber; private int currentColour = 1; bool isfinished = false; //checks to see if the game's finished // <---------- public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { MainBitmap = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); MainDisplay = Graphics.FromImage(MainBitmap); MainDisplay.Clear(Color.SteelBlue); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { MainBitmap.Dispose(); MainDisplay.Dispose(); } private void Form1_Paint(object sender, PaintEventArgs e) { e.Graphics.DrawImage(MainBitmap, 0, 0, MainBitmap.Width, MainBitmap.Height); } private void DrawGrid() { Brush b = Brushes.White; Brush bred = Brushes.Red; Brush byellow = Brushes.Yellow; int x = XStart; int y = YStart; int col = ColMax; int row = 0; for (col = 0; col < ColMax; col++) { for (row = (RowMax - 1); row >= 0; row--) { switch (Grid.State[row, col]) { case GameGrid.Gridvalues.None: MainDisplay.FillEllipse(b, x, y, DiscScale, DiscScale); y += DiscSpace; break; case GameGrid.Gridvalues.Red: MainDisplay.FillEllipse(bred, x, y, DiscScale, DiscScale); y += DiscSpace; break; case GameGrid.Gridvalues.Yellow: MainDisplay.FillEllipse(byellow, x, y, DiscScale, DiscScale); y += DiscSpace; break; } } x += DiscSpace; y = YStart; } Invalidate(); } private void StartButton_Click(object sender, EventArgs e) { //Enable the buttons this.groupColumn.Enabled = true; //clear the grid clearTheGrid(); //red persons turn currentColour = 1; //draw the grid DrawGrid(); //Write 'new game' this.richTextBox1.Text = "New Game\n" + richTextBox1.Text; //the game isn't finished yet isfinished = false; // <---------- } private void clearTheGrid() { for (int index = 0; index < RowMax; index++) { for (int index2 = 0; index2 < ColMax; index2++) { //for every position in the grid, make it 'none'(no counter) Grid.State[index, index2] = 0; } } } private void button1_Click(object sender, EventArgs e) { //Set the column number to 0 colNumber = 0; //try and put a disc in this column tryPutDisc(colNumber); } private void tryPutDisc(int colNumber) { ColEntry1 = Grid.AddDisc(colNumber, GameGrid.Gridvalues.None); if (ColEntry1.Possible) { //disable the buttons, just in case the user clicks twice before the computer moves groupColumn.Enabled = false; // <---------- //IF red if (currentColour == 1) { //Add the disc ColEntry1 = Grid.AddDisc(colNumber, GameGrid.Gridvalues.Red); richTextBox1.Text = ("You play column " + (colNumber + 1) + "\n") + richTextBox1.Text; DrawGrid(); finished(); } } else if (!ColEntry1.Possible) { richTextBox1.Text = "Full\n" + richTextBox1.Text; } if (!isfinished) // <---------- { // NodeValue nv; //make a new nodevalue nv = Grid.MachineMove(Grid.DepthMax); //assign the computers move choice to the nodeValue Grid.AddDisc(nv.Column, GameGrid.Gridvalues.Yellow); //add the disc to the selected column DrawGrid(); //redraw the grid richTextBox1.Text = ("The Computer plays column " + (nv.Column + 1) + "\n") + richTextBox1.Text; isfinished = finished(); //check to see if the game has finished if (!isfinished) //if it's not finished, carry on { groupColumn.Enabled = true; //re-enable the user's buttons } } } private bool finished() { bool full = false; long score; score = Grid.EvaluatePosition(); if (score < VeryBad) { richTextBox1.Text = "YOU WIN! \nTake that technology!\n" + richTextBox1.Text; this.groupColumn.Enabled = false; isfinished = true; return isfinished; } else if (score > VeryGood) { richTextBox1.Text = "COMPUTER WINS! \n...you lose\n" + richTextBox1.Text; this.groupColumn.Enabled = false; isfinished = true; return isfinished; } else { for (int index = 0; index < ColMax; index++) { if (Grid.State[0, index] == GameGrid.Gridvalues.None) { full = true; } } if (!full) { richTextBox1.Text = "DRAW\n" + richTextBox1.Text; this.groupColumn.Enabled = false; } else { return isfinished; } return isfinished; } } private void button2_Click(object sender, EventArgs e) { colNumber = 1; tryPutDisc(colNumber); } private void button3_Click(object sender, EventArgs e) { colNumber = 2; tryPutDisc(colNumber); } private void button4_Click(object sender, EventArgs e) { colNumber = 3; tryPutDisc(colNumber); } private void button5_Click(object sender, EventArgs e) { colNumber = 4; tryPutDisc(colNumber); } private void button6_Click(object sender, EventArgs e) { colNumber = 5; tryPutDisc(colNumber); } private void button7_Click(object sender, EventArgs e) { colNumber = 6; tryPutDisc(colNumber); } private void radioLow_CheckedChanged(object sender, EventArgs e) { Grid.DepthMax = 1; // <---------- } private void radioMedium_CheckedChanged(object sender, EventArgs e) { Grid.DepthMax = 3; // <---------- } private void radioHigh_CheckedChanged(object sender, EventArgs e) { Grid.DepthMax = 5; // <---------- } private void groupDifficulty_Enter(object sender, EventArgs e) // <---------- { } } } //------------------------------------------------------ //End of Form1.cs //------------------------------------------------------ //------------------------------------------------------ // Grid.cs //------------------------------------------------------ using System; using System.Collections.Generic; using System.Text; namespace Connect4 { class GameGrid { public enum Gridvalues : int { None, Yellow, Red }; public Gridvalues[,] State; private int depthMax = 1; // assume depth 1 unless amended public int DepthMax { get { return depthMax; } set { depthMax = value; } } private int RowMax; private int ColMax; public GameGrid(int rowMax, int colMax) { RowMax = rowMax; ColMax = colMax; State = new Gridvalues[RowMax, ColMax]; // State initially empty Clear(); } // Set all disc entries in the current game state to empty (none) public void Clear() { for (int row = 0; row < RowMax; row++) for (int col = 0; col < ColMax; col++) State[row, col] = Gridvalues.None; } // Add a disc to the selected column. Search up the column from the lowest position // until a free row is found. If top is reached then the column is full and the result // possible entry returns false public ColEntry AddDisc(int col, Gridvalues disc) { int row = 0; bool Complete = false; ColEntry result = new ColEntry(); while (!Complete) { if (row == RowMax) { result.Possible = false; Complete = true; } else { if (State[row, col] == Gridvalues.None) { State[row, col] = disc; result.Possible = true; result.Row = row; Complete = true; } else row++; } } return result; } public NodeValue MachineMove(int depth) { GameGrid tmpState = new GameGrid(RowMax, ColMax); Gridvalues disc; NodeValue Best = new NodeValue(-10000000); NodeValue Candidate = new NodeValue(-10000000); ColEntry NewDisc; if (depth % 2 == 1) { Best.Score = -1000000; disc = Gridvalues.Yellow; // computer move } else { Best.Score = 1000000; disc = Gridvalues.Red; // human move } for (int col = 0; col < ColMax; col++) { CopyState(this, tmpState); NewDisc = tmpState.AddDisc(col, disc); if (NewDisc.Possible) { // test for termination of recursion if (depth < DepthMax) Candidate = MachineMove(depth + 1); else { Candidate.Score = tmpState.EvaluatePosition(); Candidate.Column = col; } UpdateBest(Best, Candidate, depth, col); } } return Best; } // Make a copy of the current game position private void CopyState(GameGrid Original, GameGrid Copy) { for (int row = 0; row < RowMax; row++) for (int col = 0; col < ColMax; col++) Copy.State[row, col] = Original.State[row, col]; } // If the score of the candediate is beeter than the current best, update it // Check to see if from human or machine perspective first // human: highest positive, machine: highest negative private void UpdateBest(NodeValue Best, NodeValue Candidate, int currentDepth, int col) { if (currentDepth % 2 == 1) { if (Candidate.Score > Best.Score) { Best.Score = Candidate.Score; Best.Column = Candidate.Column; } } else { if (Candidate.Score < Best.Score) { Best.Score = Candidate.Score; Best.Column = Candidate.Column; } } } public long EvaluatePosition() { // All Quads must be checked !! long Value = 0; // Current evaluation /* ' there are four types of quad packets to evaluate:- ' rows, columns, down diags and up diags giving 84 quads in total ' For each grid position, iterate every quad where 3 other ' partners are available, e.g. for rows, iterate every ' row up to column 5 */ // Rows: 4x6 = 24 in total for (int row = 0; row < RowMax; row++) for (int col = 0; col < ColMax - 3; col++) Value = Value + EvalQuad(row, col, 0, 1); // Columns: 3 x 7 = 21 in total for (int row = 0; row < RowMax - 3; row++) for (int col = 0; col < ColMax; col++) Value = Value + EvalQuad(row, col, 1, 0); // Down Diagonals: 3 x 4 = 12 in total for (int row = 3; row < RowMax; row++) for (int col = 0; col < ColMax - 3; col++) Value = Value + EvalQuad(row, col, -1, 1); // Up Diagonals: 3 x 4 = 15 in total for (int row = 0; row < RowMax - 3; row++) for (int col = 0; col < ColMax - 3; col++) Value = Value + EvalQuad(row, col, 1, 1); return Value; } private long EvalQuad(int Row, int Col, int DeltaRow, int DeltaCol) { int RedCount = 0; // ' Generate Counts for Discs in Quad int YellowCount = 0; int Rowel = Row; // Start at given grid coordinates int ColEl = Col; long Score = 0; // calculate number of red and yellow tokens within the quad for (int Element = 0; Element < 4; Element++) { switch (State[Rowel, ColEl]) { case Gridvalues.Red: RedCount++; break; case Gridvalues.Yellow: YellowCount++; break; case Gridvalues.None: break; } Rowel = Rowel + DeltaRow; // generate next quad element ColEl = ColEl + DeltaCol; } //' Analyse if (RedCount > 0 && YellowCount > 0) Score = 0; // neutral quad -- nobody can win here else { if (YellowCount > 0) switch (YellowCount) { case 1: Score = 1; break; // OK to start a new quad if nothing else available case 2: Score = 10; // Two together is an advantage break; case 3: Score = 100; // 3 together -- almost there break; case 4: Score = 100000; // Winning position -- better than anything break; } else switch (RedCount) { case 1: Score = -2; break; // Not Too bad -- opponent tokens must go somewhere! case 2: Score = -21; // Two together - watch out break; case 3: Score = -201; // 3 together -- danger break; case 4: Score = -300001; // Defeat!!! break; } } // case of all empty quad is neutral (0) and handled implicitly return Score; } } } //------------------------------------------------------ //End of Form1.cs //------------------------------------------------------ //------------------------------------------------------ // Grid.cs //------------------------------------------------------ using System; using System.Collections.Generic; using System.Text; namespace Connect4 { class NodeValue { private long score; public long Score { get { return score; } set { score = value; } } private int col; public int Column { get { return col; } set { col = value; } } public NodeValue(int initial) { Score = initial; Column = -1; // impossible column } } }