Skip to content

Commit

Permalink
Cleaning up GameAlgorithms.
Browse files Browse the repository at this point in the history
Broke out the bool logic check to determine if it is the computers first turn into its own spec.

Grouping logic blocks with functions for better readability.
  • Loading branch information
Joe Shipley committed May 4, 2012
1 parent 611ffd6 commit 7239029
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 32 deletions.
2 changes: 2 additions & 0 deletions Source/Domain.GameLogic/Domain.GameLogic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
<Compile Include="Providers\RandomNumberProvider.cs" />
<Compile Include="Providers\WinningMoveProvider.cs" />
<Compile Include="Providers\WinningSetsProvider.cs" />
<Compile Include="Specifications\ComputerFirstTurnSpecification.cs" />
<Compile Include="Specifications\GameStatusSpecification.cs" />
<Compile Include="Specifications\IComputerFirstTurnSpecification.cs" />
<Compile Include="Specifications\IGameStatusSpecification.cs" />
<Compile Include="Specifications\IMoveValidationSpecification.cs" />
<Compile Include="Specifications\MoveValidationSpecification.cs" />
Expand Down
85 changes: 53 additions & 32 deletions Source/Domain.GameLogic/Processes/GameAlgorithms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using TTT.Domain.Entities;
using TTT.Domain.GameLogic.Providers;
using TTT.Domain.GameLogic.Specifications;

namespace TTT.Domain.GameLogic.Processes
{
Expand All @@ -11,80 +12,90 @@ public class GameAlgorithms : IGameAlgorithms
private readonly IRandomNumberProvider _randomNumberProvider;
private readonly IWinningMoveProvider _winningMoveProvider;
private readonly IComputerFirstTurnMoveProvider _computerFirstTurnMoveProvider;
private readonly IComputerFirstTurnSpecification _computerFirstTurnSpecification;

public GameAlgorithms(IAvailableBoardPositionsProvider availableBoardPositionsProvider,
IRandomNumberProvider randomNumberProvider,
IWinningMoveProvider winningMoveProvider,
IComputerFirstTurnMoveProvider computerFirstTurnMoveProvider)
IComputerFirstTurnMoveProvider computerFirstTurnMoveProvider,
IComputerFirstTurnSpecification computerFirstTurnSpecification)
{
_availableBoardPositionsProvider = availableBoardPositionsProvider;
_randomNumberProvider = randomNumberProvider;
_winningMoveProvider = winningMoveProvider;
_computerFirstTurnMoveProvider = computerFirstTurnMoveProvider;
_computerFirstTurnSpecification = computerFirstTurnSpecification;
}

public GameMove DetermineNextMove(Game game)
{
if(game.Moves.Count() == 1)
return getFirstMove(game);
if(_computerFirstTurnSpecification.IsFirstTurnForComputer(game))
return getComputersFirstMove(game);

var currentMoves = game.Moves;

var computerWinPositions = _winningMoveProvider.GetPotentialWinningMovesFor(currentMoves, Enums.PlayerType.Computer);
// if the computer can win this, do it!
var computerWinPositions = _winningMoveProvider.GetPotentialWinningMovesFor(game.Moves, Enums.PlayerType.Computer);
if(computerWinPositions.Any())
return GameMove.CreateFrom(Enums.PlayerType.Computer, computerWinPositions.FirstOrDefault());

var humanThreatPositions = _winningMoveProvider.GetPotentialWinningMovesFor(currentMoves, Enums.PlayerType.Human);
// if the human can potentially win, lets block it.
var humanThreatPositions = _winningMoveProvider.GetPotentialWinningMovesFor(game.Moves, Enums.PlayerType.Human);
if(humanThreatPositions.Any())
return GameMove.CreateFrom(Enums.PlayerType.Computer, humanThreatPositions.FirstOrDefault());

// TODO: determine how to handle the Corner to Corner win pattern.
// NOTE: Should we weight the N/S/E/W as higher priority moves to make or possibly
// determine if we are forcing the player to make a blocking move to keep playing, and if so
// how many possible win moves would that create for the players. Then select a move from
// the lowest win possibilities.

var availablePositions = _availableBoardPositionsProvider.GetRemainingAvailableBoardPositions(game);
availablePositions = removePositionsThatWouldForceThePlayerToBlockAndCreateDoubleWinningMoves(currentMoves, availablePositions);
return getRandomMoveFromAvailable(availablePositions);
return returnRandomSafeMoveFromAvailableMoves(game, game.Moves);
}

private IList<BoardPosition> removePositionsThatWouldForceThePlayerToBlockAndCreateDoubleWinningMoves(IList<GameMove> currentMoves, IList<BoardPosition> availablePositions)
{
var responsePositions = new List<BoardPosition>();
foreach(var position in availablePositions)
{
// check the potential move to see if it creates a need for the player to block it.
var newMovesAfterComputerHasChoosen = new List<GameMove>(currentMoves)
{
GameMove.CreateFrom(Enums.PlayerType.Computer, position)
};
var newMovesAfterComputerHasChoosen = returnGameMovesWithPossibleComputerMove(currentMoves, position);
var computerWinningMoves = _winningMoveProvider.GetPotentialWinningMovesFor(newMovesAfterComputerHasChoosen, Enums.PlayerType.Computer);
foreach(var winningPositions in computerWinningMoves)

foreach(var winningPosition in computerWinningMoves)
{
// apply the players blocking move and check to see if that move creates two winning positions
var newMovesAfterPlayerHasChoosen = new List<GameMove>(newMovesAfterComputerHasChoosen)
{
GameMove.CreateFrom(Enums.PlayerType.Human, winningPositions)
};
var newMovesAfterPlayerHasChoosen = returnGameMovesWithNextPotentialPlayerMove(newMovesAfterComputerHasChoosen, winningPosition);
var playerWinningMoves = _winningMoveProvider.GetPotentialWinningMovesFor(newMovesAfterPlayerHasChoosen, Enums.PlayerType.Human);
if(playerWinningMoves.Count() < 2)
// if the potential move does not cause the player to block and create two
// winning positions, add it to the acceptable move list.
responsePositions.Add(position);
applyMoveIfItDoesNotCreateMultipleWinningMovesForThePlayer(responsePositions, playerWinningMoves, position);
}
}
return responsePositions;
}

private List<GameMove> returnGameMovesWithPossibleComputerMove(IList<GameMove> currentMoves, BoardPosition potentialBoardPosition)
{
var newMovesAfterComputerHasChoosen = new List<GameMove>(currentMoves)
{
GameMove.CreateFrom(Enums.PlayerType.Computer, potentialBoardPosition)
};
return newMovesAfterComputerHasChoosen;

}

private List<GameMove> returnGameMovesWithNextPotentialPlayerMove(IList<GameMove> newMovesAfterComputerHasChoosen, BoardPosition playerWinningMove)
{
var newMovesAfterPlayerHasChoosen = new List<GameMove>(newMovesAfterComputerHasChoosen)
{
GameMove.CreateFrom(Enums.PlayerType.Human, playerWinningMove)
};
return newMovesAfterPlayerHasChoosen;
}

private void applyMoveIfItDoesNotCreateMultipleWinningMovesForThePlayer(IList<BoardPosition> responsePositions, IList<BoardPosition> playerWinningMoves, BoardPosition position)
{
if(playerWinningMoves.Count() < 2)
responsePositions.Add(position);
}

private GameMove getRandomMoveFromAvailable(IList<BoardPosition> availablePositions)
{
var randomNumber = _randomNumberProvider.GenerateNumber(0, availablePositions.Count() - 1);
var randomPosition = availablePositions.ElementAt(randomNumber);
return GameMove.CreateFrom(Enums.PlayerType.Computer, randomPosition);
}

private GameMove getFirstMove(Game game)
private GameMove getComputersFirstMove(Game game)
{
var move = getCenterPositionIfAvailable(game)
?? firstMoveCornerFallBackWhenCenterHasBeenTaken();
Expand All @@ -107,5 +118,15 @@ private GameMove firstMoveCornerFallBackWhenCenterHasBeenTaken()
var randomNumber = _randomNumberProvider.GenerateNumber(0, 3);
return cornerMoves.ElementAt(randomNumber);
}

private GameMove returnRandomSafeMoveFromAvailableMoves(Game game, IList<GameMove> currentMoves)
{
var availablePositions = _availableBoardPositionsProvider.GetRemainingAvailableBoardPositions(game);

// lets make sure we don't return a move that a player has to block, and it creates a double move win scenerio for them.
availablePositions = removePositionsThatWouldForceThePlayerToBlockAndCreateDoubleWinningMoves(currentMoves, availablePositions);

return getRandomMoveFromAvailable(availablePositions);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Linq;
using TTT.Domain.Entities;

namespace TTT.Domain.GameLogic.Specifications
{
public class ComputerFirstTurnSpecification : IComputerFirstTurnSpecification
{
public bool IsFirstTurnForComputer(Game game)
{
return game.Moves.All(m => m.Owner != Enums.PlayerType.Computer);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using TTT.Domain.Entities;

namespace TTT.Domain.GameLogic.Specifications
{
public interface IComputerFirstTurnSpecification
{
bool IsFirstTurnForComputer(Game game);
}
}
1 change: 1 addition & 0 deletions Source/Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
<Compile Include="Unit\Domain\Providers\RandomNumberGeneratorTests.cs" />
<Compile Include="Unit\Domain\Providers\WinningMoveProviderTests.cs" />
<Compile Include="Unit\Domain\Providers\WinningSetsProviderTests.cs" />
<Compile Include="Unit\Domain\Specifications\ComputerFirstTurnSpecificationTests.cs" />
<Compile Include="Unit\Domain\Specifications\GameSpecificationsTests.cs" />
<Compile Include="Unit\Domain\Specifications\MoveValidationSpecificationTests.cs" />
<Compile Include="Unit\Domain\Validators\MoveValidatorTests.cs" />
Expand Down
7 changes: 7 additions & 0 deletions Source/Tests/Unit/Domain/Processes/GameAlgorithmsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using TTT.Domain.Entities;
using TTT.Domain.GameLogic.Processes;
using TTT.Domain.GameLogic.Providers;
using TTT.Domain.GameLogic.Specifications;
using TTT.Tests.Helpers.Builders;
using TTT.Tests.Infrastructure;

Expand All @@ -26,6 +27,9 @@ public class When_the_players_first_move_is_not_on_the_middle_center_position
Mocks.GetMock<IComputerFirstTurnMoveProvider>()
.Setup(c => c.GetCenterSquareMove())
.Returns(new GameMoveBuilder().Build(Enums.PlayerType.Computer, BoardPosition.CreateFrom("B", 2)));
Mocks.GetMock<IComputerFirstTurnSpecification>()
.Setup(s => s.IsFirstTurnForComputer(Moq.It.IsAny<Game>()))
.Returns(true);
};

Because of = () => _result = ClassUnderTest.DetermineNextMove(_game);
Expand Down Expand Up @@ -60,6 +64,9 @@ public class When_the_players_first_move_is_on_the_middle_center_position
new GameMoveBuilder().Build(Enums.PlayerType.Computer, BoardPosition.CreateFrom("C", 1)),
new GameMoveBuilder().Build(Enums.PlayerType.Computer, BoardPosition.CreateFrom("C", 3)),
});
Mocks.GetMock<IComputerFirstTurnSpecification>()
.Setup(s => s.IsFirstTurnForComputer(Moq.It.IsAny<Game>()))
.Returns(true);
};

Because of = () => _result = ClassUnderTest.DetermineNextMove(_game);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using Machine.Specifications;
using TTT.Domain;
using TTT.Domain.Entities;
using TTT.Domain.GameLogic.Specifications;
using TTT.Tests.Helpers.Builders;
using TTT.Tests.Infrastructure;

namespace TTT.Tests.Unit.Domain.Specifications.ComputerFirstTurnSpecificationTests
{
[Subject("Domain, Specifications, ComputerFirstTurnSpecification, IsFirstTurnForComputer")]
public class When_asking_if_its_the_computers_first_turn_and_it_is
: BaseIsolationTest<ComputerFirstTurnSpecification>
{
private static bool _result;
private static Game _game;

Establish context = () =>
{
_game = new GameBuilder().WithMoves(new List<GameMove>
{
new GameMove { Owner = Enums.PlayerType.Human, Position = BoardPosition.CreateFrom("B", 2) }
})
.Build();
};

Because of = () => _result = ClassUnderTest.IsFirstTurnForComputer(_game);

It should_let_us_know_that_it_is_the_computers_first_turn = () =>
_result.ShouldBeTrue();
}

[Subject("Domain, Specifications, ComputerFirstTurnSpecification, IsFirstTurnForComputer")]
public class When_determining_if_an_invalid_move_is_legitimate_or_not
: BaseIsolationTest<ComputerFirstTurnSpecification>
{
private static bool _result;
private static Game _game;

Establish context = () =>
{
_game = new GameBuilder().WithMoves(new List<GameMove>
{
new GameMove { Owner = Enums.PlayerType.Human, Position = BoardPosition.CreateFrom("B", 1) },
new GameMove { Owner = Enums.PlayerType.Computer, Position = BoardPosition.CreateFrom("B", 2) },
new GameMove { Owner = Enums.PlayerType.Human, Position = BoardPosition.CreateFrom("B", 3) },
})
.Build();
};

Because of = () => _result = ClassUnderTest.IsFirstTurnForComputer(_game);

It should_let_us_know_that_it_is_not_the_computers_first_turn = () =>
_result.ShouldBeFalse();
}
}

0 comments on commit 7239029

Please sign in to comment.