Tuesday, January 5, 2010

Version 2 - A New Hope

I'm slowly making progress on Forge version 2. Right now it has 11 classes, 102 methods, 117 lines of comments and 280 lines of code. There are a high number of comments because I am trying to be very thorough for myself and for whoever else that may read and update my code in the future. (And yes, the title of this article references Star Wars Episode IV, you know the GOOD "Star Wars".)

Counting the total number of lines of code (loc) for a project gives you a rough estimation on how big and complex the project is. The loc for any project can be calculated many different ways. I wrote my own little program that only counted "real" lines of code. I defined a "real" line of code to be
1. Not a comment.
2. Not whitespace.
3. Not a bracket {}, since I put brackets on separate lines.

Basically the code for version 2 just generates all of the phases for both players. While this seems trivial it is not. Each player's turn has approximately 28 phases which are listed below.

The phase array is a two-dimensional array of Strings. Index 0 is the active player (the player who's turn it is), index 1 is the player who has priority (can cast a card or activate an ability), and index 2 is the phase name. In order to generate the computer's phases I just swap the Human and Computer players, which are just Strings.

Some of these "phases" are technically steps but it doesn't matter because the mana pool empties at the end of every phase and step. Forge version 1 and 2 have the phases "Declare Blockers" and "After Declare Blockers". A few of these phases are "artificial" and don't officially exist such as "After Declare Blockers", which is when Forge lets players play cards. On a side note, I will have to make sure that the mana pool empties according to the rules and the "artificial" phases don't cause any problems.

Magic has million tiny details and technically during the untap and cleanup phases no player gets priority. For the untap and cleanup phases maybe I should use an empty string "" for the priority player.

final String Human = "Human";
final String Computer = "Computer";

private String humanTurn[][] =
{
//active player, priority player, phase name
//
{Human, Human, Constant.PhaseName.Untap},

{Human, Human , Constant.PhaseName.Upkeep},
{Human, Computer, Constant.PhaseName.Upkeep},

{Human, Human , Constant.PhaseName.Draw},
{Human, Computer, Constant.PhaseName.Draw},

{Human, Human , Constant.PhaseName.Main1},
{Human, Computer, Constant.PhaseName.Main1},

{Human, Human , Constant.PhaseName.Begin_Combat},
{Human, Computer, Constant.PhaseName.Begin_Combat},

{Human, Human, Constant.PhaseName.Attack},

{Human, Human , Constant.PhaseName.Attack_After},
{Human, Computer, Constant.PhaseName.Attack_After},

{Human, Computer, Constant.PhaseName.Block},

{Human, Human , Constant.PhaseName.Block_After},
{Human, Computer, Constant.PhaseName.Block_After},

{Human, Human , Constant.PhaseName.Combat_Damage},
{Human, Computer, Constant.PhaseName.Combat_Damage},

{Human, Human , Constant.PhaseName.End_Combat},
{Human, Computer, Constant.PhaseName.End_Combat},

{Human, Human , Constant.PhaseName.Main2},
{Human, Computer, Constant.PhaseName.Main2},

{Human, Human , Constant.PhaseName.At_EOT},
{Human, Computer, Constant.PhaseName.At_EOT},

{Human, Human , Constant.PhaseName.EOT},
{Human, Computer, Constant.PhaseName.EOT},

{Human, Human , Constant.PhaseName.Until_EOT},
{Human, Computer, Constant.PhaseName.Until_EOT},

{Human, Human, Constant.PhaseName.Cleanup}
};

14 comments:

Silly Freak said...

i wonder if you can handle things like additional combat phases this way?

moxdev said...

Is there a reason you refer to players as "Human" and "Computer"? Why not Player 1 & Player 2? From what I understand, if you went with a slightly more generic approach, you could do things like have 2 AIs play against each other more easily. Even have more than 2 players someday...

wololo said...

You'd rather use a list than an array for the phases. I've tried with an array and moved away from it. See my article on the subject here

Also, as moxdev said: don't limit yourself to Human VS Computer straight from the start, this is a bad idea and actually makes your code harder to read than if all players are "just" players.

Good luck with version2 :)

nantuko84 said...

could you please share your program that counts lines of code?

Marek14 said...

Yes, at the very least the turn structure should be dynamic, to accommodate additional phases/turns.

Why is the priority specified in here, though? Wouldn't it be better to handle it elsewhere and only use the turn structure as a sort of "schedule"? That way, if multiplayer is ever implemented, you won't have to rewrite it. Also, specifying both players' turns in the same structure seems weird to me. Can't there just be variables for "active player" and "priority"?

wololo said...

I personally use something named cloc.pl to count lines of code. It is extremely easy to use and probably much more advanced than forge's script. Just google for it ;)

Huggybaby said...

Don't forget to make it easy to put a GUI on everything.

Forge said...

Silly Freak,

First and double strike do need additional combat phases. I probably should include the extra phases and just skip them when they aren't needed.

moxdev,

I refer to players as "Human" and "Computer" in order to keep things straight in my head. Also I'm used to only having only a human player play against the AI.

wololo,

In the code I actually convert the array into a list (ArrayList). Compared to lists, arrays are very inflexible.

nantuko84,

Sure I'll share my Java counting code. I didn't think anyone would want it. I'll post it in a few days.

Marek14,

"Why is the priority specified in here, though?"

To make things easier for me. It makes sense because priority is sort of hardcoded into the game anyways. Card effects change many of Magic's rules but priority always stays the same.

Huggybaby,

I'm always thinking about the gui. The code is designed to be used by the gui.

Silly Freak said...

i meant things like relentless assault: "After this main phase, there is an additional combat phase followed by an additional main phase."

telengard said...

RE:


First and double strike do need additional combat phases. I probably should include the extra phases and just skip them when they aren't needed.


Exception based programming like this leads to a mess in your core engine IMO. Ideally you'd have a way to "inject" or remove phases or modify the phase structure outside of the core engine.

I also agree w/ the comment about naming the players. I just have players. They could be both human, one AI/one human, both AI, etc.

~telengard

Forge said...

Maybe I won't add the extra phases for first/double strike and make the "rules engine" do it.

RulesEngine.handleFirstOrDoubleStrike()

Moxy said...

Maybe I'm just an old nut but...
Wouldn't it be better to use two constants instead of "Human" and "Computer" or "Player 1" and "Player 2". In this way you can use a smaller primitive, say an int in lieu of a string and associate a name with them in the UI code.

You could do something similar with the phases.

For instances
private static final int HUMAN = 0;
private static final int COMPUTER = 1;

private int humanTurn[][] =
{

{HUMAN, HUMAN, Constant.PhaseName.Untap},

{HUMAN, HUMAN , Constant.PhaseName.Upkeep},
};

Now that list is smaller in memory. I'm not sure how you're using this particular list but it doesn't seem that integrating the active player, the player with priority and the phase together is all that great of an idea. In fact the active player is always going to be the same during any one particular turn.

The player with priority (almost) always bounces back and forth between the two players and if you want to throw a third in there then it will just become a list itself you cycle through with the head pointer pointed at the active player.

Lastly more often than not the phase order is fairly static and separate from the players.

moxy said...

Might I suggest you split this into two lists? And create a Phase class that contains each of your steps.

private static final int HUMAN = 0;
private static final int COMPUTER = 1;
private static final Phase BEGINNING = 0;
private static final Phase MAIN = 1;
private static final Phase COMBAT = 2;
private static final Phase END = 3;

private int Active_Player = 0;
private LoopedList players = new LoopedList();

players.add(new Integer(HUMAN));
players.add(new Integer(COMPUTER));

players.setPointerTo(new Integer(Active_Player));

private List phases = new List();
phases.add(BEGINNING);
phases.add(MAIN);
phases.add(COMBAT);
phases.ad(END);

Now to start using it you could do something like this:
Phase currentPhase;
while(phases.hasNext()){
currentPhase = phases.pop();
currentPhase.nextStep(Active_Player, players);
}
//currentPhase should be END now if you want to assert that.
Active_Player = (Active_Player == HUMAN) ? COMPUTER : HUMAN;
players.setPointerTo(new Integer(Active_Player));


When you go to display some of this information in the UI you can check the running value to the constant and display a stored string. This could also help with translations if you use something like the ListResourceBundle Class to hold your strings. I'm not hip to that so I'll do it the way I used to.

Label activePlayerDisplay = new Label();
activePlayerDisplay.setText = (Active_Player == HUMAN)? "Human" : "Computer";

Label priorityPlayer = new Label();
priorityPlayer.setText = (players.getCurrent() == HUMAN) ? "Human" : "Computer";



Label currentPhaseDisplay = new Label();
currentPhaseDisplay.setText = currentPhase.toString();

Label currentStepDisplay = new Label();
currentStepDisplay.setText = currentPhase.getCurrentStep().toString();

moxy said...

By doing something like this and NOT hard coding the text "Human" and "Computer" you're not locking yourself into strict labels, you can easily add a third player easier (even easier if you use lists instead of my ternary operators here), you could even add something like a feature for the player to give himself or the computer a name to be displayed. Perhaps you could have different AI's that play different and could use different names to distinguish them.

You're also separating the phases and isolating them into one object like they really are. If you need to add a phase because of a card say a second combat phase it's simply adding it to a one dimensional list not a two dimensional array.

If you need to skip a turn you won't need to mess with a two dimensional array just set the Active_Player variable to the current player.

If I'm writing a card and all I care about are the phases. I can retrieve only information about phases.

If all I want is information about the active player all I need to do is query the Active_Player variable.

Again I don't know how you're using this two dimensional array but hope you reconsider grouping to much non related information together so that it is easier to use and understand by others later and easier to extend in the future.