Making sense of the madness

Now that we have the gold standard test written, we can begin to safely refactor the code. Any changes that we try to make that break the gold standard test will have to be undone and a new approach will have to be taken.

Looking at the Mastermind class, all those variables at the top of the Play method can be moved out to be class level fields. This will make them available to all the code within the class and help to both figure out what they are for and how often they are used in the app:

private char[] g;
private char[] p = new[] { 'A', 'A', 'A', 'A' };
private int i = 0;
private int j = 0;
private int x = 0;
private int c = 0;

Next, we will just work our way down the Play method, extracting all that we can into tiny private methods. We are only able to do some tiny refactoring before we need to switch gears and start fixing some of the antiquated logic in this application:

public class Mastermind
{
private readonly IInputOutput _inout;
private char[] g;
private char[] p = new[] { 'A', 'A', 'A', 'A' };
private int i = 0;
private int j = 0;
private int x = 0;
private int c = 0;

public Mastermind(IInputOutput inout)
{
_inout = inout;
}

public void Play(string[] args)
{
// Determine if a password was passed in?
if (args.Length > 0 && args[0] != null) p = args[0].ToCharArray();
else CreateRandomPassword(); // Create a password if one was not
provided
// Player move - guess the password
guess:
_inout.Write("Take a guess: ");
g = _inout.ReadLine().ToArray();
i = i + 1;
if (g.Length != 4) goto wrong_size;
if (g == p) goto success;
x = 0;
c = 0;

// Check if the password provided by the player is correct
check_loop:
if (g[x] > 65 + 26) g[x] = (char)(g[x] - 32);
if (g[x] == p[x]) _inout.Write("+", c = c + 1);
else if (p.Contains(g[x])) _inout.Write("-");
x = x + 1;
if (x < 4) goto check_loop; // Still checking??
_inout.WriteLine();
if (c == 4) goto success; // Password must have been correct
goto guess; // No correct, try again

// Password guess was wrong size - Error Message
wrong_size: _inout.WriteLine("Password length is 4.");
goto guess;

// Game over you win
success: _inout.WriteLine("Congratulations you guessed the password
in " + i + " tries.");
_inout.WriteLine("Press any key to quit.");
_inout.Read();
}
private void CreateRandomPassword()
{
// Initialize randomness
Random rand = new Random(DateTime.Now.Millisecond);

j = 0;

password_loop:
p[j] = (char)(rand.Next(6) + 65);
j = j + 1;
if (j < 4) goto password_loop;
}
}

We were able to break out a password generation method. We were also able to simplify the structure of the success code. We cannot, however, proceed without addressing the complexity of the chosen looping structures. The developer that wrote this did not use general looping structures, such as while and for loops. We need to fix that in order to better understand and work with this code:

public void Play(string[] args)
{
// Determine if a password was passed in?
if (args.Length > 0 && args[0] != null) p = args[0].ToCharArray();
else CreateRandomPassword(); // Create a password if one was not
provided
// Player move - guess the password
while (c != 4)
{
_inout.Write("Take a guess: ");
g = _inout.ReadLine().ToArray();

i = i + 1;

if (g.Length != 4)
{
// Password guess was wrong size - Error Message
_inout.WriteLine("Password length is 4.");
}
else
{
// Check if the password provided by the player is correct
for (x = 0, c = 0; g.Length == 4 && x < 4; x++)
{
if (g[x] > 65 + 26) g[x] = (char)(g[x] - 32);
if (g[x] == p[x]) _inout.Write("+", c = c + 1);
else if (p.Contains(g[x])) _inout.Write("-");
}

_inout.WriteLine();
}
}

// Game over you win
_inout.WriteLine("Congratulations you guessed the password in " + i +
" tries.");
_inout.WriteLine("Press any key to quit.");
_inout.Read();
}

We now have a structure that we can begin to work with. Let's start by making some sense of these variable names:

C ~= Correct Letter Guesses
G ~= Current Guess
P ~= Password
I ~= Tries
X ~= Loop Index / Pointer to Guess Character being checked
J ~= Loop Index / Pointer to Password Character being generated

We will want to make updates to the Play method that reflect our determinations for what the variables mean. Following we have replaced the single letter variable names with names that more appropriately represent what the variables are used for:

public void Play(string[] args)
{
// Determine if a password was passed in?
if (args.Length > 0 && args[0] != null) password =
args[0].ToCharArray();
else CreateRandomPassword(); // Create a password if one was not
provided
// Player move - guess the password
while (correctPositions != 4)
{
_inout.Write("Take a guess: ");
guess = _inout.ReadLine().ToArray();

tries = tries + 1;

if (guess.Length != 4)
{
// Password guess was wrong size - Error Message
_inout.WriteLine("Password length is 4.");
}
else
{
// Check if the password provided by the player is correct
for (x = 0, correctPositions = 0; x < 4; x++)
{
if (guess[x] > 65 + 26) guess[x] = (char)(guess[x] - 32);
if (guess[x] == password[x]) _inout.Write("+", correctPositions
= correctPositions + 1);
else if (password.Contains(guess[x])) _inout.Write("-");
}
_inout.WriteLine();
}
}
// Game over you win
_inout.WriteLine("Congratulations you guessed the password in " +
tries + " tries.");
_inout.WriteLine("Press any key to quit.");
_inout.Read();
}

Next, it would be nice if we could update the interface now that we understand the application a little better. Two things that we would like to change are the input and the very end of the game. It would be nice if the input was a simple string instead of a character array. The Play method could take a string and the program could figure out how to get the password string from the arguments.

Along those same lines, we could reduce the overall number of writes and turn the consecutive plus and minus Write commands into a single WriteLine command. This would break our gold standard test, but wouldn't actually change the functionality of the code. It would still print the pluses and minuses on a single line.

To convert the guess from a character array to a string, we must first understand what is happening on this line:

if (guess[x] > 65 + 26) guess[x] = (char)(guess[x] - 32);

Analyzing the line, we see the numbers 65, 26, and 32. If you are familiar with ASCII codes, then these lines might make sense to you. The number 65 is the starting point of the alphabet characters on the ASCII tables. There are 26 letters in the English alphabet. And, there are 32 values between "a" and "A". So, it is to be assumed that this code is either uppercasing or lowercasing the character at the specified index. We can approximate this in C# using the String.ToUpper() method.

While we are doing a small bit of gold standard changes, we should also remove the last two lines of the Play method and move them to Program.cs, as they are more related to a Console application than anything else.

In the file Program.cs:

class Program
{
static void Main(string[] args)
{
var inout = new ConsoleInputOutput();
var game = new Mastermind(inout);

var password = args.Length > 0 ? args[0] : null;
game.Play(password);

inout.WriteLine("Press any key to quit.");
inout.Read();
}
}

In the file Mastermind.cs:

public class Mastermind
{
private readonly IInputOutput _inout;
private string guess;
private int tries;
private int correctPositions;

public Mastermind(IInputOutput inout)
{
_inout = inout;
}

public void Play(string password = null)
{
// Determine if a password was passed in?
password = password ?? CreateRandomPassword();

// Player move - guess the password
while (correctPositions != 4)
{
_inout.Write("Take a guess: ");
guess = _inout.ReadLine();
tries = tries + 1;

if (guess.Length != 4)
{
// Password guess was wrong size - Error Message
_inout.WriteLine("Password length is 4.");
}
else
{
// Check if the password provided by the player is correct
guess = guess.ToUpper();
var guessResult = "";

for (var x = 0; x < 4; x++)
{
if (guess[x] == password[x])
{
guessResult += "+";
}
else if (password.Contains(guess[x]))
{
guessResult += "-";
}
}

correctPositions = guessResult.Count(c => c == '+');
_inout.WriteLine(guessResult);
}
}

// Game over you win
_inout.WriteLine("Congratulations you guessed the password in " +
tries + " tries.");
}

private string CreateRandomPassword()
{
// Initialize randomness
Random rand = new Random(DateTime.Now.Millisecond);

var password = new [] {'A', 'A', 'A', 'A'};

var j = 0;

password_loop:
password[j] = (char)(rand.Next(6) + 65);
j = j + 1;

if (j < 4) goto password_loop;
return password.ToString();
}
}

In the file GoldStandardTests.cs:

public class GoldStandardTests
{
[Fact]
public void StandardTestRun()
{
// Arrange
var inout = new MockInputOutput();
var game = new Mastermind(inout);

// Arrange - Inputs
inout.InFeed.Enqueue("AAA");
inout.InFeed.Enqueue("AAAA");
inout.InFeed.Enqueue("ABBB");
inout.InFeed.Enqueue("ABCC");
inout.InFeed.Enqueue("ABCD");
inout.InFeed.Enqueue("ABCF");
inout.InFeed.Enqueue(" ");

// Arrange - Outputs
var expectedOutputs = new Queue<string>();
expectedOutputs.Enqueue("Take a guess: ");
expectedOutputs.Enqueue("Password length is 4." +
Environment.NewLine);
expectedOutputs.Enqueue("Take a guess: ");
expectedOutputs.Enqueue("+---" + Environment.NewLine);
expectedOutputs.Enqueue("Take a guess: ");
expectedOutputs.Enqueue("++--" + Environment.NewLine);
expectedOutputs.Enqueue("Take a guess: ");
expectedOutputs.Enqueue("+++-" + Environment.NewLine);
expectedOutputs.Enqueue("Take a guess: ");
expectedOutputs.Enqueue("+++" + Environment.NewLine);
expectedOutputs.Enqueue("Take a guess: ");
expectedOutputs.Enqueue("++++" + Environment.NewLine);
expectedOutputs.Enqueue("Congratulations you guessed the password
in 6 tries." + Environment.NewLine);

// Act
game.Play("ABCF");

// Assert
inout.OutFeed.ForEach(text =>
{
Assert.Equal(expectedOutputs.Dequeue(), text);
});
}
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.105.190