You are on page 1of 18

For this assignment, you are to build a program that plays the game, Connect

Four. While expert level play is not required, it should provide a challenge for
those who are only casual players of the game.

User Requirements
Connect Four is a two-player game played on a vertical board. The board is a
grid of six rows and seven columns. Players take turns dropping their game
pieces into one of the columns. Because the columns are vertical, each piece
drops to the lowest unoccupied cell in that column. Each player's goal is to get
four of his or her pieces in a straight line of adjacenct cells, either horizontally,
vertically, or diagonally. If the board is filled before either player wins, the
game is a draw. More information, including an animation of the game, can be
found on the game's Wikipedia page (Links to an external site.)Links to an
external site..

The user is a company that wants to market a version of the game. This
implementation needs to support user play against the program. It must allow
the user the option of having either the first or second play. It also needs to
support seven levels of play difficulty. Because this is a first release of the
game, expert play is not required, but each successive level should play a
stronger game. At the end of the game, it must report the winner, or that the
game was drawn. It only needs to support playing a single game (the
program can be restarted for a rematch), but it needs to leave the final board
visible until the user exits the program.

Finding the Best Play


At the heart of the game engine is an algorithm for trying to find the best
move. To understand this algorithm, we need to view the various board
configurations as forming a tree. (Note that this tree will not be implemented
as a data structure - it is just how we think about the relationships between
board configurations in order to guide our algorithm development.) Any board
configuration can form the root of a tree. Its children are the board
configurations that can be reached by making a single play from this
configuration. Thus, a node in this tree may have up to seven children. A
portion of the tree from the initial board configuration is as follows:
In principle, we could determine the best move by examining this entire tree.
However, this tree is so large that we have no hope of exploring the whole
thing. For this reason, we will search only a portion of the tree, and use
an evaluation function to compute a number estimating the strength of a
player's position for each node whose children we don't examine. By
combining this evaluation function with a partial search of the tree, we can
typically do a better job of finding the best move than if we were to use the
evaluation function by itself. In what follows, we will first describe the tree
search algorithm, then we will describe the evaluation function.

The Negamax Algorithm


The algorithm we will use to search the tree is called the negamax algorithm,
which is a more succinct presentation of the minimax algorithm. We will
assume that the evaluation function attempts to give a higher value for a
stronger position for the player whose turn it is to move. A value of 0 indicates
a perfectly balanced configuration.

The tree shown below illustrates the negamax algorithm on some board that
is nearly full. In this example, the tree is searched to a depth of 3. The leaves
at depth 3 show the values given by the evaluation function, assuming that
player 'O' will play from these configurations. The two leaves at depth 2
represent configurations in which 'X' has lost the game. Note that from the
perspective of player 'X', the value of each of the depth-3 leaves is the
negative of the value shown. Thus, because the parent of each of these
nodes is a configuration in which 'X' will play, the best play for 'X' (shown with
darker lines) will be the one that leads to the maximum of the negatives of the
values of its child nodes. The values in these parent nodes are then these
maximum values. In this way, values and best plays can be computed for
higher and higher nodes, until we have a best play for the root node.

One case that is not shown in the above example is a node in which the board
has been completely filled. In this case, if neither player has won, the value of
the configuration is 0.

We can now summarize the negamax algorithm as a recursive tree traversal.


Recall that we will not have a tree as a data structure - we are only thinking of
the board configurations as forming a tree. We will obtain a child node by
making a play on the current board configuration. When we are finished with
that child, we will undo that play to return to the parent configuration. One of
the parameters to the algorithm will be the remaining search depth. Because
we want the algorithm to compute both a value for the configuration and a
best move from that configuration, we will require that the algorithm always be
called with a search depth of at least 1. Consequently, the algorithm should
never make a recursive call on a position representing the end of the game.

The algorithm will need a loop to determine the maximum of the negatives of
the values of the children. As it updates this maximum, it will also need to
update the play that gives this maximum. For each legal move from the
current configuration, that move is played, resulting in one of four cases:

 If the move wins the game, the value is set to a suitably large number
(1000000 in our implementation), and this play is the best. These are
immediately returned.
 Otherwise, if the move fills the board, the value is set to 0. Because this
must be the only legal move, it is the best move. These are immediately
returned.
 Otherwise, if the given search depth is 1, we compute the evaluation
function from the perspective of the otherplayer on the resulting
configuration, and use its negative in the maximum we are computing.
 Otherwise, we recursively evaluate the resulting configuration for
the other player with a search depth one smaller than the given remaining
depth. We use the negative of the result of this recursive evaluation in the
maximum we are computing.

After each legal move is processed, it must be undone to restore the board to
its previous configuration. Note that this must be done even in the cases that
immediately return a value. If the loop completes, the maximum it computes is
the value of the board configuration for the current player.

Evaluation Function
Our evaluation function will consist of two heuristics:

1. Each unoccupied cell that would win the game if occupied by the current
player will contribute 100 points. Each unoccupied cell that would win the
game for the opponent will contribute -100 points. (A cell might qualify for
both, in which case the two values cancel to 0.)
2. Each occupied cell contributes a certain value to the player who occupies
it (i.e., a positive value if the current player occupies it, or a negative value
if the opposing player occupies it). The value (either positive or negative)
is the number of length-four sequences of cells that contain that cell.
These values are fixed throughout the game, and are given by following
table:

3 4 5 7 5 4 3
4 6 8 10 8 6 4
5 8 11 13 11 8 5
5 8 11 13 11 8 5
4 6 8 10 8 6 4
3 4 5 7 5 4 3

The evaluation function is the sum of these two heuristics. Thus, heuristic 1 is
the main contributor to the evaluation function. Heuristic 2 is mainly for the
purpose of differentiating configurations that heuristic 1 evaluates equally -
particularly early in the game, when heuristic 1 will evaluate many
configurations to 0.
For example, suppose we have the following board configuration with "X" to
play:

X O X
O O O
O O X X
X X X O

For heuristic 1, "X" has one cell on the bottom row that can complete a win -
this gives 100 points. "O" has four open cells (one on the bottom row, two on
the third row from the bottom, and one on the second row from the top) that
can complete a win - this gives 400 points for "O", or -400 points for "X".
Because "X" is player making the move, the total for heuristic 1 is -300. For
heuristic 2, "X" occupies cells with the following values (going from the top
down, and left to right): 13, 8, 8, 6, 5, 7, 5, for a total of 52. "O" occupies cells
with the following values: 11, 13, 11, 8, 8, 10, 4, for a total of -65 for "X". The
total of heuristic 2 is therefore -13, and the overall value is -313. This example
illustrates the need to combine the evaluation function with some sort of
search - even though the evaluation function values this configuration as an
advantage for "O", "X" can, in fact, win immediately.

This evaluation function is rather simple, but it is easy to compute and not
terribly costly. Furthermore, when used with the negamax algorithm, it results
in reasonable game play.

Starting the Assignment


Create a GitHub repository using this URL (Links to an external site.)Links to
an external site., and clone it to your local machine. The solution contained in
this repository is a new Windows Forms Application. Note that you will need to
rename Form1.cs and the class it contains to conform to the naming
conventions (Links to an external site.)Links to an external site. for this class.
The repository also contains an executable, hw3.exe, that illustrates a
working solution.

User Interface
The program begins by opening a dialog resembling the following:
Note that this is not the main GUI. You will need to build this dialog by right-
clicking on the project name in the Solution Explorer and selecting
"Add->Windows Form...". It must be impossible to maximize or otherwise
resize this form (set its FormBorderStyle (Links to an external site.)Links to
an external site. property to FixedDialog). The NumericUpDown should
allow the user to select any integer from 1 to 7 (see its Minimum (Links to an
external site.)Links to an external site. and Maximum (Links to an external
site.)Links to an external site. properties). This value should be right-aligned in
the box (see the NumericUpDown's TextAlign (Links to an external
site.)Links to an external site. property). The two controls in the middle
are RadioButton (Links to an external site.)Links to an external site.s - the
first one should be initially checked (see its Checked (Links to an external
site.)Links to an external site. property). If the user checks one of them the
other should be unchecked (this behavior is provided automatically because
they are in the same container). If the user clicks the "OK" button, the dialog
should close, and the main GUI should open, as described below. By setting
the button's DialogResult (Links to an external site.)Links to an external
site. property to OK, you can achieve this behavior without writing an event
handler for the button. If the user closes the dialog by clicking the "X" button in
the upper-right corner, the program should exit.

The main GUI should resemble the following:


It should be impossible to maximize or otherwise resize this window (set
its FormBorderStyle property to Fixed3D).

To make it easier for the program to interact with the GUI, most of the controls
are laid out using FlowLayoutPanels (Links to an external site.)Links to an
external site.. You can find this control in the "Containers" section of the
Design Window Toolbox. One of these panels contains the row of buttons at
the top, and another contains the columns in which the game pieces will be
dropped. When a control such as a button is placed within
a FlowLayoutPanel, it is automatically placed at the next available position
within the panel. By default, controls are added from left to right, though this
behavior can be changed (see below). It will make it easier to work
with FlowLayoutPanels if you set each of their WrapContents (Links to an
external site.)Links to an external site. properties to false.

Each of the seven buttons at the top should be the same size (you can
copy/paste a button once you have it finished). They should each be initially
enabled.

Each of the columns in the second FlowLayoutPanel is


another FlowLayoutPanel. The width of each of these panels should be
exactly the same as the button above it (see the Size property of both
the Button and the FlowLayoutPanel - the first value is the width). Their
background colors (see the BackColor property) should be set to a different
color from the containing FlowLayoutPanel so that each is visible. These
columns will be used to hold Labels containing either an "X" or an "O" - these
labels will represent the game pieces played on the board. The labels will be
added by the program as pieces are played. In order for the layout to work
properly, you will need to set the FlowDirection property for each of these
columns to BottomUp. Furthermore, you will need to set the Font property,
either in each of these columns or in the containing FlowLayoutPanel to an
appropriate large size. In order to determine the appropriate font size, add six
labels containing a mix of "X"s and "O"s to a column, and adjust the font and
the column height so that the labels fill the column. Then remove these labels.
As with the buttons, it is easiest if you do all this for a single column, then
copy it six times.

The TextBox at the bottom should be read-only (see its ReadOnly (Links to
an external site.)Links to an external site. property).

If the user selects "Human plays first" in the setup dialog and closes it with the
"OK" button, the GUI should appear as shown above. The message, "Your
move.", indicates that the program is waiting for the user to play. Whenever
this message is shown, the user may play by clicking an enabled button. This
will cause an "O" to be placed in the lowest empty cell below that button. If
that play fills its column, the button clicked is disabled. If it wins the game by
completing a sequence of four "O"s in a row, either horizontally, vertically, or
diagonally, the message will change from "Your move." to "You win!". If it
does not win, but fills the last cell in the board, the message will change to
"The game is a draw." In either of these last two cases, all seven buttons
should be disabled, so that the only action that can be taken by the user is to
exit the program.

If the user selects "Computer plays first" in the setup dialog and closes it with
the "OK" button, the message shown should be, "My move.", while the
program is finding a move to make (at lower levels, this time may be short
enough that the message is not seen). The program finds a play by using the
negamax algorithm to a depth matching the level the user chose when setting
up the game; i.e., if the user chose level n, then the program searches to a
depth of n. Once the computer finds a move, it will display it by placing an "X"
in the lowest open cell in some column. This behavior will also occur following
a user play that doesn't end the game. If the program's play fills its column,
the button above that column is disabled. If it wins the game by completing a
sequence of four "X"s in a row, the message will change to "I win!". If it does
not win, but fills the last cell in the board, the message will change to "The
game is a draw." In either of these last two cases, all seven buttons should be
disabled. If the play does not end the game, the message should change to
"Your move.", and the program will await a play by the user.
A working executable has been posted. Note that the computer always plays
as "X", and the human as "O".

Software Architecture
The following class diagram shows the relationships between the classes to
be included in your program:

UserInterface and SetupDialog are the two forms described above. Board is
an internal representation of the board - the GUI will only be used for
displaying the board to the user, as accessing the GUI is
expensive. ComputerPlayer represents the computer player. Each of these
classes should be public.
The names in your program don't need to match the names shown above,
but they should obey the naming conventions for this course (Links to an
external site.)Links to an external site..

Coding Requirements
Specific requirements for the above classes are given in what follows.

The Board Class


This class will need to represent a board configuration, along with the value of
heuristic 2 for this configuration from the perspective of the first player. It will
use the numbers 1 and -1 to represent the pieces of the first and second
player, respectively. (Note that, given these definitions, to get the heuristic 2
score from the perspective of player p, we can just multiply the stored score
by p). In order to make the code more maintainable, it will also define the
following public intconstants (Links to an external site.)Links to an external
site.:

 The value used for the first player (1).


 The number of rows on the board (6).
 The number of columns on the board (7).

It should also define a private int constant giving the number of cells on the
board. The most maintainable way to define this constant is by multiplying the
number of rows by the number of columns, using the above constants.

To represent the board configuration, it will need the following private fields:

 A List<int>[ ] to store the pieces played on the board. Each list in this
array will represent a column on the board. The number of elements in the
list will be the number of pieces in the column. Thus, element 0 of a list will
store the piece in the bottom row of that column. Each element will be
either 1 or -1 to denote which player's piece occupies that cell.
 An int to record the player whose turn it is to play.
 A Stack<int> to store the history of plays so that they can be undone.
Each element in the stack is the column in which a play was made.
 An int to keep track of the heuristic 2 score from the perspective of the
first player.
 An int[ , ] field storing the value used by heuristic 2 for each cell. This type
is a 2-dimensional array - you can access elements of it by giving the row
and column as the two array indices, separated by a comma. For
example, if the array is called _cellValues, the element in row 1 and
column 3 is accessed by _cellValues[1, 3]. It can be initialized as
follows:
private int[,] _cellValues =

{3, 4, 5, 7, 5, 4, 3},

{4, 6, 8, 10, 8, 6, 4},

{5, 8, 11, 13, 11, 8, 5},

{5, 8, 11, 13, 11, 8, 5},

{4, 6, 8, 10, 8, 6, 4},

{3, 4, 5, 7, 5, 4, 3}

};

The remaining class members are described in what follows. For efficiency,
you should not do any explicit error checking on the parameters to these
methods.

A public property to get whether the board is full


This property should return a bool and contain only a get accessor. Note that
the number of elements in the stack gives the number of pieces that have
been played. If this number is the same as the number cells in the board,
the board is full. This accessor can therefore be defined either using an
expression or a block of code that returns a value.

A public constructor
This constructor needs no parameters. It will need to initialize each element of
the List<int>[ ] to a new List<int>.

A public method to make a play


This method should take as its only parameter an int giving the column of the
play. It should return nothing. Besides updating the board, it will also need to
update the heuristic 2 score and place the column number on the stack. To
update the heuristic 2 score, look up the value of the cell in which the play is
made, multiply by the player, and add the result to the current score.

A public method to undo a play


This method should take no parameters and return nothing. It should undo
everything done in the above method. To remove the last element from
a List use the List's RemoveAt (Links to an external site.)Links to an
external site. method, giving it the index of the last element.
A public method to get the heuristic 2 score
This method should take as its only parameter an int giving a player (i.e.,
either 1 or -1), and it should return an int giving the heuristic 2 score from that
player's perspective.

A public method to get the number of pieces on a column


This method should take as its only parameter an int giving a column, and it
should return an int giving the number of pieces on that column.

A private method to determine the length of a path of pieces in a


given direction
This method should take the following parameters:

 An int giving a row.


 An int giving a column.
 An int giving a vertical increment, either -1, 0, or 1.
 An int giving a horizontal increment, either -1, 0, or 1.
 An int giving a player, either -1 or 1.

The vertical and horizontal increments describe the vertical and horizontal
components of a direction, where -1 is down or left and 1 is up or right. Thus,
for example, a vertical increment of 0 and a horizontal increment of -1 would
describe the direction to the left, whereas a vertical increment of 1 and
a horizontal increment of 1 would describe the diagonal direction to the upper
right.

This method should return an int giving the number of consecutive pieces in
the given direction from the given row and column belong to the given player.
The cell at the given row and column should not be examined - its contents
are irrelevant. The consecutive pieces can be terminated by an edge of the
board, an unoccupied cell, or a cell occupied by the other player.

A public method to determine whether a piece on a given cell would


complete a win
This method should take as its parameters three ints giving a row, a column,
and a player. It should return a boolindicating whether a piece belonging to
the given player would complete four in a row if played on the given cell.
Because this method will be used both to determine whether a play will win
the game and to evaluate heuristic 1, it doesn't matter whether it is currently
possible to play on the given cell, or even whether the cell is already
occupied. However, you may assume that there are no pieces directly above
this cell. Note that the cell may be in the middle of the four-in-a-row; e.g.,
there may be one piece to the lower left and two to the upper right. You will
need to make several calls to the above method.

A public property to get whether the last play completed 4 in a row


This property should return a bool and contain only a get accessor. The value
of this property can be determined using the above method. The column of
the last play is on the top of the stack of plays. The row is one less than the
number of elements on this column. The player is the player who
is not currently playing. You can implement this accessor using either an
expression or a block of code.

The ComputerPlayer Class


This class should define the following private const int fields:

 The value of each cell that would complete a win in the computation of
heuristic 1 (100).
 The value of a win (1000000).

It should use these constants rather than the numbers themselves. In


addition, it will need the following private fields:

 The identifier of the computer player. This will be either the value of the
constant defined in the Board class to represent the first player, or the
negative of this constant, depending on whether the computer player plays
first.
 The difficulty level.
 The Board representing the current game.

The other members of this class are described in what follows.


Because constants are treated as being static, to access public constants
defined in the Board class, you will need to use the class name
(e.g., Board.FirstPlayer).

A public constructor
This constructor will need as its parameters values to assign to each of
the private fields.

A private method to compute the evaluation function


This method should take as its only parameter an int identifying the player
from whose perspective the board is to be evaluated. It should return
an int giving the value of the evaluation function. It should obtain the value of
heuristic 2 from the Board class, using the appropriate method. To this, it will
need to add the value of heuristic 1.

It will need nested loops to compute heuristic 1. The outer loop will need to
iterate through the columns of the board. The inner loop will iterate through
unoccupied rows of a column. For efficiency, you should not examine any
rows that are more than 1 above the last occupied row in the current column
or any adjacent columns, as these cells cannot complete a win.

A private method to evaluate the current board position using the


negamax algorithm
This method should take the following parameters:

 An int giving the player whose turn it is.


 An int giving the depth to search.
 An out int through which the column of the best play will be returned.

It should return an int giving the value of the current configuration represented
by the Board field, from the given player's perspective. It should implement
the negamax algorithm described in the section, "The Negamax Algorithm",
above. Recall that we are not using a tree data structure, but that the tree is
simply how we think about the relationships between various board
configurations. You should initialize the maximum to Int32.MinValue (Links to
an external site.)Links to an external site. in order to be sure you update the
best play to the maximum value. In order to match the behavior of the posted
executable, iterate through the columns from smallest index to largest, and
only update the maximum when you encounter a strictly larger value. You
may use the Board field to make the play being evaluated - just be sure to
undo that play when you have finished evaluating it, even if you are returning
from inside the loop.

Be careful in passing the out parameter to the recursive call. If you use the
same parameter that was passed in, the recursive call will change the best
play. You should therefore use a new variable. (Note that this method doesn't
actually use this value - only the method below will.)

A public method to make a play


This method should take no parameters and return an int giving the column
on which the play is made. It should find the best play using the above
method, make that play on the Board field, and return it.

The SetupDialog Class


You will only need to add the following public properties to this class:

 A property that gets a bool indicating whether the user checked the
"Computer plays first" radio button.
 A property that gets an int giving the level that the user chose. Note that
because the value of a NumericUpDown is a decimal, you will need to
cast it to an int (an int is more appropriate for the rest of the program).
The UserInterface Class
Besides the fields defined by the GUI construction, this class will need the
following private fields:

 A Board giving an internal represenation of the current board position.


 A ComputerPlayer representing the computer player.

This class will also need the private methods and event handlers described in
what follows. In some of these methods, you will need to use
the Controls (Links to an external site.)Links to an external site. property of
one of the FlowLayoutPanels in order to access individual controls in that
panel. This property gives a collection of the controls contained in that panel.
You can index into this collection as you would an array; for example, if c is
the collection returned by the Controls property of the panel containing the
columns, then c[2] will give the column associated with the button labeled 2.
You can also use a foreach loop to iterate through the controls in this
collection. Finally, you can use the collection's Add (Links to an external
site.)Links to an external site. method to add a control to the panel.
A method to finish the game
This method should take as its only parameter a string giving the message to
show in the TextBox, and should return nothing. It should disable all the
buttons and place the given message in the TextBox.

A method to determine whether the game is over


This method should take as its only parameter a string giving the message to
show if the game has been won, and it should return a bool indicating
whether the game is over. Use the appropriate properties of the Board class
to make this determination, and call the above method if the game is over. Be
sure to check for a win first, as the winning play might be in the last available
position.

A method to show a play on the form


This method should take as its parameters an int giving the column of the
play and a string containing the symbol to add to the column (i.e., "X" or "O").
It should return nothing. It is responsible for adding the symbol to the
column and disabling the appropriate button if the play fills its column.

You will need to construct a Label and set its Text property to the
given string. Because its font is determined by the FlowLayoutPanel that
you will add it to, you will need to set its AutoSize property to true. You can
use the Boardfield to determine whether the column is full.
A method to make the computer's play
The method should take no parameters and return nothing. It is responsible
for placing the message, "My move." in the TextBox, getting the computer's
play using the ComputerPlayer's public method, and using the above
method to display it on the form. Normally, updates to the form won't be
displayed until the event handler is finished. In order for the message in
the TextBox to be seen while the computer is deciding on its move, you will
need to override this behavior by calling the form's Update (Links to an
external site.)Links to an external site. method with no parameters before
getting the computer's play from the ComputerPlayer. This method
is inherited by the UserInterface class from the Form class; hence, you can
call it simply by
Update();

An event handler for a button click


We have multiple buttons that do essentially the same thing. We therefore will
have a single event handler for all the buttons. To set this up, double-click on
one of the buttons to create an event handler. To attach this event handler
to another button, go back to the Design window, click on another button, and
open its Properties window. Click on the icon that looks like a lightning bolt to
toggle the view to the events for this button (the icon to its left will toggle the
view back to the properties). To the right of Click in this list, select the event
handler from the drop-down list.

This method's first parameter will be the button that was clicked. In order to
treat it as a button, you will need to cast it to Button. You can then get
its Text property and convert it to an int in order to determine the column in
which the user played. It should record the given play on both the Board and
the form using the appropriate methods above, and if this play does not end
the game, make the computer's play using the above method. Finally, if this
does not end the game, update the TextBox to indicate that it is the user's
turn. Note that the method to determine whether the game is over will take
care of updating the form appropriately if the game is over.

An event handler for the form


Double-click on the form (not on one of its controls) to create an event handler
that will be called when the form is loaded. It is responsible for displaying the
setup dialog and if the user closes it with the OK button, retrieving the user's
choices. If the user closes the dialog with the "X" in the upper right corner, it
should exit the application by calling Application.Exit().

An instance of the SetupDialog class will need to be constructed and shown


using its ShowDialog (Links to an external site.)Links to an external
site. method. When the user closes the dialog, the ShowDialog method will
return either DialogResult.OK (assuming the "OK"
button's DialogResult property has been set to OK)
or DialogResult.Cancel to indicate how the user has closed the dialog.
If the user has chosen to allow the computer to play first, this will need to be
done. A ComputerPlayer should first be constructed and assigned to
the ComputerPlayer field. In order to make the form visible while the
computer is deciding on its play, set the form's Visible (Links to an external
site.)Links to an external site. property to true. Then make the computer's
play using the appropriate method above. Finally, set the contents of
the TextBox to indicate it is the user's move.

If the user has chosen to play first, you will still need to construct
a ComputerPlayer for the private field.

Testing and Performance


If you've implemented the code correctly, your program should give the same
behavior as the posted executable. You can therefore run the following tests
to see if the programs behave in the same way. The plays are column
numbers.

 Level 1, human plays first:

Human: 3456
Computer: 3 3 3

 Result: The human wins.


 Level 5, computer plays first:

Computer: 3 2 3 4 3 2 2 3 2 6 6 6 0 0 0 1
Human: 4443521326660001

 Result: The human wins.


 Level 4, human plays first:

Human 23224554356
Computer 3 3 3 2 4 4 4 3 5 6 6

 Result: The computer wins.


 Level 3, human plays first:

Human 135334415551222566000
Comnputer 3 4 3 3 4 4 2 1 6 5 4 1 2 2 1 6 6 6 0 0 0
 Result: The game is a draw.

You should also play a few moves at level 7 to compare your program's
performance with that of the posted executable. Yours should perform within a
factor of two of the posted executable.

Submitting Your Assignment


Be sure to refresh your Team Explorer, commit all your changes,
then push your commits to your GitHub repository. Then submit the entire
URL of the commit that you want graded. There is no need to submit a
comment, as you will not have a completion code.

Important: If the URL you submit does not contain the 40-hex-digit fingerprint
of the commit you want graded, you will receive a 0, as this fingerprint is the
only way we can verify that you completed your code prior to submitting your
assignment. We will only grade the source code that is included in the commit
that you submit. Therefore, be sure that the commit on GitHub contains
all seven ".cs" files, and that they are the version you want graded. This is
especially important if you had any trouble committing or pushing your code.

A Note on the Executable Provided


The provided executable has been obfuscated to prevent decompiling to the
original source code. Code decompiled from obfuscated code is difficult or
impossible to understand. There exist deobfuscators that can make this
decompiled code a bit more understandable, but it's still fairly obvious that it is
obfuscated code. Any use of code decompiled and/or deobfuscated from
the provided executable will be considered as cheating.

You might also like