mirror of
https://github.com/simon987/Much-Assembly-Required.git
synced 2025-04-10 14:26:45 +00:00
Initial commit
This commit is contained in:
commit
b76a1adea2
3
Plugin Cubot/plugin.properties
Normal file
3
Plugin Cubot/plugin.properties
Normal file
@ -0,0 +1,3 @@
|
||||
classpath=net.simon987.cubotplugin.CubotPlugin
|
||||
name=Cubot Plugin
|
||||
version=1.0
|
113
Plugin Cubot/src/net/simon987/cubotplugin/Cubot.java
Normal file
113
Plugin Cubot/src/net/simon987/cubotplugin/Cubot.java
Normal file
@ -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<Integer> 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<Integer> kbBuffer) {
|
||||
keyboardBuffer = kbBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Integer> getKeyboardBuffer() {
|
||||
return keyboardBuffer;
|
||||
}
|
||||
|
||||
public void clearKeyboardBuffer(){
|
||||
keyboardBuffer.clear();
|
||||
}
|
||||
|
||||
public void setCurrentAction(CubotAction currentAction) {
|
||||
this.currentAction = currentAction;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package net.simon987.cubotplugin;
|
||||
|
||||
public enum CubotAction {
|
||||
IDLE,
|
||||
DIGGING,
|
||||
WALKING
|
||||
|
||||
}
|
59
Plugin Cubot/src/net/simon987/cubotplugin/CubotDrill.java
Normal file
59
Plugin Cubot/src/net/simon987/cubotplugin/CubotDrill.java
Normal file
@ -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")));
|
||||
}
|
||||
}
|
@ -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")));
|
||||
}
|
||||
}
|
83
Plugin Cubot/src/net/simon987/cubotplugin/CubotLaser.java
Normal file
83
Plugin Cubot/src/net/simon987/cubotplugin/CubotLaser.java
Normal file
@ -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<GameObject> 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")));
|
||||
}
|
||||
}
|
77
Plugin Cubot/src/net/simon987/cubotplugin/CubotLeg.java
Normal file
77
Plugin Cubot/src/net/simon987/cubotplugin/CubotLeg.java
Normal file
@ -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")));
|
||||
}
|
||||
|
||||
|
||||
}
|
56
Plugin Cubot/src/net/simon987/cubotplugin/CubotPlugin.java
Normal file
56
Plugin Cubot/src/net/simon987/cubotplugin/CubotPlugin.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
131
Plugin Cubot/src/net/simon987/cubotplugin/CubotRadar.java
Normal file
131
Plugin Cubot/src/net/simon987/cubotplugin/CubotRadar.java
Normal file
@ -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<Node> 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")));
|
||||
}
|
||||
}
|
65
Plugin Cubot/src/net/simon987/cubotplugin/Keyboard.java
Normal file
65
Plugin Cubot/src/net/simon987/cubotplugin/Keyboard.java
Normal file
@ -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")));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
19
Plugin Cubot/test/net/simon987/cubotplugin/CubotTest.java
Normal file
19
Plugin Cubot/test/net/simon987/cubotplugin/CubotTest.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
3
Plugin Kiln/plugin.properties
Normal file
3
Plugin Kiln/plugin.properties
Normal file
@ -0,0 +1,3 @@
|
||||
classpath=net.simon987.kilnplugin.KilnPlugin
|
||||
name=Kiln Plugin
|
||||
version=1.0
|
21
Plugin Kiln/src/net/simon987/kilnplugin/KilnPlugin.java
Normal file
21
Plugin Kiln/src/net/simon987/kilnplugin/KilnPlugin.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
3
Plugin Plant/plugin.properties
Normal file
3
Plugin Plant/plugin.properties
Normal file
@ -0,0 +1,3 @@
|
||||
classpath=net.simon987.plantplugin.PlantPlugin
|
||||
name=Plant Plugin
|
||||
version=1.0
|
162
Plugin Plant/src/net/simon987/plantplugin/Plant.java
Normal file
162
Plugin Plant/src/net/simon987/plantplugin/Plant.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
32
Plugin Plant/src/net/simon987/plantplugin/PlantPlugin.java
Normal file
32
Plugin Plant/src/net/simon987/plantplugin/PlantPlugin.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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<Plant> plants = generatePlants(((WorldGenerationEvent)event).getWorld());
|
||||
|
||||
((WorldGenerationEvent)event).getWorld().getGameObjects().addAll(plants);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a list of plants for a world
|
||||
*/
|
||||
public ArrayList<Plant> 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<Plant> 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;
|
||||
}
|
||||
}
|
163
Server/src/net/simon987/server/GameServer.java
Normal file
163
Server/src/net/simon987/server/GameServer.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
31
Server/src/net/simon987/server/Main.java
Normal file
31
Server/src/net/simon987/server/Main.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
42
Server/src/net/simon987/server/ServerConfiguration.java
Normal file
42
Server/src/net/simon987/server/ServerConfiguration.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
514
Server/src/net/simon987/server/assembly/Assembler.java
Executable file
514
Server/src/net/simon987/server/assembly/Assembler.java
Executable file
@ -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<String, Character> 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<String, Character> 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: <factor> DUP(<value>)", 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<String, Character> labels, int currentLine)
|
||||
throws AssemblyException {
|
||||
/* the EQU pseudo instruction is equivalent to the #define compiler directive in C/C++
|
||||
* usage: constant_name EQU <immediate_value>
|
||||
* 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<String, Character> 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<String, Character> 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();
|
||||
|
||||
}
|
||||
}
|
99
Server/src/net/simon987/server/assembly/AssemblyResult.java
Executable file
99
Server/src/net/simon987/server/assembly/AssemblyResult.java
Executable file
@ -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<String, Character> labels = new HashMap<>(20);
|
||||
/**
|
||||
* List of exceptions encountered during the assembly attempt,
|
||||
* they will be displayed in the editor
|
||||
*/
|
||||
ArrayList<AssemblyException> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
436
Server/src/net/simon987/server/assembly/CPU.java
Executable file
436
Server/src/net/simon987/server/assembly/CPU.java
Executable file
@ -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<Integer, CpuHardware> 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<JSONObject>)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;
|
||||
}
|
||||
}
|
43
Server/src/net/simon987/server/assembly/CpuHardware.java
Normal file
43
Server/src/net/simon987/server/assembly/CpuHardware.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
87
Server/src/net/simon987/server/assembly/DefaultInstructionSet.java
Executable file
87
Server/src/net/simon987/server/assembly/DefaultInstructionSet.java
Executable file
@ -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<Integer, Instruction> 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);
|
||||
}
|
||||
}
|
22
Server/src/net/simon987/server/assembly/DefaultRegisterSet.java
Executable file
22
Server/src/net/simon987/server/assembly/DefaultRegisterSet.java
Executable file
@ -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"));
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
214
Server/src/net/simon987/server/assembly/Instruction.java
Executable file
214
Server/src/net/simon987/server/assembly/Instruction.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
33
Server/src/net/simon987/server/assembly/InstructionSet.java
Executable file
33
Server/src/net/simon987/server/assembly/InstructionSet.java
Executable file
@ -0,0 +1,33 @@
|
||||
package net.simon987.server.assembly;
|
||||
|
||||
/**
|
||||
* A set of instructions for a CPU.
|
||||
* <p>
|
||||
* 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);
|
||||
|
||||
}
|
101
Server/src/net/simon987/server/assembly/MachineCode.java
Executable file
101
Server/src/net/simon987/server/assembly/MachineCode.java
Executable file
@ -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<Character> 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();
|
||||
}
|
||||
|
||||
}
|
160
Server/src/net/simon987/server/assembly/Memory.java
Executable file
160
Server/src/net/simon987/server/assembly/Memory.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
235
Server/src/net/simon987/server/assembly/Operand.java
Executable file
235
Server/src/net/simon987/server/assembly/Operand.java
Executable file
@ -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<String, Character> 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<String, Character> 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<String, Character> 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;
|
||||
}
|
||||
}
|
26
Server/src/net/simon987/server/assembly/OperandType.java
Executable file
26
Server/src/net/simon987/server/assembly/OperandType.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
50
Server/src/net/simon987/server/assembly/Register.java
Executable file
50
Server/src/net/simon987/server/assembly/Register.java
Executable file
@ -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;
|
||||
|
||||
}
|
||||
}
|
191
Server/src/net/simon987/server/assembly/RegisterSet.java
Executable file
191
Server/src/net/simon987/server/assembly/RegisterSet.java
Executable file
@ -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<Integer, Register> 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
|
||||
* <p>
|
||||
* 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<JSONObject>)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;
|
||||
}
|
||||
}
|
21
Server/src/net/simon987/server/assembly/Segment.java
Executable file
21
Server/src/net/simon987/server/assembly/Segment.java
Executable file
@ -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
|
||||
|
||||
}
|
135
Server/src/net/simon987/server/assembly/Status.java
Executable file
135
Server/src/net/simon987/server/assembly/Status.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
30
Server/src/net/simon987/server/assembly/Target.java
Executable file
30
Server/src/net/simon987/server/assembly/Target.java
Executable file
@ -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.
|
||||
* <p>
|
||||
* For example: MOV dstTARGET, srcTARGET
|
||||
* <p>
|
||||
* 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);
|
||||
|
||||
}
|
115
Server/src/net/simon987/server/assembly/Util.java
Executable file
115
Server/src/net/simon987/server/assembly/Util.java
Executable file
@ -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);
|
||||
|
||||
}
|
||||
}
|
30
Server/src/net/simon987/server/assembly/exception/AssemblyException.java
Executable file
30
Server/src/net/simon987/server/assembly/exception/AssemblyException.java
Executable file
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package net.simon987.server.assembly.exception;
|
||||
|
||||
public class CancelledException extends Exception {
|
||||
public CancelledException() {
|
||||
super("CPU Initialisation was cancelled");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
10
Server/src/net/simon987/server/assembly/exception/EmptyLineException.java
Executable file
10
Server/src/net/simon987/server/assembly/exception/EmptyLineException.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
56
Server/src/net/simon987/server/assembly/instruction/AddInstruction.java
Executable file
56
Server/src/net/simon987/server/assembly/instruction/AddInstruction.java
Executable file
@ -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
|
||||
* <p>
|
||||
* ADD A, B
|
||||
* A = A + B
|
||||
* </p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
64
Server/src/net/simon987/server/assembly/instruction/AndInstruction.java
Executable file
64
Server/src/net/simon987/server/assembly/instruction/AndInstruction.java
Executable file
@ -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
|
||||
* <p>
|
||||
* AND A, B
|
||||
* A = A & B
|
||||
* </p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
23
Server/src/net/simon987/server/assembly/instruction/BrkInstruction.java
Executable file
23
Server/src/net/simon987/server/assembly/instruction/BrkInstruction.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
* <br>
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* DIV C
|
||||
* A = Y:A / C
|
||||
* Y = Y:A % C
|
||||
* </p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
32
Server/src/net/simon987/server/assembly/instruction/HwiInstruction.java
Executable file
32
Server/src/net/simon987/server/assembly/instruction/HwiInstruction.java
Executable file
@ -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
|
||||
* </p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
49
Server/src/net/simon987/server/assembly/instruction/MovInstruction.java
Executable file
49
Server/src/net/simon987/server/assembly/instruction/MovInstruction.java
Executable file
@ -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.
|
||||
* <p>
|
||||
* 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)
|
||||
* </p>
|
||||
* <p>
|
||||
* The MOV instruction doesn't change any flags
|
||||
* </p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
14
Server/src/net/simon987/server/assembly/instruction/NopInstruction.java
Executable file
14
Server/src/net/simon987/server/assembly/instruction/NopInstruction.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
57
Server/src/net/simon987/server/assembly/instruction/SubInstruction.java
Executable file
57
Server/src/net/simon987/server/assembly/instruction/SubInstruction.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
33
Server/src/net/simon987/server/event/GameEvent.java
Normal file
33
Server/src/net/simon987/server/event/GameEvent.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
12
Server/src/net/simon987/server/event/GameEventListener.java
Normal file
12
Server/src/net/simon987/server/event/GameEventListener.java
Normal file
@ -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);
|
||||
|
||||
}
|
10
Server/src/net/simon987/server/event/UserCreationEvent.java
Normal file
10
Server/src/net/simon987/server/event/UserCreationEvent.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
13
Server/src/net/simon987/server/game/ControllableUnit.java
Normal file
13
Server/src/net/simon987/server/game/ControllableUnit.java
Normal file
@ -0,0 +1,13 @@
|
||||
package net.simon987.server.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface ControllableUnit {
|
||||
|
||||
int getObjectId();
|
||||
|
||||
void setKeyboardBuffer(ArrayList<Integer> kbBuffer);
|
||||
|
||||
ArrayList<Integer> getKeyboardBuffer();
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
39
Server/src/net/simon987/server/game/Direction.java
Executable file
39
Server/src/net/simon987/server/game/Direction.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
26
Server/src/net/simon987/server/game/EffectType.java
Normal file
26
Server/src/net/simon987/server/game/EffectType.java
Normal file
@ -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
|
||||
|
||||
|
||||
}
|
68
Server/src/net/simon987/server/game/GameEffect.java
Normal file
68
Server/src/net/simon987/server/game/GameEffect.java
Normal file
@ -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..)
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
221
Server/src/net/simon987/server/game/GameObject.java
Executable file
221
Server/src/net/simon987/server/game/GameObject.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package net.simon987.server.game;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public interface GameObjectDeserializer {
|
||||
|
||||
GameObject deserializeObject(JSONObject object);
|
||||
|
||||
}
|
171
Server/src/net/simon987/server/game/GameUniverse.java
Normal file
171
Server/src/net/simon987/server/game/GameUniverse.java
Normal file
@ -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<World> worlds;
|
||||
private ArrayList<User> 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<World> getWorlds() {
|
||||
return worlds;
|
||||
}
|
||||
|
||||
public ArrayList<User> 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<JSONObject>)universeJson.get("worlds")){
|
||||
worlds.add(World.deserialize(worldJson));
|
||||
}
|
||||
|
||||
for(JSONObject userJson : (ArrayList<JSONObject>)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;
|
||||
}
|
||||
}
|
20
Server/src/net/simon987/server/game/InventoryHolder.java
Normal file
20
Server/src/net/simon987/server/game/InventoryHolder.java
Normal file
@ -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);
|
||||
}
|
160
Server/src/net/simon987/server/game/TileMap.java
Executable file
160
Server/src/net/simon987/server/game/TileMap.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
21
Server/src/net/simon987/server/game/TmpObject.java
Normal file
21
Server/src/net/simon987/server/game/TmpObject.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
13
Server/src/net/simon987/server/game/Updatable.java
Normal file
13
Server/src/net/simon987/server/game/Updatable.java
Normal file
@ -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();
|
||||
|
||||
}
|
193
Server/src/net/simon987/server/game/World.java
Normal file
193
Server/src/net/simon987/server/game/World.java
Normal file
@ -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<GameObject> 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<GameObject> getGameObjects() {
|
||||
return gameObjects;
|
||||
}
|
||||
|
||||
public void update(){
|
||||
|
||||
ArrayList<GameObject> 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<JSONObject>)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.
|
||||
* <p>
|
||||
* 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<GameObject> getGameObjectsAt(int x, int y) {
|
||||
|
||||
ArrayList<GameObject> gameObjects = new ArrayList<>(2);
|
||||
|
||||
for (GameObject obj : this.gameObjects) {
|
||||
|
||||
if (obj.isAt(x, y)) {
|
||||
gameObjects.add(obj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return gameObjects;
|
||||
}
|
||||
|
||||
}
|
219
Server/src/net/simon987/server/game/WorldGenerator.java
Executable file
219
Server/src/net/simon987/server/game/WorldGenerator.java
Executable file
@ -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<Point, Integer> 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;
|
||||
}
|
||||
|
||||
}
|
70
Server/src/net/simon987/server/game/pathfinding/Node.java
Executable file
70
Server/src/net/simon987/server/game/pathfinding/Node.java
Executable file
@ -0,0 +1,70 @@
|
||||
package net.simon987.server.game.pathfinding;
|
||||
|
||||
/**
|
||||
* A single node in the search graph
|
||||
* <p>
|
||||
* 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 + ")";
|
||||
}
|
||||
}
|
159
Server/src/net/simon987/server/game/pathfinding/Pathfinder.java
Executable file
159
Server/src/net/simon987/server/game/pathfinding/Pathfinder.java
Executable file
@ -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
|
||||
* <p>
|
||||
* 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> getNeighbors(World world, Node[][] nodes, Node node) {
|
||||
|
||||
ArrayList<Node> 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;
|
||||
}
|
||||
|
||||
}
|
38
Server/src/net/simon987/server/game/pathfinding/SortedArrayList.java
Executable file
38
Server/src/net/simon987/server/game/pathfinding/SortedArrayList.java
Executable file
@ -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
|
||||
* <p>
|
||||
* inspired by http://www.cokeandcode.com/main/tutorials/path-finding/
|
||||
*/
|
||||
public class SortedArrayList extends ArrayList<Node> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user