├── .gitignore ├── README.md ├── config ├── Boss.java ├── config.ini └── stub.txt ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── codingame │ │ ├── game │ │ ├── Player.java │ │ ├── Referee.java │ │ ├── engine │ │ │ ├── Action.java │ │ │ ├── ActionResult.java │ │ │ ├── Card.java │ │ │ ├── Constants.java │ │ │ ├── CreatureOnBoard.java │ │ │ ├── DraftPhase.java │ │ │ ├── EngineReferee.java │ │ │ ├── GameState.java │ │ │ ├── Gamer.java │ │ │ ├── InvalidActionHard.java │ │ │ ├── InvalidActionSoft.java │ │ │ ├── Keywords.java │ │ │ └── RefereeParams.java │ │ └── ui │ │ │ ├── CardUI.java │ │ │ ├── ConstantsUI.java │ │ │ ├── PlayerUI.java │ │ │ ├── RefereeUI.java │ │ │ └── Vector2D.java │ │ └── view │ │ ├── FXModule.java │ │ ├── endscreen │ │ └── EndScreenModule.java │ │ └── tooltip │ │ └── TooltipModule.java └── resources │ ├── cardlist.txt │ ├── cardset_template.html │ └── view │ ├── assets │ ├── atlas.png │ ├── board.jpg │ ├── logo.png │ ├── newturn.png │ ├── spritesheet.json │ ├── spritesheet.png │ └── ward.png │ ├── config.js │ ├── demo.js │ └── modules │ ├── endscreen │ └── EndScreenModule.js │ ├── fx │ └── FXModule.js │ └── tooltip │ └── TooltipModule.js └── test ├── java ├── Main.java └── PlayerEmpty.java └── resources └── log4j2.properties /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.classpath 3 | /.project 4 | /.settings/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Legends of Code and Magic (LOCM) 2 | 3 | ## Links 4 | 5 | - [LOCM webpage](https://jakubkowalski.tech/Projects/LOCM/) 6 | - [CodinGame multiplayer](https://www.codingame.com/multiplayer/bot-programming/legends-of-code-magic) 7 | 8 | ## Justification 9 | 10 | The reason for launching LOCM is to provide more user-friendly and simpler framework than existing Hearthstone-based frameworks (e.g. the one for the [Hearthstone AI Competition](https://dockhorn.antares.uberspace.de/wordpress/)), yet rich enough so it provides the same type of challenges as the existing CCG games like [TES:Legends](https://legends.bethesda.net/) or [Hearthstone](https://playhearthstone.com/). 11 | 12 | Our goal is to make developing algorithms to these domains more robust, well defined, and to provide an open framework for anytime, fair comparison of AI's, so the approaches metioned e.g. [here](https://dockhorn.antares.uberspace.de/wordpress/additional-material/) can be tested and tuned more completely on the entire game rules, without restricting to only some decks or the subsets of available cards. 13 | 14 | Moreover, our framework tackles the problem of deck choosing (as in the Arena mode in Hearthstone and TES:Legends) providing a scenario that is truly fair for both players in terms of available draft choices. 15 | 16 | ## Overview of the rules 17 | 18 | The game takes place between two players, and is a turn-based, zero-sum game without the possibility to tie. Each player has its own deck of cards chosen from the available choices (during so-called _draft phase_) before the main game (_battle phase_). 19 | 20 | During the game each player has some cards in _hand_ (visible to him but not to his opponent), some cards lying in front of him on the _board_ (visible to both players), and the rest of the cards remaining in the _deck_, face down in random order. 21 | 22 | Each player has the _health_ points associated and the goal of the game is to bring down the opponent's health to zero or below. Another resource is _mana_ (some type of action points), The mana points available to a player increases every turn (up to the maximal value: 12) and they influence the cards that a player can play during his turn, as every card has a _cost_ associated. 23 | 24 | During a turn a player can make any number of _actions_ (in any valid order) belonging to the two main types: 25 | * he can attack using a card lying on board (target of the attack is is opponent's on-board card or the player directly) 26 | * he can play some card from his hand, removing its cost from the mana pool available this turn. The played card can (depending on its type) be placed on board (in most cases it cannot attack the sane turn it is placed) or cause some instant effect an disappear. 27 | 28 | At the beginning of each turn, some number of of cards from the top of player's deck (usually 1) is taken and put into the player's hand. If there are no cards in the deck, the player is receiving damage decreasing his health for every card that he tries to draw from the empty deck. 29 | 30 | Each card, except the cost, conatins information about its _attack_, _defense_, and some additional _keywords_ (described later). 31 | 32 | The cards that can be placed on board are called _creatures_. When they attack other creature, the attacker's attack is subtracted from the defender's defense and simultaneously the defender's attack is subtracted from the attacker's defense. Any creature with the defense zero or below is removed from the board. If the defense is positive, the creature remains, but the defense remains decreased. If the creature directly attacks the player, its attack is subtracted from the player's health. 33 | 34 | The cards that cannot be placed onboard are called _items_. Target of an item is an onboard creature or the opponent player, and the card modifies target's attack, defense or keywords. Items always affect keywords first, and attack/defense later. 35 | 36 | There are six keywords that influences the behavior of onboard creatures, e.g. by allowing a creature to attack the turn it is played, or protecting it from the first damage it should normally take. Also there are three additional parameters that can directly modify (increase or decrease by some number): health of the player using the card, health of his opponent, number of cards that will be drawn by the player next turn. 37 | 38 | 39 | Before the battle phase, described above, takes place, there is a draft phase. During the draft phase both players construct their decks. 40 | 41 | Each step of the draft give both players the choice between three cards. Every player chooses, secretly and independently, one card to put in his deck (so two players can choose the same card). The number of choices is equal to the number of the decks should finally contain. 42 | 43 | 44 | 45 | 46 | ### Keywords (abilities) 47 | 48 | There are 6 keywords, that can be turned on/off for every card/creature. If the card adds a keyword to a creature that already has it, there is no change. Similarly removing unpossesed keyword do not modify this keyword. 49 | 50 | #### Breakthrough 51 | Excess damage done on a player's turn is dealt to the opponent. 52 | 53 | Important cases: The opponent creature has to be removed (e.g. there is never excess damage against creatures with ward). 54 | 55 | 56 | #### Charge 57 | When summoned, such creature can attack the turn it is played. 58 | 59 | Important cases: If the creature gains charge keyword during the turn it can attack only if it was summoned this turn. 60 | 61 | 62 | #### Drain 63 | When deals damage on a player's turn, the player gain health equal to the creature's attack. 64 | 65 | Important cases: If the defender has ward, no damage is done so no health is gained by the player. 66 | 67 | 68 | #### Guard 69 | Enemies must attack this creature. 70 | 71 | #### Lethal 72 | Removes any creature it damages. 73 | 74 | Important cases: It does not remove creatures with ward. If lethal attack is equal zero, ability do not trigger (no damage done). 75 | 76 | 77 | #### Ward 78 | Prevent the first time the creature would take damage. 79 | 80 | Important cases: After preventing damage, the ward disappears. Attacking with creature with 0 attack does not remove ward. 81 | 82 | 83 | ### Items 84 | 85 | We consider three types of items that have different roles and thus their allowed targets. 86 | 87 | #### Green items 88 | 89 | These are items that can target only friendly (i.e. player's) onboard creatures. They usually make the target creature stronger in some way. If the item card has some keywords, this keywords are added to the creature's keywords. 90 | 91 | 92 | #### Red items 93 | 94 | These items can target only the opponent's onboard creatures. They usually make the target creature weaker. If the item card has some keywords, this keywords are removed from the creature's keywords. 95 | 96 | 97 | #### Blue items 98 | 99 | These are the special items that can be used without any creature onboard and affect only player or his opponent. The valid target for the blue item is any opponent's creature or the opponent itself. If the target is the opponent, and the card's attack is non-zero, then the attack value (usually negative) is added to the opponent's health. So that card can be used to damage creature or the opponent. 100 | 101 | 102 | ### Card Draw 103 | 104 | #### Emergency Draw 105 | When a player's health points reach for the first time a certain number, this player's next turn draw is increased. The milestones for emergency draws are: 25, 20, 15, 10, 5. 106 | 107 | For example when player's health got down from 30 to 18 in one turn, at the start of his next turn he draws additional two cards. If he heals for 4 health points, and after receives 6 damage, no additional cards are drawn (the emergency draw for 20 has been already used). 108 | 109 | 110 | #### Overdraw 111 | 112 | If a player does not have a place in his hand for the additional cards, it only draws to fill the hand. More cards remain in the deck, and next turn draw is set to the standard value (i.e. 1). 113 | 114 | #### Empty deck 115 | 116 | When player is to draw from an empty deck, it immediately loses the next rune, and his health is set to the value associated with that rune. When players has no runes remaining, his health is set to 0. 117 | 118 | Important cases: When player has no cards in deck remaining, should draw some cards, but cannot due to the full hand, only the first rune is destroyed (regardless of the overdraw size). 119 | 120 | 121 | ### Action Commands 122 | 123 | The player has to output a non-empty sequence of actions. The entire sequence has to be presented in one line, each acton separated by a semicolon. Empty action is a valid action, also whitespaces surrounding semicolons are permissible. Thus, e.g. a sequence with trailing or leading semicolon are acceptable. The non-empty action can be one of the following: 124 | 125 | * `PASS` - does nothing 126 | * `SUMMON [id] [text]` - puts a card of creature type on board 127 | * `USE [id] [targetId] [text]` - uses a card from the hand with identifier `[id]` to use a item on the target. The target identifier `[targetId]` has to be valid identifier depending on the item's type: friendly creature, enemy creature or the opponent (which has the `-1` identifier assigned). 128 | * `ATTACK [id] [targetId] [text]` attacks with a friendly creature with identifier `[id]` an opponent's onboard creature with identifier `[targetId]` or the player (using the identifier `-1`). Usually only one attack per creature can be performed. Attacking do not use mana points. 129 | 130 | Above, `[text]` is (optional - can be empty) any sequence of characters not containing a semicolon. The content of `[text]` will be displayed as a chat message during the visualization (after each action). 131 | 132 | Important case: Actions that are syntactically valid, but not legal (e.g. summoning creature that is not in hand or attacking opponent while there is a guard creature onboard) will result in warning message only, so they can be safely included in the action sequence. 133 | 134 | 135 | ### Draft probabilities 136 | 137 | Draft contains 90 cards to choose from, presented o both players in triplets. The choices are computed as follows. 138 | 139 | First, the game uniformly randomizes 60 cards from the cards available. Then for each draft turn, three cards are randomized for this 60. If a card already occured in this triple it is redrawn until a new card is found. Thus, in every triple all three cards are different. 140 | 141 | 142 | ### Game options 143 | 144 | To setup the game in a repeatable way there is a possibility to set a number of parameters. Note that the randomness in the game engine occurs only before the main game phase (choice of the cards to draft and ordering of cards within players' decks). 145 | 146 | * `seed` it controls all random numbers within the game. All other parameters override outcomes of this option for specific applications. 147 | * `draftChoicesSeed` controls the proposition of cards during the draft phase (this is league-dependent) 148 | * `shufflePlayer0Seed` controls the ordering within the deck for the cards chosen by the first player during the draft phase 149 | * `shufflePlayer1Seed` controls the ordering within the deck for the cards chosen by the second player during the draft phase 150 | * `predefinedDraftIds` allows to manually specify all choices available during the draft phase (overrides the effect of `draftChoicesSeed`). There have to be 30 comma-separated triplets, each containing 3 space-separated integers with card's `baseId'`s. 151 | 152 | 153 | Example of all parameters used. Note that in this case the result will be the same regardless of the values of `seed` and `draftChoicesSeed`, as more concrete options were directly specified. 154 | 155 | ``` 156 | seed=12700 157 | draftChoicesSeed=-5113144502819146988 158 | shufflePlayer0Seed=127 159 | shufflePlayer1Seed=333 160 | predefinedDraftIds=1 2 3 , 3 2 1 , 2 2 2 ,160 160 160, 150 151 152, 130 131 132, 7 7 7, 8 8 8, 9 9 9, 10 10 10, 11 11 11, 12 12 12, 13 13 13, 14 14 14, 15 15 15, 16 16 16, 17 17 17, 18 18 18, 19 19 19, 20 20 20, 11 11 11, 12 12 12, 13 13 13, 14 14 14, 15 15 15, 16 16 16, 17 17 17, 18 18 18, 19 19 19, 30 30 30 161 | ``` 162 | -------------------------------------------------------------------------------- /config/Boss.java: -------------------------------------------------------------------------------- 1 | /** AI description 2 | * Draft phase: 3 | * - always pick the first card 4 | * Game phase: 5 | * - do nothing (outputs single ';') 6 | */ 7 | class Player 8 | { 9 | public static void main(String[] args) 10 | { 11 | for (int i=0; i < 30; i++) 12 | System.out.println(String.format("PICK 1")); 13 | 14 | while (true) 15 | System.out.println(";"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | #Wed Jun 20 17:49:19 CEST 2018 2 | min_players=2 3 | type=multi 4 | title=Legends of Code & Magic 5 | max_players=2 6 | -------------------------------------------------------------------------------- /config/stub.txt: -------------------------------------------------------------------------------- 1 | gameloop 2 | loop 2 3 | read playerHealth:int playerMana:int playerDeck:int playerRune:int playerDraw:int 4 | read opponentHand:int opponentActions:int 5 | loop opponentActions 6 | read cardNumberAndAction:string(20) 7 | read cardCount:int 8 | loop cardCount 9 | read cardNumber:int instanceId:int location:int cardType:int cost:int attack:int defense:int abilities:word(6) myHealthChange:int opponentHealthChange:int cardDraw:int 10 | write PASS 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.codingame.game 6 | locam 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 2.5 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | com.codingame.gameengine 18 | core 19 | ${gamengine.version} 20 | 21 | 22 | 23 | com.codingame.gameengine 24 | module-entities 25 | ${gamengine.version} 26 | 27 | 28 | 29 | com.codingame.gameengine 30 | runner 31 | ${gamengine.version} 32 | 33 | 34 | 35 | junit 36 | junit 37 | test 38 | 4.12 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Player.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import com.codingame.gameengine.core.AbstractMultiplayerPlayer; 4 | 5 | public class Player extends AbstractMultiplayerPlayer { 6 | 7 | public int expectedOutputLines = 1; 8 | 9 | @Override 10 | public int getExpectedOutputLines() { 11 | return expectedOutputLines; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Referee.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import com.codingame.game.engine.EngineReferee; 4 | import com.codingame.game.ui.RefereeUI; 5 | import com.codingame.gameengine.core.AbstractReferee; 6 | import com.codingame.gameengine.core.MultiplayerGameManager; 7 | import com.codingame.gameengine.module.entities.GraphicEntityModule; 8 | import com.codingame.view.FXModule; 9 | import com.codingame.view.endscreen.EndScreenModule; 10 | import com.google.inject.Inject; 11 | 12 | public class Referee extends AbstractReferee { 13 | @Inject private MultiplayerGameManager gameManager; 14 | @Inject private GraphicEntityModule graphicEntityModule; 15 | @Inject private EndScreenModule endScreenModule; 16 | @Inject private FXModule fxModule; 17 | 18 | private EngineReferee engine = new EngineReferee(); 19 | private RefereeUI ui = new RefereeUI(); 20 | 21 | //public static int turn = 0; 22 | 23 | @Override 24 | public void init() 25 | { 26 | // Engine 27 | engine.refereeInit(gameManager); 28 | 29 | // GUI. 30 | ui.engine = engine; 31 | ui.gameManager = gameManager; 32 | ui.graphicEntityModule = graphicEntityModule; 33 | ui.fxModule = fxModule; 34 | ui.init(); 35 | } 36 | 37 | @Override 38 | public void gameTurn(int turn) 39 | { 40 | //this.turn = turn; 41 | // Engine 42 | boolean end = engine.refereeGameTurn(gameManager, ui); 43 | } 44 | 45 | @Override 46 | public void onEnd() { 47 | endScreenModule.setScores(gameManager.getPlayers().stream().mapToInt(p -> p.getScore()).toArray()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/Action.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by aCat on 2018-03-22. 8 | */ 9 | public class Action 10 | { 11 | public enum Type {SUMMON, ATTACK, USE, PASS}; 12 | 13 | public Type type; 14 | public int arg1; 15 | public int arg2; 16 | public Card.Type cardType; // for items 17 | public String text; // for say 18 | public ActionResult result; 19 | 20 | public Action() 21 | { 22 | } 23 | 24 | 25 | public static List parseSequence(String data) throws InvalidActionHard 26 | { 27 | ArrayList actions = new ArrayList<>(); 28 | 29 | for (String str : data.split(";")) 30 | { 31 | str = str.trim(); 32 | if (str.isEmpty()) 33 | continue; // empty action is a valid action 34 | actions.add(Action.parse(str)); 35 | } 36 | 37 | return actions; 38 | } 39 | 40 | // todo copy constructor? 41 | 42 | 43 | public static Action parse (String data) throws InvalidActionHard 44 | { 45 | String[] str = data.split(" ", 2); 46 | 47 | Type type; 48 | switch (str[0].trim()) 49 | { 50 | case "SUMMON": type = Type.SUMMON; break; 51 | case "ATTACK": type = Type.ATTACK; break; 52 | case "USE": type = Type.USE; break; 53 | case "PASS": type = Type.PASS; break; 54 | default: throw new InvalidActionHard("Invalid action name. Should be SUMMON, ATTACK, or USE."); 55 | } 56 | 57 | if (type==Type.SUMMON) 58 | { 59 | if (Constants.LANES==1) 60 | { 61 | try 62 | { 63 | String[] args = str[1].split(" ", 2); 64 | int arg1; 65 | arg1 = Integer.parseInt(args[0]); 66 | String text = args.length < 2 ? "" : args[1].trim(); 67 | return Action.newSummon(arg1, text); 68 | } catch (Exception e) 69 | { 70 | throw new InvalidActionHard("Invalid SUMMON argument. Expected integer (card id)."); 71 | } 72 | } 73 | 74 | try 75 | { 76 | String[] args = str[1].split(" ", 3); 77 | int arg1; 78 | int arg2; 79 | arg1 = Integer.parseInt(args[0]); 80 | arg2 = Integer.parseInt(args[1]); 81 | String text = args.length < 3 ? "" : args[2].trim(); 82 | return Action.newSummon(arg1, arg2, text); 83 | } catch (Exception e) 84 | { 85 | throw new InvalidActionHard("Invalid SUMMON arguments. Expected two integers (card id and target id)."); 86 | } 87 | } 88 | else if (type==Type.PASS) { 89 | return Action.newPass(); 90 | } 91 | else { 92 | 93 | 94 | try 95 | { 96 | String[] args = str[1].split(" ", 3); 97 | int arg1; 98 | int arg2; 99 | arg1 = Integer.parseInt(args[0]); 100 | arg2 = Integer.parseInt(args[1]); 101 | String text = args.length < 3 ? "" : args[2].trim(); 102 | return type==Type.ATTACK ? Action.newAttack(arg1, arg2, text) : Action.newUse(arg1, arg2, text); 103 | } 104 | catch (Exception e) 105 | { 106 | throw new InvalidActionHard("Invalid "+type.toString()+" arguments. Expected two integers (card id and target id)."); 107 | } 108 | 109 | 110 | } 111 | } 112 | 113 | public static Action newSummon(int arg1) 114 | { 115 | return newSummon(arg1, 0, ""); 116 | } 117 | 118 | public static Action newSummon(int arg1, String text) 119 | { 120 | return newSummon(arg1, 0, text); 121 | } 122 | 123 | public static Action newSummon(int arg1, int arg2) 124 | { 125 | return newSummon(arg1, arg2, ""); 126 | } 127 | 128 | public static Action newSummon(int arg1, int arg2, String text) // todo private? 129 | { 130 | Action a = new Action(); 131 | a.type = Type.SUMMON; 132 | a.cardType = Card.Type.CREATURE; 133 | a.arg1 = arg1; 134 | a.arg2 = arg2; // lane 135 | a.text = text; 136 | return a; 137 | } 138 | 139 | public static Action newAttack(int arg1, int arg2) 140 | { 141 | return newAttack(arg1, arg2, ""); 142 | } 143 | 144 | public static Action newAttack(int arg1, int arg2, String text) // todo private? 145 | { 146 | Action a = new Action(); 147 | a.type = Type.ATTACK; 148 | a.cardType = Card.Type.CREATURE; 149 | a.arg1 = arg1; 150 | a.arg2 = arg2; 151 | a.text = text; 152 | return a; 153 | } 154 | 155 | public static Action newPass() { 156 | Action a = new Action(); 157 | a.type = Type.PASS; 158 | a.text = ""; 159 | return a; 160 | } 161 | 162 | public static Action newUse(int arg1, int arg2) 163 | { 164 | return newUse(arg1, arg2, ""); 165 | } 166 | 167 | 168 | 169 | // todo - it's just a shell now 170 | public static Action newUse(int arg1, int arg2, String text) // todo private? 171 | { 172 | Action a = new Action(); 173 | a.type = Type.USE; 174 | //a.cardType = Card.Type.CREATURE; // todo here 175 | a.arg1 = arg1; 176 | a.arg2 = arg2; 177 | a.text = text; 178 | return a; 179 | } 180 | 181 | public String toStringNoText() 182 | { 183 | switch (type) 184 | { 185 | case SUMMON: return (Constants.LANES==1) ? String.format("SUMMON %d", arg1) : String.format("SUMMON %d %d", arg1, arg2); 186 | case ATTACK: return String.format("ATTACK %d %d", arg1, arg2); 187 | case USE: return String.format("USE %d %d", arg1, arg2); 188 | case PASS: return "PASS"; 189 | } 190 | return super.toString(); 191 | } 192 | 193 | @Override 194 | public String toString() 195 | { 196 | switch (type) 197 | { 198 | case SUMMON: return (Constants.LANES==1) ? String.format("SUMMON %d %s", arg1, text) : String.format("SUMMON %d %d %s", arg1, arg2, text); 199 | case ATTACK: return String.format("ATTACK %d %d %s", arg1, arg2, text); 200 | case USE: return String.format("USE %d %d %s", arg1, arg2, text); 201 | case PASS: return "PASS"; 202 | } 203 | return super.toString(); 204 | } 205 | 206 | @Override 207 | public boolean equals(Object other) 208 | { 209 | if (other == null) return false; 210 | if (other == this) return true; 211 | if (!(other instanceof Action))return false; 212 | Action a = (Action)other; 213 | return this.toStringNoText().equals(a.toStringNoText()); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/ActionResult.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | /** 4 | * Created by aCat on 2018-03-21. 5 | */ 6 | public class ActionResult 7 | { 8 | public boolean isValid; 9 | public CreatureOnBoard attacker; 10 | public CreatureOnBoard defender; 11 | public int attackerHealthChange; 12 | public int defenderHealthChange; 13 | public int attackerAttackChange; 14 | public int defenderAttackChange; 15 | public int attackerDefenseChange; 16 | public int defenderDefenseChange; 17 | public boolean attackerDied; 18 | public boolean defenderDied; 19 | 20 | public ActionResult(boolean isValid) 21 | { 22 | this.isValid = isValid; 23 | this.attacker = null; 24 | this.defender = null; 25 | this.attackerHealthChange = 0; 26 | this.defenderHealthChange = 0; 27 | } 28 | 29 | public ActionResult(CreatureOnBoard attacker, CreatureOnBoard defender, boolean attackerDied, boolean defenderDied, int attackerHealthChange, int defenderHealthChange) 30 | { 31 | this.isValid = true; 32 | this.attackerDied = attackerDied; 33 | this.defenderDied = defenderDied; 34 | this.attacker = attacker; 35 | this.defender = defender; 36 | this.attackerHealthChange = attackerHealthChange; 37 | this.defenderHealthChange = defenderHealthChange; 38 | } 39 | 40 | public ActionResult(CreatureOnBoard attacker, CreatureOnBoard defender, int healthGain, int healthTaken) { 41 | this(attacker, defender, false, false, healthGain, healthTaken); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/Card.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import java.text.DecimalFormat; 4 | import java.text.NumberFormat; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Created by Kot on 2018-03-20. 12 | */ 13 | public class Card { 14 | 15 | public enum Type { 16 | CREATURE("creature"), 17 | ITEM_GREEN("itemGreen"), 18 | ITEM_RED("itemRed"), 19 | ITEM_BLUE("itemBlue"); 20 | 21 | private String description; 22 | 23 | public String getDescription() { 24 | return description; 25 | } 26 | 27 | public static Type fromDescription(String description) { 28 | for (Type type : values()) { 29 | if (type.description.equals(description)) { 30 | return type; 31 | } 32 | } 33 | return null; 34 | } 35 | 36 | Type(String description) { 37 | this.description = description; 38 | } 39 | 40 | } 41 | 42 | public int id; 43 | public int baseId; 44 | public Type type; 45 | public int cost; 46 | public int attack; 47 | public int defense; 48 | public Keywords keywords; 49 | //TODO maybe myHealthChange, oppHealthChange, cardDraw should be moved into Summon class? 50 | public int myHealthChange; 51 | public int oppHealthChange; 52 | public int cardDraw; 53 | public String name; 54 | public String text; 55 | private String tooltipTextBase; 56 | public String comment; 57 | 58 | 59 | 60 | // todo copy constructor with id; ? 61 | // todo constructor with text (id-based) 62 | 63 | // copy constructor 64 | public Card(Card card) 65 | { 66 | this.id = card.id; 67 | this.baseId = card.baseId; 68 | this.name = card.name; 69 | this.type = card.type; 70 | this.cost = card.cost; 71 | this.attack = card.attack; 72 | this.defense = card.defense; 73 | this.keywords = new Keywords(card.keywords); 74 | this.myHealthChange = card.myHealthChange; 75 | this.oppHealthChange = card.oppHealthChange; 76 | this.cardDraw = card.cardDraw; 77 | this.comment = card.comment; 78 | generateText(); 79 | this.tooltipTextBase = card.tooltipTextBase; 80 | } 81 | 82 | // data = {baseId, name, type, cost, attack, defense, keywords, myHealthChange, oppHealthChange, cardDraw, comment} 83 | public Card(String[] data) 84 | { 85 | this.id = -1; 86 | this.baseId = Integer.parseInt(data[0]); 87 | this.name = data[1]; 88 | this.type = Type.fromDescription(data[2]); 89 | this.cost = Integer.parseInt(data[3]); 90 | this.attack = Integer.parseInt(data[4]); 91 | this.defense = Integer.parseInt(data[5]); 92 | this.keywords = new Keywords(data[6]); 93 | this.myHealthChange = Integer.parseInt(data[7]); 94 | this.oppHealthChange = Integer.parseInt(data[8]); 95 | this.cardDraw = Integer.parseInt(data[9]); 96 | 97 | this.comment = ""; //data[11]; // comments deprecated as we are far from TESL in many cards 98 | generateText(); 99 | try 100 | { 101 | this.tooltipTextBase = data[10]; 102 | } 103 | catch (java.lang.ArrayIndexOutOfBoundsException e) 104 | { 105 | this.tooltipTextBase = ""; 106 | } 107 | } 108 | 109 | public void generateText() 110 | { 111 | StringBuilder sb = new StringBuilder(); 112 | 113 | List keywords = this.keywords.getListOfKeywords(); 114 | 115 | ArrayList summon = new ArrayList<>(); 116 | 117 | if (myHealthChange > 0) 118 | summon.add("gain "+myHealthChange+" health"); 119 | if (myHealthChange < 0) 120 | summon.add("deal "+Math.abs(myHealthChange)+" damage to yourself"); 121 | if (oppHealthChange < 0) 122 | summon.add("deal "+Math.abs(oppHealthChange)+" damage to your opponent"); 123 | if (cardDraw > 0) 124 | summon.add("draw " + (cardDraw==1 ? "a card" : (cardDraw+" cards"))); 125 | 126 | if (type == Type.CREATURE) 127 | { 128 | sb.append(String.join(", ", keywords)); 129 | if (myHealthChange!=0 || oppHealthChange!=0 || cardDraw !=0) 130 | { 131 | sb.append(keywords.isEmpty() ? "" : "; ").append("Summon: "); 132 | sb.append(String.join(", ", summon)).append("."); 133 | } 134 | } 135 | else if(type == Type.ITEM_GREEN) 136 | { 137 | if (!keywords.isEmpty()) 138 | sb.append("Give ").append(String.join(", ", keywords)); 139 | if (myHealthChange!=0 || oppHealthChange!=0 || cardDraw !=0) 140 | { 141 | sb.append(keywords.isEmpty() ? "" : "; "); 142 | sb.append(String.join(", ", summon)).append("."); 143 | } 144 | } 145 | else if(type == Type.ITEM_RED) 146 | { 147 | if (!keywords.isEmpty()) 148 | sb.append("Remove ").append(keywords.size()==7 ? "all keywords" : String.join(", ", keywords)); 149 | if (myHealthChange!=0 || oppHealthChange!=0 || cardDraw !=0) 150 | { 151 | sb.append(keywords.isEmpty() ? "" : "; "); 152 | sb.append(String.join(", ", summon)).append("."); 153 | } 154 | } 155 | else 156 | { 157 | sb.append(String.join(", ", summon)).append("."); 158 | } 159 | 160 | this.text = sb.toString(); 161 | } 162 | 163 | private String toTextDescription() 164 | { 165 | StringBuilder sb = new StringBuilder(); 166 | NumberFormat nf = new DecimalFormat("+#;-#"); 167 | 168 | if (type == Type.CREATURE) 169 | { 170 | sb.append(attack).append(" / ").append(defense).append(" creature. ").append(text); 171 | } 172 | else if(type == Type.ITEM_GREEN) 173 | { 174 | sb.append(text); 175 | if (attack != 0 || defense != 0) 176 | sb.append(" Give friendly creature ").append(nf.format(attack)).append(" / ").append(nf.format(defense)); 177 | } 178 | else if(type == Type.ITEM_RED) 179 | { 180 | sb.append(text); 181 | if (attack != 0 || defense != 0) 182 | sb.append(" Give enemy creature ").append(attack).append(" / ").append(defense); 183 | } 184 | else 185 | { 186 | sb.append(defense != 0 ? ("Deal "+Math.abs(defense)+" damage. ") : "").append(text.length()>1 ? text : ""); 187 | } 188 | 189 | return sb.toString(); 190 | } 191 | 192 | private String toTooltipInnerText() 193 | { 194 | return toTooltipInnerText(null); 195 | } 196 | 197 | private String toTooltipInnerText(CreatureOnBoard creatureOnBoard) 198 | { 199 | StringBuilder sb = new StringBuilder(); 200 | 201 | if (type == Type.CREATURE) 202 | { 203 | Keywords keywords; 204 | if (creatureOnBoard != null) { 205 | keywords = creatureOnBoard.keywords; 206 | int aDiff = creatureOnBoard.attack - attack; 207 | int dDiff = creatureOnBoard.defense - defense; 208 | if (aDiff != 0 || dDiff != 0) { 209 | sb.append(' '); 210 | } 211 | sb.append(attack).append(" / ").append(defense).append(" Creature"); 212 | if (aDiff != 0 || dDiff != 0) { 213 | sb.append("\n"); 214 | if (aDiff > 0) { 215 | sb.append("+"); 216 | } else if (aDiff == 0) { 217 | sb.append(" "); 218 | } 219 | sb.append(aDiff == 0 ? " " : aDiff).append(" "); 220 | if (dDiff > 0) { 221 | sb.append("+"); 222 | } else if (dDiff == 0) { 223 | sb.append(" "); 224 | } 225 | sb.append(dDiff == 0 ? " " : dDiff).append(" "); 226 | } 227 | } else { 228 | keywords = this.keywords; 229 | sb.append(attack).append(" / ").append(defense).append(" Creature"); 230 | } 231 | List keywordsList = keywords.getListOfKeywords(); 232 | 233 | if (!keywordsList.isEmpty()) 234 | sb.append("\\n").append(String.join(", ", keywordsList)); 235 | String tt = tooltipTextBase.trim(); 236 | if (tt.length()> 0) 237 | sb.append("\\n").append(tt); 238 | return sb.toString(); 239 | } 240 | 241 | if (type == Type.ITEM_GREEN) 242 | sb.append("Green Item\\n"); 243 | if (type == Type.ITEM_RED) 244 | sb.append("Red Item\\n"); 245 | if (type == Type.ITEM_BLUE) 246 | sb.append("Blue Item\\n"); 247 | sb.append(tooltipTextBase.trim()); 248 | 249 | return sb.toString(); 250 | } 251 | 252 | public String toTooltipText(CreatureOnBoard creatureOnBoard) 253 | { 254 | StringBuilder sb = new StringBuilder(); 255 | 256 | sb.append(this.name + " (#"+baseId+")").append("\n\n"); 257 | 258 | if (id >= 0) sb.append("instanceId: ").append(this.id).append("\n"); 259 | sb.append("cost: ").append(this.cost).append("\n"); 260 | sb.append("\n"); 261 | 262 | sb.append(toTooltipInnerText(creatureOnBoard).replace("\\n", "\n")); 263 | 264 | return sb.toString(); 265 | } 266 | 267 | public String toHTMLString(String teslcomment) 268 | { 269 | 270 | StringBuilder sb = new StringBuilder(); 271 | sb.append(""); 272 | sb.append("").append(baseId).append(""); 273 | //String portraitpath = String.format("src=\"../assets/card_images/%03d.png\"", baseId); 274 | String portraitimage = String.format("src=\"portraits/%03d.png\"", baseId); 275 | String cardimage = String.format("src=\"cards/%03d.png\"", baseId); 276 | //sb.append("").append("").append(""); // width="300" -- deprecated for now 277 | sb.append("").append("").append(""); // width="300" -- deprecated for now 278 | 279 | sb.append(""+name+ ""); 280 | 281 | sb.append("").append(type.getDescription()).append(""); 282 | 283 | sb.append("").append(cost).append(""); 284 | sb.append("").append(attack).append(""); 285 | sb.append("").append(defense).append(""); 286 | 287 | sb.append("
"); 288 | sb.append(this.keywords); 289 | sb.append("
"); 290 | 291 | sb.append("").append(myHealthChange).append(""); 292 | sb.append("").append(oppHealthChange).append(""); 293 | sb.append("").append(cardDraw).append(""); 294 | 295 | Pattern tesllink = Pattern.compile("@(\\d+)=(['\\w-]+)"); 296 | String htmlcomment = teslcomment==null ? "" : teslcomment; 297 | Matcher m = tesllink.matcher(htmlcomment); 298 | while (m.find()) 299 | { 300 | String lowname = m.group(2).replace("-", "").toLowerCase(); 301 | String fullname = m.group(2).replace("-", " "); 302 | String link = String.format("%s", 303 | m.group(1), lowname, fullname, lowname); 304 | htmlcomment = htmlcomment.replace(m.group(0), link); 305 | } 306 | 307 | String tt = toTooltipInnerText(); 308 | if (tt.length()> 0) 309 | sb.append("").append(tt.replace("\\n", "
")).append("
"); 310 | else 311 | sb.append("").append(toTextDescription()).append(""); 312 | 313 | //sb.append(""); 314 | if (htmlcomment.length() > 0) 315 | sb.append("").append(htmlcomment).append(""); 316 | 317 | 318 | // card = "" + card.replace(";", "") + "\n"; 319 | 320 | 321 | sb.append(""); 322 | // todo 323 | return sb.toString(); 324 | } 325 | 326 | public String toDescriptiveString() 327 | { 328 | StringBuilder sb = new StringBuilder(); 329 | if (id >= 0) sb.append("id:").append(this.id).append(' '); 330 | if (!name.equals("?")) sb.append(this.name).append(' '); 331 | sb.append("(#").append(this.baseId).append(")").append(' '); 332 | sb.append(type.getDescription()).append(' '); 333 | 334 | sb.append("COST:").append(this.cost).append(' '); 335 | if (this.type == Type.CREATURE ) 336 | { 337 | sb.append("ATT:").append(this.attack).append(' '); 338 | sb.append("DEF:").append(this.defense).append(' '); 339 | } 340 | else // items 341 | { 342 | sb.append("ATT:").append(String.format("%+d", this.attack)).append(' '); 343 | sb.append("DEF:").append(String.format("%+d", this.defense)).append(' '); 344 | } 345 | 346 | sb.append(" ").append(this.text); 347 | 348 | return sb.toString(); 349 | } 350 | 351 | public String toStringWithoutId() 352 | { 353 | StringBuilder sb = new StringBuilder(); 354 | sb.append(this.baseId).append(' '); 355 | //sb.append(this.type.getDescription()).append(' '); 356 | sb.append(this.type.ordinal()).append(' '); // todo test is it ok? 0, 1, 2, 3 357 | sb.append(this.cost).append(' '); 358 | sb.append(this.attack).append(' '); 359 | sb.append(this.defense).append(' '); 360 | sb.append(this.keywords); 361 | sb.append(' '); 362 | sb.append(this.myHealthChange).append(' '); 363 | sb.append(this.oppHealthChange).append(' '); 364 | sb.append(this.cardDraw).append(' '); 365 | return sb.toString(); 366 | } 367 | 368 | public String toString() 369 | { 370 | return this.id+ " " + toStringWithoutId(); 371 | } 372 | 373 | public String getAsInput() { 374 | StringBuilder s = new StringBuilder(); 375 | 376 | s.append(baseId).append(" "); 377 | s.append(id).append(" "); 378 | s.append(0).append(" "); 379 | s.append(type.ordinal()).append(" "); 380 | s.append(cost).append(" "); 381 | s.append(attack).append(" "); 382 | s.append(defense).append(" "); 383 | s.append(keywords).append(" "); 384 | s.append(myHealthChange).append(" "); 385 | s.append(oppHealthChange).append(" "); 386 | s.append(cardDraw).append(" "); 387 | 388 | if (Constants.LANES>1) 389 | s.append(-1).append(" "); 390 | 391 | return s.toString(); 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/Constants.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.io.OutputStreamWriter; 9 | import java.io.Writer; 10 | import java.nio.file.Files; 11 | import java.nio.file.Paths; 12 | import java.util.HashMap; 13 | import java.util.Scanner; 14 | 15 | /** 16 | * Created by aCat on 2018-03-22. 17 | */ 18 | public final class Constants 19 | { 20 | public static int VERBOSE_LEVEL = 3; // 3 - full, 2 - without turn details, 1 - results only, 0 - silent 21 | 22 | public static int LANES = 1; 23 | 24 | public static final int CARDS_IN_DECK = 30; // 30; 25 | public static final int CARDS_IN_DRAFT = 60; // 60; 26 | 27 | public static final int INITIAL_HAND_SIZE = 4; 28 | public static final int MAX_CARDS_IN_HAND = 8; // was 10 29 | public static final int SECOND_PLAYER_CARD_BONUS = 1; 30 | public static final int SECOND_PLAYER_MAX_CARD_BONUS = 0; 31 | public static final int SECOND_PLAYER_MANA_BONUS_TURNS = 1; 32 | //public static final int EMPTY_DECK_DAMAGE = 5; 33 | 34 | public static final int MAX_MANA = 12; 35 | public static final int INITIAL_HEALTH = 30; 36 | 37 | public static final int MAX_CREATURES_IN_LINE = 6; // was 8 38 | 39 | public static final int TIMELIMIT_FIRSTDRAFTTURN = 1000; 40 | public static final int TIMELIMIT_DRAFTTURN = 100; 41 | public static final int TIMELIMIT_FIRSTGAMETURN = 1000; 42 | public static int TIMELIMIT_GAMETURN = 100; 43 | 44 | public static final int PLAYER_TURNLIMIT = 50; 45 | public static final int MAX_TURNS_HARDLIMIT = (2*CARDS_IN_DECK + 2*CARDS_IN_DECK + 2*10) * 10; 46 | 47 | public static final HashMap CARDSET = new HashMap<>(); 48 | 49 | public static final int FRAME_DURATION_SHOWDRAFT = 1; 50 | public static final int FRAME_DURATION_SHOWDRAFT_LAST = 2500; 51 | public static final int FRAME_DURATION_DRAFT = 500; 52 | public static final int FRAME_DURATION_BATTLE = 750; 53 | public static final int FRAME_DURATION_SUMMON = 600; 54 | 55 | public static void LoadCardlist(String cardsetPath) 56 | { 57 | BufferedReader bufferedReader = null; 58 | try 59 | { 60 | bufferedReader = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream(cardsetPath), "UTF-8")); 61 | } 62 | catch (Exception e) 63 | { 64 | e.printStackTrace(); 65 | return; 66 | } 67 | 68 | String line = null; 69 | try 70 | { 71 | while((line = bufferedReader.readLine())!=null) 72 | { 73 | line=line.replaceAll("//.*","").trim(); 74 | if (line.length() > 0) 75 | { 76 | Card c = new Card(line.split("\\s*;\\s*")); 77 | CARDSET.put(c.baseId, c); 78 | } 79 | } 80 | } 81 | catch (IOException e) 82 | { 83 | e.printStackTrace(); 84 | } 85 | } 86 | 87 | public static void GenerateCardlistHTML(String templatePath, String outHTMLPath) throws IOException 88 | { 89 | Scanner s = new Scanner(new InputStreamReader(ClassLoader.getSystemResourceAsStream(templatePath), "UTF-8")).useDelimiter("\\A"); 90 | String template = s.hasNext() ? s.next() : ""; 91 | StringBuilder sb = new StringBuilder(); 92 | 93 | 94 | HashMap refs = new HashMap<>(); 95 | // refs = GenerateTESLReferences(); 96 | 97 | for (Card c : CARDSET.values()) 98 | { 99 | sb.append(c.toHTMLString(refs.get(c.baseId))+"\n"); 100 | } 101 | 102 | template = String.format(template, sb.toString()); 103 | 104 | try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outHTMLPath), "utf-8"))) 105 | { 106 | writer.write(template); 107 | } 108 | } 109 | 110 | @Deprecated 111 | public static HashMap GenerateTESLReferences() 112 | { 113 | BufferedReader bufferedReader = null; 114 | try 115 | { 116 | bufferedReader = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("TESLref-deprecated.txt"), "UTF-8")); 117 | } 118 | catch (Exception e) 119 | { 120 | e.printStackTrace(); 121 | return null; 122 | } 123 | 124 | HashMap refs = new HashMap<>(); 125 | String line = null; 126 | try 127 | { 128 | while((line = bufferedReader.readLine())!=null) 129 | { 130 | String[] parts = line.split(";"); 131 | String ref = parts[1].trim(); 132 | if (ref.contains("###")) 133 | { 134 | ref = "" + ref.replace("###", "").trim() + ""; 135 | } 136 | refs.put(Integer.parseInt(parts[0]), ref); 137 | } 138 | } 139 | catch (IOException e) 140 | { 141 | e.printStackTrace(); 142 | } 143 | return refs; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/CreatureOnBoard.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import java.util.List; 4 | 5 | import com.codingame.game.engine.Card.Type; 6 | 7 | /** 8 | * Creature that is not a card anymore but it is placed on board. 9 | */ 10 | public class CreatureOnBoard 11 | { 12 | public int id; 13 | public int baseId; 14 | public int attack; 15 | public int defense; 16 | public int cost; 17 | public int myHealthChange; 18 | public int oppHealthChange; 19 | public int cardDraw; 20 | public Keywords keywords; 21 | 22 | public boolean canAttack; 23 | public boolean hasAttacked; 24 | public int lastTurnDefense; 25 | 26 | public int lane; // usually 0 or 1 27 | 28 | public Card baseCard; 29 | 30 | public CreatureOnBoard (CreatureOnBoard creature) 31 | { 32 | this.id = creature.id; 33 | this.baseId = creature.baseId; 34 | this.cost = creature.cost; 35 | this.attack = creature.attack; 36 | this.defense = creature.defense; 37 | this.keywords = new Keywords(creature.keywords); 38 | this.lastTurnDefense = creature.lastTurnDefense; 39 | baseCard = creature.baseCard; 40 | this.canAttack = creature.canAttack; 41 | this.hasAttacked = creature.hasAttacked; 42 | this.lane = creature.lane; 43 | } 44 | 45 | /** 46 | * @param data "id baseId attack defense keywords" 47 | */ 48 | public CreatureOnBoard (String data) 49 | { 50 | String[] creature = data.split(" "); 51 | this.id = Integer.parseInt(creature[0]); 52 | this.baseId = Integer.parseInt(creature[1]); 53 | this.attack = Integer.parseInt(creature[2]); 54 | this.defense = Integer.parseInt(creature[3]); 55 | this.keywords = new Keywords(creature[4]); 56 | this.canAttack = this.keywords.hasCharge; 57 | this.lastTurnDefense = this.defense; 58 | this.lane = 0; 59 | } 60 | 61 | public CreatureOnBoard (Card card, int lane) 62 | { 63 | this.id = card.id; 64 | this.baseId = card.baseId; 65 | this.attack = card.attack; 66 | this.defense = card.defense; 67 | this.keywords = new Keywords(card.keywords); 68 | this.canAttack = this.keywords.hasCharge; 69 | this.lastTurnDefense = card.defense; 70 | this.cost = card.cost; 71 | this.myHealthChange = card.myHealthChange; 72 | this.oppHealthChange = card.oppHealthChange; 73 | this.cardDraw = card.cardDraw; 74 | baseCard = card; 75 | this.lane = lane; 76 | } 77 | 78 | public CreatureOnBoard (Card card) 79 | { 80 | this(card, -1); 81 | } 82 | 83 | public String generateText() 84 | { 85 | List keywords = this.keywords.getListOfKeywords(); 86 | 87 | return String.join(", ", keywords); 88 | } 89 | 90 | public String toDescriptiveString() 91 | { 92 | StringBuilder sb = new StringBuilder(); 93 | if (id >= 0) sb.append("id:").append(this.id).append(' '); 94 | sb.append("(").append(this.baseId).append(")").append(' '); 95 | 96 | sb.append("ATT:").append(this.attack).append(' '); 97 | sb.append("DEF:").append(this.defense).append(' '); 98 | sb.append(generateText()); 99 | 100 | return sb.toString(); 101 | } 102 | 103 | public String toString() 104 | { 105 | StringBuilder sb = new StringBuilder(); 106 | sb.append(this.id).append(' '); 107 | sb.append(this.baseId).append(' '); 108 | sb.append(this.attack).append(' '); 109 | sb.append(this.defense).append(' '); 110 | sb.append(this.keywords); 111 | return sb.toString(); 112 | } 113 | 114 | public String getAsInput(boolean isOpponentBoard) { 115 | int position = isOpponentBoard? -1: 1; 116 | StringBuilder s = new StringBuilder(); 117 | s.append(baseId).append(" "); 118 | s.append(id).append(" "); 119 | s.append(position).append(" "); 120 | s.append(Type.CREATURE.ordinal()).append(" "); 121 | s.append(cost).append(" "); 122 | s.append(attack).append(" "); 123 | s.append(defense).append(" "); 124 | s.append(keywords).append(" "); 125 | s.append(myHealthChange).append(" "); 126 | s.append(oppHealthChange).append(" "); 127 | s.append(cardDraw).append(" "); 128 | if (Constants.LANES > 1) 129 | s.append(this.lane).append(" "); 130 | return s.toString(); 131 | } 132 | 133 | public String toTooltipText() { 134 | return baseCard.toTooltipText(this); 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/DraftPhase.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | //import javafx.util.Pair; :( 4 | 5 | import java.util.*; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | /** 10 | * Created by aCat on 2018-03-24. 11 | */ 12 | public class DraftPhase 13 | { 14 | public enum Difficulty {NORMAL, LESS_EASY, EASY, VERY_EASY}; 15 | 16 | public Difficulty difficulty; 17 | public List allowedCards; 18 | //TODO List should be used everywhere, apart from creation 19 | public List draftingCards; 20 | public Card[][] draft; 21 | //TODO we shouldn't mix arrays and collections, List> would be better 22 | public ArrayList[] chosenCards; 23 | public ArrayList[] decks; // after shuffle and assigning unique id's 24 | 25 | public String[] text = new String[2]; 26 | 27 | private Random choicesRNG; 28 | private Random[] shufflesRNG; 29 | private RefereeParams params; 30 | 31 | public int[] showdraftQuantities; 32 | public ArrayList showdraftCards; 33 | 34 | // todo - add function and field documentation 35 | 36 | public DraftPhase(Difficulty difficulty, RefereeParams params) 37 | { 38 | this.difficulty = difficulty; 39 | this.params = params; 40 | 41 | chosenCards = new ArrayList[] {new ArrayList(), new ArrayList()}; 42 | decks = new ArrayList[] {new ArrayList(), new ArrayList()}; 43 | 44 | choicesRNG = params.draftChoicesRNG; 45 | shufflesRNG = new Random[] {params.shufflePlayer0RNG, params.shufflePlayer1RNG}; 46 | } 47 | 48 | private boolean isVeryEasyCard(Card card) 49 | { 50 | return card.type == Card.Type.CREATURE 51 | && !card.keywords.hasAnyKeyword() 52 | && card.myHealthChange == 0 && card.oppHealthChange == 0 && card.cardDraw == 0; 53 | } 54 | 55 | private void prepareAllowedCards() 56 | { 57 | Collection cardBase = Constants.CARDSET.values(); 58 | 59 | if (difficulty == Difficulty.NORMAL) { 60 | allowedCards = new ArrayList<>(cardBase); 61 | } else if (difficulty == Difficulty.LESS_EASY) { 62 | allowedCards = cardBase.stream() 63 | .filter(card -> !card.keywords.hasDrain && !card.keywords.hasLethal && !card.keywords.hasWard) 64 | .collect(Collectors.toList()); 65 | } else if (difficulty == Difficulty.EASY) { 66 | allowedCards = cardBase.stream() 67 | .filter(card -> card.type == Card.Type.CREATURE) 68 | .filter(card -> !card.keywords.hasDrain && !card.keywords.hasLethal && !card.keywords.hasWard) 69 | .collect(Collectors.toList()); 70 | } else { 71 | allowedCards = cardBase.stream() 72 | .filter(card -> isVeryEasyCard(card)) 73 | .collect(Collectors.toList()); 74 | } 75 | } 76 | 77 | public void PrepareChoices() 78 | { 79 | prepareAllowedCards(); 80 | 81 | if (params.predefinedDraftIds != null) // parameter-forced draft choices 82 | { 83 | draftingCards = new ArrayList<>(); // 0 size is ok here? in hand-made draft this is meaningless variable 84 | HashSet cardsInDraft = new HashSet<>(); 85 | 86 | draft = new Card[Constants.CARDS_IN_DECK][3]; 87 | for(int pick=0; pick < Constants.CARDS_IN_DECK; pick++) 88 | { 89 | for (int i=0; i < 3; i++) 90 | { 91 | draft[pick][i] = Constants.CARDSET.get(params.predefinedDraftIds[pick][i]); 92 | cardsInDraft.add(params.predefinedDraftIds[pick][i]); 93 | } 94 | } 95 | 96 | 97 | for (int baseId : cardsInDraft) 98 | draftingCards.add(Constants.CARDSET.get(baseId)); 99 | 100 | prepareShowdraft(); 101 | return; 102 | } 103 | 104 | ArrayList drafting = new ArrayList<>(); 105 | for (int pick = 0; pick < Math.min(Constants.CARDS_IN_DRAFT, allowedCards.size()); pick++) 106 | { 107 | int i = -1; 108 | do 109 | { 110 | i = choicesRNG.nextInt(allowedCards.size()); 111 | } while (drafting.contains(i)); 112 | drafting.add(i); 113 | } 114 | 115 | assert (drafting.size()>=3); 116 | 117 | draftingCards = new ArrayList<>(); 118 | for (Integer i:drafting) 119 | draftingCards.add(allowedCards.get(i)); 120 | 121 | 122 | draft = new Card[Constants.CARDS_IN_DECK][3]; 123 | for (int pick = 0; pick < Constants.CARDS_IN_DECK; pick++) 124 | { 125 | int choice1 = drafting.get(choicesRNG.nextInt(drafting.size())); 126 | int choice2; 127 | do 128 | { 129 | choice2 = drafting.get(choicesRNG.nextInt(drafting.size())); 130 | } while (choice2==choice1); 131 | int choice3; 132 | do 133 | { 134 | choice3 = drafting.get(choicesRNG.nextInt(drafting.size())); 135 | } while (choice3==choice1 || choice3==choice2); 136 | 137 | draft[pick][0] = allowedCards.get(choice1); 138 | draft[pick][1] = allowedCards.get(choice2); 139 | draft[pick][2] = allowedCards.get(choice3); 140 | } 141 | 142 | prepareShowdraft(); 143 | } 144 | 145 | private void prepareShowdraft() 146 | { 147 | showdraftQuantities = new int[161]; 148 | for (int choice = 0; choice < 90; choice++) 149 | showdraftQuantities[draft[choice/3][choice%3].baseId]++; 150 | 151 | showdraftCards = new ArrayList<>(draftingCards); 152 | 153 | showdraftCards.sort((lhs, rhs) -> { 154 | if (lhs.cost == rhs.cost && showdraftQuantities[rhs.baseId] == showdraftQuantities[lhs.baseId]) 155 | return lhs.baseId - rhs.baseId; 156 | else if (lhs.cost == rhs.cost) 157 | return showdraftQuantities[rhs.baseId] - showdraftQuantities[lhs.baseId]; 158 | else 159 | return lhs.cost - rhs.cost; 160 | }); 161 | } 162 | 163 | public ChoiceResultPair PlayerChoice(int pickNumber, String action, int player) throws InvalidActionHard 164 | { 165 | Card[] cards = draft[pickNumber]; 166 | Card choice = null; 167 | String text = ""; 168 | try 169 | { 170 | String[] command = action.split(" ", 3); 171 | text = command.length < 3 ? "" : command[2].trim(); 172 | 173 | if (!command[0].equals("PICK") && !command[0].equals("CHOOSE") && !command[0].equals("PASS")) 174 | throw new Exception(); 175 | if (command[0].equals("PASS")) { 176 | choice = cards[0]; 177 | } 178 | else if (command[0].equals("PICK")) 179 | { 180 | int value = Integer.parseInt(command[1]); 181 | if (value < 0 || value > 2) 182 | throw new InvalidActionHard("Invalid action format. \"PICK\" argument should be 0, 1 or 2."); 183 | choice = cards[value]; 184 | } 185 | else // "CHOOSE" 186 | { 187 | HashSet ids = new HashSet<>(); 188 | ids.add(cards[0].baseId); 189 | ids.add(cards[1].baseId); 190 | ids.add(cards[2].baseId); 191 | int value = Integer.parseInt(command[1]); 192 | 193 | if (!ids.contains(value)) 194 | throw new InvalidActionHard("Invalid action format. \"CHOOSE\" argument should be valid card's base id " + ids + "."); 195 | choice = Constants.CARDSET.get(value); 196 | } 197 | } 198 | catch (InvalidActionHard e) 199 | { 200 | throw e; 201 | } 202 | catch (Exception e) 203 | { 204 | throw new InvalidActionHard("Invalid action. Expected \"PICK [0,1,2]\" or \"PASS\"."); 205 | } 206 | 207 | chosenCards[player].add(choice); 208 | return new ChoiceResultPair(choice, text); 209 | } 210 | 211 | public void ShuffleDecks() 212 | { 213 | for (int player=0; player <2; player++) 214 | { 215 | for (Card c : chosenCards[player]) 216 | { 217 | decks[player].add(new Card(c)); 218 | } 219 | Collections.shuffle(decks[player], shufflesRNG[player]); 220 | for (int i=0; i < decks[player].size(); i++) 221 | decks[player].get(i).id = 2 * i + player + 1; 222 | } 223 | } 224 | 225 | public class ChoiceResultPair 226 | { 227 | public Card card; 228 | public String text; 229 | 230 | public ChoiceResultPair(Card card, String text) 231 | { 232 | this.card = card; 233 | this.text = text; 234 | } 235 | } 236 | 237 | public String[] getMockPlayersInput(int player, int turn) { 238 | ArrayList lines = new ArrayList<>(); 239 | lines.add(join(Constants.INITIAL_HEALTH, 0, turn , 25, 0)); 240 | lines.add(join(Constants.INITIAL_HEALTH, 0, player==0 ? turn : (turn+1), 25, 0)); 241 | lines.add("0 0"); 242 | lines.add("3"); 243 | 244 | 245 | return lines.stream().toArray(String[]::new); 246 | } 247 | 248 | static public String join(Object... args) { 249 | return Stream.of(args).map(String::valueOf).collect(Collectors.joining(" ")); 250 | } 251 | } 252 | 253 | 254 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/EngineReferee.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.codingame.game.Player; 7 | import com.codingame.game.engine.Action.Type; 8 | import com.codingame.game.ui.ConstantsUI; 9 | import com.codingame.game.ui.RefereeUI; 10 | import com.codingame.gameengine.core.AbstractPlayer.TimeoutException; 11 | import com.codingame.gameengine.core.MultiplayerGameManager; 12 | 13 | public class EngineReferee { 14 | //private MultiplayerGameManager gameManager; // @Inject ? 15 | 16 | public DraftPhase draft; 17 | public GameState state = null; 18 | 19 | public int gamePlayer = 0; 20 | public int gameTurn = 0; 21 | public int initGameTurn = 0; 22 | 23 | public List actionsToHandle = new ArrayList<>(); 24 | 25 | private boolean showBattleStart = true; 26 | private boolean showDraftStart = true; 27 | public int showdraftSizechoice = 0; 28 | 29 | static final int ILLEGAL_ACTION_SUMMARY_LIMIT =3; 30 | 31 | public void refereeInit(MultiplayerGameManager gameManager) { 32 | if (Constants.VERBOSE_LEVEL > 1) System.out.println("New game"); 33 | 34 | RefereeParams params = new RefereeParams(gameManager); 35 | 36 | DraftPhase.Difficulty difficulty; 37 | switch (gameManager.getLeagueLevel()) { 38 | case 1: 39 | difficulty = DraftPhase.Difficulty.VERY_EASY; 40 | break; 41 | case 2: 42 | difficulty = DraftPhase.Difficulty.EASY; 43 | break; 44 | case 3: 45 | difficulty = DraftPhase.Difficulty.LESS_EASY; 46 | break; 47 | default: 48 | difficulty = DraftPhase.Difficulty.NORMAL; 49 | break; 50 | } 51 | 52 | 53 | Constants.LoadCardlist("cardlist.txt"); 54 | if (Constants.VERBOSE_LEVEL > 1) System.out.println(" CARDSET with " + Constants.CARDSET.size() + " cards loaded."); 55 | if (Constants.VERBOSE_LEVEL > 1) System.out.println(" Difficulty is set to: " + difficulty.name() + "."); 56 | 57 | draft = new DraftPhase(difficulty, params); 58 | draft.PrepareChoices(); 59 | 60 | for (int i=0; i < ConstantsUI.SHOWDRAFT_SIZECHOICE.length; i++) 61 | { 62 | if (draft.showdraftCards.size() > ConstantsUI.SHOWDRAFT_SIZECHOICE[i]) 63 | break; 64 | showdraftSizechoice = i; 65 | } 66 | //System.out.println(showdraftSizechoice); 67 | gameTurn = -1 - (int)Math.ceil(Math.max(0,(draft.showdraftCards.size() - ConstantsUI.SHOWDRAFT_ROWSIZE[showdraftSizechoice])) / (double)ConstantsUI.SHOWDRAFT_ROWSIZE[showdraftSizechoice]); 68 | initGameTurn = gameTurn; 69 | 70 | if (Constants.VERBOSE_LEVEL > 1) System.out.println(" Draw Phase Prepared. " + draft.allowedCards.size() + " cards allowed. "); 71 | if (Constants.VERBOSE_LEVEL > 1) System.out.println(" " + draft.draftingCards.size() + " cards selected to the draft."); 72 | 73 | gameManager.setMaxTurns(Constants.MAX_TURNS_HARDLIMIT); // should be never reached, not handled on the referee's side 74 | } 75 | 76 | 77 | public boolean refereeGameTurn(MultiplayerGameManager gameManager, RefereeUI ui) 78 | { 79 | if (showDraftStart && gameTurn ==0) { 80 | showDraftStart = false; 81 | gameManager.addTooltip(gameManager.getPlayer(0), "Draft phase."); 82 | } 83 | if (showBattleStart && gameTurn == Constants.CARDS_IN_DECK) { 84 | showBattleStart = false; 85 | gameManager.addTooltip(gameManager.getPlayer(0), "Battle phase."); 86 | } 87 | 88 | if (gameTurn < 0) 89 | { 90 | gameManager.getPlayer(0).expectedOutputLines = 0; 91 | gameManager.getPlayer(0).execute(); 92 | gameManager.getPlayer(0).expectedOutputLines = 1; 93 | gameTurn++; 94 | 95 | return false; 96 | } 97 | 98 | 99 | 100 | if (gameTurn < Constants.CARDS_IN_DECK) 101 | { 102 | DraftTurn(gameManager, () -> ui.draft(gameTurn)); 103 | return false; 104 | } 105 | else 106 | { 107 | return GameTurn(gameManager, () -> ui.battle(gameTurn)); 108 | } 109 | } 110 | 111 | private void DraftTurn(MultiplayerGameManager gameManager, Runnable render) { 112 | if (Constants.VERBOSE_LEVEL > 1 && gameTurn == 0) System.out.println(" Draft phase"); 113 | if (Constants.VERBOSE_LEVEL > 2) System.out.println(" Draft turn " + gameTurn + "/" + Constants.CARDS_IN_DECK); 114 | 115 | gameManager.setTurnMaxTime(gameTurn == 0 ? Constants.TIMELIMIT_FIRSTDRAFTTURN : Constants.TIMELIMIT_DRAFTTURN); 116 | 117 | for (int player = 0; player < 2; player++) { 118 | Player sdkplayer = gameManager.getPlayer(player); 119 | for (String line : draft.getMockPlayersInput(player, gameTurn)) { 120 | sdkplayer.sendInputLine(line); 121 | } 122 | 123 | for (int card = 0; card < 3; card++) 124 | sdkplayer.sendInputLine(draft.draft[gameTurn][card].getAsInput()); 125 | sdkplayer.execute(); 126 | } 127 | 128 | for (int player = 0; player < 2; player++) { 129 | Player sdkplayer = gameManager.getPlayer(player); 130 | try { 131 | String output = sdkplayer.getOutputs().get(0); 132 | DraftPhase.ChoiceResultPair choice = draft.PlayerChoice(gameTurn, output, player); 133 | draft.text[player] = choice.text; 134 | gameManager.addToGameSummary( 135 | String.format("Player %s chose %s", sdkplayer.getNicknameToken(), choice.card.toDescriptiveString()) 136 | ); 137 | } catch (InvalidActionHard e) { 138 | HandleError(gameManager, sdkplayer, sdkplayer.getNicknameToken() + ": " + e.getMessage()); 139 | return; 140 | } catch (TimeoutException e) { 141 | HandleError(gameManager, sdkplayer, sdkplayer.getNicknameToken() + " timeout!"); 142 | return; 143 | } 144 | } 145 | 146 | render.run(); 147 | gameTurn++; 148 | } 149 | 150 | private boolean GameTurn(MultiplayerGameManager gameManager, Runnable render) { 151 | Player sdkplayer = gameManager.getPlayer(gamePlayer); 152 | gameManager.setFrameDuration(Constants.FRAME_DURATION_BATTLE); 153 | 154 | if (state == null) // frame-only turn for showing the initial state 155 | { 156 | draft.ShuffleDecks(); 157 | if (Constants.VERBOSE_LEVEL > 1) System.out.println(" Decks shuffled."); 158 | if (Constants.VERBOSE_LEVEL > 1) System.out.println(" Game phase"); 159 | state = new GameState(draft); 160 | 161 | //gameManager.setTurnMaxTime(1); // weird try but works ^^ 162 | sdkplayer.expectedOutputLines = 0; 163 | sdkplayer.execute(); 164 | sdkplayer.expectedOutputLines = 1; 165 | 166 | render.run(); 167 | return false; 168 | } 169 | 170 | if (!actionsToHandle.isEmpty()) // there is a legal action on top of the list 171 | { 172 | //gameManager.setTurnMaxTime(1); // weird try but works ^^ 173 | sdkplayer.expectedOutputLines = 0; 174 | sdkplayer.execute(); 175 | sdkplayer.expectedOutputLines = 1; 176 | 177 | Action a = actionsToHandle.remove(0); 178 | gameManager.addToGameSummary("Player " + sdkplayer.getNicknameToken() + " performed action: " + a.toStringNoText()); 179 | 180 | state.AdvanceState(a); 181 | if (a.type == Action.Type.SUMMON) { 182 | gameManager.setFrameDuration(Constants.FRAME_DURATION_SUMMON); 183 | } 184 | } else // it's time to actually call a player 185 | { 186 | if (Constants.VERBOSE_LEVEL > 2) System.out.print(" Game turn " + (gameTurn - Constants.CARDS_IN_DECK) + ", player " + gamePlayer); 187 | 188 | gameManager.setTurnMaxTime(gameTurn <= Constants.CARDS_IN_DECK + 1 ? Constants.TIMELIMIT_FIRSTGAMETURN : Constants.TIMELIMIT_GAMETURN); 189 | 190 | state.AdvanceState(); 191 | 192 | for (String line : state.getPlayersInput()) 193 | sdkplayer.sendInputLine(line); 194 | for (String line : state.getCardsInput()) 195 | sdkplayer.sendInputLine(line); 196 | sdkplayer.execute(); 197 | 198 | try { 199 | String output = sdkplayer.getOutputs().get(0); 200 | actionsToHandle = Action.parseSequence(output); 201 | if (Constants.VERBOSE_LEVEL > 2) System.out.println(" (returned " + actionsToHandle.size() + " actions)"); 202 | } catch (InvalidActionHard e) { 203 | HandleError(gameManager, sdkplayer, sdkplayer.getNicknameToken() + ": " + e.getMessage()); 204 | } catch (TimeoutException e) { 205 | HandleError(gameManager, sdkplayer, sdkplayer.getNicknameToken() + " timeout!"); 206 | } 207 | } 208 | 209 | // now we roll-out actions until next legal is found 210 | List legals = state.computeLegalActions(); //System.out.println(gameTurn + " "+ state.players[state.currentPlayer].currentMana +"/"+state.players[state.currentPlayer].maxMana + "->"+legals); 211 | int illegalActions = 0; 212 | 213 | while (!actionsToHandle.isEmpty()) { 214 | Action a = actionsToHandle.get(0); 215 | if (a.type == Type.PASS) { 216 | actionsToHandle.remove(0); // pop 217 | continue; 218 | } 219 | if (legals.contains(a)) 220 | break; 221 | actionsToHandle.remove(0); // pop 222 | illegalActions++; 223 | if (illegalActions <= ILLEGAL_ACTION_SUMMARY_LIMIT) { 224 | gameManager.addToGameSummary("[Warning] " + sdkplayer.getNicknameToken() + " Action is not legal: " + a.toString()); 225 | } 226 | } 227 | if (illegalActions > ILLEGAL_ACTION_SUMMARY_LIMIT) { 228 | gameManager.addToGameSummary("[Warning] " + sdkplayer.getNicknameToken() + " Performed another " + (illegalActions - ILLEGAL_ACTION_SUMMARY_LIMIT) + " illegalActions"); 229 | } 230 | 231 | render.run(); 232 | 233 | if (CheckAndHandleEndgame(gameManager, state)) 234 | return true; 235 | 236 | if (actionsToHandle.isEmpty()) // player change 237 | { 238 | gameTurn++; 239 | gamePlayer = (gamePlayer + 1) % 2; 240 | } 241 | 242 | return false; 243 | } 244 | 245 | private void HandleError(MultiplayerGameManager gameManager, Player sdkplayer, String errmsg) { 246 | gameManager.addToGameSummary(MultiplayerGameManager.formatErrorMessage(errmsg)); 247 | sdkplayer.deactivate(errmsg); 248 | sdkplayer.setScore(-1); 249 | gameManager.endGame(); 250 | } 251 | 252 | // returns true if the game ends 253 | private boolean CheckAndHandleEndgame(MultiplayerGameManager gameManager, GameState state) { 254 | if (state.winner == -1) 255 | return false; 256 | 257 | //gameManager.addToGameSummary("!\n" + state.toString()); 258 | 259 | if (Constants.VERBOSE_LEVEL > 1) System.out.println(" Game finished in turn " + (gameTurn - Constants.CARDS_IN_DECK) + "."); 260 | if (Constants.VERBOSE_LEVEL > 1) System.out.print(" Scores: "); 261 | if (Constants.VERBOSE_LEVEL > 0) System.out.println((state.winner == 0 ? "1" : "0") + " " + (state.winner == 1 ? "1" : "0")); 262 | 263 | gameManager.addToGameSummary(MultiplayerGameManager.formatSuccessMessage(gameManager.getPlayer(state.winner).getNicknameToken() + " won!")); 264 | gameManager.getPlayer(state.winner).setScore(1); 265 | gameManager.endGame(); 266 | return true; 267 | } 268 | 269 | } 270 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/GameState.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | 7 | /** 8 | * Created by aCat on 2018-03-20. 9 | */ 10 | public class GameState 11 | { 12 | public int turn; 13 | public int winner; 14 | public int currentPlayer; 15 | public Gamer[] players; 16 | public HashMap cardIdMap; 17 | 18 | public GameState(DraftPhase draft) 19 | { 20 | assert( draft.decks[0].size()==Constants.CARDS_IN_DECK); 21 | assert( draft.decks[1].size()==Constants.CARDS_IN_DECK); 22 | 23 | turn = -1; 24 | winner = -1; 25 | currentPlayer = 1; 26 | players = new Gamer[] {new Gamer(0, draft.decks[0]), new Gamer(1, draft.decks[1])}; 27 | 28 | cardIdMap = new HashMap<>(); 29 | for (int i=0; i < 2; i++) 30 | for (Card c: draft.decks[i]) 31 | cardIdMap.put(c.id, c); 32 | 33 | 34 | } 35 | 36 | 37 | 38 | public List computeLegalActions() 39 | { 40 | List legals = new ArrayList<>(); 41 | legals.addAll(computeLegalSummons()); 42 | legals.addAll(computeLegalAttacks()); 43 | legals.addAll(computeLegalItems()); 44 | legals.add(Action.newPass()); 45 | return legals; 46 | } 47 | 48 | public List computeLegalSummons() 49 | { 50 | Gamer player = players[currentPlayer]; 51 | ArrayList actions = new ArrayList<>(); 52 | 53 | 54 | if (Constants.LANES==1) 55 | { 56 | if (player.board.size() == Constants.MAX_CREATURES_IN_LINE) 57 | return actions; 58 | 59 | for (Card c : player.hand) 60 | { 61 | if (c.type != Card.Type.CREATURE || c.cost > player.currentMana) 62 | continue; 63 | actions.add(Action.newSummon(c.id)); 64 | } 65 | 66 | return actions; 67 | } 68 | 69 | int[] creaturesinline = new int[Constants.LANES]; 70 | for (CreatureOnBoard c:player.board) 71 | creaturesinline[c.lane]++; 72 | 73 | for (int l=0; l < Constants.LANES; l++) 74 | { 75 | if (creaturesinline[l] == Constants.MAX_CREATURES_IN_LINE / Constants.LANES) 76 | continue; 77 | 78 | for (Card c : player.hand) 79 | { 80 | if (c.type != Card.Type.CREATURE || c.cost > player.currentMana) 81 | continue; 82 | actions.add(Action.newSummon(c.id, l)); 83 | } 84 | } 85 | 86 | return actions; 87 | } 88 | 89 | private List computeLegalTargets(int lane) 90 | { 91 | Gamer enemyPlayer = players[1 - currentPlayer]; 92 | 93 | ArrayList targets = new ArrayList<>(); 94 | 95 | for (CreatureOnBoard c : enemyPlayer.board) // First priority - guards 96 | if (c.keywords.hasGuard && c.lane==lane) 97 | targets.add(c.id); 98 | 99 | if (targets.isEmpty()) // if no guards we can freely attack any creature plus face 100 | { 101 | targets.add(-1); 102 | for (CreatureOnBoard c : enemyPlayer.board) 103 | if (c.lane==lane) 104 | targets.add(c.id); 105 | } 106 | 107 | return targets; 108 | } 109 | 110 | public List computeLegalAttacks() 111 | { 112 | Gamer player = players[currentPlayer]; 113 | ArrayList actions = new ArrayList<>(); 114 | List targets; 115 | 116 | if (Constants.LANES==1) 117 | { 118 | targets = computeLegalTargets(0); 119 | 120 | for (CreatureOnBoard c : player.board) 121 | { 122 | if (!c.canAttack) 123 | continue; 124 | for (Integer tid : targets) 125 | actions.add(Action.newAttack(c.id, tid)); 126 | } 127 | return actions; 128 | } 129 | 130 | for (CreatureOnBoard c : player.board) 131 | { 132 | if (!c.canAttack) 133 | continue; 134 | targets = computeLegalTargets(c.lane); 135 | for (Integer tid : targets) 136 | actions.add(Action.newAttack(c.id, tid)); 137 | } 138 | return actions; 139 | 140 | } 141 | 142 | public List computeLegalItems() 143 | { 144 | Gamer player = players[currentPlayer]; 145 | 146 | ArrayList actions = new ArrayList<>(); 147 | 148 | for (Card c:player.hand) 149 | { 150 | if (c.type == Card.Type.CREATURE || c.cost > player.currentMana) 151 | continue; 152 | 153 | if (c.type == Card.Type.ITEM_GREEN) // on friendly creatures 154 | { 155 | for (CreatureOnBoard cb : player.board) 156 | actions.add(Action.newUse(c.id, cb.id)); 157 | } 158 | else // red or blue item: on enemy creatures 159 | { 160 | for (CreatureOnBoard cb : players[1-currentPlayer].board) 161 | actions.add(Action.newUse(c.id, cb.id)); 162 | if (c.type == Card.Type.ITEM_BLUE) // blue also on the player 163 | actions.add(Action.newUse(c.id, -1)); 164 | } 165 | } 166 | 167 | return actions; 168 | } 169 | 170 | 171 | 172 | public void AdvanceState() 173 | { 174 | CheckWinCondition(); 175 | 176 | for (CreatureOnBoard c : players[currentPlayer].board) { 177 | c.canAttack = false; 178 | c.hasAttacked = false; 179 | } 180 | 181 | players[currentPlayer].drawValueToShow = players[currentPlayer].nextTurnDraw; // this is the correct way 182 | 183 | currentPlayer = 1 - currentPlayer; 184 | Gamer player = players[currentPlayer]; 185 | player.performedActions.clear(); 186 | turn++; 187 | 188 | 189 | if (player.maxMana < Constants.MAX_MANA + (player.bonusManaTurns > 0 ? 1 : 0 )) 190 | { 191 | player.maxMana += 1; 192 | } 193 | 194 | if (player.bonusManaTurns > 0 && player.currentMana==0) 195 | { 196 | player.bonusManaTurns--; 197 | if (player.bonusManaTurns==0) 198 | player.maxMana--; 199 | } 200 | 201 | player.currentMana = player.maxMana; 202 | 203 | for (CreatureOnBoard c : player.board) 204 | { 205 | c.canAttack = true; // mark ALL creatures as ready to charge 206 | //if (c.keywords.hasRegenerate && c.defense < c.lastTurnDefense) 207 | //c.defense = c.lastTurnDefense; 208 | c.lastTurnDefense = c.defense; // for all creatures (just in case) 209 | } 210 | 211 | player.DrawCards(player.nextTurnDraw, turn/2); 212 | player.drawValueToShow = player.nextTurnDraw; 213 | player.nextTurnDraw = 1; 214 | CheckWinCondition(); 215 | } 216 | 217 | public void AdvanceState(Action action) // ASSUMING THE ACTION IS LEGAL ! 218 | { 219 | if (action.type == Action.Type.SUMMON) // SUMMON [id] 220 | { 221 | Card c = cardIdMap.get(action.arg1); 222 | 223 | players[currentPlayer].hand.remove(c); 224 | players[currentPlayer].currentMana -= c.cost; 225 | CreatureOnBoard creature = (Constants.LANES==1) ? new CreatureOnBoard(c, 0) : new CreatureOnBoard(c, action.arg2); 226 | players[currentPlayer].board.add(creature); 227 | 228 | players[currentPlayer].ModifyHealth(c.myHealthChange); 229 | players[1-currentPlayer].ModifyHealth(c.oppHealthChange); 230 | players[currentPlayer].nextTurnDraw += c.cardDraw; 231 | 232 | action.result = new ActionResult(creature, null, false, false, c.myHealthChange, c.oppHealthChange); 233 | } 234 | else if (action.type == Action.Type.ATTACK) // ATTACK [id1] [id2] 235 | { 236 | int indexatt = -1; 237 | for (int i=0; i < players[currentPlayer].board.size(); i++) 238 | if (players[currentPlayer].board.get(i).id==action.arg1) 239 | indexatt = i; 240 | CreatureOnBoard att = players[currentPlayer].board.get(indexatt); 241 | 242 | int indexdef = -1; 243 | CreatureOnBoard def; 244 | ActionResult result = null; 245 | 246 | if (action.arg2 == -1) // attacking player 247 | { 248 | result = ResolveAttack(att); 249 | } 250 | else 251 | { 252 | for (int i=0; i < players[1-currentPlayer].board.size(); i++) 253 | if (players[1-currentPlayer].board.get(i).id==action.arg2) 254 | indexdef = i; 255 | def = players[1-currentPlayer].board.get(indexdef); 256 | 257 | result = ResolveAttack(att, def); 258 | 259 | if (result.defenderDied) { 260 | players[1-currentPlayer].removeFromBoard(indexdef); 261 | 262 | } 263 | else 264 | players[1-currentPlayer].board.set(indexdef, result.defender); 265 | } 266 | 267 | if (result.attackerDied) 268 | players[currentPlayer].removeFromBoard(indexatt); 269 | else 270 | players[currentPlayer].board.set(indexatt, result.attacker); 271 | 272 | players[currentPlayer].ModifyHealth(result.attackerHealthChange); 273 | players[1-currentPlayer].ModifyHealth(result.defenderHealthChange); 274 | action.result = result; 275 | } 276 | else if (action.type == Action.Type.USE) // USE [id1] [id2] 277 | { 278 | Card item = cardIdMap.get(action.arg1); 279 | 280 | players[currentPlayer].hand.remove(item); 281 | players[currentPlayer].currentMana -= item.cost; 282 | 283 | if (item.type == Card.Type.ITEM_GREEN) // here we assume that green cards never remove friendly creatures! 284 | { 285 | int indextarg = -1; 286 | for (int i=0; i < players[currentPlayer].board.size(); i++) 287 | if (players[currentPlayer].board.get(i).id==action.arg2) 288 | indextarg = i; 289 | CreatureOnBoard targ = players[currentPlayer].board.get(indextarg); 290 | 291 | ActionResult result = ResolveUse(item, targ); 292 | 293 | players[currentPlayer].board.set(indextarg, result.defender); 294 | 295 | players[currentPlayer].ModifyHealth(result.attackerHealthChange); 296 | players[1-currentPlayer].ModifyHealth(result.defenderHealthChange); 297 | players[currentPlayer].nextTurnDraw += item.cardDraw; 298 | action.result = result; 299 | } 300 | else // red and blue cards 301 | { 302 | int indextarg = -1; 303 | ActionResult result = null; 304 | 305 | if (action.arg2 == -1) // using on player 306 | { 307 | result = ResolveUse(item); 308 | } 309 | else // using on creature 310 | { 311 | for (int i=0; i < players[1-currentPlayer].board.size(); i++) 312 | if (players[1-currentPlayer].board.get(i).id==action.arg2) 313 | indextarg = i; 314 | CreatureOnBoard targ = players[1-currentPlayer].board.get(indextarg); 315 | 316 | result = ResolveUse(item, targ); 317 | 318 | if (result.defenderDied) 319 | players[1-currentPlayer].removeFromBoard(indextarg); 320 | else 321 | players[1-currentPlayer].board.set(indextarg, result.defender); 322 | } 323 | 324 | players[currentPlayer].ModifyHealth(result.attackerHealthChange); 325 | players[1-currentPlayer].ModifyHealth(result.defenderHealthChange); 326 | players[currentPlayer].nextTurnDraw += item.cardDraw; 327 | action.result = result; 328 | } 329 | } 330 | 331 | players[currentPlayer].performedActions.add(action); 332 | CheckWinCondition(); 333 | } 334 | 335 | 336 | public void CheckWinCondition() 337 | { 338 | if (players[1-currentPlayer].health <= 0) // first proper win 339 | winner = currentPlayer; 340 | else if (players[currentPlayer].health <= 0) // second self-kill 341 | winner = 1-currentPlayer; 342 | } 343 | 344 | // when creature attacks creatures // run it ONLY on legal actions 345 | public static ActionResult ResolveAttack(CreatureOnBoard attacker, CreatureOnBoard defender) 346 | { 347 | if (!attacker.canAttack) 348 | return new ActionResult(false); 349 | 350 | CreatureOnBoard attackerAfter = new CreatureOnBoard(attacker); 351 | CreatureOnBoard defenderAfter = new CreatureOnBoard(defender); 352 | 353 | attackerAfter.canAttack = false; 354 | attackerAfter.hasAttacked = true; 355 | 356 | if (defender.keywords.hasWard) defenderAfter.keywords.hasWard = attacker.attack == 0; 357 | if (attacker.keywords.hasWard) attackerAfter.keywords.hasWard = defender.attack == 0; 358 | 359 | int damageGiven = defender.keywords.hasWard ? 0 : attacker.attack; 360 | int damageTaken = attacker.keywords.hasWard ? 0 : defender.attack; 361 | int healthGain = 0; 362 | int healthTaken = 0; 363 | 364 | // attacking 365 | if (damageGiven >= defender.defense) defenderAfter = null; 366 | if (attacker.keywords.hasBreakthrough && defenderAfter==null) healthTaken = defender.defense - damageGiven; 367 | if (attacker.keywords.hasLethal && damageGiven > 0) defenderAfter = null; 368 | if (attacker.keywords.hasDrain && damageGiven > 0) healthGain = attacker.attack; 369 | if (defenderAfter != null) defenderAfter.defense -= damageGiven; 370 | 371 | // defending 372 | if (damageTaken >= attacker.defense) attackerAfter = null; 373 | if (defender.keywords.hasLethal && damageTaken > 0) attackerAfter = null; 374 | if (attackerAfter != null) attackerAfter.defense -= damageTaken; 375 | ActionResult result = new ActionResult(attackerAfter == null ? attacker : attackerAfter, defenderAfter == null ? defender : defenderAfter, attackerAfter == null, defenderAfter == null, healthGain, healthTaken); 376 | result.attackerDefenseChange = -damageTaken; 377 | result.defenderDefenseChange = -damageGiven; 378 | return result; 379 | } 380 | 381 | // when creature attacks player // run it ONLY on legal actions 382 | public static ActionResult ResolveAttack(CreatureOnBoard attacker) 383 | { 384 | if (!attacker.canAttack) 385 | return new ActionResult(false); 386 | 387 | CreatureOnBoard attackerAfter = new CreatureOnBoard(attacker); 388 | 389 | attackerAfter.canAttack = false; 390 | attackerAfter.hasAttacked = true; 391 | 392 | int healthGain = attacker.keywords.hasDrain ? attacker.attack : 0; 393 | int healthTaken = -attacker.attack; 394 | 395 | ActionResult result = new ActionResult(attackerAfter, null, healthGain, healthTaken); 396 | result.defenderDefenseChange = healthTaken; 397 | return result; 398 | } 399 | 400 | // when item is used on a creature // run it ONLY on legal actions 401 | public static ActionResult ResolveUse(Card item, CreatureOnBoard target) 402 | { 403 | CreatureOnBoard targetAfter = new CreatureOnBoard(target); 404 | 405 | if (item.type==Card.Type.ITEM_GREEN) // add keywords 406 | { 407 | targetAfter.keywords.hasCharge = target.keywords.hasCharge || item.keywords.hasCharge; 408 | if (item.keywords.hasCharge) 409 | targetAfter.canAttack = !targetAfter.hasAttacked; // No Swift Strike hack 410 | targetAfter.keywords.hasBreakthrough = target.keywords.hasBreakthrough || item.keywords.hasBreakthrough; 411 | targetAfter.keywords.hasDrain = target.keywords.hasDrain || item.keywords.hasDrain; 412 | targetAfter.keywords.hasGuard = target.keywords.hasGuard || item.keywords.hasGuard; 413 | targetAfter.keywords.hasLethal = target.keywords.hasLethal || item.keywords.hasLethal; 414 | //targetAfter.keywords.hasRegenerate = target.keywords.hasRegenerate || item.keywords.hasRegenerate; 415 | targetAfter.keywords.hasWard = target.keywords.hasWard || item.keywords.hasWard; 416 | } 417 | else // Assumming ITEM_BLUE or ITEM_RED - remove keywords 418 | { 419 | targetAfter.keywords.hasCharge = target.keywords.hasCharge && !item.keywords.hasCharge; 420 | targetAfter.keywords.hasBreakthrough = target.keywords.hasBreakthrough && !item.keywords.hasBreakthrough; 421 | targetAfter.keywords.hasDrain = target.keywords.hasDrain && !item.keywords.hasDrain; 422 | targetAfter.keywords.hasGuard = target.keywords.hasGuard && !item.keywords.hasGuard; 423 | targetAfter.keywords.hasLethal = target.keywords.hasLethal && !item.keywords.hasLethal; 424 | //targetAfter.keywords.hasRegenerate = target.keywords.hasRegenerate && !item.keywords.hasRegenerate; 425 | targetAfter.keywords.hasWard = target.keywords.hasWard && !item.keywords.hasWard; 426 | } 427 | 428 | targetAfter.attack = Math.max(0, target.attack + item.attack); 429 | 430 | if (targetAfter.keywords.hasWard && item.defense < 0) 431 | targetAfter.keywords.hasWard = false; 432 | else 433 | targetAfter.defense += item.defense; 434 | if (targetAfter.defense <= 0) targetAfter = null; 435 | 436 | int itemgiverHealthChange = item.myHealthChange; 437 | int targetHealthChange = item.oppHealthChange; 438 | 439 | ActionResult result = new ActionResult(new CreatureOnBoard(item), targetAfter == null ? target : targetAfter, false, targetAfter == null, itemgiverHealthChange, targetHealthChange); 440 | result.defenderAttackChange = item.attack; 441 | result.defenderDefenseChange = item.defense; 442 | return result; 443 | } 444 | 445 | // when item is used on a player // run it ONLY on legal actions 446 | public static ActionResult ResolveUse(Card item) 447 | { 448 | int itemgiverHealthChange = item.myHealthChange; 449 | int targetHealthChange = item.defense + item.oppHealthChange; 450 | 451 | return new ActionResult(null, null, itemgiverHealthChange, targetHealthChange); 452 | } 453 | 454 | // old method 455 | public String[] toStringLines() 456 | { 457 | ArrayList lines = new ArrayList<>(); 458 | 459 | Gamer player = players[currentPlayer]; 460 | Gamer opponent = players[1-currentPlayer]; 461 | 462 | lines.add(String.valueOf(turn)); 463 | lines.add(String.format("%d %d %d %d %d %d %d", player.health, player.nextRune(), player.maxMana, player.nextTurnDraw, player.hand.size(), player.board.size(), player.deck.size())); 464 | for (Card c : player.hand) 465 | lines.add(c.toString()); 466 | for (CreatureOnBoard b : player.board) 467 | lines.add(b.toString()); 468 | lines.add(String.format("%d %d %d %d %d %d %d %d", opponent.health, opponent.nextRune(), opponent.maxMana, opponent.nextTurnDraw, opponent.hand.size(), opponent.board.size(), opponent.deck.size(), opponent.performedActions.size())); 469 | for (CreatureOnBoard b : opponent.board) 470 | lines.add(b.toString()); 471 | for (Action a: opponent.performedActions) 472 | lines.add(cardIdMap.get(a.arg1).baseId + " " + a.toStringNoText()); 473 | 474 | return lines.stream().toArray(String[]::new); 475 | } 476 | 477 | public String[] getPlayersInput() { 478 | ArrayList lines = new ArrayList<>(); 479 | 480 | Gamer player = players[currentPlayer]; 481 | Gamer opponent = players[1-currentPlayer]; 482 | lines.add(player.getPlayerInput()); 483 | lines.add(opponent.getPlayerInput()); 484 | 485 | return lines.stream().toArray(String[]::new); 486 | } 487 | 488 | public String[] getCardsInput() { 489 | ArrayList lines = new ArrayList<>(); 490 | 491 | Gamer player = players[currentPlayer]; 492 | Gamer opponent = players[1-currentPlayer]; 493 | 494 | lines.add(String.valueOf(opponent.hand.size()) + " " + toString().valueOf(opponent.performedActions.size())); 495 | for (Action a:opponent.performedActions) 496 | lines.add(String.valueOf(cardIdMap.get(a.arg1).baseId)+ " " + a.toStringNoText()); 497 | 498 | int cardCount = player.hand.size() + player.board.size() + opponent.board.size(); 499 | lines.add(String.valueOf(cardCount)); 500 | 501 | for (Card c : player.hand) 502 | lines.add(c.getAsInput()); 503 | for (CreatureOnBoard b : player.board) 504 | lines.add(b.getAsInput(false)); 505 | for (CreatureOnBoard b : opponent.board) 506 | lines.add(b.getAsInput(true)); 507 | return lines.stream().toArray(String[]::new); 508 | } 509 | 510 | public String toString() 511 | { 512 | StringBuilder sb = new StringBuilder(); 513 | 514 | for (String line: getPlayersInput()) 515 | sb.append(line).append("\n"); 516 | for (String line: getPlayersInput()) 517 | sb.append(line).append("\n"); 518 | 519 | return sb.toString(); 520 | } 521 | } 522 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/Gamer.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | 6 | import static com.codingame.game.engine.Constants.INITIAL_HAND_SIZE; 7 | 8 | /** 9 | * Created by aCat on 2018-03-24. 10 | */ 11 | public class Gamer 12 | { 13 | public int id; 14 | public int bonusManaTurns; 15 | public ArrayList hand; 16 | public ArrayList deck; 17 | public ArrayList board; 18 | public ArrayList graveyard; 19 | public int health; 20 | public int maxMana; 21 | public int currentMana; 22 | public int nextTurnDraw; 23 | public int drawValueToShow; 24 | 25 | public ArrayList runes = new ArrayList() {{ add(5);add(10);add(15);add(20);add(25); }}; 26 | public ArrayList performedActions; 27 | public int handLimit; 28 | 29 | // todo rest 30 | 31 | 32 | 33 | public Gamer(int id, ArrayList deck) 34 | { 35 | this.id = id; 36 | this.hand = new ArrayList<>(); 37 | this.deck = new ArrayList<>(deck); 38 | this.board = new ArrayList<>(); 39 | this.graveyard = new ArrayList<>(); 40 | this.performedActions = new ArrayList<>(); 41 | this.health = Constants.INITIAL_HEALTH; 42 | this.maxMana = id==1 && Constants.SECOND_PLAYER_MANA_BONUS_TURNS > 0 ? 1 : 0; 43 | this.currentMana = this.maxMana; 44 | this.nextTurnDraw = 1; 45 | this.drawValueToShow = this.nextTurnDraw; 46 | 47 | bonusManaTurns = id==1? Constants.SECOND_PLAYER_MANA_BONUS_TURNS : 0; 48 | 49 | handLimit = Constants.MAX_CARDS_IN_HAND + (id==0 ? 0 : Constants.SECOND_PLAYER_MAX_CARD_BONUS); 50 | DrawCards(INITIAL_HAND_SIZE + (id==0 ? 0 : Constants.SECOND_PLAYER_CARD_BONUS), 0); 51 | } 52 | 53 | private void suicideRunes() 54 | { 55 | if (!runes.isEmpty()) // first rune gone 56 | { 57 | Integer r = runes.remove(runes.size() - 1); 58 | health = r; 59 | } 60 | else // final run gone - suicide 61 | { 62 | health = 0; 63 | } 64 | } 65 | 66 | public void DrawCards(int n, int playerturn) 67 | { 68 | for (int i=0; i=Constants.PLAYER_TURNLIMIT) 71 | { 72 | suicideRunes(); 73 | continue; 74 | } 75 | 76 | if (hand.size()==handLimit) 77 | { 78 | break; // additional draws are simply wasted 79 | } 80 | 81 | Card c = deck.remove(0); 82 | hand.add(c); 83 | } 84 | 85 | } 86 | 87 | 88 | public void ModifyHealth(int mod) 89 | { 90 | health += mod; 91 | 92 | if (mod >= 0) 93 | return; 94 | 95 | for (int r=runes.size()-1; r >=0; r--) // rune checking; 96 | { 97 | if (health <= runes.get(r)) 98 | { 99 | nextTurnDraw += 1; 100 | runes.remove(r); 101 | } 102 | } 103 | } 104 | 105 | public int nextRune() 106 | { 107 | if (runes.isEmpty()) return 0; 108 | return runes.get(runes.size()-1); 109 | } 110 | 111 | public void removeFromBoard(int creatureIndex) { 112 | graveyard.add(board.remove(creatureIndex)); 113 | } 114 | 115 | public String toDescriptiveString(boolean reverse) 116 | { 117 | String line1 = String.format("[Player %d] Health: %d %s Mana: %d/%d", id, health, runes, currentMana, maxMana); 118 | String line2 = String.format("Cards in hand: %d In deck: %d Next turn draw: %d", hand.size(), deck.size(), nextTurnDraw); 119 | 120 | ArrayList inhand = new ArrayList<>(); 121 | inhand.add("Hand:"); 122 | for (Card c: hand) 123 | inhand.add((c.cost <= this.currentMana ? " * " : " ") + c.toDescriptiveString()); 124 | 125 | ArrayList onboard = new ArrayList<>(); 126 | onboard.add("Board:"); 127 | for (CreatureOnBoard c: board) 128 | onboard.add((c.canAttack ? " * " : " ") + c.toDescriptiveString()); 129 | 130 | ArrayList description = new ArrayList<>(); 131 | description.add(line1); 132 | description.add(line2); 133 | description.add(String.join("\n",inhand)); 134 | description.add(String.join("\n",onboard)); 135 | if (reverse) 136 | Collections.reverse(description); 137 | 138 | return String.join("\n", description); 139 | } 140 | 141 | // todo 142 | public String toString() 143 | { 144 | return super.toString(); 145 | } 146 | 147 | public String getPlayerInput() { 148 | StringBuilder s = new StringBuilder(); 149 | s.append(health).append(" "); 150 | s.append(maxMana).append(" "); 151 | s.append(deck.size()).append(" "); 152 | s.append(nextRune()).append(" "); 153 | s.append(drawValueToShow); 154 | return s.toString(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/InvalidActionHard.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | public class InvalidActionHard extends Exception 4 | { 5 | private static final long serialVersionUID = -8185589153224401565L; 6 | 7 | public InvalidActionHard(String message) 8 | { 9 | super(message); 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/InvalidActionSoft.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | public class InvalidActionSoft extends Exception 4 | { 5 | private static final long serialVersionUID = -8185589153224401564L; 6 | 7 | public InvalidActionSoft(String message) 8 | { 9 | super(message); 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/Keywords.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * http://dominisz.pl 8 | * 02.04.2018 9 | */ 10 | public class Keywords { 11 | 12 | public boolean hasBreakthrough; 13 | public boolean hasCharge; 14 | public boolean hasDrain; 15 | public boolean hasGuard; 16 | public boolean hasLethal; 17 | //public boolean hasRegenerate; 18 | public boolean hasWard; 19 | 20 | public boolean hasAnyKeyword() { 21 | return hasBreakthrough || hasCharge || hasDrain || hasGuard || hasLethal /*|| hasRegenerate*/ || hasWard; 22 | } 23 | 24 | //TODO maybe this method should return already joined String 25 | public List getListOfKeywords() { 26 | List keywords = new ArrayList<>(); 27 | if (hasBreakthrough) keywords.add("Breakthrough"); 28 | if (hasCharge) keywords.add("Charge"); 29 | if (hasDrain) keywords.add("Drain"); 30 | if (hasGuard) keywords.add("Guard"); 31 | if (hasLethal) keywords.add("Lethal"); 32 | //if (hasRegenerate) keywords.add("Regenerate"); 33 | if (hasWard) keywords.add("Ward"); 34 | return keywords; 35 | } 36 | 37 | public Keywords(String data) { 38 | hasBreakthrough = data.charAt(0) == 'B'; 39 | hasCharge = data.charAt(1) == 'C'; 40 | hasDrain = data.charAt(2) == 'D'; 41 | hasGuard = data.charAt(3) == 'G'; 42 | hasLethal = data.charAt(4) == 'L'; 43 | //hasRegenerate = data.charAt(5) == 'R'; 44 | hasWard = data.charAt(5) == 'W'; 45 | } 46 | 47 | public Keywords(Keywords keywords) { 48 | hasBreakthrough = keywords.hasBreakthrough; 49 | hasCharge = keywords.hasCharge; 50 | hasDrain = keywords.hasDrain; 51 | hasGuard = keywords.hasGuard; 52 | hasLethal = keywords.hasLethal; 53 | //hasRegenerate = keywords.hasRegenerate; 54 | hasWard = keywords.hasWard; 55 | } 56 | 57 | public String toString() { 58 | StringBuilder sb = new StringBuilder(); 59 | sb.append(hasBreakthrough ? 'B' : '-'); 60 | sb.append(hasCharge ? 'C' : '-'); 61 | sb.append(hasDrain ? 'D' : '-'); 62 | sb.append(hasGuard ? 'G' : '-'); 63 | sb.append(hasLethal ? 'L' : '-'); 64 | //sb.append(hasRegenerate ? 'R' : '-'); 65 | sb.append(hasWard ? 'W' : '-'); 66 | return sb.toString(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/engine/RefereeParams.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.engine; 2 | 3 | import com.codingame.game.Player; 4 | import com.codingame.gameengine.core.MultiplayerGameManager; 5 | 6 | import java.util.Properties; 7 | import java.util.Random; 8 | 9 | public class RefereeParams 10 | { 11 | public Random draftChoicesRNG; 12 | public Random shufflePlayer0RNG; 13 | public Random shufflePlayer1RNG; 14 | public Integer[][] predefinedDraftIds = null; 15 | private Properties params; 16 | 17 | public RefereeParams(long draftChoicesSeed, long shufflePlayer0Seed, long shufflePlayer1Seed) 18 | { 19 | draftChoicesRNG = new Random(draftChoicesSeed); 20 | shufflePlayer0RNG = new Random(shufflePlayer0Seed); 21 | shufflePlayer1RNG = new Random(shufflePlayer1Seed); 22 | } 23 | 24 | public RefereeParams(MultiplayerGameManager gameManager) 25 | { 26 | // pure initialization if seed set by the manager 27 | Long mainSeed = gameManager.getSeed(); 28 | Random RNG = new Random(mainSeed); 29 | long draftChoicesSeed = RNG.nextLong(); 30 | long shufflePlayer0Seed = RNG.nextLong(); 31 | long shufflePlayer1Seed = RNG.nextLong(); 32 | 33 | params = gameManager.getGameParameters(); 34 | 35 | if ( isNumber(params.getProperty("seed"))) // overriding when seed given as parameter 36 | { 37 | mainSeed = Long.parseLong(params.getProperty("seed")); 38 | RNG = new Random(mainSeed); 39 | draftChoicesSeed = RNG.nextLong(); 40 | shufflePlayer0Seed = RNG.nextLong(); 41 | shufflePlayer1Seed = RNG.nextLong(); 42 | } 43 | 44 | // overriding remaining seeds 45 | if ( isNumber(params.getProperty("draftChoicesSeed"))) 46 | draftChoicesSeed = Long.parseLong(params.getProperty("draftChoicesSeed")); 47 | if ( isNumber(params.getProperty("shufflePlayer0Seed"))) 48 | shufflePlayer0Seed = Long.parseLong(params.getProperty("shufflePlayer0Seed")); 49 | if ( isNumber(params.getProperty("shufflePlayer1Seed"))) 50 | shufflePlayer1Seed = Long.parseLong(params.getProperty("shufflePlayer1Seed")); 51 | 52 | if ( params.getProperty("predefinedDraftIds")!=null) 53 | { 54 | predefinedDraftIds = new Integer[Constants.CARDS_IN_DECK][3]; 55 | String[] picks = params.getProperty("predefinedDraftIds").split(","); 56 | 57 | assert (picks.length >= Constants.CARDS_IN_DECK); 58 | 59 | for(int pick=0; pick < Constants.CARDS_IN_DECK; pick++) 60 | { 61 | String[] choice = picks[pick].trim().split("\\s+"); 62 | for (int i=0; i < 3; i++) 63 | { 64 | predefinedDraftIds[pick][i] = Integer.parseInt(choice[i].trim()); 65 | } 66 | } 67 | } 68 | 69 | // update params values 70 | // we can't update predefinedDraftIds if there were not set by the user... 71 | params.setProperty("draftChoicesSeed", Long.toString(draftChoicesSeed)); 72 | params.setProperty("shufflePlayer0Seed", Long.toString(shufflePlayer0Seed)); 73 | params.setProperty("shufflePlayer1Seed", Long.toString(shufflePlayer1Seed)); 74 | 75 | // set RNG's 76 | draftChoicesRNG = new Random(draftChoicesSeed); 77 | shufflePlayer0RNG = new Random(shufflePlayer0Seed); 78 | shufflePlayer1RNG = new Random(shufflePlayer1Seed); 79 | 80 | //System.out.println(toString()); 81 | } 82 | 83 | @Override 84 | public String toString() 85 | { 86 | StringBuilder sb = new StringBuilder(); 87 | sb.append("draftChoicesSeed").append("=").append(params.getProperty("draftChoicesSeed")).append("\n"); 88 | sb.append("shufflePlayer0Seed").append("=").append(params.getProperty("shufflePlayer0Seed")).append("\n"); 89 | sb.append("shufflePlayer1Seed").append("=").append(params.getProperty("shufflePlayer1Seed")).append("\n"); 90 | //sb.append("predefinedDraftIds").append("=").append(params.getProperty("predefinedDraftIds")).append("\n"); 91 | return sb.toString(); 92 | } 93 | // todo toString? 94 | 95 | private boolean isNumber(String str) 96 | { 97 | try { 98 | Long.parseLong(str); 99 | return true; 100 | } catch (NumberFormatException nfe) {} 101 | return false; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/ui/CardUI.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.ui; 2 | 3 | import java.util.HashMap; 4 | 5 | import com.codingame.game.engine.ActionResult; 6 | import com.codingame.game.engine.Card; 7 | import com.codingame.game.engine.CreatureOnBoard; 8 | import com.codingame.gameengine.module.entities.Curve; 9 | import com.codingame.gameengine.module.entities.GraphicEntityModule; 10 | import com.codingame.gameengine.module.entities.Group; 11 | import com.codingame.gameengine.module.entities.Sprite; 12 | import com.codingame.gameengine.module.entities.Text; 13 | import com.codingame.view.tooltip.TooltipModule; 14 | 15 | public class CardUI { 16 | private TooltipModule tooltipModule; 17 | private GraphicEntityModule graphicEntityModule; 18 | 19 | private Group group; 20 | private Sprite background; 21 | private Sprite overlay; 22 | private Sprite ward; 23 | private Sprite lethal; 24 | private Sprite shadow; 25 | private Sprite impact, heal; 26 | private Sprite[] keywords = new Sprite[6]; 27 | private Text attack; 28 | private Text cost; 29 | private Text defense; 30 | private Text[] extras = new Text[3]; 31 | private Text damageFloat, healFloat; 32 | private Sprite[] extraIcons = new Sprite[3]; 33 | 34 | public CardUI(GraphicEntityModule graphicEntityModule, TooltipModule tooltipModule) { 35 | this.graphicEntityModule = graphicEntityModule; 36 | this.tooltipModule = tooltipModule; 37 | 38 | attack = graphicEntityModule.createText("0") 39 | .setAnchor(0.5) 40 | .setFillColor(0xffffff) 41 | .setFontSize(35) 42 | .setStrokeColor(0x000000) 43 | .setStrokeThickness(4.0) 44 | .setX(45 * 2 * 260 / 740) 45 | .setY(210 * 2 * 260 / 740) 46 | .setZIndex(2); 47 | 48 | background = graphicEntityModule.createSprite() 49 | .setBaseWidth(ConstantsUI.CARD_DIM.x) 50 | .setBaseHeight(ConstantsUI.CARD_DIM.y) 51 | .setZIndex(1); 52 | 53 | cost = graphicEntityModule.createText("0") 54 | .setAnchor(0.5) 55 | .setFillColor(0xffffff) 56 | .setFontSize(35) 57 | .setStrokeColor(0x000000) 58 | .setStrokeThickness(4.0) 59 | .setX(150 * 2 * 260 / 740) 60 | .setY(220 * 2 * 260 / 740) 61 | .setZIndex(2); 62 | 63 | defense = graphicEntityModule.createText("0") 64 | .setAnchor(0.5) 65 | .setFillColor(0xffffff) 66 | .setFontSize(35) 67 | .setStrokeColor(0x000000) 68 | .setStrokeThickness(4.0) 69 | .setX(255 * 2 * 260 / 740) 70 | .setY(210 * 2 * 260 / 740) 71 | .setZIndex(2); 72 | 73 | overlay = graphicEntityModule.createSprite() 74 | .setAlpha(1) 75 | .setBaseHeight(ConstantsUI.CARD_DIM.y) 76 | .setBaseWidth(ConstantsUI.CARD_DIM.x) 77 | .setImage("basic_overlay.png") 78 | .setZIndex(1); 79 | 80 | ward = graphicEntityModule.createSprite() 81 | .setAlpha(0) 82 | .setBaseHeight(ConstantsUI.CARD_DIM.y) 83 | .setBaseWidth(ConstantsUI.CARD_DIM.x) 84 | .setImage("ward.png") 85 | .setZIndex(3); 86 | 87 | lethal = graphicEntityModule.createSprite() 88 | .setAlpha(0) 89 | .setBaseWidth((int) (ConstantsUI.CARD_DIM.x * (28f/300))) 90 | .setBaseHeight((int) (ConstantsUI.CARD_DIM.y * (66f/370))) 91 | .setImage("lethal.png") 92 | .setZIndex(4) 93 | .setX((int) (ConstantsUI.CARD_DIM.x * (3f/300))) 94 | .setY((int) (ConstantsUI.CARD_DIM.y * (170f/370))); 95 | 96 | 97 | shadow = graphicEntityModule.createSprite() 98 | .setAlpha(0) 99 | .setAnchor(-0.045) // (-0.035) 100 | .setBaseHeight(ConstantsUI.CARD_DIM.y) 101 | .setBaseWidth(ConstantsUI.CARD_DIM.x) 102 | .setImage("shadow.png") 103 | //.setTint(0x808080) 104 | .setZIndex(0); 105 | 106 | impact = graphicEntityModule.createSprite() 107 | .setAlpha(0) 108 | .setAnchor(.5) 109 | .setImage("impact.png") 110 | .setZIndex(1); 111 | 112 | heal = graphicEntityModule.createSprite() 113 | .setAlpha(0) 114 | .setAnchor(.5) 115 | .setImage("heal.png") 116 | .setZIndex(1); 117 | 118 | damageFloat = graphicEntityModule.createText("") 119 | .setAnchor(0.5) 120 | .setFillColor(0xffffff) 121 | .setFontSize(36) 122 | .setStrokeColor(0x000000) 123 | .setStrokeThickness(2.0) 124 | .setZIndex(2); 125 | 126 | healFloat = graphicEntityModule.createText("") 127 | .setAnchor(0.5) 128 | .setFillColor(0xffffff) 129 | .setFontSize(36) 130 | .setStrokeColor(0x000000) 131 | .setStrokeThickness(2.0) 132 | .setZIndex(2); 133 | 134 | Group damageGroup = graphicEntityModule.createGroup(impact, damageFloat) 135 | .setZIndex(6) 136 | .setX(ConstantsUI.CARD_DIM.x / 2) 137 | .setY(80); 138 | Group healGroup = graphicEntityModule.createGroup(heal, healFloat) 139 | .setZIndex(5) 140 | .setX(ConstantsUI.CARD_DIM.x / 2) 141 | .setY(80); 142 | 143 | group = graphicEntityModule.createGroup(shadow, background, overlay, ward, lethal, attack, cost, defense, healGroup, damageGroup) 144 | .setScale(1.0); 145 | 146 | extraIcons = new Sprite[3]; 147 | for (int index = 0; index < 3; ++index) { 148 | group.add(extraIcons[index] = graphicEntityModule.createSprite() 149 | .setAnchorY(0.5) 150 | .setScale(0.63) 151 | .setX(ConstantsUI.CARD_EXTRAS[index].x + 15) 152 | .setY(ConstantsUI.CARD_EXTRAS[index].y) 153 | .setZIndex(1)); 154 | } 155 | extraIcons[0].setImage("heart_icon.png"); 156 | extraIcons[1].setImage("heart_icon_rev.png"); 157 | extraIcons[2].setImage("card_stat.png"); 158 | 159 | for (int index = 0; index < 3; ++index) 160 | { 161 | group.add(extras[index] = graphicEntityModule.createText("-") 162 | .setAnchor(0.5) 163 | .setFillColor(0xffffff) 164 | .setFontSize(25) 165 | .setStrokeColor(0x000000) 166 | .setStrokeThickness(2.0) 167 | .setX(ConstantsUI.CARD_EXTRAS[index].x) 168 | .setY(ConstantsUI.CARD_EXTRAS[index].y) 169 | .setZIndex(2) 170 | ); 171 | } 172 | 173 | for (int index = 0; index < 6; ++index) 174 | { 175 | group.add(keywords[index] = graphicEntityModule.createSprite() 176 | .setAnchor(0.5) 177 | .setImage(ConstantsUI.CARD_KEYWORDS_IMAGES[index]) 178 | .setScale(0.6) 179 | .setX(ConstantsUI.CARD_KEYWORDS[index].x) 180 | .setY(ConstantsUI.CARD_KEYWORDS[index].y) 181 | .setZIndex(2) 182 | ); 183 | } 184 | } 185 | 186 | public int getX() { 187 | return group.getX(); 188 | } 189 | 190 | public int getY() { 191 | return group.getY(); 192 | } 193 | 194 | public double getScaleX() { 195 | return group.getScaleX(); 196 | } 197 | 198 | public double getScaleY() { 199 | return group.getScaleY(); 200 | } 201 | 202 | public CardUI setScaleX(double scale) { 203 | group.setScaleX(scale); 204 | return this; 205 | } 206 | 207 | public CardUI setScaleY(double scale) { 208 | group.setScaleY(scale); 209 | return this; 210 | } 211 | 212 | public CardUI setScale(double scale) { 213 | return setScaleX(scale).setScaleY(scale); 214 | } 215 | 216 | public int getWidth() { 217 | return (int) (getScaleX() * ConstantsUI.CARD_DIM.x); 218 | } 219 | 220 | public int getHeight() { 221 | return (int) (getScaleY() * ConstantsUI.CARD_DIM.y); 222 | } 223 | 224 | public CardUI move(int x, int y, Card card) { 225 | return move(x, y, card, new CreatureOnBoard(card), false); 226 | } 227 | 228 | public CardUI move(int x, int y, Card base, CreatureOnBoard card, boolean isOnBoard) { 229 | setVisible(true); 230 | 231 | group.setX(x).setY(y); 232 | 233 | attack 234 | .setText(formatAttDef(card.attack, base.type)) 235 | .setFillColor(colorAttDef(base.attack, card.attack), Curve.NONE); 236 | cost 237 | .setText(Integer.toString(base.cost)); 238 | defense 239 | .setText(formatAttDef(card.defense, base.type)) 240 | .setFillColor(colorAttDef(base.defense, card.defense), Curve.NONE); 241 | 242 | attack.setVisible(base.type == Card.Type.CREATURE || card.attack != 0); 243 | defense.setVisible(base.type == Card.Type.CREATURE || card.defense != 0); 244 | 245 | background.setImage("atlas-" + (card.baseId - 1)); 246 | overlay.setImage(isOnBoard && card.keywords.hasGuard ? "guard_overlay.png" : "basic_overlay.png"); 247 | ward.setAlpha(isOnBoard && card.keywords.hasWard ? 1 : 0); 248 | lethal.setAlpha(isOnBoard && card.keywords.hasLethal ? 1 : 0); 249 | shadow.setAlpha(isOnBoard && card.canAttack ? 1 : 0); 250 | 251 | int[] extraValues = new int[] { 252 | base.myHealthChange, 253 | base.oppHealthChange, 254 | base.cardDraw 255 | }; 256 | 257 | for (int index = 0; index < 3; ++index) { 258 | int value = extraValues[index]; 259 | extras[index].setText(formatExtra(value)); 260 | if (value == 0) { 261 | extras[index].setAlpha(0.1); 262 | extraIcons[index].setAlpha(0.1); 263 | } else { 264 | extras[index].setAlpha(1); 265 | extraIcons[index].setAlpha(1); 266 | } 267 | } 268 | 269 | keywords[0].setAlpha(card.keywords.hasBreakthrough ? 1 : 0.1); 270 | keywords[1].setAlpha(card.keywords.hasCharge ? 1 : 0.1); 271 | keywords[2].setAlpha(card.keywords.hasDrain ? 1 : 0.1); 272 | keywords[3].setAlpha(card.keywords.hasGuard ? 1 : 0.1); 273 | keywords[4].setAlpha(card.keywords.hasLethal ? 1 : 0.1); 274 | keywords[5].setAlpha(card.keywords.hasWard ? 1 : 0.1); 275 | 276 | this.tooltipModule.registerEntity(group, new HashMap<>()); 277 | this.tooltipModule.updateExtraTooltipText(group, card.toTooltipText()); 278 | 279 | return this; 280 | } 281 | 282 | private int colorAttDef(int baseValue, int cardValue) { 283 | if (baseValue < cardValue) { 284 | return 0x00B16A; // Green 285 | } else if (baseValue > cardValue) { 286 | return 0xF22613; // Red 287 | } else { 288 | return 0xFFFFFF; // White 289 | } 290 | } 291 | 292 | private String formatAttDef(int val, Card.Type type) { 293 | String str = Integer.toString(val); 294 | if (val < 0 || type == Card.Type.CREATURE) 295 | return str; 296 | return "+" + str; 297 | } 298 | 299 | private String formatExtra(int val) { 300 | if (val > 0) return "+" + val; 301 | if (val < 0) return "" + val; 302 | return "-"; 303 | } 304 | 305 | public CardUI action() { 306 | impact.setAlpha(0, Curve.IMMEDIATE); 307 | heal.setAlpha(0, Curve.IMMEDIATE); 308 | 309 | damageFloat.setText(""); 310 | healFloat.setText(""); 311 | graphicEntityModule.commitEntityState(0, damageFloat, impact, healFloat, heal); 312 | 313 | return this; 314 | } 315 | 316 | public CardUI action(ActionResult result, int id) { 317 | int attack = result.attacker != null && result.attacker.id == id 318 | ? result.attackerAttackChange 319 | : result.defender != null && result.defender.id == id 320 | ? result.defenderAttackChange 321 | : 0; 322 | int defense = result.attacker != null && result.attacker.id == id 323 | ? result.attackerDefenseChange 324 | : result.defender != null && result.defender.id == id 325 | ? result.defenderDefenseChange 326 | : 0; 327 | 328 | 329 | if (defense < 0) { 330 | damageFloat.setText(Integer.toString(defense)); 331 | impact.setAlpha(1, Curve.NONE); 332 | graphicEntityModule.commitEntityState(0.5, damageFloat, impact); 333 | } else if (defense > 0) { 334 | healFloat.setText(Integer.toString(defense)); 335 | heal.setAlpha(1, Curve.NONE); 336 | graphicEntityModule.commitEntityState(0.5, healFloat, heal); 337 | } 338 | 339 | return this; 340 | } 341 | 342 | public CardUI lift(int elevation) { 343 | group.setZIndex(elevation); 344 | return this; 345 | } 346 | 347 | public CardUI lift() { 348 | return lift(5); 349 | } 350 | 351 | public CardUI ground() { 352 | return lift(0); 353 | } 354 | 355 | public CardUI commitGroup(double state) { 356 | graphicEntityModule.commitEntityState( 357 | state, 358 | group 359 | ); 360 | 361 | return this; 362 | } 363 | 364 | public CardUI commit(double state) { 365 | graphicEntityModule.commitEntityState( 366 | state, 367 | attack, 368 | background, 369 | cost, 370 | defense, 371 | extras[0], 372 | extras[1], 373 | extras[2], 374 | extraIcons[0], 375 | extraIcons[1], 376 | extraIcons[2], 377 | group, 378 | overlay, 379 | ward, 380 | lethal, 381 | keywords[0], 382 | keywords[1], 383 | keywords[2], 384 | keywords[3], 385 | keywords[4], 386 | keywords[5], 387 | shadow 388 | ); 389 | 390 | return this; 391 | } 392 | 393 | public CardUI setVisible(boolean visible) { 394 | group.setAlpha(visible ? 1 : 0); 395 | if (!visible) 396 | action(); 397 | return this; 398 | } 399 | 400 | public CardUI setExtrasVisibility(boolean visible) { 401 | for (Text text : extras) 402 | text.setAlpha(visible ? 1 : 0); 403 | for (Sprite icon : extraIcons) 404 | icon.setAlpha(visible ? 1 : 0); 405 | return this; 406 | } 407 | 408 | public CardUI zoom(int x, int y, double scale) { 409 | group.setX(x).setY(y).setScale(scale); 410 | return this; 411 | } 412 | 413 | public CardUI setVisibility(double alpha) { 414 | group.setAlpha(alpha); 415 | return this; 416 | } 417 | 418 | public CardUI touch() { 419 | double alpha = group.getAlpha(); 420 | group.setAlpha(alpha + 0.0001 * (alpha == 1 ? -1 : 1)); 421 | return this; 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/ui/ConstantsUI.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.ui; 2 | 3 | public class ConstantsUI { 4 | public static final Vector2D SCREEN_DIM = new Vector2D(1920, 1080); 5 | public static final Vector2D CARD_DIM = new Vector2D(210, 260); 6 | 7 | public static final int CARD_BOARD_SPACE = 50; 8 | public static final int CARD_HAND_SPACE = 20; 9 | 10 | public static final Vector2D[] CARD_KEYWORDS = { 11 | new Vector2D(30, 205), 12 | new Vector2D(60, 205), 13 | new Vector2D(90, 205), 14 | new Vector2D(120, 205), 15 | new Vector2D(150, 205), 16 | new Vector2D(180, 205) 17 | }; 18 | 19 | public static final String[] CARD_KEYWORDS_IMAGES = { 20 | "B.png", 21 | "C.png", 22 | "D.png", 23 | "G.png", 24 | "L.png", 25 | "W.png" 26 | }; 27 | 28 | public static final Vector2D[] CARD_EXTRAS = { 29 | new Vector2D(30, 237), 30 | new Vector2D(95, 237), 31 | new Vector2D(155, 237) 32 | }; 33 | 34 | public static final Vector2D BOARD = new Vector2D(1210, SCREEN_DIM.y / 2); 35 | public static final Vector2D BOARD_DIM = new Vector2D(1435, 644); 36 | 37 | public static final Vector2D PLAYER_OFFSET = new Vector2D(0, 610); 38 | 39 | public static final Vector2D PLAYER_AVATAR = new Vector2D(229, 112); 40 | public static final Vector2D PLAYER_AVATAR_DIM = new Vector2D(137, 137); 41 | public static final Vector2D PLAYER_DRAW_TXT = new Vector2D(378, 343); // new Vector2D(378, 369); 42 | public static final Vector2D PLAYER_DECK_POS = new Vector2D(326, 307); 43 | public static final int PLAYER_DECK_OFFSET = 606; 44 | public static final Vector2D PLAYER_DECK_TXT = new Vector2D(378, 395); 45 | public static final Vector2D PLAYER_DECK_DIM = new Vector2D(100, 131); 46 | public static final Vector2D PLAYER_FRAME_DIM = new Vector2D(183, 183); 47 | public static final Vector2D PLAYER_HEALTH_TXT = new Vector2D(95, 370); 48 | public static final Vector2D PLAYER_MANA_TXT = new Vector2D(245, 370); 49 | public static final Vector2D PLAYER_NICK_TXT = new Vector2D(229, 250); 50 | 51 | public static final Vector2D[] PLAYER_RUNES = { 52 | new Vector2D(95, 434), 53 | new Vector2D(144, 415), 54 | new Vector2D(157, 367), 55 | new Vector2D(144, 322), 56 | new Vector2D(95, 304) 57 | }; 58 | 59 | public static final Vector2D[] PLAYER_BUBBLES_POSITION = { 60 | new Vector2D(229, 539), 61 | new Vector2D(229, 531) 62 | }; 63 | 64 | public static final Vector2D PLAYER_LONG_BUBBLES_TEXT_POSITION = new Vector2D(0, 27); 65 | public static final Vector2D PLAYER_BUBBLES_TEXT_POSITION = new Vector2D(0, 58); 66 | 67 | public static final double CARD_BOARD_SCALE = 1.0; 68 | public static final double LIFTED_CARD_BOARD_SCALE = 1.2; 69 | public static final double CARD_DRAFT_SCALE = 1.3; 70 | public static final double CARD_DECK_SCALE = (double) PLAYER_DECK_DIM.y / (double) CARD_DIM.y; 71 | public static final double CARD_HAND_SCALE = 0.77; 72 | 73 | public static final Vector2D CARD_BOARD_DIM = Vector2D.mult(CARD_DIM, CARD_BOARD_SCALE); 74 | public static final Vector2D CARD_DRAFT_DIM = Vector2D.mult(CARD_DIM, CARD_DRAFT_SCALE); 75 | public static final Vector2D CARD_DECK_DIM = Vector2D.mult(CARD_DIM, CARD_BOARD_SCALE); 76 | public static final Vector2D CARD_HAND_DIM = Vector2D.mult(CARD_DIM, CARD_HAND_SCALE); 77 | public static final Vector2D ZOOM_OFFSET = new Vector2D( 78 | -(int) (ConstantsUI.CARD_BOARD_DIM.x * (ConstantsUI.LIFTED_CARD_BOARD_SCALE - ConstantsUI.CARD_BOARD_SCALE) / 2), 79 | -(int) (ConstantsUI.CARD_BOARD_DIM.y * (ConstantsUI.LIFTED_CARD_BOARD_SCALE - ConstantsUI.CARD_BOARD_SCALE) / 2) 80 | ); 81 | 82 | public static final int MC_GRAPH_LOWY = SCREEN_DIM.y - 40; 83 | public static final int MC_COST_FONTSIZE = 30; 84 | public static final int MC_QUANTITY_FONTSIZE = 24; 85 | public static final int MC_COST_WIDTH = 100; 86 | public static final int MC_GRAPH_WIDTH = 80; 87 | public static final int MC_COST_X = 470 + (SCREEN_DIM.x-470 - 7*MC_COST_WIDTH)/2; 88 | public static final int MC_GRAPH_STEP = 25; 89 | public static final int MC_GRAPH_MAXSIZE = 175; 90 | public static final int MC_GRAPH_ZEROSIZE = 2; 91 | public static final int[] MC_PLAYERS_OFFSET = {0, -860}; 92 | public static final int MC_TYINFO_X = 470 + 160; 93 | public static final int MC_TYINFO_X_QUANTITY_OFFS = 130; 94 | public static final int MC_TYINFO_Y = SCREEN_DIM.y - 220/2; 95 | 96 | public static final int SHOWDRAFT_LOAD_INIT = 47; // max is 47 97 | public static final int SHOWDRAFT_LOAD_STEP = 14; // max is 14 98 | public static final double SHOWDRAFT_ALPHAZERO = 0.4; 99 | public static final int[] SHOWDRAFT_SIZECHOICE = {90 , 60 , 30 , 15, }; 100 | public static final int[] SHOWDRAFT_ROWSIZE = {15 , 12 , 10 , 5 }; 101 | public static final double[] SHOWDRAFT_SCALE = {0.50, 0.65, 0.78, 1.10}; 102 | public static final int SHOWDRAFT_SPACE = 20; 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/ui/PlayerUI.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.ui; 2 | 3 | import com.codingame.game.Player; 4 | import com.codingame.game.engine.ActionResult; 5 | import com.codingame.game.engine.Constants; 6 | import com.codingame.game.engine.Gamer; 7 | import com.codingame.gameengine.module.entities.Curve; 8 | import com.codingame.gameengine.module.entities.GraphicEntityModule; 9 | import com.codingame.gameengine.module.entities.Group; 10 | import com.codingame.gameengine.module.entities.Sprite; 11 | import com.codingame.gameengine.module.entities.Text; 12 | 13 | public class PlayerUI { 14 | private static final int[] CHARS_TO_CROP = new int[] {22, 45}; 15 | private GraphicEntityModule graphicEntityModule; 16 | private Player player; 17 | 18 | private Sprite avatar; 19 | private Sprite[] frame = new Sprite[2]; 20 | private Sprite[] runes = new Sprite[5]; 21 | private Text draw; 22 | private Text deck; 23 | private Text health; 24 | private Text mana; 25 | private Text nick; 26 | 27 | private Text damageFloat, healFloat; 28 | private Sprite impact, heal; 29 | private Group bubble; 30 | private Text bubbleText; 31 | private Sprite bubbleSprite; 32 | 33 | private Vector2D bubblePosition; 34 | 35 | public PlayerUI(GraphicEntityModule graphicEntityModule, Player player) 36 | { 37 | this.graphicEntityModule = graphicEntityModule; 38 | this.player = player; 39 | 40 | int playerIndex = player.getIndex(); 41 | 42 | Vector2D offset = Vector2D.mult(ConstantsUI.PLAYER_OFFSET, 1 - playerIndex); 43 | bubblePosition = ConstantsUI.PLAYER_BUBBLES_POSITION[1 - playerIndex]; 44 | 45 | avatar = graphicEntityModule.createSprite() 46 | .setAnchor(0.5) 47 | .setBaseHeight(ConstantsUI.PLAYER_AVATAR_DIM.y) 48 | .setBaseWidth(ConstantsUI.PLAYER_AVATAR_DIM.x) 49 | .setImage(player.getAvatarToken()) 50 | .setX(Vector2D.add(ConstantsUI.PLAYER_AVATAR, offset).x) 51 | .setY(Vector2D.add(ConstantsUI.PLAYER_AVATAR, offset).y); 52 | 53 | draw = graphicEntityModule.createText("") 54 | .setAnchor(0.5) 55 | .setFillColor(0xffffff) 56 | .setFontSize(40) 57 | .setStrokeColor(0x000000) 58 | .setStrokeThickness(4.0) 59 | .setX(Vector2D.add(ConstantsUI.PLAYER_DRAW_TXT, offset).x) 60 | .setY(Vector2D.add(ConstantsUI.PLAYER_DRAW_TXT, offset).y); 61 | 62 | deck = graphicEntityModule.createText("") 63 | .setAnchor(0.5) 64 | .setFillColor(0xffffff) 65 | .setFontSize(40) 66 | .setStrokeColor(0x000000) 67 | .setStrokeThickness(4.0) 68 | .setX(Vector2D.add(ConstantsUI.PLAYER_DECK_TXT, offset).x) 69 | .setY(Vector2D.add(ConstantsUI.PLAYER_DECK_TXT, offset).y); 70 | 71 | frame[0] = graphicEntityModule.createSprite() 72 | .setAlpha(0) 73 | .setAnchor(0.5) 74 | .setBaseHeight(ConstantsUI.PLAYER_FRAME_DIM.y) 75 | .setBaseWidth(ConstantsUI.PLAYER_FRAME_DIM.x) 76 | .setImage("playerframe-active.png") 77 | .setX(Vector2D.add(ConstantsUI.PLAYER_AVATAR, offset).x) 78 | .setY(Vector2D.add(ConstantsUI.PLAYER_AVATAR, offset).y); 79 | 80 | frame[1] = graphicEntityModule.createSprite() 81 | .setAnchor(0.5) 82 | .setBaseHeight(ConstantsUI.PLAYER_FRAME_DIM.y) 83 | .setBaseWidth(ConstantsUI.PLAYER_FRAME_DIM.x) 84 | .setImage("playerframe-inactive.png") 85 | .setX(Vector2D.add(ConstantsUI.PLAYER_AVATAR, offset).x) 86 | .setY(Vector2D.add(ConstantsUI.PLAYER_AVATAR, offset).y); 87 | 88 | health = graphicEntityModule.createText("") 89 | .setAnchor(0.5) 90 | .setFillColor(0xffffff) 91 | .setFontSize(40) 92 | .setStrokeColor(0x000000) 93 | .setStrokeThickness(4.0) 94 | .setX(Vector2D.add(ConstantsUI.PLAYER_HEALTH_TXT, offset).x) 95 | .setY(Vector2D.add(ConstantsUI.PLAYER_HEALTH_TXT, offset).y); 96 | 97 | damageFloat = graphicEntityModule.createText("") 98 | .setAnchor(0.5) 99 | .setFillColor(0xffffff) 100 | .setFontSize(36) 101 | .setStrokeColor(0x000000) 102 | .setStrokeThickness(4.0) 103 | .setZIndex(2); 104 | 105 | impact = graphicEntityModule.createSprite() 106 | .setAlpha(0) 107 | .setAnchor(.5) 108 | .setImage("impact.png") 109 | .setZIndex(1); 110 | 111 | healFloat = graphicEntityModule.createText("") 112 | .setAnchor(0.5) 113 | .setFillColor(0xffffff) 114 | .setFontSize(36) 115 | .setStrokeColor(0x000000) 116 | .setStrokeThickness(4.0) 117 | .setZIndex(2); 118 | 119 | heal = graphicEntityModule.createSprite() 120 | .setAlpha(0) 121 | .setAnchor(.5) 122 | .setImage("heal.png") 123 | .setZIndex(1); 124 | 125 | bubbleText = graphicEntityModule.createText("") 126 | .setFillColor(player.getColorToken()) 127 | .setFontSize(30) 128 | .setZIndex(2) 129 | .setAnchor(.5) 130 | .setX(ConstantsUI.PLAYER_LONG_BUBBLES_TEXT_POSITION.x) 131 | .setY((playerIndex == 0 ? 1 : -1) * ConstantsUI.PLAYER_LONG_BUBBLES_TEXT_POSITION.y) 132 | .setFontFamily("Monospace"); 133 | 134 | bubbleSprite = graphicEntityModule.createSprite() 135 | .setAlpha(0) 136 | .setZIndex(1) 137 | .setScale(playerIndex == 0 ? 1 : -1) 138 | .setAnchorX(0.5); 139 | 140 | bubble = graphicEntityModule.createGroup( 141 | bubbleSprite, 142 | bubbleText 143 | ); 144 | 145 | graphicEntityModule.createGroup(impact, damageFloat) 146 | .setZIndex(1) 147 | .setX(avatar.getX()) 148 | .setY(avatar.getY()); 149 | 150 | graphicEntityModule.createGroup(heal, healFloat) 151 | .setZIndex(1) 152 | .setX(avatar.getX()) 153 | .setY(avatar.getY()); 154 | 155 | mana = graphicEntityModule.createText("") 156 | .setAnchor(0.5) 157 | .setFillColor(0xffffff) 158 | .setFontSize(40) 159 | .setStrokeColor(0x000000) 160 | .setStrokeThickness(4.0) 161 | .setX(Vector2D.add(ConstantsUI.PLAYER_MANA_TXT, offset).x) 162 | .setY(Vector2D.add(ConstantsUI.PLAYER_MANA_TXT, offset).y); 163 | 164 | nick = graphicEntityModule.createText(player.getNicknameToken()) 165 | .setAnchor(0.5) 166 | .setFillColor(player.getColorToken()) 167 | .setFontSize(60) 168 | .setStrokeColor(0x000000) 169 | .setStrokeThickness(4.0) 170 | .setX(Vector2D.add(ConstantsUI.PLAYER_NICK_TXT, offset).x) 171 | .setY(Vector2D.add(ConstantsUI.PLAYER_NICK_TXT, offset).y); 172 | 173 | 174 | for (int index = 0; index < 5; ++index) 175 | runes[index] = graphicEntityModule 176 | .createSprite() 177 | .setAlpha(0) 178 | .setAnchor(0.5) 179 | .setImage("rune.png") 180 | .setX(Vector2D.add(ConstantsUI.PLAYER_RUNES[index], offset).x) 181 | .setY(Vector2D.add(ConstantsUI.PLAYER_RUNES[index], offset).y); 182 | } 183 | 184 | public void attacker(ActionResult result) 185 | { 186 | handleHealthChange(result.attackerHealthChange); 187 | } 188 | 189 | public void defender(ActionResult result) 190 | { 191 | handleHealthChange(result.defenderHealthChange); 192 | } 193 | 194 | public PlayerUI updateStats(Gamer gamer) 195 | { 196 | draw.setText("+" + Integer.toString(gamer.nextTurnDraw)); 197 | if (gamer.hand.size()+gamer.nextTurnDraw > Constants.MAX_CARDS_IN_HAND) // show overdraw 198 | draw.setFillColor(0xff5500); 199 | else 200 | draw.setFillColor(0xffffff); 201 | 202 | deck.setText(Integer.toString(gamer.deck.size())); // show empty deck approaching 203 | if (gamer.deck.size() == 0) 204 | deck.setFillColor(0xff0000); 205 | else if (gamer.deck.size() <= 5) 206 | deck.setFillColor(0xff5500); 207 | 208 | damageFloat.setText(""); 209 | healFloat.setText(""); 210 | hideImpact(); 211 | hideHeal(); 212 | health.setText(Integer.toString(gamer.health)); 213 | mana.setText(Integer.toString(gamer.currentMana) + "/" + Integer.toString(gamer.maxMana)) 214 | .setFillColor(gamer.bonusManaTurns > 0 ? 0x009000 : 0xffffff); 215 | 216 | for (int index = 0; index < 5; ++index) 217 | runes[index].setAlpha(gamer.runes.size() > index ? 1 : 0); 218 | 219 | return this; 220 | } 221 | 222 | public PlayerUI setActive(boolean active) 223 | { 224 | frame[0].setAlpha(active ? 1 : 0, Curve.IMMEDIATE); 225 | frame[1].setAlpha(active ? 0 : 1, Curve.IMMEDIATE); 226 | return this; 227 | } 228 | 229 | private void handleHealthChange(int healthChange) { 230 | if (healthChange > 0) { 231 | healFloat.setText(Integer.toString(healthChange)); 232 | displayHeal(); 233 | hideImpact(); 234 | } else if (healthChange < 0) { 235 | damageFloat.setText(Integer.toString(healthChange)); 236 | displayImpact(); 237 | hideHeal(); 238 | } else { 239 | hideImpact(); 240 | hideHeal(); 241 | } 242 | } 243 | 244 | private void displayImpact() { 245 | impact.setAlpha(1, Curve.NONE); 246 | graphicEntityModule.commitEntityState(0.5, damageFloat, impact); 247 | } 248 | private void displayHeal() { 249 | heal.setAlpha(1, Curve.NONE); 250 | graphicEntityModule.commitEntityState(0.5, healFloat, heal); 251 | } 252 | 253 | private void hideImpact() { 254 | impact.setAlpha(0, Curve.IMMEDIATE); 255 | damageFloat.setText(""); 256 | graphicEntityModule.commitEntityState(0, damageFloat, impact); 257 | } 258 | private void hideHeal() { 259 | heal.setAlpha(0, Curve.IMMEDIATE); 260 | healFloat.setText(""); 261 | graphicEntityModule.commitEntityState(0, healFloat, heal); 262 | } 263 | 264 | public void talk(String text, boolean draftPhase) { 265 | int playerIndex = player.getIndex(); 266 | 267 | bubbleSprite.setAlpha(1, Curve.NONE); 268 | 269 | if (draftPhase) { 270 | bubble.setX(bubblePosition.x, Curve.IMMEDIATE).setY(bubblePosition.y, Curve.IMMEDIATE); 271 | bubbleSprite.setImage("bubble_long.png"); 272 | if (text.length() > 22) { 273 | bubbleText.setFontSize(26); 274 | if (text.length() > 25) { 275 | text = text.substring(0, 23) + "..."; 276 | } 277 | } else { 278 | bubbleText.setFontSize(30); 279 | } 280 | } else { 281 | bubbleSprite.setImage("bubble.png"); 282 | bubble.setX(bubblePosition.x, Curve.IMMEDIATE).setY(bubblePosition.y + (playerIndex == 0 ? -1 : 1) * 60, Curve.IMMEDIATE); 283 | bubbleText.setFontSize(30) 284 | .setY((playerIndex == 0 ? 1 : -1) * ConstantsUI.PLAYER_BUBBLES_TEXT_POSITION.y, Curve.IMMEDIATE); 285 | 286 | for (int i : CHARS_TO_CROP) { 287 | if (text.length() > i) { 288 | text = text.substring(0, i) + "\n" + text.substring(i, text.length()); 289 | } 290 | } 291 | if (text.length() > 67) { 292 | text = text.substring(0, 65) + "..."; 293 | } 294 | } 295 | 296 | bubbleText.setText(text); 297 | graphicEntityModule.commitEntityState(0, bubbleText, bubbleSprite); 298 | } 299 | 300 | public void hideBubble() { 301 | bubbleSprite.setAlpha(0, Curve.NONE); 302 | bubbleText.setText(""); 303 | graphicEntityModule.commitEntityState(0, bubbleText, bubbleSprite); 304 | } 305 | 306 | public Text getNick() { 307 | return nick; 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/ui/RefereeUI.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.ui; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | 9 | import com.codingame.game.Player; 10 | import com.codingame.game.engine.Action; 11 | import com.codingame.game.engine.Card; 12 | import com.codingame.game.engine.Card.Type; 13 | import com.codingame.game.engine.Constants; 14 | import com.codingame.game.engine.CreatureOnBoard; 15 | import com.codingame.game.engine.EngineReferee; 16 | import com.codingame.game.engine.Gamer; 17 | import com.codingame.gameengine.core.MultiplayerGameManager; 18 | import com.codingame.gameengine.module.entities.GraphicEntityModule; 19 | import com.codingame.gameengine.module.entities.Rectangle; 20 | import com.codingame.gameengine.module.entities.Sprite; 21 | import com.codingame.gameengine.module.entities.Text; 22 | import com.codingame.view.FXModule; 23 | import com.codingame.view.tooltip.TooltipModule; 24 | 25 | public class RefereeUI { 26 | private static final double EPSILON = 0.001; 27 | 28 | public EngineReferee engine; 29 | public MultiplayerGameManager gameManager; 30 | public GraphicEntityModule graphicEntityModule; 31 | public FXModule fxModule; 32 | 33 | private TooltipModule tooltipModule; 34 | 35 | private Map cardsPool = new HashMap<>(); 36 | private static PlayerUI[] players = new PlayerUI[2]; 37 | 38 | private Text[] deck = new Text[2]; 39 | 40 | private Text[][] manaCurveCosts = new Text[2][8]; 41 | private Rectangle[][] manaCurve = new Rectangle[2][8]; 42 | private Text[][] manaCurveQuantity = new Text[2][8]; 43 | 44 | private Text[] cardTypesInfo = new Text[2]; 45 | private Text[] cardTypesQuantity = new Text[2]; 46 | 47 | private Sprite newTurnBackground; 48 | private Text newTurn; 49 | private int lastturn = -1; 50 | 51 | int showdraftShownrows = 0; 52 | int showdraftLoaded = 0; 53 | Rectangle showdraftBackground; 54 | private Text[] showdraftQuantity = new Text[90]; 55 | 56 | 57 | public void init() { 58 | tooltipModule = new TooltipModule(gameManager); 59 | gameManager.setFrameDuration(Constants.FRAME_DURATION_SHOWDRAFT); 60 | 61 | graphicEntityModule.createSpriteSheetLoader() 62 | .setName("atlas-") 63 | .setSourceImage("atlas.png") 64 | .setWidth(600 / 4) 65 | .setHeight(740 / 4) 66 | .setOrigRow(0) 67 | .setOrigCol(0) 68 | .setImageCount(160) 69 | .setImagesPerRow(10) 70 | .load(); 71 | 72 | graphicEntityModule.createSprite() 73 | .setAnchor(0) 74 | .setImage("board.jpg") 75 | .setX(0) 76 | .setY(0); 77 | 78 | newTurnBackground = graphicEntityModule.createSprite() 79 | .setAnchor(0) 80 | .setImage("newturn.png") 81 | .setX(78) 82 | .setY(482) 83 | .setAlpha(0); 84 | 85 | for (Player player : gameManager.getPlayers()) 86 | { 87 | players[player.getIndex()] = new PlayerUI(graphicEntityModule, player); 88 | fxModule.registerNickname(players[player.getIndex()].getNick()); 89 | } 90 | } 91 | 92 | public void draft(int turn) 93 | { 94 | gameManager.setFrameDuration(Constants.FRAME_DURATION_DRAFT); 95 | 96 | if (turn == 0) 97 | { 98 | newTurn = graphicEntityModule.createText(""); 99 | deck[0] = graphicEntityModule.createText(""); 100 | deck[1] = graphicEntityModule.createText(""); 101 | initManaCurve(); 102 | } 103 | 104 | for (CardUI card : cardsPool.values()) 105 | card.setVisible(false); 106 | 107 | int draftX = ConstantsUI.BOARD.x - (int) (ConstantsUI.CARD_DIM.x * 1.5 * ConstantsUI.CARD_DRAFT_SCALE) - ConstantsUI.CARD_BOARD_SPACE - 140; // handmade value 108 | int draftY = ConstantsUI.BOARD.y; 109 | int draftCardSpace = (int) ((ConstantsUI.CARD_BOARD_SPACE + ConstantsUI.CARD_DIM.x) * ConstantsUI.CARD_DRAFT_SCALE); 110 | 111 | Card[] pick = engine.draft.draft[turn]; 112 | 113 | for (int index = 0; index < 3; ++index) { 114 | int cardX = draftX + draftCardSpace * index - ConstantsUI.CARD_BOARD_SPACE; 115 | int cardY = draftY - (int) (ConstantsUI.CARD_DIM.y / 2 * ConstantsUI.CARD_DRAFT_SCALE); 116 | 117 | Card card = pick[index]; 118 | getCardFromPool(-(index + 1)) 119 | .setScale(ConstantsUI.CARD_DRAFT_SCALE) 120 | .move(cardX, cardY, card) 121 | .commit(0.0); 122 | 123 | for (Player player : gameManager.getPlayers()) { 124 | int playerIndex = player.getIndex(); 125 | Vector2D offset = Vector2D.mult(ConstantsUI.PLAYER_OFFSET, 1 - playerIndex); 126 | 127 | deck[1 - playerIndex] 128 | .setText(Integer.toString(1 + turn)) 129 | .setAnchor(0.5) 130 | .setFillColor(0xffffff) 131 | .setFontSize(40) 132 | .setStrokeColor(0x000000) 133 | .setStrokeThickness(4.0) 134 | .setX(Vector2D.add(ConstantsUI.PLAYER_DECK_TXT, offset).x) 135 | .setY(Vector2D.add(ConstantsUI.PLAYER_DECK_TXT, offset).y); 136 | 137 | if (engine.draft.chosenCards[playerIndex].get(turn).baseId != pick[index].baseId) 138 | continue; 139 | 140 | if (!engine.draft.text[playerIndex].isEmpty()) { 141 | players[playerIndex].talk(engine.draft.text[playerIndex], true); 142 | } else { 143 | players[playerIndex].hideBubble(); 144 | } 145 | 146 | this.getCardFromPool(-(playerIndex + 4)) 147 | .lift(1) 148 | .setScale(ConstantsUI.CARD_DRAFT_SCALE) 149 | .move(cardX, cardY, card) 150 | .commit(0.0) 151 | .setScale(ConstantsUI.CARD_DECK_SCALE) 152 | .move( 153 | ConstantsUI.PLAYER_DECK_POS.x, 154 | ConstantsUI.PLAYER_DECK_POS.y + ConstantsUI.PLAYER_DECK_OFFSET*(1 - playerIndex), 155 | card 156 | ) 157 | .commit(1.0); 158 | } 159 | } 160 | 161 | newTurnBackground 162 | .setX(ConstantsUI.SCREEN_DIM.x - 314 - 20) 163 | .setY(482) 164 | .setAlpha(1); 165 | 166 | newTurn 167 | .setText("Draft: " + Integer.toString(1 + turn) + "/30") 168 | .setAnchor(0.5) 169 | .setFontSize(50) 170 | .setFillColor(0xffffff) 171 | .setStrokeColor(0x000000) 172 | .setStrokeThickness(4.0) 173 | .setX(ConstantsUI.SCREEN_DIM.x - (314/2) - 20) 174 | .setY(draftY); 175 | 176 | graphicEntityModule.commitEntityState(0, newTurnBackground, newTurn); 177 | drawManaCurve(); 178 | } 179 | 180 | private void initManaCurve() 181 | { 182 | for (int p=0; p<2; p++) 183 | { 184 | cardTypesInfo[p] = graphicEntityModule.createText(" Creatures:\nGreen Items:\n Red Items:\n Blue Items:") 185 | .setAnchor(0.5) 186 | .setFontSize(ConstantsUI.MC_COST_FONTSIZE) 187 | .setFillColor(0xffffff) 188 | .setStrokeColor(0x000000) 189 | .setStrokeThickness(4.0) 190 | .setX(ConstantsUI.MC_TYINFO_X) 191 | .setY( ConstantsUI.MC_PLAYERS_OFFSET[p] + ConstantsUI.MC_TYINFO_Y); 192 | 193 | cardTypesQuantity[p] = graphicEntityModule.createText("0\n0\n0\n0") 194 | .setAnchor(0.5) 195 | .setFontSize(ConstantsUI.MC_COST_FONTSIZE) 196 | .setFillColor(0xffffff) 197 | .setStrokeColor(0x000000) 198 | .setStrokeThickness(4.0) 199 | .setX(ConstantsUI.MC_TYINFO_X + ConstantsUI.MC_TYINFO_X_QUANTITY_OFFS) 200 | .setY(ConstantsUI.MC_PLAYERS_OFFSET[p] + ConstantsUI.MC_TYINFO_Y); 201 | 202 | for (int m = 0; m < 8; m++) 203 | { 204 | manaCurveCosts[p][m] = graphicEntityModule.createText(m < 7 ? Integer.toString(m) : "7+") 205 | .setAnchor(0.5) 206 | .setFontSize(ConstantsUI.MC_COST_FONTSIZE) 207 | .setFillColor(0xffffff) 208 | .setStrokeColor(0x000000) 209 | .setStrokeThickness(4.0) 210 | .setX(ConstantsUI.MC_COST_X + (m * ConstantsUI.MC_COST_WIDTH) + ConstantsUI.MC_COST_WIDTH / 2) 211 | .setY(ConstantsUI.MC_PLAYERS_OFFSET[p] + ConstantsUI.MC_GRAPH_LOWY + (ConstantsUI.SCREEN_DIM.y - ConstantsUI.MC_GRAPH_LOWY) / 2); 212 | 213 | manaCurve[p][m] = graphicEntityModule.createRectangle() 214 | .setFillColor(0x4d79d0) 215 | .setHeight(0) 216 | .setWidth(ConstantsUI.MC_GRAPH_WIDTH) 217 | .setX(ConstantsUI.MC_COST_X + m * ConstantsUI.MC_COST_WIDTH + (ConstantsUI.MC_COST_WIDTH-ConstantsUI.MC_GRAPH_WIDTH)/2) 218 | .setY(ConstantsUI.MC_PLAYERS_OFFSET[p] + ConstantsUI.MC_GRAPH_LOWY); 219 | 220 | manaCurveQuantity[p][m] = graphicEntityModule.createText("") 221 | .setAnchor(0.5) 222 | .setFontSize(ConstantsUI.MC_QUANTITY_FONTSIZE) 223 | .setFillColor(0xffffff) 224 | .setStrokeColor(0x000000) 225 | .setStrokeThickness(4.0) 226 | .setX(ConstantsUI.MC_COST_X + (m * ConstantsUI.MC_COST_WIDTH) + ConstantsUI.MC_COST_WIDTH/2) 227 | .setY(ConstantsUI.MC_PLAYERS_OFFSET[p] + ConstantsUI.MC_GRAPH_LOWY + (ConstantsUI.SCREEN_DIM.y - ConstantsUI.MC_GRAPH_LOWY)/2); 228 | } 229 | } 230 | } 231 | 232 | private void drawManaCurve() 233 | { 234 | for (int p=0; p<2; p++) 235 | { 236 | int[] mc = new int[8]; 237 | int[] ct = new int[4]; 238 | 239 | for (Card c : engine.draft.chosenCards[p]) 240 | { 241 | if (c.cost < 7) mc[c.cost]++; 242 | else mc[7]++; 243 | 244 | switch (c.type) 245 | { 246 | case CREATURE: ct[0]++; break; 247 | case ITEM_GREEN: ct[1]++; break; 248 | case ITEM_RED: ct[2]++; break; 249 | case ITEM_BLUE: ct[3]++; break; 250 | } 251 | } 252 | String[] cts = new String[4]; 253 | for (int i=0; i < 4; i++) cts[i] = ct[i] + (ct[i] < 10 ? " " : ""); 254 | cardTypesQuantity[p].setText(String.join("\n", cts)); 255 | 256 | int maxmc = Arrays.stream(mc).max().getAsInt(); 257 | boolean overflow = maxmc * ConstantsUI.MC_GRAPH_STEP > ConstantsUI.MC_GRAPH_MAXSIZE; 258 | 259 | for (int m = 0; m < 8; m++) 260 | { 261 | int h = mc[m] * ConstantsUI.MC_GRAPH_STEP; 262 | if (overflow) 263 | h = ConstantsUI.MC_GRAPH_MAXSIZE * mc[m] / maxmc; 264 | if (h == 0) 265 | h = ConstantsUI.MC_GRAPH_ZEROSIZE; 266 | 267 | manaCurve[p][m] 268 | .setHeight(h) 269 | .setY(ConstantsUI.MC_PLAYERS_OFFSET[p] + ConstantsUI.MC_GRAPH_LOWY - h); 270 | 271 | int texty = ConstantsUI.MC_GRAPH_LOWY - h + ConstantsUI.MC_QUANTITY_FONTSIZE / 2; // text below 272 | if (h < ConstantsUI.MC_QUANTITY_FONTSIZE) 273 | texty = ConstantsUI.MC_GRAPH_LOWY - h - ConstantsUI.MC_QUANTITY_FONTSIZE / 2; // text above 274 | 275 | manaCurveQuantity[p][m] 276 | .setText(mc[m] > 0 ? Integer.toString(mc[m]) : "") 277 | .setY(ConstantsUI.MC_PLAYERS_OFFSET[p] + texty); 278 | } 279 | } 280 | } 281 | 282 | public void battle(int turn) { 283 | for (int playerIndex = 0; playerIndex < 2; ++playerIndex) { 284 | players[playerIndex].hideBubble(); 285 | } 286 | 287 | newTurn.setAlpha(0); 288 | deck[0].setAlpha(0); 289 | deck[1].setAlpha(0); 290 | 291 | for (int p=0; p<2; p++) 292 | { 293 | cardTypesInfo[p].setAlpha(0); 294 | cardTypesQuantity[p].setAlpha(0); 295 | for (int m = 0; m < 8; m++) 296 | { 297 | manaCurveCosts[p][m].setAlpha(0); 298 | manaCurve[p][m].setAlpha(0); 299 | manaCurveQuantity[p][m].setAlpha(0); 300 | } 301 | } 302 | 303 | if (turn != lastturn) 304 | { 305 | lastturn = turn; 306 | 307 | newTurnBackground 308 | .setX(78) 309 | .setAlpha(1); 310 | 311 | newTurn 312 | .setText("Turn " + Integer.toString(turn/2 - 14)) 313 | .setAnchor(0.5) 314 | .setFontSize(50) 315 | .setFillColor(gameManager.getPlayers().get(turn%2).getColorToken()) 316 | .setStrokeColor(0x000000) 317 | .setStrokeThickness(4.0) 318 | .setX(78+314/2) 319 | .setY(ConstantsUI.BOARD.y) 320 | .setAlpha(1); 321 | } 322 | else 323 | { 324 | newTurnBackground.setAlpha(0); 325 | newTurn.setText(""); 326 | } 327 | 328 | graphicEntityModule.commitEntityState(0, newTurn, newTurnBackground); 329 | 330 | for (CardUI card : cardsPool.values()) 331 | card.setVisible(false); 332 | 333 | 334 | for (Player player : gameManager.getPlayers()) { 335 | int playerIndex = player.getIndex(); 336 | Vector2D offset = Vector2D.mult(ConstantsUI.PLAYER_OFFSET, 1 - playerIndex); 337 | 338 | // Expose an API in the engine? 339 | Gamer gamer = engine.state.players[playerIndex]; 340 | 341 | // Update player. 342 | players[playerIndex] 343 | .setActive(turn % 2 == playerIndex) 344 | .updateStats(gamer); 345 | 346 | // Update board. 347 | ArrayList board = gamer.board; 348 | 349 | int boardX = ConstantsUI.BOARD.x; 350 | int boardY = ConstantsUI.BOARD.y + (int) (ConstantsUI.BOARD_DIM.y * (0.5 - playerIndex) / 2.0); 351 | int bcw = (int) (ConstantsUI.CARD_BOARD_SCALE * ConstantsUI.CARD_DIM.x * (board.size() > 5 ? 0.8 : 1)); 352 | int bch = (int) (ConstantsUI.CARD_BOARD_SCALE * ConstantsUI.CARD_DIM.y); 353 | int bcs = ConstantsUI.CARD_BOARD_SPACE; 354 | int boardCenterX = ((bcw + bcs) * board.size() + (board.size() % 2) * bcs / 2) / 2; 355 | 356 | for (int index = 0; index < board.size(); ++index) { 357 | int cardXOffset = (bcw + bcs) * index; 358 | int cardX = boardX - boardCenterX + cardXOffset; 359 | int cardY = boardY - bch / 2; 360 | 361 | CreatureOnBoard card = board.get(index); 362 | Card base = Constants.CARDSET.values().stream() 363 | .filter(x -> x.baseId == card.baseId) 364 | .findAny() 365 | .get(); 366 | 367 | getCardFromPool(card.id) 368 | .setScale(ConstantsUI.CARD_BOARD_SCALE) 369 | .move(cardX, cardY, base, card, true); 370 | } 371 | 372 | // Update hand. 373 | ArrayList hand = gamer.hand; 374 | 375 | int handX = ConstantsUI.BOARD.x; 376 | int handY = ConstantsUI.BOARD.y + (ConstantsUI.BOARD_DIM.y + ConstantsUI.CARD_HAND_DIM.y + 20) * ((1 - playerIndex) * 2 - 1) / 2; 377 | int hcw = (int) (ConstantsUI.CARD_HAND_SCALE * ConstantsUI.CARD_DIM.x); 378 | int hch = (int) (ConstantsUI.CARD_HAND_SCALE * ConstantsUI.CARD_DIM.y); 379 | int hcs = ConstantsUI.CARD_HAND_SPACE; 380 | int handCenterX = ((hcw + hcs) * hand.size() + (hand.size() % 2) * hcs / 2) / 2; 381 | 382 | for (int index = 0; index < hand.size(); ++index) { 383 | int cardXOffset = (hcw + hcs) * index; 384 | int cardX = handX - handCenterX + cardXOffset; 385 | int cardY = handY - hch / 2; 386 | 387 | Card card = hand.get(index); 388 | CardUI cardUI = getCardFromPool(card.id); 389 | 390 | if (cardUI.getX() == 0) { 391 | cardUI 392 | .lift() 393 | .setScale(ConstantsUI.CARD_DECK_SCALE) 394 | .move( 395 | Vector2D.add(Vector2D.sub(ConstantsUI.PLAYER_DRAW_TXT, Vector2D.div(ConstantsUI.PLAYER_DECK_DIM, 2)), offset).x, 396 | Vector2D.add(Vector2D.sub(ConstantsUI.PLAYER_DRAW_TXT, Vector2D.div(ConstantsUI.PLAYER_DECK_DIM, 2)), offset).y, 397 | card 398 | ) 399 | .commit(0.0); 400 | } 401 | 402 | cardUI 403 | .setScale(ConstantsUI.CARD_HAND_SCALE) 404 | .move(cardX, cardY, card) 405 | .ground(); 406 | } 407 | } 408 | 409 | int playerIndex = turn % 2; 410 | Vector2D offset = Vector2D.mult(ConstantsUI.PLAYER_OFFSET, playerIndex); 411 | 412 | ArrayList actions = engine.state.players[playerIndex].performedActions; 413 | Action action = actions.size() == 0 ? null : actions.get(actions.size() - 1); 414 | 415 | if (action != null && action.text != null && !action.text.isEmpty()) { 416 | players[playerIndex].talk(action.text, false); 417 | } else { 418 | players[playerIndex].hideBubble(); 419 | } 420 | 421 | if (action != null && (action.type == Action.Type.ATTACK || action.type == Action.Type.USE)) { 422 | if (actionPlayedOnSelf(action)) { 423 | offset = Vector2D.mult(ConstantsUI.PLAYER_OFFSET, 1 - playerIndex); 424 | } 425 | CardUI card1 = cardsPool.get(action.arg1); 426 | CardUI card2 = action.arg2 == -1 ? null : cardsPool.get(action.arg2); 427 | 428 | if (actionDoesntAffectTargetCreature(action)) { 429 | card2 = null; 430 | } 431 | 432 | int x1 = card1.getX(); 433 | int y1 = card1.getY(); 434 | 435 | Card card = engine.state.cardIdMap.get(action.arg1); 436 | Optional creature = engine.state.players[playerIndex].board.stream() 437 | .filter(x -> x.id == card.id) 438 | .findAny(); 439 | 440 | int x2 = card2 == null 441 | ? ConstantsUI.PLAYER_AVATAR.x - card1.getWidth() / 2 + offset.x 442 | : card2.getX() + (card2.getWidth() - card1.getWidth()) / 2; 443 | int y2 = card2 == null 444 | ? ConstantsUI.PLAYER_AVATAR.y - card1.getHeight() / 2 + offset.y 445 | : card2.getY() + (card2.getHeight() - card1.getHeight()) / 2; 446 | 447 | 448 | if (creature.isPresent()) { 449 | animateAttackAndLive(card1, card, creature.get(), action, card.id, x1, y1, x2, y2); 450 | } else { 451 | Optional graveyardCreature = engine.state.players[playerIndex].graveyard.stream() 452 | .filter(x -> x.id == card.id) 453 | .findAny(); 454 | CreatureOnBoard realCreature = graveyardCreature.isPresent() ? graveyardCreature.get() : new CreatureOnBoard(card); 455 | boolean isOnBoard = graveyardCreature.isPresent(); 456 | if (!isOnBoard) { 457 | animateUse(card1, card, realCreature, action, card.id, x1, y1, x2, y2); 458 | } else { 459 | animateAttackAndDie(card1, card, realCreature, action, card.id, x1, y1, x2, y2); 460 | } 461 | } 462 | 463 | 464 | if (card2 != null) { 465 | Optional graveyardCreature2 = engine.state.players[1 - playerIndex].graveyard.stream() 466 | .filter(x -> x.id == action.arg2) 467 | .findAny(); 468 | if (graveyardCreature2.isPresent()) { 469 | forceCommitAlpha1(card2, 0.5); 470 | card2.setVisible(false); 471 | card2.commit(1); 472 | } 473 | card2.action(action.result, action.arg2); 474 | } 475 | } 476 | if (action != null) { 477 | players[playerIndex].attacker(action.result); 478 | players[1 - playerIndex].defender(action.result); 479 | } 480 | } 481 | 482 | private boolean actionPlayedOnSelf(Action action) { 483 | Card card = engine.state.cardIdMap.get(action.arg1); 484 | 485 | return card.type == Type.ITEM_BLUE && 486 | action.result.attackerHealthChange > 0 && 487 | action.result.defenderHealthChange == 0; 488 | } 489 | 490 | private boolean actionDoesntAffectTargetCreature(Action action) { 491 | Card card = engine.state.cardIdMap.get(action.arg1); 492 | 493 | return card.type == Type.ITEM_BLUE && 494 | action.result.defenderDefenseChange == 0 495 | && action.result.defenderAttackChange == 0; 496 | } 497 | 498 | private void animateUse(CardUI cardUI, Card card, CreatureOnBoard creature, Action action, int attackerId, int x1, int y1, int x2, int y2) { 499 | cardUI 500 | .lift() 501 | .action(action.result, card.id) 502 | .move(x1, y1, card, creature, false) 503 | .commitGroup(0.0) 504 | .zoom(x1, y1, (cardUI.getScaleX() + cardUI.getScaleY()) / 2) 505 | .commitGroup(0.1) 506 | .move(x2, y2, card, creature, false) 507 | .setVisible(true) 508 | .commit(0.5) 509 | .ground() 510 | .setVisible(false) 511 | .commit(1); 512 | } 513 | 514 | private void animateAttackAndDie(CardUI cardUI, Card card, CreatureOnBoard creature, Action action, int attackerId, int x1, int y1, int x2, int y2) { 515 | cardUI 516 | .lift() 517 | .action(action.result, card.id) 518 | .move(x1, y1, card, creature, true) 519 | .commitGroup(0.0) 520 | .zoom(x1 + ConstantsUI.ZOOM_OFFSET.x, y1 + ConstantsUI.ZOOM_OFFSET.y, ConstantsUI.LIFTED_CARD_BOARD_SCALE) 521 | .commitGroup(0.1) 522 | .move(x2 + ConstantsUI.ZOOM_OFFSET.x, y2 + ConstantsUI.ZOOM_OFFSET.y, card, creature, true) 523 | .commitGroup(0.5) 524 | .move(x1 + ConstantsUI.ZOOM_OFFSET.x, y1 + ConstantsUI.ZOOM_OFFSET.y, card, creature, true) 525 | .ground() 526 | .setVisibility(0.1) 527 | .commitGroup(0.9) 528 | .zoom(x1, y1, ConstantsUI.CARD_BOARD_SCALE) 529 | .setVisible(false) 530 | .commit(1.0); 531 | } 532 | 533 | private void animateAttackAndLive(CardUI cardUI, Card card, CreatureOnBoard creature, Action action, int attackerId, int x1, int y1, int x2, int y2) { 534 | cardUI 535 | .lift() 536 | .action(action.result, attackerId) 537 | .move(x1, y1, card, creature, true) 538 | .commitGroup(0.0) 539 | .zoom(x1 + ConstantsUI.ZOOM_OFFSET.x, y1 + ConstantsUI.ZOOM_OFFSET.y, ConstantsUI.LIFTED_CARD_BOARD_SCALE) 540 | .commitGroup(0.1) 541 | .move(x2 + ConstantsUI.ZOOM_OFFSET.x, y2 + ConstantsUI.ZOOM_OFFSET.y, card, creature, true) 542 | .commitGroup(0.5) 543 | .move(x1 + ConstantsUI.ZOOM_OFFSET.x, y1 + ConstantsUI.ZOOM_OFFSET.y, card, creature, true) 544 | .ground() 545 | .commitGroup(0.9) 546 | .zoom(x1, y1, ConstantsUI.CARD_BOARD_SCALE) 547 | .commit(1.0); 548 | } 549 | 550 | /** 551 | * Work-around a limitation of the SDK in its current state.
552 | * It is not possible to start value interpolation at a given t if the value has not changed at t 553 | * (unless a different property is changed too).
554 | * This method changes the value of alpha by an imperceptable amount
555 | * It assumes alpha is currently set to 1 556 | */ 557 | private void forceCommitAlpha1(CardUI card, double t) { 558 | card.setVisibility(1-EPSILON); 559 | card.commit(t); 560 | } 561 | 562 | private CardUI getCardFromPool(int cardId) { 563 | if (cardsPool.containsKey(cardId)) 564 | return cardsPool.get(cardId).setVisible(true); 565 | 566 | CardUI card = new CardUI(graphicEntityModule, tooltipModule); 567 | cardsPool.put(cardId, card); 568 | return card; 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/ui/Vector2D.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.ui; 2 | 3 | public class Vector2D { 4 | public int x; 5 | public int y; 6 | 7 | public Vector2D() { 8 | this(0); 9 | } 10 | 11 | public Vector2D(int x) { 12 | this(x, x); 13 | } 14 | 15 | public Vector2D(int x, int y) { 16 | this.x = x; 17 | this.y = y; 18 | } 19 | 20 | public Vector2D(Vector2D vect) { 21 | this.x = vect.x; 22 | this.y = vect.y; 23 | } 24 | 25 | public boolean equals(Vector2D a) { 26 | return this.x == a.x && this.y == a.y; 27 | } 28 | 29 | public boolean isNull() { 30 | return (this.x | this.y) == 0; 31 | } 32 | 33 | public Vector2D set(int x, int y) { 34 | this.x = x; 35 | this.y = y; 36 | return this; 37 | } 38 | 39 | public Vector2D set(Vector2D a) { 40 | this.x = a.x; 41 | this.y = a.y; 42 | return this; 43 | } 44 | 45 | public Vector2D add(Vector2D a) { 46 | this.x += a.x; 47 | this.y += a.y; 48 | return this; 49 | } 50 | 51 | public Vector2D sub(Vector2D a) { 52 | this.x -= a.x; 53 | this.y -= a.y; 54 | return this; 55 | } 56 | 57 | public Vector2D mult(int a) { 58 | this.x *= a; 59 | this.y *= a; 60 | return this; 61 | } 62 | public Vector2D mult(double a) { 63 | this.x *= a; 64 | this.y *= a; 65 | return this; 66 | } 67 | 68 | public Vector2D div(int a) { 69 | this.x /= a; 70 | this.y /= a; 71 | return this; 72 | } 73 | 74 | public Vector2D negate() { 75 | this.x = -this.x; 76 | this.y = -this.y; 77 | return this; 78 | } 79 | 80 | public Vector2D normalize() { 81 | if (isNull()) 82 | return this; 83 | 84 | int absx = Math.abs(this.x); 85 | int absy = Math.abs(this.y); 86 | if (absx > absy) { 87 | this.x /= absx; 88 | this.y = 0; 89 | } else if (absx < absy) { 90 | this.x = 0; 91 | this.y /= absy; 92 | } else { 93 | this.x /= absx; 94 | this.y /= absy; 95 | } 96 | return this; 97 | } 98 | 99 | public int manhattanDistance() { 100 | return Math.abs(x) + Math.abs(y); 101 | } 102 | 103 | public int manhattanDistance(Vector2D a) { 104 | return Math.abs(this.x - a.x) + Math.abs(this.y - a.y); 105 | } 106 | 107 | public double euclidianDistance2() { 108 | return x * x + y * y; 109 | } 110 | 111 | public double euclidianDistance2(Vector2D a) { 112 | return Math.pow(this.x - a.x, 2) + Math.pow(this.y - a.y, 2); 113 | } 114 | 115 | public double euclidianDistance() { 116 | return Math.sqrt(euclidianDistance()); 117 | } 118 | 119 | public double euclidianDistance(Vector2D a) { 120 | return Math.sqrt(euclidianDistance2(a)); 121 | } 122 | 123 | public static Vector2D add(Vector2D a, Vector2D b) { 124 | return new Vector2D(a).add(b); 125 | } 126 | 127 | public static Vector2D sub(Vector2D a, Vector2D b) { 128 | return new Vector2D(a).sub(b); 129 | } 130 | 131 | public static Vector2D mult(Vector2D a, int b) { 132 | return new Vector2D(a).mult(b); 133 | } 134 | 135 | public static Vector2D mult(Vector2D a, double b) { 136 | return new Vector2D(a).mult(b); 137 | } 138 | 139 | public static Vector2D div(Vector2D a, int b) { 140 | return new Vector2D(a).div(b); 141 | } 142 | 143 | public String toString() { 144 | return "[" + x + ":" + y + "]"; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/FXModule.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.codingame.game.Player; 7 | import com.codingame.gameengine.core.Module; 8 | import com.codingame.gameengine.core.MultiplayerGameManager; 9 | import com.codingame.gameengine.module.entities.Text; 10 | import com.google.inject.Inject; 11 | 12 | public class FXModule implements Module { 13 | 14 | MultiplayerGameManager gameManager; 15 | List nicknames = new ArrayList<>(2); 16 | 17 | @Inject 18 | public FXModule(MultiplayerGameManager gameManager) { 19 | this.gameManager = gameManager; 20 | gameManager.registerModule(this); 21 | 22 | } 23 | 24 | @Override 25 | public void onGameInit() { 26 | gameManager.setViewGlobalData("fx", nicknames.stream().mapToInt(n -> n.getId()).toArray()); 27 | } 28 | 29 | public void registerNickname(Text nick) { 30 | nicknames.add(nick); 31 | } 32 | 33 | @Override 34 | public void onAfterGameTurn() { 35 | 36 | } 37 | 38 | @Override 39 | public void onAfterOnEnd() { 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/endscreen/EndScreenModule.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view.endscreen; 2 | 3 | import com.codingame.gameengine.core.AbstractPlayer; 4 | import com.codingame.gameengine.core.GameManager; 5 | import com.codingame.gameengine.core.Module; 6 | import com.google.inject.Inject; 7 | 8 | public class EndScreenModule implements Module { 9 | 10 | private GameManager gameManager; 11 | private int[] scores; 12 | 13 | @Inject 14 | EndScreenModule(GameManager gameManager) { 15 | this.gameManager = gameManager; 16 | gameManager.registerModule(this); 17 | } 18 | 19 | public void setScores(int[] scores) { 20 | this.scores = scores; 21 | } 22 | 23 | @Override 24 | public final void onGameInit() { 25 | } 26 | @Override 27 | public final void onAfterGameTurn() { 28 | } 29 | 30 | @Override 31 | public final void onAfterOnEnd() { 32 | gameManager.setViewData("endScreen", scores); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/tooltip/TooltipModule.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view.tooltip; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import com.codingame.game.Player; 8 | import com.codingame.gameengine.core.Module; 9 | import com.codingame.gameengine.core.MultiplayerGameManager; 10 | import com.codingame.gameengine.module.entities.Entity; 11 | 12 | public class TooltipModule implements Module { 13 | 14 | MultiplayerGameManager gameManager; 15 | Map> registrations; 16 | Map> newRegistrations; 17 | Map extra, newExtra; 18 | 19 | public TooltipModule(MultiplayerGameManager gameManager) { 20 | this.gameManager = gameManager; 21 | gameManager.registerModule(this); 22 | registrations = new HashMap<>(); 23 | newRegistrations = new HashMap<>(); 24 | extra = new HashMap<>(); 25 | newExtra = new HashMap<>(); 26 | } 27 | 28 | @Override 29 | public void onGameInit() { 30 | sendFrameData(); 31 | } 32 | 33 | @Override 34 | public void onAfterGameTurn() { 35 | sendFrameData(); 36 | } 37 | 38 | @Override 39 | public void onAfterOnEnd() { 40 | sendFrameData(); 41 | } 42 | 43 | private void sendFrameData() { 44 | Object[] data = { newRegistrations, newExtra }; 45 | gameManager.setViewData("tooltips", data); 46 | newRegistrations.clear(); 47 | newExtra.clear(); 48 | } 49 | 50 | public void registerEntity(Entity entity) { 51 | registerEntity(entity, new HashMap<>()); 52 | } 53 | 54 | public void registerEntity(Entity entity, Map params) { 55 | int id = entity.getId(); 56 | if (!params.equals(registrations.get(id))) { 57 | newRegistrations.put(id, params); 58 | registrations.put(id, params); 59 | } 60 | } 61 | 62 | boolean deepEquals(String[] a, String[] b) { 63 | return Arrays.deepEquals(a,b); 64 | } 65 | 66 | public void updateExtraTooltipText(Entity entity, String... lines) { 67 | int id = entity.getId(); 68 | if (!deepEquals(lines, extra.get(id))) { 69 | newExtra.put(id, lines); 70 | extra.put(id, lines); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/cardlist.txt: -------------------------------------------------------------------------------- 1 | 1 ; Slimer ; creature ; 1 ; 2 ; 1 ; ------ ; 1 ; 0 ; 0 ; Summon: You gain 1 health. 2 | 2 ; Scuttler ; creature ; 1 ; 1 ; 2 ; ------ ; 0 ; -1 ; 0 ; Summon: Deal 1 damage to your opponent. 3 | 3 ; Beavrat ; creature ; 1 ; 2 ; 2 ; ------ ; 0 ; 0 ; 0 ; 4 | 4 ; Plated Toad ; creature ; 2 ; 1 ; 5 ; ------ ; 0 ; 0 ; 0 ; 5 | 5 ; Grime Gnasher ; creature ; 2 ; 4 ; 1 ; ------ ; 0 ; 0 ; 0 ; 6 | 6 ; Murgling ; creature ; 2 ; 3 ; 2 ; ------ ; 0 ; 0 ; 0 ; 7 | 7 ; Rootkin Sapling ; creature ; 2 ; 2 ; 2 ; -----W ; 0 ; 0 ; 0 ; 8 | 8 ; Psyshroom ; creature ; 2 ; 2 ; 3 ; ------ ; 0 ; 0 ; 0 ; 9 | 9 ; Corrupted Beavrat ; creature ; 3 ; 3 ; 4 ; ------ ; 0 ; 0 ; 0 ; 10 | 10 ; Carnivorous Bush ; creature ; 3 ; 3 ; 1 ; --D--- ; 0 ; 0 ; 0 ; 11 | 11 ; Snowsaur ; creature ; 3 ; 5 ; 2 ; ------ ; 0 ; 0 ; 0 ; 12 | 12 ; Woodshroom ; creature ; 3 ; 2 ; 5 ; ------ ; 0 ; 0 ; 0 ; 13 | 13 ; Swamp Terror ; creature ; 4 ; 5 ; 3 ; ------ ; 1 ; -1 ; 0 ; Summon: You gain 1 health and deal\n1 damage to your opponent. 14 | 14 ; Fanged Lunger ; creature ; 4 ; 9 ; 1 ; ------ ; 0 ; 0 ; 0 ; 15 | 15 ; Pouncing Flailmouth ; creature ; 4 ; 4 ; 5 ; ------ ; 0 ; 0 ; 0 ; 16 | 16 ; Wrangler Fish ; creature ; 4 ; 6 ; 2 ; ------ ; 0 ; 0 ; 0 ; 17 | 17 ; Ash Walker ; creature ; 4 ; 4 ; 5 ; ------ ; 0 ; 0 ; 0 ; 18 | 18 ; Acid Golem ; creature ; 4 ; 7 ; 4 ; ------ ; 0 ; 0 ; 0 ; 19 | 19 ; Foulbeast ; creature ; 5 ; 5 ; 6 ; ------ ; 0 ; 0 ; 0 ; 20 | 20 ; Hedge Demon ; creature ; 5 ; 8 ; 2 ; ------ ; 0 ; 0 ; 0 ; 21 | 21 ; Crested Scuttler ; creature ; 5 ; 6 ; 5 ; ------ ; 0 ; 0 ; 0 ; 22 | 22 ; Sigbovak ; creature ; 6 ; 7 ; 5 ; ------ ; 0 ; 0 ; 0 ; 23 | 23 ; Titan Cave Hog ; creature ; 7 ; 8 ; 8 ; ------ ; 0 ; 0 ; 0 ; 24 | 24 ; Exploding Skitterbug ; creature ; 1 ; 1 ; 1 ; ------ ; 0 ; -1 ; 0 ; Summon: Deal 1 damage to your opponent. 25 | 25 ; Spiney Chompleaf ; creature ; 2 ; 3 ; 1 ; ------ ; -2 ; -2 ; 0 ; Summon: Deal 2 damage to each player. 26 | 26 ; Razor Crab ; creature ; 2 ; 3 ; 2 ; ------ ; 0 ; -1 ; 0 ; Summon: Deal 1 damage to your opponent. 27 | 27 ; Nut Gatherer ; creature ; 2 ; 2 ; 2 ; ------ ; 2 ; 0 ; 0 ; Summon: You gain 2 health. 28 | 28 ; Infested Toad ; creature ; 2 ; 1 ; 2 ; ------ ; 0 ; 0 ; 1 ; Summon: Draw a card. 29 | 29 ; Steelplume Nestling ; creature ; 2 ; 2 ; 1 ; ------ ; 0 ; 0 ; 1 ; Summon: Draw a card. 30 | 30 ; Venomous Bog Hopper ; creature ; 3 ; 4 ; 2 ; ------ ; 0 ; -2 ; 0 ; Summon: Deal 2 damage to your opponent. 31 | 31 ; Woodland Hunter ; creature ; 3 ; 3 ; 1 ; ------ ; 0 ; -1 ; 0 ; Summon: Deal 1 damage to your opponent. 32 | 32 ; Sandsplat ; creature ; 3 ; 3 ; 2 ; ------ ; 0 ; 0 ; 1 ; Summon: Draw a card. 33 | 33 ; Chameleskulk ; creature ; 4 ; 4 ; 3 ; ------ ; 0 ; 0 ; 1 ; Summon: Draw a card. 34 | 34 ; Eldritch Cyclops ; creature ; 5 ; 3 ; 5 ; ------ ; 0 ; 0 ; 1 ; Summon: Draw a card. 35 | 35 ; Snail-eyed Hulker ; creature ; 6 ; 5 ; 2 ; B----- ; 0 ; 0 ; 1 ; Summon: Draw a card. 36 | 36 ; Possessed Skull ; creature ; 6 ; 4 ; 4 ; ------ ; 0 ; 0 ; 2 ; Summon: Draw two cards. 37 | 37 ; Eldritch Multiclops ; creature ; 6 ; 5 ; 7 ; ------ ; 0 ; 0 ; 1 ; Summon: Draw a card. 38 | 38 ; Imp ; creature ; 1 ; 1 ; 3 ; --D--- ; 0 ; 0 ; 0 ; 39 | 39 ; Voracious Imp ; creature ; 1 ; 2 ; 1 ; --D--- ; 0 ; 0 ; 0 ; 40 | 40 ; Rock Gobbler ; creature ; 3 ; 2 ; 3 ; --DG-- ; 0 ; 0 ; 0 ; 41 | 41 ; Blizzard Demon ; creature ; 3 ; 2 ; 2 ; -CD--- ; 0 ; 0 ; 0 ; 42 | 42 ; Flying Leech ; creature ; 4 ; 4 ; 2 ; --D--- ; 0 ; 0 ; 0 ; 43 | 43 ; Screeching Nightmare ; creature ; 6 ; 5 ; 5 ; --D--- ; 0 ; 0 ; 0 ; 44 | 44 ; Deathstalker ; creature ; 6 ; 3 ; 7 ; --D-L- ; 0 ; 0 ; 0 ; 45 | 45 ; Night Howler ; creature ; 6 ; 6 ; 5 ; B-D--- ; -3 ; 0 ; 0 ; Summon: You lose 3 health. 46 | 46 ; Soul Devourer ; creature ; 9 ; 7 ; 7 ; --D--- ; 0 ; 0 ; 0 ; 47 | 47 ; Gnipper ; creature ; 2 ; 1 ; 5 ; --D--- ; 0 ; 0 ; 0 ; 48 | 48 ; Venom Hedgehog ; creature ; 1 ; 1 ; 1 ; ----L- ; 0 ; 0 ; 0 ; 49 | 49 ; Shiny Prowler ; creature ; 2 ; 1 ; 2 ; ---GL- ; 0 ; 0 ; 0 ; 50 | 50 ; Puff Biter ; creature ; 3 ; 3 ; 2 ; ----L- ; 0 ; 0 ; 0 ; 51 | 51 ; Elite Bilespitter ; creature ; 4 ; 3 ; 5 ; ----L- ; 0 ; 0 ; 0 ; 52 | 52 ; Bilespitter ; creature ; 4 ; 2 ; 4 ; ----L- ; 0 ; 0 ; 0 ; 53 | 53 ; Possessed Abomination ; creature ; 4 ; 1 ; 1 ; -C--L- ; 0 ; 0 ; 0 ; 54 | 54 ; Shadow Biter ; creature ; 3 ; 2 ; 2 ; ----L- ; 0 ; 0 ; 0 ; 55 | 55 ; Hermit Slime ; creature ; 2 ; 0 ; 5 ; ---G-- ; 0 ; 0 ; 0 ; 56 | 56 ; Giant Louse ; creature ; 4 ; 2 ; 7 ; ------ ; 0 ; 0 ; 0 ; 57 | 57 ; Dream-Eater ; creature ; 4 ; 1 ; 8 ; ------ ; 0 ; 0 ; 0 ; 58 | 58 ; Darkscale Predator ; creature ; 6 ; 5 ; 6 ; B----- ; 0 ; 0 ; 0 ; 59 | 59 ; Sea Ghost ; creature ; 7 ; 7 ; 7 ; ------ ; 1 ; -1 ; 0 ; Summon: You gain 1 health and deal\n1 damage to your opponent. 60 | 60 ; Gritsuck Troll ; creature ; 7 ; 4 ; 8 ; ------ ; 0 ; 0 ; 0 ; 61 | 61 ; Alpha Troll ; creature ; 9 ; 10 ; 10 ; ------ ; 0 ; 0 ; 0 ; 62 | 62 ; Mutant Troll ; creature ; 12 ; 12 ; 12 ; B--G-- ; 0 ; 0 ; 0 ; 63 | 63 ; Rootkin Drone ; creature ; 2 ; 0 ; 4 ; ---G-W ; 0 ; 0 ; 0 ; 64 | 64 ; Coppershell Tortoise ; creature ; 2 ; 1 ; 1 ; ---G-W ; 0 ; 0 ; 0 ; 65 | 65 ; Steelplume Defender ; creature ; 2 ; 2 ; 2 ; -----W ; 0 ; 0 ; 0 ; 66 | 66 ; Staring Wickerbeast ; creature ; 5 ; 5 ; 1 ; -----W ; 0 ; 0 ; 0 ; 67 | 67 ; Flailing Hammerhead ; creature ; 6 ; 5 ; 5 ; -----W ; 0 ; -2 ; 0 ; Summon: Deal 2 damage to your opponent. 68 | 68 ; Giant Squid ; creature ; 6 ; 7 ; 5 ; -----W ; 0 ; 0 ; 0 ; 69 | 69 ; Charging Boarhound ; creature ; 3 ; 4 ; 4 ; B----- ; 0 ; 0 ; 0 ; 70 | 70 ; Murglord ; creature ; 4 ; 6 ; 3 ; B----- ; 0 ; 0 ; 0 ; 71 | 71 ; Flying Murgling ; creature ; 4 ; 3 ; 2 ; BC---- ; 0 ; 0 ; 0 ; 72 | 72 ; Shuffling Nightmare ; creature ; 4 ; 5 ; 3 ; B----- ; 0 ; 0 ; 0 ; 73 | 73 ; Bog Bounder ; creature ; 4 ; 4 ; 4 ; B----- ; 4 ; 0 ; 0 ; Summon: You gain 4 health. 74 | 74 ; Crusher ; creature ; 5 ; 5 ; 4 ; B--G-- ; 0 ; 0 ; 0 ; 75 | 75 ; Titan Prowler ; creature ; 5 ; 6 ; 5 ; B----- ; 0 ; 0 ; 0 ; 76 | 76 ; Crested Chomper ; creature ; 6 ; 5 ; 5 ; B-D--- ; 0 ; 0 ; 0 ; 77 | 77 ; Lumbering Giant ; creature ; 7 ; 7 ; 7 ; B----- ; 0 ; 0 ; 0 ; 78 | 78 ; Shambler ; creature ; 8 ; 5 ; 5 ; B----- ; 0 ; -5 ; 0 ; Summon: Deal 5 damage to your opponent. 79 | 79 ; Scarlet Colossus ; creature ; 8 ; 8 ; 8 ; B----- ; 0 ; 0 ; 0 ; 80 | 80 ; Corpse Guzzler ; creature ; 8 ; 8 ; 8 ; B--G-- ; 0 ; 0 ; 1 ; Summon: Draw a card. 81 | 81 ; Flying Corpse Guzzler ; creature ; 9 ; 6 ; 6 ; BC---- ; 0 ; 0 ; 0 ; 82 | 82 ; Slithering Nightmare ; creature ; 7 ; 5 ; 5 ; B-D--W ; 0 ; 0 ; 0 ; 83 | 83 ; Restless Owl ; creature ; 0 ; 1 ; 1 ; -C---- ; 0 ; 0 ; 0 ; 84 | 84 ; Fighter Tick ; creature ; 2 ; 1 ; 1 ; -CD--W ; 0 ; 0 ; 0 ; 85 | 85 ; Heartless Crow ; creature ; 3 ; 2 ; 3 ; -C---- ; 0 ; 0 ; 0 ; 86 | 86 ; Crazed Nose-pincher ; creature ; 3 ; 1 ; 5 ; -C---- ; 0 ; 0 ; 0 ; 87 | 87 ; Bloat Demon ; creature ; 4 ; 2 ; 5 ; -C-G-- ; 0 ; 0 ; 0 ; 88 | 88 ; Abyss Nightmare ; creature ; 5 ; 4 ; 4 ; -C---- ; 0 ; 0 ; 0 ; 89 | 89 ; Boombeak ; creature ; 5 ; 4 ; 1 ; -C---- ; 2 ; 0 ; 0 ; Summon: You gain 2 health. 90 | 90 ; Eldritch Swooper ; creature ; 8 ; 5 ; 5 ; -C---- ; 0 ; 0 ; 0 ; 91 | 91 ; Flumpy ; creature ; 0 ; 1 ; 2 ; ---G-- ; 0 ; 1 ; 0 ; Summon: Your opponent gains 1 health. 92 | 92 ; Wurm ; creature ; 1 ; 0 ; 1 ; ---G-- ; 2 ; 0 ; 0 ; Summon: You gain 2 health. 93 | 93 ; Spinekid ; creature ; 1 ; 2 ; 1 ; ---G-- ; 0 ; 0 ; 0 ; 94 | 94 ; Rootkin Defender ; creature ; 2 ; 1 ; 4 ; ---G-- ; 0 ; 0 ; 0 ; 95 | 95 ; Wildum ; creature ; 2 ; 2 ; 3 ; ---G-- ; 0 ; 0 ; 0 ; 96 | 96 ; Prairie Protector ; creature ; 2 ; 3 ; 2 ; ---G-- ; 0 ; 0 ; 0 ; 97 | 97 ; Turta ; creature ; 3 ; 3 ; 3 ; ---G-- ; 0 ; 0 ; 0 ; 98 | 98 ; Lilly Hopper ; creature ; 3 ; 2 ; 4 ; ---G-- ; 0 ; 0 ; 0 ; 99 | 99 ; Cave Crab ; creature ; 3 ; 2 ; 5 ; ---G-- ; 0 ; 0 ; 0 ; 100 | 100 ; Stalagopod ; creature ; 3 ; 1 ; 6 ; ---G-- ; 0 ; 0 ; 0 ; 101 | 101 ; Engulfer ; creature ; 4 ; 3 ; 4 ; ---G-- ; 0 ; 0 ; 0 ; 102 | 102 ; Mole Demon ; creature ; 4 ; 3 ; 3 ; ---G-- ; 0 ; -1 ; 0 ; Summon: Deal 1 damage to your opponent. 103 | 103 ; Mutating Rootkin ; creature ; 4 ; 3 ; 6 ; ---G-- ; 0 ; 0 ; 0 ; 104 | 104 ; Deepwater Shellcrab ; creature ; 4 ; 4 ; 4 ; ---G-- ; 0 ; 0 ; 0 ; 105 | 105 ; King Shellcrab ; creature ; 5 ; 4 ; 6 ; ---G-- ; 0 ; 0 ; 0 ; 106 | 106 ; Far-reaching Nightmare ; creature ; 5 ; 5 ; 5 ; ---G-- ; 0 ; 0 ; 0 ; 107 | 107 ; Worker Shellcrab ; creature ; 5 ; 3 ; 3 ; ---G-- ; 3 ; 0 ; 0 ; Summon: You gain 3 health. 108 | 108 ; Rootkin Elder ; creature ; 5 ; 2 ; 6 ; ---G-- ; 0 ; 0 ; 0 ; 109 | 109 ; Elder Engulfer ; creature ; 5 ; 5 ; 6 ; ------ ; 0 ; 0 ; 0 ; 110 | 110 ; Gargoyle ; creature ; 5 ; 0 ; 9 ; ---G-- ; 0 ; 0 ; 0 ; 111 | 111 ; Turta Knight ; creature ; 6 ; 6 ; 6 ; ---G-- ; 0 ; 0 ; 0 ; 112 | 112 ; Rootkin Leader ; creature ; 6 ; 4 ; 7 ; ---G-- ; 0 ; 0 ; 0 ; 113 | 113 ; Tamed Bilespitter ; creature ; 6 ; 2 ; 4 ; ---G-- ; 4 ; 0 ; 0 ; Summon: You gain 4 health. 114 | 114 ; Gargantua ; creature ; 7 ; 7 ; 7 ; ---G-- ; 0 ; 0 ; 0 ; 115 | 115 ; Rootkin Warchief ; creature ; 8 ; 5 ; 5 ; ---G-W ; 0 ; 0 ; 0 ; 116 | 116 ; Emperor Nightmare ; creature ; 12 ; 8 ; 8 ; BCDGLW ; 0 ; 0 ; 0 ; 117 | 117 ; Protein ; itemGreen ; 1 ; 1 ; 1 ; B----- ; 0 ; 0 ; 0 ; Give a friendly creature +1/+1 and Breakthrough. 118 | 118 ; Royal Helm ; itemGreen ; 0 ; 0 ; 3 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +0/+3. 119 | 119 ; Serrated Shield ; itemGreen ; 1 ; 1 ; 2 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +1/+2. 120 | 120 ; Venomfruit ; itemGreen ; 2 ; 1 ; 0 ; ----L- ; 0 ; 0 ; 0 ; Give a friendly creature +1/+0 and Lethal. 121 | 121 ; Enchanted Hat ; itemGreen ; 2 ; 0 ; 3 ; ------ ; 0 ; 0 ; 1 ; Give a friendly creature +0/+3.\nDraw a card. 122 | 122 ; Bolstering Bread ; itemGreen ; 2 ; 1 ; 3 ; ---G-- ; 0 ; 0 ; 0 ; Give a friendly creature +1/+3 and Guard. 123 | 123 ; Wristguards ; itemGreen ; 2 ; 4 ; 0 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +4/+0. 124 | 124 ; Blood Grapes ; itemGreen ; 3 ; 2 ; 1 ; --D--- ; 0 ; 0 ; 0 ; Give a friendly creature +2/+1 and Drain. 125 | 125 ; Healthy Veggies ; itemGreen ; 3 ; 1 ; 4 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +1/+4. 126 | 126 ; Heavy Shield ; itemGreen ; 3 ; 2 ; 3 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +2/+3. 127 | 127 ; Imperial Helm ; itemGreen ; 3 ; 0 ; 6 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +0/+6. 128 | 128 ; Enchanted Cloth ; itemGreen ; 4 ; 4 ; 3 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +4/+3. 129 | 129 ; Enchanted Leather ; itemGreen ; 4 ; 2 ; 5 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +2/+5. 130 | 130 ; Helm of Remedy ; itemGreen ; 4 ; 0 ; 6 ; ------ ; 4 ; 0 ; 0 ; Give a friendly creature +0/+6.\nYou gain 4 health. 131 | 131 ; Heavy Gauntlet ; itemGreen ; 4 ; 4 ; 1 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +4/+1. 132 | 132 ; High Protein ; itemGreen ; 5 ; 3 ; 3 ; B----- ; 0 ; 0 ; 0 ; Give a friendly creature +3/+3 and Breakthrough. 133 | 133 ; Pie of Power ; itemGreen ; 5 ; 4 ; 0 ; -----W ; 0 ; 0 ; 0 ; Give a friendly creature +4/+0 and Ward. 134 | 134 ; Light The Way ; itemGreen ; 4 ; 2 ; 2 ; ------ ; 0 ; 0 ; 1 ; Give a friendly creature +2/+2.\nDraw a card. 135 | 135 ; Imperial Armour ; itemGreen ; 6 ; 5 ; 5 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +5/+5. 136 | 136 ; Buckler ; itemGreen ; 0 ; 1 ; 1 ; ------ ; 0 ; 0 ; 0 ; Give a friendly creature +1/+1. 137 | 137 ; Ward ; itemGreen ; 2 ; 0 ; 0 ; -----W ; 0 ; 0 ; 0 ; Give a friendly creature Ward. 138 | 138 ; Grow Horns ; itemGreen ; 2 ; 0 ; 0 ; ---G-- ; 0 ; 0 ; 1 ; Give a friendly creature Guard.\nDraw a card. 139 | 139 ; Grow Stingers ; itemGreen ; 4 ; 0 ; 0 ; ----LW ; 0 ; 0 ; 0 ; Give a friendly creature Lethal and Ward. 140 | 140 ; Grow Wings ; itemGreen ; 2 ; 0 ; 0 ; -C---- ; 0 ; 0 ; 0 ; Give a friendly creature Charge. 141 | 141 ; Throwing Knife ; itemRed ; 0 ; -1 ; -1 ; ------ ; 0 ; 0 ; 0 ; Give an enemy creature -1/-1. 142 | 142 ; Staff of Suppression ; itemRed ; 0 ; 0 ; 0 ; BCDGLW ; 0 ; 0 ; 0 ; Remove all abilities from an enemy creature. 143 | 143 ; Pierce Armour ; itemRed ; 0 ; 0 ; 0 ; ---G-- ; 0 ; 0 ; 0 ; Remove Guard from an enemy creature. 144 | 144 ; Rune Axe ; itemRed ; 1 ; 0 ; -2 ; ------ ; 0 ; 0 ; 0 ; Deal 2 damage to an enemy creature. 145 | 145 ; Cursed Sword ; itemRed ; 3 ; -2 ; -2 ; ------ ; 0 ; 0 ; 0 ; Give an enemy creature -2/-2. 146 | 146 ; Cursed Scimitar ; itemRed ; 4 ; -2 ; -2 ; ------ ; 0 ; -2 ; 0 ; Give an enemy creature -2/-2.\nDeal 2 damage to your opponent. 147 | 147 ; Quick Shot ; itemRed ; 2 ; 0 ; -1 ; ------ ; 0 ; 0 ; 1 ; Deal 1 damage to an enemy creature.\nDraw a card. 148 | 148 ; Helm Crusher ; itemRed ; 2 ; 0 ; -2 ; BCDGLW ; 0 ; 0 ; 0 ; Remove all abilities from an enemy creature,\nthen deal 2 damage to it. 149 | 149 ; Rootkin Ritual ; itemRed ; 3 ; 0 ; 0 ; BCDGLW ; 0 ; 0 ; 1 ; Remove all abilities from an enemy creature.\nDraw a card. 150 | 150 ; Throwing Axe ; itemRed ; 2 ; 0 ; -3 ; ------ ; 0 ; 0 ; 0 ; Deal 3 damage to an enemy creature. 151 | 151 ; Decimate ; itemRed ; 5 ; 0 ; -99 ; BCDGLW ; 0 ; 0 ; 0 ; Remove all abilities from an enemy creature,\nthen deal 99 damage to it. 152 | 152 ; Mighty Throwing Axe ; itemRed ; 7 ; 0 ; -7 ; ------ ; 0 ; 0 ; 1 ; Deal 7 damage to an enemy creature.\nDraw a card. 153 | 153 ; Healing Potion ; itemBlue ; 2 ; 0 ; 0 ; ------ ; 5 ; 0 ; 0 ; Gain 5 health. 154 | 154 ; Poison ; itemBlue ; 2 ; 0 ; 0 ; ------ ; 0 ; -2 ; 1 ; Deal 2 damage to your opponent.\nDraw a card. 155 | 155 ; Scroll of Firebolt ; itemBlue ; 3 ; 0 ; -3 ; ------ ; 0 ; -1 ; 0 ; Deal 3 damage.\nDeal 1 damage to your opponent 156 | 156 ; Major Life Steal Potion ; itemBlue ; 3 ; 0 ; 0 ; ------ ; 3 ; -3 ; 0 ; Deal 3 damage to your opponent and gain 3 health. 157 | 157 ; Life Sap Drop ; itemBlue ; 3 ; 0 ; -1 ; ------ ; 1 ; 0 ; 1 ; Deal 1 damage, gain 1 health, and draw a card. 158 | 158 ; Tome of Thunder ; itemBlue ; 3 ; 0 ; -4 ; ------ ; 0 ; 0 ; 0 ; Deal 4 damage. 159 | 159 ; Vial of Soul Drain ; itemBlue ; 4 ; 0 ; -3 ; ------ ; 3 ; 0 ; 0 ; Deal 3 damage and gain 3 health. 160 | 160 ; Minor Life Steal Potion ; itemBlue ; 2 ; 0 ; 0 ; ------ ; 2 ; -2 ; 0 ; Deal 2 damage to your opponent and gain 2 health. 161 | -------------------------------------------------------------------------------- /src/main/resources/cardset_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Legends of Code and Magic 6 | 7 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | %s 89 | 90 |
IdImageNameTypeCostAttackDefenseAbilitiesPlayerHPEnemyHPCardDrawText description
91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/resources/view/assets/atlas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/LegendsOfCodeAndMagic/1aacfeafcf183b9e3bd79b037391e990b9a49749/src/main/resources/view/assets/atlas.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/LegendsOfCodeAndMagic/1aacfeafcf183b9e3bd79b037391e990b9a49749/src/main/resources/view/assets/board.jpg -------------------------------------------------------------------------------- /src/main/resources/view/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/LegendsOfCodeAndMagic/1aacfeafcf183b9e3bd79b037391e990b9a49749/src/main/resources/view/assets/logo.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/newturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/LegendsOfCodeAndMagic/1aacfeafcf183b9e3bd79b037391e990b9a49749/src/main/resources/view/assets/newturn.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/spritesheet.json: -------------------------------------------------------------------------------- 1 | { 2 | "frames": { 3 | "B.png": { 4 | "frame": { 5 | "x": 368, 6 | "y": 492, 7 | "w": 42, 8 | "h": 42 9 | }, 10 | "rotated": false, 11 | "trimmed": false, 12 | "spriteSourceSize": { 13 | "x": 0, 14 | "y": 0, 15 | "w": 42, 16 | "h": 42 17 | }, 18 | "sourceSize": { 19 | "w": 42, 20 | "h": 42 21 | } 22 | }, 23 | "bubble.png": { 24 | "frame": { 25 | "x": 0, 26 | "y": 555, 27 | "w": 419, 28 | "h": 140 29 | }, 30 | "rotated": false, 31 | "trimmed": false, 32 | "spriteSourceSize": { 33 | "x": 0, 34 | "y": 0, 35 | "w": 419, 36 | "h": 140 37 | }, 38 | "sourceSize": { 39 | "w": 419, 40 | "h": 140 41 | } 42 | }, 43 | "bubble_long.png": { 44 | "frame": { 45 | "x": 0, 46 | "y": 696, 47 | "w": 419, 48 | "h": 79 49 | }, 50 | "rotated": false, 51 | "trimmed": false, 52 | "spriteSourceSize": { 53 | "x": 0, 54 | "y": 0, 55 | "w": 419, 56 | "h": 79 57 | }, 58 | "sourceSize": { 59 | "w": 419, 60 | "h": 79 61 | } 62 | }, 63 | "C.png": { 64 | "frame": { 65 | "x": 387, 66 | "y": 128, 67 | "w": 42, 68 | "h": 42 69 | }, 70 | "rotated": false, 71 | "trimmed": false, 72 | "spriteSourceSize": { 73 | "x": 0, 74 | "y": 0, 75 | "w": 42, 76 | "h": 42 77 | }, 78 | "sourceSize": { 79 | "w": 42, 80 | "h": 42 81 | } 82 | }, 83 | "card_stat.png": { 84 | "frame": { 85 | "x": 387, 86 | "y": 326, 87 | "w": 30, 88 | "h": 37 89 | }, 90 | "rotated": false, 91 | "trimmed": false, 92 | "spriteSourceSize": { 93 | "x": 0, 94 | "y": 0, 95 | "w": 30, 96 | "h": 37 97 | }, 98 | "sourceSize": { 99 | "w": 30, 100 | "h": 37 101 | } 102 | }, 103 | "D.png": { 104 | "frame": { 105 | "x": 301, 106 | "y": 128, 107 | "w": 42, 108 | "h": 42 109 | }, 110 | "rotated": false, 111 | "trimmed": false, 112 | "spriteSourceSize": { 113 | "x": 0, 114 | "y": 0, 115 | "w": 42, 116 | "h": 42 117 | }, 118 | "sourceSize": { 119 | "w": 42, 120 | "h": 42 121 | } 122 | }, 123 | "G.png": { 124 | "frame": { 125 | "x": 301, 126 | "y": 326, 127 | "w": 42, 128 | "h": 42 129 | }, 130 | "rotated": false, 131 | "trimmed": false, 132 | "spriteSourceSize": { 133 | "x": 0, 134 | "y": 0, 135 | "w": 42, 136 | "h": 42 137 | }, 138 | "sourceSize": { 139 | "w": 42, 140 | "h": 42 141 | } 142 | }, 143 | "heal.png": { 144 | "frame": { 145 | "x": 301, 146 | "y": 171, 147 | "w": 107, 148 | "h": 87 149 | }, 150 | "rotated": false, 151 | "trimmed": false, 152 | "spriteSourceSize": { 153 | "x": 0, 154 | "y": 0, 155 | "w": 107, 156 | "h": 87 157 | }, 158 | "sourceSize": { 159 | "w": 107, 160 | "h": 87 161 | } 162 | }, 163 | "heart_icon.png": { 164 | "frame": { 165 | "x": 368, 166 | "y": 371, 167 | "w": 41, 168 | "h": 38 169 | }, 170 | "rotated": false, 171 | "trimmed": false, 172 | "spriteSourceSize": { 173 | "x": 0, 174 | "y": 0, 175 | "w": 41, 176 | "h": 38 177 | }, 178 | "sourceSize": { 179 | "w": 41, 180 | "h": 38 181 | } 182 | }, 183 | "heart_icon_rev.png": { 184 | "frame": { 185 | "x": 368, 186 | "y": 453, 187 | "w": 41, 188 | "h": 38 189 | }, 190 | "rotated": false, 191 | "trimmed": false, 192 | "spriteSourceSize": { 193 | "x": 0, 194 | "y": 0, 195 | "w": 41, 196 | "h": 38 197 | }, 198 | "sourceSize": { 199 | "w": 41, 200 | "h": 38 201 | } 202 | }, 203 | "impact.png": { 204 | "frame": { 205 | "x": 301, 206 | "y": 0, 207 | "w": 128, 208 | "h": 127 209 | }, 210 | "rotated": false, 211 | "trimmed": false, 212 | "spriteSourceSize": { 213 | "x": 0, 214 | "y": 0, 215 | "w": 128, 216 | "h": 127 217 | }, 218 | "sourceSize": { 219 | "w": 128, 220 | "h": 127 221 | } 222 | }, 223 | "L.png": { 224 | "frame": { 225 | "x": 344, 226 | "y": 128, 227 | "w": 42, 228 | "h": 42 229 | }, 230 | "rotated": false, 231 | "trimmed": false, 232 | "spriteSourceSize": { 233 | "x": 0, 234 | "y": 0, 235 | "w": 42, 236 | "h": 42 237 | }, 238 | "sourceSize": { 239 | "w": 42, 240 | "h": 42 241 | } 242 | }, 243 | "lethal.png": { 244 | "frame": { 245 | "x": 301, 246 | "y": 259, 247 | "w": 28, 248 | "h": 66 249 | }, 250 | "rotated": false, 251 | "trimmed": false, 252 | "spriteSourceSize": { 253 | "x": 0, 254 | "y": 0, 255 | "w": 28, 256 | "h": 66 257 | }, 258 | "sourceSize": { 259 | "w": 28, 260 | "h": 66 261 | } 262 | }, 263 | "playerframe-active.png": { 264 | "frame": { 265 | "x": 0, 266 | "y": 371, 267 | "w": 183, 268 | "h": 183 269 | }, 270 | "rotated": false, 271 | "trimmed": false, 272 | "spriteSourceSize": { 273 | "x": 0, 274 | "y": 0, 275 | "w": 183, 276 | "h": 183 277 | }, 278 | "sourceSize": { 279 | "w": 183, 280 | "h": 183 281 | } 282 | }, 283 | "playerframe-inactive.png": { 284 | "frame": { 285 | "x": 184, 286 | "y": 371, 287 | "w": 183, 288 | "h": 183 289 | }, 290 | "rotated": false, 291 | "trimmed": false, 292 | "spriteSourceSize": { 293 | "x": 0, 294 | "y": 0, 295 | "w": 183, 296 | "h": 183 297 | }, 298 | "sourceSize": { 299 | "w": 183, 300 | "h": 183 301 | } 302 | }, 303 | "R.png": { 304 | "frame": { 305 | "x": 344, 306 | "y": 326, 307 | "w": 42, 308 | "h": 42 309 | }, 310 | "rotated": false, 311 | "trimmed": false, 312 | "spriteSourceSize": { 313 | "x": 0, 314 | "y": 0, 315 | "w": 42, 316 | "h": 42 317 | }, 318 | "sourceSize": { 319 | "w": 42, 320 | "h": 42 321 | } 322 | }, 323 | "rune.png": { 324 | "frame": { 325 | "x": 330, 326 | "y": 259, 327 | "w": 28, 328 | "h": 36 329 | }, 330 | "rotated": false, 331 | "trimmed": false, 332 | "spriteSourceSize": { 333 | "x": 0, 334 | "y": 0, 335 | "w": 28, 336 | "h": 36 337 | }, 338 | "sourceSize": { 339 | "w": 28, 340 | "h": 36 341 | } 342 | }, 343 | "shadow.png": { 344 | "frame": { 345 | "x": 430, 346 | "y": 0, 347 | "w": 300, 348 | "h": 370 349 | }, 350 | "rotated": false, 351 | "trimmed": false, 352 | "spriteSourceSize": { 353 | "x": 0, 354 | "y": 0, 355 | "w": 300, 356 | "h": 370 357 | }, 358 | "sourceSize": { 359 | "w": 300, 360 | "h": 370 361 | } 362 | }, 363 | "W.png": { 364 | "frame": { 365 | "x": 368, 366 | "y": 410, 367 | "w": 42, 368 | "h": 42 369 | }, 370 | "rotated": false, 371 | "trimmed": false, 372 | "spriteSourceSize": { 373 | "x": 0, 374 | "y": 0, 375 | "w": 42, 376 | "h": 42 377 | }, 378 | "sourceSize": { 379 | "w": 42, 380 | "h": 42 381 | } 382 | }, 383 | "basic_overlay.png": { 384 | "frame": { 385 | "x": 0, 386 | "y": 0, 387 | "w": 300, 388 | "h": 370 389 | }, 390 | "rotated": false, 391 | "trimmed": false, 392 | "spriteSourceSize": { 393 | "x": 0, 394 | "y": 0, 395 | "w": 300, 396 | "h": 370 397 | }, 398 | "sourceSize": { 399 | "w": 300, 400 | "h": 370 401 | } 402 | }, 403 | "guard_overlay.png": { 404 | "frame": { 405 | "x": 430, 406 | "y": 371, 407 | "w": 300, 408 | "h": 370 409 | }, 410 | "rotated": false, 411 | "trimmed": false, 412 | "spriteSourceSize": { 413 | "x": 0, 414 | "y": 0, 415 | "w": 300, 416 | "h": 370 417 | }, 418 | "sourceSize": { 419 | "w": 300, 420 | "h": 370 421 | } 422 | } 423 | }, 424 | "meta": { 425 | "app": "https://www.leshylabs.com/apps/sstool/", 426 | "version": "Leshy SpriteSheet Tool v0.8.4", 427 | "image": "spritesheet.png", 428 | "size.png": { 429 | "w": 730, 430 | "h": 775 431 | }, 432 | "scale": 1 433 | } 434 | } -------------------------------------------------------------------------------- /src/main/resources/view/assets/spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/LegendsOfCodeAndMagic/1aacfeafcf183b9e3bd79b037391e990b9a49749/src/main/resources/view/assets/spritesheet.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/ward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/LegendsOfCodeAndMagic/1aacfeafcf183b9e3bd79b037391e990b9a49749/src/main/resources/view/assets/ward.png -------------------------------------------------------------------------------- /src/main/resources/view/config.js: -------------------------------------------------------------------------------- 1 | import { GraphicEntityModule } from './entity-module/GraphicEntityModule.js' 2 | import { EndScreenModule } from './modules/endscreen/EndScreenModule.js' 3 | import { TooltipModule } from './modules/tooltip/TooltipModule.js' 4 | import { FXModule, api } from './modules/fx/FXModule.js' 5 | 6 | export const modules = [ 7 | GraphicEntityModule, 8 | EndScreenModule, 9 | TooltipModule, 10 | FXModule 11 | ] 12 | 13 | export const gameName = 'LegendsOfCodeAndMagic' 14 | 15 | export const options = [{ 16 | title: 'SHOW LIFE CHANGE INFO', 17 | get: function () { 18 | return api.showDamage 19 | }, 20 | set: function (value) { 21 | api.showDamage = value 22 | }, 23 | values: { 24 | 'ON': true, 25 | 'OFF': false 26 | } 27 | }] 28 | -------------------------------------------------------------------------------- /src/main/resources/view/demo.js: -------------------------------------------------------------------------------- 1 | export const demo = null; 2 | -------------------------------------------------------------------------------- /src/main/resources/view/modules/endscreen/EndScreenModule.js: -------------------------------------------------------------------------------- 1 | import {WIDTH, HEIGHT} from '../../core/constants.js' 2 | import {lerp, unlerp} from '../../core/utils.js' 3 | 4 | /* global PIXI */ 5 | 6 | export class EndScreenModule { 7 | constructor (assets) { 8 | this.states = [] 9 | this.globalData = {} 10 | window.module = this 11 | this.atEnd = false 12 | } 13 | 14 | static get name () { 15 | return 'endScreen' 16 | } 17 | 18 | atLeastOnePixel (width) { 19 | if (width > 0 && width < this.toPixel) { 20 | return this.toPixel 21 | } 22 | return width 23 | } 24 | 25 | updateScene (previousData, currentData, progress) { 26 | if (currentData.scores && progress === 1) { 27 | this.atEnd = true 28 | } else { 29 | this.atEnd = false 30 | } 31 | } 32 | 33 | handleFrameData (frameInfo, scores) { 34 | const state = { 35 | number: frameInfo.number, 36 | scores 37 | } 38 | if (scores) { 39 | this.scores = scores 40 | } 41 | this.states.push(state) 42 | return state 43 | } 44 | 45 | reinitScene (container, canvasData) { 46 | this.toDestroy = [] 47 | this.container = container 48 | this.endLayer = this.createEndScene(this) 49 | if (this.atEnd) { 50 | this.initEndScene() 51 | } 52 | this.container.addChild(this.endLayer) 53 | this.toPixel = (WIDTH / canvasData.width) * canvasData.oversampling 54 | } 55 | 56 | animateScene (delta) { 57 | let step = Math.min(32, delta) 58 | 59 | if (this.atEnd) { 60 | if (!this.animationEnded) { 61 | this.renderEndScene(step) 62 | } 63 | } else { 64 | if (this.endTime > 0) { 65 | this.destroyEndScene() 66 | } 67 | this.endTime = 0 68 | } 69 | } 70 | 71 | destroyEndScene () { 72 | this.animationEnded = false 73 | this.endLayer.visible = false 74 | } 75 | 76 | initEndScene () { 77 | this.animationEnded = false 78 | this.endLayer.visible = true 79 | } 80 | 81 | renderEndScene (step) { 82 | var endOfEnd = 10000 83 | if (this.endTime === 0) { 84 | this.initEndScene() 85 | } 86 | 87 | var backS = 0 88 | var backD = 400 89 | var backP = unlerp(backS, backS + backD, this.endTime) 90 | this.endLayer.backgroundRanking.alpha = backP * 0.9 91 | 92 | var logoS = 400 93 | var logoD = 600 94 | var logoP = unlerp(logoS, logoS + logoD, this.endTime) 95 | this.endLayer.titleRanking.scale.x = this.endLayer.titleRanking.scale.y = 0.001 + lerp(10, 0.8, logoP) 96 | this.endLayer.titleRanking.visible = !!logoP 97 | 98 | var rankS = 1000 99 | var rankD = 300 100 | for (let i = 0; i < this.finishers.length; ++i) { 101 | var p = unlerp(rankS + rankD * i, rankS + rankD * i + rankD, this.endTime) 102 | this.finishers[i].alpha = p 103 | } 104 | 105 | this.endTime += step 106 | 107 | if (this.endTime >= endOfEnd) { 108 | this.animationEnded = true 109 | } 110 | } 111 | 112 | handleGlobalData (players, globalData) { 113 | this.globalData = { 114 | players: players, 115 | playerCount: players.length 116 | } 117 | } 118 | 119 | generateText (text, size, align, color, forceLato) { 120 | var textEl 121 | if (!forceLato) { 122 | textEl = new PIXI.extras.BitmapText(text, { 123 | font: size + 'px 04b', 124 | tint: color 125 | }) 126 | textEl.lineHeight = size 127 | } else { 128 | textEl = new PIXI.Text(text, { 129 | fontSize: Math.round(size / 1.2) + 'px', 130 | fontFamily: 'Lato', 131 | fontWeight: 'bold', 132 | fill: color 133 | }) 134 | textEl.lineHeight = Math.round(size / 1.2) 135 | this.toDestroy.push(textEl) 136 | } 137 | if (align === 'right') { 138 | textEl.anchor.x = 1 139 | } else if (align === 'center') { 140 | textEl.anchor.x = 0.5 141 | } 142 | return textEl 143 | } 144 | 145 | createFinisher (finisher) { 146 | var layer = new PIXI.Container() 147 | 148 | /** ************************************* */ 149 | var avatarContainer = new PIXI.Container() 150 | avatarContainer.y = 0 151 | avatarContainer.x = 0 152 | 153 | var backgroundAvatar = new PIXI.Graphics() 154 | backgroundAvatar.beginFill(0xffffff) 155 | backgroundAvatar.alpha = 0.1 156 | backgroundAvatar.drawRect(0, 0, 240, 120) 157 | avatarContainer.addChild(backgroundAvatar) 158 | 159 | var avatarBorder = new PIXI.Graphics() 160 | avatarBorder.lineStyle(this.atLeastOnePixel(1), 0xffffff) 161 | avatarBorder.alpha = 0.5 162 | avatarBorder.drawRect(0, 0, 120, 120) 163 | avatarContainer.addChild(avatarBorder) 164 | 165 | var avatar = new PIXI.Sprite(finisher.player.avatar) 166 | avatar.width = avatar.height = 120 167 | 168 | var rank = this.generateText(finisher.rank.toString(), 76, 'center', finisher.player.color, true) 169 | rank.anchor.y = 0.5 170 | rank.position.x = 160 171 | rank.position.y = 56 172 | avatarContainer.addChild(rank) 173 | 174 | var rankLetter = this.generateText(finisher.rank === 1 ? 'ST' : 'ND'.toString(), 34, 'left', finisher.player.color, true) 175 | rankLetter.position.x = 184 176 | rankLetter.position.y = 32 177 | avatarContainer.addChild(rankLetter) 178 | 179 | var hudAvatar = new PIXI.Container() 180 | hudAvatar.addChild(avatar) 181 | 182 | avatarContainer.addChild(hudAvatar) 183 | 184 | /** ************************************* */ 185 | 186 | var name = this.generateText(finisher.player.name.toUpperCase(), 50, 'left', finisher.player.color, true) 187 | var scoreLabel = this.generateText(finisher.rank === 1 ? 'Victory' : 'Defeat', 64, 'left', finisher.player.color, true) 188 | 189 | name.x = 330 190 | name.y = -4 191 | scoreLabel.x = 330 192 | scoreLabel.y = 50 193 | 194 | layer.addChild(avatarContainer) 195 | layer.addChild(name) 196 | layer.addChild(scoreLabel) 197 | 198 | return layer 199 | } 200 | 201 | createEndScene () { 202 | var layer = new PIXI.Container() 203 | 204 | var background = new PIXI.Graphics() 205 | background.beginFill(0, 0.85) 206 | background.drawRect(0, 0, WIDTH, HEIGHT) 207 | background.endFill() 208 | 209 | layer.backgroundRanking = background 210 | 211 | var titleRanking = PIXI.Sprite.fromFrame('logo.png') 212 | titleRanking.anchor.x = titleRanking.anchor.y = 0.5 213 | layer.titleRanking = titleRanking 214 | 215 | titleRanking.position.x = WIDTH / 2 216 | titleRanking.position.y = 230 217 | 218 | var podium = [] 219 | for (var i = 0; i < this.globalData.playerCount; ++i) { 220 | podium.push({ 221 | score: this.scores[i], 222 | player: this.globalData.players[i], 223 | rank: 0 224 | }) 225 | } 226 | podium.sort(function (a, b) { 227 | return b.score - a.score 228 | }) 229 | 230 | this.finishers = [] 231 | var finishers = new PIXI.Container() 232 | var curRank = 1 233 | var elem 234 | for (i = 0; i < podium.length; ++i) { 235 | if (i > 0 && podium[i - 1].score !== podium[i].score) { 236 | curRank++ 237 | } 238 | 239 | podium[i].rank = curRank 240 | elem = this.createFinisher(podium[i]) 241 | finishers.addChild(elem) 242 | this.finishers.push(elem) 243 | } 244 | 245 | for (i = 0; i < this.finishers.length; ++i) { 246 | this.finishers[i].position.x = (WIDTH - this.finishers[0].width) / 2 247 | this.finishers[i].position.y = i * 150 248 | } 249 | finishers.y = 500 250 | 251 | layer.addChild(background) 252 | layer.addChild(titleRanking) 253 | layer.addChild(finishers) 254 | 255 | layer.visible = false 256 | return layer 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/main/resources/view/modules/fx/FXModule.js: -------------------------------------------------------------------------------- 1 | import { api as entityModule } from '../../entity-module/GraphicEntityModule.js' 2 | import {lerp, unlerp, fitAspectRatio} from '../../core/utils.js' 3 | 4 | export const api = { 5 | showDamage: true, 6 | showCurve: false 7 | } 8 | 9 | export class FXModule { 10 | constructor (assets) { 11 | this.wards = [] 12 | this.time = 0 13 | this.impacts = [] 14 | this.mustShrinkNickname = true 15 | this.nicknameIds = [] 16 | } 17 | 18 | static get name () { 19 | return 'fx' 20 | } 21 | 22 | updateScene (previousData, currentData, progress) { 23 | this.wards = [] 24 | this.impacts = [] 25 | entityModule.entities.forEach(entity => { 26 | if (entity.currentState.image === 'ward.png' && 27 | entity.currentState.alpha > 0 && 28 | entity.currentState.visible) { 29 | this.wards.push(entity) 30 | } 31 | if ((entity.currentState.image === 'impact.png' || entity.currentState.image === 'heal.png') && entity.currentState.alpha > 0 && entity.currentState.visible) { 32 | this.impacts.push(entity) 33 | } 34 | }) 35 | 36 | if (this.mustShrinkNickname) { 37 | this.mustShrinkNickname = false 38 | this.nicknameIds.forEach(entityId => { 39 | let entity = entityModule.entities.get(entityId) 40 | if (!entity.currentState.text || entity.currentState.text === '') { 41 | this.mustShrinkNickname = true 42 | } else { 43 | if (entity.graphics.width > 450) { 44 | let aspectRatio = fitAspectRatio(entity.graphics.width, entity.graphics.height, 450, 60) 45 | entity.graphics.scale.set(aspectRatio) 46 | } 47 | } 48 | }) 49 | } 50 | } 51 | 52 | handleFrameData (frameInfo, nothing) { 53 | return {...frameInfo} 54 | } 55 | 56 | reinitScene (container, canvasData) { 57 | this.mustShrinkNickname = true 58 | } 59 | 60 | animateScene (delta) { 61 | this.time += delta 62 | const w = 210 63 | const h = 260 64 | for (let ward of this.wards) { 65 | ward.graphics.anchor.set(0.5) 66 | ward.graphics.position.set(w / 2, h / 2) 67 | let scale = lerp(0.85, 1, unlerp(-1, 1, Math.cos(this.time / 600))) 68 | 69 | ward.graphics.scale.set( 70 | scale * w / ward.graphics.texture.width, 71 | scale * h / ward.graphics.texture.height 72 | ) 73 | } 74 | 75 | for (let impact of this.impacts) { 76 | impact.container.parent.visible = api.showDamage 77 | } 78 | } 79 | 80 | handleGlobalData (players, nicknameIds) { 81 | this.nicknameIds = nicknameIds || [] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/resources/view/modules/tooltip/TooltipModule.js: -------------------------------------------------------------------------------- 1 | import { ErrorLog } from '../../core/ErrorLog.js'; 2 | import { WIDTH, HEIGHT } from '../../core/constants.js'; 3 | import * as utils from '../../core/utils.js'; 4 | import { api as entityModule } from '../../entity-module/GraphicEntityModule.js'; 5 | 6 | 7 | function getMouseOverFunc(id, tooltip) { 8 | return function () { 9 | tooltip.inside[id] = true; 10 | }; 11 | } 12 | 13 | function getMouseOutFunc(id, tooltip) { 14 | return function () { 15 | delete tooltip.inside[id]; 16 | }; 17 | } 18 | 19 | function getEntityState(entity, frame, progress) { 20 | const subStates = entity.states[frame]; 21 | if (subStates && subStates.length) { 22 | return subStates[subStates.length - 1]; 23 | } 24 | return null; 25 | } 26 | 27 | function getMouseMoveFunc(tooltip, container, module) { 28 | return function (ev) { 29 | if (tooltip) { 30 | var pos = ev.data.getLocalPosition(container); 31 | tooltip.x = pos.x; 32 | tooltip.y = pos.y; 33 | var point = { 34 | x: pos.x * entityModule.coeff, 35 | y: pos.y * entityModule.coeff 36 | }; 37 | 38 | const showing = []; 39 | const ids = Object.keys(tooltip.inside).map(n => +n); 40 | 41 | for (let id of ids) { 42 | if (tooltip.inside[id]) { 43 | const entity = entityModule.entities.get(id); 44 | const state = entity && getEntityState(entity, module.currentFrame.number); 45 | if (!state || state.alpha === 0 || !state.visible) { 46 | delete tooltip.inside[id]; 47 | } else { 48 | showing.push(id); 49 | } 50 | } 51 | } 52 | 53 | if (showing.length) { 54 | const tooltipBlocks = []; 55 | for (let show of showing) { 56 | const entity = entityModule.entities.get(show); 57 | const state = getEntityState(entity, module.currentFrame.number); 58 | if (state !== null) { 59 | let tooltipBlock; 60 | const params = module.currentFrame.registered[show]; 61 | 62 | tooltip.visible = true; 63 | const extra = module.currentFrame.extraText[show]; 64 | if (extra && extra.length) { 65 | tooltipBlock = extra; 66 | } 67 | tooltipBlocks.push(tooltipBlock); 68 | } 69 | } 70 | tooltip.label.text = tooltipBlocks.join('\n──────────\n') 71 | } else { 72 | tooltip.visible = false; 73 | } 74 | 75 | tooltip.background.width = tooltip.label.width + 20; 76 | tooltip.background.height = tooltip.label.height + 20; 77 | 78 | tooltip.pivot.x = -30; 79 | tooltip.pivot.y = -50; 80 | 81 | if (tooltip.y - tooltip.pivot.y + tooltip.height > HEIGHT) { 82 | tooltip.pivot.y = 10 + tooltip.height; 83 | tooltip.y -= tooltip.y - tooltip.pivot.y + tooltip.height - HEIGHT 84 | } 85 | 86 | if (tooltip.x - tooltip.pivot.x + tooltip.width > WIDTH) { 87 | tooltip.pivot.x = tooltip.width; 88 | } 89 | } 90 | 91 | } 92 | }; 93 | 94 | export class TooltipModule { 95 | constructor(assets) { 96 | this.interactive = {}; 97 | this.previousFrame = { 98 | registrations: {}, 99 | extra: {} 100 | }; 101 | this.lastProgress = 1; 102 | this.lastFrame = 0; 103 | 104 | } 105 | 106 | static get name() { 107 | return 'tooltips'; 108 | } 109 | 110 | updateScene(previousData, currentData, progress) { 111 | this.currentFrame = currentData; 112 | this.currentProgress = progress; 113 | } 114 | 115 | handleFrameData(frameInfo, [registrations, extra]) { 116 | const registered = { ...registrations }; 117 | const extraText = { ...this.previousFrame.extraText, ...extra }; 118 | 119 | Object.keys(registrations).forEach( 120 | k => { 121 | this.interactive[k] = true; 122 | } 123 | ); 124 | 125 | const frame = { registered, extraText, number: frameInfo.number }; 126 | this.previousFrame = frame; 127 | return frame; 128 | } 129 | 130 | reinitScene(container, canvasData) { 131 | this.tooltip = this.initTooltip(); 132 | entityModule.entities.forEach(entity => { 133 | if (this.interactive[entity.id]) { 134 | entity.container.interactive = true; 135 | entity.container.mouseover = getMouseOverFunc(entity.id, this.tooltip); 136 | entity.container.mouseout = getMouseOutFunc(entity.id, this.tooltip); 137 | } 138 | }); 139 | this.container = container; 140 | container.interactive = true; 141 | container.mousemove = getMouseMoveFunc(this.tooltip, container, this); 142 | container.addChild(this.tooltip); 143 | } 144 | 145 | generateText(text, size, color, align) { 146 | var textEl = new PIXI.Text(text, { 147 | fontSize: Math.round(size / 1.2) + 'px', 148 | fontFamily: 'monospace', 149 | fontWeight: 'bold', 150 | fill: color 151 | }); 152 | 153 | textEl.lineHeight = Math.round(size / 1.2); 154 | if (align === 'right') { 155 | textEl.anchor.x = 1; 156 | } else if (align === 'center') { 157 | textEl.anchor.x = 0.5; 158 | } 159 | 160 | return textEl; 161 | }; 162 | 163 | initTooltip() { 164 | var tooltip = new PIXI.Container(); 165 | var background = tooltip.background = new PIXI.Graphics(); 166 | var label = tooltip.label = this.generateText('', 36, 0xFFFFFF, 'left'); 167 | 168 | background.beginFill(0x0, 0.7); 169 | background.drawRect(0, 0, 200, 185); 170 | background.endFill(); 171 | background.x = -10; 172 | background.y = -10; 173 | 174 | tooltip.visible = false; 175 | tooltip.inside = {}; 176 | 177 | tooltip.addChild(background); 178 | tooltip.addChild(label); 179 | 180 | tooltip.interactiveChildren = false; 181 | return tooltip; 182 | }; 183 | 184 | animateScene(delta) { 185 | 186 | } 187 | 188 | handleGlobalData(players, globalData) { 189 | 190 | } 191 | } 192 | 193 | class NotYetImplemented extends Error { 194 | constructor(feature) { 195 | super('Not yet implemented: "' + feature); 196 | this.feature = feature; 197 | this.name = 'NotYetImplemented'; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/test/java/Main.java: -------------------------------------------------------------------------------- 1 | import java.util.Properties; 2 | 3 | import com.codingame.game.engine.Constants; 4 | import com.codingame.gameengine.runner.MultiplayerGameRunner; 5 | 6 | public class Main { 7 | public static void main(String[] args) { 8 | MultiplayerGameRunner gameRunner = new MultiplayerGameRunner(); 9 | 10 | Properties gameParameters = new Properties(); 11 | // set game parameters here 12 | // gameRunner.setSeed(1279960l); 13 | // gameParameters.setProperty("draftChoicesSeed", "-5113144502819146988"); 14 | // gameParameters.setProperty("shufflePlayer0Seed", "127"); 15 | // gameParameters.setProperty("shufflePlayer1Seed", "333"); 16 | // gameParameters.setProperty("predefinedDraftIds", "91 92 93,94 95 96,97 98 99,100 101 102,103 104 105,106 107 108,109 110 111,112 113 114,115 116 117,118 119 120,121 122 123,124 125 126,127 128 129,130 131 132,133 134 135,136 137 138,139 140 141,142 143 144,145 146 147,148 149 150,151 152 153,154 155 156,157 158 159,160 160 160,160 160 160,160 160 160,160 160 160,160 160 160,160 160 160,160 160 160"); 17 | gameRunner.setGameParameters(gameParameters); 18 | 19 | gameRunner.addAgent(PlayerEmpty.class); 20 | gameRunner.addAgent(PlayerEmpty.class); 21 | 22 | Constants.VERBOSE_LEVEL = 2; 23 | 24 | //set ruleset here 25 | System.setProperty("league.level", "4"); 26 | 27 | gameRunner.start(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/PlayerEmpty.java: -------------------------------------------------------------------------------- 1 | /** AI description 2 | * Draft phase: 3 | * - always pick the first card 4 | * Game phase: 5 | * - do nothing (outputs single ';') 6 | */ 7 | public class PlayerEmpty 8 | { 9 | public static void main(String[] args) 10 | { 11 | for (int i=0; i < 30; i++) 12 | System.out.println(String.format("PICK 0")); 13 | 14 | while (true) 15 | System.out.println(";"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | # Configuration 2 | name = PropertiesConfig 3 | status = WARN 4 | 5 | appender.console.type = Console 6 | appender.console.name = CONSOLE 7 | appender.console.layout.type = PatternLayout 8 | appender.console.layout.pattern = [%d{yyyy/MM/dd HH:mm:ss}][GF] %-5p : %c{1} - %m%n 9 | 10 | rootLogger.level = info 11 | rootLogger.appenderRef.console.ref = CONSOLE 12 | --------------------------------------------------------------------------------