Initial commit

This commit is contained in:
simon 2017-11-02 21:51:22 -04:00
commit b76a1adea2
130 changed files with 8441 additions and 0 deletions

View File

@ -0,0 +1,3 @@
classpath=net.simon987.cubotplugin.CubotPlugin
name=Cubot Plugin
version=1.0

View 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;
}
}

View File

@ -0,0 +1,8 @@
package net.simon987.cubotplugin;
public enum CubotAction {
IDLE,
DIGGING,
WALKING
}

View 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")));
}
}

View File

@ -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")));
}
}

View 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")));
}
}

View 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")));
}
}

View 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;
}
}

View 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")));
}
}

View 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")));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -0,0 +1,3 @@
classpath=net.simon987.kilnplugin.KilnPlugin
name=Kiln Plugin
version=1.0

View 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;
}
}

View File

@ -0,0 +1,3 @@
classpath=net.simon987.plantplugin.PlantPlugin
name=Plant Plugin
version=1.0

View 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;
}
}
}

View 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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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();
}
}

View 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);
}
}

View 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();
}
}

View 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);
}
}
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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"));
}
}

View File

@ -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);
}
}

View 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;
}
}

View 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);
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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
}

View 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;
}
}

View 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);
}

View 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);
}
}

View 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;
}
}

View File

@ -0,0 +1,7 @@
package net.simon987.server.assembly.exception;
public class CancelledException extends Exception {
public CancelledException() {
super("CPU Initialisation was cancelled");
}
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View File

@ -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;
}
}

View 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;
/**
* 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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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;
/**
* 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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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);
}
}
}
}
}

View 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);
}

View 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);
}
}

View File

@ -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();
}
}

View 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();
}

View File

@ -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);
}

View 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;
}
}
}

View 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
}

View 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;
}
}

View 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;
}
}

View File

@ -0,0 +1,9 @@
package net.simon987.server.game;
import org.json.simple.JSONObject;
public interface GameObjectDeserializer {
GameObject deserializeObject(JSONObject object);
}

View 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;
}
}

View 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);
}

View 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;
}
}

View 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;
}
}

View 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();
}

View 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;
}
}

View 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;
}
}

View 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 + ")";
}
}

View 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;
}
}

View 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