1
\$\begingroup\$

While I was, sort of, studying the various TCGs around, concepts, similarities, differences and how they could be implemented, if designed to be in a virtual environment (e.g. PC game) I obviously noticed the most common element between them all: The Board

As a gaming developer community, you may know that The Board is where all the "action" takes place. It's similar to an MVC FrontController, handling everything of the game.

Programmatically speaking, several Observers would be implemented within it, to catch-up and interfere if needed in between every step taken by every card.

Most of them comes in a pyramid-a-like format like this:

              +------+
              |  12  |
              +------+

           //          \\

      +------+        +------+
      |  10  |   ==   |  11  |
      +------+        +------+

   //         \\   //        \\

+------+      +------+      +------+
|  07  |  ==  |  08  |  ==  |  09  |
+------+      +------+      +------+

   ||   \\   //   ||   \\   //  ||

-----------------------------------

+------+      +------+      +------+
|  01  |  ==  |  02  |  ==  |  03  |
+------+      +------+      +------+

   \\        //      \\       //

      +------+        +------+
      |  04  |   ==   |  05  |
      +------+        +------+

           \\          //

              +------+
              |  06  |
              +------+

This varies from game to game and can be 1x1, 3x3, 6x6, more or even less. This also can be like I designed above, vertically (which is easier to draw >.<), or horizontally, which I think is the best way for a programming implementation, specially considering a multi-platform goal.

In the diagram above, the lines and doubled equal signs represent possible interactions between cards, like, for example, supportive cards which sole objective in the game is to support (doh) adjacent cards somehow while disengaged ("not in combat").

Last but not least, cards may move between available spaces, be them on their own side (below/above the middle line) or in opponent's territory (the other half).

However, although I took notice of all these raw aspects, I couldn't find a way to make it work, again, programmatically speaking:

  • Should I use a multi-dimensional arrays or something better?
  • How to identify that some space is free or occupied?
    • If occupied, how to identify if it is by a "team mate" card or opponent's
    • If it's free, how to find its neighborhood to identify possible "team mates" and targets

I tried to hard-code a super structure with all possible attacker-opponent combinations, considering all possible targets for each attacker card on the front line group of 3 cards and also their adjacent supporters.

At the beginning of an hypothetical match of an hypothetical game (really), considering the cards as numbered above I came up to this, in a PHP array:

$board = [];

$board[ 1 ] = [ 't' => [ 7, 8 ],    's' => [ 2, 4 ] ];
$board[ 2 ] = [ 't' => [ 7, 8, 9 ], 's' => [ 1, 3, 4, 5 ] ];
$board[ 3 ] = [ 't' => [ 8, 9 ],    's' => [ 2, 5 ] ];

Each index of $board would represent a front line card with its possible targets under t and its supportive cards, if any, under s

Sounded good at first.

Then I simulated, all future possibilities if the first person, here named as the one on the lower half of the board, decided to make a move going to 1-7, 1-8, 2-7, 2-8 and so on and, after winning, automatically moving itself to the spot of its defeated opponent (which is mandatory in all TCGs I've seen to keep the board rotating).

This resulted in 7 different arrays:

// Active Player engages 1-7 and wins. Auto-move applied

$d1 = [];

$d1[ 8 ]  = [ 't' => [ 1, 2, 3 ], 's' => [ 9, 10, 11 ] ];
$d1[ 9 ]  = [ 't' => [ 2, 3 ],    's' => [ 8, 11 ] ];
$d1[ 10 ] = [ 't' => [ 1 ],       's' => [ 8, 11, 12 ] ];

// Active Player engages 1-8 and wins. Auto-move also applied

$d2 = [];

$d2[ 7 ]  = [ 't' => [ 1, 2 ],    's' => [ 10 ] ];
$d2[ 9 ]  = [ 't' => [ 1, 2, 3 ], 's' => [ 11 ] ];
$d2[ 10 ] = [ 't' => [ 1 ],       's' => [ 7, 11, 12 ] ];
$d2[ 11 ] = [ 't' => [ 1 ],       's' => [ 9, 10, 12 ] ];

// Active Player engages 2-7, wins. Auto-move also applied

$d3 = [];

$d3[ 8 ]  = [ 't' => [ 1, 2, 3 ], 's' => [ 9, 10, 11 ] ];
$d3[ 9 ]  = [ 't' => [ 3 ],       's' => [ 8, 11 ] ];
$d3[ 10 ] = [ 't' => [ 2 ],       's' => [ 8, 10, 11 ] ];

// Active Player engages 2-8 and wins. Auto-move also applied

$d4 = [];

$d4[ 7 ]  = [ 't' => [ 1, 2 ], 's' => [ 10 ] ];
$d4[ 9 ]  = [ 't' => [ 2, 3 ], 's' => [ 11 ] ];
$d4[ 10 ] = [ 't' => [ 2 ],    's' => [ 7, 11, 12 ] ];
$d4[ 11 ] = [ 't' => [ 2 ],    's' => [ 9, 10, 11, 12 ] ];

// Active Player engages 2-9 and wins. Auto-move also applied

$d5 = [];

$d5[ 7 ]  = [ 't' => [ 1, 2 ],    's' => [ 8, 10 ] ];
$d5[ 8 ]  = [ 't' => [ 1, 2, 3 ], 's' => [ 7, 10, 11 ] ];
$d5[ 11 ] = [ 't' => [ 2 ],       's' => [ 8, 10, 12 ] ];

// Active Player engages 3-8 and wins. Auto-move also applied

$d6 = [];

$d6[ 7 ]   = [ 't' => [ 1, 2, 3 ], 's' => [ 10 ] ];
$d6[ 9 ]   = [ 't' => [ 2, 3 ],    's' => [ 11 ] ];
$d6[ 10 ]  = [ 't' => [ 8 ],       's' => [ 7, 11, 12 ] ];
$d6[ 11 ]  = [ 't' => [ 8 ],       's' => [ 9, 10, 12 ] ];

// Active Player engages 3-9 and wins. Auto-move also applied

$d7 = [];

$d7[ 7 ]  = [ 't' => [ 1, 2 ],    's' => [ 8, 10 ] ];
$d7[ 8 ]  = [ 't' => [ 1, 2, 3 ], 's' => [ 7, 10, 11 ] ];
$d7[ 11 ] = [ 't' => [ 3 ],       's' => [ 8, 10, 12 ] ];

It grew a lot, but since all mathematical combinations that usually results in a moderate overload (specially for a web-based environment with PHP) was hard-coded, I didn't see a problem at first then and I kept going.

The next step was to consider all possible scenarios if the so called Active Player, now the former Opponent, in his own turn, decided to go in your first possible target 8-1

Assuming a winning scenario, 8 could move itself to where 1 was or then 10 could be moved instead. It's mandatory, at least one card must always exchange positions when winning, otherwise the board doesn't rotate.

However, here comes the mathematically scary, although expected, part:

  • If 8 Moves into 1, then:
    • 9, 10 or 11 can move over the previous location of 8, and then:
      • If 9 moves, 11 can move towards the former position of 9, and then:
        • If 11 moves, 10 or 12 can go to where 11 was, and then:
          • If 10 goes, 12 can jump to where 10 was
      • If 10 moves instead, 11 or 12 can move towards 10, In this case:
        • If 11 moves, 12 can go to 11
        • If 12 goes instead, 11 can be pulled back to 12
      • Now if 11 moves, then
        • 9, 10 or 12 can go to where 11 was, and then
          • If 9 goes, the flow stops
          • If 10 goes, 12 can go to 10
          • If 12 goes, 10 can pull back where 12 was

And here I stopped designing that data-structure mainly because this exponential grow wouldn't never reduce as far as other cards start leaving the fray, because in every "turn" several possible destination would still be available.

Their movement would still be restricted at once per turn, regardless the number of free spots, but still there are a lot of possibilities and I'm struggling to figure out a way to do make it viable.

All of this without mention cards I've seen in several TCGs with special abilities that allow them to jump more than one slot (usually the known as flying type). But this a future matter.

And by viable I kind of mean lightweight because, in order to explain here it obviously appears to be light, but in a real case scenario, each occupied space, for example, would store a object, with its own logic, dependency and thus, overload.

I thought about using a Binary Tree, but the nested "lower to left, higher to right" concept, at first, sounded not applicable. But I never used one before, so I might be wrong here.

I'd really appreciate some enlightenment.

\$\endgroup\$
  • 4
    \$\begingroup\$ What would be the TLDR version of your question? \$\endgroup\$ – Vaillancourt May 19 '16 at 19:55
  • \$\begingroup\$ There are only two questions, one directly related to the other, and both of them are clearly stated and can be easily found with a quick search for the question mark symbol. Honestly, I don't know you (in the community), I don't know if you have what it would take to answer the question and I don't even know if it was your who downvoted the question, but ask for a "TL;DR" in a development issue, which has been drafted, with as many details as possible, strictly to outline as many scenarios as possible, is almost preposterous for the Stack Exchange standards. \$\endgroup\$ – Bruno Augusto May 19 '16 at 21:06
  • \$\begingroup\$ I would vote to close this question as "too broad" if I could still do that. Your question seems to be mostly you having some ideas about how to do some things, but at the end it seems like you're just looking for a discussion about different kinds implementation architectures. \$\endgroup\$ – Tyyppi_77 May 22 '16 at 14:06
  • \$\begingroup\$ No way. I don't need to know how many different kinds of bricks are out there to live in a house. I explained, extensively in fact, the goal: Structure a fictional TCG board like in the "picture" in way I can easily (sort of) get their surroundings and determine if it's an empty space, a target or an "team mate" in way flexible enough to accommodate a 2x2 board or a 6x6 as well. The question just look "big" because I did my part! I kept my researching, experimenting and then updating the question with my findings \$\endgroup\$ – Bruno Augusto May 22 '16 at 16:10
4
+50
\$\begingroup\$

Card and Slot concepts should not be mixed. They're not the same. My behind is not fused with the chair on which I'm sitting. This allows me to play musical chairs.

Let's organize your data:

// A card; belongs to a player, has abilities (among which we find 'CanJump')
Card
  Owner : Player // Blue or red
  CanJump : bool // Tells if the card can jump two slots


// A spot where a card can be on the board
Slot
  Neighbours : Slot[] // Immediate neighbours. Refer to slots on the board.
  JumpNeighbours : Slot[] // Slots that are 2 slots away and to which some card can jump
                          // Refer to slots on the board.
  Card : Card // Can be null; tells if there is a card there or not.

// Basically, a set of slots
Board
  Slots : Slot[] // All the slots on the board.

Should I use a multi-dimensional arrays or something better?

No, use objects; the data structure is described above.

How to identify that some space is free or occupied?

With the data above, this should be quite clear.

If occupied, how to identify if it is by a "team mate" card or opponent's

Same.

If it's free, how to find its neighbourhood to identify possible "team mates" and targets

Again, the answer is in the structure.

This structure allows for data driven design:

The following data will stay the same during the whole game session:

Slots on board definitions; define what slots exist

1
2
3
4
5
6
7
8
9
10
11
12

Slot Neighbours definitions; define which slots are direct neighbours

1 2
1 4
1 7
1 8
2 3
2 4
2 5
2 7
2 8
2 9
...

Slot Jump Neighbours definitions; define which slots have a 'jump' connection

1 3
1 6
....

Card definitions; describe what card exist and their features

Infantry     CannotJump
Magician     CanJump
ShieldBearer CannotJump
Lancer       CanJump
...

Now on a per-game session basis:

Slot card; the start definitions of the relations between a slot and a card, with the additional information; in this case, you need to tell to which player the card belongs.

1 Infantry     Player0
2 Magician     Player0
3 ShieldBearer Player0
4 ...
..
10 Lancer      Player1
11 ...

Your game code will update the slots in which the cards are 'placed'.

You can adjust the slots and the boards on your needs.

How to identify the neighbor nodes?

Board.getSlots(2).getNeighbours()
\$\endgroup\$
  • \$\begingroup\$ Although I am already structuring everything separately, this clarifies a bit and give me some ideas, but the most important point that's still unclear to me, is how to identify the neighbor nodes. From your answer, they still seem to be manually defined, hard coded as an array or semi-automatically (perhaps by an Subject notifying an Observer after each movement) by re-referencing nodes. Also, your nodes structure looks unilateral,and the vast majority of TCGs I've been studying, are in a pyramid-a-like format. Because of this, your SlotNeighbour 1 2, in fact should be 2 3 \$\endgroup\$ – Bruno Augusto May 23 '16 at 12:08
  • 1
    \$\begingroup\$ @BrunoAugusto I edited my answer. "From your answer, they still seem to be manually defined" yes, at some point a human has to take decisions, typically a game designer will create the data for the board disposition. Not specified in there: there could be additional information in the Slots on board definitions to specify where the slot is, physically, on the board. "1 2, in fact should be 2 3" this should be cleared up now: I displayed the data at first to save vertical scrolling, now the data is clearly independent. \$\endgroup\$ – Vaillancourt May 23 '16 at 13:33
  • \$\begingroup\$ @BrunoAugusto Keep in mind that we can't answer 'how do they do it' questions. We can only answer questions of the type 'how could I achieve something like it'. There is no one single TCG game and no single one TCG game developer, thus there are many ways to make such a game. You'll have to find a way that works for your situation. \$\endgroup\$ – Vaillancourt May 23 '16 at 13:36
  • \$\begingroup\$ I read your answer a few days ago and, although your comment explained more than the answer itself, my problem is solved. Took me some time to get back here because when I passed this point of the code, several other subjects practically solved themselves like a well adjusted geared machinery. Thank you very much! \$\endgroup\$ – Bruno Augusto May 27 '16 at 20:46
1
\$\begingroup\$

I'm not 100% sure I caught your main idea between card relationships here, but to me it looks like some sort of network-like structure might work here. Your cards would have neighbours specified with some relation. It seems like you are trying to force a generic array here, but I think this is where using some traditional OOP simplifies the thinking process at least for me.

class Node
{
    Node neighbours[];

    Card card;
}

This separates the actual cards from the possible card positions. For swapping cards, you could then just easily swap the assigned cards of the required nodes. You might want to store a reference to each card's parent node in the card implementation, but that depends on the way you want the interface to work.

\$\endgroup\$
  • \$\begingroup\$ Could you elaborate more, in terms of code? \$\endgroup\$ – Bruno Augusto May 22 '16 at 21:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.