Skip to content

Rock, Paper, Scissors, Lizard, Spock (Without regex)#5

Open
andresmoschini wants to merge 31 commits intoMakingSense:masterfrom
andresmoschini:rpsls
Open

Rock, Paper, Scissors, Lizard, Spock (Without regex)#5
andresmoschini wants to merge 31 commits intoMakingSense:masterfrom
andresmoschini:rpsls

Conversation

@andresmoschini
Copy link
Member

Well, because of a confusion, I have worked in a different kata than specified in the repo.

Anyway, I think that it could be useful, to enrich this repository. It is pending to add the specification, but in general, it is based on https://gist.github.com/trikitrok/63bfb5226f8cc2e8f6c3 and https://ajlopez.wordpress.com/2013/04/26/tdd-kata-1-piedra-papel-tijera-lagarto-spock/

I would like to some advice about how to include it in the repo, maybe it is enough adding a new readme.md, but I would like to hear Sensei's (@DanielAltamirano) opinion, or if he is not available, @AlphaGit 's one. I also would like to see your reviews.

Also, I still have to recover information about the time that I have spent on this.

I am not following the fine granular TDD approach, and now that I am doing this commit I am thinking that it would be equally easy to apply a granular approach as I did with Draw. Well, it is already done so it tests a lot of scenarios together.
Since all test have the same structure, I think that it could be a good idea to merge them. Thoughts?
…ting multiples implementations

I am stopping working for the moment, the following steps could be:

* Add new referee implementation following  [nickmcdowall's Stage 2](https://github.com/nickmcdowall/Rock-Paper-Scissors-Kata/blob/master/README.md#stage-2)
* Add support for Spock and Lizard as it is described in [nickmcdowall's Stage 3](https://github.com/nickmcdowall/Rock-Paper-Scissors-Kata/blob/master/README.md#stage-3)
* Add richer result object with the action that winner choice does on the looser, for example _rocks **crushes** scissors_
…Referee

**Attention!** something is wrong: All test passed when they should not. See the next commit for more information.
Since in commit 9975412 I made together the changes in the tests and the new implementation, I did not verify that test failed.

**Lesson learned** Always verify that tests fails before fix them.
I have removed redundant rules, but I am still not happy with OOP approach, I still prefer rule based one.
@andresmoschini
Copy link
Member Author

Added a new "explanation" feature. I wanted to check how refactoring painless is each approach.

Copy link
Member

@AlphaGit AlphaGit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the solution! It took me a bit to figure out why the testing solution required so many classes, but when I understood it, it was clearly the best approach.

Regarding the game logic, I left some questions about naming and responsibility delegation. I think that the game itself is fairly simple but trying to abstract it may have made the game a little bit more difficult to follow.

However, this is an open-ended discussion. Let me know what you think!

{
public class ExtensionRulesGameReferee : IGameReferee
{
private (GameChoice player1Choice, GameChoice player2Choice, GameResult result)[] _rules = new[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an array of tuples? It does look like them but I didn't know each tuple could have named values.

Or are these anonymous objects? In that case, I did not know they could be assigned to a class-level variable.

Please, enlight me!


Regardless, do you think that the complexity of the structure would have required a data structure? Something like a GamePrediction class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an array of tuples? It does look like them but I didn't know each tuple could have named values.

Or are these anonymous objects? In that case, I did not know they could be assigned to a class-level variable.

It is an array of ValueTuple, you can see a detailed explanation in Mark Zhou's Tech Blog, for example:

You can even give a name to every element in the value tuple, like this:

public (string name, int age) GetStudentInfo(string id)
{
    // Search by ID and find the student.
    return (name: "Annie", age: 25);
}
 
public void Test()
{
    (string name, int age) info = GetStudentInfo("100-000-1000"); 
    Console.WriteLine($"Name: {info.name}, Age: {info.age}");
}

Regardless, do you think that the complexity of the structure would have required a data structure? Something like a GamePrediction class?

You agree with you, a class would be better, but since I was using it internally I did not pay attention to that detail. Maybe the name should be related to rule in place of prediction.

Copy link
Member

@AlphaGit AlphaGit Jan 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I knew about ValueTypes, I did not know you could declare an array of value tuples with names in them. That blew my mind.

I probably (wrongly) assumed that ValueTuples where just associated objects without structure (like pretty much other language treat tuples) but it seems not to be the case.

Thank you!

public abstract class GameRefereeTest<TSut> : GameRefereeTest
where TSut : IGameReferee, new()
{
protected override IGameReferee GetSut() => new TSut();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does Sut stand for? "System under test"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is System Under Test, It is common to see this name in Mark Seemann's posts

{
TheWinnerIsPlayer1 = player1Won;
TheWinnerIsPlayer2 = player2Won;
ItIsADraw = player1Won == player2Won;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The game, conceptually, has 3 possible result states: Player1 wins, Player2 wins, the game is a Draw. Each of these 3 possible states is mutually exclusive from all the others.

The class definition has independent Player1Won (boolean: 2 states), Player2Won (boolean: 2 states), ItIsADraw (boolean: 2 states). As such, the class allows for 8 distinct states.

I'm thinking maybe the GameResult class was not a good idea as a class with state, but rather it should have been into a wrapper of an enum (GameResult) and an explanation string.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your distrust about boolean properties, but in my opinion, they represent the result in a simpler way. To avoid illegal states and inconsistencies, I make these properties read-only and used named constructors (Player1Won(), Player2Won() and Draw()) to generate the result, then the class does not allow for 8 distinct states.

Also, GameResult does not have state because all properties are read-only.

}

// This method is only necessary to adapt this new implementation to previous
// interface. If we decided to use this implementation definitivaly, it should
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor typo: definitely.

}

protected GameResult Draw() => GameResult.Draw(ToGameChoice());
public GameResult IWin(string verb, Choice looser) => _resultFactoryIfItWins(ToGameChoice(), verb, looser.ToGameChoice());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor typo: loser

?? GameResult.Draw(gameSetup.Player1Choice);
}

private GameResult Evaluate(Func<GameChoice, string, GameChoice, GameResult> playerWon, GameChoice playerAChoice, GameChoice playerBChoice)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: throughout most of the classes, players had been named player and player2, but in here, they're named playerA and playerB. It is generally better to keep a same denomination across the code for consistency.

public GameResult Evaluate(GameSetup gameSetup)
{
return Evaluate(GameResult.Player1Won, gameSetup.Player1Choice, gameSetup.Player2Choice)
?? Evaluate(GameResult.Player2Won, gameSetup.Player2Choice, gameSetup.Player1Choice)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does Evaluate need to be called twice, otherwise, the result is a Draw?

My first instinct tells me (because of the inverted parameters) that each evaluates if player1 won or player2 won, so I'd imagine that a better signature could have been PlayerWins(Player.Player1, gameSetup).

But then, the signature of the private Evaluate confuses me again: why does it receive a Func as the first parameter? Since this class is the referee -- what behavior is it delegating? Maybe renaming the arguments could help clear it up -- or maybe even a named interface would clear it up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. Since this Evaluate() overload is private, I did not pay enough attention to the name and semantics, but I will review it. Thanks!

@AlphaGit
Copy link
Member

AlphaGit commented Jan 7, 2018

I would like to some advice about how to include it in the repo, maybe it is enough adding a new readme.md

I'd say that's absolutely right. As long as you credit the source and there are no legal issues with it, feel free to do so!

@andresmoschini
Copy link
Member Author

Thanks for the review @AlphaGit ! I really appreciate it.

I just come back from vacations so I will be checking your comments during some time, maybe a comment per day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants