From b76a1adea209e5de6da4a1c180fde12888536eb3 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 2 Nov 2017 21:51:22 -0400 Subject: [PATCH] Initial commit --- Plugin Cubot/plugin.properties | 3 + .../src/net/simon987/cubotplugin/Cubot.java | 113 ++++ .../net/simon987/cubotplugin/CubotAction.java | 8 + .../net/simon987/cubotplugin/CubotDrill.java | 59 ++ .../simon987/cubotplugin/CubotInventory.java | 54 ++ .../net/simon987/cubotplugin/CubotLaser.java | 83 +++ .../net/simon987/cubotplugin/CubotLeg.java | 77 +++ .../net/simon987/cubotplugin/CubotPlugin.java | 56 ++ .../net/simon987/cubotplugin/CubotRadar.java | 131 +++++ .../net/simon987/cubotplugin/Keyboard.java | 65 +++ .../event/CpuInitialisationListener.java | 43 ++ .../event/UserCreationListener.java | 33 ++ .../net/simon987/cubotplugin/CubotTest.java | 19 + Plugin Kiln/plugin.properties | 3 + .../net/simon987/kilnplugin/KilnPlugin.java | 21 + Plugin Plant/plugin.properties | 3 + .../src/net/simon987/plantplugin/Plant.java | 162 ++++++ .../net/simon987/plantplugin/PlantPlugin.java | 32 ++ .../event/WorldCreationListener.java | 93 ++++ .../src/net/simon987/server/GameServer.java | 163 ++++++ Server/src/net/simon987/server/Main.java | 31 ++ .../simon987/server/ServerConfiguration.java | 42 ++ .../simon987/server/assembly/Assembler.java | 514 ++++++++++++++++++ .../server/assembly/AssemblyResult.java | 99 ++++ .../src/net/simon987/server/assembly/CPU.java | 436 +++++++++++++++ .../simon987/server/assembly/CpuHardware.java | 43 ++ .../assembly/DefaultInstructionSet.java | 87 +++ .../server/assembly/DefaultRegisterSet.java | 22 + .../assembly/IllegalOperandException.java | 9 + .../simon987/server/assembly/Instruction.java | 214 ++++++++ .../server/assembly/InstructionSet.java | 33 ++ .../simon987/server/assembly/MachineCode.java | 101 ++++ .../net/simon987/server/assembly/Memory.java | 160 ++++++ .../net/simon987/server/assembly/Operand.java | 235 ++++++++ .../simon987/server/assembly/OperandType.java | 26 + .../simon987/server/assembly/Register.java | 50 ++ .../simon987/server/assembly/RegisterSet.java | 191 +++++++ .../net/simon987/server/assembly/Segment.java | 21 + .../net/simon987/server/assembly/Status.java | 135 +++++ .../net/simon987/server/assembly/Target.java | 30 + .../net/simon987/server/assembly/Util.java | 115 ++++ .../assembly/exception/AssemblyException.java | 30 + .../exception/CancelledException.java | 7 + .../exception/DuplicateSegmentException.java | 19 + .../exception/EmptyLineException.java | 10 + .../exception/InvalidMnemonicException.java | 10 + .../exception/InvalidOperandException.java | 18 + .../exception/PseudoInstructionException.java | 11 + .../assembly/instruction/AddInstruction.java | 56 ++ .../assembly/instruction/AndInstruction.java | 64 +++ .../assembly/instruction/BrkInstruction.java | 23 + .../assembly/instruction/CallInstruction.java | 52 ++ .../assembly/instruction/CmpInstruction.java | 57 ++ .../assembly/instruction/DivInstruction.java | 66 +++ .../assembly/instruction/HwiInstruction.java | 32 ++ .../assembly/instruction/JgInstruction.java | 41 ++ .../assembly/instruction/JgeInstruction.java | 41 ++ .../assembly/instruction/JlInstruction.java | 41 ++ .../assembly/instruction/JleInstruction.java | 41 ++ .../assembly/instruction/JmpInstruction.java | 39 ++ .../assembly/instruction/JnsInstruction.java | 39 ++ .../assembly/instruction/JnzInstruction.java | 41 ++ .../assembly/instruction/JsInstruction.java | 39 ++ .../assembly/instruction/JzInstruction.java | 41 ++ .../assembly/instruction/MovInstruction.java | 49 ++ .../assembly/instruction/MulInstruction.java | 56 ++ .../assembly/instruction/NegInstruction.java | 21 + .../assembly/instruction/NopInstruction.java | 14 + .../assembly/instruction/OrInstruction.java | 58 ++ .../assembly/instruction/PopInstruction.java | 34 ++ .../assembly/instruction/PushInstruction.java | 43 ++ .../assembly/instruction/RetInstruction.java | 40 ++ .../assembly/instruction/ShlInstruction.java | 56 ++ .../assembly/instruction/ShrInstruction.java | 72 +++ .../assembly/instruction/SubInstruction.java | 57 ++ .../assembly/instruction/TestInstruction.java | 53 ++ .../assembly/instruction/XorInstruction.java | 58 ++ .../server/event/CpuInitialisationEvent.java | 18 + .../net/simon987/server/event/GameEvent.java | 33 ++ .../server/event/GameEventDispatcher.java | 26 + .../server/event/GameEventListener.java | 12 + .../server/event/UserCreationEvent.java | 10 + .../server/event/WorldGenerationEvent.java | 15 + .../server/game/ControllableUnit.java | 13 + .../server/game/CpuHardwareDeserializer.java | 10 + .../net/simon987/server/game/Direction.java | 39 ++ .../net/simon987/server/game/EffectType.java | 26 + .../net/simon987/server/game/GameEffect.java | 68 +++ .../net/simon987/server/game/GameObject.java | 221 ++++++++ .../server/game/GameObjectDeserializer.java | 9 + .../simon987/server/game/GameUniverse.java | 171 ++++++ .../simon987/server/game/InventoryHolder.java | 20 + .../src/net/simon987/server/game/TileMap.java | 160 ++++++ .../net/simon987/server/game/TmpObject.java | 21 + .../net/simon987/server/game/Updatable.java | 13 + .../src/net/simon987/server/game/World.java | 193 +++++++ .../simon987/server/game/WorldGenerator.java | 219 ++++++++ .../server/game/pathfinding/Node.java | 70 +++ .../server/game/pathfinding/Pathfinder.java | 159 ++++++ .../game/pathfinding/SortedArrayList.java | 38 ++ .../simon987/server/io/DatabaseManager.java | 45 ++ .../simon987/server/io/JSONSerialisable.java | 9 + .../server/logging/GenericFormatter.java | 41 ++ .../simon987/server/logging/LogManager.java | 41 ++ .../simon987/server/plugin/PluginManager.java | 71 +++ .../simon987/server/plugin/ServerPlugin.java | 61 +++ Server/src/net/simon987/server/user/User.java | 98 ++++ .../server/webserver/CodeRequestHandler.java | 23 + .../server/webserver/CodeUploadHandler.java | 32 ++ .../server/webserver/KeypressHandler.java | 20 + .../webserver/MessageEventDispatcher.java | 43 ++ .../server/webserver/MessageHandler.java | 9 + .../webserver/ObjectsRequestHandler.java | 64 +++ .../simon987/server/webserver/OnlineUser.java | 42 ++ .../server/webserver/OnlineUserManager.java | 49 ++ .../server/webserver/SocketServer.java | 142 +++++ .../webserver/SocketServerDatabase.java | 51 ++ .../webserver/TerrainRequestHandler.java | 43 ++ .../webserver/UserInfoRequestHandler.java | 29 + .../net/simon987/server/assembly/CPUTest.java | 39 ++ .../simon987/server/assembly/MemoryTest.java | 51 ++ .../simon987/server/assembly/OperandTest.java | 151 +++++ .../server/assembly/RegisterSetTest.java | 78 +++ .../instruction/AddInstructionTest.java | 163 ++++++ .../instruction/AndInstructionTest.java | 84 +++ .../instruction/BrkInstructionTest.java | 24 + .../instruction/CallInstructionTest.java | 47 ++ config.properties | 83 +++ plugins/Cubot.jar | Bin 0 -> 16609 bytes plugins/Plant.jar | Bin 0 -> 5651 bytes 130 files changed, 8441 insertions(+) create mode 100644 Plugin Cubot/plugin.properties create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/Cubot.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/CubotAction.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/CubotDrill.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/CubotInventory.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/CubotLaser.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/CubotLeg.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/CubotPlugin.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/CubotRadar.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/Keyboard.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/event/CpuInitialisationListener.java create mode 100644 Plugin Cubot/src/net/simon987/cubotplugin/event/UserCreationListener.java create mode 100644 Plugin Cubot/test/net/simon987/cubotplugin/CubotTest.java create mode 100644 Plugin Kiln/plugin.properties create mode 100644 Plugin Kiln/src/net/simon987/kilnplugin/KilnPlugin.java create mode 100644 Plugin Plant/plugin.properties create mode 100644 Plugin Plant/src/net/simon987/plantplugin/Plant.java create mode 100644 Plugin Plant/src/net/simon987/plantplugin/PlantPlugin.java create mode 100644 Plugin Plant/src/net/simon987/plantplugin/event/WorldCreationListener.java create mode 100644 Server/src/net/simon987/server/GameServer.java create mode 100644 Server/src/net/simon987/server/Main.java create mode 100644 Server/src/net/simon987/server/ServerConfiguration.java create mode 100755 Server/src/net/simon987/server/assembly/Assembler.java create mode 100755 Server/src/net/simon987/server/assembly/AssemblyResult.java create mode 100755 Server/src/net/simon987/server/assembly/CPU.java create mode 100644 Server/src/net/simon987/server/assembly/CpuHardware.java create mode 100755 Server/src/net/simon987/server/assembly/DefaultInstructionSet.java create mode 100755 Server/src/net/simon987/server/assembly/DefaultRegisterSet.java create mode 100644 Server/src/net/simon987/server/assembly/IllegalOperandException.java create mode 100755 Server/src/net/simon987/server/assembly/Instruction.java create mode 100755 Server/src/net/simon987/server/assembly/InstructionSet.java create mode 100755 Server/src/net/simon987/server/assembly/MachineCode.java create mode 100755 Server/src/net/simon987/server/assembly/Memory.java create mode 100755 Server/src/net/simon987/server/assembly/Operand.java create mode 100755 Server/src/net/simon987/server/assembly/OperandType.java create mode 100755 Server/src/net/simon987/server/assembly/Register.java create mode 100755 Server/src/net/simon987/server/assembly/RegisterSet.java create mode 100755 Server/src/net/simon987/server/assembly/Segment.java create mode 100755 Server/src/net/simon987/server/assembly/Status.java create mode 100755 Server/src/net/simon987/server/assembly/Target.java create mode 100755 Server/src/net/simon987/server/assembly/Util.java create mode 100755 Server/src/net/simon987/server/assembly/exception/AssemblyException.java create mode 100644 Server/src/net/simon987/server/assembly/exception/CancelledException.java create mode 100755 Server/src/net/simon987/server/assembly/exception/DuplicateSegmentException.java create mode 100755 Server/src/net/simon987/server/assembly/exception/EmptyLineException.java create mode 100755 Server/src/net/simon987/server/assembly/exception/InvalidMnemonicException.java create mode 100755 Server/src/net/simon987/server/assembly/exception/InvalidOperandException.java create mode 100755 Server/src/net/simon987/server/assembly/exception/PseudoInstructionException.java create mode 100755 Server/src/net/simon987/server/assembly/instruction/AddInstruction.java create mode 100755 Server/src/net/simon987/server/assembly/instruction/AndInstruction.java create mode 100755 Server/src/net/simon987/server/assembly/instruction/BrkInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/CallInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/CmpInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/DivInstruction.java create mode 100755 Server/src/net/simon987/server/assembly/instruction/HwiInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JgInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JgeInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JlInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JleInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JmpInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JnsInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JnzInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JsInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/JzInstruction.java create mode 100755 Server/src/net/simon987/server/assembly/instruction/MovInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/MulInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/NegInstruction.java create mode 100755 Server/src/net/simon987/server/assembly/instruction/NopInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/OrInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/PopInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/PushInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/RetInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/ShlInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/ShrInstruction.java create mode 100755 Server/src/net/simon987/server/assembly/instruction/SubInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/TestInstruction.java create mode 100644 Server/src/net/simon987/server/assembly/instruction/XorInstruction.java create mode 100644 Server/src/net/simon987/server/event/CpuInitialisationEvent.java create mode 100644 Server/src/net/simon987/server/event/GameEvent.java create mode 100644 Server/src/net/simon987/server/event/GameEventDispatcher.java create mode 100644 Server/src/net/simon987/server/event/GameEventListener.java create mode 100644 Server/src/net/simon987/server/event/UserCreationEvent.java create mode 100644 Server/src/net/simon987/server/event/WorldGenerationEvent.java create mode 100644 Server/src/net/simon987/server/game/ControllableUnit.java create mode 100644 Server/src/net/simon987/server/game/CpuHardwareDeserializer.java create mode 100755 Server/src/net/simon987/server/game/Direction.java create mode 100644 Server/src/net/simon987/server/game/EffectType.java create mode 100644 Server/src/net/simon987/server/game/GameEffect.java create mode 100755 Server/src/net/simon987/server/game/GameObject.java create mode 100644 Server/src/net/simon987/server/game/GameObjectDeserializer.java create mode 100644 Server/src/net/simon987/server/game/GameUniverse.java create mode 100644 Server/src/net/simon987/server/game/InventoryHolder.java create mode 100755 Server/src/net/simon987/server/game/TileMap.java create mode 100644 Server/src/net/simon987/server/game/TmpObject.java create mode 100644 Server/src/net/simon987/server/game/Updatable.java create mode 100644 Server/src/net/simon987/server/game/World.java create mode 100755 Server/src/net/simon987/server/game/WorldGenerator.java create mode 100755 Server/src/net/simon987/server/game/pathfinding/Node.java create mode 100755 Server/src/net/simon987/server/game/pathfinding/Pathfinder.java create mode 100755 Server/src/net/simon987/server/game/pathfinding/SortedArrayList.java create mode 100755 Server/src/net/simon987/server/io/DatabaseManager.java create mode 100644 Server/src/net/simon987/server/io/JSONSerialisable.java create mode 100755 Server/src/net/simon987/server/logging/GenericFormatter.java create mode 100755 Server/src/net/simon987/server/logging/LogManager.java create mode 100644 Server/src/net/simon987/server/plugin/PluginManager.java create mode 100644 Server/src/net/simon987/server/plugin/ServerPlugin.java create mode 100755 Server/src/net/simon987/server/user/User.java create mode 100644 Server/src/net/simon987/server/webserver/CodeRequestHandler.java create mode 100644 Server/src/net/simon987/server/webserver/CodeUploadHandler.java create mode 100644 Server/src/net/simon987/server/webserver/KeypressHandler.java create mode 100644 Server/src/net/simon987/server/webserver/MessageEventDispatcher.java create mode 100644 Server/src/net/simon987/server/webserver/MessageHandler.java create mode 100644 Server/src/net/simon987/server/webserver/ObjectsRequestHandler.java create mode 100644 Server/src/net/simon987/server/webserver/OnlineUser.java create mode 100644 Server/src/net/simon987/server/webserver/OnlineUserManager.java create mode 100644 Server/src/net/simon987/server/webserver/SocketServer.java create mode 100644 Server/src/net/simon987/server/webserver/SocketServerDatabase.java create mode 100644 Server/src/net/simon987/server/webserver/TerrainRequestHandler.java create mode 100644 Server/src/net/simon987/server/webserver/UserInfoRequestHandler.java create mode 100644 Server/test/net/simon987/server/assembly/CPUTest.java create mode 100644 Server/test/net/simon987/server/assembly/MemoryTest.java create mode 100644 Server/test/net/simon987/server/assembly/OperandTest.java create mode 100644 Server/test/net/simon987/server/assembly/RegisterSetTest.java create mode 100644 Server/test/net/simon987/server/assembly/instruction/AddInstructionTest.java create mode 100644 Server/test/net/simon987/server/assembly/instruction/AndInstructionTest.java create mode 100644 Server/test/net/simon987/server/assembly/instruction/BrkInstructionTest.java create mode 100644 Server/test/net/simon987/server/assembly/instruction/CallInstructionTest.java create mode 100644 config.properties create mode 100644 plugins/Cubot.jar create mode 100644 plugins/Plant.jar diff --git a/Plugin Cubot/plugin.properties b/Plugin Cubot/plugin.properties new file mode 100644 index 0000000..5460a2b --- /dev/null +++ b/Plugin Cubot/plugin.properties @@ -0,0 +1,3 @@ +classpath=net.simon987.cubotplugin.CubotPlugin +name=Cubot Plugin +version=1.0 \ No newline at end of file diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/Cubot.java b/Plugin Cubot/src/net/simon987/cubotplugin/Cubot.java new file mode 100644 index 0000000..f2f56c0 --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/Cubot.java @@ -0,0 +1,113 @@ +package net.simon987.cubotplugin; + +import net.simon987.server.game.*; +import org.json.simple.JSONObject; + +import java.util.ArrayList; + +public class Cubot extends GameObject implements Updatable, ControllableUnit { + + private static final char MAP_INFO = 0x0080; + public static final int ID = 1; + + private EffectType currentEmote = null; + + /** + * Hit points + */ + private int hp; + private int heldItem; + + private CubotAction currentAction = CubotAction.IDLE; + private CubotAction lastAction = CubotAction.IDLE; + + private ArrayList keyboardBuffer = new ArrayList<>(); + + public Cubot() { + + } + + @Override + public char getMapInfo() { + return MAP_INFO; + } + + @Override + public void update() { + + if (currentAction == CubotAction.WALKING) { + if (!incrementLocation()) { + //Couldn't walk + currentAction = CubotAction.IDLE; + } + } + + if (currentEmote != null) { + // getWorld().getQueuedGameEffects().add(new GameEffect(currentEmote, getX(), getY())); + currentEmote = null; + } + + /* + * CurrentAction is set during the code execution and this function is called right after + * If no action as been set, the action sent to the client is the action in currentAction that + * was set last tick (IDLE) + */ + lastAction = currentAction; + currentAction = CubotAction.IDLE; + } + + @Override + public JSONObject serialise() { + JSONObject json = new JSONObject(); + json.put("id", getObjectId()); + json.put("type", ID); + json.put("x", getX()); + json.put("y", getY()); + json.put("direction", getDirection().ordinal()); + json.put("heldItem", heldItem); + json.put("hp", hp); + json.put("action", lastAction.ordinal()); + + return json; + } + + public static Cubot deserialize(JSONObject json) { + + Cubot cubot = new Cubot(); + cubot.setObjectId((int)(long)json.get("id")); + cubot.setX((int)(long)json.get("x")); + cubot.setY((int)(long)json.get("y")); + cubot.hp = (int)(long)json.get("hp"); + cubot.setDirection(Direction.getDirection((int)(long)json.get("direction"))); + cubot.heldItem = (int)(long)json.get("heldItem"); + + return cubot; + + } + + public void setHeldItem(int heldItem) { + this.heldItem = heldItem; + } + + public int getHeldItem() { + return heldItem; + } + + @Override + public void setKeyboardBuffer(ArrayList kbBuffer) { + keyboardBuffer = kbBuffer; + } + + @Override + public ArrayList getKeyboardBuffer() { + return keyboardBuffer; + } + + public void clearKeyboardBuffer(){ + keyboardBuffer.clear(); + } + + public void setCurrentAction(CubotAction currentAction) { + this.currentAction = currentAction; + } +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/CubotAction.java b/Plugin Cubot/src/net/simon987/cubotplugin/CubotAction.java new file mode 100644 index 0000000..e754576 --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/CubotAction.java @@ -0,0 +1,8 @@ +package net.simon987.cubotplugin; + +public enum CubotAction { + IDLE, + DIGGING, + WALKING + +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/CubotDrill.java b/Plugin Cubot/src/net/simon987/cubotplugin/CubotDrill.java new file mode 100644 index 0000000..5eca059 --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/CubotDrill.java @@ -0,0 +1,59 @@ +package net.simon987.cubotplugin; + +import net.simon987.server.GameServer; +import net.simon987.server.assembly.CpuHardware; +import net.simon987.server.assembly.Status; +import net.simon987.server.game.TileMap; +import org.json.simple.JSONObject; + +public class CubotDrill extends CpuHardware { + + /** + * Hardware ID (Should be unique) + */ + public static final int HWID = 0x0005; + + public static final int DEFAULT_ADDRESS = 5; + + private static final int GATHER = 1; + + private Cubot cubot; + + public CubotDrill(Cubot cubot) { + this.cubot = cubot; + } + + @Override + public void handleInterrupt(Status status) { + int a = getCpu().getRegisterSet().getRegister("A").getValue(); + + if (a == GATHER) { + + int tile = cubot.getWorld().getTileMap().getTileAt(cubot.getX(), cubot.getY()); + + if (tile == TileMap.IRON_TILE) { + cubot.setHeldItem(TileMap.ITEM_IRON); + + } else if (tile == TileMap.COPPER_TILE) { + cubot.setHeldItem(TileMap.ITEM_COPPER); + + } else { + System.out.println("FAILED: dig"); + } + + } + } + + @Override + public JSONObject serialise() { + JSONObject json = new JSONObject(); + json.put("hwid", HWID); + json.put("cubot", cubot.getObjectId()); + + return json; + } + + public static CubotDrill deserialize(JSONObject hwJSON){ + return new CubotDrill((Cubot) GameServer.INSTANCE.getGameUniverse().getObject((int)(long)hwJSON.get("cubot"))); + } +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/CubotInventory.java b/Plugin Cubot/src/net/simon987/cubotplugin/CubotInventory.java new file mode 100644 index 0000000..16538ad --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/CubotInventory.java @@ -0,0 +1,54 @@ +package net.simon987.cubotplugin; + +import net.simon987.server.GameServer; +import net.simon987.server.assembly.CpuHardware; +import net.simon987.server.assembly.Status; +import org.json.simple.JSONObject; + +public class CubotInventory extends CpuHardware { + + /** + * Hardware ID (Should be unique) + */ + public static final int HWID = 0x0006; + + public static final int DEFAULT_ADDRESS = 6; + + private Cubot cubot; + + private static final int POLL = 1; + private static final int CLEAR = 2; + + public CubotInventory(Cubot cubot) { + this.cubot = cubot; + } + + @Override + public void handleInterrupt(Status status) { + + int a = getCpu().getRegisterSet().getRegister("A").getValue(); + + if(a == POLL) { + + getCpu().getRegisterSet().getRegister("B").setValue(cubot.getHeldItem()); + + } else if (a == CLEAR) { + cubot.setHeldItem(0); + } + + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + json.put("hwid", HWID); + json.put("cubot", cubot.getObjectId()); + + return json; + } + + public static CubotInventory deserialize(JSONObject hwJSON){ + return new CubotInventory((Cubot) GameServer.INSTANCE.getGameUniverse().getObject((int)(long)hwJSON.get("cubot"))); + } +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/CubotLaser.java b/Plugin Cubot/src/net/simon987/cubotplugin/CubotLaser.java new file mode 100644 index 0000000..c9f4233 --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/CubotLaser.java @@ -0,0 +1,83 @@ +package net.simon987.cubotplugin; + +import net.simon987.server.GameServer; +import net.simon987.server.assembly.CpuHardware; +import net.simon987.server.assembly.Status; +import net.simon987.server.game.GameObject; +import net.simon987.server.game.InventoryHolder; +import org.json.simple.JSONObject; + +import java.awt.*; +import java.util.ArrayList; + +public class CubotLaser extends CpuHardware { + + /** + * Hardware ID (Should be unique) + */ + public static final int HWID = 0x0002; + + public static final int DEFAULT_ADDRESS = 2; + + private Cubot cubot; + + private static final int WITHDRAW = 1; + + + public CubotLaser(Cubot cubot) { + this.cubot = cubot; + } + + @Override + public void handleInterrupt(Status status) { + + int a = getCpu().getRegisterSet().getRegister("A").getValue(); + int b = getCpu().getRegisterSet().getRegister("B").getValue(); + + + if(a == WITHDRAW) { + + System.out.println("withdraw"); + + Point frontTile = cubot.getFrontTile(); + ArrayList objects = cubot.getWorld().getGameObjectsAt(frontTile.x, frontTile.y); + + if(objects.size() > 0){ + + if (objects.get(0) instanceof InventoryHolder) { + //Take the item + if (((InventoryHolder) objects.get(0)).takeItem(b)) { + + cubot.setHeldItem(b); + System.out.println("took " + b); + + } else { + //The inventory holder can't provide this item + //todo Add emote here + System.out.println("FAILED: take (The inventory holder can't provide this item)"); + } + + } + } else { + //Nothing in front + //todo Add emote here + System.out.println("FAILED: take (Nothing in front)"); + } + } + + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + json.put("hwid", HWID); + json.put("cubot", cubot.getObjectId()); + + return json; + } + + public static CubotLaser deserialize(JSONObject hwJSON){ + return new CubotLaser((Cubot) GameServer.INSTANCE.getGameUniverse().getObject((int)(long)hwJSON.get("cubot"))); + } +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/CubotLeg.java b/Plugin Cubot/src/net/simon987/cubotplugin/CubotLeg.java new file mode 100644 index 0000000..7fb1795 --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/CubotLeg.java @@ -0,0 +1,77 @@ +package net.simon987.cubotplugin; + +import net.simon987.server.GameServer; +import net.simon987.server.assembly.CpuHardware; +import net.simon987.server.assembly.Status; +import net.simon987.server.game.Direction; +import net.simon987.server.io.JSONSerialisable; +import org.json.simple.JSONObject; + +public class CubotLeg extends CpuHardware implements JSONSerialisable { + + public static final int DEFAULT_ADDRESS = 1; + + public static final String NAME = "Cubot Leg"; + + private static final int SET_DIR = 1; + private static final int SET_DIR_AND_WALK = 2; + + /** + * Hardware ID (Should be unique) + */ + public static final int HWID = 0x0001; + + private Cubot cubot; + + public CubotLeg(Cubot cubot) { + this.cubot = cubot; + } + + @Override + public void handleInterrupt(Status status) { + int a = getCpu().getRegisterSet().getRegister("A").getValue(); + int b = getCpu().getRegisterSet().getRegister("B").getValue(); + + if(a == SET_DIR){ + + Direction dir = Direction.getDirection(b); + + if(dir != null){ + cubot.setDirection(Direction.getDirection(b)); + status.setErrorFlag(false); + } else { + status.setErrorFlag(true); + } + + + } else if(a == SET_DIR_AND_WALK){ + + Direction dir = Direction.getDirection(b); + + if(dir != null){ + cubot.setDirection(Direction.getDirection(b)); + status.setErrorFlag(false); + } else { + status.setErrorFlag(true); + } + + cubot.setCurrentAction(CubotAction.WALKING); + } + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + json.put("hwid", HWID); + json.put("cubot", cubot.getObjectId()); + + return json; + } + + public static CubotLeg deserialize(JSONObject hwJSON){ + return new CubotLeg((Cubot)GameServer.INSTANCE.getGameUniverse().getObject((int)(long)hwJSON.get("cubot"))); + } + + +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/CubotPlugin.java b/Plugin Cubot/src/net/simon987/cubotplugin/CubotPlugin.java new file mode 100644 index 0000000..2fea52f --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/CubotPlugin.java @@ -0,0 +1,56 @@ +package net.simon987.cubotplugin; + +import net.simon987.cubotplugin.event.CpuInitialisationListener; +import net.simon987.cubotplugin.event.UserCreationListener; +import net.simon987.server.assembly.CpuHardware; +import net.simon987.server.game.CpuHardwareDeserializer; +import net.simon987.server.game.GameObject; +import net.simon987.server.game.GameObjectDeserializer; +import net.simon987.server.logging.LogManager; +import net.simon987.server.plugin.ServerPlugin; +import org.json.simple.JSONObject; + +public class CubotPlugin extends ServerPlugin implements GameObjectDeserializer, CpuHardwareDeserializer{ + + + @Override + public void init() { + listeners.add(new CpuInitialisationListener()); + listeners.add(new UserCreationListener()); + + LogManager.LOGGER.info("Initialised Cubot plugin"); + } + + @Override + public GameObject deserializeObject(JSONObject object) { + + int objType = (int)(long)object.get("type"); + + if(objType == Cubot.ID) { + + return Cubot.deserialize(object); + } + + return null; + } + + @Override + public CpuHardware deserializeHardware(JSONObject hwJson) { + int hwid = (int)(long)hwJson.get("hwid"); + + switch (hwid){ + case CubotLeg.HWID: + return CubotLeg.deserialize(hwJson); + case CubotLaser.HWID: + return CubotLaser.deserialize(hwJson); + case CubotRadar.HWID: + return CubotRadar.deserialize(hwJson); + case CubotDrill.HWID: + return CubotDrill.deserialize(hwJson); + case CubotInventory.HWID: + return CubotInventory.deserialize(hwJson); + } + + return null; + } +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/CubotRadar.java b/Plugin Cubot/src/net/simon987/cubotplugin/CubotRadar.java new file mode 100644 index 0000000..37d8525 --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/CubotRadar.java @@ -0,0 +1,131 @@ +package net.simon987.cubotplugin; + +import net.simon987.server.GameServer; +import net.simon987.server.assembly.CpuHardware; +import net.simon987.server.assembly.Status; +import net.simon987.server.game.World; +import net.simon987.server.game.pathfinding.Node; +import net.simon987.server.game.pathfinding.Pathfinder; +import net.simon987.server.io.JSONSerialisable; +import org.json.simple.JSONObject; + +import java.util.ArrayList; + +public class CubotRadar extends CpuHardware implements JSONSerialisable { + + /** + * Hardware ID (Should be unique) + */ + public static final int HWID = 0x0003; + + public static final int DEFAULT_ADDRESS = 3; + + private Cubot cubot; + + private static final int GET_POS = 1; + private static final int GET_PATH = 2; + private static final int GET_MAP = 3; + + public CubotRadar(Cubot cubot) { + this.cubot = cubot; + } + + @Override + public void handleInterrupt(Status status) { + + int a = getCpu().getRegisterSet().getRegister("A").getValue(); + + switch (a){ + case GET_POS: + getCpu().getRegisterSet().getRegister("X").setValue(cubot.getX()); + getCpu().getRegisterSet().getRegister("Y").setValue(cubot.getY()); + break; + case GET_PATH: + int b = getCpu().getRegisterSet().getRegister("B").getValue(); + int destX = getCpu().getRegisterSet().getRegister("X").getValue(); + int destY = getCpu().getRegisterSet().getRegister("Y").getValue(); + + //Get path + ArrayList nodes = Pathfinder.findPath(cubot.getWorld(), cubot.getX(), cubot.getY(), + destX, destY, b); + +// System.out.println(nodes.size() + " nodes"); + + //Write to memory + byte[] mem = getCpu().getMemory().getBytes(); + + int counter = 0; //todo get memory address from config/constant + + if (nodes != null) { + + Node lastNode = null; + + for (Node n : nodes) { + //Store the path as a sequence of directions + + if (lastNode == null) { + lastNode = n; + continue; + } + + if (n.x < lastNode.x) { + //West + mem[counter++] = 0; + mem[counter++] = 3; + } else if (n.x > lastNode.x) { + //East + mem[counter++] = 0; + mem[counter++] = 1; + } else if (n.y < lastNode.y) { + //North + mem[counter++] = 0; + mem[counter++] = 0; + } else if (n.y > lastNode.y) { + //South + mem[counter++] = 0; + mem[counter++] = 2; + } + + lastNode = n; + } + + //Indicate end of path with 0xAAAA + mem[counter++] = -86; + mem[counter] = -86; + } else { + //Indicate invalid path 0xFFFF + mem[counter++] = -1; + mem[counter] = -1; + } + + System.out.println("path to" + destX + "," + destY); + break; + + case GET_MAP: + char[][] mapInfo = cubot.getWorld().getMapInfo(); + + int i = 0; + for (int y = 0; y < World.WORLD_SIZE; y++) { + for (int x = 0; x < World.WORLD_SIZE; x++) { + getCpu().getMemory().set(i++, mapInfo[x][y]); + } + } + } + + + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + json.put("hwid", HWID); + json.put("cubot", cubot.getObjectId()); + + return json; + } + + public static CubotRadar deserialize(JSONObject hwJSON){ + return new CubotRadar((Cubot) GameServer.INSTANCE.getGameUniverse().getObject((int)(long)hwJSON.get("cubot"))); + } +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/Keyboard.java b/Plugin Cubot/src/net/simon987/cubotplugin/Keyboard.java new file mode 100644 index 0000000..3049a25 --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/Keyboard.java @@ -0,0 +1,65 @@ +package net.simon987.cubotplugin; + +import net.simon987.server.GameServer; +import net.simon987.server.assembly.CpuHardware; +import net.simon987.server.assembly.Status; +import org.json.simple.JSONObject; + +public class Keyboard extends CpuHardware { + + public static final int DEFAULT_ADDRESS = 4; + + public static final String NAME = "Wireless Keyboard"; + + + private static final int CLEAR_BUFFER = 0; + private static final int FETCH_KEY = 1; + + /** + * Hardware ID (Should be unique) + */ + public static final int HWID = 0x0004; + + private Cubot cubot; + + public Keyboard(Cubot cubot) { + this.cubot = cubot; + } + + @Override + public void handleInterrupt(Status status) { + + int a = getCpu().getRegisterSet().getRegister("A").getValue(); + + if(a == CLEAR_BUFFER){ + + cubot.clearKeyboardBuffer(); + + } else if (a == FETCH_KEY){ + //pop + int key = 0; + if(cubot.getKeyboardBuffer().size() > 0){ + key = cubot.getKeyboardBuffer().get(0); + cubot.getKeyboardBuffer().remove(0); + } + + getCpu().getRegisterSet().getRegister("B").setValue(key); + + } + + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + json.put("hwid", HWID); + json.put("cubot", cubot.getObjectId()); + + return json; + } + + public static Keyboard deserialize(JSONObject hwJSON){ + return new Keyboard((Cubot) GameServer.INSTANCE.getGameUniverse().getObject((int)(long)hwJSON.get("cubot"))); + } +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/event/CpuInitialisationListener.java b/Plugin Cubot/src/net/simon987/cubotplugin/event/CpuInitialisationListener.java new file mode 100644 index 0000000..3483ce4 --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/event/CpuInitialisationListener.java @@ -0,0 +1,43 @@ +package net.simon987.cubotplugin.event; + +import net.simon987.cubotplugin.*; +import net.simon987.server.assembly.CPU; +import net.simon987.server.event.CpuInitialisationEvent; +import net.simon987.server.event.GameEvent; +import net.simon987.server.event.GameEventListener; +import net.simon987.server.user.User; + +public class CpuInitialisationListener implements GameEventListener { + @Override + public Class getListenedEventType() { + return CpuInitialisationEvent.class; + } + + @Override + public void handle(GameEvent event) { + System.out.println("CPU Init"); + + CPU cpu = (CPU)event.getSource(); + User user = ((CpuInitialisationEvent)event).getUser(); + + CubotLeg legHw = new CubotLeg((Cubot) user.getControlledUnit()); + legHw.setCpu(cpu); + CubotLaser laserHw = new CubotLaser((Cubot) user.getControlledUnit()); + laserHw.setCpu(cpu); + CubotRadar radarHw = new CubotRadar((Cubot) user.getControlledUnit()); + radarHw.setCpu(cpu); + Keyboard keyboard = new Keyboard((Cubot) user.getControlledUnit()); + keyboard.setCpu(cpu); + CubotDrill drillHw = new CubotDrill((Cubot) user.getControlledUnit()); + drillHw.setCpu(cpu); + CubotInventory invHw = new CubotInventory((Cubot) user.getControlledUnit()); + invHw.setCpu(cpu); + + cpu.attachHardware(legHw, CubotLeg.DEFAULT_ADDRESS); + cpu.attachHardware(laserHw, CubotLaser.DEFAULT_ADDRESS); + cpu.attachHardware(radarHw, CubotRadar.DEFAULT_ADDRESS); + cpu.attachHardware(keyboard, Keyboard.DEFAULT_ADDRESS); + cpu.attachHardware(drillHw, CubotDrill.DEFAULT_ADDRESS); + cpu.attachHardware(invHw, CubotInventory.DEFAULT_ADDRESS); + } +} diff --git a/Plugin Cubot/src/net/simon987/cubotplugin/event/UserCreationListener.java b/Plugin Cubot/src/net/simon987/cubotplugin/event/UserCreationListener.java new file mode 100644 index 0000000..4648a9e --- /dev/null +++ b/Plugin Cubot/src/net/simon987/cubotplugin/event/UserCreationListener.java @@ -0,0 +1,33 @@ +package net.simon987.cubotplugin.event; + +import net.simon987.cubotplugin.Cubot; +import net.simon987.server.GameServer; +import net.simon987.server.event.GameEvent; +import net.simon987.server.event.GameEventListener; +import net.simon987.server.event.UserCreationEvent; +import net.simon987.server.user.User; + +public class UserCreationListener implements GameEventListener { + @Override + public Class getListenedEventType() { + return UserCreationEvent.class; + } + + @Override + public void handle(GameEvent event) { + + User user = (User)event.getSource(); + + + Cubot cubot = new Cubot(); + cubot.setWorld(GameServer.INSTANCE.getGameUniverse().getWorld(0,0)); + + cubot.setObjectId(GameServer.INSTANCE.getGameUniverse().getNextObjectId()); + + cubot.setX(6); + cubot.setY(6); + + user.setControlledUnit(cubot); + + } +} diff --git a/Plugin Cubot/test/net/simon987/cubotplugin/CubotTest.java b/Plugin Cubot/test/net/simon987/cubotplugin/CubotTest.java new file mode 100644 index 0000000..e4147a9 --- /dev/null +++ b/Plugin Cubot/test/net/simon987/cubotplugin/CubotTest.java @@ -0,0 +1,19 @@ +package net.simon987.cubotplugin; + + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class CubotTest { + + @Test + public void test(){ + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + assertEquals(1,2); + } +} \ No newline at end of file diff --git a/Plugin Kiln/plugin.properties b/Plugin Kiln/plugin.properties new file mode 100644 index 0000000..9f1fec7 --- /dev/null +++ b/Plugin Kiln/plugin.properties @@ -0,0 +1,3 @@ +classpath=net.simon987.kilnplugin.KilnPlugin +name=Kiln Plugin +version=1.0 \ No newline at end of file diff --git a/Plugin Kiln/src/net/simon987/kilnplugin/KilnPlugin.java b/Plugin Kiln/src/net/simon987/kilnplugin/KilnPlugin.java new file mode 100644 index 0000000..1b66834 --- /dev/null +++ b/Plugin Kiln/src/net/simon987/kilnplugin/KilnPlugin.java @@ -0,0 +1,21 @@ +package net.simon987.kilnplugin; + +import net.simon987.server.game.GameObject; +import net.simon987.server.game.GameObjectDeserializer; +import net.simon987.server.plugin.ServerPlugin; +import org.json.simple.JSONObject; + +public class KilnPlugin extends ServerPlugin implements GameObjectDeserializer { + + + + @Override + public void init() { + + } + + @Override + public GameObject deserializeObject(JSONObject object) { + return null; + } +} diff --git a/Plugin Plant/plugin.properties b/Plugin Plant/plugin.properties new file mode 100644 index 0000000..5c3b2cb --- /dev/null +++ b/Plugin Plant/plugin.properties @@ -0,0 +1,3 @@ +classpath=net.simon987.plantplugin.PlantPlugin +name=Plant Plugin +version=1.0 \ No newline at end of file diff --git a/Plugin Plant/src/net/simon987/plantplugin/Plant.java b/Plugin Plant/src/net/simon987/plantplugin/Plant.java new file mode 100644 index 0000000..0a9275f --- /dev/null +++ b/Plugin Plant/src/net/simon987/plantplugin/Plant.java @@ -0,0 +1,162 @@ +package net.simon987.plantplugin; + +import net.simon987.server.GameServer; +import net.simon987.server.game.GameObject; +import net.simon987.server.game.InventoryHolder; +import net.simon987.server.game.Updatable; +import org.json.simple.JSONObject; + +public class Plant extends GameObject implements Updatable, InventoryHolder{ + + private static final char MAP_INFO = 0x4000; + public static final int ID = 2; + + /** + * Grow time (see config.properties) + */ + private static final int GROW_TIME = GameServer.INSTANCE.getConfig().getInt("plant_grow_time"); + + /** + * Game time of the creation of this Plant + */ + private long creationTime; + + /** + * Whether the plant is grown or not + */ + private boolean grown; + + /** + * Yield of the plant, in biomass units + */ + private int biomassCount; + /** + * Style of the plant (Only visual) + */ + private int style; + + private static final int ITM_BIOMASS = 1; + + @Override + public char getMapInfo() { + return MAP_INFO; + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + + json.put("type", ID); + json.put("id", getObjectId()); + json.put("x", getX()); + json.put("y", getY()); + json.put("creationTime", creationTime); + json.put("grown", grown); + json.put("biomassCount", biomassCount); + json.put("style", style); + + return json; + } + + /** + * Called every tick + */ + @Override + public void update() { + if (!grown) { + //Check grow + if (creationTime + GROW_TIME <= GameServer.INSTANCE.getGameUniverse().getTime()) { + grown = true; + biomassCount = GameServer.INSTANCE.getConfig().getInt("plant_yield"); + } + } + } + + public long getCreationTime() { + return creationTime; + } + + public void setCreationTime(long creationTime) { + this.creationTime = creationTime; + } + + public boolean isGrown() { + return grown; + } + + public void setGrown(boolean grown) { + this.grown = grown; + } + + public int getBiomassCount() { + return biomassCount; + } + + public void setBiomassCount(int biomassCount) { + this.biomassCount = biomassCount; + } + + public int getStyle() { + return style; + } + + public void setStyle(int style) { + this.style = style; + } + + public static Plant deserialize(JSONObject json){ + + Plant plant = new Plant(); + + plant.setObjectId((int)(long)json.get("id")); + plant.setX((int)(long)json.get("x")); + plant.setY((int)(long)json.get("y")); + plant.grown = (boolean)json.get("grown"); + plant.creationTime = (long)json.get("creationTime"); + plant.style = (int)(long)json.get("style"); + plant.biomassCount = (int)(long)json.get("biomassCount"); + + return plant; + } + + /** + * Called when an object attempts to place an item in this Plant + * + * @param item item id (see MarConstants.ITEM_*) + * @return Always returns false + */ + @Override + public boolean placeItem(int item) { + //Why would you want to place an item in a plant? + return false; + } + + /** + * Called when an object attempts to take an item from this Plant. + * If the object requests biomass, it will be subtracted from biomassCount, and + * if it reaches 0, the plant is deleted + * + * @param item item id (see MarConstants.ITEM_*) + * @return true if the requested item is ITEM_BIOMASS and if the plant is grown + */ + @Override + public boolean takeItem(int item) { + + if (item == ITM_BIOMASS) { + if (grown && biomassCount > 1) { + biomassCount--; + return true; + } else if (grown) { + //Delete plant + setDead(true); + return true; + } else { + return false; + } + } else { + return false; + } + + } +} diff --git a/Plugin Plant/src/net/simon987/plantplugin/PlantPlugin.java b/Plugin Plant/src/net/simon987/plantplugin/PlantPlugin.java new file mode 100644 index 0000000..d57414e --- /dev/null +++ b/Plugin Plant/src/net/simon987/plantplugin/PlantPlugin.java @@ -0,0 +1,32 @@ +package net.simon987.plantplugin; + +import net.simon987.plantplugin.event.WorldCreationListener; +import net.simon987.server.game.GameObject; +import net.simon987.server.game.GameObjectDeserializer; +import net.simon987.server.logging.LogManager; +import net.simon987.server.plugin.ServerPlugin; +import org.json.simple.JSONObject; + +public class PlantPlugin extends ServerPlugin implements GameObjectDeserializer { + + + + @Override + public void init() { + listeners.add(new WorldCreationListener()); + LogManager.LOGGER.info("Initialised Plant plugin"); + } + + @Override + public GameObject deserializeObject(JSONObject object) { + + int objType = (int)(long)object.get("type"); + + if(objType == Plant.ID) { + + return Plant.deserialize(object); + } + + return null; + } +} diff --git a/Plugin Plant/src/net/simon987/plantplugin/event/WorldCreationListener.java b/Plugin Plant/src/net/simon987/plantplugin/event/WorldCreationListener.java new file mode 100644 index 0000000..1b07270 --- /dev/null +++ b/Plugin Plant/src/net/simon987/plantplugin/event/WorldCreationListener.java @@ -0,0 +1,93 @@ +package net.simon987.plantplugin.event; + +import net.simon987.plantplugin.Plant; +import net.simon987.server.GameServer; +import net.simon987.server.event.GameEvent; +import net.simon987.server.event.GameEventListener; +import net.simon987.server.event.WorldGenerationEvent; +import net.simon987.server.game.World; +import net.simon987.server.game.WorldGenerator; +import net.simon987.server.logging.LogManager; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Random; + +public class WorldCreationListener implements GameEventListener { + @Override + public Class getListenedEventType() { + return WorldGenerationEvent.class; + } + + @Override + public void handle(GameEvent event) { + + ArrayList plants = generatePlants(((WorldGenerationEvent)event).getWorld()); + + ((WorldGenerationEvent)event).getWorld().getGameObjects().addAll(plants); + + } + + /** + * Generate a list of plants for a world + */ + public ArrayList generatePlants(World world) { + + int minTreeCount = GameServer.INSTANCE.getConfig().getInt("minTreeCount"); + int maxTreeCount = GameServer.INSTANCE.getConfig().getInt("maxTreeCount"); + int plant_yield = GameServer.INSTANCE.getConfig().getInt("plant_yield"); + + Random random = new Random(); + int treeCount = random.nextInt(maxTreeCount - minTreeCount) + minTreeCount; + ArrayList plants = new ArrayList<>(maxTreeCount); + + //Count number of plain tiles. If there is less plain tiles than desired amount of trees, + //set the desired amount of trees to the plain tile count + int[][] tiles = world.getTileMap().getTiles(); + int plainCount = 0; + for (int y = 0; y < World.WORLD_SIZE; y++) { + for (int x = 0; x < World.WORLD_SIZE; x++) { + + if (tiles[x][y] == 0) { + plainCount++; + } + } + } + + if (treeCount > plainCount) { + treeCount = plainCount; + } + + outerLoop: + for (int i = 0; i < treeCount; i++) { + + Point p = WorldGenerator.getRandomPlainTile(world.getTileMap().getTiles()); + + if (p != null) { + + for (Plant plant : plants) { + if (plant.getX() == p.x && plant.getY() == p.y) { + //There is already a plant here + continue outerLoop; + } + } + + Plant plant = new Plant(); + plant.setObjectId(GameServer.INSTANCE.getGameUniverse().getNextObjectId()); + plant.setStyle(0); //TODO: set style depending on difficulty level? or random? from config? + plant.setBiomassCount(plant_yield); + plant.setCreationTime(0); // Plants generated by the world generator always have creationTime of 0 + plant.setX(p.x); + plant.setY(p.y); + plant.setWorld(world); + + plants.add(plant); + } + } + + LogManager.LOGGER.info("Generated " + plants.size() + " plants for World (" + world.getX() + ',' + + world.getY() + ')'); + + return plants; + } +} diff --git a/Server/src/net/simon987/server/GameServer.java b/Server/src/net/simon987/server/GameServer.java new file mode 100644 index 0000000..8a6b8de --- /dev/null +++ b/Server/src/net/simon987/server/GameServer.java @@ -0,0 +1,163 @@ +package net.simon987.server; + + +import net.simon987.server.event.GameEventDispatcher; +import net.simon987.server.game.GameUniverse; +import net.simon987.server.game.World; +import net.simon987.server.logging.LogManager; +import net.simon987.server.plugin.PluginManager; +import net.simon987.server.plugin.ServerPlugin; +import net.simon987.server.user.User; +import net.simon987.server.webserver.SocketServer; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +public class GameServer implements Runnable { + + public final static GameServer INSTANCE = new GameServer(); + + private GameUniverse gameUniverse; + private GameEventDispatcher eventDispatcher; + private PluginManager pluginManager; + + private ServerConfiguration config; + + private SocketServer socketServer; + + public GameServer() { + + this.config = new ServerConfiguration(new File("config.properties")); + + gameUniverse = new GameUniverse(config); + pluginManager = new PluginManager(); + + //Load all plugins in plugins folder, if it doesn't exist, create it + File pluginDir = new File("plugins/"); + File[] pluginDirListing = pluginDir.listFiles(); + + if(pluginDirListing != null) { + for(File pluginFile : pluginDirListing) { + + if(pluginFile.getName().endsWith(".jar")){ + pluginManager.load(pluginFile); + } + + } + } else { + if(!pluginDir.mkdir()) { + LogManager.LOGGER.severe("Couldn't create plugin directory"); + } + } + + eventDispatcher = new GameEventDispatcher(pluginManager); + + } + + public GameUniverse getGameUniverse() { + return gameUniverse; + } + + public GameEventDispatcher getEventDispatcher() { + return eventDispatcher; + } + + @Override + public void run() { + LogManager.LOGGER.info("(G) Started game loop"); + + long startTime; //Start time of the loop + long uTime; //update time + long waitTime; //time to wait + + boolean running = true; + + while (running) { + + startTime = System.currentTimeMillis(); + + tick(); + + uTime = System.currentTimeMillis() - startTime; + waitTime = config.getInt("tick_length") - uTime; + + LogManager.LOGGER.info("Wait time : " + waitTime + "ms | Update time: " + uTime + "ms | " + (int) (((double) uTime / waitTime) * 100) + "% load"); + + try { + if (waitTime >= 0) { + Thread.sleep(waitTime); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + + + } + + private void tick() { + gameUniverse.incrementTime(); + + //Process user code + for(User user : gameUniverse.getUsers()){ + user.getCpu().reset(); + user.getCpu().execute(); + +// System.out.println(user.getCpu()); + } + + //Process each worlds + for (World world : gameUniverse.getWorlds()) { + world.update(); + } + + socketServer.tick(); + + LogManager.LOGGER.info("Processed " + gameUniverse.getWorlds().size() + " worlds"); + } + /** + * Save game universe to file in JSON format + * @param file JSON file to save + */ + public void save(File file){ + + try { + FileWriter fileWriter = new FileWriter(file); + + JSONObject universe = gameUniverse.serialise(); + + JSONArray plugins = new JSONArray(); + + for(ServerPlugin plugin : pluginManager.getPlugins()){ + plugins.add(plugin.serialise()); + } + + universe.put("plugins", plugins); + + fileWriter.write(universe.toJSONString()); + fileWriter.close(); + + LogManager.LOGGER.info("Saved to file " + file.getName()); + + } catch (IOException e) { + e.printStackTrace(); + } + + } + + public ServerConfiguration getConfig() { + return config; + } + + public PluginManager getPluginManager() { + return pluginManager; + } + + public void setSocketServer(SocketServer socketServer) { + this.socketServer = socketServer; + } +} diff --git a/Server/src/net/simon987/server/Main.java b/Server/src/net/simon987/server/Main.java new file mode 100644 index 0000000..1611649 --- /dev/null +++ b/Server/src/net/simon987/server/Main.java @@ -0,0 +1,31 @@ +package net.simon987.server; + +import net.simon987.server.logging.LogManager; +import net.simon987.server.webserver.SocketServer; + +import java.io.File; +import java.net.InetSocketAddress; + + +public class Main { + public static void main(String[] args){ + + //todo: User registration + //todo: Display usernames under cubot + + LogManager.initialize(); + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + + //Load + GameServer.INSTANCE.getGameUniverse().load(new File("save.json")); + + SocketServer socketServer = new SocketServer(new InetSocketAddress(config.getString("webSocket_host"), + config.getInt("webSocket_port")), config); + + GameServer.INSTANCE.setSocketServer(socketServer); + + (new Thread(socketServer)).start(); + (new Thread(GameServer.INSTANCE)).start(); + } + +} diff --git a/Server/src/net/simon987/server/ServerConfiguration.java b/Server/src/net/simon987/server/ServerConfiguration.java new file mode 100644 index 0000000..84d715a --- /dev/null +++ b/Server/src/net/simon987/server/ServerConfiguration.java @@ -0,0 +1,42 @@ +package net.simon987.server; + + +import net.simon987.server.logging.LogManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Wrapper for Java Property + */ +public class ServerConfiguration { + + /** + * Properties + */ + private Properties properties; + + public ServerConfiguration(File file) { + try { + properties = new Properties(); + + properties.load(new FileInputStream(file)); + + } catch (IOException e) { + LogManager.LOGGER.severe("Problem loading server configuration: " + e.getMessage()); + } + } + + public int getInt(String key) { + return Integer.valueOf((String) properties.get(key)); + + } + + public String getString(String key) { + + return (String) properties.get(key); + } + +} diff --git a/Server/src/net/simon987/server/assembly/Assembler.java b/Server/src/net/simon987/server/assembly/Assembler.java new file mode 100755 index 0000000..92c702f --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Assembler.java @@ -0,0 +1,514 @@ +package net.simon987.server.assembly; + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.exception.*; +import net.simon987.server.logging.LogManager; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.HashMap; + +/** + * Top-level class for assembly operations. + */ +public class Assembler { + + private ServerConfiguration config; + + private InstructionSet instructionSet; + + private RegisterSet registerSet; + + public Assembler(InstructionSet instructionSet, RegisterSet registerSet, ServerConfiguration config) { + this.instructionSet = instructionSet; + this.registerSet = registerSet; + this.config = config; + } + + /** + * Remove the comment part of a line + * + * @param line The line to trim + * @return The line without its comment part + */ + private static String removeComment(String line) { + if (line.indexOf(';') != -1) { + return line.substring(0, line.indexOf(';')); + } else { + return line; + } + } + + /** + * Remove the label part of a line + * + * @param line The line to trim + * @return The line without its label part + */ + private static String removeLabel(String line) { + if (line.indexOf(':') != -1) { + return line.substring(line.indexOf(':') + 1); + } else { + return line; + } + } + + /** + * Check for and save the origin + * + * @param line Current line. Assuming that the comments & labels are removed + * @param result Current line number + */ + private static void checkForORGInstruction(String line, AssemblyResult result, int currentLine) + throws AssemblyException { + line = removeComment(line); + line = removeLabel(line); + + //Split string + String[] tokens = line.trim().split("\\s+"); + String mnemonic = tokens[0]; + + if (mnemonic.toUpperCase().equals("ORG")) { + if (tokens.length > 1) { + try { + result.origin = (Integer.decode(tokens[1])); + throw new PseudoInstructionException(currentLine); + } catch (NumberFormatException e) { + throw new InvalidOperandException("Invalid operand \"" + tokens[1] + '"', currentLine); + } + } + } + } + + /** + * Check for labels in a line and save it + * + * @param line Line to check + * @param result Current assembly result + * @param currentOffset Current offset in bytes + */ + private static void checkForLabel(String line, AssemblyResult result, char currentOffset) { + + line = removeComment(line); + + //Check for labels + if (line.indexOf(':') != -1) { + + line = line.substring(0, line.indexOf(':')); + String label = line.trim(); + + System.out.println("Label " + label + " @ " + (result.origin + currentOffset)); + result.labels.put(label, (char) (result.origin + currentOffset)); + } + } + + /** + * Check if a line is empty + * + * @param line Line to check + * @return true if a line only contains white space + */ + private static boolean isLineEmpty(String line) { + return line.replaceAll("\\s+", "").isEmpty(); + } + + /** + * Parse the DW instruction (Define word). Handles DUP operator + * + * @param line Current line. assuming that comments & labels are removed + * @param currentLine Current line number + * @param labels Map of labels + * @return Encoded instruction, null if the line is not a DW instruction + */ + private static byte[] parseDWInstruction(String line, HashMap labels, int currentLine) + throws InvalidOperandException { + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(bos); + + System.out.println(line); + + if (line.substring(0, 2).toUpperCase().equals("DW")) { + + try { + + String[] values = line.substring(2, line.length()).split(","); + + for (String value : values) { + + value = value.trim(); + + String[] valueTokens = value.split("\\s+"); + + //Handle DUP operator + if (valueTokens.length == 2 && valueTokens[1].toUpperCase().contains("DUP(")) { + out.write(parseDUPOperator16(valueTokens, labels, currentLine)); + } else if (labels != null && labels.containsKey(value)) { + //Handle label + out.writeChar(labels.get(value)); + + } else { + //Handle integer value + try { + out.writeChar(Integer.decode(value)); + + } catch (NumberFormatException e) { + //Handle assumed label + if (labels == null) { + + // Write placeholder word + out.writeChar(0); + + } else { + throw new InvalidOperandException("Invalid operand \"" + value + '"', currentLine); + } + } + } + } + + } catch (IOException e) { + e.printStackTrace(); + } + + } else { + return null; + } + + return bos.toByteArray(); + + } + + /** + * Parse the dup operator + * + * @param valueTokens Value tokens e.g. {"8", "DUP(12)"} + * @param labels Map of labels + * @return The encoded instruction + */ + private static byte[] parseDUPOperator16(String[] valueTokens, HashMap labels, int currentLine) + throws InvalidOperandException { + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + + int factor = Integer.decode(valueTokens[0]); + String value = valueTokens[1].substring(4, valueTokens[1].lastIndexOf(')')); + + //Handle label + if (labels != null && labels.containsKey(value)) { + //Label value is casted to byte + for (int i = 0; i < factor; i++) { + char s = labels.get(value); + + out.write(Util.getHigherByte(s)); + out.write(Util.getLowerByte(s)); + } + + } else { + //Handle integer value + char s = (char)(int)Integer.decode(value); + + for (int i = 0; i < factor; i++) { + out.write(Util.getHigherByte(s)); + out.write(Util.getLowerByte(s)); + } + } + + + } catch (NumberFormatException e) { + throw new InvalidOperandException("Usage: DUP()", currentLine); + } + + return out.toByteArray(); + + } + + /** + * Parse the DW instruction (Define word). Handles DUP operator + * + * @param line Current line. assuming that comments & labels are removed + * @param currentLine Current line number + * @return Encoded instruction, null if the line is not a DW instruction + */ + private static byte[] parseDWInstruction(String line, int currentLine) throws AssemblyException { + return parseDWInstruction(line, null, currentLine); + } + + /** + * Check for and handle segment declarations (.text & .data) + * + * @param line Current line + */ + private static void checkForSegmentDeclaration(String line, AssemblyResult result, + int currentLine, int currentOffset) throws AssemblyException { + + String[] tokens = line.split("\\s+"); + + if (tokens[0].toUpperCase().equals(".TEXT")) { + + result.defineSegment(Segment.TEXT, currentLine, currentOffset); + throw new PseudoInstructionException(currentLine); + + } else if (tokens[0].toUpperCase().equals(".DATA")) { + + result.defineSegment(Segment.DATA, currentLine, currentOffset); + throw new PseudoInstructionException(currentLine); + } + } + + /** + * Check for and handle the EQU instruction + * + * @param line Current line. The method is assuming that comments and labels are removed + * @param labels Map of labels. Constants will be added as labels + * @param currentLine Current line number + */ + private static void checkForEQUInstruction(String line, HashMap labels, int currentLine) + throws AssemblyException { + /* the EQU pseudo instruction is equivalent to the #define compiler directive in C/C++ + * usage: constant_name EQU + * A constant treated the same way as a label. + */ + + String[] tokens = line.split("\\s+"); + + + if (line.toUpperCase().contains(" EQU ")) { + if (tokens[1].toUpperCase().equals("EQU") && tokens.length == 3) { + try { + //Save value as a label + labels.put(tokens[0], (char)(int)Integer.decode(tokens[2])); + } catch (NumberFormatException e) { + throw new InvalidOperandException("Usage: constant_name EQU immediate_value", currentLine); + } + } else { + throw new InvalidOperandException("Usage: constant_name EQU immediate_value", currentLine); + } + + throw new PseudoInstructionException(currentLine); + } + } + + /** + * Parses a text and assembles it. The assembler splits the text in + * lines and parses them one by one. It does 3 passes, the first one + * gets the origin of the code, the second one gets the label offsets + * and the third pass encodes the instructions. + * + * @param text text to assemble + * @return the result of the assembly. Includes the assembled code and + * the errors, if any. + */ + public AssemblyResult parse(String text) { + + int currentLine; + + //Split in lines + AssemblyResult result = new AssemblyResult(config); + String[] lines = text.split("\n"); + + LogManager.LOGGER.info("Assembly job started: " + lines.length + " lines to parse."); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + //Pass 1: Get code origin + for (currentLine = 0; currentLine < lines.length; currentLine++) { + try { + checkForORGInstruction(lines[currentLine], result, currentLine); + + } catch (PseudoInstructionException e) { + break; //Origin is set, skip checking the rest + + } catch (AssemblyException e) { + //Ignore error + } + } + + //Pass 2: Save label names and location + char currentOffset = 0; + for (currentLine = 0; currentLine < lines.length; currentLine++) { + try { + checkForLabel(lines[currentLine], result, currentOffset); + + //Increment offset + currentOffset += parseInstruction(lines[currentLine], currentLine, instructionSet).length / 2; + + } catch (AssemblyException e) { + //Ignore error on pass 2 + System.out.println(e); + } + } + + + //Pass 3: encode instructions + currentOffset = 0; + for (currentLine = 0; currentLine < lines.length; currentLine++) { + + String line = lines[currentLine]; + + try { + + line = removeComment(line); + line = removeLabel(line); + + if (isLineEmpty(line)) { + throw new EmptyLineException(currentLine); + } + + //Check for pseudo instructions + checkForSegmentDeclaration(line, result, currentLine, currentOffset); + checkForEQUInstruction(line, result.labels, currentLine); + checkForORGInstruction(line, result, currentLine); + + //Encode instruction + byte[] bytes = parseInstruction(line, currentLine, result.labels, instructionSet); + currentOffset += bytes.length / 2; + out.write(bytes); + + } catch (EmptyLineException | PseudoInstructionException e) { + //Ignore empty lines and pseudo-instructions + } catch (AssemblyException asmE) { + //Save errors on pass3 + result.exceptions.add(asmE); + } catch (IOException ioE) { + ioE.printStackTrace(); + } + } + + result.bytes = out.toByteArray(); + + LogManager.LOGGER.info("Assembled " + result.bytes.length + " bytes (" + result.exceptions.size() + " errors)"); + for (AssemblyException e : result.exceptions) { + LogManager.LOGGER.severe(e.getMessage() + '@' + e.getLine()); + } + LogManager.LOGGER.info('\n' + Util.toHex(result.bytes)); + + return result; + + } + + /** + * Parse an instruction and encode it + * + * @param line Line to parse + * @param currentLine Current line + * @return The encoded instruction + */ + private byte[] parseInstruction(String line, int currentLine, InstructionSet instructionSet) throws AssemblyException { + return parseInstruction(line, currentLine, null, instructionSet, true); + } + + /** + * Parse an instruction and encode it + * + * @param line Line to parse + * @param currentLine Current line + * @param labels List of labels + * @return The encoded instruction + */ + private byte[] parseInstruction(String line, int currentLine, HashMap labels, + InstructionSet instructionSet) + throws AssemblyException { + return parseInstruction(line, currentLine, labels, instructionSet, false); + } + + /** + * Parse an instruction and encode it + * + * @param line Line to parse + * @param currentLine Current line + * @param labels List of labels + * @param assumeLabels Assume that unknown operands are labels + * @return The encoded instruction + */ + private byte[] parseInstruction(String line, int currentLine, HashMap labels, + InstructionSet instructionSet, boolean assumeLabels) + throws AssemblyException { + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + line = removeComment(line); + line = removeLabel(line); + line = line.trim(); + + if (isLineEmpty(line)) { + throw new EmptyLineException(currentLine); + } + + //Split string + String[] tokens = line.trim().split("\\s+"); + String mnemonic = tokens[0]; + + //Check for DW instruction + try { + if (assumeLabels) { + byte[] bytes = parseDWInstruction(line, currentLine); + if (bytes != null) { + out.write(bytes); + return out.toByteArray(); + } + } else { + byte[] bytes = parseDWInstruction(line, labels, currentLine); + if (bytes != null) { + out.write(bytes); + return out.toByteArray(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + if (instructionSet.get(mnemonic) == null) { + throw new InvalidMnemonicException(mnemonic, currentLine); + + } + + //Check operands and encode instruction + if (line.contains(",")) { + //2 operands + String strO1 = line.substring(line.indexOf(mnemonic) + mnemonic.length(), line.indexOf(',')); + String strO2 = line.substring(line.indexOf(',')); + + Operand o1, o2; + + if (assumeLabels) { + o1 = new Operand(strO1, registerSet, currentLine); + o2 = new Operand(strO2, registerSet, currentLine); + } else { + o1 = new Operand(strO1, labels, registerSet, currentLine); + o2 = new Operand(strO2, labels, registerSet, currentLine); + } + + //Encode instruction + //Get instruction by name + instructionSet.get(mnemonic).encode(out, o1, o2, currentLine); + + } else if (tokens.length > 1) { + //1 operand + + String strO1 = line.substring(line.indexOf(mnemonic) + mnemonic.length()); + + Operand o1; + if (assumeLabels) { + o1 = new Operand(strO1, registerSet, currentLine); + } else { + o1 = new Operand(strO1, labels, registerSet, currentLine); + } + + //Encode instruction + //Get instruction by name + instructionSet.get(mnemonic).encode(out, o1, currentLine); + } else { + //No operand + + //Encode instruction + //Get instruction by name + instructionSet.get(mnemonic).encode(out, currentLine); + } + + return out.toByteArray(); + + } +} diff --git a/Server/src/net/simon987/server/assembly/AssemblyResult.java b/Server/src/net/simon987/server/assembly/AssemblyResult.java new file mode 100755 index 0000000..91e6c8b --- /dev/null +++ b/Server/src/net/simon987/server/assembly/AssemblyResult.java @@ -0,0 +1,99 @@ +package net.simon987.server.assembly; + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.exception.AssemblyException; +import net.simon987.server.assembly.exception.DuplicateSegmentException; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Result of an assembly attempt + */ +public class AssemblyResult { + + + /** + * The origin of the program, default is 0x400 + */ + public int origin; + /** + * A list of labels + */ + HashMap labels = new HashMap<>(20); + /** + * List of exceptions encountered during the assembly attempt, + * they will be displayed in the editor + */ + ArrayList exceptions = new ArrayList<>(50); + /** + * Offset of the code segment + */ + private int codeSegmentOffset; + /** + * Line of the code segment definition (for editor icons) + */ + private int codeSegmentLine; + + /** + * The encoded user code (will be incomplete or invalid if the + * assembler encountered an error during assembly) + */ + public byte[] bytes; + /** + * Offset of the data segment, default is 0x4000 + */ + private int dataSegmentOffset; + /** + * Line of the data segment definition (for editor icons) + */ + private int dataSegmentLine; + /** + * Whether or not the code segment is set + */ + private boolean codeSegmentSet = false; + /** + * Whether or not the data segment is set + */ + private boolean dataSegmentSet = false; + + AssemblyResult(ServerConfiguration config) { + origin = config.getInt("org_offset"); + } + + /** + * Define a segment. + * + * @param segment Segment to define + * @param currentOffset Current offset, in bytes of the segment + * @param currentLine Line number of the segment declaration + * @throws DuplicateSegmentException when a segment is defined twice + */ + void defineSegment(Segment segment, int currentLine, int currentOffset) throws DuplicateSegmentException { + + if (segment == Segment.TEXT) { + //Code segment + + if (!codeSegmentSet) { + codeSegmentOffset = origin + currentOffset; + codeSegmentLine = currentLine; + codeSegmentSet = true; + } else { + throw new DuplicateSegmentException(currentLine); + } + + } else { + //Data segment + if (!dataSegmentSet) { + dataSegmentOffset = origin + currentOffset; + dataSegmentLine = currentLine; + dataSegmentSet = true; + } else { + throw new DuplicateSegmentException(currentLine); + } + + } + + } + +} diff --git a/Server/src/net/simon987/server/assembly/CPU.java b/Server/src/net/simon987/server/assembly/CPU.java new file mode 100755 index 0000000..343c9b5 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/CPU.java @@ -0,0 +1,436 @@ +package net.simon987.server.assembly; + +import net.simon987.server.GameServer; +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.exception.CancelledException; +import net.simon987.server.assembly.instruction.*; +import net.simon987.server.event.CpuInitialisationEvent; +import net.simon987.server.event.GameEvent; +import net.simon987.server.io.JSONSerialisable; +import net.simon987.server.logging.LogManager; +import net.simon987.server.user.User; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * CPU: Central Processing Unit. A CPU is capable of reading bytes from + * a Memory object and execute them. A CPU object holds registers objects & + * a Memory object. + */ +public class CPU implements JSONSerialisable{ + + /** + * + */ + private Status status; + + /** + * Memory associated with the CPU, 64kb max + */ + private Memory memory; + + /** + * set of instructions of this CPU + */ + private InstructionSet instructionSet; + + /** + * set of registers of this CPU + */ + private RegisterSet registerSet; + + /** + * Offset of the code segment. The code starts to get + * executed at this address each tick. Defaults to 0x4000 + */ + private int codeSegmentOffset; + + /** + * Instruction pointer, always points to the next instruction + */ + private int ip; + + /** + * List of attached hardware, 'modules' + */ + private HashMap attachedHardware; + + private ServerConfiguration config; + + private long timeout; + + private int registerSetSize; + + /** + * Creates a new CPU + */ + public CPU(ServerConfiguration config, User user) throws CancelledException{ + this.config = config; + instructionSet = new DefaultInstructionSet(); + registerSet = new DefaultRegisterSet(); + attachedHardware = new HashMap<>(); + codeSegmentOffset = config.getInt("org_offset"); + + timeout = config.getInt("user_timeout"); + + instructionSet.add(new JmpInstruction(this)); + instructionSet.add(new JnzInstruction(this)); + instructionSet.add(new JzInstruction(this)); + instructionSet.add(new JgInstruction(this)); + instructionSet.add(new JgeInstruction(this)); + instructionSet.add(new JleInstruction(this)); + instructionSet.add(new JlInstruction(this)); + instructionSet.add(new PushInstruction(this)); + instructionSet.add(new PopInstruction(this)); + instructionSet.add(new CallInstruction(this)); + instructionSet.add(new RetInstruction(this)); + instructionSet.add(new MulInstruction(this)); + instructionSet.add(new DivInstruction(this)); + instructionSet.add(new JnsInstruction(this)); + instructionSet.add(new JsInstruction(this)); + instructionSet.add(new HwiInstruction(this)); + + status = new Status(); + memory = new Memory(config.getInt("memory_size")); + + GameEvent event = new CpuInitialisationEvent(this, user); + GameServer.INSTANCE.getEventDispatcher().dispatch(event); + if(event.isCancelled()){ + throw new CancelledException(); + } + } + + public void reset() { + status.clear(); + registerSet.getRegister("SP").setValue(config.getInt("stack_bottom")); + registerSet.getRegister("BP").setValue(config.getInt("stack_bottom")); + ip = codeSegmentOffset; + } + + public void execute() { + + long startTime = System.currentTimeMillis(); + int counter = 0; + status.clear(); + + registerSetSize = registerSet.size(); + + // status.breakFlag = true; + while (!status.isBreakFlag()) { + counter++; + + if(counter % 1000 == 0){ + if (System.currentTimeMillis() >= (startTime + timeout)) { + System.out.println("CPU Timeout " + this + " after " + counter + "instructions (" + timeout + "ms): " + (double)counter/((double)timeout/1000)/1000000 + "MHz"); + return; + } + } + + //fetch instruction + int machineCode = memory.get(ip); + + /* + * Contents of machineCode should look like this: + * SSSS SDDD DDOO OOOO + * Where S is source, D is destination and O is the opCode + */ + Instruction instruction = instructionSet.get(machineCode & 0x03F); // 0000 0000 00XX XXXX + + int source = (machineCode >> 11) & 0x001F; // XXXX X000 0000 0000 + int destination = (machineCode >> 6) & 0x001F; // 0000 0XXX XX00 0000 + + executeInstruction(instruction, source, destination); +// LogManager.LOGGER.info(instruction.getMnemonic()); + } + double elapsed = (System.currentTimeMillis() - startTime); + System.out.println("----------\n" + counter + " instruction in " + elapsed + "ms : " + (double)counter/(elapsed/1000)/1000000 + "MHz"); + } + + public void executeInstruction(Instruction instruction, int source, int destination) { + + + //Execute the instruction + if (source == 0) { + //No operand (assuming that destination is also null) + ip++; + instruction.execute(status); + } else if (source == Operand.IMMEDIATE_VALUE) { + ip++; + int sourceValue = memory.get(ip); + + if (destination == 0) { + //Single operand + ip++; + instruction.execute(sourceValue, status); + } else if (destination == Operand.IMMEDIATE_VALUE) { + //Destination is an immediate value too + //this shouldn't happen + LogManager.LOGGER.severe("Trying to execute an instruction with 2" + + "immediate values as operands"); //todo remove debug info + + } else if (destination == Operand.IMMEDIATE_VALUE_MEM) { + //Destination is memory immediate + ip += 2; + instruction.execute(memory, memory.get(ip - 1), sourceValue, status); + } else if (destination <= registerSetSize) { + //Destination is a register + ip++; + instruction.execute(registerSet, destination, sourceValue, status); + + } else if (destination <= registerSetSize * 2) { + //Destination is [reg] + ip++; + instruction.execute(memory, registerSet.get(destination - registerSetSize), sourceValue, status); + } else { + //Assuming that destination is [reg + x] + ip += 2; + instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1), + sourceValue, status); + } + + } else if (source == Operand.IMMEDIATE_VALUE_MEM) { + //Source is [x] + ip++; + int sourceValue = memory.get(memory.get(ip)); + + if (destination == 0) { + //Single operand + ip++; + instruction.execute(sourceValue, status); + instruction.execute(memory, memory.get(ip - 1), status); //For POP instruction + } else if (destination == Operand.IMMEDIATE_VALUE) { + //Destination is an immediate value + + //this shouldn't happen + LogManager.LOGGER.severe("Trying to execute an instruction with an" + + "immediate values as dst operand"); //todo remove debug info + } else if (destination == Operand.IMMEDIATE_VALUE_MEM) { + //Destination is memory immediate too + ip += 2; + instruction.execute(memory, memory.get(ip - 1), sourceValue, status); + } else if (destination <= registerSetSize) { + //Destination is a register + ip++; + instruction.execute(registerSet, destination, sourceValue, status); + } else if (destination <= registerSetSize * 2) { + //Destination is [reg] + ip++; + instruction.execute(memory, registerSet.get(destination - registerSetSize), memory, sourceValue, status); + } else { + //Assuming that destination is [reg + x] + ip += 2; + instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1), sourceValue, status); + } + + } else if (source <= registerSetSize) { + //Source is a register + + if (destination == 0) { + //Single operand + ip++; + instruction.execute(registerSet, source, status); + + } else if (destination == Operand.IMMEDIATE_VALUE) { + //Destination is an immediate value + //this shouldn't happen + LogManager.LOGGER.severe("Trying to execute an instruction with an" + + "immediate values as dst operand"); //todo remove debug info + } else if (destination == Operand.IMMEDIATE_VALUE_MEM) { + //Destination is memory immediate + ip += 2; + instruction.execute(memory, memory.get(ip - 1), registerSet, source, status); + } else if (destination <= registerSetSize) { + //Destination is a register too + ip++; + instruction.execute(registerSet, destination, registerSet, source, status); + } else if (destination <= registerSetSize * 2) { + //Destination is [reg] + ip++; + instruction.execute(memory, registerSet.get(destination - registerSetSize), registerSet, source, status); + } else { + //Assuming that destination is [reg + x] + ip += 2; + instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1), + registerSet, source, status); + } + + } else if (source <= registerSetSize * 2) { + //Source is [reg] + if (destination == 0) { + //Single operand + ip++; + instruction.execute(memory, registerSet.get(source), status); + } else if (destination == Operand.IMMEDIATE_VALUE) { + //Destination is an immediate value + //this shouldn't happen + LogManager.LOGGER.severe("Trying to execute an instruction with an" + + "immediate values as dst operand"); //todo remove debug info + } else if (destination == Operand.IMMEDIATE_VALUE_MEM) { + //Destination is an memory immediate + ip++; + instruction.execute(memory, memory.get(ip++), memory, registerSet.get(source - registerSetSize), status); + } else if (destination <= registerSetSize) { + //Destination is a register + ip++; + instruction.execute(registerSet, destination, memory, registerSet.get(source), status); + } else if (destination <= registerSetSize * 2) { + //Destination is [reg] + ip++; + instruction.execute(memory, registerSet.get(destination - registerSetSize), memory, registerSet.get(source), status); + } else { + //Assuming that destination is [reg + x] + ip += 2; + instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1), + memory, registerSet.get(source - registerSetSize), status); + } + } else { + //Assuming that source is [reg + X] + + ip++; + int sourceDisp = memory.get(ip); + + if (destination == 0) { + //Single operand + ip += 1; + instruction.execute(memory, registerSet.get(source - registerSetSize - registerSetSize) + memory.get(ip - 1), status); + + } else if (destination == Operand.IMMEDIATE_VALUE) { + //Destination is an immediate value + //this shouldn't happen + LogManager.LOGGER.severe("Trying to execute an instruction with an" + + "immediate values as dst operand"); //todo remove debug info + } else if (destination == Operand.IMMEDIATE_VALUE_MEM) { + //Destination is memory immediate + ip += 2; + instruction.execute(memory, memory.get(ip - 1), memory, + registerSet.get(source - registerSetSize - registerSetSize) + sourceDisp, status); + } else if (destination < registerSetSize) { + //Destination is a register + ip++; + instruction.execute(registerSet, destination, memory, + registerSet.get(source - registerSetSize - registerSetSize) + sourceDisp, status); + } else if (destination <= registerSetSize * 2) { + //Destination is [reg] + ip++; + instruction.execute(memory, registerSet.get(destination - registerSetSize), memory, + registerSet.get(source - registerSetSize - registerSetSize) + sourceDisp, status); + } else { + //Assuming that destination is [reg + x] + ip += 2; + instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1), + memory, registerSet.get(source - registerSetSize - registerSetSize) + sourceDisp, status); + } + } + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + + json.put("memory", memory.serialise()); + + json.put("registerSet", registerSet.serialise()); + json.put("codeSegmentOffset", codeSegmentOffset); + + JSONArray hardwareList = new JSONArray(); + + for(Integer address : attachedHardware.keySet()){ + + CpuHardware hardware = attachedHardware.get(address); + + if(hardware instanceof JSONSerialisable){ + + JSONObject serialisedHw = ((JSONSerialisable) hardware).serialise(); + serialisedHw.put("address", address); + hardwareList.add(serialisedHw); + } + } + + json.put("hardware", hardwareList); + + return json; + } + + public static CPU deserialize(JSONObject json, User user) throws CancelledException { + + CPU cpu = new CPU(GameServer.INSTANCE.getConfig(), user); + + cpu.codeSegmentOffset = (int)(long)json.get("codeSegmentOffset"); + + JSONArray hardwareList = (JSONArray)json.get("hardware"); + + for(JSONObject serialisedHw : (ArrayList)hardwareList){ + CpuHardware hw = CpuHardware.deserialize(serialisedHw); + hw.setCpu(cpu); + cpu.attachHardware(hw, (int)(long)serialisedHw.get("address")); + } + + cpu.memory = Memory.deserialize((JSONObject)json.get("memory")); + cpu.registerSet = RegisterSet.deserialize((JSONObject) json.get("registerSet")); + + return cpu; + + } + + public InstructionSet getInstructionSet() { + return instructionSet; + } + + public RegisterSet getRegisterSet() { + return registerSet; + } + + public Memory getMemory() { + return memory; + } + + public Status getStatus() { + return status; + } + + public int getIp() { + return ip; + } + + public void setIp(char ip) { + this.ip = ip; + } + + public void setCodeSegmentOffset(int codeSegmentOffset) { + this.codeSegmentOffset = codeSegmentOffset; + } + + public void attachHardware(CpuHardware hardware, int address){ + attachedHardware.put(address, hardware); + } + + public void detachHardware(int address){ + attachedHardware.remove(address); + } + + public boolean hardwareInterrupt(int address){ + CpuHardware hardware = attachedHardware.get(address); + + if(hardware != null){ + hardware.handleInterrupt(status); + return true; + } else { + return false; + } + } + + @Override + public String toString() { + + String str = "------------------------\n"; + str += registerSet.toString(); + str += status.toString(); + str += "------------------------\n"; + + return str; + } +} diff --git a/Server/src/net/simon987/server/assembly/CpuHardware.java b/Server/src/net/simon987/server/assembly/CpuHardware.java new file mode 100644 index 0000000..92ac165 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/CpuHardware.java @@ -0,0 +1,43 @@ +package net.simon987.server.assembly; + + +import net.simon987.server.GameServer; +import net.simon987.server.game.CpuHardwareDeserializer; +import net.simon987.server.io.JSONSerialisable; +import net.simon987.server.plugin.ServerPlugin; +import org.json.simple.JSONObject; + +public abstract class CpuHardware implements JSONSerialisable { + + CPU cpu; + + /** + * Handle an HWI instruction + */ + public abstract void handleInterrupt(Status status); + + public CPU getCpu() { + return cpu; + } + + public void setCpu(CPU cpu) { + this.cpu = cpu; + } + + public static CpuHardware deserialize(JSONObject hwJson){ + + for(ServerPlugin plugin : GameServer.INSTANCE.getPluginManager().getPlugins()){ + + if(plugin instanceof CpuHardwareDeserializer){ + CpuHardware hw = ((CpuHardwareDeserializer) plugin).deserializeHardware(hwJson); + + if(hw != null){ + return hw; + } + } + } + + return null; + } + +} diff --git a/Server/src/net/simon987/server/assembly/DefaultInstructionSet.java b/Server/src/net/simon987/server/assembly/DefaultInstructionSet.java new file mode 100755 index 0000000..bb78f73 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/DefaultInstructionSet.java @@ -0,0 +1,87 @@ +package net.simon987.server.assembly; + +import net.simon987.server.assembly.instruction.*; + +import java.util.HashMap; + +/** + * Default instruction set for the CPU + */ +public class DefaultInstructionSet implements InstructionSet { + + /** + * Map of instructions, stored in opcode : Instruction format + */ + private HashMap instructionMap = new HashMap<>(32); + + private Instruction defaultInstruction; + + /** + * Create an empty instruction set + */ + DefaultInstructionSet() { + Instruction nop = new NopInstruction(); + defaultInstruction = nop; + + add(nop); + add(new BrkInstruction()); + add(new MovInstruction()); + add(new AddInstruction()); + add(new SubInstruction()); + add(new AndInstruction()); + add(new OrInstruction()); + add(new ShlInstruction()); + add(new ShrInstruction()); + add(new XorInstruction()); + add(new TestInstruction()); + add(new CmpInstruction()); + add(new NegInstruction()); + } + + /** + * Get an instruction from its opcode + * + * @param opcode opcode of the instruction + * @return the instruction, default is not found + */ + @Override + public Instruction get(int opcode) { + + Instruction instruction = instructionMap.get(opcode); + if(instruction != null){ + return instruction; + } else { + // System.out.println("Invalid instruction " + opcode); + //Todo: Notify user? Set error flag? + return defaultInstruction; + } + } + + /** + * Add a new instruction to the instructionSet + * + * @param opcode opcode of the instruction + * @param instruction Instruction to add + */ + public void addInstruction(int opcode, Instruction instruction) { + instructionMap.put(opcode, instruction); + } + + + @Override + public Instruction get(String mnemonic) { + for (Instruction ins : instructionMap.values()) { + if (ins.getMnemonic().equalsIgnoreCase(mnemonic)) { + return ins; + } + } + + return null; + } + + + @Override + public void add(Instruction instruction) { + instructionMap.put(instruction.getOpCode(), instruction); + } +} diff --git a/Server/src/net/simon987/server/assembly/DefaultRegisterSet.java b/Server/src/net/simon987/server/assembly/DefaultRegisterSet.java new file mode 100755 index 0000000..7526df1 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/DefaultRegisterSet.java @@ -0,0 +1,22 @@ +package net.simon987.server.assembly; + +/** + * RegisterSet with default values + */ +class DefaultRegisterSet extends RegisterSet { + + + DefaultRegisterSet() { + super(); + + addRegister(1, new Register("A")); + addRegister(2, new Register("B")); + addRegister(3, new Register("C")); + addRegister(4, new Register("D")); + addRegister(5, new Register("X")); + addRegister(6, new Register("Y")); + addRegister(7, new Register("SP")); + addRegister(8, new Register("BP")); + + } +} diff --git a/Server/src/net/simon987/server/assembly/IllegalOperandException.java b/Server/src/net/simon987/server/assembly/IllegalOperandException.java new file mode 100644 index 0000000..a998823 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/IllegalOperandException.java @@ -0,0 +1,9 @@ +package net.simon987.server.assembly; + +import net.simon987.server.assembly.exception.AssemblyException; + +public class IllegalOperandException extends AssemblyException { + public IllegalOperandException(String msg, int line) { + super(msg, line); + } +} diff --git a/Server/src/net/simon987/server/assembly/Instruction.java b/Server/src/net/simon987/server/assembly/Instruction.java new file mode 100755 index 0000000..6d68a48 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Instruction.java @@ -0,0 +1,214 @@ +package net.simon987.server.assembly; + + +import java.io.ByteArrayOutputStream; + +/** + * Represents a instruction type (e.g. MOV) that performs an action + * base on 0-2 Targets + */ +public abstract class Instruction { + + + /** + * Symbolic name of the instruction + */ + private String mnemonic; + + /** + * Opcode of the instruction (6-bit signed integer) + */ + private int opCode; + + /** + * Create a new Instruction + * + * @param mnemonic Mnemonic of the instruction + * @param opCode opCode of the instruction (6-bit signed integer) + */ + public Instruction(String mnemonic, int opCode) { + this.mnemonic = mnemonic; + this.opCode = opCode; + } + + /** + * Execute an instruction with 2 operands + * + * @param dst Destination operand + * @param dstIndex Index of the destination operand + * @param src Source operand + * @param srcIndex Index of the source operand + * @param status Status of the CPU before the execution + * @return Status after the execution + */ + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + return status; + } + + /** + * Execute an instruction with 2 operands, the second one being an immediate operand + * + * @param dst Destination operand + * @param dstIndex Index of the destination operand + * @param src Source operand + * @param status Status of the CPU before execution + * @return Status after the execution + */ + public Status execute(Target dst, int dstIndex, int src, Status status) { + + + return status; + } + + /** + * Execute an instruction with 1 operand + * + * @param src Source operand + * @param srcIndex Index of the source operand + * @param status Status of the CPU before execution + * @return Status after execution + */ + public Status execute(Target src, int srcIndex, Status status) { + + return status; + } + + /** + * Execute an instruction with 1 operand that is an immediate value + * + * @param src Source operand + * @param status Status of the CPU before execution + * @return Status after execution + */ + public Status execute(int src, Status status) { + + return status; + } + + /** + * Execute an instruction that doesn't take any operand + */ + public Status execute(Status status) { + return status; + } + + /** + * Check if the operand combinaison is valid. + * + * @param o1 Destination operand + * @param o2 Source operand + * @return true if valid + */ + private static boolean operandsValid(Operand o1, Operand o2) throws IllegalOperandException { + return o1.getType() != OperandType.IMMEDIATE16; + } + + /** + * Check if the operand is valid for this instruction + * + * @param o1 source operand + * @return true if the specified operand can be used with this instruction + */ + private static boolean operandValid(Operand o1) { + return true; + } + + /** + * Whether or not the instruction is valid without any + * operands + */ + private static boolean noOperandsValid() { + return true; + } + + String getMnemonic() { + return mnemonic; + } + + /** + * Encodes the instruction. Writes the result in the outputStream + * + * @param out encoded bytes will be written here + */ + void encode(ByteArrayOutputStream out, int currentLine) throws IllegalOperandException { + + if (!noOperandsValid()) { + throw new IllegalOperandException("This instruction must have operand(s)!", currentLine); + } + + MachineCode code = new MachineCode(); + code.writeOpcode(opCode); + + for (byte b : code.bytes()) { + out.write(b); + } + } + + void encode(ByteArrayOutputStream out, Operand o1, Operand o2, int currentLine) + throws IllegalOperandException { + MachineCode code = new MachineCode(); + code.writeOpcode(opCode); + + if (!operandsValid(o1, o2)) { + throw new IllegalOperandException("Illegal operand combination : " + o1.getType() + + " and " + o2.getType(), currentLine); + } + + //Source operand + if (o2.getType() == OperandType.REGISTER16 || o2.getType() == OperandType.MEMORY_REG16) { + //operand can be stored in its 5-bit space + code.writeSourceOperand(o2.getValue()); + } else { + //operand needs to be stored in another word + code.writeSourceOperand(o2.getValue()); + code.appendWord((char) o2.getData()); + } + + //Destination operand + if (o1.getType() == OperandType.REGISTER16 || o1.getType() == OperandType.MEMORY_REG16) { + //operand can be stored in its 5-bit space + code.writeDestinationOperand(o1.getValue()); + } else { + //operand needs to be stored in another word + code.writeDestinationOperand(o1.getValue()); + code.appendWord((char) o1.getData()); + } + + for (byte b : code.bytes()) { + out.write(b); + } + } + + void encode(ByteArrayOutputStream out, Operand o1, int currentLine) + throws IllegalOperandException { + MachineCode code = new MachineCode(); + code.writeOpcode(opCode); + + if (!operandValid(o1)) { + throw new IllegalOperandException("Illegal operand combination: " + o1.getType() + " (none)", currentLine); + } + + //Source operand + if (o1.getType() == OperandType.REGISTER16 || o1.getType() == OperandType.MEMORY_REG16) { + //operand can be stored in its 5-bit space + code.writeSourceOperand(o1.getValue()); + } else { + //operand needs to be stored in another word + code.writeSourceOperand(o1.getValue()); + code.appendWord((char) o1.getData()); + } + + //Destination bits are left blank + + System.out.println("o1: " + o1.getType()); + + for (byte b : code.bytes()) { + out.write(b); + } + } + + int getOpCode() { + return opCode; + } +} diff --git a/Server/src/net/simon987/server/assembly/InstructionSet.java b/Server/src/net/simon987/server/assembly/InstructionSet.java new file mode 100755 index 0000000..ef36146 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/InstructionSet.java @@ -0,0 +1,33 @@ +package net.simon987.server.assembly; + +/** + * A set of instructions for a CPU. + *

+ * Defines what a CPU can do + */ +public interface InstructionSet { + + /** + * Get an instruction by its opcode + * + * @param opcode opcode of the instruction + * @return the instruction, null is not found + */ + Instruction get(int opcode); + + /** + * Get an instruction by its mnemonic + * + * @param mnemonic mnemonic of the instruction, not case sensitive + * @return the instruction, if not found, the default instruction is returned + */ + Instruction get(String mnemonic); + + /** + * Add an instruction to the set + * + * @param instruction instruction to add + */ + void add(Instruction instruction); + +} diff --git a/Server/src/net/simon987/server/assembly/MachineCode.java b/Server/src/net/simon987/server/assembly/MachineCode.java new file mode 100755 index 0000000..bd3355b --- /dev/null +++ b/Server/src/net/simon987/server/assembly/MachineCode.java @@ -0,0 +1,101 @@ +package net.simon987.server.assembly; + +import net.simon987.server.logging.LogManager; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; + + +/** + * Represents an encoded instruction. this class is used to easily + * write to an 16bit value. + */ +class MachineCode { + + /** + * Value of the initial 2-byte instruction + */ + private char value; + + /** + * Appended words after the instruction bytes. Used to store immediate values + */ + private ArrayList additionalWords = new ArrayList<>(2); + + /** + * Write the opCode in the 6 least significant bit + * + * @param opCode signed 6-bit integer (value 0-63) + */ + void writeOpcode(int opCode) { + + if (opCode < 0 || opCode > 63) { + LogManager.LOGGER.severe("Couldn't write the opCode for instruction :" + opCode); + } else { + + //OpCode is the 6 least significant bits + value &= 0xFFC0; // 1111 1111 1100 0000 mask last 6 bits + value |= opCode; + } + } + + /** + * Write the source operand in the bits 6-11 (bit 0 being the least significant) + * + * @param src signed 5-bit integer (value 0-31) + */ + void writeSourceOperand(int src) { + + if (src < 0 || src > 31) { + LogManager.LOGGER.severe("Couldn't write the scr operand for instruction :" + src); + } else { + + //Src is the 5 most significant bits + value &= 0x07FF; //0000 0111 1111 1111 + src <<= 11; //XXXX X000 0000 0000 + value |= src; + } + } + + /** + * Write the destination operand in the 5 most significant bits + * + * @param dst signed 5-bit integer (value 0-31) + */ + void writeDestinationOperand(int dst) { + if (dst < 0 || dst > 31) { + LogManager.LOGGER.severe("Couldn't write the dst operand for instruction :" + dst); + } else { + + //Src is the 5 most significant bits + value &= 0xF83F; //1111 1000 0011 1111 + dst <<= 6; //0000 0XXX XX00 0000 + value |= dst; + } + } + + void appendWord(char word) { + additionalWords.add(word); + } + + /** + * Get the bytes of the code + */ + byte[] bytes() { + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + + out.write(value >> 8); + out.write(value); + + + for (Character s : additionalWords) { + out.write(s >> 8); + out.write(s); + } + + return out.toByteArray(); + } + +} diff --git a/Server/src/net/simon987/server/assembly/Memory.java b/Server/src/net/simon987/server/assembly/Memory.java new file mode 100755 index 0000000..d8cc9a9 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Memory.java @@ -0,0 +1,160 @@ +package net.simon987.server.assembly; + + +import net.simon987.server.io.JSONSerialisable; +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Base64; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterOutputStream; + +/** + * Represents the available memory for a CPU in the game universe + */ +public class Memory implements Target, JSONSerialisable { + + + /** + * Contents of the memory + */ + private byte[] bytes; + + /** + * Create an empty Memory object + * + * @param size Size of the memory, in words + */ + public Memory(int size) { + bytes = new byte[size]; + } + + /** + * Get the value at an address + * + * @param address Address of the value + * @return 16-bit value at the specified address + */ + @Override + public int get(int address) { + address = (char)address * 2; //Because our Memory is only divisible by 16bits + + if (address < 0 || address + 2 > bytes.length) { + LogManager.LOGGER.info("DEBUG: Trying to get memory out of bounds " + address); + return 0; + } + + return (((bytes[address] & 0xFF) << 8) | (bytes[address + 1] & 0xFF)); + } + + /** + * Write x words from an array at an offset + */ + public boolean write(int offset, byte[] bytes, int count) { + + offset = (char)offset * 2; + + + if (offset + count > this.bytes.length || count < 0 || offset < 0) { + return false; + } + + System.arraycopy(bytes, 0, this.bytes, offset, count); + return true; + } + + /** + * Set the value at an address + * + * @param address address of the value to change + * @param value 16-bit value to set + */ + @Override + public void set(int address, int value) { + + address = (char)address * 2; + + + if (address < 0 || address + 2 > bytes.length) { + LogManager.LOGGER.info("DEBUG: Trying to set memory out of bounds: " + address); + return; + } + + bytes[address] = (byte) ((value >> 8) & 0xFF); + bytes[address + 1] = (byte) (value & 0xFF); + } + + /** + * Fill the memory with 0s + */ + public void clear() { + Arrays.fill(bytes, (byte) 0); + } + + /** + * Get byte array of the Memory object + */ + public byte[] getBytes() { + return bytes; + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + + try { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION, true); + DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(stream, compressor); + deflaterOutputStream.write(bytes); + deflaterOutputStream.close(); + byte[] compressedBytes = stream.toByteArray(); + + json.put("zipBytes", new String(Base64.getEncoder().encode(compressedBytes))); + + } catch (IOException e) { + e.printStackTrace(); + } + + //To deflate + + /* + ByteArrayOutputStream stream2 = new ByteArrayOutputStream(); + Inflater decompresser = new Inflater(true); + InflaterOutputStream inflaterOutputStream = new InflaterOutputStream(stream2, decompresser); + inflaterOutputStream.write(output); + inflaterOutputStream.close(); + byte[] output2 = stream2.toByteArray(); + */ + + + return json; + } + + public static Memory deserialize(JSONObject json){ + + Memory memory = new Memory(0); + byte[] compressedBytes = Base64.getDecoder().decode((String)json.get("zipBytes")); + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Inflater decompressor = new Inflater(true); + InflaterOutputStream inflaterOutputStream = new InflaterOutputStream(baos, decompressor); + inflaterOutputStream.write(compressedBytes); + inflaterOutputStream.close(); + + memory.bytes = baos.toByteArray(); + + } catch (IOException e) { + e.printStackTrace(); + } + + return memory; + } +} diff --git a/Server/src/net/simon987/server/assembly/Operand.java b/Server/src/net/simon987/server/assembly/Operand.java new file mode 100755 index 0000000..a413400 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Operand.java @@ -0,0 +1,235 @@ +package net.simon987.server.assembly; + +import net.simon987.server.assembly.exception.InvalidOperandException; + +import java.util.HashMap; + +/** + * Represents an operand of an instruction. An operand can refer to a + * location in memory, a register or an immediate value + */ +public class Operand { + + static final int IMMEDIATE_VALUE = 0b11111; //1 1111 + + static final int IMMEDIATE_VALUE_MEM = 0b11110; //1 1110 + + /** + * The actual text of the operand (e.g. "[AX]") + */ + private String text; + + /** + * Type of the operand + */ + private OperandType type; + + /** + * Value of the the operand, this is the part that will + * written into the instruction. + */ + private int value = 0; + + /** + * Data of the operand. This will be appended after the instruction. + * For example, "[AX+3]" value={index of AX] + {number of registers}, Data=3 + */ + private int data = 0; + + /** + * Create an Operand from text. It assumes that the numerical values that can't be + * parsed are labels that are not defined yet. + * + * @param text Text of the operand + * @param line Line of the instruction. Will be used to report exceptions + */ + public Operand(String text, RegisterSet registerSet, int line) throws InvalidOperandException { + this(text, null, registerSet, line); + } + + /** + * Creates an Operand from text. If labels is not null, it will be used to parse the + * operand. + * + * @param text Text of the operand + * @param labels Map of labels + * @param line Line of the instruction. Will be used to report exceptions + */ + public Operand(String text, HashMap labels, RegisterSet registerSet, int line) + throws InvalidOperandException { + + this.text = text.replace(",", ""); + this.text = this.text.trim(); + + + if (!parseImmediate(this.text) && !parseReg(this.text, registerSet) && !parseLabel(this.text, labels)) { + if (this.text.startsWith("[") && this.text.endsWith("]")) { + + //Remove []s + this.text = this.text.substring(1, this.text.length() - 1); + + if (parseImmediate(this.text) || parseLabel(this.text, labels)) { + //Operand refers to memory + type = OperandType.MEMORY_IMM16; + value = Operand.IMMEDIATE_VALUE_MEM; + + } else if (!parseRegExpr(registerSet, labels)) { + + if (labels == null) { + type = OperandType.MEMORY_IMM16; + data = 0; + } else { + throw new InvalidOperandException("Invalid operand " + this.text, line); + } + + } + + } else { + if (labels == null) { + type = OperandType.IMMEDIATE16; + data = 0; + } else { + throw new InvalidOperandException("Invalid operand " + this.text, line); + } + } + } + } + + + /** + * Attempt to parse an integer + * + * @param text Text to parse, can be a label or immediate value (hex or dec) + * @return true if successful, false otherwise + */ + private boolean parseImmediate(String text) { + + text = text.trim(); + + try { + //Try IMM + type = OperandType.IMMEDIATE16; + data = Integer.decode(text); + value = IMMEDIATE_VALUE; + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * Attempt to parse a user-defined label + * + * @param text Text to parse + * @param labels Map of labels + * @return true if parsing is successful, false otherwise + */ + private boolean parseLabel(String text, HashMap labels) { + + text = text.trim(); + + if (labels == null) { + return false; + } else if (labels.containsKey(text)) { + type = OperandType.IMMEDIATE16; + data = labels.get(text); + value = IMMEDIATE_VALUE; + return true; + } else { + return false; + } + } + + /** + * Attempt to parse a register + * + * @param text Text to parse + * @return true if successful + */ + private boolean parseReg(String text, RegisterSet registerSet) { + + int index = registerSet.getIndex(text.trim()); + + if (index != -1) { + value = index; + type = OperandType.REGISTER16; + return true; + } else { + return false; + } + } + + /** + * Attempt to parse a register followed by an expression. + * The expression has to follow this format: REGISTER+X or REGISTER-X. Any amount of white space between + * the terms will be ignored. + * + * @return true if successful + */ + private boolean parseRegExpr(RegisterSet registerSet, HashMap labels) { + + String expr; + + text = text.replaceAll("\\s+", ""); + + if (text.isEmpty()) { + return false; + } + + if (text.length() >= 2 && parseReg(text.substring(0, 2), registerSet)) { + expr = text.substring(2); + } else if (parseReg(text.substring(0, 1), registerSet)) { + //Starts with 1-char register + expr = text.substring(1); + + } else { + return false; + } + + if (expr.replaceAll("\\s+", "").isEmpty()) { + //No data specified + type = OperandType.MEMORY_REG16; + value += registerSet.size(); //refers to memory. + data = 0; + return true; + } + + //Remove white space + expr = expr.replaceAll("\\s+", ""); + + try { + type = OperandType.MEMORY_REG_DISP16; + + if (labels != null) { + + Character address = labels.get(expr.replaceAll("[^A-Za-z0-9_]", "")); + if (address != null) { + data = address; + value += registerSet.size() * 2;//refers to memory with disp + + return true; + } + } + + //label is invalid + + data = Integer.decode(expr); + value += registerSet.size() * 2; //refers to memory with disp + return true; + } catch (NumberFormatException e) { + return false; + } + } + + OperandType getType() { + return type; + } + + public int getValue() { + return value; + } + + int getData() { + return data; + } +} diff --git a/Server/src/net/simon987/server/assembly/OperandType.java b/Server/src/net/simon987/server/assembly/OperandType.java new file mode 100755 index 0000000..39c6e90 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/OperandType.java @@ -0,0 +1,26 @@ +package net.simon987.server.assembly; + +/** + * Types of an operand + */ +public enum OperandType { + + REGISTER16("16-bit Register"), + MEMORY_IMM16("16-bit Memory referred by immediate"), + MEMORY_REG16("16-bit Memory referred by register"), + MEMORY_REG_DISP16("16-bit Memory referred by register with displacement"), + IMMEDIATE16("16-bit Immediate"); + + /** + * Description of the Operand type + */ + private String description; + + public String getDescription() { + return description; + } + + OperandType(String desc) { + this.description = desc; + } +} diff --git a/Server/src/net/simon987/server/assembly/Register.java b/Server/src/net/simon987/server/assembly/Register.java new file mode 100755 index 0000000..9a240f7 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Register.java @@ -0,0 +1,50 @@ +package net.simon987.server.assembly; + +/** + * Represents a register in a cpu + */ +public class Register { + + /** + * Name of the register + */ + private String name; + + /** + * 16-bit value of the register + */ + private char value = 0; + + /** + * Create a new Register + * + * @param name Name of the register, always set in Upper case + */ + public Register(String name) { + this.name = name.toUpperCase(); + } + + public String getName() { + return name; + } + + public char getValue() { + return value; + } + + /** + * Set value of the register. + * + * @param value value to set. It is casted to char + */ + public void setValue(int value) { + this.value = (char) value; + } + + @Override + public String toString() { + + return name + "=" + value; + + } +} diff --git a/Server/src/net/simon987/server/assembly/RegisterSet.java b/Server/src/net/simon987/server/assembly/RegisterSet.java new file mode 100755 index 0000000..552b202 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/RegisterSet.java @@ -0,0 +1,191 @@ +package net.simon987.server.assembly; + + +import net.simon987.server.io.JSONSerialisable; +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * A set of registers for a CPU + */ +public class RegisterSet implements Target, JSONSerialisable { + + /** + * List of registers + */ + private HashMap registers = new HashMap<>(8); + + + /** + * Create an empty Register set + */ + public RegisterSet() { + + } + + /** + * Get the index of a Register by its name + * This method assumes that the + * + * @param name Name of the register + * @return index of the register, -1 if not found + */ + int getIndex(String name) { + + name = name.toUpperCase(); + + for (Integer i : registers.keySet()) { + if (registers.get(i).getName().equals(name)) { + + return i; + } + } + + return -1; + } + + /** + * Get a register by its index + * + * @param index index of the register + */ + public Register getRegister(int index) { + return registers.get(index); + } + + /** + * Get a register by its name (e.g. "AX") + * + * @param name Name of the register, case insensitive + */ + public Register getRegister(String name) { + + name = name.toUpperCase(); + + for (Register r : registers.values()) { + if (r.getName().equals(name)) { + return r; + } + } + + return null; + } + + /** + * Get the value of a register + * + * @param address Address of the value. Can refer to a memory address or the index + * of a register + * @return 16-bit value of a register + */ + @Override + public int get(int address) { + + Register register = registers.get(address); + + if(register != null){ + return register.getValue(); + } else { + return 0; + } + + } + + /** + * Set the value of a register + * + * @param address index of the value to change + * @param value value to set + */ + @Override + public void set(int address, int value) { + + Register register = registers.get(address); + + if(register != null){ + register.setValue(value); + } else { + LogManager.LOGGER.info("DEBUG: trying to set unknown reg index : " + address); + } + } + + public void put(int index, Register register) { + registers.put(index, register); + } + + public void clear() { + for (Register register : registers.values()) { + register.setValue(0); + } + } + + /** + * Add a register to the register set + *

+ * the register set will break if the indexes of the registers + * are not consecutive, starting at address 1. + * + * @param index Index of the register + * @param reg Register to add + */ + void addRegister(int index, Register reg) { + registers.put(index, reg); + } + + int size() { + return registers.size(); + } + + + @Override + public JSONObject serialise() { + JSONArray registers = new JSONArray(); + for(Integer index : this.registers.keySet()){ + JSONObject register = new JSONObject(); + + register.put("index", index); + register.put("name", getRegister(index).getName()); + register.put("value", getRegister(index).getValue()); + + registers.add(register); + } + + JSONObject json = new JSONObject(); + json.put("registers", registers); + + return json; + } + + public static RegisterSet deserialize(JSONObject json) { + + RegisterSet registerSet = new RegisterSet(); + + JSONArray registers = (JSONArray)json.get("registers"); + + for(JSONObject jsonRegister : (ArrayList)registers){ + + Register register = new Register((String)jsonRegister.get("name")); + register.setValue((int)(long)jsonRegister.get("value")); + + registerSet.registers.put((int)(long)jsonRegister.get("index"), register); + + } + + return registerSet; + } + + @Override + public String toString() { + String str = ""; + + for(Integer index: registers.keySet()){ + str += index + " " + registers.get(index).getName() + "=" + Util.toHex(registers.get(index).getValue()) + "\n"; + } + + return str; + } +} diff --git a/Server/src/net/simon987/server/assembly/Segment.java b/Server/src/net/simon987/server/assembly/Segment.java new file mode 100755 index 0000000..44cd7e8 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Segment.java @@ -0,0 +1,21 @@ +package net.simon987.server.assembly; + +/** + * Section of a user-created program. + * The execution will start at the beginning of the code + * segment and a warning message will be displayed when execution + * reached the data segment during debugging + */ +public enum Segment { + + /** + * Code section of the program. Contains executable code + */ + TEXT, + + /** + * Data section of the program. Contains initialised data + */ + DATA + +} diff --git a/Server/src/net/simon987/server/assembly/Status.java b/Server/src/net/simon987/server/assembly/Status.java new file mode 100755 index 0000000..4b370a6 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Status.java @@ -0,0 +1,135 @@ +package net.simon987.server.assembly; + +/** + * Represents the state of the processor + */ +public class Status { + + /** + * Set to true when the result of + * an 8/16bit operation is too big to fit + * in a 8/16bit register or memory + */ + private boolean carryFlag = false; + + /** + * Set to true when the result of an operation + * is equal to zero + */ + private boolean zeroFlag; + + /** + * Set to true when the most significant + * (highest) bit of the result is set + */ + private boolean signFlag = false; + + /** + * Set to true when the result of + * a signed operation + * * (+) + (+) = (-) + * * (-) + (-) = (+) + */ + private boolean overflowFlag = false; + + /** + * CPU execution will stop when this flag + * is set + */ + private boolean breakFlag = false; + + /** + * Set when an error occurred (division by 0, ) + */ + private boolean errorFlag = false; + + /** + * Unset all flags + */ + public void clear() { + carryFlag = false; + zeroFlag = false; + signFlag = false; + overflowFlag = false; + breakFlag = false; + errorFlag = false; + } + + public String toString() { + return "" + (signFlag ? 1 : 0) + ' ' + + (zeroFlag ? 1 : 0) + ' ' + + (carryFlag ? 1 : 0) + ' ' + + (overflowFlag ? 1 : 0) + '\n'; + } + + /** + * Set to true when the result of + * an 8/16bit operation is too big to fit + * in a 8/16bit register or memory + */ + public boolean isCarryFlag() { + return carryFlag; + } + + public void setCarryFlag(boolean carryFlag) { + this.carryFlag = carryFlag; + } + + /** + * Set to true when the result of an operation + * is equal to zero + */ + public boolean isZeroFlag() { + return zeroFlag; + } + + public void setZeroFlag(boolean zeroFlag) { + this.zeroFlag = zeroFlag; + } + + /** + * Set to true when the most significant + * (highest) bit of the result is set + */ + public boolean isSignFlag() { + return signFlag; + } + + public void setSignFlag(boolean signFlag) { + this.signFlag = signFlag; + } + + /** + * Set to true when the result of + * a signed operation + * * (+) + (+) = (-) + * * (-) + (-) = (+) + */ + public boolean isOverflowFlag() { + return overflowFlag; + } + + public void setOverflowFlag(boolean overflowFlag) { + this.overflowFlag = overflowFlag; + } + + /** + * CPU execution will stop when this flag + * is set + */ + public boolean isBreakFlag() { + return breakFlag; + } + + public void setBreakFlag(boolean breakFlag) { + this.breakFlag = breakFlag; + } + + public boolean isErrorFlag() { + return errorFlag; + } + + public void setErrorFlag(boolean errorFlag) { + this.errorFlag = errorFlag; + } +} diff --git a/Server/src/net/simon987/server/assembly/Target.java b/Server/src/net/simon987/server/assembly/Target.java new file mode 100755 index 0000000..9c8ce58 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Target.java @@ -0,0 +1,30 @@ +package net.simon987.server.assembly; + +/** + * A Target is a location that can be read and written to during + * the execution of an instruction. + *

+ * For example: MOV dstTARGET, srcTARGET + *

+ * A target is usually Memory or Register + */ +public interface Target { + + /** + * Get a value from a Target at an address. + * + * @param address Address of the value. Can refer to a memory address or the index + * of a register + * @return value at specified address + */ + int get(int address); + + /** + * Set a value at an address + * + * @param address address of the value to change + * @param value value to set + */ + void set(int address, int value); + +} diff --git a/Server/src/net/simon987/server/assembly/Util.java b/Server/src/net/simon987/server/assembly/Util.java new file mode 100755 index 0000000..d67c5b3 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/Util.java @@ -0,0 +1,115 @@ +package net.simon987.server.assembly; + +/** + * Set of utility functions related to assembly language parsing and execution + */ +public class Util { + + /** + * Get the lower byte of a word + */ + static byte getLowerByte(char value) { + return (byte) (value & 0x00FF); + } + + /** + * Get the higher byte of a word + */ + public static byte getHigherByte(char value) { + return (byte) ((value >> 8) & 0x00FF); + } + + public static char getHigherWord(int value) { + return (char) ((value >> 16) & 0x0000FFFF); + } + + public static char getLowerWord(int value) { + return (char) (value & 0x0000FFFF); + } + + /** + * Convert a 32bit value to a unsigned 8bit value + */ + public static int uByte(int b) { + return (b) & 0x00FF; + } + + /** + * Convert a 32bit value to a unsigned 16bit value + */ + private static int uShort(int s) { + return s & 0x0000FFFF; + } + + public static String toHex(int a){ + return String.format("%04X ", uShort(a)); + } + + public static String toHex(byte[] byteArray) { + + String result = ""; + + int count = 0; + + for (byte b : byteArray) { + result += String.format("%02X ", b); + if (count == 16) { + count = -1; + result += "\n"; + } + count++; + } + + return result; + } + + /** + * Check if a 16bit value is negative using two's complement representation + * + * @param result 16bit integer + */ + public static boolean checkSign16(int result) { + result = Util.uShort(result); + return (result >> 15) == 1; + } + + /** + * Check if the overflow flag should be set for an + * add operation. + * + * @param a summand + * @param b summand + */ + public static boolean checkOverFlowAdd16(int a, int b) { + boolean aSign = (Util.uShort(a) >> 15) == 1; + boolean bSign = (Util.uShort(b) >> 15) == 1; + + return aSign == bSign && aSign != checkSign16(a + b); + } + + /** + * Check if the overflow flag should be set for a + * sub operation. + * + * @param a minuend + * @param b subtrahend + */ + public static boolean checkOverFlowSub16(int a, int b) { + return checkOverFlowAdd16(a, -b); + } + + /** + * Check if the carry flag should be set + * + * @param result Result of a 16bit operation + */ + public static boolean checkCarry16(int result) { + return ((result) & 0x10000) == 0x10000; //Check if 17th bit is set + } + + + public static int manhattanDist(int x1, int y1, int x2, int y2) { + return Math.abs(x1 - x2) + Math.abs(y1 - y2); + + } +} diff --git a/Server/src/net/simon987/server/assembly/exception/AssemblyException.java b/Server/src/net/simon987/server/assembly/exception/AssemblyException.java new file mode 100755 index 0000000..0ada13c --- /dev/null +++ b/Server/src/net/simon987/server/assembly/exception/AssemblyException.java @@ -0,0 +1,30 @@ +package net.simon987.server.assembly.exception; + +/** + * Threw when a problem is encountered while parsing a line + * of a user's code, making it impossible to translate it to + * binary code. + */ +public class AssemblyException extends Exception { + + /** + * Line offset in the user's code. + */ + private int line; + + /** + * Create a new Assembly Exception + * + * @param msg Message of the exception + * @param line Line offset in the user's code. + */ + public AssemblyException(String msg, int line) { + super(msg); + this.line = line; + } + + public int getLine() { + return line; + } + +} diff --git a/Server/src/net/simon987/server/assembly/exception/CancelledException.java b/Server/src/net/simon987/server/assembly/exception/CancelledException.java new file mode 100644 index 0000000..a3aafaf --- /dev/null +++ b/Server/src/net/simon987/server/assembly/exception/CancelledException.java @@ -0,0 +1,7 @@ +package net.simon987.server.assembly.exception; + +public class CancelledException extends Exception { + public CancelledException() { + super("CPU Initialisation was cancelled"); + } +} diff --git a/Server/src/net/simon987/server/assembly/exception/DuplicateSegmentException.java b/Server/src/net/simon987/server/assembly/exception/DuplicateSegmentException.java new file mode 100755 index 0000000..f9e4f18 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/exception/DuplicateSegmentException.java @@ -0,0 +1,19 @@ +package net.simon987.server.assembly.exception; + +/** + * Threw when a user attempts to define the same section twice + */ +public class DuplicateSegmentException extends AssemblyException { + + /** + * Message of the exception + */ + private static final String message = "Segments can only be defined once"; + + /** + * Create a new Duplicate Segment Exception + */ + public DuplicateSegmentException(int line) { + super(message, line); + } +} diff --git a/Server/src/net/simon987/server/assembly/exception/EmptyLineException.java b/Server/src/net/simon987/server/assembly/exception/EmptyLineException.java new file mode 100755 index 0000000..e2d7b83 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/exception/EmptyLineException.java @@ -0,0 +1,10 @@ +package net.simon987.server.assembly.exception; + +/** + * Threw when the parser encounters an empty line + */ +public class EmptyLineException extends AssemblyException { + public EmptyLineException(int line) { + super("Encountered empty line", line); + } +} diff --git a/Server/src/net/simon987/server/assembly/exception/InvalidMnemonicException.java b/Server/src/net/simon987/server/assembly/exception/InvalidMnemonicException.java new file mode 100755 index 0000000..ed0d787 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/exception/InvalidMnemonicException.java @@ -0,0 +1,10 @@ +package net.simon987.server.assembly.exception; + +/** + * Threw when the parse encounters an invalid mnemonic + */ +public class InvalidMnemonicException extends AssemblyException { + public InvalidMnemonicException(String mnemonic, int line) { + super("Unknown mnemonic \"" + mnemonic + "\"", line); + } +} diff --git a/Server/src/net/simon987/server/assembly/exception/InvalidOperandException.java b/Server/src/net/simon987/server/assembly/exception/InvalidOperandException.java new file mode 100755 index 0000000..04d730d --- /dev/null +++ b/Server/src/net/simon987/server/assembly/exception/InvalidOperandException.java @@ -0,0 +1,18 @@ +package net.simon987.server.assembly.exception; + +/** + * Threw when the Assembler attempts to parse a malformed or invalid operand + */ +public class InvalidOperandException extends AssemblyException { + + /** + * Creates a new Invalid operand Exception + * + * @param msg Message + * @param line Line offset in the user's code.Used to display an error icon + * in the editor. + */ + public InvalidOperandException(String msg, int line) { + super(msg, line); + } +} diff --git a/Server/src/net/simon987/server/assembly/exception/PseudoInstructionException.java b/Server/src/net/simon987/server/assembly/exception/PseudoInstructionException.java new file mode 100755 index 0000000..a0b76c8 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/exception/PseudoInstructionException.java @@ -0,0 +1,11 @@ +package net.simon987.server.assembly.exception; + +/** + * Threw when the parser encounters a pseudo instruction + * (Instruction that doesn't produce any binary output + */ +public class PseudoInstructionException extends AssemblyException { + public PseudoInstructionException(int line) { + super("Pseudo instruction encountered", line); + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/AddInstruction.java b/Server/src/net/simon987/server/assembly/instruction/AddInstruction.java new file mode 100755 index 0000000..1ab1534 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/AddInstruction.java @@ -0,0 +1,56 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * Add two numbers together, the result is stored in the destination operand + *

+ * ADD A, B + * A = A + B + *

+ */ +public class AddInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 2; + + public AddInstruction() { + super("add", OPCODE); + } + + private static Status add(int a, int b, Status status, Target dst, int dstIndex) { + int result = a + b; + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(Util.checkOverFlowAdd16(a, b)); + status.setCarryFlag(Util.checkCarry16(result)); + + dst.set(dstIndex, result); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + int a = (char)dst.get(dstIndex); + int b = (char)src.get(srcIndex); + + return add(a, b, status, dst, dstIndex); + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + + int a = (char)dst.get(dstIndex); + int b = (char)src; + + return add(a, b, status, dst, dstIndex); + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/AndInstruction.java b/Server/src/net/simon987/server/assembly/instruction/AndInstruction.java new file mode 100755 index 0000000..1df2557 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/AndInstruction.java @@ -0,0 +1,64 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * AND two numbers together, the result is stored in the destination operand + *

+ * AND A, B + * A = A & B + *

+ * FLAGS: OF=0 S=* Z=* X=0 + */ +public class AndInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 4; + + public AndInstruction() { + super("and", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + int a =(char)dst.get(dstIndex); + int b = (char)src.get(srcIndex); + + + int result = (a & b); + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(false); + status.setCarryFlag(false); + + dst.set(dstIndex, result); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + int a = (char)dst.get(dstIndex); + int b = (char)src; + + + int result = (a & b); + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(false); + status.setCarryFlag(false); + + dst.set(dstIndex, result); + + return status; + } + +} diff --git a/Server/src/net/simon987/server/assembly/instruction/BrkInstruction.java b/Server/src/net/simon987/server/assembly/instruction/BrkInstruction.java new file mode 100755 index 0000000..ba713c5 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/BrkInstruction.java @@ -0,0 +1,23 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; + +/** + * BRK (Break) Instruction. Will set the break flag and stop the CPU + * execution + */ +public class BrkInstruction extends Instruction { + + public BrkInstruction() { + super("brk", 0); + } + + @Override + public Status execute(Status status) { + + status.setBreakFlag(true); + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/CallInstruction.java b/Server/src/net/simon987/server/assembly/instruction/CallInstruction.java new file mode 100644 index 0000000..da9fb18 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/CallInstruction.java @@ -0,0 +1,52 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Move the execution (Jump) to an address, and save the current IP value, the execution will return to this value + * after the RET instruction is executed + *
+ * FLAGS are not altered + */ +public class CallInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 21; + + private CPU cpu; + + public CallInstruction(CPU cpu) { + super("call", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + //Push ip + cpu.getRegisterSet().set(7, cpu.getRegisterSet().get(7) - 1); //Decrement SP (stack grows towards smaller addresses) + cpu.getMemory().set(cpu.getRegisterSet().get(7), cpu.getIp()); + + //Jmp + cpu.setIp((char) src.get(srcIndex)); + + return status; + } + + @Override + public Status execute(int src, Status status) { + //Push ip + cpu.getRegisterSet().set(7, cpu.getRegisterSet().get(7) - 1); //Decrement SP (stack grows towards smaller addresses) + cpu.getMemory().set(cpu.getRegisterSet().get(7), cpu.getIp()); + + //Jmp + cpu.setIp((char) src); + + return status; + } +} \ No newline at end of file diff --git a/Server/src/net/simon987/server/assembly/instruction/CmpInstruction.java b/Server/src/net/simon987/server/assembly/instruction/CmpInstruction.java new file mode 100644 index 0000000..06ef08b --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/CmpInstruction.java @@ -0,0 +1,57 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * Compare two numbers. Same as SUB instruction, but the result isn't stored + */ +public class CmpInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 12; + + public CmpInstruction() { + super("cmp", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + + int a = (char)dst.get(dstIndex); + int b = (char)src.get(srcIndex); + + + int result = a - b; + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(Util.checkOverFlowAdd16(a, b)); + status.setCarryFlag(Util.checkCarry16(result)); + + + // System.out.println(a + " ?= " + b); + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + + int a = (char)dst.get(dstIndex); + int b = (char)src; + + int result = a - b; + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(Util.checkOverFlowSub16(a, b)); + status.setCarryFlag(Util.checkCarry16(result)); + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/DivInstruction.java b/Server/src/net/simon987/server/assembly/instruction/DivInstruction.java new file mode 100644 index 0000000..217bc3f --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/DivInstruction.java @@ -0,0 +1,66 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Divide instruction. + *

+ * DIV C + * A = Y:A / C + * Y = Y:A % C + *

+ */ +public class DivInstruction extends Instruction { + + public static final int OPCODE = 24; + + private CPU cpu; + + public DivInstruction(CPU cpu) { + super("div", OPCODE); + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + + //Source = Y:A + int source = ((((char)cpu.getRegisterSet().getRegister("Y").getValue() & 0xFFFF) << 16)) | + ((char)cpu.getRegisterSet().getRegister("A").getValue() & 0xFFFF); + + if (src.get(srcIndex) == 0) { + //Division by 0 + status.setBreakFlag(true); + status.setErrorFlag(true); + } else { + cpu.getRegisterSet().getRegister("A").setValue((char)(source / (char)src.get(srcIndex))); + cpu.getRegisterSet().getRegister("Y").setValue((char)(source % (char)src.get(srcIndex))); + } + + return status; + } + + @Override + public Status execute(int src, Status status) { + + + //Source = Y:A + int source = ((((char)cpu.getRegisterSet().getRegister("Y").getValue() & 0xFFFF) << 16)) | + ((char)cpu.getRegisterSet().getRegister("A").getValue() & 0xFFFF); + + if (src == 0) { + //Division by 0 + status.setBreakFlag(true); + status.setErrorFlag(true); + } else { + cpu.getRegisterSet().getRegister("A").setValue((char)(source / (char)src)); + cpu.getRegisterSet().getRegister("Y").setValue((char)(source % (char)src)); + } + + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/HwiInstruction.java b/Server/src/net/simon987/server/assembly/instruction/HwiInstruction.java new file mode 100755 index 0000000..73877ee --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/HwiInstruction.java @@ -0,0 +1,32 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; + +/** + * Send hardware interupt + * Used to interact with the World using hardware + *

+ */ +public class HwiInstruction extends Instruction { + + public static final int OPCODE = 9; + + private CPU cpu; + + public HwiInstruction(CPU cpu) { + super("hwi", OPCODE); + this.cpu = cpu; + } + + @Override + public Status execute(int src, Status status) { + + status.setErrorFlag(cpu.hardwareInterrupt(src)); + + return status; + } + + +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JgInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JgInstruction.java new file mode 100644 index 0000000..658cc60 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JgInstruction.java @@ -0,0 +1,41 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Created by Gilbert Fortier on 3/11/2017. + */ +public class JgInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 15; + + private CPU cpu; + + public JgInstruction(CPU cpu) { + super("jg", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + if (status.isSignFlag() == status.isOverflowFlag() && !status.isZeroFlag()) { + cpu.setIp((char) src.get(srcIndex)); + } + return status; + } + + @Override + public Status execute(int src, Status status) { + if (status.isSignFlag() == status.isOverflowFlag() && !status.isZeroFlag()) { + cpu.setIp((char) src); + } + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JgeInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JgeInstruction.java new file mode 100644 index 0000000..6d50556 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JgeInstruction.java @@ -0,0 +1,41 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Conditional jump: jump if greater or equal + */ +public class JgeInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 16; + + private CPU cpu; + + public JgeInstruction(CPU cpu) { + super("jge", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + if (status.isSignFlag() == status.isOverflowFlag()) { + cpu.setIp((char) src.get(srcIndex)); + } + return status; + } + + @Override + public Status execute(int src, Status status) { + if (status.isSignFlag() == status.isOverflowFlag()) { + cpu.setIp((char) src); + } + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JlInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JlInstruction.java new file mode 100644 index 0000000..3cdb69c --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JlInstruction.java @@ -0,0 +1,41 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Created by Gilbert Fortier on 3/11/2017. + */ +public class JlInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 17; + + private CPU cpu; + + public JlInstruction(CPU cpu) { + super("jl", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + if (status.isSignFlag() != status.isOverflowFlag()) { + cpu.setIp((char) src.get(srcIndex)); + } + return status; + } + + @Override + public Status execute(int src, Status status) { + if (status.isSignFlag() != status.isOverflowFlag()) { + cpu.setIp((char) src); + } + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JleInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JleInstruction.java new file mode 100644 index 0000000..7118336 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JleInstruction.java @@ -0,0 +1,41 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Created by Gilbert Fortier on 3/11/2017. + */ +public class JleInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 18; + + private CPU cpu; + + public JleInstruction(CPU cpu) { + super("jle", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + if (status.isSignFlag() != status.isOverflowFlag() || status.isZeroFlag()) { + cpu.setIp((char) src.get(srcIndex)); + } + return status; + } + + @Override + public Status execute(int src, Status status) { + if (status.isSignFlag() != status.isOverflowFlag() || status.isZeroFlag()) { + cpu.setIp((char) src); + } + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JmpInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JmpInstruction.java new file mode 100644 index 0000000..da24b8e --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JmpInstruction.java @@ -0,0 +1,39 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Created by Gilbert Fortier on 3/11/2017. + */ +public class JmpInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 10; + + private CPU cpu; + + public JmpInstruction(CPU cpu) { + super("jmp", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + + cpu.setIp((char) src.get(srcIndex)); + return status; + } + + @Override + public Status execute(int src, Status status) { + + cpu.setIp((char) src); + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JnsInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JnsInstruction.java new file mode 100644 index 0000000..c132b14 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JnsInstruction.java @@ -0,0 +1,39 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +public class JnsInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 27; + + private CPU cpu; + + public JnsInstruction(CPU cpu) { + super("jns", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + if (!status.isSignFlag()) { + cpu.setIp((char) src.get(srcIndex)); + } + return status; + } + + @Override + public Status execute(int src, Status status) { + if (!status.isSignFlag()) { + cpu.setIp((char) src); + } + return status; + } + +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JnzInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JnzInstruction.java new file mode 100644 index 0000000..efa2c20 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JnzInstruction.java @@ -0,0 +1,41 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Created by Gilbert Fortier on 3/11/2017. + */ +public class JnzInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 13; + + private CPU cpu; + + public JnzInstruction(CPU cpu) { + super("jnz", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + if (!status.isZeroFlag()) { + cpu.setIp((char) src.get(srcIndex)); + } + return status; + } + + @Override + public Status execute(int src, Status status) { + if (!status.isZeroFlag()) { + cpu.setIp((char) src); + } + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JsInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JsInstruction.java new file mode 100644 index 0000000..233e872 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JsInstruction.java @@ -0,0 +1,39 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +public class JsInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 26; + + private CPU cpu; + + public JsInstruction(CPU cpu) { + super("js", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + if (status.isSignFlag()) { + cpu.setIp((char) src.get(srcIndex)); + } + return status; + } + + @Override + public Status execute(int src, Status status) { + if (status.isSignFlag()) { + cpu.setIp((char) src); + } + return status; + } + +} diff --git a/Server/src/net/simon987/server/assembly/instruction/JzInstruction.java b/Server/src/net/simon987/server/assembly/instruction/JzInstruction.java new file mode 100644 index 0000000..9fb35f7 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/JzInstruction.java @@ -0,0 +1,41 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Created by Gilbert Fortier on 3/11/2017. + */ +public class JzInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 14; + + private CPU cpu; + + public JzInstruction(CPU cpu) { + super("jz", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + if (status.isZeroFlag()) { + cpu.setIp((char) src.get(srcIndex)); + } + return status; + } + + @Override + public Status execute(int src, Status status) { + if (status.isZeroFlag()) { + cpu.setIp((char) src); + } + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/MovInstruction.java b/Server/src/net/simon987/server/assembly/instruction/MovInstruction.java new file mode 100755 index 0000000..a4a2f33 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/MovInstruction.java @@ -0,0 +1,49 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * The MOV instruction copies data from a source to a destination. + * it overwrites the value in the destination. The destination can be + * memory or register, while the source can be an immediate value, memory + * or register. + *

+ * The instruction might have unexpected behavior if the an + * operand of type [register + displacement] is used and its sum + * is greater than 0xFFFF (It will overflow) + *

+ *

+ * The MOV instruction doesn't change any flags + *

+ */ +public class MovInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 1; + + public MovInstruction() { + super("mov", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + dst.set(dstIndex, src.get(srcIndex)); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + + dst.set(dstIndex, src); + + + return status; + } + +} diff --git a/Server/src/net/simon987/server/assembly/instruction/MulInstruction.java b/Server/src/net/simon987/server/assembly/instruction/MulInstruction.java new file mode 100644 index 0000000..c3c8fa1 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/MulInstruction.java @@ -0,0 +1,56 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.*; + + +public class MulInstruction extends Instruction { + + public static final int OPCODE = 23; + + private CPU cpu; + + public MulInstruction(CPU cpu) { + super("mul", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + + int result = (char)cpu.getRegisterSet().getRegister("A").getValue() * (char)src.get(srcIndex); + + int hWord = Util.getHigherWord(result); + if (hWord != 0) { + status.setOverflowFlag(true); + status.setCarryFlag(true); + cpu.getRegisterSet().getRegister("Y").setValue(hWord);//Don't overwrite Y register if it's blank + } + status.setOverflowFlag(false); + status.setCarryFlag(false); + cpu.getRegisterSet().set(1, Util.getLowerWord(result)); + + return status; + } + + @Override + public Status execute(int src, Status status) { + + + int result = cpu.getRegisterSet().getRegister("A").getValue() * (char)src; + + int hWord = Util.getHigherWord(result); + if (hWord != 0) { + status.setOverflowFlag(true); + status.setCarryFlag(true); + cpu.getRegisterSet().getRegister("Y").setValue(hWord);//Don't overwrite Y register if it's blank + } + status.setOverflowFlag(false); + status.setCarryFlag(false); + cpu.getRegisterSet().getRegister("A").setValue(Util.getLowerWord(result)); + + return status; + } + + +} diff --git a/Server/src/net/simon987/server/assembly/instruction/NegInstruction.java b/Server/src/net/simon987/server/assembly/instruction/NegInstruction.java new file mode 100644 index 0000000..14b6316 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/NegInstruction.java @@ -0,0 +1,21 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +public class NegInstruction extends Instruction { + + public static final int OPCODE = 25; + + public NegInstruction() { + super("neg", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Status status) { + dst.set(dstIndex, -dst.get(dstIndex)); + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/NopInstruction.java b/Server/src/net/simon987/server/assembly/instruction/NopInstruction.java new file mode 100755 index 0000000..a0c8b95 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/NopInstruction.java @@ -0,0 +1,14 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; + +/** + * NOP (No operation instruction). + * Does nothing + */ +public class NopInstruction extends Instruction { + + public NopInstruction() { + super("nop", 63); + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/OrInstruction.java b/Server/src/net/simon987/server/assembly/instruction/OrInstruction.java new file mode 100644 index 0000000..e17a314 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/OrInstruction.java @@ -0,0 +1,58 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * Created by Gilbert Fortier on 3/12/2017. + */ +public class OrInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 5; + + public OrInstruction() { + super("or", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + int a = (char)dst.get(dstIndex); + int b = (char)src.get(srcIndex); + + + int result = (a | b); + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(false); + status.setCarryFlag(false); + + dst.set(dstIndex, result); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + int a = (char)dst.get(dstIndex); + int b = (char)src; + + + int result = (a | b); + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(false); + status.setCarryFlag(false); + + dst.set(dstIndex, result); + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/PopInstruction.java b/Server/src/net/simon987/server/assembly/instruction/PopInstruction.java new file mode 100644 index 0000000..ce38b67 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/PopInstruction.java @@ -0,0 +1,34 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; + +/** + * Created by simon on 02/06/17. + */ +public class PopInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 20; + + private CPU cpu; + + public PopInstruction(CPU cpu) { + super("pop", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target dst, int dstIndex, Status status) { + + dst.set(dstIndex, cpu.getMemory().get(cpu.getRegisterSet().getRegister("SP").getValue())); + cpu.getRegisterSet().getRegister("SP").setValue(cpu.getRegisterSet().getRegister("SP").getValue() + 1); //Increment SP (stack grows towards smaller) + + return status; + } +} \ No newline at end of file diff --git a/Server/src/net/simon987/server/assembly/instruction/PushInstruction.java b/Server/src/net/simon987/server/assembly/instruction/PushInstruction.java new file mode 100644 index 0000000..48d0e6a --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/PushInstruction.java @@ -0,0 +1,43 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.*; + +/** + * Created by simon on 02/06/17. + */ +public class PushInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 19; + + private CPU cpu; + + public PushInstruction(CPU cpu) { + super("push", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Target src, int srcIndex, Status status) { + + Register sp = cpu.getRegisterSet().getRegister("SP"); + + sp.setValue(sp.getValue() - 1); //Decrement SP (stack grows towards smaller) + cpu.getMemory().set(sp.getValue(), src.get(srcIndex)); + + return status; + } + + @Override + public Status execute(int src, Status status) { + Register sp = cpu.getRegisterSet().getRegister("SP"); + + sp.setValue(sp.getValue() - 1); //Decrement SP (stack grows towards smaller) + cpu.getMemory().set(sp.getValue(), src); + + return status; + } +} \ No newline at end of file diff --git a/Server/src/net/simon987/server/assembly/instruction/RetInstruction.java b/Server/src/net/simon987/server/assembly/instruction/RetInstruction.java new file mode 100644 index 0000000..0d9f250 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/RetInstruction.java @@ -0,0 +1,40 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; + +/** + * Created by simon on 02/06/17. + */ +public class RetInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 22; + + private CPU cpu; + + public RetInstruction(CPU cpu) { + super("ret", OPCODE); + + this.cpu = cpu; + } + + @Override + public Status execute(Status status) { + cpu.setIp((char) cpu.getMemory().get(cpu.getRegisterSet().get(7))); //Jmp + cpu.getRegisterSet().set(7, cpu.getRegisterSet().get(7) + 1); //Inc SP + + return status; + } + + @Override + public Status execute(int src, Status status) { + cpu.setIp((char) cpu.getMemory().get(cpu.getRegisterSet().get(7))); //Jmp + cpu.getRegisterSet().set(7, cpu.getRegisterSet().get(7) + src); //Inc SP + + return status; + } +} \ No newline at end of file diff --git a/Server/src/net/simon987/server/assembly/instruction/ShlInstruction.java b/Server/src/net/simon987/server/assembly/instruction/ShlInstruction.java new file mode 100644 index 0000000..5b30846 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/ShlInstruction.java @@ -0,0 +1,56 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * Created by Gilbert Fortier on 3/12/2017. + */ +public class ShlInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 6; + + public ShlInstruction() { + super("shl", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + int a = (char)dst.get(dstIndex); + int count = (char)src.get(srcIndex); + + int result = a << count; + + status.setOverflowFlag(Util.checkSign16(a) != Util.checkSign16(result)); + status.setCarryFlag(result >> 16 != 0); + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + + dst.set(dstIndex, result); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + int a = (char)dst.get(dstIndex); + int count = (char)src; + + int result = a << count; + + status.setOverflowFlag(Util.checkSign16(a) != Util.checkSign16(result)); + status.setCarryFlag(result >> 16 != 0); + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + + dst.set(dstIndex, result); + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/ShrInstruction.java b/Server/src/net/simon987/server/assembly/instruction/ShrInstruction.java new file mode 100644 index 0000000..98f421d --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/ShrInstruction.java @@ -0,0 +1,72 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * Created by Gilbert Fortier on 3/12/2017. + */ +public class ShrInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 7; + + public ShrInstruction() { + super("shr", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + int a = (char)dst.get(dstIndex); + int count = (char)src.get(srcIndex); + + int result = a >> count; + + if (Util.checkSign16(a) != Util.checkSign16(result)) { + status.setOverflowFlag(true); + } + + /* + SHR 2 carry flag check example + + 0000 0000 0000 0000 1111 1111 1111 1111 << 16 + 1111 1111 1111 1111 0000 0000 0000 0000 >> 2 + 0011 1111 1111 1111 1100 0000 0000 0000 & 0x8000 (15th bit) + 0000 0000 0000 0000 1000 0000 0000 0000 + carry flag is set + */ + + status.setCarryFlag((((a << 16) >> count) & 0x8000) != 0); + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + + dst.set(dstIndex, result); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + int a = (char)dst.get(dstIndex); + int count = (char)src; + + int result = a >> count; + + if (Util.checkSign16(a) != Util.checkSign16(result)) { + status.setOverflowFlag(true); + } + + status.setCarryFlag((((a << 16) >> count) & 0x8000) != 0); + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + + dst.set(dstIndex, result); + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/SubInstruction.java b/Server/src/net/simon987/server/assembly/instruction/SubInstruction.java new file mode 100755 index 0000000..fdaef41 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/SubInstruction.java @@ -0,0 +1,57 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * Created by Gilbert Fortier on 3/12/2017. + */ +public class SubInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 3; + + public SubInstruction() { + super("sub", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + int a = (char)dst.get(dstIndex); + int b = (char)src.get(srcIndex); + + int result = a - b; + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(Util.checkOverFlowAdd16(a, b)); + status.setCarryFlag(Util.checkCarry16(result)); + + dst.set(dstIndex, result); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + + int a = (char)dst.get(dstIndex); + int b = (char)src; + + int result = a - b; + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(Util.checkOverFlowSub16(a, b)); + status.setCarryFlag(Util.checkCarry16(result)); + + dst.set(dstIndex, result); + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/TestInstruction.java b/Server/src/net/simon987/server/assembly/instruction/TestInstruction.java new file mode 100644 index 0000000..0386013 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/TestInstruction.java @@ -0,0 +1,53 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * Created by Gilbert Fortier on 3/12/2017. + */ +public class TestInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 11; + + public TestInstruction() { + super("test", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + int a = (char)dst.get(dstIndex); + int b = (char)src.get(srcIndex); + + int result = (a & b); + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(false); + status.setCarryFlag(false); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + int a = (char)dst.get(dstIndex); + int b = (char)src; + + + int result = (a & b); + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(false); + status.setCarryFlag(false); + + return status; + } +} diff --git a/Server/src/net/simon987/server/assembly/instruction/XorInstruction.java b/Server/src/net/simon987/server/assembly/instruction/XorInstruction.java new file mode 100644 index 0000000..c8bbe26 --- /dev/null +++ b/Server/src/net/simon987/server/assembly/instruction/XorInstruction.java @@ -0,0 +1,58 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Instruction; +import net.simon987.server.assembly.Status; +import net.simon987.server.assembly.Target; +import net.simon987.server.assembly.Util; + +/** + * Created by Gilbert Fortier on 3/12/2017. + */ +public class XorInstruction extends Instruction { + + /** + * Opcode of the instruction + */ + public static final int OPCODE = 5; + + public XorInstruction() { + super("xor", OPCODE); + } + + @Override + public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) { + + int a = (char)dst.get(dstIndex); + int b = (char)src.get(srcIndex); + + + int result = (a ^ b); + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(false); + status.setCarryFlag(false); + + dst.set(dstIndex, result); + + return status; + } + + @Override + public Status execute(Target dst, int dstIndex, int src, Status status) { + int a = (char)dst.get(dstIndex); + int b = (char)src; + + + int result = (a ^ b); + + status.setSignFlag(Util.checkSign16(result)); + status.setZeroFlag((char) result == 0); + status.setOverflowFlag(false); + status.setCarryFlag(false); + + dst.set(dstIndex, result); + + return status; + } +} diff --git a/Server/src/net/simon987/server/event/CpuInitialisationEvent.java b/Server/src/net/simon987/server/event/CpuInitialisationEvent.java new file mode 100644 index 0000000..b2343eb --- /dev/null +++ b/Server/src/net/simon987/server/event/CpuInitialisationEvent.java @@ -0,0 +1,18 @@ +package net.simon987.server.event; + +import net.simon987.server.assembly.CPU; +import net.simon987.server.user.User; + +public class CpuInitialisationEvent extends GameEvent{ + + private User user; + + public CpuInitialisationEvent(CPU cpu, User user) { + setSource(cpu); + this.user = user; + } + + public User getUser() { + return user; + } +} diff --git a/Server/src/net/simon987/server/event/GameEvent.java b/Server/src/net/simon987/server/event/GameEvent.java new file mode 100644 index 0000000..1416f97 --- /dev/null +++ b/Server/src/net/simon987/server/event/GameEvent.java @@ -0,0 +1,33 @@ +package net.simon987.server.event; + + +public class GameEvent { + + /** + * If the event is cancelled the action won't be performed + */ + private boolean cancelled = false; + + /** + * The game object that triggered the event + */ + private Object source; + + + //---------------------------- + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public Object getSource() { + return source; + } + + public void setSource(Object source) { + this.source = source; + } +} diff --git a/Server/src/net/simon987/server/event/GameEventDispatcher.java b/Server/src/net/simon987/server/event/GameEventDispatcher.java new file mode 100644 index 0000000..65de03b --- /dev/null +++ b/Server/src/net/simon987/server/event/GameEventDispatcher.java @@ -0,0 +1,26 @@ +package net.simon987.server.event; + +import net.simon987.server.plugin.PluginManager; +import net.simon987.server.plugin.ServerPlugin; + + +public class GameEventDispatcher { + + private PluginManager pluginManager; + + public GameEventDispatcher(PluginManager pluginManager) { + this.pluginManager = pluginManager; + } + + public void dispatch(GameEvent event){ + for(ServerPlugin plugin: pluginManager.getPlugins()){ + for(GameEventListener listener : plugin.getListeners()){ + if(event.getClass().equals(listener.getListenedEventType())){ + listener.handle(event); + } + } + } + + } + +} diff --git a/Server/src/net/simon987/server/event/GameEventListener.java b/Server/src/net/simon987/server/event/GameEventListener.java new file mode 100644 index 0000000..6218556 --- /dev/null +++ b/Server/src/net/simon987/server/event/GameEventListener.java @@ -0,0 +1,12 @@ +package net.simon987.server.event; + +/** + * Listens for and handles a single type of event + */ +public interface GameEventListener { + + Class getListenedEventType(); + + void handle(GameEvent event); + +} diff --git a/Server/src/net/simon987/server/event/UserCreationEvent.java b/Server/src/net/simon987/server/event/UserCreationEvent.java new file mode 100644 index 0000000..9688eae --- /dev/null +++ b/Server/src/net/simon987/server/event/UserCreationEvent.java @@ -0,0 +1,10 @@ +package net.simon987.server.event; + +import net.simon987.server.user.User; + +public class UserCreationEvent extends GameEvent { + + public UserCreationEvent(User user) { + setSource(user); + } +} diff --git a/Server/src/net/simon987/server/event/WorldGenerationEvent.java b/Server/src/net/simon987/server/event/WorldGenerationEvent.java new file mode 100644 index 0000000..2f8eb8f --- /dev/null +++ b/Server/src/net/simon987/server/event/WorldGenerationEvent.java @@ -0,0 +1,15 @@ +package net.simon987.server.event; + +import net.simon987.server.game.World; + +public class WorldGenerationEvent extends GameEvent { + + public WorldGenerationEvent(World world) { + setSource(world); + } + + public World getWorld(){ + return (World)getSource(); + } + +} diff --git a/Server/src/net/simon987/server/game/ControllableUnit.java b/Server/src/net/simon987/server/game/ControllableUnit.java new file mode 100644 index 0000000..57c8bff --- /dev/null +++ b/Server/src/net/simon987/server/game/ControllableUnit.java @@ -0,0 +1,13 @@ +package net.simon987.server.game; + +import java.util.ArrayList; + +public interface ControllableUnit { + + int getObjectId(); + + void setKeyboardBuffer(ArrayList kbBuffer); + + ArrayList getKeyboardBuffer(); + +} diff --git a/Server/src/net/simon987/server/game/CpuHardwareDeserializer.java b/Server/src/net/simon987/server/game/CpuHardwareDeserializer.java new file mode 100644 index 0000000..d65ba97 --- /dev/null +++ b/Server/src/net/simon987/server/game/CpuHardwareDeserializer.java @@ -0,0 +1,10 @@ +package net.simon987.server.game; + +import net.simon987.server.assembly.CpuHardware; +import org.json.simple.JSONObject; + +public interface CpuHardwareDeserializer { + + + CpuHardware deserializeHardware(JSONObject hwJson); +} diff --git a/Server/src/net/simon987/server/game/Direction.java b/Server/src/net/simon987/server/game/Direction.java new file mode 100755 index 0000000..ec244c6 --- /dev/null +++ b/Server/src/net/simon987/server/game/Direction.java @@ -0,0 +1,39 @@ +package net.simon987.server.game; + +/** + * Direction of a game object in a 4-direction grid-based + * area + */ +public enum Direction { + /** + * North, up + */ + NORTH, + /** + * East, right + */ + EAST, + /** + * South, bottom + */ + SOUTH, + /** + * West, left + */ + WEST; + + public static Direction getDirection(int x) { + switch (x) { + case 0: + return NORTH; + case 1: + return EAST; + case 2: + return SOUTH; + case 3: + return WEST; + default: + return null; + } + } +} diff --git a/Server/src/net/simon987/server/game/EffectType.java b/Server/src/net/simon987/server/game/EffectType.java new file mode 100644 index 0000000..834eb93 --- /dev/null +++ b/Server/src/net/simon987/server/game/EffectType.java @@ -0,0 +1,26 @@ +package net.simon987.server.game; + +/** + * Types of GameEffects + */ +public enum EffectType { + + /** + * Warning icon + */ + WARNING, + /** + * Error icon + */ + ERROR, + /** + * Dig particle effect + */ + DIG, + /** + * 'A' Icon + */ + A_EMOTE + + +} diff --git a/Server/src/net/simon987/server/game/GameEffect.java b/Server/src/net/simon987/server/game/GameEffect.java new file mode 100644 index 0000000..87846d2 --- /dev/null +++ b/Server/src/net/simon987/server/game/GameEffect.java @@ -0,0 +1,68 @@ +package net.simon987.server.game; + +import net.simon987.server.io.JSONSerialisable; +import org.json.simple.JSONObject; + +/** + * Represents a game effect in a World (e.g. Particles made when digging, Error animation, Attack effects etc..) + *

+ * The game effect is generated by the server when certain circumstances are met, and inserted into the database. + * The client requests the list of game effects for this World each tick and handles it. This list (called queuedGameEffects) + * is cleared at the beginning of each tick. + *

+ * These effects are purely visual and could be changed or ignored by the client + */ +public class GameEffect implements JSONSerialisable{ + + + /** + * Type of the effect + */ + private EffectType type; + + private int x; + + private int y; + + public GameEffect(EffectType type, int x, int y) { + this.type = type; + this.x = x; + this.y = y; + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + + json.put("x", x); + json.put("y", y); + json.put("type", type); + + return json; + } + + public EffectType getType() { + return type; + } + + public void setType(EffectType type) { + this.type = type; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } +} diff --git a/Server/src/net/simon987/server/game/GameObject.java b/Server/src/net/simon987/server/game/GameObject.java new file mode 100755 index 0000000..d95b4bf --- /dev/null +++ b/Server/src/net/simon987/server/game/GameObject.java @@ -0,0 +1,221 @@ +package net.simon987.server.game; + +import net.simon987.server.GameServer; +import net.simon987.server.io.JSONSerialisable; +import net.simon987.server.plugin.ServerPlugin; +import org.json.simple.JSONObject; + +import java.awt.*; + +/** + * An INSTANCE of an object (e.g. a Tree, a character ...) inside the + * game universe + */ +public abstract class GameObject implements JSONSerialisable { + + private boolean dead; + /** + * Object's unique identifier + */ + private int objectId; + + /** + * X coordinate of the object in its World + */ + private int x; + + /** + * Y coordinate of the object in its World + */ + private int y; + + /** + * Direction of the object + */ + private Direction direction = Direction.NORTH; + + /** + * Current World of the object + */ + private World world; + + + //-------- + + /** + * Increment the location of the game object by 1 tile + * Collision checks happen here + */ + public boolean incrementLocation() { + + int newX = 0, newY = 0; + + if (direction == Direction.NORTH) { + newX = x; + newY = (y - 1); + + } else if (direction == Direction.EAST) { + newX = (x + 1); + newY = y; + + } else if (direction == Direction.SOUTH) { + newX = x; + newY = (y + 1); + + } else if (direction == Direction.WEST) { + newX = (x - 1); + newY = y; + } + + //Check if out of World bounds / collision + if(newX < 0) { + //Move object to adjacent World (left) + World leftWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX() - 1, world.getY()); + + if(leftWorld != null){ + world.getGameObjects().remove(this); + leftWorld.getGameObjects().add(this); + setWorld(leftWorld); + + x = World.WORLD_SIZE - 1; + } + } else if(newX >= World.WORLD_SIZE) { + //Move object to adjacent World (right) + World rightWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX() + 1, world.getY()); + + if(rightWorld != null){ + world.getGameObjects().remove(this); + rightWorld.getGameObjects().add(this); + setWorld(rightWorld); + + x = 0; + } + } else if (newY < 0) { + //Move object to adjacent World (down) + World downWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX(), world.getY() - 1); + + if(downWorld != null){ + world.getGameObjects().remove(this); + downWorld.getGameObjects().add(this); + setWorld(downWorld); + + y = World.WORLD_SIZE - 1; + } + } else if(newY >= World.WORLD_SIZE) { + //Move object to adjacent World (up) + World upWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX(), world.getY() + 1); + + if(upWorld != null){ + world.getGameObjects().remove(this); + upWorld.getGameObjects().add(this); + setWorld(upWorld); + + y = 0; + } + } + //Check collision + else if (!world.isTileBlocked(newX, newY)) { + //Tile is passable + x = newX; + y = newY; + } else { + //Display error when object is trying to walk in a wall + //TODO Add emote here + System.out.println("DEBUG: FAILED walk"); + return false; + } + + return true; + + } + + public abstract char getMapInfo(); + + public Point getFrontTile() { + + if (direction == Direction.NORTH) { + return new Point(x, y - 1); + } else if (direction == Direction.EAST) { + return new Point(x + 1, y); + } else if (direction == Direction.SOUTH) { + return new Point(x, y + 1); + } else { + return new Point(x - 1, y); + } + + } + + public int getObjectId() { + return objectId; + } + + public void setObjectId(int objectId) { + this.objectId = objectId; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + public Direction getDirection() { + return direction; + } + + public void setDirection(Direction direction) { + this.direction = direction; + } + + public World getWorld() { + return world; + } + + public void setWorld(World world) { + this.world = world; + } + + @Override + public JSONObject serialise() { + return new JSONObject(); + } + + public static GameObject deserialize(JSONObject objJson) { + + for(ServerPlugin plugin : GameServer.INSTANCE.getPluginManager().getPlugins()){ + + if(plugin instanceof GameObjectDeserializer){ + GameObject object = ((GameObjectDeserializer) plugin).deserializeObject(objJson); + + if(object != null){ + return object; + } + } + } + + return null; + } + + + public boolean isAt(int x, int y) { + return this.x == x && this.y == y; + } + + public boolean isDead() { + return dead; + } + + public void setDead(boolean dead) { + this.dead = dead; + } +} \ No newline at end of file diff --git a/Server/src/net/simon987/server/game/GameObjectDeserializer.java b/Server/src/net/simon987/server/game/GameObjectDeserializer.java new file mode 100644 index 0000000..6fb0fb5 --- /dev/null +++ b/Server/src/net/simon987/server/game/GameObjectDeserializer.java @@ -0,0 +1,9 @@ +package net.simon987.server.game; + +import org.json.simple.JSONObject; + +public interface GameObjectDeserializer { + + GameObject deserializeObject(JSONObject object); + +} diff --git a/Server/src/net/simon987/server/game/GameUniverse.java b/Server/src/net/simon987/server/game/GameUniverse.java new file mode 100644 index 0000000..516ed45 --- /dev/null +++ b/Server/src/net/simon987/server/game/GameUniverse.java @@ -0,0 +1,171 @@ +package net.simon987.server.game; + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.exception.CancelledException; +import net.simon987.server.io.JSONSerialisable; +import net.simon987.server.logging.LogManager; +import net.simon987.server.user.User; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; + +public class GameUniverse implements JSONSerialisable{ + + private ArrayList worlds; + private ArrayList users; + private WorldGenerator worldGenerator; + + private long time; + + private int nextObjectId = 0; + + public GameUniverse(ServerConfiguration config) { + + worlds = new ArrayList<>(32); + users = new ArrayList<>(16); + + worldGenerator = new WorldGenerator(config); + + } + + public long getTime() { + return time; + } + + public World getWorld(int x, int y) { + + for (World world : worlds) { + if (world.getX() == x && world.getY() == y) { + return world; + } + } + + //World does not exist + LogManager.LOGGER.severe("Trying to read a World that does not exist!"); + + World world = createWorld(x,y); + + worlds.add(world); + + return world; + + } + + public World createWorld(int x, int y) { + + World world = null; + try { + world = worldGenerator.generateWorld(x, y); + + + } catch (CancelledException e) { + e.printStackTrace(); + } + + return world; + } + + public User getUser(String username) { + + for (User user : users) { + if (user.getUsername().equals(username)) { + return user; + } + } + + return null; + } + + public GameObject getObject(int id) { + + for (World world : worlds) { + for(GameObject object : world.getGameObjects()){ + if(object.getObjectId() == id){ + return object; + } + } + } + + return null; + } + + + public void incrementTime(){ + time++; + } + + public ArrayList getWorlds() { + return worlds; + } + + public ArrayList getUsers() { + return users; + } + + @Override + public JSONObject serialise() { + JSONObject json = new JSONObject(); + + JSONArray worlds = new JSONArray(); + + for(World world : this.worlds){ + worlds.add(world.serialise()); + } + + JSONArray users = new JSONArray(); + for(User user : this.users){ + users.add(user.serialise()); + } + + + json.put("users", users); + json.put("worlds", worlds); + json.put("time", time); + json.put("nextObjectId", nextObjectId); + + return json; + } + + /** + * Load game universe from JSON save file + * @param file JSON save file + */ + public void load(File file){ + + JSONParser parser = new JSONParser(); + + try { + FileReader reader = new FileReader(file); + JSONObject universeJson = (JSONObject)parser.parse(reader); + + time = (long)universeJson.get("time"); + nextObjectId = (int)(long)universeJson.get("nextObjectId"); + + for(JSONObject worldJson : (ArrayList)universeJson.get("worlds")){ + worlds.add(World.deserialize(worldJson)); + } + + for(JSONObject userJson : (ArrayList)universeJson.get("users")){ + users.add(User.deserialize(userJson)); + } + + System.out.println("Loaded " + worlds.size()); + + reader.close(); + + } catch (IOException | ParseException | CancelledException e) { + e.printStackTrace(); + } + + } + + public int getNextObjectId() { + return ++nextObjectId; + } +} diff --git a/Server/src/net/simon987/server/game/InventoryHolder.java b/Server/src/net/simon987/server/game/InventoryHolder.java new file mode 100644 index 0000000..7df1111 --- /dev/null +++ b/Server/src/net/simon987/server/game/InventoryHolder.java @@ -0,0 +1,20 @@ +package net.simon987.server.game; + + +public interface InventoryHolder { + + /** + * Place an item into the inventory + * + * @param item item id (see MarConstants.ITEM_*) + */ + boolean placeItem(int item); + + /** + * Take an item from the inventory + * @param item Desired item id (see MarConstants.ITEM_*) + * @return true is the take item action executed properly, true also means that the desired item + * was removed from the inventory + */ + boolean takeItem(int item); +} diff --git a/Server/src/net/simon987/server/game/TileMap.java b/Server/src/net/simon987/server/game/TileMap.java new file mode 100755 index 0000000..c80b03f --- /dev/null +++ b/Server/src/net/simon987/server/game/TileMap.java @@ -0,0 +1,160 @@ +package net.simon987.server.game; + + +import net.simon987.server.io.JSONSerialisable; +import org.json.simple.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterOutputStream; + +/** + * A 2D map of Tile objects of size width*height + */ +public class TileMap implements JSONSerialisable { + + public static final int PLAIN_TILE = 0; + public static final int WALL_TILE = 1; + public static final int IRON_TILE = 2; + public static final int COPPER_TILE = 3; + + public static final int ITEM_IRON = 3; + public static final int ITEM_COPPER = 4; + + /** + * The map of tile + */ + private int[][] tiles; + + /** + * width, in tiles + */ + private int width; + + /** + * Height, in tiles + */ + private int height; + + /** + * Create a blank (All 0s) map + */ + public TileMap(int width, int height) { + this.width = width; + this.height = height; + + tiles = new int[width][height]; + } + + /** + * Change the tile at a specified position + * Sets the modified flag + * + * @param tileId id of the new Tile + * @param x X coordinate of the tile to set + * @param y Y coordinate of the tile to set + */ + public void setTileAt(int tileId, int x, int y) { + + try { + tiles[x][y] = tileId; + } catch (ArrayIndexOutOfBoundsException e) { + //Shouldn't happen + e.printStackTrace(); + } + } + + /** + * Get the tile at a specific position + * + * @param x X coordinate of the tile to get + * @param y Y coordinate of the tile to get + * @return the tile at the specified position, -1 if out of bounds + */ + public int getTileAt(int x, int y) { + try { + return tiles[x][y]; + } catch (ArrayIndexOutOfBoundsException e) { + return -1; + } + } + + public int[][] getTiles() { + return tiles; + } + + public int getWidth() { + + return width; + } + + public int getHeight() { + return height; + } + + @Override + public JSONObject serialise() { + JSONObject json = new JSONObject(); + + byte[] terrain = new byte[width*width]; + + for (int x = 0; x < World.WORLD_SIZE; x++) { + for (int y = 0; y < World.WORLD_SIZE; y++) { + terrain[x * width + y] = (byte)tiles[x][y]; + } + } + try { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION, true); + DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(stream, compressor); + + deflaterOutputStream.write(terrain); + + deflaterOutputStream.close(); + byte[] compressedBytes = stream.toByteArray(); + + json.put("zipTerrain", new String(Base64.getEncoder().encode(compressedBytes))); + + } catch (IOException e) { + e.printStackTrace(); + } + + return json; + } + + public static TileMap deserialize(JSONObject object) { + + TileMap tileMap = new TileMap(World.WORLD_SIZE, World.WORLD_SIZE); + + + byte[] compressedBytes = Base64.getDecoder().decode((String)object.get("zipTerrain")); + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Inflater decompressor = new Inflater(true); + InflaterOutputStream inflaterOutputStream = new InflaterOutputStream(baos, decompressor); + inflaterOutputStream.write(compressedBytes); + inflaterOutputStream.close(); + + byte[] terrain = baos.toByteArray(); + + for (int x = 0; x < World.WORLD_SIZE; x++) { + for (int y = 0; y < World.WORLD_SIZE; y++) { + tileMap.tiles[x][y] = terrain[x * World.WORLD_SIZE + y]; + } + } + + return tileMap; + + + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } +} diff --git a/Server/src/net/simon987/server/game/TmpObject.java b/Server/src/net/simon987/server/game/TmpObject.java new file mode 100644 index 0000000..89089d7 --- /dev/null +++ b/Server/src/net/simon987/server/game/TmpObject.java @@ -0,0 +1,21 @@ +package net.simon987.server.game; + +import net.simon987.server.GameServer; + +public class TmpObject extends GameObject{ + + public TmpObject(){ + + GameServer.INSTANCE.getGameUniverse(); + + setWorld(GameServer.INSTANCE.getGameUniverse().getWorld(0,0)); + setX(6); + setY(6); + + } + + @Override + public char getMapInfo() { + return 0; + } +} diff --git a/Server/src/net/simon987/server/game/Updatable.java b/Server/src/net/simon987/server/game/Updatable.java new file mode 100644 index 0000000..067238a --- /dev/null +++ b/Server/src/net/simon987/server/game/Updatable.java @@ -0,0 +1,13 @@ +package net.simon987.server.game; + +/** + * Updatable objects needs to be updated each tick + */ +public interface Updatable { + + /** + * Called every tick + */ + void update(); + +} diff --git a/Server/src/net/simon987/server/game/World.java b/Server/src/net/simon987/server/game/World.java new file mode 100644 index 0000000..86a5e10 --- /dev/null +++ b/Server/src/net/simon987/server/game/World.java @@ -0,0 +1,193 @@ +package net.simon987.server.game; + +import net.simon987.server.io.JSONSerialisable; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.util.ArrayList; + +public class World implements JSONSerialisable{ + + /** + * Size of the side of a world + */ + public static final int WORLD_SIZE = 16; + + private static final char INFO_BLOCKED = 0x8000; + private static final char INFO_IRON = 0x0200; + private static final char INFO_COPPER = 0x0100; + + private int x; + private int y; + + private TileMap tileMap; + + private ArrayList gameObjects = new ArrayList<>(16); + + public World(int x, int y, TileMap tileMap) { + this.x = x; + this.y = y; + this.tileMap = tileMap; + } + + private World(){ + + } + + public TileMap getTileMap() { + return tileMap; + } + + /** + * Check if a tile is blocked, either by a game object or an impassable tile type + */ + public boolean isTileBlocked(int x, int y) { + + return getGameObjectsAt(x, y).size() > 0 || tileMap.getTileAt(x, y) == TileMap.WALL_TILE; + + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public ArrayList getGameObjects() { + return gameObjects; + } + + public void update(){ + + ArrayList gameObjects_ = new ArrayList<>(gameObjects); + + for(GameObject object : gameObjects_){ + if(object instanceof Updatable){ + ((Updatable) object).update(); + } + if(object.isDead()){ + System.out.println("Removed" + object.getObjectId()); + gameObjects.remove(object); + } + } + } + + @Override + public JSONObject serialise() { + JSONObject json = new JSONObject(); + + JSONArray objects = new JSONArray(); + for(GameObject obj : gameObjects){ + objects.add(obj.serialise()); + } + json.put("objects", objects); + + json.put("terrain", tileMap.serialise()); + + json.put("x", x); + json.put("y", y); + + return json; + } + + @Override + public String toString() { + + String str = "World (" + x + ", " + y + ")\n"; + int[][] tileMap = this.tileMap.getTiles(); + + for (int x = 0; x < WORLD_SIZE; x++) { + for (int y = 0; y < WORLD_SIZE; y++) { + str += tileMap[x][y] + " "; + } + str += "\n"; + } + + return str; + + } + + public static World deserialize(JSONObject json) { + World world = new World(); + world.x = (int)(long)json.get("x"); + world.y = (int)(long)json.get("y"); + + world.tileMap = TileMap.deserialize((JSONObject)json.get("terrain")); + + for(JSONObject objJson : (ArrayList)json.get("objects")){ + + GameObject object = GameObject.deserialize(objJson); + + object.setWorld(world); + world.gameObjects.add(object); + } + + return world; + } + + /** + * Get a binary representation of the map as an array of 16-bit bit fields, one word for each + * tile. + *

+ * todo Performance cache this? + */ + public char[][] getMapInfo() { + + char[][] mapInfo = new char[World.WORLD_SIZE][World.WORLD_SIZE]; + int[][] tiles = tileMap.getTiles(); + + //Tile + for (int y = 0; y < World.WORLD_SIZE; y++) { + for (int x = 0; x < World.WORLD_SIZE; x++) { + + + if (tiles[y][x] == TileMap.PLAIN_TILE) { + + mapInfo[x][y] = 0; + + } else if (tiles[y][x] == TileMap.WALL_TILE) { + + mapInfo[x][y] = INFO_BLOCKED; + } else if (tiles[y][x] == TileMap.COPPER_TILE) { + mapInfo[x][y] = INFO_COPPER; + } else if (tiles[y][x] == TileMap.IRON_TILE) { + mapInfo[x][y] = INFO_IRON; + } + } + } + + //Objects + for (GameObject obj : this.gameObjects) { + mapInfo[obj.getX()][obj.getY()] |= obj.getMapInfo(); + + } + + return mapInfo; + + } + + /** + * Get the list of game objects at a location + * + * @param x X coordinate on the World + * @param y Y coordinate on the World + * @return the list of game objects at a location + */ + public ArrayList getGameObjectsAt(int x, int y) { + + ArrayList gameObjects = new ArrayList<>(2); + + for (GameObject obj : this.gameObjects) { + + if (obj.isAt(x, y)) { + gameObjects.add(obj); + } + + } + + return gameObjects; + } + +} diff --git a/Server/src/net/simon987/server/game/WorldGenerator.java b/Server/src/net/simon987/server/game/WorldGenerator.java new file mode 100755 index 0000000..bb1ed64 --- /dev/null +++ b/Server/src/net/simon987/server/game/WorldGenerator.java @@ -0,0 +1,219 @@ +package net.simon987.server.game; + +import net.simon987.server.GameServer; +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.exception.CancelledException; +import net.simon987.server.event.GameEvent; +import net.simon987.server.event.WorldGenerationEvent; +import net.simon987.server.logging.LogManager; + +import java.awt.*; +import java.util.HashMap; +import java.util.Random; + +/** + * Generates random Worlds + */ +public class WorldGenerator { + + /** + * Minimum number of center points. + */ + private int centerPointCountMin; + + /** + * Maximum number of center points. + */ + private int centerPointCountMax; + + /** + * Number of plain Tiles for each wall Tile + */ + private int wallPlainRatio; + + private int minIronCount; + private int maxIronCount; + private int minCopperCount; + private int maxCopperCount; + + /** + * Map of center points + */ + private HashMap centerPointsMap; + + + WorldGenerator(ServerConfiguration config) { + + centerPointCountMin = config.getInt("wg_centerPointCountMin"); + centerPointCountMax = config.getInt("wg_centerPointCountMax"); + wallPlainRatio = config.getInt("wg_wallPlainRatio"); + minIronCount = config.getInt("wg_minIronCount"); + maxIronCount = config.getInt("wg_maxIronCount"); + minCopperCount = config.getInt("wg_minCopperCount"); + maxCopperCount = config.getInt("wg_maxCopperCount"); + } + + /** + * Distance between 2 points rounded to int + */ + private static int distanceBetween(int x1, int y1, int x2, int y2) { + + return (int) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); + + } + + private int getClosestCenterPointTile(int x, int y) { + + int minDistance = 9999; + int closest = -1; + + for (Point point : centerPointsMap.keySet()) { + int distance = distanceBetween(point.x, point.y, x, y); + + if (distance < minDistance) { + minDistance = distance; + closest = centerPointsMap.get(point); + } + } + + + return closest; + + } + + public static Point getRandomPlainTile(int[][] tiles) { + + Random random = new Random(); + + int counter = 0; + while (true) { + counter++; + + //Prevent infinite loop + if (counter >= 1000) { + return null; + } + + int rx = random.nextInt(World.WORLD_SIZE); + int ry = random.nextInt(World.WORLD_SIZE); + + if (tiles[rx][ry] == TileMap.PLAIN_TILE) { + return new Point(rx, ry); + } + } + + } + + /** + * Generates an empty World + */ + private static World generateEmptyWorld(int locX, int locY) { + + return new World(locX, locY, new TileMap(World.WORLD_SIZE, World.WORLD_SIZE)); + } + + /** + * Create a randomly generated World + */ + public World generateWorld(int locX, int locY) throws CancelledException{ + LogManager.LOGGER.info("Generating random world"); + Random random = new Random(); + + World world = generateEmptyWorld(locX, locY); + + centerPointsMap = new HashMap<>(16); + + int centerPointCount = random.nextInt(centerPointCountMax - centerPointCountMin) + centerPointCountMin; + + //Create center points + for (int i = centerPointCount; i >= 0; i--) { + + int tile = random.nextInt(wallPlainRatio) == 0 ? 1 : 0; + centerPointsMap.put(new Point(random.nextInt(World.WORLD_SIZE), random.nextInt(World.WORLD_SIZE)), tile); + } + + //Fill unset tiles + for (int y = 0; y < World.WORLD_SIZE; y++) { + for (int x = 0; x < World.WORLD_SIZE; x++) { + int tile = getClosestCenterPointTile(x, y); + /* + * There is 1-tile thick wall around the World, with 4-tile wide entrances + * each side. Each entrance is accessible (There is a 1-tick plain-terrain + * border inside the walls). The center part can be anything (hence the '*'). + * + * 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 + * 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 + * 1 0 * * * * * * * * * * * * 0 1 + * 1 0 * * * * * * * * * * * * 0 1 + * 1 0 * * * * * * * * * * * * 0 1 + * 1 0 * * * * * * * * * * * * 0 1 + * 0 0 * * * * * * * * * * * * 0 0 + * 0 0 * * * * * * * * * * * * 0 0 + * 0 0 * * * * * * * * * * * * 0 0 + * 0 0 * * * * * * * * * * * * 0 0 + * 1 0 * * * * * * * * * * * * 0 1 + * 1 0 * * * * * * * * * * * * 0 1 + * 1 0 * * * * * * * * * * * * 0 1 + * 1 0 * * * * * * * * * * * * 0 1 + * 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 + * 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 + */ + + if (x == 0 || x == World.WORLD_SIZE - 1) { + //Vertical (West & East) walls + if (y < 6 || y > 9) { + tile = 1; + } else { + tile = 0; + } + } + if (y == 0 || y == World.WORLD_SIZE - 1) { + // Horizontal (North & South) walls + if (x < 6 || x > 9) { + tile = 1; + } else { + tile = 0; + } + } + if (((x == 1 || x == World.WORLD_SIZE - 2) && y > 0 && y < World.WORLD_SIZE - 1) || + ((y == 1 || y == World.WORLD_SIZE - 2) && x > 0 && x < World.WORLD_SIZE - 1)) { + //Inner border + tile = 0; + } + + + world.getTileMap().getTiles()[x][y] = tile; + } + } + + //Replace plain tiles by iron and copper tiles + int ironCount = random.nextInt(maxIronCount - minIronCount) + minIronCount; + int copperCount = random.nextInt(maxCopperCount - minCopperCount) + minCopperCount; + + for (int i = 0; i < ironCount; i++) { + + Point p = getRandomPlainTile(world.getTileMap().getTiles()); + + if (p != null) { + world.getTileMap().getTiles()[p.x][p.y] = TileMap.IRON_TILE; + } + } + for (int i = 0; i < copperCount; i++) { + + Point p = getRandomPlainTile(world.getTileMap().getTiles()); + + if (p != null) { + world.getTileMap().getTiles()[p.x][p.y] = TileMap.COPPER_TILE; + } + } + + GameEvent event = new WorldGenerationEvent(world); + GameServer.INSTANCE.getEventDispatcher().dispatch(event); + if(event.isCancelled()){ + throw new CancelledException(); + } + + return world; + } + +} diff --git a/Server/src/net/simon987/server/game/pathfinding/Node.java b/Server/src/net/simon987/server/game/pathfinding/Node.java new file mode 100755 index 0000000..70acd20 --- /dev/null +++ b/Server/src/net/simon987/server/game/pathfinding/Node.java @@ -0,0 +1,70 @@ +package net.simon987.server.game.pathfinding; + +/** + * A single node in the search graph + *

+ * Inspired by http://www.cokeandcode.com/main/tutorials/path-finding/ + */ +public class Node implements Comparable { + + /** + * x coordinate of the node + */ + public int x; + + /** + * y coordinate of the node + */ + public int y; + + /** + * Cost of getting from the start node to this node + */ + public int gScore; + + /** + * Total cost of getting from the start node to the goal + */ + public int fScore; + + /** + * Parent of the node + */ + public Node parent; + + + /** + * Create a new Node + * + * @param x X coordinate of the node + * @param y Y coordinate of the node + */ + public Node(int x, int y) { + this.x = x; + this.y = y; + + gScore = Integer.MAX_VALUE; + fScore = Integer.MAX_VALUE; + } + + /** + * Compare two Nodes using their fScore + */ + @Override + public int compareTo(Object o) { + Node other = (Node) o; + + if (fScore < other.fScore) { + return -1; + } else if (fScore > other.fScore) { + return 1; + } else { + return 0; + } + } + + @Override + public String toString() { + return "(" + this.x + ", " + this.y + ")"; + } +} diff --git a/Server/src/net/simon987/server/game/pathfinding/Pathfinder.java b/Server/src/net/simon987/server/game/pathfinding/Pathfinder.java new file mode 100755 index 0000000..ad25845 --- /dev/null +++ b/Server/src/net/simon987/server/game/pathfinding/Pathfinder.java @@ -0,0 +1,159 @@ +package net.simon987.server.game.pathfinding; + +import net.simon987.server.assembly.Util; +import net.simon987.server.game.World; +import net.simon987.server.logging.LogManager; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * Class to compute paths in the game universe. It supports + * paths between within the same World. + */ +public class Pathfinder { + + + /** + * Create a pathfinder + */ + public Pathfinder() { + + } + + /** + * Find the shortest path between 2 set of coordinates within a single World + *

+ * based on https://en.wikipedia.org/wiki/A*_search_algorithm + * + * @param world World to search the path in + * @param sX X coordinate of the start + * @param sY Y coordinate of the start + * @param gX X coordinate of the goal + * @param gY Y coordinate of the goal + * @return The shortest path to the goal from the start + */ + public static ArrayList findPath(World world, int sX, int sY, int gX, int gY, int range) { + + if (sX == gX && sY == gY) { + return null; + } + + if (gX < 0 || gX > 15 || gY < 0 || gY > 15) { + return null; + } + + + ArrayList closed = new ArrayList<>(64); + SortedArrayList open = new SortedArrayList(); + + //Initialize node map + Node[][] nodes = new Node[World.WORLD_SIZE][World.WORLD_SIZE]; + + for (int x = 0; x < World.WORLD_SIZE; x++) { + for (int y = 0; y < World.WORLD_SIZE; y++) { + + nodes[x][y] = new Node(x, y); + + } + } + + Node start = nodes[sX][sY]; + + //The cost of going from start to start is 0 + start.gScore = 0; + start.fScore = (Math.abs(sX - gX) + Math.abs(sY - gY)); + + open.add(start); + + + int counter = 0; + while (open.size() > 0) { + counter++; + Node current = open.first(); + + if (Util.manhattanDist(current.x, current.y, gX, gY) <= range) { + //goal is reached + //Reconstruct the path + ArrayList reconstructedPath = new ArrayList<>(48); + + + while (current != null) { + reconstructedPath.add(current); + current = current.parent; // crawl back up to the start + } + + //Reverse in the start -> goal order + Collections.reverse(reconstructedPath); + + return reconstructedPath; + + } + + open.remove(current); + closed.add(current); + + ArrayList neighbors = getNeighbors(world, nodes, current); + for (Node neighbor : neighbors) { + + if (closed.contains(neighbor)) { + continue; + } + + int tentativeGScore = current.gScore + 1; + + if (!open.contains(neighbor)) { + open.add(neighbor); + } else if (tentativeGScore >= neighbor.gScore) { + continue; + } + + neighbor.parent = current; + neighbor.gScore = tentativeGScore; + neighbor.fScore = neighbor.gScore + (Math.abs(neighbor.x - gX) + Math.abs(neighbor.y - gY)); + } + + } + + //Incomplete path + LogManager.LOGGER.info("Incomplete path! " + counter); + return null; + + } + + /** + * Get the valid neighbors of a node within a single World + * + * @param world World to check the validity of a position + * @param nodes Map of nodes to get the neighbors from + * @param node Node to get the neighbors of + * @return a list of valid neighbors of the specified node + */ + private static ArrayList getNeighbors(World world, Node[][] nodes, Node node) { + + ArrayList neighbors = new ArrayList<>(4); + + //Check if left neighbor is within the World boundaries and isn't blocked + if (node.x != 0 && !world.isTileBlocked(node.x - 1, node.y)) { + neighbors.add(nodes[node.x - 1][node.y]); + } + + //Check if the right neighbor is within the World boundaries and isn't blocked + if (node.x != (World.WORLD_SIZE - 1) && !world.isTileBlocked(node.x + 1, node.y)) { + neighbors.add(nodes[node.x + 1][node.y]); + } + + //Check if the top neighbor is within the World boundaries and isn't blocked + if (node.y != 0 && !world.isTileBlocked(node.x, node.y - 1)) { + neighbors.add(nodes[node.x][node.y - 1]); + } + + //Check if the bottom neighbor is within the World boundaries and isn't blocked + if (node.y != (World.WORLD_SIZE - 1) && !world.isTileBlocked(node.x, node.y + 1)) { + neighbors.add(nodes[node.x][node.y + 1]); + } + + return neighbors; + } + +} diff --git a/Server/src/net/simon987/server/game/pathfinding/SortedArrayList.java b/Server/src/net/simon987/server/game/pathfinding/SortedArrayList.java new file mode 100755 index 0000000..61372e2 --- /dev/null +++ b/Server/src/net/simon987/server/game/pathfinding/SortedArrayList.java @@ -0,0 +1,38 @@ +package net.simon987.server.game.pathfinding; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * Wrapper of ArrayList to make it sorted + *

+ * inspired by http://www.cokeandcode.com/main/tutorials/path-finding/ + */ +public class SortedArrayList extends ArrayList { + + /** + * Get the first element from the list + * + * @return element at index 0 + */ + Node first() { + return get(0); + } + + + /** + * Add an node to the list and sort it + * + * @param node node to add + * @return always return true + */ + @Override + public boolean add(Node node) { + super.add(node); + Collections.sort(this); + + return true; //Return value ignored + } + + +} diff --git a/Server/src/net/simon987/server/io/DatabaseManager.java b/Server/src/net/simon987/server/io/DatabaseManager.java new file mode 100755 index 0000000..8d57a9a --- /dev/null +++ b/Server/src/net/simon987/server/io/DatabaseManager.java @@ -0,0 +1,45 @@ +package net.simon987.server.io; + + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.logging.LogManager; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** + * Manages the database: this class manages the interactions with the database + */ +public abstract class DatabaseManager { + + /** + * SQL connection url + */ + private String url; + + /** + * SQL username + */ + private String username; + + /** + * SQL password + */ + private String password; + + public DatabaseManager(ServerConfiguration config) { + this.url = config.getString("mysql_url"); + this.username = config.getString("mysql_user"); + this.password = config.getString("mysql_pass"); + } + + protected Connection getConnection() { + try { + return DriverManager.getConnection(url, this.username, password); + } catch (SQLException e) { + LogManager.LOGGER.severe("Couldn't connect to MySQL server state:" + e.getSQLState() + " error:" + e.getErrorCode()); + return null; + } + } +} diff --git a/Server/src/net/simon987/server/io/JSONSerialisable.java b/Server/src/net/simon987/server/io/JSONSerialisable.java new file mode 100644 index 0000000..eff743e --- /dev/null +++ b/Server/src/net/simon987/server/io/JSONSerialisable.java @@ -0,0 +1,9 @@ +package net.simon987.server.io; + +import org.json.simple.JSONObject; + +public interface JSONSerialisable { + + JSONObject serialise(); + +} diff --git a/Server/src/net/simon987/server/logging/GenericFormatter.java b/Server/src/net/simon987/server/logging/GenericFormatter.java new file mode 100755 index 0000000..dbee2b0 --- /dev/null +++ b/Server/src/net/simon987/server/logging/GenericFormatter.java @@ -0,0 +1,41 @@ +package net.simon987.server.logging; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * Generic formatter for the game logging + */ +public class GenericFormatter extends Formatter { + + @Override + public String format(LogRecord record) { + + StringBuilder sb = new StringBuilder(); + + if (record.getLevel() == Level.FINE) { + //Chat message, maximum 50 char per line + if (record.getMessage().length() > 50) { + sb.append(record.getMessage().substring(0, 50)); + sb.append('\n'); + sb.append(record.getMessage().substring(50)); + } else { + sb.append(record.getMessage()); + } + sb.append('\n'); + } else { + //Regular record + Date date = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("MM/dd HH:mm:ss:SSS"); //ex. 11/25 22:03:59:010 + + sb.append(String.format("[%s] [%s] %s", sdf.format(date), record.getLevel(), record.getMessage())); + sb.append('\n'); + } + + + return sb.toString(); + } +} diff --git a/Server/src/net/simon987/server/logging/LogManager.java b/Server/src/net/simon987/server/logging/LogManager.java new file mode 100755 index 0000000..1a2262a --- /dev/null +++ b/Server/src/net/simon987/server/logging/LogManager.java @@ -0,0 +1,41 @@ +package net.simon987.server.logging; + +import java.io.IOException; +import java.util.logging.*; + +/** + * Utility class to manage log entries + */ +public class LogManager { + + /** + * Singleton Logger + */ + public final static Logger LOGGER = Logger.getLogger("mar"); + + /** + * Initialises the logger + */ + public static void initialize() { + LOGGER.setUseParentHandlers(false); + + Handler handler = new ConsoleHandler(); + handler.setLevel(Level.ALL); + handler.setFormatter(new GenericFormatter()); + + try { + Handler fileHandler = new FileHandler("mar.log"); + fileHandler.setLevel(Level.ALL); + fileHandler.setFormatter(new GenericFormatter()); + + LOGGER.addHandler(handler); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.ALL); + + } catch (IOException e) { + e.printStackTrace(); + } + + + } +} diff --git a/Server/src/net/simon987/server/plugin/PluginManager.java b/Server/src/net/simon987/server/plugin/PluginManager.java new file mode 100644 index 0000000..24210d6 --- /dev/null +++ b/Server/src/net/simon987/server/plugin/PluginManager.java @@ -0,0 +1,71 @@ +package net.simon987.server.plugin; + +import net.simon987.server.logging.LogManager; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class PluginManager { + + private ArrayList plugins; + + public PluginManager() { + this.plugins = new ArrayList<>(10); + } + + public void load(File pluginFile){ + + LogManager.LOGGER.info("Loading plugin file " + pluginFile.getName()); + + try{ + //Get the plugin config file from the archive + ZipFile zipFile = new ZipFile(pluginFile); + + ZipEntry configEntry = zipFile.getEntry("plugin.properties"); + + if(configEntry != null){ + + InputStream stream = zipFile.getInputStream(configEntry); + Properties config = new Properties(); + config.load(stream); + + //Load the plugin + ClassLoader loader = URLClassLoader.newInstance(new URL[] {pluginFile.toURI().toURL() }); + Class aClass = Class.forName(config.getProperty("classpath"), true, loader); + Class pluginClass = aClass.asSubclass(ServerPlugin.class); + Constructor constructor = pluginClass.getConstructor(); + + ServerPlugin plugin = constructor.newInstance(); + plugin.setName(config.getProperty("name")); + plugin.setVersion(config.getProperty("version")); + + LogManager.LOGGER.info("Loaded " + plugin.name + " V" + plugin.version); + + //Add it to the list + plugins.add(plugin); + + //Init the plugin + plugin.init(); + + } else { + LogManager.LOGGER.severe("Couldn't find plugin.properties in " + pluginFile.getName()); + } + zipFile.close(); + + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public ArrayList getPlugins() { + return plugins; + } +} diff --git a/Server/src/net/simon987/server/plugin/ServerPlugin.java b/Server/src/net/simon987/server/plugin/ServerPlugin.java new file mode 100644 index 0000000..89c2ab9 --- /dev/null +++ b/Server/src/net/simon987/server/plugin/ServerPlugin.java @@ -0,0 +1,61 @@ +package net.simon987.server.plugin; + +import net.simon987.server.event.GameEventListener; +import net.simon987.server.io.JSONSerialisable; +import org.json.simple.JSONObject; + +import java.util.ArrayList; + +public abstract class ServerPlugin implements JSONSerialisable { + + /** + * Name of the plugin + */ + protected String name; + + /** + * Version of the plugin + */ + protected String version; + + /** + * List of event listeners + */ + protected ArrayList listeners = new ArrayList<>(5); + + /** + * Called when the plugin is loaded + */ + public abstract void init(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public ArrayList getListeners() { + return listeners; + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + + json.put("name", name); + json.put("version", version); + + return json; + } +} diff --git a/Server/src/net/simon987/server/user/User.java b/Server/src/net/simon987/server/user/User.java new file mode 100755 index 0000000..8fb8c77 --- /dev/null +++ b/Server/src/net/simon987/server/user/User.java @@ -0,0 +1,98 @@ +package net.simon987.server.user; + +import net.simon987.server.GameServer; +import net.simon987.server.assembly.CPU; +import net.simon987.server.assembly.exception.CancelledException; +import net.simon987.server.event.GameEvent; +import net.simon987.server.event.UserCreationEvent; +import net.simon987.server.game.ControllableUnit; +import net.simon987.server.io.JSONSerialisable; +import org.json.simple.JSONObject; + +/** + * Represents a User (or player) of the game + */ +public class User implements JSONSerialisable{ + + private String username; + + private String userCode; + + private CPU cpu; + + private ControllableUnit controlledUnit; + + public User() throws CancelledException { + GameEvent event = new UserCreationEvent(this); + GameServer.INSTANCE.getEventDispatcher().dispatch(event); + if(event.isCancelled()) { + throw new CancelledException(); + } + + + } + + public User(ControllableUnit unit) { + this.controlledUnit = unit; + } + + @Override + public JSONObject serialise() { + + JSONObject json = new JSONObject(); + + json.put("username", username); + json.put("code", userCode); + json.put("controlledUnit", controlledUnit.getObjectId()); + json.put("cpu", cpu.serialise()); + + return json; + } + + public static User deserialize(JSONObject userJson) throws CancelledException { + + User user = new User((ControllableUnit)GameServer.INSTANCE.getGameUniverse().getObject((int)(long)userJson.get("controlledUnit"))); + user.username = (String)userJson.get("username"); + user.userCode = (String)userJson.get("code"); + + user.cpu = CPU.deserialize((JSONObject)userJson.get("cpu"), user); + + return user; + } + + //---- + + public String getUserCode() { + return userCode; + } + + public void setUserCode(String userCode) { + this.userCode = userCode; + } + + public CPU getCpu() { + return cpu; + } + + public void setCpu(CPU cpu) { + this.cpu = cpu; + } + + public String getUsername() { + return username; + } + + public ControllableUnit getControlledUnit() { + return controlledUnit; + } + + public void setControlledUnit(ControllableUnit controlledUnit) { + this.controlledUnit = controlledUnit; + } + + public void setUsername(String username) { + this.username = username; + } + + +} diff --git a/Server/src/net/simon987/server/webserver/CodeRequestHandler.java b/Server/src/net/simon987/server/webserver/CodeRequestHandler.java new file mode 100644 index 0000000..a0ffd62 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/CodeRequestHandler.java @@ -0,0 +1,23 @@ +package net.simon987.server.webserver; + +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONObject; + +public class CodeRequestHandler implements MessageHandler { + @Override + public void handle(OnlineUser user, JSONObject json) { + + if(json.get("t").equals("codeRequest")){ + + LogManager.LOGGER.info("(WS) Code request from " + user.getUser().getUsername()); + + JSONObject response = new JSONObject(); + + response.put("t", "code"); + response.put("code", user.getUser().getUserCode()); + + user.getWebSocket().send(response.toJSONString()); + + } + } +} diff --git a/Server/src/net/simon987/server/webserver/CodeUploadHandler.java b/Server/src/net/simon987/server/webserver/CodeUploadHandler.java new file mode 100644 index 0000000..09f91ec --- /dev/null +++ b/Server/src/net/simon987/server/webserver/CodeUploadHandler.java @@ -0,0 +1,32 @@ +package net.simon987.server.webserver; + +import net.simon987.server.GameServer; +import net.simon987.server.assembly.Assembler; +import net.simon987.server.assembly.AssemblyResult; +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONObject; + +public class CodeUploadHandler implements MessageHandler { + + @Override + public void handle(OnlineUser user, JSONObject json) { + if(json.get("t").equals("uploadCode")){ + + LogManager.LOGGER.info("(WS) Code upload from " + user.getUser().getUsername()); + + //TODO Should we wait at the end of the tick to modify the CPU ? + user.getUser().setUserCode((String)json.get("code")); + + AssemblyResult ar = new Assembler(user.getUser().getCpu().getInstructionSet(), + user.getUser().getCpu().getRegisterSet(), + GameServer.INSTANCE.getConfig()).parse(user.getUser().getUserCode()); + + user.getUser().getCpu().getMemory().clear(); + + //Write assembled code to mem + user.getUser().getCpu().getMemory().write((char) ar.origin, ar.bytes, ar.bytes.length); + user.getUser().getCpu().setCodeSegmentOffset(ar.origin); + + } + } +} diff --git a/Server/src/net/simon987/server/webserver/KeypressHandler.java b/Server/src/net/simon987/server/webserver/KeypressHandler.java new file mode 100644 index 0000000..19ad220 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/KeypressHandler.java @@ -0,0 +1,20 @@ +package net.simon987.server.webserver; + +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONObject; + + +public class KeypressHandler implements MessageHandler { + + @Override + public void handle(OnlineUser user, JSONObject json) { + if(json.get("t").equals("k")){ + + LogManager.LOGGER.info("(WS) Received keypress"); + + int key = (int)(long)json.get("k"); + + user.getUser().getControlledUnit().getKeyboardBuffer().add(key); + } + } +} diff --git a/Server/src/net/simon987/server/webserver/MessageEventDispatcher.java b/Server/src/net/simon987/server/webserver/MessageEventDispatcher.java new file mode 100644 index 0000000..7fe2bc2 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/MessageEventDispatcher.java @@ -0,0 +1,43 @@ +package net.simon987.server.webserver; + + +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import java.util.ArrayList; + +public class MessageEventDispatcher { + + private ArrayList handlers = new ArrayList<>(10); + + public MessageEventDispatcher() { + + } + + public void addHandler(MessageHandler handler) { + + handlers.add(handler); + + } + + public void dispatch(OnlineUser user, String message) { + + JSONParser parser = new JSONParser(); + try { + JSONObject json = (JSONObject) parser.parse(message); + + if (json.containsKey("t")) { + for (MessageHandler handler : handlers) { + handler.handle(user, json); + } + } else { + LogManager.LOGGER.info("Malformed JSON sent by " + user.getUser().getUsername()); + } + + } catch (ParseException e) { + LogManager.LOGGER.info("Malformed JSON sent by " + user.getUser().getUsername()); + } + } +} diff --git a/Server/src/net/simon987/server/webserver/MessageHandler.java b/Server/src/net/simon987/server/webserver/MessageHandler.java new file mode 100644 index 0000000..fa1adf5 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/MessageHandler.java @@ -0,0 +1,9 @@ +package net.simon987.server.webserver; + +import org.json.simple.JSONObject; + +public interface MessageHandler { + + void handle(OnlineUser user, JSONObject json); + +} diff --git a/Server/src/net/simon987/server/webserver/ObjectsRequestHandler.java b/Server/src/net/simon987/server/webserver/ObjectsRequestHandler.java new file mode 100644 index 0000000..e0dd8e5 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/ObjectsRequestHandler.java @@ -0,0 +1,64 @@ +package net.simon987.server.webserver; + +import net.simon987.server.GameServer; +import net.simon987.server.game.GameObject; +import net.simon987.server.io.JSONSerialisable; +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.util.ArrayList; + +public class ObjectsRequestHandler implements MessageHandler { + + + private String cachedResponse; + private long timeCached; + + @Override + public void handle(OnlineUser user, JSONObject json) { + if (json.get("t").equals("object")) { + LogManager.LOGGER.info("(WS) Objects request from " + user.getUser().getUsername()); + + if (timeCached == GameServer.INSTANCE.getGameUniverse().getTime()) { + + if (user.getWebSocket().isOpen()) { + user.getWebSocket().send(cachedResponse); + } + + } else { + + if (json.containsKey("x") && json.containsKey("y")) { + int x = Long.valueOf((long) json.get("x")).intValue(); + int y = Long.valueOf((long) json.get("y")).intValue(); + + ArrayList gameObjects = GameServer.INSTANCE.getGameUniverse().getWorld(x, y).getGameObjects(); + + JSONObject response = new JSONObject(); + JSONArray objects = new JSONArray(); + + + for (GameObject object : gameObjects) { + + if (object instanceof JSONSerialisable) { + objects.add(object.serialise()); + } + + } + + response.put("t", "object"); + response.put("objects", objects); + + cachedResponse = response.toJSONString(); + timeCached = GameServer.INSTANCE.getGameUniverse().getTime(); + + if (user.getWebSocket().isOpen()) { + user.getWebSocket().send(cachedResponse); + } + } else { + LogManager.LOGGER.info("(WS) Malformed Objects request from " + user.getUser().getUsername()); + } + } + } + } +} diff --git a/Server/src/net/simon987/server/webserver/OnlineUser.java b/Server/src/net/simon987/server/webserver/OnlineUser.java new file mode 100644 index 0000000..bb23326 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/OnlineUser.java @@ -0,0 +1,42 @@ +package net.simon987.server.webserver; + +import net.simon987.server.user.User; +import org.java_websocket.WebSocket; + +public class OnlineUser { + + + private boolean authenticated = false; + + private WebSocket webSocket; + + /** + * Associated game user (if authenticated) + */ + private User user; + + public OnlineUser(WebSocket webSocket) { + this.webSocket = webSocket; + + } + + public WebSocket getWebSocket() { + return webSocket; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public boolean isAuthenticated() { + return authenticated; + } + + public void setAuthenticated(boolean authenticated) { + this.authenticated = authenticated; + } +} diff --git a/Server/src/net/simon987/server/webserver/OnlineUserManager.java b/Server/src/net/simon987/server/webserver/OnlineUserManager.java new file mode 100644 index 0000000..c58f8c6 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/OnlineUserManager.java @@ -0,0 +1,49 @@ +package net.simon987.server.webserver; + +import org.java_websocket.WebSocket; + +import java.util.ArrayList; + +public class OnlineUserManager { + + /** + * List of online users. + */ + private ArrayList onlineUsers = new ArrayList<>(10); + + + public OnlineUser getUser(WebSocket socket) { + + ArrayList _onlineUsers = new ArrayList<>(onlineUsers); + + for (OnlineUser user : _onlineUsers) { + if (user.getWebSocket().equals(socket)) { + return user; + } + } + + return null; + } + + /** + * Add an user to the list + * + * @param user user to add + */ + public void add(OnlineUser user) { + onlineUsers.add(user); + } + + /** + * Remove an user to the list + * + * @param user user to remove + */ + public void remove(OnlineUser user) { + onlineUsers.remove(user); + } + + public ArrayList getOnlineUsers() { + return onlineUsers; + } +} diff --git a/Server/src/net/simon987/server/webserver/SocketServer.java b/Server/src/net/simon987/server/webserver/SocketServer.java new file mode 100644 index 0000000..854af95 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/SocketServer.java @@ -0,0 +1,142 @@ +package net.simon987.server.webserver; + +import net.simon987.server.GameServer; +import net.simon987.server.ServerConfiguration; +import net.simon987.server.logging.LogManager; +import net.simon987.server.user.User; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +public class SocketServer extends WebSocketServer { + + private OnlineUserManager userManager = new OnlineUserManager(); + + private SocketServerDatabase database; + + private MessageEventDispatcher messageEventDispatcher = new MessageEventDispatcher(); + + public SocketServer(InetSocketAddress address, ServerConfiguration config) { + super(address); + + database = new SocketServerDatabase(config); + + messageEventDispatcher.addHandler(new UserInfoRequestHandler()); + messageEventDispatcher.addHandler(new TerrainRequestHandler()); + messageEventDispatcher.addHandler(new ObjectsRequestHandler()); + messageEventDispatcher.addHandler(new CodeUploadHandler()); + messageEventDispatcher.addHandler(new CodeRequestHandler()); + messageEventDispatcher.addHandler(new KeypressHandler()); + + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + LogManager.LOGGER.info("(WS) New Websocket connection " + conn.getRemoteSocketAddress()); + + userManager.add(new OnlineUser(conn)); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + LogManager.LOGGER.info("(WS) Closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason); + userManager.add(new OnlineUser(conn)); + } + + @Override + public void onMessage(WebSocket conn, String message) { + OnlineUser onlineUser = userManager.getUser(conn); + + if (onlineUser != null) { + + if (onlineUser.isAuthenticated()) { + + messageEventDispatcher.dispatch(onlineUser, message); + + } else { + LogManager.LOGGER.info("(WS) Received message from unauthenticated user " + conn.getRemoteSocketAddress()); + + String username = database.validateAuthToken(message); + User user = GameServer.INSTANCE.getGameUniverse().getUser(username); + + if (user != null) { + LogManager.LOGGER.info("(WS) User was successfully authenticated: " + user.getUsername()); + + onlineUser.setUser(user); + onlineUser.setAuthenticated(true); + + conn.send("{\"t\":\"auth\", \"m\":\"ok\"}"); + + } else { + LogManager.LOGGER.info("(WS) Unsuccessful authentication attempt " + conn.getRemoteSocketAddress()); + conn.send("{\"t\":\"auth\", \"m\":\"failed\"}"); + } + + + } + + } else { + + LogManager.LOGGER.info("(WS) FIXME: SocketServer:onMessage"); + + } + + + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + System.out.println("received ByteBuffer from " + conn.getRemoteSocketAddress()); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + System.err.println("an error occured on connection " + conn.getRemoteSocketAddress() + ':' + ex); + ex.printStackTrace(); + } + + @Override + public void onStart() { + LogManager.LOGGER.info("(WS) Server started successfully"); + } + + /** + * Called every tick + */ + public void tick() { + + JSONObject json = new JSONObject(); + json.put("t", "tick"); + + LogManager.LOGGER.info("Notified " + userManager.getOnlineUsers().size() + " users"); + + for (OnlineUser user : userManager.getOnlineUsers()) { + + if (user.getWebSocket().isOpen()) { + //Send keyboard updated buffer + try{ + ArrayList kbBuffer = user.getUser().getControlledUnit().getKeyboardBuffer(); + JSONArray keys = new JSONArray(); + keys.addAll(kbBuffer); + json.put("keys", keys); + //Send tick message + user.getWebSocket().send(json.toJSONString()); + } catch (NullPointerException e){ + //User is online but not completely initialised + } + + } + } + + } + + public OnlineUserManager getUserManager() { + return userManager; + } +} \ No newline at end of file diff --git a/Server/src/net/simon987/server/webserver/SocketServerDatabase.java b/Server/src/net/simon987/server/webserver/SocketServerDatabase.java new file mode 100644 index 0000000..dad9451 --- /dev/null +++ b/Server/src/net/simon987/server/webserver/SocketServerDatabase.java @@ -0,0 +1,51 @@ +package net.simon987.server.webserver; + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.io.DatabaseManager; +import net.simon987.server.logging.LogManager; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +class SocketServerDatabase extends DatabaseManager { + + public SocketServerDatabase(ServerConfiguration config) { + super(config); + } + + String validateAuthToken(String token) { + + Connection connection = null; + try { + connection = getConnection(); + + PreparedStatement p = connection.prepareStatement("SELECT username FROM mar_user WHERE authToken=?"); + p.setString(1, token); + + ResultSet rs = p.executeQuery(); + + if (rs.next()) { + + return rs.getString("username"); + + } else { + return null; + } + + } catch (SQLException e) { + LogManager.LOGGER.severe("MySQL Error " + e.getErrorCode() + ": " + e.getMessage()); + + } finally { + try { + connection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + return null; + } + +} diff --git a/Server/src/net/simon987/server/webserver/TerrainRequestHandler.java b/Server/src/net/simon987/server/webserver/TerrainRequestHandler.java new file mode 100644 index 0000000..62f7f9d --- /dev/null +++ b/Server/src/net/simon987/server/webserver/TerrainRequestHandler.java @@ -0,0 +1,43 @@ +package net.simon987.server.webserver; + +import net.simon987.server.GameServer; +import net.simon987.server.game.World; +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +public class TerrainRequestHandler implements MessageHandler { + + @Override + public void handle(OnlineUser user, JSONObject json) { + if (json.get("t").equals("terrain")) { + + LogManager.LOGGER.info("Terrain request from " + user.getUser().getUsername()); + + World world = GameServer.INSTANCE.getGameUniverse().getWorld( + Long.valueOf((long) json.get("x")).intValue(), + Long.valueOf((long) json.get("y")).intValue()); + + //todo It might be a good idea to cache this + if (world != null) { + JSONObject response = new JSONObject(); + + JSONArray terrain = new JSONArray(); + + int[][] tiles = world.getTileMap().getTiles(); + for (int x = 0; x < World.WORLD_SIZE; x++) { + for (int y = 0; y < World.WORLD_SIZE; y++) { + terrain.add(tiles[y][x]); + } + } + + response.put("t", "terrain"); + response.put("terrain", terrain); + + user.getWebSocket().send(response.toJSONString()); + } else { + LogManager.LOGGER.severe("FIXME handle:TerrainRequestHandler"); + } + } + } +} diff --git a/Server/src/net/simon987/server/webserver/UserInfoRequestHandler.java b/Server/src/net/simon987/server/webserver/UserInfoRequestHandler.java new file mode 100644 index 0000000..218b35a --- /dev/null +++ b/Server/src/net/simon987/server/webserver/UserInfoRequestHandler.java @@ -0,0 +1,29 @@ +package net.simon987.server.webserver; + +import net.simon987.server.game.GameObject; +import net.simon987.server.logging.LogManager; +import org.json.simple.JSONObject; + +public class UserInfoRequestHandler implements MessageHandler { + + + @Override + public void handle(OnlineUser user, JSONObject message) { + + if (message.get("t").equals("userInfo")) { + LogManager.LOGGER.info("(WS) User info request from " + user.getUser().getUsername()); + + GameObject object = (GameObject)user.getUser().getControlledUnit(); + + JSONObject json = new JSONObject(); + json.put("t", "userInfo"); + json.put("worldX", object.getWorld().getX()); + json.put("worldY", object.getWorld().getY()); + json.put("x", object.getX()); + json.put("y", object.getY()); + + user.getWebSocket().send(json.toJSONString()); + + } + } +} diff --git a/Server/test/net/simon987/server/assembly/CPUTest.java b/Server/test/net/simon987/server/assembly/CPUTest.java new file mode 100644 index 0000000..db6426b --- /dev/null +++ b/Server/test/net/simon987/server/assembly/CPUTest.java @@ -0,0 +1,39 @@ +package net.simon987.server.assembly; + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.exception.CancelledException; +import net.simon987.server.user.User; +import org.junit.Test; + +import java.io.File; +import java.util.Random; + +public class CPUTest { + + @Test + public void executeInstruction() throws CancelledException { + + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + User user = new User(); + CPU cpu = new CPU(config, user); + + + for(int i = 0 ; i < 3 ; i++){ + //Execute every possible instruction with random values in memory + cpu.reset(); + cpu.getMemory().clear(); + Random random = new Random(); + random.nextBytes(cpu.getMemory().getBytes()); + + for (int machineCode = Character.MIN_VALUE; machineCode < Character.MAX_VALUE; machineCode++) { + Instruction instruction = cpu.getInstructionSet().get(machineCode & 0x03F); // 0000 0000 00XX XXXX + + int source = (machineCode >> 11) & 0x001F; // XXXX X000 0000 0000 + int destination = (machineCode >> 6) & 0x001F; // 0000 0XXX XX00 0000 + + cpu.executeInstruction(instruction, source, destination); + } + } + } + +} \ No newline at end of file diff --git a/Server/test/net/simon987/server/assembly/MemoryTest.java b/Server/test/net/simon987/server/assembly/MemoryTest.java new file mode 100644 index 0000000..b26c3c2 --- /dev/null +++ b/Server/test/net/simon987/server/assembly/MemoryTest.java @@ -0,0 +1,51 @@ +package net.simon987.server.assembly; + +import net.simon987.server.ServerConfiguration; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.*; + + +public class MemoryTest { + @Test + public void getSet() { + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + int memorySize = config.getInt("memory_size"); + Memory memory = new Memory(memorySize); + + memory.set(1, 1); + assertEquals(1, memory.get(1)); + + memory.set(memorySize / 2 - 1, 1); + assertEquals(1, memory.get(memorySize / 2 - 1)); + + memory.get(memorySize / 2); + memory.get(-1); + + memory.set(memorySize / 2, 1); + memory.set(-1, 1); + } + + @Test + public void write() { + + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + int memorySize = config.getInt("memory_size"); + Memory memory = new Memory(memorySize); + + + assertTrue(memory.write(0, new byte[memorySize], memorySize)); + assertFalse(memory.write(0, new byte[memorySize], memorySize + 1)); + assertFalse(memory.write(0, new byte[memorySize], -1)); + assertFalse(memory.write(-1, new byte[memorySize], 10)); + + assertFalse(memory.write(memorySize / 2, new byte[15], 1)); + assertFalse(memory.write((memorySize / 2) - 5, new byte[11], 11)); + assertTrue(memory.write((memorySize / 2) - 5, new byte[11], 10)); + + } + + +} \ No newline at end of file diff --git a/Server/test/net/simon987/server/assembly/OperandTest.java b/Server/test/net/simon987/server/assembly/OperandTest.java new file mode 100644 index 0000000..ec62389 --- /dev/null +++ b/Server/test/net/simon987/server/assembly/OperandTest.java @@ -0,0 +1,151 @@ +package net.simon987.server.assembly; + +import net.simon987.server.assembly.exception.InvalidOperandException; +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + + + +public class OperandTest { + + + @Test + public void Operand() { + + RegisterSet registerSet = new DefaultRegisterSet(); + HashMap labels = new HashMap<>(10); + labels.put("label1", (char) 10); + labels.put("label2", (char) 20); + labels.put("label3", (char) 30); + labels.put("label4", (char) 40); + + + //Valid operands + try { + //Register + Operand reg1 = new Operand("a", labels, registerSet, 0); + assertEquals(OperandType.REGISTER16, reg1.getType()); + assertEquals(1, reg1.getValue()); + assertEquals(0, reg1.getData()); + + Operand reg2 = new Operand("a ", labels, registerSet, 0); + assertEquals(OperandType.REGISTER16, reg2.getType()); + assertEquals(1, reg2.getValue()); + assertEquals(0, reg2.getData()); + + Operand reg3 = new Operand(" A ", labels, registerSet, 0); + assertEquals(OperandType.REGISTER16, reg3.getType()); + assertEquals(1, reg3.getValue()); + assertEquals(0, reg3.getData()); + + Operand reg4 = new Operand("B", labels, registerSet, 0); + assertEquals(OperandType.REGISTER16, reg4.getType()); + assertEquals(2, reg4.getValue()); + assertEquals(0, reg4.getData()); + + //Immediate + Operand imm1 = new Operand(" +12", labels, registerSet, 0); + assertEquals(OperandType.IMMEDIATE16, imm1.getType()); + assertEquals(Operand.IMMEDIATE_VALUE, imm1.getValue()); + assertEquals(12, imm1.getData()); + + Operand imm2 = new Operand(" -12", labels, registerSet, 0); + assertEquals(OperandType.IMMEDIATE16, imm2.getType()); + assertEquals(Operand.IMMEDIATE_VALUE, imm2.getValue()); + assertEquals(-12, imm2.getData()); + + Operand imm3 = new Operand(" 0xABCD", labels, registerSet, 0); + assertEquals(OperandType.IMMEDIATE16, imm3.getType()); + assertEquals(Operand.IMMEDIATE_VALUE, imm3.getValue()); + assertEquals(0xABCD, imm3.getData()); + + Operand imm4 = new Operand(" label1", labels, registerSet, 0); + assertEquals(OperandType.IMMEDIATE16, imm4.getType()); + assertEquals(Operand.IMMEDIATE_VALUE, imm4.getValue()); + assertEquals(10, imm4.getData()); + + //Memory Immediate + Operand mem1 = new Operand("[+12]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_IMM16, mem1.getType()); + assertEquals(Operand.IMMEDIATE_VALUE_MEM, mem1.getValue()); + assertEquals(12, mem1.getData()); + + Operand mem2 = new Operand("[-12 ]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_IMM16, mem2.getType()); + assertEquals(Operand.IMMEDIATE_VALUE_MEM, mem2.getValue()); + assertEquals(-12, mem2.getData()); + + Operand mem3 = new Operand(" [ 0xABCD]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_IMM16, mem3.getType()); + assertEquals(Operand.IMMEDIATE_VALUE_MEM, mem3.getValue()); + assertEquals(0xABCD, mem3.getData()); + + Operand mem4 = new Operand("[ label1 ]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_IMM16, mem4.getType()); + assertEquals(Operand.IMMEDIATE_VALUE_MEM, mem4.getValue()); + assertEquals(10, mem4.getData()); + + //Memory Reg + Operand mem5 = new Operand("[ A ]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_REG16, mem5.getType()); + assertEquals(1 + registerSet.size(), mem5.getValue()); + assertEquals(0, mem5.getData()); + + Operand mem6 = new Operand("[ B ]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_REG16, mem6.getType()); + assertEquals(2 + registerSet.size(), mem6.getValue()); + assertEquals(0, mem6.getData()); + + //Memory Reg + displacement + Operand mem7 = new Operand("[ A + 1 ]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_REG_DISP16, mem7.getType()); + assertEquals(1 + 2 * registerSet.size(), mem7.getValue()); + assertEquals(1, mem7.getData()); + + Operand mem8 = new Operand("[ B + label1 ]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_REG_DISP16, mem8.getType()); + assertEquals(2 + 2 * registerSet.size(), mem8.getValue()); + assertEquals(10, mem8.getData()); + + Operand mem9 = new Operand("[ BP + 1 ]", labels, registerSet, 0); + assertEquals(OperandType.MEMORY_REG_DISP16, mem9.getType()); + assertEquals(8 + 2 * registerSet.size(), mem9.getValue()); + assertEquals(1, mem9.getData()); + + + } catch (InvalidOperandException e) { + fail("Failed trying to parse a valid operand"); + } + + //Invalid operands + try{ new Operand("aa", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("a1", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("a_", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("_a", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("_1", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("S", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("label1_", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("+label1", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[- 12]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[12+1]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[+label1", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[*12]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[-A]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[A B]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[A + B]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[A + -1]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[A + ]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[A+A+]", labels, registerSet, 0); } catch (InvalidOperandException e){} + try{ new Operand("[A+[1]]", labels, registerSet, 0); } catch (InvalidOperandException e){} + + + + + } + +} \ No newline at end of file diff --git a/Server/test/net/simon987/server/assembly/RegisterSetTest.java b/Server/test/net/simon987/server/assembly/RegisterSetTest.java new file mode 100644 index 0000000..c14c98a --- /dev/null +++ b/Server/test/net/simon987/server/assembly/RegisterSetTest.java @@ -0,0 +1,78 @@ +package net.simon987.server.assembly; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class RegisterSetTest { + @Test + public void getIndex() { + + RegisterSet registerSet = new RegisterSet(); + + Register r1 = new Register("R1"); + Register r2 = new Register("R2"); + + registerSet.put(1, r1); + registerSet.put(2, r2); + + assertEquals(1, registerSet.getIndex("R1")); + assertEquals(2, registerSet.getIndex("R2")); + + assertEquals(-1, registerSet.getIndex("Unknown register name")); + } + + @Test + public void getRegister() { + + RegisterSet registerSet = new RegisterSet(); + + Register r1 = new Register("R1"); + Register r2 = new Register("R2"); + + registerSet.put(1, r1); + registerSet.put(2, r2); + + assertEquals(r1, registerSet.getRegister("R1")); + assertEquals(r1, registerSet.getRegister(1)); + + assertEquals(r2, registerSet.getRegister("R2")); + assertEquals(r2, registerSet.getRegister(2)); + + //Test unknown registers + assertEquals(null, registerSet.getRegister("Unknown")); + assertEquals(null, registerSet.getRegister(3)); + } + + @Test + public void get() { + RegisterSet registerSet = new RegisterSet(); + + Register r1 = new Register("R1"); + + registerSet.put(1, r1); + + r1.setValue(10); + assertEquals(10, registerSet.get(1)); + + //Test unknown indexes + assertEquals(0, registerSet.get(2)); + } + + @Test + public void set() { + + RegisterSet registerSet = new RegisterSet(); + + Register r1 = new Register("R1"); + + registerSet.put(1, r1); + registerSet.set(1, 10); + + assertEquals(10, r1.getValue()); + + //Test unknown indexes + registerSet.set(3, 10); + } + +} \ No newline at end of file diff --git a/Server/test/net/simon987/server/assembly/instruction/AddInstructionTest.java b/Server/test/net/simon987/server/assembly/instruction/AddInstructionTest.java new file mode 100644 index 0000000..e0a89df --- /dev/null +++ b/Server/test/net/simon987/server/assembly/instruction/AddInstructionTest.java @@ -0,0 +1,163 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.Memory; +import net.simon987.server.assembly.Register; +import net.simon987.server.assembly.RegisterSet; +import net.simon987.server.assembly.Status; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.*; + + +public class AddInstructionTest { + + + /** + * ADD mem/reg, mem/reg + */ + @Test + public void addTargetTarget() { + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + int memorySize = config.getInt("memory_size"); + + //Memory + Memory memory = new Memory(memorySize); + memory.clear(); + + //RegisterSet + RegisterSet registerSet = new RegisterSet(); + registerSet.put(1, new Register("T1")); + registerSet.put(2, new Register("T2")); + registerSet.clear(); + + //Status + Status status = new Status(); + status.clear(); + + AddInstruction addInstruction = new AddInstruction(); + + //ADD mem, mem + //Positive numbers + memory.set(0, 10); + memory.set(1, 10); + addInstruction.execute(memory, 0, memory, 1, status); + assertEquals(20, memory.get(0)); + assertEquals(10, memory.get(1)); + assertEquals(10, memory.get(1)); + //FLAGS Should be CF=0 ZF=0 SF=0 OF=0 + assertFalse(status.isSignFlag()); + assertFalse(status.isZeroFlag()); + assertFalse(status.isCarryFlag()); + assertFalse(status.isOverflowFlag()); + assertFalse(status.isBreakFlag()); + + memory.clear(); + memory.set(memorySize, 10); + memory.set(1, 10); + addInstruction.execute(memory, memorySize, memory, 1, status); + assertEquals(20, memory.get(memorySize)); + assertEquals(10, memory.get(1)); + //FLAGS Should be CF=0 ZF=0 SF=0 OF=0 + assertFalse(status.isSignFlag()); + assertFalse(status.isZeroFlag()); + assertFalse(status.isCarryFlag()); + assertFalse(status.isOverflowFlag()); + assertFalse(status.isBreakFlag()); + + //Large positive numbers + memory.clear(); + memory.set(0, 2); + memory.set(1, 0xFFFF); + addInstruction.execute(memory, memorySize, memory, 1, status); + assertEquals(1, memory.get(0)); + assertEquals(0xFFFF, memory.get(1)); + //FLAGS Should be CF=1 ZF=0 SF=0 OF=0 + assertFalse(status.isSignFlag()); + assertFalse(status.isZeroFlag()); + assertTrue(status.isCarryFlag()); + assertFalse(status.isOverflowFlag()); + assertFalse(status.isBreakFlag()); + + //Zero result + memory.clear(); + memory.set(0, 1); + memory.set(1, 0xFFFF); + addInstruction.execute(memory, memorySize, memory, 1, status); + assertEquals(0, memory.get(0)); + assertEquals(0xFFFF, memory.get(1)); + //FLAGS Should be CF=1 ZF=1 SF=0 OF=0 + assertTrue(status.isCarryFlag()); + assertTrue(status.isZeroFlag()); + assertFalse(status.isSignFlag()); + assertFalse(status.isOverflowFlag()); + assertFalse(status.isBreakFlag()); + + //Overflow + memory.clear(); + memory.set(0, 0x8000); + memory.set(1, 0xFFFF); + addInstruction.execute(memory, 0, memory, 1, status); + assertEquals(0x7FFF, memory.get(0)); + assertEquals(0xFFFF, memory.get(1)); + //FLAGS Should be CF=1 ZF=0 SF=0 OF=1 + assertTrue(status.isCarryFlag()); + assertFalse(status.isZeroFlag()); + assertFalse(status.isSignFlag()); + assertTrue(status.isOverflowFlag()); + assertFalse(status.isBreakFlag()); + + //ADD reg, reg + //Positive numbers + registerSet.set(1, 10); + registerSet.set(2, 10); + addInstruction.execute(registerSet, 1, registerSet, 2, status); + assertEquals(20, registerSet.get(1)); + assertEquals(10, registerSet.get(2)); + //FLAGS Should be CF=0 ZF=0 SF=0 OF=0 + assertFalse(status.isSignFlag()); + assertFalse(status.isZeroFlag()); + assertFalse(status.isCarryFlag()); + assertFalse(status.isOverflowFlag()); + assertFalse(status.isBreakFlag()); + + } + + + /** + * ADD mem/reg, imm + */ + @Test + public void addTargetImm() { + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + int memorySize = config.getInt("memory_size"); + + //Memory + Memory memory = new Memory(memorySize); + memory.clear(); + + //Status + Status status = new Status(); + status.clear(); + + AddInstruction addInstruction = new AddInstruction(); + + //Positive number + memory.clear(); + memory.set(0, 10); + addInstruction.execute(memory, 0, 10, status); + assertEquals(20, memory.get(0)); + //FLAGS Should be CF=0 ZF=0 SF=0 OF=0 + assertFalse(status.isSignFlag()); + assertFalse(status.isZeroFlag()); + assertFalse(status.isCarryFlag()); + assertFalse(status.isOverflowFlag()); + assertFalse(status.isBreakFlag()); + + //The rest is assumed to work since it is tested with addTargetTarget() and behavior shouldn't be different; + + } + +} \ No newline at end of file diff --git a/Server/test/net/simon987/server/assembly/instruction/AndInstructionTest.java b/Server/test/net/simon987/server/assembly/instruction/AndInstructionTest.java new file mode 100644 index 0000000..1e56911 --- /dev/null +++ b/Server/test/net/simon987/server/assembly/instruction/AndInstructionTest.java @@ -0,0 +1,84 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.Memory; +import net.simon987.server.assembly.Status; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertEquals; + + +public class AndInstructionTest { + @Test + public void executeTargetTarget() { + + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + int memorySize = config.getInt("memory_size"); + + //Memory + Memory memory = new Memory(memorySize); + memory.clear(); + + //Status + Status status = new Status(); + status.clear(); + + AndInstruction andInstruction = new AndInstruction(); + + //mem mem + memory.clear(); + memory.set(0, 0xF010); + memory.set(1, 0xF111); + andInstruction.execute(memory, 0, memory, 1, status); + + assertEquals(0xF010, memory.get(0)); + assertEquals(true, status.isSignFlag()); + assertEquals(false, status.isZeroFlag()); + assertEquals(false, status.isOverflowFlag()); + assertEquals(false, status.isCarryFlag()); + + //mem mem + memory.clear(); + memory.set(0, 0x1010); + memory.set(1, 0x0101); + andInstruction.execute(memory, 0, memory, 1, status); + + assertEquals(0, memory.get(0)); + assertEquals(false, status.isSignFlag()); + assertEquals(true, status.isZeroFlag()); + assertEquals(false, status.isOverflowFlag()); + assertEquals(false, status.isCarryFlag()); + + } + + @Test + public void executeTargetImm() { + + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + int memorySize = config.getInt("memory_size"); + + //Memory + Memory memory = new Memory(memorySize); + memory.clear(); + + //Status + Status status = new Status(); + status.clear(); + + AndInstruction andInstruction = new AndInstruction(); + + //mem imm + memory.clear(); + memory.set(0, 0x1010); + andInstruction.execute(memory, 0, 0x0101, status); + + assertEquals(0, memory.get(0)); + assertEquals(false, status.isSignFlag()); + assertEquals(true, status.isZeroFlag()); + assertEquals(false, status.isOverflowFlag()); + assertEquals(false, status.isCarryFlag()); + } + +} \ No newline at end of file diff --git a/Server/test/net/simon987/server/assembly/instruction/BrkInstructionTest.java b/Server/test/net/simon987/server/assembly/instruction/BrkInstructionTest.java new file mode 100644 index 0000000..240d223 --- /dev/null +++ b/Server/test/net/simon987/server/assembly/instruction/BrkInstructionTest.java @@ -0,0 +1,24 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.assembly.Status; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class BrkInstructionTest { + @Test + public void execute() throws Exception { + + //Status + Status status = new Status(); + status.clear(); + + BrkInstruction brkInstruction = new BrkInstruction(); + + brkInstruction.execute(status); + + assertEquals(true, status.isBreakFlag()); + + } + +} \ No newline at end of file diff --git a/Server/test/net/simon987/server/assembly/instruction/CallInstructionTest.java b/Server/test/net/simon987/server/assembly/instruction/CallInstructionTest.java new file mode 100644 index 0000000..734b73d --- /dev/null +++ b/Server/test/net/simon987/server/assembly/instruction/CallInstructionTest.java @@ -0,0 +1,47 @@ +package net.simon987.server.assembly.instruction; + +import net.simon987.server.ServerConfiguration; +import net.simon987.server.assembly.*; +import net.simon987.server.user.User; +import org.junit.Test; + +import java.io.File; + +public class CallInstructionTest { + + + @Test + public void execute() throws Exception { + + ServerConfiguration config = new ServerConfiguration(new File("config.properties")); + int memorySize = config.getInt("memory_size"); + + //Memory + Memory memory = new Memory(memorySize); + memory.clear(); + + //RegisterSet + RegisterSet registerSet = new RegisterSet(); + registerSet.put(1, new Register("T1")); + registerSet.put(2, new Register("T2")); + registerSet.clear(); + + //Status + Status status = new Status(); + status.clear(); + + CPU cpu = new CPU(config, new User()); + + CallInstruction callInstruction = new CallInstruction(cpu); + + //We have to check if IP is 'pushed' correctly (try to pop it), the + + + + } + + @Test + public void execute1() throws Exception { + } + +} \ No newline at end of file diff --git a/config.properties b/config.properties new file mode 100644 index 0000000..cff4e33 --- /dev/null +++ b/config.properties @@ -0,0 +1,83 @@ +# MySQL username +mysql_user=mar +# MySQL password +mysql_pass=mar +# MySQL address +mysql_url=jdbc:mysql://localhost:3306/mar?useSSL=false +save_interval=10 +# Web server port +webSocket_port=8887 +webSocket_host=localhost +# ---------------------------------------------- + +# Length of a tick in ms +tick_length=1000 +# Default offset of the origin (starting point of code execution) in words +org_offset=1024 +# Address of the stack bottom +stack_bottom=32768 +# Size of the memory in bytes +memory_size=65536 +# The game server will process the new user every 'new_user_interval' ticks +new_user_interval=10 +# todo set to 10^ +# Initial location of new user's controlled unit +new_user_x=7 +new_user_y=15 +new_user_worldX = 0 +new_user_worldY = 0 +# Effect when the new user's controlled unit spawns +new_user_effect=WARNING +# Default user code +new_user_code=; TODO: Add link to wiki or something here\n\ + .data\n\ + ; Declare data here\n\ + .text\n\ + main:\n\ + ; Write code here\n\ + brk +# Default held item +new_user_item=0 +# ---------------------------------------------- + +# Biomass units yield for a plant +plant_yield=2 +# Grow time in ticks for a plant to grow +plant_grow_time=32 +# Minimum tree count for the WorldGenerator +minTreeCount=3 +# Maximum tree count for the WorldGenerator +maxTreeCount=10 +# ---------------------------------------------- + +#Number of ticks of cooking for 1 unit of biomass +biomass_fuel_value=32 +#Number of ticks required to cook iron (1-255) +iron_cook_time=8 +#Number of ticks required to cook copper (1-255) +copper_cook_time=8 +# Hit points of the Tortoise GameObject +tortoise_hp=10 +# Number of biomass parts required to make a biogas unit +# The number of biomass units required to make a biogas unit= +# biomass_cost / biomass_fuel_value +biogas_cost=48 +# ---------------------------------------------- +# Minimum center point count for the WorldGenerator +wg_centerPointCountMin=5 +# Maximum center point count for the WorldGenerator +wg_centerPointCountMax=15 +# Wall/Plain tile ratio for the WorldGenerator +wg_wallPlainRatio=4 +# Minimum iron tiles count for the WorldGenerator +wg_minIronCount=1 +# Minimum iron tile count for the WorldGenerator +wg_maxIronCount=3 +# Minimum copper tile count for the WorldGenerator +wg_minCopperCount=1 +# Maximum copper tile count for the WorldGenerator +wg_maxCopperCount=3 +# ---------------------------------------------- + +# Maximum execution time of user code in ms +user_timeout=40 \ No newline at end of file diff --git a/plugins/Cubot.jar b/plugins/Cubot.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b084539e8d63e79f1a73e8e686422860113c93e GIT binary patch literal 16609 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 literal 0 HcmV?d00001 diff --git a/plugins/Plant.jar b/plugins/Plant.jar new file mode 100644 index 0000000000000000000000000000000000000000..e616e70af7e473e6c5aad0dd330143b5868a7fa5 GIT binary patch literal 5651 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

3URXMUNJ$LYS7@duVG=x^bP?P&bk1#f5()1&W zSl&SV`ROGOsk!EfeSE_#u!~XeiKA4=!?yjkWkYo~GJsE6{nLd5+ft=nUR5hsV@dMS zy?jJ}DQ%41cUs#tPJ|Z|B=h|>?ay$$HL6e>Ki1HmqBk`^0}gExGrPFG0%{b`w4+cl z+rayV#os8GWJ58eChi1XE$6s+34AC75)%Lj#Qy!2z}58rrjwCey^_B~EN)jf7gwm8 zhXd4I4XMJ(&8^JYTl!gzYnTH$@OiMjf_H+WM`eJ6t%prxaA1T0fVk1(RMOBf^T@a~ z81qpMXJvtifYji)*5LK;aPh!)>Lk)v!PT!mEj0{Ge88WDx+|04@JqJ)v)8q7_X`iM zj{hd({n_SJrix{RK?oe@Oa2dhYLWuj}&nlDV$S eUtlHsFIB3ghJD3Fuhm~Xfc{mX=U*8C0RIQ%9>g^O literal 0 HcmV?d00001