[WIP] Consumable JSON game logs

As discussed in https://github.com/magefree/mage/issues/4515

This exposes a JSON log of game interactions that can be analyzed.

This is just a first pass, to get up to speed with how the messaging works. It'd like to trim down the messages much
further so they don't include redundant information in each message. Also gson supports much more advances serialization
options; such as using the @Expose annotation. We should probably use that, but I ran into some issues (I'm not a java
developer, so still learning).

TODO:
  These currently only exist on the client side; ideally we'd submit this logs back up to a central server after the
  games completion; thinking this could be simple via an S3 file drop, and a Lambda function to process and expose the
  logs; maybe via a kafka stream.

Examples of log messages are below:

```
{
  "gameId": "2cede8c5-ff8e-4f8c-b9ac-66af53c0a254",
  "sessionId": "5c4o149-678483-je42ycva-1-je42ycw2-4",
  "type": "GAME_SELECT",
  "value": {
    "gameView": {
      "priorityTime": 3000,
      "players": [
        {
          "name": "computer",
          "life": 20,
          "counters": {}
        },
        {
          "name": "hooptie",
          "life": 20,
          "counters": {}
        }
      ],
      "hand": {
        "425d774f-ee0c-4a9b-8516-c98f886943f0": {
          "name": "Springleaf Drum",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{1}"
          ],
          "convertedManaCost": 1,
          "type": 0,
          "paid": false,
          "id": "425d774f-ee0c-4a9b-8516-c98f886943f0"
        },
        "dd41bb4b-7fc3-4a3c-a69c-d18e281a1bff": {
          "name": "Blade of the Bloodchief",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{1}"
          ],
          "convertedManaCost": 1,
          "type": 0,
          "paid": false,
          "id": "dd41bb4b-7fc3-4a3c-a69c-d18e281a1bff"
        },
        "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806": {
          "name": "Ornithopter",
          "power": "0",
          "toughness": "2",
          "loyalty": "",
          "manaCost": [
            "{0}"
          ],
          "convertedManaCost": 0,
          "type": 0,
          "paid": false,
          "id": "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806"
        },
        "a2f9cc13-e71a-4c9c-96aa-5424ea1a6b64": {
          "name": "Springleaf Drum",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{1}"
          ],
          "convertedManaCost": 1,
          "type": 0,
          "paid": false,
          "id": "a2f9cc13-e71a-4c9c-96aa-5424ea1a6b64"
        },
        "91239f4f-9003-4c48-8ca1-4c318f892489": {
          "name": "Cranial Plating",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{2}"
          ],
          "convertedManaCost": 2,
          "type": 0,
          "paid": false,
          "id": "91239f4f-9003-4c48-8ca1-4c318f892489"
        },
        "feb268d8-0535-4a9c-8915-83dd92a08c4c": {
          "name": "Arcbound Ravager",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{2}"
          ],
          "convertedManaCost": 2,
          "type": 0,
          "paid": false,
          "id": "feb268d8-0535-4a9c-8915-83dd92a08c4c"
        }
      },
      "canPlayInHand": [
        "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806"
      ],
      "stack": {},
      "combat": [],
      "phase": "PRECOMBAT_MAIN",
      "step": "PRECOMBAT_MAIN"
    },
    "message": "Play spells and abilities.",
    "options": {
      "queryType": "SELECT"
    }
  }
}
{
  "gameId": "2cede8c5-ff8e-4f8c-b9ac-66af53c0a254",
  "sessionId": "5c4o149-678483-je42ycva-1-je42ycw2-4",
  "type": "SEND_PLAYER_UUID",
  "value": "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806"
}
{
  "gameId": "2cede8c5-ff8e-4f8c-b9ac-66af53c0a254",
  "sessionId": "5c4o149-678483-je42ycva-1-je42ycw2-4",
  "type": "GAME_CHOOSE_PILE",
  "value": {
    "choices": {
      "1ecf8671-be4c-4060-a76b-af614235a5b7": "Cast Ornithopter"
    }
  }
}
{
  "gameId": "2cede8c5-ff8e-4f8c-b9ac-66af53c0a254",
  "sessionId": "5c4o149-3v2cj2-je43178o-1-je43179f-4",
  "type": "GAME_INIT",
  "value": {
    "priorityTime": 3000,
    "players": [
      {
        "name": "computer",
        "life": 20,
        "counters": {}
      },
      {
        "name": "hooptie",
        "life": 20,
        "counters": {}
      }
    ],
    "hand": {
      "425d774f-ee0c-4a9b-8516-c98f886943f0": {
        "name": "Springleaf Drum",
        "power": "0",
        "toughness": "0",
        "loyalty": "",
        "manaCost": [
          "{1}"
        ],
        "convertedManaCost": 1,
        "type": 0,
        "paid": false,
        "id": "425d774f-ee0c-4a9b-8516-c98f886943f0"
      },
      "dd41bb4b-7fc3-4a3c-a69c-d18e281a1bff": {
        "name": "Blade of the Bloodchief",
        "power": "0",
        "toughness": "0",
        "loyalty": "",
        "manaCost": [
          "{1}"
        ],
        "convertedManaCost": 1,
        "type": 0,
        "paid": false,
        "id": "dd41bb4b-7fc3-4a3c-a69c-d18e281a1bff"
      },
      "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806": {
        "name": "Ornithopter",
        "power": "0",
        "toughness": "2",
        "loyalty": "",
        "manaCost": [
          "{0}"
        ],
        "convertedManaCost": 0,
        "type": 0,
        "paid": false,
        "id": "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806"
      },
      "a2f9cc13-e71a-4c9c-96aa-5424ea1a6b64": {
        "name": "Springleaf Drum",
        "power": "0",
        "toughness": "0",
        "loyalty": "",
        "manaCost": [
          "{1}"
        ],
        "convertedManaCost": 1,
        "type": 0,
        "paid": false,
        "id": "a2f9cc13-e71a-4c9c-96aa-5424ea1a6b64"
      },
      "91239f4f-9003-4c48-8ca1-4c318f892489": {
        "name": "Cranial Plating",
        "power": "0",
        "toughness": "0",
        "loyalty": "",
        "manaCost": [
          "{2}"
        ],
        "convertedManaCost": 2,
        "type": 0,
        "paid": false,
        "id": "91239f4f-9003-4c48-8ca1-4c318f892489"
      },
      "feb268d8-0535-4a9c-8915-83dd92a08c4c": {
        "name": "Arcbound Ravager",
        "power": "0",
        "toughness": "0",
        "loyalty": "",
        "manaCost": [
          "{2}"
        ],
        "convertedManaCost": 2,
        "type": 0,
        "paid": false,
        "id": "feb268d8-0535-4a9c-8915-83dd92a08c4c"
      }
    },
    "canPlayInHand": [
      "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806"
    ],
    "stack": {},
    "combat": [],
    "phase": "PRECOMBAT_MAIN",
    "step": "PRECOMBAT_MAIN"
  }
}
{
  "gameId": "2cede8c5-ff8e-4f8c-b9ac-66af53c0a254",
  "sessionId": "5c4o149-3v2cj2-je43178o-1-je43179f-4",
  "type": "GAME_SELECT",
  "value": {
    "gameView": {
      "priorityTime": 3000,
      "players": [
        {
          "name": "computer",
          "life": 20,
          "counters": {}
        },
        {
          "name": "hooptie",
          "life": 20,
          "counters": {}
        }
      ],
      "hand": {
        "425d774f-ee0c-4a9b-8516-c98f886943f0": {
          "name": "Springleaf Drum",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{1}"
          ],
          "convertedManaCost": 1,
          "type": 0,
          "paid": false,
          "id": "425d774f-ee0c-4a9b-8516-c98f886943f0"
        },
        "dd41bb4b-7fc3-4a3c-a69c-d18e281a1bff": {
          "name": "Blade of the Bloodchief",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{1}"
          ],
          "convertedManaCost": 1,
          "type": 0,
          "paid": false,
          "id": "dd41bb4b-7fc3-4a3c-a69c-d18e281a1bff"
        },
        "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806": {
          "name": "Ornithopter",
          "power": "0",
          "toughness": "2",
          "loyalty": "",
          "manaCost": [
            "{0}"
          ],
          "convertedManaCost": 0,
          "type": 0,
          "paid": false,
          "id": "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806"
        },
        "a2f9cc13-e71a-4c9c-96aa-5424ea1a6b64": {
          "name": "Springleaf Drum",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{1}"
          ],
          "convertedManaCost": 1,
          "type": 0,
          "paid": false,
          "id": "a2f9cc13-e71a-4c9c-96aa-5424ea1a6b64"
        },
        "91239f4f-9003-4c48-8ca1-4c318f892489": {
          "name": "Cranial Plating",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{2}"
          ],
          "convertedManaCost": 2,
          "type": 0,
          "paid": false,
          "id": "91239f4f-9003-4c48-8ca1-4c318f892489"
        },
        "feb268d8-0535-4a9c-8915-83dd92a08c4c": {
          "name": "Arcbound Ravager",
          "power": "0",
          "toughness": "0",
          "loyalty": "",
          "manaCost": [
            "{2}"
          ],
          "convertedManaCost": 2,
          "type": 0,
          "paid": false,
          "id": "feb268d8-0535-4a9c-8915-83dd92a08c4c"
        }
      },
      "canPlayInHand": [
        "06eb0a6c-1e70-4dc0-bd1c-93b6ea444806"
      ],
      "stack": {},
      "combat": [],
      "phase": "PRECOMBAT_MAIN",
      "step": "PRECOMBAT_MAIN"
    },
    "message": "Play spells and abilities.",
    "options": {
      "queryType": "SELECT"
    }
  }
}
{
  "gameId": "2cede8c5-ff8e-4f8c-b9ac-66af53c0a254",
  "sessionId": "5c4o149-678483-je42ycva-1-je42ycw2-4",
  "type": "SEND_PLAYER_UUID",
  "value": "1ecf8671-be4c-4060-a76b-af614235a5b7"
}
```
This commit is contained in:
Shaun Hannah 2018-02-26 19:23:00 -05:00
parent 871b035bb4
commit 6aeb3c7c3a
7 changed files with 239 additions and 5 deletions

View file

@ -31,6 +31,9 @@ import java.awt.event.KeyEvent;
import java.util.List;
import java.util.UUID;
import javax.swing.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import mage.cards.decks.Deck;
import mage.client.MageFrame;
import mage.client.SessionHandler;
@ -47,6 +50,9 @@ import mage.client.util.audio.AudioManager;
import mage.client.util.object.SaveObjectUtil;
import mage.interfaces.callback.CallbackClient;
import mage.interfaces.callback.ClientCallback;
import mage.remote.ActionData;
import mage.remote.Session;
import mage.remote.SessionImpl;
import mage.utils.CompressUtil;
import mage.view.*;
import mage.view.ChatMessage.MessageType;
@ -102,7 +108,6 @@ public class CallbackClientImpl implements CallbackClient {
break;
case CHATMESSAGE: {
ChatMessage message = (ChatMessage) callback.getData();
// Drop messages from ignored users
if (message.getUsername() != null && IgnoreList.IGNORED_MESSAGE_TYPES.contains(message.getMessageType())) {
final String serverAddress = SessionHandler.getSession().getServerHostname().orElseGet(() -> "");
@ -183,6 +188,7 @@ public class CallbackClientImpl implements CallbackClient {
case GAME_INIT: {
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_INIT", callback.getObjectId(), (GameView) callback.getData());
panel.init((GameView) callback.getData());
}
break;
@ -190,6 +196,7 @@ public class CallbackClientImpl implements CallbackClient {
case GAME_OVER: {
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_OVER", callback.getObjectId(), callback.getData());
panel.endMessage((String) callback.getData(), callback.getMessageId());
}
break;
@ -201,6 +208,7 @@ public class CallbackClientImpl implements CallbackClient {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_ASK", callback.getObjectId(), message);
panel.ask(message.getMessage(), message.getGameView(), callback.getMessageId(), message.getOptions());
}
break;
@ -208,8 +216,10 @@ public class CallbackClientImpl implements CallbackClient {
case GAME_TARGET: // e.g. Pick triggered ability
{
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_TARGET", callback.getObjectId(), message);
panel.pickTarget(message.getMessage(), message.getCardsView(), message.getGameView(),
message.getTargets(), message.isFlag(), message.getOptions(), callback.getMessageId());
}
@ -217,8 +227,10 @@ public class CallbackClientImpl implements CallbackClient {
}
case GAME_SELECT: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_SELECT", callback.getObjectId(), message);
panel.select(message.getMessage(), message.getGameView(), callback.getMessageId(), message.getOptions());
}
break;
@ -226,6 +238,7 @@ public class CallbackClientImpl implements CallbackClient {
case GAME_CHOOSE_ABILITY: {
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_CHOOSE_PILE", callback.getObjectId(), callback.getData());
panel.pickAbility((AbilityPickerView) callback.getData());
}
break;
@ -234,15 +247,18 @@ public class CallbackClientImpl implements CallbackClient {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_CHOOSE_PILE", callback.getObjectId(), message);
panel.pickPile(message.getMessage(), message.getPile1(), message.getPile2());
}
break;
}
case GAME_CHOOSE_CHOICE: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_CHOOSE_CHOICE", callback.getObjectId(), message);
panel.getChoice(message.getChoice(), callback.getObjectId());
}
break;
@ -251,34 +267,44 @@ public class CallbackClientImpl implements CallbackClient {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_PLAY_MANA", callback.getObjectId(), message);
panel.playMana(message.getMessage(), message.getGameView(), message.getOptions(), callback.getMessageId());
}
break;
}
case GAME_PLAY_XMANA: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_PLAY_XMANA", callback.getObjectId(), message);
panel.playXMana(message.getMessage(), message.getGameView(), callback.getMessageId());
}
break;
}
case GAME_GET_AMOUNT: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_GET_AMOUNT", callback.getObjectId(), message);
panel.getAmount(message.getMin(), message.getMax(), message.getMessage());
}
break;
}
case GAME_UPDATE: {
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_UPDATE", callback.getObjectId(), callback.getData());
panel.updateGame((GameView) callback.getData());
}
break;
}
case END_GAME_INFO:
appendJsonEvent("GAME_OVER", callback.getObjectId(), callback.getData());
MageFrame.getInstance().showGameEndDialog((GameEndView) callback.getData());
break;
case SHOW_USERMESSAGE:
@ -293,6 +319,7 @@ public class CallbackClientImpl implements CallbackClient {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_INFORM", callback.getObjectId(), message);
panel.inform(message.getMessage(), message.getGameView(), callback.getMessageId());
}
}
@ -375,7 +402,12 @@ public class CallbackClientImpl implements CallbackClient {
}
});
}
private void appendJsonEvent(String name, UUID gameId, Object value) {
Session session = SessionHandler.getSession();
ActionData actionData = new ActionData(name, gameId);
actionData.value = value;
session.appendJsonLog(actionData);
}
private void createChatStartMessage(ChatPanelBasic chatPanel) {
chatPanel.setStartMessageDone(true);
ChatPanelBasic usedPanel = chatPanel;