From 6e78ed93ccac8fd84ed0b82ebba15c68d08e3f8d Mon Sep 17 00:00:00 2001 From: Tommaso Romani <44830726+TommasoRomani@users.noreply.github.com> Date: Mon, 21 Nov 2022 09:51:45 +0100 Subject: [PATCH 01/14] Aggiunto cards.json aggiunto il file contenente il Json che permette di aggiungere e gestire le carte --- cards.json | 4258 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4258 insertions(+) create mode 100644 cards.json diff --git a/cards.json b/cards.json new file mode 100644 index 0000000..30fc4d5 --- /dev/null +++ b/cards.json @@ -0,0 +1,4258 @@ +{ + "2161": { + "card": { + "doublefacedCard": false, + "cardID": 2161, + "alternateCardName": "", + "cardName": "Armor of Thorns", + "splitCard": false, + "flipCard": false, + "multiverseid": 382848 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "flash" + ], + "toughness": "", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{G}", + "levelerCard": false, + "power": "", + "faceName": "Armor of Thorns", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "you may cast armor of thorns as though it had flash. if you cast it any time a sorcery couldn't have been cast, the controller of the permanent it becomes sacrifices it at the beginning of the next cleanup step.", + "enchant nonblack creature", + "enchanted creature gets +2/+2." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1067": { + "card": { + "doublefacedCard": false, + "cardID": 1067, + "alternateCardName": "", + "cardName": "Mountain", + "splitCard": false, + "flipCard": false, + "multiverseid": 2763 + }, + "face1": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [ + "Mountain" + ], + "supertypes": [ + "basic" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "", + "levelerCard": false, + "power": "", + "faceName": "Mountain", + "loyalty": "0", + "types": [ + "land" + ], + "abilities": [ + "R" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2197": { + "card": { + "doublefacedCard": false, + "cardID": 2197, + "alternateCardName": "", + "cardName": "Benthic Behemoth", + "splitCard": false, + "flipCard": false, + "multiverseid": 4690 + }, + "face1": { + "keywordAbilities": [ + "islandwalk ", + "landwalk" + ], + "toughness": "6", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Serpent" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{5}{U}{U}{U}", + "levelerCard": false, + "power": "7", + "faceName": "Benthic Behemoth", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "islandwalk (this creature can't be blocked as long as defending player controls an island.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2186": { + "card": { + "doublefacedCard": false, + "cardID": 2186, + "alternateCardName": "", + "cardName": "Genju of the Falls", + "splitCard": false, + "flipCard": false, + "multiverseid": 74582 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "flying" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{U}", + "levelerCard": false, + "power": "", + "faceName": "Genju of the Falls", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant island", + "{2}: enchanted island becomes a 3/2 blue spirit creature with flying until end of turn. it's still a land.", + "when enchanted island is put into a graveyard, you may return genju of the falls from your graveyard to your hand." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1958": { + "card": { + "doublefacedCard": false, + "cardID": 1958, + "alternateCardName": "", + "cardName": "Aggression", + "splitCard": false, + "flipCard": false, + "multiverseid": 2605 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "first strike", + "trample" + ], + "toughness": "", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{R}", + "levelerCard": false, + "power": "", + "faceName": "Aggression", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant non-wall creature", + "enchanted creature has first strike and trample.", + "at the beginning of the end step of enchanted creature's controller, destroy that creature if it didn't attack this turn." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2190": { + "card": { + "doublefacedCard": false, + "cardID": 2190, + "alternateCardName": "", + "cardName": "Genju of the Realm", + "splitCard": false, + "flipCard": false, + "multiverseid": 75364 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "trample" + ], + "toughness": "", + "colorIndicator": [ + "black", + "blue", + "green", + "red", + "white" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [ + "legendary" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{W}{U}{B}{R}{G}", + "levelerCard": false, + "power": "", + "faceName": "Genju of the Realm", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant land", + "{2}: enchanted land becomes a legendary 8/12 spirit creature with trample until end of turn. it's still a land.", + "when enchanted land is put into a graveyard, you may return genju of the realm from your graveyard to your hand." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2169": { + "card": { + "doublefacedCard": false, + "cardID": 2169, + "alternateCardName": "", + "cardName": "Encase in Ice", + "splitCard": false, + "flipCard": false, + "multiverseid": 394564 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "flash" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{U}", + "levelerCard": false, + "power": "", + "faceName": "Encase in Ice", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "flash (you may cast this spell any time you could cast an instant.)", + "enchant red or green creature", + "when encase in ice enters the battlefield, tap enchanted creature.", + "enchanted creature doesn't untap during its controller's untap step." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1068": { + "card": { + "doublefacedCard": false, + "cardID": 1068, + "alternateCardName": "", + "cardName": "Forest", + "splitCard": false, + "flipCard": false, + "multiverseid": 2748 + }, + "face1": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [ + "Forest" + ], + "supertypes": [ + "basic" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "", + "levelerCard": false, + "power": "", + "faceName": "Forest", + "loyalty": "0", + "types": [ + "land" + ], + "abilities": [ + "G" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1710": { + "card": { + "doublefacedCard": false, + "cardID": 1710, + "alternateCardName": "", + "cardName": "Canyon Wildcat", + "splitCard": false, + "flipCard": false, + "multiverseid": 4807 + }, + "face1": { + "keywordAbilities": [ + "mountainwalk" + ], + "toughness": "1", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Cat" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{R}", + "levelerCard": false, + "power": "2", + "faceName": "Canyon Wildcat", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "mountainwalk (this creature can't be blocked as long as defending player controls a mountain.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1049": { + "card": { + "doublefacedCard": false, + "cardID": 1049, + "alternateCardName": "", + "cardName": "True-Faith Censer", + "splitCard": false, + "flipCard": false, + "multiverseid": 410035 + }, + "face1": { + "keywordAbilities": [ + "equip", + "vigilance" + ], + "toughness": "", + "colorIndicator": [], + "subtypes": [ + "Equipment" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}", + "levelerCard": false, + "power": "", + "faceName": "True-Faith Censer", + "loyalty": "0", + "types": [ + "artifact" + ], + "abilities": [ + "equipped creature gets +1/+1 and has vigilance.", + "as long as equipped creature is a human, it gets an additional +1/+0.", + "equip {2} ({2}: attach to target creature you control. equip only as a sorcery.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2199": { + "card": { + "doublefacedCard": false, + "cardID": 2199, + "alternateCardName": "", + "cardName": "Zodiac Rooster", + "splitCard": false, + "flipCard": false, + "multiverseid": 10506 + }, + "face1": { + "keywordAbilities": [ + "plainswalk" + ], + "toughness": "1", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Bird" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{G}", + "levelerCard": false, + "power": "2", + "faceName": "Zodiac Rooster", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "plainswalk (this creature can't be blocked as long as defending player controls a plains.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1418": { + "card": { + "doublefacedCard": false, + "cardID": 1418, + "alternateCardName": "", + "cardName": "Armament Master", + "splitCard": false, + "flipCard": false, + "multiverseid": 190420 + }, + "face1": { + "keywordAbilities": [ + "equip" + ], + "toughness": "2", + "colorIndicator": [ + "white" + ], + "subtypes": [ + "Kor", + "Soldier" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{W}{W}", + "levelerCard": false, + "power": "2", + "faceName": "Armament Master", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "other kor creatures you control get +2/+2 for each equipment attached to armament master." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2177": { + "card": { + "doublefacedCard": false, + "cardID": 2177, + "alternateCardName": "", + "cardName": "Uncontrolled Infestation", + "splitCard": false, + "flipCard": false, + "multiverseid": 46614 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{R}", + "levelerCard": false, + "power": "", + "faceName": "Uncontrolled Infestation", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant nonbasic land", + "when enchanted land becomes tapped, destroy it." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2194": { + "card": { + "doublefacedCard": false, + "cardID": 2194, + "alternateCardName": "", + "cardName": "Boggart Brute", + "splitCard": false, + "flipCard": false, + "multiverseid": 398606 + }, + "face1": { + "keywordAbilities": [ + "menace" + ], + "toughness": "2", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Goblin", + "Warrior" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{R}", + "levelerCard": false, + "power": "3", + "faceName": "Boggart Brute", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "menace (this creature can't be blocked except by two or more creatures.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1064": { + "card": { + "doublefacedCard": false, + "cardID": 1064, + "alternateCardName": "", + "cardName": "Plains", + "splitCard": false, + "flipCard": false, + "multiverseid": 2773 + }, + "face1": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [ + "Plains" + ], + "supertypes": [ + "basic" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "", + "levelerCard": false, + "power": "", + "faceName": "Plains", + "loyalty": "0", + "types": [ + "land" + ], + "abilities": [ + "W" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2168": { + "card": { + "doublefacedCard": false, + "cardID": 2168, + "alternateCardName": "", + "cardName": "Coral Net", + "splitCard": false, + "flipCard": false, + "multiverseid": 19696 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{U}", + "levelerCard": false, + "power": "", + "faceName": "Coral Net", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant green or white creature", + "enchanted creature has \"at the beginning of your upkeep, sacrifice this creature unless you discard a card.\"" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2160": { + "card": { + "doublefacedCard": false, + "cardID": 2160, + "alternateCardName": "", + "cardName": "Indestructibility", + "splitCard": false, + "flipCard": false, + "multiverseid": 370673 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "indestructible" + ], + "toughness": "", + "colorIndicator": [ + "white" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{W}", + "levelerCard": false, + "power": "", + "faceName": "Indestructibility", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant permanent", + "enchanted permanent has indestructible. (effects that say \"destroy\" don't destroy that permanent. a creature with indestructible can't be destroyed by damage.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2173": { + "card": { + "doublefacedCard": false, + "cardID": 2173, + "alternateCardName": "", + "cardName": "Psychic Possession", + "splitCard": false, + "flipCard": false, + "multiverseid": 107254 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{U}{U}", + "levelerCard": false, + "power": "", + "faceName": "Psychic Possession", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant opponent", + "skip your draw step.", + "whenever enchanted opponent draws a card, you may draw a card." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1083": { + "card": { + "doublefacedCard": false, + "cardID": 1083, + "alternateCardName": "", + "cardName": "Jace, Unraveler of Secrets", + "splitCard": false, + "flipCard": false, + "multiverseid": 409812 + }, + "face1": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Jace" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N2": "", + "N1": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{U}{U}", + "levelerCard": false, + "power": "", + "faceName": "Jace, Unraveler of Secrets", + "loyalty": "5", + "types": [ + "planeswalker" + ], + "abilities": [ + "+1: scry 1, then draw a card.", + "\u22122: return target creature to its owner's hand.", + "\u22128: you get an emblem with \"whenever an opponent casts his or her first spell each turn, counter that spell.\"" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N2": "", + "N1": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2175": { + "card": { + "doublefacedCard": false, + "cardID": 2175, + "alternateCardName": "", + "cardName": "Spellweaver Volute", + "splitCard": false, + "flipCard": false, + "multiverseid": 136032 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{U}{U}", + "levelerCard": false, + "power": "", + "faceName": "Spellweaver Volute", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant instant card in a graveyard", + "whenever you cast a sorcery spell, copy the enchanted instant card. you may cast the copy without paying its mana cost. if you do, exile the enchanted card and attach spellweaver volute to another instant card in a graveyard." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2187": { + "card": { + "doublefacedCard": false, + "cardID": 2187, + "alternateCardName": "", + "cardName": "Genju of the Fens", + "splitCard": false, + "flipCard": false, + "multiverseid": 74035 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{B}", + "levelerCard": false, + "power": "", + "faceName": "Genju of the Fens", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant swamp", + "{2}{B}: until end of turn, enchanted swamp becomes a 2/2 black spirit creature with \"", + "when enchanted swamp is put into a graveyard, you may return genju of the fens from your graveyard to your hand." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2166": { + "card": { + "doublefacedCard": false, + "cardID": 2166, + "alternateCardName": "", + "cardName": "Decomposition", + "splitCard": false, + "flipCard": false, + "multiverseid": 3381 + }, + "face1": { + "keywordAbilities": [ + "cumulative upkeep", + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{G}", + "levelerCard": false, + "power": "", + "faceName": "Decomposition", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant black creature", + "enchanted creature has \"cumulative upkeep\u2014pay 1 life.\" (at the beginning of its controller's upkeep, that player puts an age counter on it, then sacrifices it unless he or she pays its upkeep cost for each age counter on it.)", + "when enchanted creature dies, its controller loses 2 life." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1511": { + "card": { + "doublefacedCard": false, + "cardID": 1511, + "alternateCardName": "", + "cardName": "Frontier Guide", + "splitCard": false, + "flipCard": false, + "multiverseid": 180361 + }, + "face1": { + "keywordAbilities": [], + "toughness": "1", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Elf", + "Scout" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{G}", + "levelerCard": false, + "power": "1", + "faceName": "Frontier Guide", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "{3}{G}, {T}: search your library for a basic land card and put it onto the battlefield tapped. then shuffle your library." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2189": { + "card": { + "doublefacedCard": false, + "cardID": 2189, + "alternateCardName": "", + "cardName": "Genju of the Cedars", + "splitCard": false, + "flipCard": false, + "multiverseid": 74424 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{G}", + "levelerCard": false, + "power": "", + "faceName": "Genju of the Cedars", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant forest", + "{2}: enchanted forest becomes a 4/4 green spirit creature until end of turn. it's still a land.", + "when enchanted forest is put into a graveyard, you may return genju of the cedars from your graveyard to your hand." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2171": { + "card": { + "doublefacedCard": false, + "cardID": 2171, + "alternateCardName": "", + "cardName": "Glimmerdust Nap", + "splitCard": false, + "flipCard": false, + "multiverseid": 139436 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{U}", + "levelerCard": false, + "power": "", + "faceName": "Glimmerdust Nap", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant tapped creature", + "enchanted creature doesn't untap during its controller's untap step." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1065": { + "card": { + "doublefacedCard": false, + "cardID": 1065, + "alternateCardName": "", + "cardName": "Island", + "splitCard": false, + "flipCard": false, + "multiverseid": 2768 + }, + "face1": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [ + "Island" + ], + "supertypes": [ + "basic" + ], + "levelerAbilities": { + "toughness": "", + "N2": "", + "N1": "", + "power": "", + "abilities": [] + }, + "manaCost": "", + "levelerCard": false, + "power": "", + "faceName": "Island", + "loyalty": "0", + "types": [ + "land" + ], + "abilities": [ + "U" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N2": "", + "N1": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2192": { + "card": { + "doublefacedCard": false, + "cardID": 2192, + "alternateCardName": "", + "cardName": "Barbarian General", + "splitCard": false, + "flipCard": false, + "multiverseid": 10574 + }, + "face1": { + "keywordAbilities": [ + "horsemanship" + ], + "toughness": "2", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Human", + "Barbarian", + "Soldier" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{4}{R}", + "levelerCard": false, + "power": "3", + "faceName": "Barbarian General", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "horsemanship (this creature can't be blocked except by creatures with horsemanship.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1673": { + "card": { + "doublefacedCard": false, + "cardID": 1673, + "alternateCardName": "", + "cardName": "Commander Greven il-Vec", + "splitCard": false, + "flipCard": false, + "multiverseid": 397539 + }, + "face1": { + "keywordAbilities": [ + "fear" + ], + "toughness": "5", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Human", + "Warrior" + ], + "supertypes": [ + "legendary" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{B}{B}{B}", + "levelerCard": false, + "power": "7", + "faceName": "Commander Greven il-Vec", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "fear (this creature can't be blocked except by artifact creatures and/or black creatures.)", + "when commander greven il-vec enters the battlefield, sacrifice a creature." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2183": { + "card": { + "doublefacedCard": false, + "cardID": 2183, + "alternateCardName": "", + "cardName": "Convincing Mirage", + "splitCard": false, + "flipCard": false, + "multiverseid": 190161 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{U}", + "levelerCard": false, + "power": "", + "faceName": "Convincing Mirage", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant land", + "as convincing mirage enters the battlefield, choose a basic land type.", + "enchanted land is the chosen type." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2159": { + "card": { + "doublefacedCard": false, + "cardID": 2159, + "alternateCardName": "", + "cardName": "Artificer's Hex", + "splitCard": false, + "flipCard": false, + "multiverseid": 370634 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "equip" + ], + "toughness": "", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{B}", + "levelerCard": false, + "power": "", + "faceName": "Artificer's Hex", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant equipment", + "at the beginning of your upkeep, if enchanted equipment is attached to a creature, destroy that creature." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "911": { + "card": { + "doublefacedCard": false, + "cardID": 911, + "alternateCardName": "", + "cardName": "Crow of Dark Tidings", + "splitCard": false, + "flipCard": false, + "multiverseid": 409852 + }, + "face1": { + "keywordAbilities": [ + "flying" + ], + "toughness": "1", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Zombie", + "Bird" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{B}", + "levelerCard": false, + "power": "2", + "faceName": "Crow of Dark Tidings", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "flying", + "when crow of dark tidings enters the battlefield or dies, put the top two cards of your library into your graveyard." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1459": { + "card": { + "doublefacedCard": false, + "cardID": 1459, + "alternateCardName": "", + "cardName": "Welkin Tern", + "splitCard": false, + "flipCard": false, + "multiverseid": 191353 + }, + "face1": { + "keywordAbilities": [ + "flying", + "reach" + ], + "toughness": "1", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Bird" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{U}", + "levelerCard": false, + "power": "2", + "faceName": "Welkin Tern", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "flying (this creature can't be blocked except by creatures with flying or reach.)", + "welkin tern can block only creatures with flying." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2164": { + "card": { + "doublefacedCard": false, + "cardID": 2164, + "alternateCardName": "", + "cardName": "Mind Harness", + "splitCard": false, + "flipCard": false, + "multiverseid": 3349 + }, + "face1": { + "keywordAbilities": [ + "cumulative upkeep", + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{U}", + "levelerCard": false, + "power": "", + "faceName": "Mind Harness", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant red or green creature", + "{1}cumulative upkeep (at the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)", + "you control enchanted creature." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2198": { + "card": { + "doublefacedCard": false, + "cardID": 2198, + "alternateCardName": "", + "cardName": "Bayou Dragonfly", + "splitCard": false, + "flipCard": false, + "multiverseid": 4749 + }, + "face1": { + "keywordAbilities": [ + "flying", + "swampwalk" + ], + "toughness": "1", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Insect" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{G}", + "levelerCard": false, + "power": "1", + "faceName": "Bayou Dragonfly", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "flying; swampwalk (this creature can't be blocked as long as defending player controls a swamp.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2162": { + "card": { + "doublefacedCard": false, + "cardID": 2162, + "alternateCardName": "", + "cardName": "Call to Serve", + "splitCard": false, + "flipCard": false, + "multiverseid": 240081 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "flying" + ], + "toughness": "", + "colorIndicator": [ + "white" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{W}", + "levelerCard": false, + "power": "", + "faceName": "Call to Serve", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant nonblack creature", + "enchanted creature gets +1/+2, has flying, and is an angel in addition to its other types." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2176": { + "card": { + "doublefacedCard": false, + "cardID": 2176, + "alternateCardName": "", + "cardName": "Suppression Bonds", + "splitCard": false, + "flipCard": false, + "multiverseid": 398602 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "white" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{W}", + "levelerCard": false, + "power": "", + "faceName": "Suppression Bonds", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant nonland permanent", + "enchanted permanent can't attack or block, and its activated abilities can't be activated." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1460": { + "card": { + "doublefacedCard": false, + "cardID": 1460, + "alternateCardName": "", + "cardName": "Bala Ged Thief", + "splitCard": false, + "flipCard": false, + "multiverseid": 197402 + }, + "face1": { + "keywordAbilities": [], + "toughness": "2", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Human", + "Rogue", + "Ally" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{B}", + "levelerCard": false, + "power": "2", + "faceName": "Bala Ged Thief", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "whenever bala ged thief or another ally enters the battlefield under your control, target player reveals a number of cards from his or her hand equal to the number of allies you control. you choose one of them. that player discards that card." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1201": { + "card": { + "doublefacedCard": false, + "cardID": 1201, + "alternateCardName": "", + "cardName": "Llanowar Elves", + "splitCard": false, + "flipCard": false, + "multiverseid": 189878 + }, + "face1": { + "keywordAbilities": [], + "toughness": "1", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Elf", + "Druid" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{G}", + "levelerCard": false, + "power": "1", + "faceName": "Llanowar Elves", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "{T}{G}: add {G} to your mana pool." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2167": { + "card": { + "doublefacedCard": false, + "cardID": 2167, + "alternateCardName": "", + "cardName": "Controlled Instincts", + "splitCard": false, + "flipCard": false, + "multiverseid": 183053 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{U}", + "levelerCard": false, + "power": "", + "faceName": "Controlled Instincts", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant red or green creature", + "enchanted creature doesn't untap during its controller's untap step." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2170": { + "card": { + "doublefacedCard": false, + "cardID": 2170, + "alternateCardName": "", + "cardName": "Entangling Vines", + "splitCard": false, + "flipCard": false, + "multiverseid": 189901 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{G}", + "levelerCard": false, + "power": "", + "faceName": "Entangling Vines", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant tapped creature", + "enchanted creature doesn't untap during its controller's untap step." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2165": { + "card": { + "doublefacedCard": false, + "cardID": 2165, + "alternateCardName": "", + "cardName": "Consuming Ferocity", + "splitCard": false, + "flipCard": false, + "multiverseid": 3437 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{R}", + "levelerCard": false, + "power": "", + "faceName": "Consuming Ferocity", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant non-wall creature", + "enchanted creature gets +1/+0.", + "at the beginning of your upkeep, put a +1/+0 counter on enchanted creature. if that creature has three or more +1/+0 counters on it, it deals damage equal to its power to its controller, then destroy that creature and it can't be regenerated." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1487": { + "card": { + "doublefacedCard": false, + "cardID": 1487, + "alternateCardName": "", + "cardName": "Goblin Guide", + "splitCard": false, + "flipCard": false, + "multiverseid": 170987 + }, + "face1": { + "keywordAbilities": [ + "haste" + ], + "toughness": "2", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Goblin", + "Scout" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{R}", + "levelerCard": false, + "power": "2", + "faceName": "Goblin Guide", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "haste", + "whenever goblin guide attacks, defending player reveals the top card of his or her library. if it's a land card, that player puts it into his or her hand." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1678": { + "card": { + "doublefacedCard": false, + "cardID": 1678, + "alternateCardName": "", + "cardName": "Dauthi Horror", + "splitCard": false, + "flipCard": false, + "multiverseid": 397594 + }, + "face1": { + "keywordAbilities": [ + "shadow" + ], + "toughness": "1", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Dauthi", + "Horror" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{B}", + "levelerCard": false, + "power": "2", + "faceName": "Dauthi Horror", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "shadow (this creature can block or be blocked by only creatures with shadow.)", + "dauthi horror can't be blocked by white creatures." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2163": { + "card": { + "doublefacedCard": false, + "cardID": 2163, + "alternateCardName": "", + "cardName": "Carry Away", + "splitCard": false, + "flipCard": false, + "multiverseid": 48572 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "equip" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{U}{U}", + "levelerCard": false, + "power": "", + "faceName": "Carry Away", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant equipment", + "when carry away enters the battlefield, unattach enchanted equipment.", + "you control enchanted equipment." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2172": { + "card": { + "doublefacedCard": false, + "cardID": 2172, + "alternateCardName": "", + "cardName": "Krovikan Plague", + "splitCard": false, + "flipCard": false, + "multiverseid": 3081 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{B}", + "levelerCard": false, + "power": "", + "faceName": "Krovikan Plague", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant non-wall creature you control", + "when krovikan plague enters the battlefield, draw a card at the beginning of the next turn's upkeep.", + "enchant" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1169": { + "card": { + "doublefacedCard": false, + "cardID": 1169, + "alternateCardName": "", + "cardName": "Flight", + "splitCard": false, + "flipCard": false, + "multiverseid": 401 + }, + "face1": { + "keywordAbilities": [ + "enchant", + "flying" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{U}", + "levelerCard": false, + "power": "", + "faceName": "Flight", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant creature", + "enchanted creature has flying." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2196": { + "card": { + "doublefacedCard": false, + "cardID": 2196, + "alternateCardName": "", + "cardName": "Heartwood Treefolk", + "splitCard": false, + "flipCard": false, + "multiverseid": 4767 + }, + "face1": { + "keywordAbilities": [ + "forestwalk" + ], + "toughness": "4", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Treefolk" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{G}{G}", + "levelerCard": false, + "power": "3", + "faceName": "Heartwood Treefolk", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "forestwalk (this creature can't be blocked as long as defending player controls a forest.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2179": { + "card": { + "doublefacedCard": false, + "cardID": 2179, + "alternateCardName": "", + "cardName": "Steal Enchantment", + "splitCard": false, + "flipCard": false, + "multiverseid": 4729 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{U}{U}", + "levelerCard": false, + "power": "", + "faceName": "Steal Enchantment", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant enchantment", + "you control enchanted enchantment." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2174": { + "card": { + "doublefacedCard": false, + "cardID": 2174, + "alternateCardName": "", + "cardName": "Soul Tithe", + "splitCard": false, + "flipCard": false, + "multiverseid": 265372 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "white" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{W}", + "levelerCard": false, + "power": "", + "faceName": "Soul Tithe", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant nonland permanent", + "{variable colorless}at the beginning of the upkeep of enchanted permanent's controller, that player sacrifices it unless he or she pays , where x is its converted mana cost." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2191": { + "card": { + "doublefacedCard": false, + "cardID": 2191, + "alternateCardName": "", + "cardName": "Cao Ren, Wei Commander", + "splitCard": false, + "flipCard": false, + "multiverseid": 10712 + }, + "face1": { + "keywordAbilities": [ + "horsemanship" + ], + "toughness": "3", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Human", + "Soldier", + "Warrior" + ], + "supertypes": [ + "legendary" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{B}{B}", + "levelerCard": false, + "power": "3", + "faceName": "Cao Ren, Wei Commander", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "horsemanship (this creature can't be blocked except by creatures with horsemanship.)", + "when cao ren, wei commander enters the battlefield, you lose 3 life." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2178": { + "card": { + "doublefacedCard": false, + "cardID": 2178, + "alternateCardName": "", + "cardName": "Wurmweaver Coil", + "splitCard": false, + "flipCard": false, + "multiverseid": 97230 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{4}{G}{G}", + "levelerCard": false, + "power": "", + "faceName": "Wurmweaver Coil", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant green creature", + "enchanted creature gets +6/+6.", + "{G}{G}{G}, sacrifice wurmweaver coil: create a 6/6 green wurm creature token." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2188": { + "card": { + "doublefacedCard": false, + "cardID": 2188, + "alternateCardName": "", + "cardName": "Genju of the Spires", + "splitCard": false, + "flipCard": false, + "multiverseid": 74666 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{R}", + "levelerCard": false, + "power": "", + "faceName": "Genju of the Spires", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant mountain", + "{2}: enchanted mountain becomes a 6/1 red spirit creature until end of turn. it's still a land.", + "when enchanted mountain is put into a graveyard, you may return genju of the spires from your graveyard to your hand." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2185": { + "card": { + "doublefacedCard": false, + "cardID": 2185, + "alternateCardName": "", + "cardName": "Genju of the Fields", + "splitCard": false, + "flipCard": false, + "multiverseid": 74087 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "white" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{W}", + "levelerCard": false, + "power": "", + "faceName": "Genju of the Fields", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant plains", + "{2}: until end of turn, enchanted plains becomes a 2/5 white spirit creature with \"whenever this creature deals damage, its controller gains that much life.\" it's still a land.", + "when enchanted plains is put into a graveyard, you may return genju of the fields from your graveyard to your hand." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2180": { + "card": { + "doublefacedCard": false, + "cardID": 2180, + "alternateCardName": "", + "cardName": "Curse of Death's Hold", + "splitCard": false, + "flipCard": false, + "multiverseid": 227075 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "black" + ], + "subtypes": [ + "Aura", + "Curse" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{B}{B}", + "levelerCard": false, + "power": "", + "faceName": "Curse of Death's Hold", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant player", + "creatures enchanted player controls get -1/-1." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2195": { + "card": { + "doublefacedCard": false, + "cardID": 2195, + "alternateCardName": "", + "cardName": "Elemental Resonance", + "splitCard": false, + "flipCard": false, + "multiverseid": 107369 + }, + "face1": { + "keywordAbilities": [ + "enchant" + ], + "toughness": "", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Aura" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{G}{G}", + "levelerCard": false, + "power": "", + "faceName": "Elemental Resonance", + "loyalty": "0", + "types": [ + "enchantment" + ], + "abilities": [ + "enchant permanent", + "at the beginning of your precombat main phase, add mana equal to enchanted permanent's mana cost to your mana pool. (mana cost includes color. if a mana symbol has multiple colors, choose one.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2184": { + "card": { + "doublefacedCard": false, + "cardID": 2184, + "alternateCardName": "", + "cardName": "Darksteel Garrison", + "splitCard": false, + "flipCard": false, + "multiverseid": 126211 + }, + "face1": { + "keywordAbilities": [ + "fortify", + "indestructible" + ], + "toughness": "", + "colorIndicator": [], + "subtypes": [ + "Fortification" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}", + "levelerCard": false, + "power": "", + "faceName": "Darksteel Garrison", + "loyalty": "0", + "types": [ + "artifact" + ], + "abilities": [ + "fortified land has indestructible.", + "whenever fortified land becomes tapped, target creature gets +1/+1 until end of turn.", + "fortify {3} ({3}: attach to target land you control. fortify only as a sorcery. this card enters the battlefield unattached and stays on the battlefield if the land leaves.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "1066": { + "card": { + "doublefacedCard": false, + "cardID": 1066, + "alternateCardName": "", + "cardName": "Swamp", + "splitCard": false, + "flipCard": false, + "multiverseid": 397467 + }, + "face1": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [ + "Swamp" + ], + "supertypes": [ + "basic" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "", + "levelerCard": false, + "power": "", + "faceName": "Swamp", + "loyalty": "0", + "types": [ + "land" + ], + "abilities": [ + "B" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "880": { + "card": { + "doublefacedCard": false, + "cardID": 880, + "alternateCardName": "", + "cardName": "Forgotten Creation", + "splitCard": false, + "flipCard": false, + "multiverseid": 409806 + }, + "face1": { + "keywordAbilities": [ + "skulk" + ], + "toughness": "3", + "colorIndicator": [ + "blue" + ], + "subtypes": [ + "Zombie", + "Horror" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{U}", + "levelerCard": false, + "power": "3", + "faceName": "Forgotten Creation", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "skulk (this creature can't be blocked by creatures with greater power.)", + "at the beginning of your upkeep, you may discard all the cards in your hand. if you do, draw that many cards." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2881": { + "card": { + "doublefacedCard": false, + "cardID": 2881, + "alternateCardName": "", + "cardName": "Bladetusk Boar", + "splitCard": false, + "flipCard": false, + "multiverseid": 380379 + }, + "face1": { + "keywordAbilities": [ + "intimidate" + ], + "toughness": "2", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Boar" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{R}", + "levelerCard": false, + "power": "3", + "faceName": "Bladetusk Boar", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "Intimidate (This creature can't be blocked except by artifact creatures and/or creatures that share a color with it.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2882": { + "card": { + "doublefacedCard": false, + "cardID": 2882, + "alternateCardName": "", + "cardName": "Marsh Threader", + "splitCard": false, + "flipCard": false, + "multiverseid": 194718 + }, + "face1": { + "keywordAbilities": [ + "landwalk", "swampwalk" + ], + "toughness": "1", + "colorIndicator": [ + "white" + ], + "subtypes": [ + "Kor", "Scout" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{W}", + "levelerCard": false, + "power": "2", + "faceName": "Marsh Threader", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "Swampwalk (This creature can't be blocked as long as defending player controls a Swamp.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2883": { + "card": { + "doublefacedCard": false, + "cardID": 2883, + "alternateCardName": "", + "cardName": "Canyon Wildca", + "splitCard": false, + "flipCard": false, + "multiverseid": 397566 + }, + "face1": { + "keywordAbilities": [ + "landwalk", "mountainwalk" + ], + "toughness": "1", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Cat" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{R}", + "levelerCard": false, + "power": "2", + "faceName": "Canyon Wildca", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "Mountainwalk (This creature can't be blocked as long as defending player controls a Mountain.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2884": { + "card": { + "doublefacedCard": false, + "cardID": 2884, + "alternateCardName": "", + "cardName": "Elite Cat Warrior", + "splitCard": false, + "flipCard": false, + "multiverseid": 221917 + }, + "face1": { + "keywordAbilities": [ + "landwalk", "forestwalk" + ], + "toughness": "3", + "colorIndicator": [ + "green" + ], + "subtypes": [ + "Cat", "Warrior" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{2}{G}", + "levelerCard": false, + "power": "2", + "faceName": "Elite Cat Warrior", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "Forestwalk (This creature can't be blocked as long as defending player controls a Forest.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2885": { + "card": { + "doublefacedCard": true, + "cardID": 2885, + "alternateCardName": "", + "cardName": "Archangel Avacyn", + "splitCard": false, + "flipCard": false, + "multiverseid": 409741 + }, + "face1": { + "keywordAbilities": [ + "Flying", "Vigilance", "Transform", "Flash" + ], + "toughness": "4", + "colorIndicator": [ + "white" + ], + "subtypes": [ + "Angel" + ], + "supertypes": [ + "legendary" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}{W}{W}", + "levelerCard": false, + "power": "4", + "faceName": "Archangel Avacyn", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "Flash \n Flying, vigilance \n When Archangel Avacyn enters the battlefield, creatures you control gain indestructible until end of turn. \n When a non-Angel creature you control dies, transform Archangel Avacyn at the beginning of the next upkeep" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [ + "flying" + ], + "toughness": "5", + "colorIndicator": [ + "red" + ], + "subtypes": [ + "Angel" + ], + "supertypes": [ + "legendary" + ], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "6", + "faceName": "Avacyn, the Purifier", + "loyalty": "", + "types": [ + "creature" + ], + "abilities": [ + "Flying \n When this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent." + ], + "color": [] + } + }, + "2886": { + "card": { + "doublefacedCard": false, + "cardID": 2886, + "alternateCardName": "", + "cardName": "Immerwolf", + "splitCard": false, + "flipCard": false, + "multiverseid": 262700 + }, + "face1": { + "keywordAbilities": [ + "Intimidate" + ], + "toughness": "2", + "colorIndicator": [ + "green", "red" + ], + "subtypes": [ + "Wolf" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{1}{R}{G}", + "levelerCard": false, + "power": "2", + "faceName": "Immerwolf", + "loyalty": "0", + "types": [ + "creature" + ], + "abilities": [ + "Intimidate (This creature can't be blocked except by artifact creatures and/or creatures that share a color with it.) \n Each other creature you control that's a Wolf or a Werewolf gets +1/+1. \n Non-Human Werewolves you control can't transform." + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + }, + "2887": { + "card": { + "doublefacedCard": false, + "cardID": 2887, + "alternateCardName": "", + "cardName": "Armory Automaton", + "splitCard": false, + "flipCard": false, + "multiverseid": 420668 + }, + "face1": { + "keywordAbilities": [ ], + "toughness": "2", + "colorIndicator": [ ], + "subtypes": [ + "Construct" + ], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "manaCost": "{3}", + "levelerCard": false, + "power": "2", + "faceName": "Armory Automaton", + "loyalty": "0", + "types": [ + "creature", + "artifact" + ], + "abilities": [ + "Whenever Armory Automaton enters the battlefield or attacks, you may attach any number of target Equipment to it. (Control of the Equipment doesn't change.)" + ], + "color": [] + }, + "face2": { + "keywordAbilities": [], + "toughness": "", + "colorIndicator": [], + "subtypes": [], + "supertypes": [], + "levelerAbilities": { + "toughness": "", + "N1": "", + "N2": "", + "power": "", + "abilities": [] + }, + "levelerCard": false, + "power": "", + "faceName": "", + "loyalty": "", + "types": [], + "abilities": [], + "color": [] + } + } +} \ No newline at end of file From 26ec3d158fcf43b3001576cd00f3c0b8faa61d0e Mon Sep 17 00:00:00 2001 From: Zecown Date: Tue, 22 Nov 2022 20:25:59 +0100 Subject: [PATCH 02/14] Modificata parte della regola 514.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aggiunto menù a tendina per selezione delle carte da scartare. --- .../src/main/java/com/magicengine/Game.java | 1 + .../src/main/java/com/magicengine/Player.java | 9 +++ mtgengine/src/main/resources/rules/Rules.drl | 67 +++++++++++++++++-- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/mtgengine/src/main/java/com/magicengine/Game.java b/mtgengine/src/main/java/com/magicengine/Game.java index 6ed9e99..94bf880 100644 --- a/mtgengine/src/main/java/com/magicengine/Game.java +++ b/mtgengine/src/main/java/com/magicengine/Game.java @@ -97,6 +97,7 @@ public class Game { public static final int CHOOSE_STARTING_PLAYER = 1; public static final int TAKE_MULLIGAN = 2; public static final int TAKE_SCRY = 3; + public static final int DISCARD_TO_MAXHANDSIZE = 4; // MULLIGAN TYPES public static final int VANCOUVER_MULLIGAN = 1; diff --git a/mtgengine/src/main/java/com/magicengine/Player.java b/mtgengine/src/main/java/com/magicengine/Player.java index 42c1b82..d443c0c 100644 --- a/mtgengine/src/main/java/com/magicengine/Player.java +++ b/mtgengine/src/main/java/com/magicengine/Player.java @@ -52,6 +52,7 @@ public class Player implements Target { private int mulliganCounter=0; private boolean discarding = false; // true se il giocatore è in fase di scarto + private boolean discardToMaxHandSize = false; // true se il giocatore deve scartare fino ad avere 7 carte in mano private LinkedList attached_by; private LinkedList attby_perm; @@ -496,4 +497,12 @@ public void setDiscarding(boolean discarding) { this.discarding = discarding; } + public boolean isDiscardToMaxHandSize() { + return discardToMaxHandSize; + } + + public void setDiscardToMaxHandSize(boolean discardToMaxHandSize) { + this.discardToMaxHandSize = discardToMaxHandSize; + } + } \ No newline at end of file diff --git a/mtgengine/src/main/resources/rules/Rules.drl b/mtgengine/src/main/resources/rules/Rules.drl index 80f5199..1be517d 100644 --- a/mtgengine/src/main/resources/rules/Rules.drl +++ b/mtgengine/src/main/resources/rules/Rules.drl @@ -4887,21 +4887,78 @@ end rule "514.1" /* -November 19, 2021 +November 22, 2022 First, if the active player�s hand contains more cards than his or her maximum hand size (normally seven), he or she discards enough cards to reduce his or her hand size to that number. This turn-based action doesn�t use the stack.*/ agenda-group "general" -salience 200 +salience 100 dialect "mvel" when $g:Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.BEGIN_TIME_FRAME) - eval($g.currentStep.getObject().name=="cleanup") - $p : Player(hand.size < maxHandSize) from $ac.object + eval($g.currentStep.getObject().name=="cleanup") + //Mettere > ora sta in debugmode + $p : Player(hand.size > maxHandSize) from $ac.object then //il giocatore sceglie quale carta scartare - GameEngine.sendToNode("Il giocatore: " + $p.nickname + " sceglie quali carte scartare"); + $p.discardToMaxHandSize = true; + System.out.println("discrdToMaxHandSize started"); +end + +rule "514.1 selectHandler" +/* +November 22, 2022 +First, if the active player�s hand contains more cards than his or her +maximum hand size (normally seven), he or she discards enough cards to reduce +his or her hand size to that number. This turn-based action doesn�t use the +stack.*/ +agenda-group "general" +salience 200 +dialect "mvel" +when + $g : Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.BEGIN_TIME_FRAME) + $p : Player(discardToMaxHandSize) from $ac.object +then + //il giocatore sceglie quale carta scartare + MakeChoice choice = new MakeChoice(); + choice.idChoice = Game.DISCARD_TO_MAXHANDSIZE; + choice.choiceText = "Choose " + ($p.getHand().size() - $p.getMaxHandSize()) + " card/s to discard"; + //choice.setChoiceOptions($p.getHand()); + //controllare come aggiungere direttamente la lista + for(Card card : $p.hand) { + choice.addOption(card.magicTargetId, card.getNameAsString()); + } + GameEngine.sendToNode(choice, Game.CHOICE, Game.MULTIPLE_CHOICE, $p.id); + //$p.discardToMaxHandSize = false; + //update($g); +end + +rule "514.1 answerHandler" +/* +November 22, 2022 +First, if the active player�s hand contains more cards than his or her +maximum hand size (normally seven), he or she discards enough cards to reduce +his or her hand size to that number. This turn-based action doesn�t use the +stack.*/ +agenda-group "general" +salience 300 +dialect "mvel" +when + $g : Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.BEGIN_TIME_FRAME) + $p : Player(discardToMaxHandSize) from $ac.object + $ca: ChoiceAnswer(idChoice == Game.DISCARD_TO_MAXHANDSIZE, idPlayer == $p.id) +then + //Cerco e rimuovo le carte dalla mano del giocatore + for(Card card : $p.hand){ + if(card.magicTargetId == $ca.getIdOptions().get(0)){ + $p.hand.remove(card); + $ca.getIdOptions().removeFirst(); + } + } + $p.discardToMaxHandSize = false; + retract($ca); + update($g); end rule "514.2" From f5e69591dba1cd184ffb64e010e7d69e060371a7 Mon Sep 17 00:00:00 2001 From: Zecown Date: Fri, 23 Dec 2022 14:08:46 +0100 Subject: [PATCH 03/14] Aggiornamento Regola Aggiunto timeStep CleanUp --- .../src/main/java/com/magicengine/Game.java | 1 + .../src/main/java/com/magicengine/Player.java | 9 ++++ .../src/main/java/com/magicengine/Zone.java | 4 ++ mtgengine/src/main/resources/rules/Rules.drl | 48 +++++++++++++------ 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/mtgengine/src/main/java/com/magicengine/Game.java b/mtgengine/src/main/java/com/magicengine/Game.java index 94bf880..5589331 100644 --- a/mtgengine/src/main/java/com/magicengine/Game.java +++ b/mtgengine/src/main/java/com/magicengine/Game.java @@ -71,6 +71,7 @@ public class Game { public static final int DURING_TIME_FRAME = 2; public static final int END_TIME_FRAME = 0; public static final int ABSENT_TIME_FRAME = -1; + public static final int CLEANUP_TIME_FRAME = 3; // STAGE public static final int STARTING_STAGE = 0; diff --git a/mtgengine/src/main/java/com/magicengine/Player.java b/mtgengine/src/main/java/com/magicengine/Player.java index d443c0c..530eff3 100644 --- a/mtgengine/src/main/java/com/magicengine/Player.java +++ b/mtgengine/src/main/java/com/magicengine/Player.java @@ -53,6 +53,7 @@ public class Player implements Target { private boolean discarding = false; // true se il giocatore è in fase di scarto private boolean discardToMaxHandSize = false; // true se il giocatore deve scartare fino ad avere 7 carte in mano + private boolean discardToMaxHandSize_state = true; private LinkedList attached_by; private LinkedList attby_perm; @@ -505,4 +506,12 @@ public void setDiscardToMaxHandSize(boolean discardToMaxHandSize) { this.discardToMaxHandSize = discardToMaxHandSize; } + public boolean isDiscardToMaxHandSize_state() { + return discardToMaxHandSize_state; + } + + public void setDiscardToMaxHandSize_state(boolean discardToMaxHandSize_state) { + this.discardToMaxHandSize_state = discardToMaxHandSize_state; + } + } \ No newline at end of file diff --git a/mtgengine/src/main/java/com/magicengine/Zone.java b/mtgengine/src/main/java/com/magicengine/Zone.java index 6528679..3dccd61 100644 --- a/mtgengine/src/main/java/com/magicengine/Zone.java +++ b/mtgengine/src/main/java/com/magicengine/Zone.java @@ -24,6 +24,10 @@ public Zone(String nameZone, boolean isPublic,int magicTargetId) { this.magicTargetId=magicTargetId; } + public void remove_elem(int magicTargetId) { + MagicObject tmp = new MagicObject(magicTargetId); + super.remove(tmp); + } public int getMagicTargetId() { return magicTargetId; diff --git a/mtgengine/src/main/resources/rules/Rules.drl b/mtgengine/src/main/resources/rules/Rules.drl index 1be517d..c86517a 100644 --- a/mtgengine/src/main/resources/rules/Rules.drl +++ b/mtgengine/src/main/resources/rules/Rules.drl @@ -4893,17 +4893,28 @@ maximum hand size (normally seven), he or she discards enough cards to reduce his or her hand size to that number. This turn-based action doesn�t use the stack.*/ agenda-group "general" -salience 100 +salience 300 dialect "mvel" when $g:Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.BEGIN_TIME_FRAME) eval($g.currentStep.getObject().name=="cleanup") + //Mettere > ora sta in debugmode - $p : Player(hand.size > maxHandSize) from $ac.object + //TODO:: Prendere entrambi non solo quello attivo + $p : Player(hand.size > maxHandSize) from $ac.object + //$p : Player(hand.size <= maxHandSize) from $ac.object + //$p : Player($id : id) from $ac.object + //eval($p.hand.size < $p.maxHandSize) then //il giocatore sceglie quale carta scartare $p.discardToMaxHandSize = true; - System.out.println("discrdToMaxHandSize started"); + $g.stepTimeFrame = Game.CLEANUP_TIME_FRAME; + System.out.println("**discrdToMaxHandSize started"); + System.out.println("**Player attivo" + $p); + System.out.println($p.hand.size); + System.out.println($p.maxHandSize); + System.out.println("**DIO PORCO"); + update($g); end rule "514.1 selectHandler" @@ -4917,9 +4928,10 @@ agenda-group "general" salience 200 dialect "mvel" when - $g : Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.BEGIN_TIME_FRAME) - $p : Player(discardToMaxHandSize) from $ac.object + $g : Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.CLEANUP_TIME_FRAME) + $p : Player(discardToMaxHandSize && discardToMaxHandSize_state) from $ac.object then + System.out.println("**Creo la MakeChoice"); //il giocatore sceglie quale carta scartare MakeChoice choice = new MakeChoice(); choice.idChoice = Game.DISCARD_TO_MAXHANDSIZE; @@ -4930,8 +4942,10 @@ then choice.addOption(card.magicTargetId, card.getNameAsString()); } GameEngine.sendToNode(choice, Game.CHOICE, Game.MULTIPLE_CHOICE, $p.id); + System.out.println("**Ho mandato la MakeChoice"); + $p.discardToMaxHandSize_state = false; //$p.discardToMaxHandSize = false; - //update($g); + update($g); end rule "514.1 answerHandler" @@ -4942,22 +4956,28 @@ maximum hand size (normally seven), he or she discards enough cards to reduce his or her hand size to that number. This turn-based action doesn�t use the stack.*/ agenda-group "general" -salience 300 +salience 100 dialect "mvel" when - $g : Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.BEGIN_TIME_FRAME) - $p : Player(discardToMaxHandSize) from $ac.object + $g : Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.CLEANUP_TIME_FRAME) + $p : Player(discardToMaxHandSize && !discardToMaxHandSize_state) from $ac.object $ca: ChoiceAnswer(idChoice == Game.DISCARD_TO_MAXHANDSIZE, idPlayer == $p.id) then + System.out.println("**Ho scelto la carta da scartare"); //Cerco e rimuovo le carte dalla mano del giocatore - for(Card card : $p.hand){ - if(card.magicTargetId == $ca.getIdOptions().get(0)){ - $p.hand.remove(card); - $ca.getIdOptions().removeFirst(); - } + System.out.println($ca.getIdOptions()); + System.out.println($ca.getIdOptions().get(0).getClass().getName()); + for(String elem : $ca.getIdOptions()){ + System.out.println(elem); + $p.hand.remove_elem(Integer.valueOf(elem)); } + System.out.println("**Sono fuori dal FOR"); $p.discardToMaxHandSize = false; + System.out.println("**Fine fase di scartamento"); + $p.discardToMaxHandSize_state = true; + $g.stepTimeFrame = Game.BEGIN_TIME_FRAME; retract($ca); + update($p); update($g); end From 928832d0d3159357028382b1140b4f390314188c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Fri, 23 Dec 2022 16:02:09 +0100 Subject: [PATCH 04/14] Possibile Fix Trovato un possibile fix alla regole. Testato brevemente e sembra funzionare. --- .../src/main/java/com/magicengine/Zone.java | 13 ++++++-- mtgengine/src/main/resources/rules/Rules.drl | 33 +++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/mtgengine/src/main/java/com/magicengine/Zone.java b/mtgengine/src/main/java/com/magicengine/Zone.java index 3dccd61..ae89312 100644 --- a/mtgengine/src/main/java/com/magicengine/Zone.java +++ b/mtgengine/src/main/java/com/magicengine/Zone.java @@ -24,9 +24,16 @@ public Zone(String nameZone, boolean isPublic,int magicTargetId) { this.magicTargetId=magicTargetId; } - public void remove_elem(int magicTargetId) { - MagicObject tmp = new MagicObject(magicTargetId); - super.remove(tmp); + public boolean remove(String magicTargetId) { + int magicTargetIdint = Integer.valueOf(magicTargetId); + + for (MagicObject card : this) { + if (card.getMagicTargetId() == magicTargetIdint) { + return remove(card); + } + } + + return false; } public int getMagicTargetId() { diff --git a/mtgengine/src/main/resources/rules/Rules.drl b/mtgengine/src/main/resources/rules/Rules.drl index c86517a..d35684f 100644 --- a/mtgengine/src/main/resources/rules/Rules.drl +++ b/mtgengine/src/main/resources/rules/Rules.drl @@ -4896,12 +4896,12 @@ agenda-group "general" salience 300 dialect "mvel" when - $g:Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.BEGIN_TIME_FRAME) + $g:Game(stage == Game.GAME_STAGE, $players : players, stepTimeFrame == Game.BEGIN_TIME_FRAME) eval($g.currentStep.getObject().name=="cleanup") //Mettere > ora sta in debugmode //TODO:: Prendere entrambi non solo quello attivo - $p : Player(hand.size > maxHandSize) from $ac.object + $p : Player(hand.size > maxHandSize) from $players //$p : Player(hand.size <= maxHandSize) from $ac.object //$p : Player($id : id) from $ac.object //eval($p.hand.size < $p.maxHandSize) @@ -4928,8 +4928,8 @@ agenda-group "general" salience 200 dialect "mvel" when - $g : Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.CLEANUP_TIME_FRAME) - $p : Player(discardToMaxHandSize && discardToMaxHandSize_state) from $ac.object + $g : Game(stage == Game.GAME_STAGE, $players : players, stepTimeFrame == Game.CLEANUP_TIME_FRAME) + $p : Player(discardToMaxHandSize && discardToMaxHandSize_state) from $players then System.out.println("**Creo la MakeChoice"); //il giocatore sceglie quale carta scartare @@ -4959,18 +4959,25 @@ agenda-group "general" salience 100 dialect "mvel" when - $g : Game(stage == Game.GAME_STAGE, $ac : activePlayer, stepTimeFrame == Game.CLEANUP_TIME_FRAME) - $p : Player(discardToMaxHandSize && !discardToMaxHandSize_state) from $ac.object + $g : Game(stage == Game.GAME_STAGE, $players : players, stepTimeFrame == Game.CLEANUP_TIME_FRAME) + $p : Player(discardToMaxHandSize && !discardToMaxHandSize_state) from $players $ca: ChoiceAnswer(idChoice == Game.DISCARD_TO_MAXHANDSIZE, idPlayer == $p.id) then System.out.println("**Ho scelto la carta da scartare"); //Cerco e rimuovo le carte dalla mano del giocatore System.out.println($ca.getIdOptions()); System.out.println($ca.getIdOptions().get(0).getClass().getName()); - for(String elem : $ca.getIdOptions()){ - System.out.println(elem); - $p.hand.remove_elem(Integer.valueOf(elem)); + + // elimina le carte solo se è stato scelto il numero corretto di carte + if ($ca.getIdOptions().size() == ($p.getHand().size() - $p.getMaxHandSize())) { + for(String cardId : $ca.getIdOptions()) { + System.out.println(cardId); + $p.hand.remove(cardId); + } + } else { + System.out.println("Il giocatore ha scelto un numero errato di carte !!"); } + System.out.println("**Sono fuori dal FOR"); $p.discardToMaxHandSize = false; System.out.println("**Fine fase di scartamento"); @@ -8137,8 +8144,8 @@ rule "702.31b" A creature with horsemanship can't be blocked by creatures without horsemanship. A creature with horsemanship can block a creature with or without horsemanship. - se il bloccate ha horsemanship può bloccare creature con o senza horsemanship - un attaccante con horsemanship non può essere bloccato da creature senza horsemanship + se il bloccate ha horsemanship pu� bloccare creature con o senza horsemanship + un attaccante con horsemanship non pu� essere bloccato da creature senza horsemanship */ dialect "mvel" no-loop true @@ -8162,7 +8169,7 @@ agenda-group "general" for (Permanent attacker : blocker.blockedCreatures.listReference) { // ... se almeno 1 attaccante ha shadow ... if (attacker.checkKeywordAbility("horsemanship")){ - // ... il blocco non è valido + // ... il blocco non � valido System.out.println("Blocco non valido!"); $g.blockValid = false; } @@ -8216,7 +8223,7 @@ agenda-group "general" boolean typeArtifactCreatureFear = false; // Per ogni bloccante ... for (Permanent blocker : $blockers) { - // ... se non è di tipo artefatto o non è di colore nero... + // ... se non � di tipo artefatto o non � di colore nero... //if (! blocker.getCardType().toString() == "artifact creature" or ! blocker.getColorIndicator("black")) // ... controllo le creature che blocca ... for (Permanent attacker : blocker.blockedCreatures.listReference) { From 005d0512a76d086946ed3ce9fb608e78c013f262 Mon Sep 17 00:00:00 2001 From: Zecown Date: Fri, 23 Dec 2022 18:04:55 +0100 Subject: [PATCH 05/14] Rule 514.1 fixed Regola funzionante e testata. --- .../src/main/java/com/magicengine/Game.java | 2 +- .../src/main/java/com/magicengine/Player.java | 14 ++-- .../src/main/java/com/magicengine/Zone.java | 6 ++ mtgengine/src/main/resources/rules/Rules.drl | 77 +++++++++---------- 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/mtgengine/src/main/java/com/magicengine/Game.java b/mtgengine/src/main/java/com/magicengine/Game.java index 5589331..7d7e0b6 100644 --- a/mtgengine/src/main/java/com/magicengine/Game.java +++ b/mtgengine/src/main/java/com/magicengine/Game.java @@ -71,7 +71,7 @@ public class Game { public static final int DURING_TIME_FRAME = 2; public static final int END_TIME_FRAME = 0; public static final int ABSENT_TIME_FRAME = -1; - public static final int CLEANUP_TIME_FRAME = 3; + public static final int CLEANUP_TIME_FRAME = 3; //Frame per aspettare la scelta dell'utente alla regola 514.1 // STAGE public static final int STARTING_STAGE = 0; diff --git a/mtgengine/src/main/java/com/magicengine/Player.java b/mtgengine/src/main/java/com/magicengine/Player.java index 530eff3..cab83e1 100644 --- a/mtgengine/src/main/java/com/magicengine/Player.java +++ b/mtgengine/src/main/java/com/magicengine/Player.java @@ -51,9 +51,11 @@ public class Player implements Target { //numero dei mulligan usati dal giocatore private int mulliganCounter=0; - private boolean discarding = false; // true se il giocatore è in fase di scarto + private boolean discarding = false; // true se il giocatore è in fase di scarto mulligan + + //Variabili per la gestione della regola 514.1 private boolean discardToMaxHandSize = false; // true se il giocatore deve scartare fino ad avere 7 carte in mano - private boolean discardToMaxHandSize_state = true; + private boolean beginningDiscardingToMaxHandSize = true; // serve per impedire di creare infinite choiceAnswer private LinkedList attached_by; private LinkedList attby_perm; @@ -506,12 +508,12 @@ public void setDiscardToMaxHandSize(boolean discardToMaxHandSize) { this.discardToMaxHandSize = discardToMaxHandSize; } - public boolean isDiscardToMaxHandSize_state() { - return discardToMaxHandSize_state; + public boolean isBeginningDiscardingToMaxHandSize() { + return beginningDiscardingToMaxHandSize; } - public void setDiscardToMaxHandSize_state(boolean discardToMaxHandSize_state) { - this.discardToMaxHandSize_state = discardToMaxHandSize_state; + public void setBeginningDiscardingToMaxHandSize(boolean beginningDiscardingToMaxHandSize) { + this.beginningDiscardingToMaxHandSize = beginningDiscardingToMaxHandSize; } } \ No newline at end of file diff --git a/mtgengine/src/main/java/com/magicengine/Zone.java b/mtgengine/src/main/java/com/magicengine/Zone.java index ae89312..a4c0486 100644 --- a/mtgengine/src/main/java/com/magicengine/Zone.java +++ b/mtgengine/src/main/java/com/magicengine/Zone.java @@ -24,6 +24,12 @@ public Zone(String nameZone, boolean isPublic,int magicTargetId) { this.magicTargetId=magicTargetId; } + /** + * Rimuove una carta dalla Zona prendendo come parametro il magicTargetId della carta da rimuovere. + * @param magicTargetId Id della carta da rimuovere + * @return true se la carta è stata eliminata, altrimenti false + * @author Nicolò Posta, Tommaso Romani, Nicolò Vescera, Fabrizio Fagiolo, Cristian Cosci. + */ public boolean remove(String magicTargetId) { int magicTargetIdint = Integer.valueOf(magicTargetId); diff --git a/mtgengine/src/main/resources/rules/Rules.drl b/mtgengine/src/main/resources/rules/Rules.drl index d35684f..2e0331d 100644 --- a/mtgengine/src/main/resources/rules/Rules.drl +++ b/mtgengine/src/main/resources/rules/Rules.drl @@ -4888,100 +4888,95 @@ end rule "514.1" /* November 22, 2022 -First, if the active player�s hand contains more cards than his or her +First, if the active players hand contains more cards than his or her maximum hand size (normally seven), he or she discards enough cards to reduce -his or her hand size to that number. This turn-based action doesn�t use the -stack.*/ +his or her hand size to that number. This turn-based action doesnt use the +stack. + +Edited by Nicolò Posta, Tommaso Romani, Nicolò Vescera, Fabrizio Fagiolo, Cristian Cosci. +*/ agenda-group "general" salience 300 dialect "mvel" when $g:Game(stage == Game.GAME_STAGE, $players : players, stepTimeFrame == Game.BEGIN_TIME_FRAME) eval($g.currentStep.getObject().name=="cleanup") - - //Mettere > ora sta in debugmode - //TODO:: Prendere entrambi non solo quello attivo + $p : Player(hand.size > maxHandSize) from $players - //$p : Player(hand.size <= maxHandSize) from $ac.object - //$p : Player($id : id) from $ac.object - //eval($p.hand.size < $p.maxHandSize) then - //il giocatore sceglie quale carta scartare + //passa alla fase di scarto delle carte in eccesso $p.discardToMaxHandSize = true; $g.stepTimeFrame = Game.CLEANUP_TIME_FRAME; - System.out.println("**discrdToMaxHandSize started"); - System.out.println("**Player attivo" + $p); - System.out.println($p.hand.size); - System.out.println($p.maxHandSize); - System.out.println("**DIO PORCO"); + System.out.println("514.1 -> discrdToMaxHandSize started"); update($g); end rule "514.1 selectHandler" /* November 22, 2022 -First, if the active player�s hand contains more cards than his or her +First, if the active players hand contains more cards than his or her maximum hand size (normally seven), he or she discards enough cards to reduce -his or her hand size to that number. This turn-based action doesn�t use the -stack.*/ +his or her hand size to that number. This turn-based action doesnt use the +stack. + +Edited by Nicolò Posta, Tommaso Romani, Nicolò Vescera, Fabrizio Fagiolo, Cristian Cosci. +*/ agenda-group "general" salience 200 dialect "mvel" when $g : Game(stage == Game.GAME_STAGE, $players : players, stepTimeFrame == Game.CLEANUP_TIME_FRAME) - $p : Player(discardToMaxHandSize && discardToMaxHandSize_state) from $players + $p : Player(discardToMaxHandSize && beginningDiscardingToMaxHandSize) from $players then - System.out.println("**Creo la MakeChoice"); - //il giocatore sceglie quale carta scartare + //crea la MakeChoise da inviare al Node MakeChoice choice = new MakeChoice(); choice.idChoice = Game.DISCARD_TO_MAXHANDSIZE; choice.choiceText = "Choose " + ($p.getHand().size() - $p.getMaxHandSize()) + " card/s to discard"; - //choice.setChoiceOptions($p.getHand()); - //controllare come aggiungere direttamente la lista for(Card card : $p.hand) { choice.addOption(card.magicTargetId, card.getNameAsString()); } + + //invia la choice al server GameEngine.sendToNode(choice, Game.CHOICE, Game.MULTIPLE_CHOICE, $p.id); - System.out.println("**Ho mandato la MakeChoice"); - $p.discardToMaxHandSize_state = false; - //$p.discardToMaxHandSize = false; + + //Impedisce che questa regola venga avviata molteplici volte mentre il giocatore aspetta di fare la scelta + $p.beginningDiscardingToMaxHandSize = false; update($g); end rule "514.1 answerHandler" /* November 22, 2022 -First, if the active player�s hand contains more cards than his or her +First, if the active players hand contains more cards than his or her maximum hand size (normally seven), he or she discards enough cards to reduce -his or her hand size to that number. This turn-based action doesn�t use the -stack.*/ +his or her hand size to that number. This turn-based action doesnt use the +stack. + +Edited by Nicolò Posta, Tommaso Romani, Nicolò Vescera, Fabrizio Fagiolo, Cristian Cosci. +*/ agenda-group "general" salience 100 dialect "mvel" when $g : Game(stage == Game.GAME_STAGE, $players : players, stepTimeFrame == Game.CLEANUP_TIME_FRAME) - $p : Player(discardToMaxHandSize && !discardToMaxHandSize_state) from $players + $p : Player(discardToMaxHandSize && !beginningDiscardingToMaxHandSize) from $players $ca: ChoiceAnswer(idChoice == Game.DISCARD_TO_MAXHANDSIZE, idPlayer == $p.id) then - System.out.println("**Ho scelto la carta da scartare"); - //Cerco e rimuovo le carte dalla mano del giocatore - System.out.println($ca.getIdOptions()); - System.out.println($ca.getIdOptions().get(0).getClass().getName()); - - // elimina le carte solo se è stato scelto il numero corretto di carte + + //elimina le carte solo se è stato scelto il numero corretto di carte if ($ca.getIdOptions().size() == ($p.getHand().size() - $p.getMaxHandSize())) { for(String cardId : $ca.getIdOptions()) { - System.out.println(cardId); $p.hand.remove(cardId); } } else { - System.out.println("Il giocatore ha scelto un numero errato di carte !!"); + //avvisa il player della scelta errata della carte da scartare + System.out.println("514.1 -> Il giocatore ha scelto un numero errato di carte !!"); + GameEngine.sendToNode("Il giocatore " + $p.nickname + " ha scelto un numero errato di carte !!"); } - System.out.println("**Sono fuori dal FOR"); + //reset delle varibili allo stato iniziale per passare alle fasi successive del gioco $p.discardToMaxHandSize = false; - System.out.println("**Fine fase di scartamento"); - $p.discardToMaxHandSize_state = true; + $p.beginningDiscardingToMaxHandSize = true; $g.stepTimeFrame = Game.BEGIN_TIME_FRAME; retract($ca); update($p); From 76d3d1aeefbd0c3cb45e2fde0b4a72fcc010ff0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Tue, 22 Nov 2022 19:45:57 +0100 Subject: [PATCH 06/14] Client Disconnect Bug Fix Risolto il problema che causava il crash di tutto quando uno giocatore (o debugger) lasciava il gioco. E' ancora migliorabile !! --- mtghub/server.js | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/mtghub/server.js b/mtghub/server.js index 3faaff2..7e67389 100644 --- a/mtghub/server.js +++ b/mtghub/server.js @@ -120,15 +120,33 @@ function onClientDisconnect(){ return; } - console.log("Player has disconnected " + this.id); - - //send request "close stream" to java server + // inizializzo il messaggio di avviso disconnessione + var msg = `Player has disconnected ${this.id}`; + + console.debug(socket2player[this.id]); + + //send request "close stream" to java server var playerRoom = socket2player[this.id].room; jSocket = room2jsocket[playerRoom]; - jSocket.sendMessage({"exit": removePlayerId}); + + // player disconnection + this.leave(room); + + // special message and killing debugger + if (this.id === idDebugger) { + idDebugger = null; + msg = 'Debugger has disconnected'; + } + + // emit the message + console.log(msg); + io.sockets.in(room).emit('messaggio', msg); //delete player from maps delete socket2player[this.id]; + + //if (numconn <= 0) jSocket.sendMessage({"exit": removePlayerId}); + }; // Client send Attemtp message @@ -163,7 +181,8 @@ function onNewDebugger(numPlayer){ //Max num player parameter from CreateRoom.ht //var roomSize = getSizeRoom(room.toString()); socket2player[this.id] = newDebugger; idDebugger=this.id; - console.log("debugger connected"); + + console.log("debugger connected " + this.id); io.sockets.in(room).emit('messaggio', "Debugger join to the room..."); }; @@ -339,7 +358,7 @@ function onData(data){ }else{ */ messages[message.id] = messages[message.id] + message.data; - sendToPlayer(message.idPlayer, messages[message.id]); + sendToPlayer(message.idPlayer, messages[message.id]); delete messages[message.id]; } @@ -790,6 +809,7 @@ function sendToDebugger(data) { }; function onClose(){ + // Dovrebbe chiudere il socket o roba del genere ?? console.log('Connection from java closed'); }; From f017c44b55142109b30c16a49fc59389f2df9af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Wed, 23 Nov 2022 18:24:54 +0100 Subject: [PATCH 07/14] Debug Print & Room Update Aggiunte stampe per il debug Il nome della stanza viene modificato quando un client crasha. --- mtghub/server.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mtghub/server.js b/mtghub/server.js index 7e67389..10dd454 100644 --- a/mtghub/server.js +++ b/mtghub/server.js @@ -142,6 +142,9 @@ function onClientDisconnect(){ console.log(msg); io.sockets.in(room).emit('messaggio', msg); + // create new room + room += '1'; + //delete player from maps delete socket2player[this.id]; @@ -201,7 +204,10 @@ function onNewPlayer(numPlayer){ //Max num player parameter from CreateRoom.html //ToDO: choice ROOM this.join(room); //fill the maps - var newPlayer = new Player(room,numconn); + + console.debug(room); + + var newPlayer = new Player(room, numconn); socket2player[this.id] = newPlayer; //ToDo: handle more room @@ -224,7 +230,6 @@ function onNewPlayer(numPlayer){ //Max num player parameter from CreateRoom.html javaSocket.sendMessage(JSON_PLAYERS); //fill the map room2jsocket[room]=javaSocket; - } if(roomSize>PLAYERS_NUMBER){ @@ -243,6 +248,8 @@ function onReady(data){ console.log("id who pressed " + socket2player[this.id].id); data.playerSettings.playerInfo.id = socket2player[this.id].id; console.log("player " + this.id + " is ready to play"); + + console.debug('Room: ' + room); console.log(data); var playerRoom = socket2player[this.id].room; From 12b4553da133fb2ebeae27881c96b8e5f9dcc8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Fri, 25 Nov 2022 21:46:43 +0100 Subject: [PATCH 08/14] Probabile Fix per Client Disconnect Probabilmente abbiamo risolto il problema che causava il crash del server node quando un client si disconnetteva. La comunicazione con il GameEngine (da parte di Node) non era gestita bene. Indentato il codice server.js Ora quando un client si disconnette tutti gli altri client della room vengono disconnessi. Co-authored-by: F-A-B-R-I-Z-I-O --- .../main/java/com/magicengine/GameEngine.java | 20 +- mtggameinterface/script/clientL.js | 9 +- mtghub/server.js | 1285 +++++++++-------- 3 files changed, 682 insertions(+), 632 deletions(-) diff --git a/mtgengine/src/main/java/com/magicengine/GameEngine.java b/mtgengine/src/main/java/com/magicengine/GameEngine.java index dea186b..4f7c5f7 100644 --- a/mtgengine/src/main/java/com/magicengine/GameEngine.java +++ b/mtgengine/src/main/java/com/magicengine/GameEngine.java @@ -213,12 +213,29 @@ public static void sendToNode(Object toSend, int messageType, int messageSubtype void exit(String json) { int idPlayer = gson.fromJson(json, int.class); for (Player p : game.getPriorityOrder()) { - if (p.getId() == idPlayer) { + if (p.getId() == idPlayer) { //TODO: forse questa condizione deve essere invertita sendToNode("player " + p.getNickname() + " has disconnected, VICTORY!"); break; } } } + +// /** +// * Questa funzione, quando viene chiamata dal server Node, resetta il gioco +// * dato che un giocatore ha abbandonato la partita. +// * @author Nicolò Vescera +// * +// * @param json Id del giocatore che ha abbandonato il gioco (ricevuto da Node) +// */ +// void resetGame(String json) { +// int idPlayer = gson.fromJson(json, int.class); +// for (Player p : game.getPriorityOrder()) { +// if (p.getId() != idPlayer) { +// sendToNode("player " + p.getNickname() + " has disconnected, Game will be resetted!"); +// //break; +// } +// } +// } void initPlayer(String json) { boolean isNewPlayer = false; @@ -417,6 +434,7 @@ public void run() { exit(received); break; } + // JSON "ATTEMPT" received = getJson(cleanJson, "attempt"); if (received != null) { diff --git a/mtggameinterface/script/clientL.js b/mtggameinterface/script/clientL.js index 6552863..9946a6b 100644 --- a/mtggameinterface/script/clientL.js +++ b/mtggameinterface/script/clientL.js @@ -321,6 +321,12 @@ window.onload = function() { socket.on('messaggio', function(data) { onMessage(data); }); + + socket.on('disconnect', function(data) { + disconnectFunction(); + alert(data); + //console.log(data); + }); }; //BUTTON START GAME @@ -368,7 +374,7 @@ window.onload = function() { }; //BUTTON DISCONNECT - disconnect.onclick = function() { + function disconnectFunction() { document.getElementById("message").textContent = "disconnected"; console.log("DISCONNECTED"); socket.disconnect(); @@ -376,6 +382,7 @@ window.onload = function() { $('.create_room').show(); $('.in_room').hide(); } + disconnect.onclick = disconnectFunction; /************************************************** ** OTHER FUNCTIONS ** diff --git a/mtghub/server.js b/mtghub/server.js index 10dd454..a3bf695 100644 --- a/mtghub/server.js +++ b/mtghub/server.js @@ -1,401 +1,418 @@ /************************************************** -** NODE.JS REQUIREMENTS -**************************************************/ - -var app = require('express')(), - server = require('http').createServer(app), - io = require('socket.io').listen(server), - net = require('net'), - JsonSocket = require('json-socket'); + ** NODE.JS REQUIREMENTS + **************************************************/ +var app = require("express")(), + server = require("http").createServer(app), + io = require("socket.io").listen(server), + net = require("net"), + JsonSocket = require("json-socket"); var Player = require("./Player").Player; -var Debugger = require("./Player").Debugger;//Player class - - - /************************************************** -** GLOBAL SETTINGS -**************************************************/ -var SERVER_PORT=2012; //port number for html client -var JAVA_SERVER_PORT=2013; //java port number -var JAVA_SERVER_ADDR='127.0.0.1'; //java address -var PLAYERS_NUMBER=2; //max number of players -var JSON_PLAYERS={"playersNumber": PLAYERS_NUMBER}; //JSON alerts java on players number, it sended when room is full -var JSON_START_GAME={"startGame":"true"}; -var JSON_SAVE_GAME={"saveGame":"true"}; -var JSON_DEBUG_ON={"debugOn":"true"}; -var JSON_DEBUG_OFF={"debugOff":"true"}; - - /************************************************** -** GLOBAL VARIABLES -**************************************************/ -var numconn=0; //number of player connected to Node.js -var socketGuest; //socket of guest player -var room="magicRoom"; //room of game -var jSocket; //java socket +var Debugger = require("./Player").Debugger; //Player class + +/************************************************** + ** GLOBAL SETTINGS + **************************************************/ +var SERVER_PORT = 2012; //port number for html client +var JAVA_SERVER_PORT = 2013; //java port number +var JAVA_SERVER_ADDR = "127.0.0.1"; //java address +var PLAYERS_NUMBER = 2; //max number of players +var JSON_PLAYERS = { playersNumber: PLAYERS_NUMBER }; //JSON alerts java on players number, it sended when room is full +var JSON_START_GAME = { startGame: "true" }; +var JSON_SAVE_GAME = { saveGame: "true" }; +var JSON_DEBUG_ON = { debugOn: "true" }; +var JSON_DEBUG_OFF = { debugOff: "true" }; + +/************************************************** + ** GLOBAL VARIABLES + **************************************************/ +var numconn = 0; //number of player connected to Node.js +var socketGuest; //socket of guest player +var room = "magicRoom"; //room of game +var jSocket; //java socket //maps -var room2jsocket={}; //KEYS: room of game, VALUE: java socket -var socket2player={}; //KEYS: html socket, VALUE: player object +var room2jsocket = {}; //KEYS: room of game, VALUE: java socket +var socket2player = {}; //KEYS: html socket, VALUE: player object //messages -var messages={}; //save the incoming incomplete messages -var prevData=""; -var idDebugger=null; +var messages = {}; //save the incoming incomplete messages +var prevData = ""; +var idDebugger = null; /************************************************** -** SERVER INITIALISATION -**************************************************/ -function init(){ - // Set up Socket.IO to listen on port SERVER_PORT - server.listen(SERVER_PORT, () => { - console.log('listening on: ' + SERVER_PORT); - }); - - // Start listening for events - setServerEventHandlers(); + ** SERVER INITIALISATION + **************************************************/ +function init() { + // Set up Socket.IO to listen on port SERVER_PORT + server.listen(SERVER_PORT, () => { + console.log("listening on: " + SERVER_PORT); + }); + + // Start listening for events + setServerEventHandlers(); +} + +/************************************************** + ** SERVER EVENT HANDLERS + **************************************************/ +var setServerEventHandlers = function () { + io.sockets.on("connection", onServerSocketConnection); }; +// New socket Server connection +function onServerSocketConnection(htmlClient) { + //Listen for client choice + htmlClient.on("choice", onChoice); -/************************************************** -** SERVER EVENT HANDLERS -**************************************************/ -var setServerEventHandlers = function(){ - io.sockets.on('connection',onServerSocketConnection); -}; + // Listen for client disconnected + htmlClient.on("disconnect", onClientDisconnect); + // Listen for client Attempt message + htmlClient.on("attempt", onAttempt); + // Listen for new player message + htmlClient.on("new player", onNewPlayer); -// New socket Server connection -function onServerSocketConnection(htmlClient){ - - //Listen for client choice - htmlClient.on("choice", onChoice); - - // Listen for client disconnected - htmlClient.on("disconnect", onClientDisconnect); - - // Listen for client Attempt message - htmlClient.on("attempt", onAttempt); - - // Listen for new player message - htmlClient.on("new player", onNewPlayer); - htmlClient.on("new debugger", onNewDebugger); - - // Listen for client settings - htmlClient.on("ready", onReady); - - //Listen for start game - htmlClient.on("start game", onStartGame); - - //Listen for receive zone - htmlClient.on("receive zone", onReceiveZone); - - //Listen for receive game - htmlClient.on("receive game", onReceiveGame); - - //Listen for save game - htmlClient.on("save game", onSaveGame); - + + // Listen for client settings + htmlClient.on("ready", onReady); + + //Listen for start game + htmlClient.on("start game", onStartGame); + + //Listen for receive zone + htmlClient.on("receive zone", onReceiveZone); + + //Listen for receive game + htmlClient.on("receive game", onReceiveGame); + + //Listen for save game + htmlClient.on("save game", onSaveGame); + //Listen for the Debugger - htmlClient.on("DebugSet", setPositionDebug); - + htmlClient.on("DebugSet", setPositionDebug); + //Listen for the ModificatioOn htmlClient.on("DebugOn", onDebugOn); - + //Listen for the ModificatioOff htmlClient.on("DebugOff", onDebugOff); - //Listen for the Modify into players + //Listen for the Modify into players htmlClient.on("DebugSetToPlayer", setPlayerDatas); - +} - -}; -// Socket client has disconnected -function onClientDisconnect(){ - numconn--; - - if(socketGuest && this.id == socketGuest.id){ - console.log("Guest Player has disconnected " + this.id); - return; - } - +/** + * Socket client has disconnected + * Questa funzione gestisce la disconnessione di un Client HTML + * Se è un debugger a disconnettersi: + * - elimina tutte le informazioni salvate su di lui + * - avvisa tutti quelli nella room che il debugger è uscito + * - lascia effettivamente la stanza + * + * Se è un player che sta abbandonando: + * - oltre a fare le stesse cose che fa il debug, avvisa java + * di terminare il GameEngine relativo alla partita e + * avvisa tutti gli altri client connessi di disconnettersi + * + * @author Fabrizio Fagiolo, Nicolò Vescera + */ +function onClientDisconnect() { + numconn--; + + if (socketGuest && this.id == socketGuest.id) { + console.log("Guest Player has disconnected " + this.id); + return; + } + + // se TRUE, un player si sta disconnettendo + // se FALSe, il debugger si sta disconnettendo + var isPlayerDisconnecting = true; + // inizializzo il messaggio di avviso disconnessione var msg = `Player has disconnected ${this.id}`; - + console.debug(socket2player[this.id]); - + //send request "close stream" to java server - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; + var playerRoom = socket2player[this.id].room; + var playerId = socket2player[this.id].id; + jSocket = room2jsocket[playerRoom]; // player disconnection - this.leave(room); - - // special message and killing debugger + this.leave(playerRoom); + if (this.id === idDebugger) { - idDebugger = null; - msg = 'Debugger has disconnected'; + isPlayerDisconnecting = false; // indica che il debugger si disconnette + idDebugger = null; // resetta l'idDebugger + msg = "Debugger has disconnected"; // messagio da mandare ai client } // emit the message console.log(msg); - io.sockets.in(room).emit('messaggio', msg); + io.sockets.in(playerRoom).emit("messaggio", msg); - // create new room - room += '1'; - - //delete player from maps - delete socket2player[this.id]; - - //if (numconn <= 0) jSocket.sendMessage({"exit": removePlayerId}); + if (isPlayerDisconnecting) { + // avvisa tutti i client connessi alla stanza di abbandonare la partita + io.sockets.in(playerRoom).emit("disconnect", msg); + jSocket.sendMessage({"exit": playerId }); // avvisa java di resettare il game + //room += '1'; // create new room + } -}; + //delete player from maps + delete socket2player[this.id]; +} // Client send Attemtp message -function onAttempt(data){ - //Node.js assign id to client - data.attempt.id = socket2player[this.id].id; - - //forward json message to java - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - jSocket.sendMessage(data); -}; +function onAttempt(data) { + //Node.js assign id to client + data.attempt.id = socket2player[this.id].id; + + //forward json message to java + var playerRoom = socket2player[this.id].room; + jSocket = room2jsocket[playerRoom]; + jSocket.sendMessage(data); +} //onChoice -function onChoice(data){ - console.log(data); - var playerId = socket2player[this.id].id; - data.choice.idPlayer = playerId; - //send to java server the data JSON modified - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - jSocket.sendMessage(data); -}; +function onChoice(data) { + console.log(data); + var playerId = socket2player[this.id].id; + data.choice.idPlayer = playerId; + //send to java server the data JSON modified + var playerRoom = socket2player[this.id].room; + jSocket = room2jsocket[playerRoom]; + jSocket.sendMessage(data); +} -function onNewDebugger(numPlayer){ //Max num player parameter from CreateRoom.html +function onNewDebugger(numPlayer) { + //Max num player parameter from CreateRoom.html this.join(room); numDeb = 0; - var newDebugger = new Debugger(room,numDeb); + var newDebugger = new Debugger(room, numDeb); //var roomSize = getSizeRoom(room.toString()); socket2player[this.id] = newDebugger; - idDebugger=this.id; + idDebugger = this.id; console.log("debugger connected " + this.id); - io.sockets.in(room).emit('messaggio', "Debugger join to the room..."); - -}; + io.sockets.in(room).emit("messaggio", "Debugger join to the room..."); +} // New player has joined -function onNewPlayer(numPlayer){ //Max num player parameter from CreateRoom.html +function onNewPlayer(numPlayer) { + //Max num player parameter from CreateRoom.html numconn++; - //SOLO SE è il PRIMO -- cioè se numConn == 1 - //if PNumber==2 non tocco nulla - if(numconn==1){ - PLAYERS_NUMBER = numPlayer.num_player; //toReview - JSON_PLAYERS={"playersNumber": PLAYERS_NUMBER}; + console.debug(`numconn: ${numconn}`); + console.debug("numPlayer: ", typeof numPlayer, numPlayer); + console.debug(`PLAYERS_NUMBER: ${PLAYERS_NUMBER}`); + console.debug(`JSON_PLAYERS: ${JSON_PLAYERS}`); + + + //SOLO SE è il PRIMO -- cioè se numConn == 1 + //if PNumber==2 non tocco nulla + if (numconn == 1) { + PLAYERS_NUMBER = numPlayer.num_player; //toReview + JSON_PLAYERS = { playersNumber: PLAYERS_NUMBER }; } - //ToDO: choice ROOM - this.join(room); - //fill the maps + + /* if (numconn > PLAYERS_NUMBER) { + console.debug("Troppi giocatori"); + return; + } */ + + //ToDO: choice ROOM + this.join(room); + //fill the maps console.debug(room); - - var newPlayer = new Player(room, numconn); - socket2player[this.id] = newPlayer; - - //ToDo: handle more room - var roomSize = getSizeRoom(room.toString()); - console.log(roomSize + " players in room"); - - /* CHECK IF ROOM IS FULL --------------------------------------------------------------------------------------------------------*/ - if(roomSizePLAYERS_NUMBER){ + if (numconn > PLAYERS_NUMBER) { + console.log("DIOCANE"); //guest player - socketGuest=this; - socketGuest.emit("messaggio", "Connected to the game..."); - console.log("Guest connected " + this.id); + //socketGuest = this; + //socketGuest.emit("messaggio", "Connected to the game..."); + //console.log("Guest connected " + this.id); } /*END --------------------------------------------------------------------------------------------------------------------------------*/ -}; - +} + //send player settings to java -function onReady(data){ - io.sockets.in(room).emit('messaggio', this.id+" ready"); - //Node.js assign id to client - console.log("id who pressed " + socket2player[this.id].id); - data.playerSettings.playerInfo.id = socket2player[this.id].id; - console.log("player " + this.id + " is ready to play"); - - console.debug('Room: ' + room); - console.log(data); - - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - jSocket.sendMessage(data); // Notare che se viene invitato prima della connessione con il server JAVA crasha il Node -}; +function onReady(data) { + io.sockets.in(room).emit("messaggio", this.id + " ready"); + //Node.js assign id to client + console.log("id who pressed " + socket2player[this.id].id); + data.playerSettings.playerInfo.id = socket2player[this.id].id; + console.log("player " + this.id + " is ready to play"); + + console.debug("Room: " + room); + console.log(data); + + var playerRoom = socket2player[this.id].room; + jSocket = room2jsocket[playerRoom]; + jSocket.sendMessage(data); // Notare che se viene invitato prima della connessione con il server JAVA crasha il Node +} //send start game request to java -function onStartGame(){ - io.sockets.in(room).emit('messaggio', "Start game request sent..."); - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - jSocket.sendMessage(JSON_START_GAME); -}; +function onStartGame() { + io.sockets.in(room).emit("messaggio", "Start game request sent..."); + var playerRoom = socket2player[this.id].room; + jSocket = room2jsocket[playerRoom]; + jSocket.sendMessage(JSON_START_GAME); +} //save game request to java function onSaveGame() { - io.sockets.in(room).emit('messaggio', "Save game request sent..."); - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - jSocket.sendMessage(JSON_SAVE_GAME); -}; + io.sockets.in(room).emit("messaggio", "Save game request sent..."); + var playerRoom = socket2player[this.id].room; + jSocket = room2jsocket[playerRoom]; + jSocket.sendMessage(JSON_SAVE_GAME); +} //send zone request to java -function onReceiveZone(data){ - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - data.receiveZone = socket2player[this.id].id; - console.log('zone'+ data); - jSocket.sendMessage(data); -}; +function onReceiveZone(data) { + var playerRoom = socket2player[this.id].room; + jSocket = room2jsocket[playerRoom]; + data.receiveZone = socket2player[this.id].id; + console.log("zone" + data); + jSocket.sendMessage(data); +} //send game request to java -function onReceiveGame(data){ // - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - data.receiveGame = socket2player[this.id].id; - jSocket.sendMessage(data); -}; +function onReceiveGame(data) { + // + var playerRoom = socket2player[this.id].room; + jSocket = room2jsocket[playerRoom]; + data.receiveGame = socket2player[this.id].id; + jSocket.sendMessage(data); +} +/************************************************** + ** CLIENT INITIALISATION + **************************************************/ +function connectToJava() { + //Create new socket + var javaSocket = new JsonSocket(new net.Socket()); + //Open connection with java + javaSocket.connect(JAVA_SERVER_PORT, JAVA_SERVER_ADDR); + + //Start listening for events + setClientEventHandlers(javaSocket); + + return javaSocket; +} /************************************************** -** CLIENT INITIALISATION -**************************************************/ -function connectToJava(){ - //Create new socket - var javaSocket=new JsonSocket(new net.Socket()); - - //Open connection with java - javaSocket.connect(JAVA_SERVER_PORT, JAVA_SERVER_ADDR); - - //Start listening for events - setClientEventHandlers(javaSocket); - - return javaSocket; -}; - - -/************************************************** -** CLIENT EVENT HANDLERS -**************************************************/ -var setClientEventHandlers = function(javaSocket){ - - javaSocket.on('connect',onClientSocketConnection); - - //Listen for java message - javaSocket.on('data',onData); - - //Listen for java disconnected - javaSocket.on('close',onClose); -}; + ** CLIENT EVENT HANDLERS + **************************************************/ +var setClientEventHandlers = function (javaSocket) { + javaSocket.on("connect", onClientSocketConnection); + + //Listen for java message + javaSocket.on("data", onData); + + //Listen for java disconnected + javaSocket.on("close", onClose); +}; //Connection with java opened successfully -function onClientSocketConnection(){ - console.log("Connected to java Server"); -}; +function onClientSocketConnection() { + console.log("Connected to java Server"); +} //Received json message from java -function onData(data){ - var i; - data = prevData + data.toString(); - prevData = ""; - - // If data contains multiple messages, elaborate them one by one - data = data.split("\n"); - for(i=0; i < data.length; i++) - { - // Take a single message - var message = data[i]; - if(message.length > 1) - { - if(IsJsonString(message)) - { - // Parse the message - message = JSON.parse(message); - //console.log('Send to: ' + message.idPlayer); - //console.log('MessageID: ' + message.id); - //console.log('Remaining: ' + message.remaining); +function onData(data) { + var i; + data = prevData + data.toString(); + prevData = ""; + + // If data contains multiple messages, elaborate them one by one + data = data.split("\n"); + for (i = 0; i < data.length; i++) { + // Take a single message + var message = data[i]; + if (message.length > 1) { + if (IsJsonString(message)) { + // Parse the message + message = JSON.parse(message); + //console.log('Send to: ' + message.idPlayer); + //console.log('MessageID: ' + message.id); + //console.log('Remaining: ' + message.remaining); //console.log(message); - //console.log('Data: ' + message.data); - if(message.remaining <= 0) - { - if(message.id in messages) - { + //console.log('Data: ' + message.data); + if (message.remaining <= 0) { + if (message.id in messages) { /* - if(message.idPlayer < 0) - { - messages[message.id] = messages[message.id] + message.data; - sendToDebugger(messages[message.id]); - console.log('Data Debug: ' + message.data); - delete messages[message.id]; - }else{ - */ - messages[message.id] = messages[message.id] + message.data; + if(message.idPlayer < 0) + { + messages[message.id] = messages[message.id] + message.data; + sendToDebugger(messages[message.id]); + console.log('Data Debug: ' + message.data); + delete messages[message.id]; + }else{ + */ + messages[message.id] = messages[message.id] + message.data; sendToPlayer(message.idPlayer, messages[message.id]); - delete messages[message.id]; - - } - else - { - sendToPlayer(message.idPlayer, message.data) - } - } - else - { - if(message.id in messages) - { - messages[message.id] += message.data; - } - else - { - messages[message.id] = message.data; - } - } - } - else - { - prevData = message; - } - } - } - - //if socket guest is connected then send message - //if(socketGuest){ socketGuest.emit('messaggio', data.toString());} -}; + delete messages[message.id]; + } else { + sendToPlayer(message.idPlayer, message.data); + } + } else { + if (message.id in messages) { + messages[message.id] += message.data; + } else { + messages[message.id] = message.data; + } + } + } else { + prevData = message; + } + } + } + + //if socket guest is connected then send message + //if(socketGuest){ socketGuest.emit('messaggio', data.toString());} +} function IsJsonString(str) { try { @@ -407,428 +424,436 @@ function IsJsonString(str) { } function sendToPlayer(idPlayer, data) { - if(idPlayer > 0) - { - if( (idPlayer == 999 ) && (idDebugger != null) ) { - io.sockets.connected[idDebugger].emit('messaggio', data); + if (idPlayer > 0) { + if (idPlayer == 999 && idDebugger != null) { + io.sockets.connected[idDebugger].emit("messaggio", data); return; } - - for(var socketId in socket2player) - { - if(socket2player[socketId].id == idPlayer) - { - // Send message to a single client - io.sockets.connected[socketId].emit('messaggio', data); - } - } - } - else - { - // Send broadcast message - io.sockets.in(room).emit('messaggio', data); - } -}; -function onDebugOn(){ + for (var socketId in socket2player) { + if (socket2player[socketId].id == idPlayer) { + // Send message to a single client + io.sockets.connected[socketId].emit("messaggio", data); + } + } + } else { + // Send broadcast message + io.sockets.in(room).emit("messaggio", data); + } +} + +function onDebugOn() { jSocket.sendMessage(JSON_DEBUG_ON); - io.sockets.in(room).emit('messaggio',"Debug On(attempt disabilitati)"); + io.sockets.in(room).emit("messaggio", "Debug On(attempt disabilitati)"); } -function onDebugOff(){ +function onDebugOff() { jSocket.sendMessage(JSON_DEBUG_OFF); - io.sockets.in(room).emit('messaggio',"Debug Off(attempt abilitati)"); - + io.sockets.in(room).emit("messaggio", "Debug Off(attempt abilitati)"); } - -function setPlayerDatas(data){ +function setPlayerDatas(data) { /* - "idPlayer":idPleyer, - "dataLTPlayer" : dataLTPlayer, - "dataLMPlayer" : dataLMPlayer, - "poisonCounter" : poisonCounter - - */ + "idPlayer":idPleyer, + "dataLTPlayer" : dataLTPlayer, + "dataLMPlayer" : dataLMPlayer, + "poisonCounter" : poisonCounter + + */ var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - - var JSON_SET={"setDataPlayer":"true", - "idplayer": parseInt(data.idPlayer), - "LTPlayer": parseInt(data.dataLTPlayer), - "LMPlayer": data.dataLMPlayer, - "poisonCounter": parseInt(data.poisonCounter) - }; + jSocket = room2jsocket[playerRoom]; + + var JSON_SET = { + setDataPlayer: "true", + idplayer: parseInt(data.idPlayer), + LTPlayer: parseInt(data.dataLTPlayer), + LMPlayer: data.dataLMPlayer, + poisonCounter: parseInt(data.poisonCounter), + }; jSocket.sendMessage(JSON_SET); - } - - - - -function setPositionDebug(data){ +function setPositionDebug(data) { /* - * ["idPlayer":idPleyer, - "idCard" : cardId, - "from" : fromOpp, - "numCard" : numCard, - "toOpp" : toOpp - "numPlace":numPlace}; - * ] - */ - + * ["idPlayer":idPleyer, + "idCard" : cardId, + "from" : fromOpp, + "numCard" : numCard, + "toOpp" : toOpp + "numPlace":numPlace}; + * ] + */ + //set up config - var playerRoom = socket2player[this.id].room; - jSocket = room2jsocket[playerRoom]; - - - var oppList = {"H": "Hand", - "L" : "Library", - "G" : "Graveyard", - "B" : "Battlefield", - "E" : "Exile", - "S" : "Stack", - "A" : "Ante", - "C" : "commander"}; - + var playerRoom = socket2player[this.id].room; + jSocket = room2jsocket[playerRoom]; + + var oppList = { + H: "Hand", + L: "Library", + G: "Graveyard", + B: "Battlefield", + E: "Exile", + S: "Stack", + A: "Ante", + C: "commander", + }; + var functVar = data.from + "_" + data.toOpp; console.log(functVar); - switch(functVar) { - case "H_L": // hand to library + switch (functVar) { + case "H_L": // hand to library // ancora non c'è la variabile in input // var position = data.numCard; var position = parseInt(data.numPlace); - var JSON_MOVE={"H_L":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + H_L: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - - case "H_G": // hand to graveyard - // ancora non c'è la variabile in input + break; + + case "H_G": // hand to graveyard + // ancora non c'è la variabile in input // var position = data.numCard; var position = parseInt(data.numPlace); - var JSON_MOVE={"H_G":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + H_G: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "H_B": // hand to Battelfield - - var JSON_MOVE={"H_B":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - }; + break; + case "H_B": // hand to Battelfield + var JSON_MOVE = { + H_B: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "H_E": // hand to exile - - var JSON_MOVE={"H_E":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - }; + break; + case "H_E": // hand to exile + var JSON_MOVE = { + H_E: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "H_S": // hand to stack - - var JSON_MOVE={"H_S":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - }; + break; + case "H_S": // hand to stack + var JSON_MOVE = { + H_S: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "L_H": // library to hand + break; + case "L_H": // library to hand var position = parseInt(data.numCard); - var JSON_MOVE={"L_H":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + L_H: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "L_G": // library to graveyard + break; + case "L_G": // library to graveyard var position = parseInt(data.numCard); var position1 = parseInt(data.numPlace); - var JSON_MOVE={"L_G":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position, - "numPlace":position1 - }; + var JSON_MOVE = { + L_G: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + numPlace: position1, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "L_B": // library to battlefield + break; + case "L_B": // library to battlefield var position = parseInt(data.numCard); - var JSON_MOVE={"L_B":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + L_B: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "L_E": // library to Exile + break; + case "L_E": // library to Exile var position = parseInt(data.numCard); - var JSON_MOVE={"L_E":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + L_E: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "L_S": // library to stack + break; + case "L_S": // library to stack var position = parseInt(data.numCard); - var JSON_MOVE={"L_S":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + L_S: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "G_H": // graveyard to hand + break; + case "G_H": // graveyard to hand var position = parseInt(data.numCard); - var JSON_MOVE={"G_H":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + G_H: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "G_L": // graveyard to library + break; + case "G_L": // graveyard to library var position = parseInt(data.numCard); var position1 = parseInt(data.numPlace); - var JSON_MOVE={"G_L":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position, - "numPlace":position1 - }; + var JSON_MOVE = { + G_L: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + numPlace: position1, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "G_B": // graveyard to battlefield + break; + case "G_B": // graveyard to battlefield var position = parseInt(data.numCard); - var JSON_MOVE={"G_B":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + G_B: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "G_E": // graveyard to exile + break; + case "G_E": // graveyard to exile var position = parseInt(data.numCard); - var JSON_MOVE={"G_E":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + G_E: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); jSocket.sendMessage(JSON_MOVE); - break; - case "G_S": // graveyard to stack + break; + case "G_S": // graveyard to stack var position = parseInt(data.numCard); - var JSON_MOVE={"G_S":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + G_S: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "B_H": // battlefield to hand + jSocket.sendMessage(JSON_MOVE); + break; + case "B_H": // battlefield to hand var position = parseInt(data.numCard); - var JSON_MOVE={"B_H":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + var JSON_MOVE = { + B_H: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "B_L": // battlefield to library + jSocket.sendMessage(JSON_MOVE); + break; + case "B_L": // battlefield to library var position = parseInt(data.numPlace); - var JSON_MOVE={"B_L":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + B_L: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "B_G": // battlefield to graveyard + jSocket.sendMessage(JSON_MOVE); + break; + case "B_G": // battlefield to graveyard var position = parseInt(data.numPlace); - var JSON_MOVE={"B_G":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + B_G: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "B_E": // battlefield to exile + jSocket.sendMessage(JSON_MOVE); + break; + case "B_E": // battlefield to exile //var position = parseInt(data.numCard); - var JSON_MOVE={"B_E":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + var JSON_MOVE = { + B_E: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "B_S": // battlefiled to stack + jSocket.sendMessage(JSON_MOVE); + break; + case "B_S": // battlefiled to stack //var position = parseInt(data.numCard); - var JSON_MOVE={"B_S":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + var JSON_MOVE = { + B_S: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "E_H": // exile to hand + jSocket.sendMessage(JSON_MOVE); + break; + case "E_H": // exile to hand //var position = parseInt(data.numCard); - var JSON_MOVE={"E_H":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + var JSON_MOVE = { + E_H: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "E_L": // exile to library + jSocket.sendMessage(JSON_MOVE); + break; + case "E_L": // exile to library var position = parseInt(data.numPlace); - var JSON_MOVE={"E_L":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + E_L: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "E_G": // exile to graveyard + jSocket.sendMessage(JSON_MOVE); + break; + case "E_G": // exile to graveyard var position = parseInt(data.numPlace); - var JSON_MOVE={"E_G":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + E_G: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "E_B": // exile to battlefield + jSocket.sendMessage(JSON_MOVE); + break; + case "E_B": // exile to battlefield //var position = parseInt(data.numCard); - var JSON_MOVE={"E_B":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + var JSON_MOVE = { + E_B: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "E_S": // exile to stack + jSocket.sendMessage(JSON_MOVE); + break; + case "E_S": // exile to stack //var position = parseInt(data.numCard); - var JSON_MOVE={"E_S":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + var JSON_MOVE = { + E_S: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "S_H": // stack to hand - var JSON_MOVE={"S_H":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + jSocket.sendMessage(JSON_MOVE); + break; + case "S_H": // stack to hand + var JSON_MOVE = { + S_H: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "S_L": // stack to library + jSocket.sendMessage(JSON_MOVE); + break; + case "S_L": // stack to library var position = parseInt(data.numPlace); - var JSON_MOVE={"S_L":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + S_L: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "S_B": // stack to battlefield - var JSON_MOVE={"S_B":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + jSocket.sendMessage(JSON_MOVE); + break; + case "S_B": // stack to battlefield + var JSON_MOVE = { + S_B: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "S_G": // stack to graveyard + jSocket.sendMessage(JSON_MOVE); + break; + case "S_G": // stack to graveyard var position = parseInt(data.numPlace); - var JSON_MOVE={"S_G":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard), - "numCard":position - }; + var JSON_MOVE = { + S_G: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + numCard: position, + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - case "S_E": // stack to exile - var JSON_MOVE={"S_E":"true", - "idplayer": parseInt(data.idPlayer), - "idCard": parseInt(data.idCard) - }; + jSocket.sendMessage(JSON_MOVE); + break; + case "S_E": // stack to exile + var JSON_MOVE = { + S_E: "true", + idplayer: parseInt(data.idPlayer), + idCard: parseInt(data.idCard), + }; console.log("i'm in"); - jSocket.sendMessage(JSON_MOVE); - break; - - - - - - default: - return; -} - - - - -}; - - - + jSocket.sendMessage(JSON_MOVE); + break; + default: + return; + } +} //da fare function sendToDebugger(data) { - - console.log("\n\n ************************************************** pre emit al debugger \n\n" + idDebugger + "\n\n *********************************************\n\n"); + console.log( + "\n\n ************************************************** pre emit al debugger \n\n" + + idDebugger + + "\n\n *********************************************\n\n" + ); // Send message to debugger - io.sockets.connected[idDebugger].emit('messaggio', data); -}; + io.sockets.connected[idDebugger].emit("messaggio", data); +} -function onClose(){ +function onClose() { // Dovrebbe chiudere il socket o roba del genere ?? - console.log('Connection from java closed'); -}; + console.log("Connection from java closed"); +} /************************************************** -** HELP FUNCTIONS -**************************************************/ -function getSizeRoom(roomName){ - var num = io.nsps['/'].adapter.rooms[roomName]; - return Object.keys(num).length; -}; + ** HELP FUNCTIONS + **************************************************/ +function getSizeRoom(roomName) { + var num = io.nsps["/"].adapter.rooms[roomName]; + return Object.keys(num).length; +} /************************************************** -** RUN THE GAME -**************************************************/ + ** RUN THE GAME + **************************************************/ init(); From 4b6dad04b10a7ad81b73ee27e5f5942b0fe2d568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Mon, 28 Nov 2022 10:14:41 +0100 Subject: [PATCH 09/14] Gestone Guest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migliroata e sistemata la gestione del Guest Player: - ora il guest player può connettersi e disconnettersi senza rompere la partita - quando il guest di disconnette viene pulita tutta la roba che lo riguarda --- mtghub/server.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/mtghub/server.js b/mtghub/server.js index a3bf695..e4086d9 100644 --- a/mtghub/server.js +++ b/mtghub/server.js @@ -28,7 +28,7 @@ var JSON_DEBUG_OFF = { debugOff: "true" }; ** GLOBAL VARIABLES **************************************************/ var numconn = 0; //number of player connected to Node.js -var socketGuest; //socket of guest player +var socketGuest = null; //socket of guest player var room = "magicRoom"; //room of game var jSocket; //java socket //maps @@ -121,11 +121,6 @@ function onServerSocketConnection(htmlClient) { function onClientDisconnect() { numconn--; - if (socketGuest && this.id == socketGuest.id) { - console.log("Guest Player has disconnected " + this.id); - return; - } - // se TRUE, un player si sta disconnettendo // se FALSe, il debugger si sta disconnettendo var isPlayerDisconnecting = true; @@ -149,6 +144,13 @@ function onClientDisconnect() { msg = "Debugger has disconnected"; // messagio da mandare ai client } + // guest disconnecting + if (socketGuest !== null && this.id == socketGuest.id) { + isPlayerDisconnecting = false; // indica che il guest si disconnette + socketGuest = null + msg = `Guest Player has disconnected ${this.id}`; + } + // emit the message console.log(msg); io.sockets.in(playerRoom).emit("messaggio", msg); @@ -221,11 +223,6 @@ function onNewPlayer(numPlayer) { JSON_PLAYERS = { playersNumber: PLAYERS_NUMBER }; } - /* if (numconn > PLAYERS_NUMBER) { - console.debug("Troppi giocatori"); - return; - } */ - //ToDO: choice ROOM this.join(room); //fill the maps @@ -267,11 +264,13 @@ function onNewPlayer(numPlayer) { } if (numconn > PLAYERS_NUMBER) { - console.log("DIOCANE"); + console.log("Guest connected " + this.id); + //guest player - //socketGuest = this; - //socketGuest.emit("messaggio", "Connected to the game..."); - //console.log("Guest connected " + this.id); + socketGuest = this; + + // avvisa tutti gli altri partecipanti della connessione del guest + io.sockets.in(room).emit("messaggio", "Guest Player join the room !"); } /*END --------------------------------------------------------------------------------------------------------------------------------*/ } @@ -847,9 +846,15 @@ function onClose() { /************************************************** ** HELP FUNCTIONS + ** + ** questa funzione ritorna il numero di Client aperti !! + ** Basta solo aprire un client e non fare nulla per + ** aumentare la size della Room !! **************************************************/ function getSizeRoom(roomName) { var num = io.nsps["/"].adapter.rooms[roomName]; + //console.debug(`NUMM: ${num}`); + return Object.keys(num).length; } From 9728a2d950a42a51effd934c0c45ae71503e8bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Mon, 28 Nov 2022 12:06:06 +0100 Subject: [PATCH 10/14] Java no more Loops Risolto problema che faceva andare Java in loop infinito quando il server NodeJs veniva chiuso. --- .../main/java/com/magicengine/GameEngine.java | 19 ++++++++++++- mtghub/server.js | 27 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/mtgengine/src/main/java/com/magicengine/GameEngine.java b/mtgengine/src/main/java/com/magicengine/GameEngine.java index 4f7c5f7..628ddc6 100644 --- a/mtgengine/src/main/java/com/magicengine/GameEngine.java +++ b/mtgengine/src/main/java/com/magicengine/GameEngine.java @@ -219,7 +219,7 @@ void exit(String json) { } } } - + // /** // * Questa funzione, quando viene chiamata dal server Node, resetta il gioco // * dato che un giocatore ha abbandonato la partita. @@ -435,6 +435,23 @@ public void run() { break; } + /** + * Questo messaggio permette di uccidere il GameEngine + * e viene utilizzato quando NodeJs viene chiuso (SIGINT) per + * impedire a Java di andare in Loop Infinito + * + * received avrà come valore un numero intero + * + * @author Nicolò Vescera + */ + // JSON "KILL" + received = getJson(cleanJson, "kill"); + if (received != null) { + System.out.println("ricevuto kill"); + System.out.println(String.format("Killing Game because NodeJs is closed (exit message %s)", received)); + break; + } + // JSON "ATTEMPT" received = getJson(cleanJson, "attempt"); if (received != null) { diff --git a/mtghub/server.js b/mtghub/server.js index e4086d9..0f07278 100644 --- a/mtghub/server.js +++ b/mtghub/server.js @@ -47,6 +47,33 @@ function init() { console.log("listening on: " + SERVER_PORT); }); + /** + * Quando NodeJs riceve SIGINT (CTRL+C) vengono chiusi + * tutti i socket con Java per evitare che questo vada in Loop + * + * @author Nicolò Vescera + */ + process.on('SIGINT', function() { + console.log("\nDetected SIGINT (CTRL+C) !!"); + console.log("Closing all Sockets ..."); + + for (const [socketRoom, socket] of Object.entries(room2jsocket)) { + console.log(`Closing Sockets for Room ${socketRoom}`); + + console.log(`Closing JSocket`); + socket.sendMessage({"kill": 0}); + + console.log(`Closing ClientHTML connection`); + io.sockets.in(socketRoom).emit("disconnect", 'NodeJs server closed'); + + + } + + console.log("Done :D"); + process.exit(); + }); + + // Start listening for events setServerEventHandlers(); } From 09d3ddf2c4eab0f02796f1cb8aa827ca10fced5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Mon, 28 Nov 2022 12:25:48 +0100 Subject: [PATCH 11/14] Commentato Codice Commentate le varie modifiche che abbiamo fatto. --- .../main/java/com/magicengine/GameEngine.java | 17 --------- mtggameinterface/script/clientL.js | 16 ++++++++ mtghub/server.js | 37 ++++++++++++++----- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/mtgengine/src/main/java/com/magicengine/GameEngine.java b/mtgengine/src/main/java/com/magicengine/GameEngine.java index 628ddc6..5aad35f 100644 --- a/mtgengine/src/main/java/com/magicengine/GameEngine.java +++ b/mtgengine/src/main/java/com/magicengine/GameEngine.java @@ -220,23 +220,6 @@ void exit(String json) { } } -// /** -// * Questa funzione, quando viene chiamata dal server Node, resetta il gioco -// * dato che un giocatore ha abbandonato la partita. -// * @author Nicolò Vescera -// * -// * @param json Id del giocatore che ha abbandonato il gioco (ricevuto da Node) -// */ -// void resetGame(String json) { -// int idPlayer = gson.fromJson(json, int.class); -// for (Player p : game.getPriorityOrder()) { -// if (p.getId() != idPlayer) { -// sendToNode("player " + p.getNickname() + " has disconnected, Game will be resetted!"); -// //break; -// } -// } -// } - void initPlayer(String json) { boolean isNewPlayer = false; int position = 0; diff --git a/mtggameinterface/script/clientL.js b/mtggameinterface/script/clientL.js index 9946a6b..810a092 100644 --- a/mtggameinterface/script/clientL.js +++ b/mtggameinterface/script/clientL.js @@ -322,6 +322,14 @@ window.onload = function() { onMessage(data); }); + /** + * Quando il server invia questo messaggio ai client + * vuol dire che un client ha abbandonato o il server NodeJs + * è stato chiuso. Il client deve tornare allo stato iniziale e + * mostra a video il messaggio ricevuto. + * + * @author Fabrizio Fagiolo, Nicolò Vescera + */ socket.on('disconnect', function(data) { disconnectFunction(); alert(data); @@ -374,6 +382,14 @@ window.onload = function() { }; //BUTTON DISCONNECT + /** + * Questa funzione fa disconnettere il client e ritorna allo stato iniziale + * Viene avviata quando: + * - il bottone Disconnect viene premuto + * - il server invia un messaggio di disconnect al client + * + * @author Fabrizio Fagiolo, Nicolò Vescera + */ function disconnectFunction() { document.getElementById("message").textContent = "disconnected"; console.log("DISCONNECTED"); diff --git a/mtghub/server.js b/mtghub/server.js index 0f07278..1a7f43a 100644 --- a/mtghub/server.js +++ b/mtghub/server.js @@ -142,6 +142,10 @@ function onServerSocketConnection(htmlClient) { * - oltre a fare le stesse cose che fa il debug, avvisa java * di terminare il GameEngine relativo alla partita e * avvisa tutti gli altri client connessi di disconnettersi + * + * Se è un Guest che sta abbandonando: + * - Resetta a null la variabile socketGuest + * - Fa le setesse code del debugger * * @author Fabrizio Fagiolo, Nicolò Vescera */ @@ -232,16 +236,24 @@ function onNewDebugger(numPlayer) { } // New player has joined +/** + * Funzione che gestisce la connessione di un nuovo Player. + * - Incrementa il numero di connessioni numconn + * - Crea i vari socket + * - Solo quando ci sono 2 player connessi crea la connessione con Java + * - Quando ci sono più di 2 player vengono settati come Guest (può essercene solo 1 credo) + * + * @author Fabrizio Fagiolo, Nicolò Vescera + * + * @param {*} numPlayer Max num player parameter from CreateRoom.html + */ function onNewPlayer(numPlayer) { - //Max num player parameter from CreateRoom.html - numconn++; - console.debug(`numconn: ${numconn}`); - console.debug("numPlayer: ", typeof numPlayer, numPlayer); - console.debug(`PLAYERS_NUMBER: ${PLAYERS_NUMBER}`); - console.debug(`JSON_PLAYERS: ${JSON_PLAYERS}`); - + //console.debug(`numconn: ${numconn}`); + //console.debug("numPlayer: ", typeof numPlayer, numPlayer); + //console.debug(`PLAYERS_NUMBER: ${PLAYERS_NUMBER}`); + //console.debug(`JSON_PLAYERS: ${JSON_PLAYERS}`); //SOLO SE è il PRIMO -- cioè se numConn == 1 //if PNumber==2 non tocco nulla @@ -251,15 +263,20 @@ function onNewPlayer(numPlayer) { } //ToDO: choice ROOM - this.join(room); - //fill the maps - console.debug(room); + this.join(room); + //fill the maps var newPlayer = new Player(room, numconn); socket2player[this.id] = newPlayer; //ToDo: handle more room + /* + * queste 2 riche non fanno molto, + * la gunzione getSizeRoom ritorna il numero di ClientHTML connessi, + * basta solo che se ne apre uno (senza compilare nessun campo o premere il botteno Join Room) + * per aumentarne il valore. + */ var roomSize = getSizeRoom(room.toString()); console.log(roomSize + " players in room"); From 931bade79ed67791808df0a8b1594cc3d76ec29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Mon, 28 Nov 2022 18:19:55 +0100 Subject: [PATCH 12/14] ClientHTML Reset Ora il ClientHTML viene completamente resettato quando il server NodeJs forza la disconnessione. Co-authored-by: F-A-B-R-I-Z-I-O --- mtggameinterface/script/clientL.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mtggameinterface/script/clientL.js b/mtggameinterface/script/clientL.js index 810a092..a17b924 100644 --- a/mtggameinterface/script/clientL.js +++ b/mtggameinterface/script/clientL.js @@ -332,8 +332,12 @@ window.onload = function() { */ socket.on('disconnect', function(data) { disconnectFunction(); - alert(data); + //console.log(data); + alert(data); + + // ricarica la pagine per resettare lo stato iniziale del client + window.location.reload(); }); }; From fca555f583630b71ad82a52647361a0be4fc0d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Wed, 30 Nov 2022 10:03:10 +0100 Subject: [PATCH 13/14] Client Disconnect Message Fix Risolto problema che triggerava l'evento "disconnect" di socket.io Avevamo chiamato un evento del client con lo stesso evento di socket.io e questo partiva troppe volte. --- mtggameinterface/script/clientL.js | 2 +- mtghub/server.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mtggameinterface/script/clientL.js b/mtggameinterface/script/clientL.js index a17b924..082ed7e 100644 --- a/mtggameinterface/script/clientL.js +++ b/mtggameinterface/script/clientL.js @@ -330,7 +330,7 @@ window.onload = function() { * * @author Fabrizio Fagiolo, Nicolò Vescera */ - socket.on('disconnect', function(data) { + socket.on('clientLeave', function(data) { disconnectFunction(); //console.log(data); diff --git a/mtghub/server.js b/mtghub/server.js index 1a7f43a..882720c 100644 --- a/mtghub/server.js +++ b/mtghub/server.js @@ -64,7 +64,7 @@ function init() { socket.sendMessage({"kill": 0}); console.log(`Closing ClientHTML connection`); - io.sockets.in(socketRoom).emit("disconnect", 'NodeJs server closed'); + io.sockets.in(socketRoom).emit("clientLeave", 'NodeJs server closed'); } @@ -188,7 +188,7 @@ function onClientDisconnect() { if (isPlayerDisconnecting) { // avvisa tutti i client connessi alla stanza di abbandonare la partita - io.sockets.in(playerRoom).emit("disconnect", msg); + io.sockets.in(playerRoom).emit("clientLeave", msg); jSocket.sendMessage({"exit": playerId }); // avvisa java di resettare il game //room += '1'; // create new room } From b39aa63c8085ca38e681ecc721eac6f1147023bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Vescera?= Date: Wed, 30 Nov 2022 10:58:09 +0100 Subject: [PATCH 14/14] NodeJS & Java Disconnection Gestite eccezioni non catturate in NodeJs Quando Java crasha viene mandato un messaggio ai Client e chiusi tutti i socket. Testato debugger e Guest, sembra funzionare. --- mtghub/server.js | 75 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/mtghub/server.js b/mtghub/server.js index 882720c..4f34d49 100644 --- a/mtghub/server.js +++ b/mtghub/server.js @@ -38,6 +38,33 @@ var socket2player = {}; //KEYS: html socket, VALUE: player object var messages = {}; //save the incoming incomplete messages var prevData = ""; var idDebugger = null; + + +/** + * Questa funzione viene lanciata quando i server NodeJs o Java vengono + * chiusi di cattiveria. Forzano la disconnessione di tutti i socket + * io (quelli tra Node e ClientHTML) e JSocket (tra Node e Java). + * @author Fabrizio Fagiolo, Nicolò Vescera + * + * @param {string} msg messaggio da mostrare ai ClientHTML + */ +function forcedExit(msg="NodeJs server closed") { + console.log("Closing all Sockets ..."); + + for (const [socketRoom, socket] of Object.entries(room2jsocket)) { + console.log(`Closing Sockets for Room ${socketRoom}`); + + console.log(`Closing JSocket`); + socket.sendMessage({"kill": 0}); + + console.log(`Closing ClientHTML connection`); + io.sockets.in(socketRoom).emit("clientLeave", msg); + } + + console.log("Done :D"); +}; + + /************************************************** ** SERVER INITIALISATION **************************************************/ @@ -47,6 +74,23 @@ function init() { console.log("listening on: " + SERVER_PORT); }); + + + /** + * Quando viene lanciata un'eccezione non gestita + * viene forzata la chiusura di tutti i Socket (io e JSocket) + * ed infine chiuso il server NodeJS + * + * @author Fabrizio Fagiolo, Nicolò Vescera + */ + process.on('uncaughtException', (err, origin) => { + console.error(`Exception: ${err}`); + console.error(`from origin: ${origin}`); + + forcedExit(); + process.exit(1); + }); + /** * Quando NodeJs riceve SIGINT (CTRL+C) vengono chiusi * tutti i socket con Java per evitare che questo vada in Loop @@ -55,22 +99,8 @@ function init() { */ process.on('SIGINT', function() { console.log("\nDetected SIGINT (CTRL+C) !!"); - console.log("Closing all Sockets ..."); - - for (const [socketRoom, socket] of Object.entries(room2jsocket)) { - console.log(`Closing Sockets for Room ${socketRoom}`); - - console.log(`Closing JSocket`); - socket.sendMessage({"kill": 0}); - - console.log(`Closing ClientHTML connection`); - io.sockets.in(socketRoom).emit("clientLeave", 'NodeJs server closed'); - - - } - - console.log("Done :D"); - process.exit(); + forcedExit(); + process.exit(0); }); @@ -262,6 +292,10 @@ function onNewPlayer(numPlayer) { JSON_PLAYERS = { playersNumber: PLAYERS_NUMBER }; } + /* if(numconn%PLAYERS_NUMBER >= JSON_PLAYERS) { + room += '1'; + } */ + //ToDO: choice ROOM console.debug(room); this.join(room); @@ -886,6 +920,15 @@ function sendToDebugger(data) { function onClose() { // Dovrebbe chiudere il socket o roba del genere ?? console.log("Connection from java closed"); + + /** + * Quando Java viene chiuso o crasha (?) + * vengono chiusi tutti i Socket (io e JSocket) e + * viene inviato il messaggio ai ClientHTML + * + * @author Fabrizio Fagiolo, Nicolò Vescera + */ + forcedExit(msg="Java server closed"); } /**************************************************