----------- review the split line-----------
This series aims to develop an online version of the "who is undercover + killing games" Ghost game , recording the whole process of development starting from the analysis game , through this project to familiarize yourself with the object-oriented solid principles, improve the understanding of design patterns, refactoring.
Index Directory :
0. Index (in continuous update)
1. Game Flow Introduction and technology selection
2. Design Business Objects and object Responsibilities (1) (illustrated old version)
3. Design business object and object Responsibility Division (2) (old version code anatomy)
4. Design business object and object responsibility Division (3) (new version business object design)
5. Business object Core Code writing and unit testing (before the game starts: Players seated and exited)
6. Business object Core Code writing and unit testing (game start: Draw, sub-role, open Ghost discussion mode)
7. Code and test (ghost discussion, Ghost vote)
8. Code and test (player speaking)
----------- review End Split Line-----------
First put the source code, SVN address: https://115.29.246.25/svn/Catghost/
Account: Guest Password: Guest (support source code download, set read-only permission, until I basically make the initial version and then put to git)
----------- This chapter begins the split line----------
is still done according to the sequence diagram (increasing the sense of the class diagram, sequence diagram to the guidance of the Code)
1. Players Speak
1-3 steps in the sequence diagram: After the Currentspeaker has spoken, the Speakmanager is recorded and displayed, and the next player allowed to speak is set.
In Speakmanager in fact already written most of the content of Playerspeak (), just add a line setnextspeaker ().
Public voidPlayerspeak (player player,stringstr) { if(Isghostdiscussing ()) {checkghostspeaker (player); } Else{checkcurrentspeaker (player); } Addtorecord (Formatspeak (player. Nickname, str)); Setnextspeaker (player);}/// <summary>///set up the next speaker/// </summary>/// <param name= "Currentplayer" >Current Speakers</param>Private void setnextspeaker(Player currentplayer) {player[] players=Getplayermanager (). Getallplayerarray (); Player Nextplayer=NULL; for(inti =0; I < players. Length; i++) { if(Players[i]. Equals (Currentplayer)) {if(i = = players. Count ()-1) {Nextplayer= players[0]; Break; } Nextplayer= Players[i +1]; Break; }} setspeaker (Nextplayer);}
The test code is as follows: use for to test if the setnextspeaker is satisfied with the loop statement
[TestMethod] Public voidplayerspeakunittest () {joingame (); //Ghost discussing ...Player Electplayer= Getplayermanager (). Getallplayerarray () [5];//elect playerghostvoting (Electplayer); for (int i = 0; i < 2; i++ ) { playerspeaking(Electplayer, i); } showplayerlisten ();}//Private MethodPrivate void playerspeaking(Player Starter,intTimes ) { BOOLCanspeak =false; if(Times >0) Canspeak =true; foreach(Player PinchGetplayermanager (). Getallplayerarray ()) {if(p.equals (starter)) {Canspeak=true; } if(canspeak) {P.speak ("i ' m"+p.nickname); } }}
Test results are as expected: all players can see, and from the 6th player (Kimi) to start speaking, two rounds after the end. Because I did not join the Loopmanager to monitor, so to Vivian before the end. After joining Loopmanager, you should finish the speech at Coco.
Looking at the code metrics again, there seems to be room for progress: Judging too much leads to increased cyclomatic complexity, and class coupling can be appropriately reduced.
First look at the current setnextspeaker () code
/// <summary>///set up the next speaker/// </summary>/// <param name= "Currentplayer" >Current Speakers</param>Private voidSetnextspeaker (Player currentplayer) {player[] players=Getplayermanager (). Getallplayerarray (); Player Nextplayer=NULL; for(inti =0; I < players. Length; i++) { if(Players[i]. Equals (Currentplayer)) { If (i = = players. Count ()- 1 ) {nextplayer= players[0]; Break; } Nextplayer= Players[i +1]; Break; }} setspeaker (Nextplayer);}
The feel of the inner most if (already bold) is simply to determine if the loop is to the end of the array, starting with the first one. The bad taste has arisen-is that what Speakmanager should do? The main task of the whole Setnextspeaker () is to set the next player to allow the speech, you do not give me the entire current player is who, you directly to me to the next player who is not? So, the whole setnextspeaker () has crossed its own responsibilities, and whose duties have been occupied? Who knows who the next player is? Does player know that--I don't know, only the player manager Playermanager know, because that's what he does--to maintain the player list!
To mention, it is easy to think of the state pattern--player to replace a player automatically, that is, the state of the Currentspeaker flag is changing--but unfortunately, this is not appropriate: first, the player himself should not know who he is next, It's the playermanager that knows, and if the player knows his next one, then he has the right to decide who the next one is (state mode is the next state to easily increase/change delivery), which is not the same as the game rules. It also requires a global outsider Playermanager to operate (a bit of builder's flavor in builder mode).
First, add the Getnextplayer () method to the Playermanager:
/// <summary>///back to next player/// </summary>/// <param name= "Currentplayer" >Current Player</param>/// <returns>Next player</returns> Publicplayer Getnextplayer(player Currentplayer) {checkplayer (Currentplayer); Player result=NULL; for(inti =0; I < Getallplayerarray (). Length; i++) { if(Getallplayerarray () [i]. Equals (Currentplayer)) {if(i = = Getallplayerarray (). Length-1) {result= Getallplayerarray () [0]; }
Else
{Result= Getallplayerarray () [i +1];
} } } returnresult;}
The Setnextspeaker () in the corresponding Speakmanager will be very simple:
/// <summary> /// set up the next speaker /// </summary> /// <param name= "Currentplayer" > Current Speakers </param> Private void Setnextspeaker (Player currentplayer) { = getplayermanager (). Getnextplayer(currentplayer); Setspeaker (Nextplayer);}
In terms of code metrics: reduced class coupling (separation of duties that do not belong), but cyclomatic complexity passed to Playermanager. Need to continue optimization:
We can observe that the key to finding the next player is the seat number, regardless of the current player's judgment, or the next player's screening, through the seat number, so consider extracting the Getseatorder () method:
Publicplayer Getnextplayer (player Currentplayer) {player[] players=Getallplayerarray (); intCurrentseatorder = getseatorder(Currentplayer); returnCurrentseatorder = = players. Length-1? players[0]: Players[currentseatorder +1];}/// <summary>///back to player seat number/// </summary>/// <param name= "Player" >player</param>/// <returns>Seat number</returns>Private int Getseatorder(player player) {checkplayer (player); for(inti =0; I < Getallplayerarray (). Length; i++) { if(Getallplayerarray () [i]. Equals (player)) {returni; } } return-1;}
Test it, no problem. Look at the code metrics again: Well, it's the part of the player that has finished speaking.
2. Circular Management
4-6 steps in the sequence diagram: After setting the next allowed speaker, Loopmanager is responsible for checking whether the cycle is over (two loops in the first round), and if it is not finished, the Speakmanager sends a system order to tell everyone to start voting, and the voting session is not allowed to speak. , so we should do some treatment to currentspeaker.
First increase the checkisend () check at Speakmanager.setnextspeaker (): If the speech is finished, set the currently allowed speaker to be empty, and the system prompts.
/// <summary>///set up the next speaker/// </summary>/// <param name= "Currentplayer" >Current Speakers</param>Private voidsetnextspeaker (player Currentplayer) {player Nextplayer=Getplayermanager (). Getnextplayer (Currentplayer); Setspeaker (Nextplayer); chechisloopend (Nextplayer);}/// <summary>///check if loop ends/// </summary>/// <param name= "Currentplayer" >Current Player</param>Private void chechisloopend(Player currentplayer) {if(Getloopmanager (). Isloopend (Currentplayer)) {Setspeaker (NULL); Systemspeak (GetSetting (). Getappsettingvalue ("Votetip")); }}
Then fill the Loopmanager with the Isloopend () method: its attention to the first round is to speak two laps.
public bool Isloopend (Player currentplayer) { if (Currentplayer.equals (this ._loopstarter) "{ /span>if (_isfirstloop) {_isfirstloo P = false ; return false ; return true ; return false ;}
Test results need to make some small adjustments: the Speakmanager.checkcurrentspeaker () of the "No Off-field" exception first disabled, otherwise it will be an error can not see the output.
As can be seen, the first round of each person spoke two times, and the second (and later) each person can only speak once, the last box outside the Kimi-vivian statement is because the exception is not blocked, as expected. Test passed. Code metrics are ok too, so don't map them.
In this, the circular management is also completed.
Perhaps friends will ask: what is the exception to the treatment at the end? Do you return a string or terminate the program? Or will it not be handled? -These are all considered in the UI, this link is only for the core models code writing. Must not be confused for a moment too much to consider-- rice to eat a mouthful, code to write in one place.
Online Ghost Game Development III-code and test (player speaking)