iTranslated by AI
Introduction to the Legends of Code & Magic Contest and Fundamentals of Card Game Statistical Analysis
Overview
Legends of Code & Magic is a programming contest where you create a bot to play a two-player card game and compete for its strength. The contest is permanently available on CodinGame.
I will cover the following two main topics:
- A brief introduction to the rules
- Analysis and interpretation of statistical data
A brief introduction to the rules
The gif above is a recording of actual bots playing against each other. [1]
If you are familiar with them, you can think of it as a game similar to Magic: The Gathering, Hearthstone, or Shadowverse. The game systems are quite similar to these.
The game is divided into two major phases: the Draft Phase and the Battle Phase.
- Draft Phase
Three cards are randomly presented from the entire pool, and each player chooses one to add to their deck. This is repeated 30 times to construct a deck.\dots - Battle Phase
Players fight their opponent using the deck constructed during the draft. You win by reducing the opponent's HP to 0 or less.\dots
First, I will explain the system of the Battle Phase, and then I will describe the types and effects of the cards.
Basic System
- Each player has 30 HP.
- The first player starts with 4 cards, and the second player starts with 5 cards. There are no mulligans. The maximum hand size is 8. You draw one card from your deck at the start of every turn.
- Some cards can increase the number of cards drawn on the following turn.
- Mana (the cost paid to use cards) starts at 1 for the first player and 2 for the second player. Every time your turn comes, your mana refills and the capacity increases by 1. The maximum capacity is 12.
- For the second player, after they use up all their mana, the capacity decreases by 1. (It is helpful to think of it as having a right to temporarily increase the mana limit by 1 for that turn only.)
- Each player has 5 runes. On the turn after a rune is lost, that player draws one additional card.
- A rune is lost the first time HP drops to 25, 20, 15, 10, or 5 or less.
- There are cards called Creatures, which can be summoned to your board. You can summon up to 6 at a time. (Card details are described later.)
- Once per turn, a creature can attack the opponent or an opponent's creature on the board. When attacking a creature, both deal damage to each other equal to their attack power, and health is reduced.
- For example, if a creature with 3 Attack/4 Health attacks a creature with 2 Attack/3 Health, your creature's health becomes 2, and the opponent's is destroyed.
- Creatures cannot attack on the turn they are summoned.
- Once per turn, a creature can attack the opponent or an opponent's creature on the board. When attacking a creature, both deal damage to each other equal to their attack power, and health is reduced.
The first and second players take turns, acting with the goal of reducing the opponent's HP to 0 or less.
Card Types
There are four main types of cards: Creatures, Green Items, Red Items, and Blue Items.
- Creatures
Cards that can be summoned to your board by paying mana. They have attack power and health, and are destroyed when health reaches 0 or less.\dots

Card Id: 4 Plated Toad 2 cost 1/5
- Green Items
By paying mana and consuming the card, you can activate its effect. These target a creature on your board to make it stronger.\dots

Card Id: 120 Venomfruit 2 cost 1/0 Give a friendly creature +1/+0 and Lethal.
- Red Items
By paying mana and consuming the card, you can activate its effect. These target an opponent's creature on the board to give it disadvantageous effects such as reducing its attack or health.\dots

Card Id: 145 Cursed Sword 3 cost -2/-2 Give an enemy creature -2/-2.
- Blue Items
By paying mana and consuming the card, you can activate its effect. These target you, the opponent, or an opponent's creature on the board to activate an effect.\dots

Card Id: 157 Life Sap Drop 3 cost 0/-1 Deal 1 damage, gain 1 health, and draw a card.
Creature Keyword Abilities
Some creatures have abilities represented by specific keywords. [2]
- Breakthrough
When attacking a creature, if your attack power exceeds the opponent's health, the excess damage is dealt to the opponent.\dots - Charge
Can attack on the turn it is summoned.\dots - Drain
When it attacks, it restores its own health equal to the damage dealt.\dots - Guard
Opposing creatures must first target creatures with this keyword ability when attacking.\dots - Lethal
When it deals damage to a creature, that creature is destroyed.\dots - Ward
Nullifies the next damage it would receive once.\dots
Some items grant or remove keyword abilities when used on a creature.
Specifics of LOCM
LOCM is a programming contest and assumes that player actions are performed via standard input/output. Therefore, there are some differences from normal card games.
- At the start of a turn, information such as the HP, mana, and runes of yourself and your opponent, as well as the cards in hand and on the board, are input.
- Within a specified execution time limit, you must output the actions you will take that turn in a single line.
In general card games, drawing a card during a turn is common. In LOCM, due to the nature of only one input being possible per turn, there are no such effects.
Also, effects that would require decision-making by the opponent during your turn are not implemented.
Official rules and judges can be found at the following URLs. You can check the rules in more detail there.
Statistical Analysis
In CodinGame contests, you can run matches locally between your own programs.
Furthermore, by setting options, you can control the draft choices to present the same cards.
By utilizing this, you can conduct statistical analysis by repeatedly playing matches with constructed decks and collecting data from the logs. [3]
What to look at and what to know
This time, I obtained the following data:
- Game Data
- Whether the first or second player won or lost
- At which turn the victory or defeat was decided
- Card Data
- At which turn a certain card was drawn
- At which turn a certain card was played
By combining this data, you can investigate statistics such as:
- Win rate in matches where a certain card was drawn (hereafter referred to as DrawnWR)
- Win rate in matches where a certain card was played (hereafter referred to as PlayedWR)
- Win rate for each turn a certain card was drawn in matches where it was drawn
- Win rate for each turn a certain card was played in matches where it was played
Furthermore, considering what we want to know by investigating these, examples include:
- From a player's perspective
- I want to check the strength of each card. If there are weak cards, I want to swap them out to increase the win rate.
- I want to analyze the strengths and weaknesses of the deck and apply it to my playstyle.
- From an organizer's perspective
- I want to analyze where to make adjustments to balance the game.
Playing with the Same Deck
I will have both players use the following deck list and analyze its statistics.
- By controlling the draft choices and the drafting algorithm, the decks for the two competing bots are fixed.
- During the Battle Phase, the thought routines for the two bots are identical.
Since it's 30 cards and quite long, you don't need to read through it all. Images of cards are attached where necessary for checking details.
Deck List Details
| Id | Name | Type | Cost | Damage | Health | Abilities | PlayerHP | EnemyHP | CardDraw | Text Description |
|---|---|---|---|---|---|---|---|---|---|---|
| 3 | Beavrat | creature | 1 | 2 | 2 | ------ | 0 | 0 | 0 | |
| 7 | Rootkin Sapling | creature | 2 | 2 | 2 | -----W | 0 | 0 | 0 | |
| 12 | Woodshroom | creature | 3 | 2 | 5 | ------ | 0 | 0 | 0 | |
| 15 | Pouncing Flailmouth | creature | 4 | 4 | 5 | ------ | 0 | 0 | 0 | |
| 19 | Foulbeast | creature | 5 | 5 | 6 | ------ | 0 | 0 | 0 | |
| 21 | Crested Scuttler | creature | 5 | 6 | 5 | ------ | 0 | 0 | 0 | |
| 23 | Titan Cave Hog | creature | 7 | 8 | 8 | ------ | 0 | 0 | 0 | |
| 29 | Steelplume Nestling | creature | 2 | 2 | 1 | ------ | 0 | 0 | 1 | Summon: Draw a card. |
| 32 | Sandsplat | creature | 3 | 3 | 2 | ------ | 0 | 0 | 1 | Summon: Draw a card. |
| 33 | Chameleskulk | creature | 4 | 4 | 3 | ------ | 0 | 0 | 1 | Summon: Draw a card. |
| 37 | Eldritch Multiclops | creature | 6 | 5 | 7 | ------ | 0 | 0 | 1 | Summon: Draw a card. |
| 45 | Night Howler | creature | 6 | 6 | 5 | B-D--- | -3 | 0 | 0 | Summon: You lose 3 health. |
| 48 | Venom Hedgehog | creature | 1 | 1 | 1 | ----L- | 0 | 0 | 0 | |
| 53 | Possessed Abomination | creature | 4 | 1 | 1 | -C--L- | 0 | 0 | 0 | |
| 56 | Giant Louse | creature | 4 | 2 | 7 | ------ | 0 | 0 | 0 | |
| 65 | Steelplume Defender | creature | 2 | 2 | 2 | -----W | 0 | 0 | 0 | |
| 66 | Staring Wickerbeast | creature | 5 | 5 | 1 | -----W | 0 | 0 | 0 | |
| 68 | Giant Squid | creature | 6 | 7 | 5 | -----W | 0 | 0 | 0 | |
| 82 | Slithering Nightmare | creature | 7 | 5 | 5 | B-D--W | 0 | 0 | 0 | |
| 88 | Abyss Nightmare | creature | 5 | 4 | 4 | -C---- | 0 | 0 | 0 | |
| 98 | Lilly Hopper | creature | 3 | 2 | 4 | ---G-- | 0 | 0 | 0 | |
| 103 | Mutating Rootkin | creature | 4 | 3 | 6 | ---G-- | 0 | 0 | 0 | |
| 112 | Rootkin Leader | creature | 6 | 4 | 7 | ---G-- | 0 | 0 | 0 | |
| 118 | Royal Helm | itemGreen | 0 | 0 | 3 | ------ | 0 | 0 | 0 | Give a friendly creature +0/+3. |
| 120 | Venomfruit | itemGreen | 2 | 1 | 0 | ----L- | 0 | 0 | 0 | Give a friendly creature +1/+0 and Lethal. |
| 128 | Enchanted Cloth | itemGreen | 4 | 4 | 3 | ------ | 0 | 0 | 0 | Give a friendly creature +4/+3. |
| 139 | Grow Stingers | itemGreen | 4 | 0 | 0 | ----LW | 0 | 0 | 0 | Give a friendly creature Lethal and Ward. |
| 144 | Rune Axe | itemRed | 1 | 0 | -2 | ------ | 0 | 0 | 0 | Deal 2 damage to an enemy creature. |
| 152 | Mighty Throwing Axe | itemRed | 7 | 0 | -7 | ------ | 0 | 0 | 1 | Deal 7 damage to an enemy creature.\nDraw a card. |
| 158 | Tome of Thunder | itemBlue | 3 | 0 | -4 | ------ | 0 | 0 | 0 | Deal 4 damage. |
- The deck is constructed to be a standard midrange deck. The mana curve is as follows. (Mana curve: the distribution of costs for cards in the deck.)

Game-wide Statistics
In this section, we examine the overall game statistics, primarily focusing on the gap between the first and second players.
- Out of 4,019 matches, the first player won 2,215 and lost 1,804. The win rate for the first player is 55.11% (leaving the second player with 44.89%).
- The average turn to victory was 12.50 turns for the first player and 13.53 turns for the second player. The overall average turn to victory is 12.96 turns.
Below is a graph showing the number of victories for the first and second players per turn.

From these data, we can observe the following:
- In matches ending on turns 5, 6, and 7, the second player has a higher win rate.
- In matches ending between turns 8 and 14, the first player has a higher win rate.
- As matches become longer, the second player tends to win more often.
- Specifically, for matches lasting 13 turns or more, the first player's win rate is 47.42%, while the second player's is 52.58%.
Since the deck lists are identical, the advantages and disadvantages for the first and second players are likely due to the game system itself.
- Factors favoring the first player:
- (Obviously) they act first.
- This factor seems significant in why the first player leads in matches ending between turns 8 and 14.
- (Obviously) they act first.
- Factors favoring the second player:
- They can use 1 extra mana once.
- This factor seems significant in why the second player leads in matches ending on turns 5, 6, and 7.
- they start with one more card.
- This factor seems to contribute to the higher win rate for the second player in matches lasting 13 turns or more.
- They can use 1 extra mana once.
Card Statistics
In this section, we check the statistics for individual cards.
First, let's look at the PlayedWR and DrawnWR for each card. From left to right: Card ID, Cost, PlayedWR, First Player PlayedWR, Second Player PlayedWR, DrawnWR, First Player DrawnWR, and Second Player DrawnWR.
| CardId | Cost | PlayedWR | FirstPlayedWR | SecondPlayedWR | DrawnWR | FirstDrawnWR | SecondDrawnWR |
|---|---|---|---|---|---|---|---|
| 3 | 1 | 49.05% | 51.76% | 46.30% | 49.85% | 52.52% | 47.15% |
| 7 | 2 | 51.83% | 54.57% | 49.10% | 52.17% | 54.97% | 49.40% |
| 12 | 3 | 49.70% | 51.36% | 48.05% | 49.40% | 51.99% | 46.87% |
| 15 | 4 | 48.29% | 49.71% | 46.86% | 47.81% | 50.35% | 45.33% |
| 19 | 5 | 48.38% | 49.31% | 47.43% | 47.89% | 50.29% | 45.53% |
| 21 | 5 | 49.40% | 49.03% | 49.79% | 47.75% | 49.79% | 45.78% |
| 23 | 7 | 48.65% | 49.34% | 47.94% | 47.46% | 50.29% | 44.70% |
| 29 | 2 | 50.59% | 52.68% | 48.48% | 50.20% | 53.63% | 46.84% |
| 32 | 3 | 51.11% | 53.16% | 49.01% | 49.37% | 52.85% | 46.02% |
| 33 | 4 | 51.82% | 51.55% | 52.10% | 48.40% | 51.20% | 45.78% |
| 37 | 6 | 49.58% | 49.36% | 49.81% | 47.24% | 49.43% | 45.12% |
| 45 | 6 | 50.77% | 50.42% | 51.16% | 45.72% | 48.61% | 42.93% |
| 48 | 1 | 51.74% | 53.23% | 50.24% | 52.11% | 54.01% | 50.24% |
| 53 | 4 | 48.17% | 50.70% | 45.72% | 49.77% | 52.91% | 46.67% |
| 56 | 4 | 48.25% | 50.74% | 45.74% | 47.63% | 50.84% | 44.54% |
| 65 | 2 | 51.58% | 53.67% | 49.47% | 52.10% | 54.34% | 49.84% |
| 66 | 5 | 50.56% | 51.84% | 49.29% | 48.94% | 51.44% | 46.52% |
| 68 | 6 | 50.54% | 53.25% | 47.87% | 50.86% | 53.59% | 48.15% |
| 82 | 7 | 49.45% | 50.57% | 48.29% | 47.60% | 50.03% | 45.23% |
| 88 | 5 | 47.05% | 47.97% | 46.13% | 47.81% | 50.14% | 45.55% |
| 98 | 3 | 49.03% | 51.19% | 46.86% | 49.43% | 52.51% | 46.36% |
| 103 | 4 | 47.74% | 49.91% | 45.57% | 48.35% | 50.89% | 45.86% |
| 112 | 6 | 46.76% | 46.79% | 46.72% | 47.50% | 50.85% | 44.19% |
| 118 | 0 | 48.79% | 50.99% | 46.67% | 49.95% | 52.24% | 47.73% |
| 120 | 2 | 48.22% | 50.31% | 46.15% | 48.23% | 50.66% | 45.87% |
| 128 | 4 | 51.26% | 53.65% | 48.88% | 47.99% | 50.97% | 45.16% |
| 139 | 4 | 51.25% | 52.50% | 49.98% | 48.96% | 51.37% | 46.60% |
| 144 | 1 | 48.30% | 50.55% | 46.07% | 49.86% | 52.07% | 47.67% |
| 152 | 7 | 48.24% | 48.20% | 48.28% | 48.45% | 51.28% | 45.74% |
| 158 | 3 | 50.31% | 47.09% | 53.82% | 47.48% | 50.45% | 44.54% |
PlayedWR and DrawnWR
Since information in a table can be hard to read, let's convert it into a more visible format to check card statistics. Below are scatter plots showing the relationship between card cost and PlayedWR, as well as card cost and DrawnWR.


For example, we can observe the following:
- There appears to be little correlation between cost and PlayedWR (correlation coefficient
).r = -0.23 - There seems to be a slight negative correlation between cost and DrawnWR (correlation coefficient
).r = -0.63
The reason for the negative correlation between cost and DrawnWR becomes clearer when looking at statistics per turn. Therefore, let's examine the turn-by-turn statistics.
PlayedWR and DrawnWR per Turn
As it is difficult to look at turn-by-turn statistics for every card, let's focus on two specific cards to check the data. These two cards are:

Card Id: 3 Beavrat 1 cost 2/2

Card Id: 82 Slithering Nightmare 7 cost 5/5 Breakthrough, Drain, Ward
The PlayedWR and DrawnWR for each turn are as follows:


From these graphs, we can interpret the following, focusing up to around turn 15, which is after the average victory turn:
- For Beavrat, both PlayedWR and DrawnWR gradually decrease as the number of turns increases.
- For Slithering Nightmare, PlayedWR gradually decreases as the number of turns increases.
- Since it can only be played from the second player's turn 6 onwards, there are no statistics before turn 6.
- For Slithering Nightmare, the DrawnWR is low in the early game but increases slightly after the turn it becomes playable.
Returning to the relationship between cost and DrawnWR:
- Beavrat is a 1-cost creature, so naturally, it is a card you are happy to draw early. Conversely, Slithering Nightmare is a 7-cost creature, so it is a card you are happy to draw from turn 7 onwards.
- Drawing Slithering Nightmare during turns when it cannot be used can be said to lower the win rate.
- This can be viewed not just as this card being unusable, but also as it taking the place of another card that could have been used. Therefore, in a game system with a small hand size, the effect of early-game DrawnWR becoming lower is thought to be significant.
- Drawing Slithering Nightmare during turns when it cannot be used can be said to lower the win rate.
- In other words, high-cost cards with long periods where they cannot be used tend to have lower DrawnWR.
- Conversely, for low-cost cards, the benefit of drawing them late is lower than for high-cost cards. The balance of these factors results in the final DrawnWR, and in this case, high-cost cards are evaluated lower.
Finally, let's consider whether PlayedWR or DrawnWR is more suitable for investigating card strength.
We have seen that DrawnWR reflects effectiveness after a card is drawn. Furthermore, when considering what card strength is, it is clear that it is better to consider not only the strength when used but also the impact when it cannot be used.
Therefore, we can see that DrawnWR is more suitable than PlayedWR for evaluating card strength.
Strong and Weak Cards
Based on the discussion so far, here are some cards considered strong.

Card Id: 7 Rootkin Sapling 2 cost 2/2 Ward
- Steelplume Defender is the exact same card.

Card Id: 48 Venom Hedgehog 1 cost 1/1 Lethal

Card Id: 53 Possessed Abomination 4 cost 1/1 Charge, Lethal

Card Id: 66 Staring Wickerbeast 5 cost 5/1 Ward

Card Id: 68 Giant Squid 6 cost 7/5 Ward

Card Id: 152 Mighty Throwing Axe 7 cost 0/-7 Deal 7 damage to an enemy creature.
Draw a card.
- Many of these creatures have the keyword abilities Lethal or Ward.
- These allow a single card to destroy multiple opponent creatures, or destroy a higher-cost creature even if only one can be destroyed.
- Mighty Throwing Axe can be expected to play the role of destroying a relatively strong creature summoned by the opponent on the previous turn while increasing your draw for the next turn.
- While these are qualitatively understood to be strong, their strength can also be inferred from the statistics alone.
Conversely, here are some cards considered weak based on the discussion so far.

Card Id: 45 Night Howler 6 cost 6/5 Breakthrough, Drain, Summon: You lose 3 health.

Card Id: 120 Venomfruit 2 cost 1/0 Give a friendly creature +1/+0 and Lethal.

Card Id: 158 Tome of Thunder 3 cost 0/-4 Deal 4 damage.
- Items like Venomfruit and Tome of Thunder are cards that, even if they could be used in the current turn, often have situations where it's better not to use them from the perspective of the entire match.
- For example, if the opponent's board is empty and you can use Tome of Thunder, it's stronger to use it against a creature the opponent summons next rather than using it to chip away at the opponent's HP.
- Since LOCM is a contest, there is a 100ms execution time limit, making it difficult to perform deep look-aheads. Therefore, cards that are difficult to use effectively tend to be evaluated poorly.
Patterns for Overcoming the Second Player's Disadvantage
As mentioned earlier, the win rate for the second player was 44.89%. We will investigate what happens in matches where the second player wins.
Below are the PlayedWR per turn for the second player for several cards.


Card Id: 65 Steelplume Defender 2 cost 2/2 Ward

Card Id: 118 Royal Helm 0 cost 0/3 Give a friendly creature +0/+3.

Card Id: 128 Enchanted Cloth 4 cost 4/3 Give a friendly creature +4/+3.

Card Id: 139 Grow Stingers 4 cost 0/0 Give a friendly creature Lethal and Ward.
Focusing on the Green Items among the cards above, we can see that in games where these could be used in early turns, the win rate is around 60%. However, to use these, a creature must already exist on your board when the turn starts.
Ultimately, we can see that it is necessary to create a situation where you have a board advantage in the early game.
Below are the DrawnWR per turn for the second player for the same cards.

Looking at this, it's clear that simply drawing these Green Items early does not significantly improve the win rate. These cards can amplify an existing advantage, but they cannot turn a disadvantageous situation into an advantageous one.
Why the average DrawnWR is higher or lower than the win rate
The win rate for the first player is 55.11%, but the maximum DrawnWR for the first player is 54.97%. In other words, the DrawnWR for every card is lower than the win rate.
Intuitively, one might expect the average DrawnWR to be around the same value as the win rate, so why is this not the case?
Recalling the overall game statistics, the win rate deteriorated as the match became longer.
In other words, in winning matches, players drew fewer cards on average compared to losing matches.
Let's actually verify this. The total number of cards drawn in winning matches was 44,399, averaging 20.04 cards. In contrast, the total number of cards drawn in losing matches was 41,723, averaging 23.13 cards. The overall DrawnWR becomes
Ultimately, when the number of cards drawn differs between winning and losing matches, the DrawnWR will not align with the win rate.
Afterword
When it comes to statistical analysis of card games, services like HSReplay.net and Untapped.gg are famous. While these are convenient and interesting services, ever since I found the LOCM contest, I've wanted to do everything from processing raw data to aggregation and analysis myself, which led to this article.
Since turn-by-turn DrawnWR cannot be seen on those sites, I feel there was value in conducting this aggregation.
Initially, I planned to add a chapter analyzing data from matches between aggro and control decks, but it became too long and there was already plenty to write about even with the same deck list, so I decided to omit it.
-
Clicking the gif will take you to a page where you can watch a clean replay. ↩︎
-
I won't explain the behavior when keyword abilities are combined, but it's easier to think about whether the result was that damage was dealt. ↩︎
-
For those who want to run local matches themselves: A quick web search suggests that building a local match environment using https://github.com/dreignier/cg-brutaltester is common, but the version listed as the LOCM Referee will not work because LOCM has been updated and the I/O format has changed. While it might not be the most elegant way, you can run it by replacing
gameRunner.addAgent(PlayerEmpty.class);withgameRunner.addAgent(SOME_COMMAND);insrc/test/java/Main.javafrom https://github.com/CodinGame/LegendsOfCodeAndMagic. ↩︎ -
DrawnWR refers to the win rate when a certain card is drawn. Here, it can be thought of as finding the proportion of times the act of drawing a card belonged to a winning match. ↩︎

Discussion