9 Commits
v1.1a ... v1.2a

Author SHA1 Message Date
simon
8701007ad9 Bug fixes related to floppy drive 2017-11-14 20:58:07 -05:00
simon
cfb8050cee Added basic floppy drives #3. 2017-11-14 17:25:12 -05:00
simon
da7d050661 Universe warp around and maximum size #9 2017-11-13 22:45:19 -05:00
simon
3ee9b4be95 Added SAR Instructions #7. 2017-11-13 21:39:16 -05:00
simon
42421b7710 Added RCR/RCL Instructions #7. 2017-11-13 20:08:07 -05:00
simon
9979986c6a Added SAL Instruction #6. Added ROL/ROR Instructions #7. 2017-11-13 17:51:16 -05:00
simon
c5abe2efc5 Added ROL/ROR Instructions #7 as well as JC/JNC and JO/JNO instructions 2017-11-13 11:28:06 -05:00
Simon Fortier
fdfa568e07 Update README.md 2017-11-12 13:07:39 -05:00
simon
1bf1682cb3 Updated readme 2017-11-12 13:05:39 -05:00
36 changed files with 936 additions and 104 deletions

View File

@@ -1,6 +1,7 @@
package net.simon987.cubotplugin; package net.simon987.cubotplugin;
import net.simon987.server.GameServer; import net.simon987.server.GameServer;
import net.simon987.server.assembly.Memory;
import net.simon987.server.game.ControllableUnit; import net.simon987.server.game.ControllableUnit;
import net.simon987.server.game.Direction; import net.simon987.server.game.Direction;
import net.simon987.server.game.GameObject; import net.simon987.server.game.GameObject;
@@ -29,6 +30,8 @@ public class Cubot extends GameObject implements Updatable, ControllableUnit {
private ArrayList<Integer> keyboardBuffer = new ArrayList<>(); private ArrayList<Integer> keyboardBuffer = new ArrayList<>();
private FloppyDisk floppyDisk;
private User parent; private User parent;
private int energy; private int energy;
@@ -181,4 +184,9 @@ public class Cubot extends GameObject implements Updatable, ControllableUnit {
public int getMaxEnergy() { public int getMaxEnergy() {
return maxEnergy; return maxEnergy;
} }
@Override
public Memory getFloppyData() {
return ((CubotFloppyDrive) getParent().getCpu().getHardware(CubotFloppyDrive.DEFAULT_ADDRESS)).getFloppy().getMemory();
}
} }

View File

@@ -0,0 +1,107 @@
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 CubotFloppyDrive extends CpuHardware {
/**
* Hardware ID (Should be unique)
*/
static final char HWID = 0x000B;
public static final int DEFAULT_ADDRESS = 0x000B;
private static final int POLL = 1;
private static final int READ_SECTOR = 2;
private static final int WRITE_SECTOR = 3;
private Cubot cubot;
private FloppyDisk floppyDisk;
public CubotFloppyDrive(Cubot cubot) {
this.cubot = cubot;
}
@Override
public void handleInterrupt(Status status) {
int a = getCpu().getRegisterSet().getRegister("A").getValue();
if (a == POLL) {
if (floppyDisk != null) {
getCpu().getRegisterSet().getRegister("B").setValue(0);
} else {
getCpu().getRegisterSet().getRegister("B").setValue(1);
}
} else if (a == READ_SECTOR) {
if (floppyDisk == null) {
getCpu().getRegisterSet().getRegister("B").setValue(0);
} else {
if (cubot.spendEnergy(1)) {
getCpu().getRegisterSet().getRegister("B").setValue(1);
int x = getCpu().getRegisterSet().getRegister("X").getValue();
int y = getCpu().getRegisterSet().getRegister("Y").getValue();
floppyDisk.readSector(x, cubot.getParent().getCpu().getMemory(), y);
}
}
} else if (a == WRITE_SECTOR) {
if (floppyDisk == null) {
getCpu().getRegisterSet().getRegister("B").setValue(0);
} else {
if (cubot.spendEnergy(1)) {
getCpu().getRegisterSet().getRegister("B").setValue(1);
int x = getCpu().getRegisterSet().getRegister("X").getValue();
int y = getCpu().getRegisterSet().getRegister("Y").getValue();
floppyDisk.writeSector(x, cubot.getParent().getCpu().getMemory(), y);
}
}
}
}
@Override
public char getId() {
return HWID;
}
@Override
public JSONObject serialise() {
JSONObject json = new JSONObject();
json.put("hwid", (int) HWID);
json.put("cubot", cubot.getObjectId());
if (floppyDisk != null) {
json.put("floppy", floppyDisk.serialise());
}
return json;
}
public static CubotFloppyDrive deserialize(JSONObject hwJSON) {
CubotFloppyDrive drive = new CubotFloppyDrive((Cubot) GameServer.INSTANCE.getGameUniverse().getObject((int) (long) hwJSON.get("cubot")));
if (hwJSON.containsKey("floppy")) {
drive.floppyDisk = FloppyDisk.deserialise((JSONObject) hwJSON.get("floppy"));
} else {
drive.floppyDisk = new FloppyDisk();
}
return drive;
}
public FloppyDisk getFloppy() {
return floppyDisk;
}
}

View File

@@ -55,6 +55,8 @@ public class CubotPlugin extends ServerPlugin implements GameObjectDeserializer,
return CubotHologram.deserialize(hwJson); return CubotHologram.deserialize(hwJson);
case CubotBattery.HWID: case CubotBattery.HWID:
return CubotBattery.deserialize(hwJson); return CubotBattery.deserialize(hwJson);
case CubotFloppyDrive.HWID:
return CubotFloppyDrive.deserialize(hwJson);
} }
return null; return null;

View File

@@ -0,0 +1,109 @@
package net.simon987.cubotplugin;
import net.simon987.server.assembly.Memory;
import net.simon987.server.io.JSONSerialisable;
import org.json.simple.JSONObject;
/**
* Represents a floppy disk that is inside a floppy drive.
* Floppies contains 80 tracks with 18 sectors per track.
* That's 1440 sectors of 512 words. (total 1,474,560 bytes / 737,280 words / 1.44Mb)
*/
public class FloppyDisk implements JSONSerialisable {
/**
* Contents of the disk
*/
private Memory memory;
/**
* Current location of the read/write head.
* Used to calculate seek time
*/
private int rwHeadTrack = 0;
public FloppyDisk() {
this.memory = new Memory(1024 * 1440);
}
/**
* Read 512 words from the specified sector to cpu memory at specified address
*
* @param sector sector to read (0-1440)
* @param cpuMemory Cpu memory to write to
* @param ramAddress address of the data to write in CPU memory
* @return Whether or not the read operation was in the same track as the last r/w
*/
public boolean readSector(int sector, Memory cpuMemory, int ramAddress) {
if (sector <= 1440) {
cpuMemory.write(ramAddress, memory.getBytes(), sector * 1024, 1024);
//Calculate seek time
int deltaTrack = (sector / 80) - rwHeadTrack;
if (deltaTrack != 0) {
rwHeadTrack = (sector / 80);
return false;
} else {
return true;
}
}
return false;
}
/**
* Write 512 words to the specified sector from cpu memory at the specified address
*
* @param sector sector to write (0-1440)
* @param cpuMemory Cpu memory to read from
* @param ramAddress address of the data to read in CPU memory
* @return Whether or not the read operation was in the same track as the last r/w
*/
public boolean writeSector(int sector, Memory cpuMemory, int ramAddress) {
if (sector <= 1440) {
memory.write(sector * 512, cpuMemory.getBytes(), ramAddress * 2, 1024);
//Calculate seek time
int deltaTrack = (sector / 80) - rwHeadTrack;
if (deltaTrack != 0) {
rwHeadTrack = (sector / 80);
return false;
} else {
return true;
}
} else {
return false;
}
}
@Override
public JSONObject serialise() {
JSONObject json = new JSONObject();
json.put("rwHeadTrack", rwHeadTrack);
json.put("memory", memory.serialise());
return json;
}
public static FloppyDisk deserialise(JSONObject json) {
FloppyDisk floppyDisk = new FloppyDisk();
floppyDisk.rwHeadTrack = (int) (long) json.get("rwHeadTrack");
floppyDisk.memory = Memory.deserialize((JSONObject) json.get("memory"));
return floppyDisk;
}
public Memory getMemory() {
return memory;
}
}

View File

@@ -37,6 +37,8 @@ public class CpuInitialisationListener implements GameEventListener {
emoteHw.setCpu(cpu); emoteHw.setCpu(cpu);
CubotBattery batteryHw = new CubotBattery((Cubot) user.getControlledUnit()); CubotBattery batteryHw = new CubotBattery((Cubot) user.getControlledUnit());
batteryHw.setCpu(cpu); batteryHw.setCpu(cpu);
CubotFloppyDrive floppyHw = new CubotFloppyDrive((Cubot) user.getControlledUnit());
floppyHw.setCpu(cpu);
cpu.attachHardware(legHw, CubotLeg.DEFAULT_ADDRESS); cpu.attachHardware(legHw, CubotLeg.DEFAULT_ADDRESS);
cpu.attachHardware(laserHw, CubotLaser.DEFAULT_ADDRESS); cpu.attachHardware(laserHw, CubotLaser.DEFAULT_ADDRESS);
@@ -47,5 +49,6 @@ public class CpuInitialisationListener implements GameEventListener {
cpu.attachHardware(invHw, CubotInventory.DEFAULT_ADDRESS); cpu.attachHardware(invHw, CubotInventory.DEFAULT_ADDRESS);
cpu.attachHardware(emoteHw, CubotHologram.DEFAULT_ADDRESS); cpu.attachHardware(emoteHw, CubotHologram.DEFAULT_ADDRESS);
cpu.attachHardware(batteryHw, CubotBattery.DEFAULT_ADDRESS); cpu.attachHardware(batteryHw, CubotBattery.DEFAULT_ADDRESS);
cpu.attachHardware(floppyHw, CubotFloppyDrive.DEFAULT_ADDRESS);
} }
} }

View File

@@ -1,3 +1,11 @@
# Much-Assembly-Required # [Live demo](https://muchassemblyrequired.com)
Program the 8086-like microprocessor of a robot in a grid-based multiplayer world. The game is web based so no installation is required.
In its current state, players can walk around the game universe and collect Biomass blobs & Iron/copper ore using the online code editor.
https://muchassemblyrequired.com ![screenshot from 2017-11-12 13-01-43](https://user-images.githubusercontent.com/7120851/32701793-e5d07e98-c7a9-11e7-9931-f8db7b287994.png)
Wiki: [GitHub](https://github.com/simon987/Much-Assembly-Required/wiki)
Chat: [Slack](https://join.slack.com/t/muchassemblyrequired/shared_invite/enQtMjY3Mjc1OTUwNjEwLTkyOTIwOTA5OGY4MDVlMGI4NzM5YzlhMWJiMGY1OWE2NjUxODQ1NWQ1YTcxMTA1NGZkYzNjYzMyM2E1ODdmNzg)
_Building instructions coming soon_

View File

@@ -104,11 +104,18 @@ public class GameServer implements Runnable {
gameUniverse.incrementTime(); gameUniverse.incrementTime();
//Process user code //Process user code
for(User user : gameUniverse.getUsers()){ ArrayList<User> users_ = gameUniverse.getUsers();
for (User user : users_) {
if(user.getCpu() != null){ if(user.getCpu() != null){
user.getCpu().reset(); try {
user.getCpu().execute(); user.getCpu().reset();
user.getCpu().execute();
} catch (Exception e) {
LogManager.LOGGER.severe("Error executing " + user.getUsername() + "'s code");
e.printStackTrace();
}
} }
} }

View File

@@ -271,7 +271,7 @@ public class Assembler {
* usage: constant_name EQU <immediate_value> * usage: constant_name EQU <immediate_value>
* A constant treated the same way as a label. * A constant treated the same way as a label.
*/ */
line = line.trim();
String[] tokens = line.split("\\s+"); String[] tokens = line.split("\\s+");

View File

@@ -94,6 +94,10 @@ public class CPU implements JSONSerialisable{
instructionSet.add(new HwiInstruction(this)); instructionSet.add(new HwiInstruction(this));
instructionSet.add(new HwqInstruction(this)); instructionSet.add(new HwqInstruction(this));
instructionSet.add(new XchgInstruction(this)); instructionSet.add(new XchgInstruction(this));
instructionSet.add(new JcInstruction(this));
instructionSet.add(new JncInstruction(this));
instructionSet.add(new JnoInstruction(this));
instructionSet.add(new JoInstruction(this));
status = new Status(); status = new Status();
memory = new Memory(config.getInt("memory_size")); memory = new Memory(config.getInt("memory_size"));
@@ -148,7 +152,7 @@ public class CPU implements JSONSerialisable{
// LogManager.LOGGER.info(instruction.getMnemonic()); // LogManager.LOGGER.info(instruction.getMnemonic());
} }
double elapsed = (System.currentTimeMillis() - startTime); double elapsed = (System.currentTimeMillis() - startTime);
LogManager.LOGGER.fine("----------\n" + counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz"); LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz");
} }
public void executeInstruction(Instruction instruction, int source, int destination) { public void executeInstruction(Instruction instruction, int source, int destination) {

View File

@@ -1,6 +1,7 @@
package net.simon987.server.assembly; package net.simon987.server.assembly;
import net.simon987.server.assembly.instruction.*; import net.simon987.server.assembly.instruction.*;
import net.simon987.server.logging.LogManager;
import java.util.HashMap; import java.util.HashMap;
@@ -31,12 +32,18 @@ public class DefaultInstructionSet implements InstructionSet {
add(new AndInstruction()); add(new AndInstruction());
add(new OrInstruction()); add(new OrInstruction());
add(new ShlInstruction()); add(new ShlInstruction());
add(new SalInstruction()); //Alias is added
add(new ShrInstruction()); add(new ShrInstruction());
add(new XorInstruction()); add(new XorInstruction());
add(new TestInstruction()); add(new TestInstruction());
add(new CmpInstruction()); add(new CmpInstruction());
add(new NegInstruction()); add(new NegInstruction());
add(new NotInstruction()); add(new NotInstruction());
add(new RorInstruction());
add(new RolInstruction());
add(new RclInstruction());
add(new RcrInstruction());
add(new SarInstruction());
} }
/** /**
@@ -83,6 +90,12 @@ public class DefaultInstructionSet implements InstructionSet {
@Override @Override
public void add(Instruction instruction) { public void add(Instruction instruction) {
instructionMap.put(instruction.getOpCode(), instruction); if (instructionMap.containsKey(instruction.getOpCode())) {
LogManager.LOGGER.fine(instruction.getMnemonic() + " instruction is an alias for " +
instructionMap.get(instruction.getOpCode()).getMnemonic());
} else {
instructionMap.put(instruction.getOpCode(), instruction);
}
} }
} }

View File

@@ -55,16 +55,16 @@ public class Memory implements Target, JSONSerialisable {
/** /**
* Write x words from an array at an offset * Write x words from an array at an offset
*/ */
public boolean write(int offset, byte[] bytes, int count) { public boolean write(int offset, byte[] bytes, int srcOffset, int count) {
offset = (char)offset * 2; offset = (char)offset * 2;
if (offset + count > this.bytes.length || count < 0 || offset < 0) { if (offset + count > this.bytes.length || count < 0 || offset < 0 || srcOffset >= bytes.length) {
return false; return false;
} }
System.arraycopy(bytes, 0, this.bytes, offset, count); System.arraycopy(bytes, srcOffset, this.bytes, offset, count);
return true; return true;
} }
@@ -122,18 +122,6 @@ public class Memory implements Target, JSONSerialisable {
e.printStackTrace(); 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; return json;
} }
@@ -157,4 +145,8 @@ public class Memory implements Target, JSONSerialisable {
return memory; return memory;
} }
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
} }

View File

@@ -0,0 +1,36 @@
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 JcInstruction extends Instruction {
private static final int OPCODE = 33;
private CPU cpu;
public JcInstruction(CPU cpu) {
super("jc", OPCODE);
this.cpu = cpu;
}
@Override
public Status execute(Target src, int srcIndex, Status status) {
if (status.isCarryFlag()) {
cpu.setIp((char) src.get(srcIndex));
}
return status;
}
@Override
public Status execute(int src, Status status) {
if (status.isCarryFlag()) {
cpu.setIp((char) src);
}
return status;
}
}

View File

@@ -0,0 +1,35 @@
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 JncInstruction extends Instruction {
private static final int OPCODE = 34;
private CPU cpu;
public JncInstruction(CPU cpu) {
super("jnc", OPCODE);
this.cpu = cpu;
}
@Override
public Status execute(Target src, int srcIndex, Status status) {
if (!status.isCarryFlag()) {
cpu.setIp((char) src.get(srcIndex));
}
return status;
}
@Override
public Status execute(int src, Status status) {
if (!status.isCarryFlag()) {
cpu.setIp((char) src);
}
return status;
}
}

View File

@@ -0,0 +1,35 @@
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 JnoInstruction extends Instruction {
private static final int OPCODE = 37;
private CPU cpu;
public JnoInstruction(CPU cpu) {
super("jno", OPCODE);
this.cpu = cpu;
}
@Override
public Status execute(Target src, int srcIndex, Status status) {
if (!status.isOverflowFlag()) {
cpu.setIp((char) src.get(srcIndex));
}
return status;
}
@Override
public Status execute(int src, Status status) {
if (!status.isOverflowFlag()) {
cpu.setIp((char) src);
}
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;
public class JoInstruction extends Instruction {
private static final int OPCODE = 36;
private CPU cpu;
public JoInstruction(CPU cpu) {
super("jo", OPCODE);
this.cpu = cpu;
}
@Override
public Status execute(Target src, int srcIndex, Status status) {
if (status.isOverflowFlag()) {
cpu.setIp((char) src.get(srcIndex));
}
return status;
}
@Override
public Status execute(int src, Status status) {
if (status.isOverflowFlag()) {
cpu.setIp((char) src);
}
return status;
}
}

View File

@@ -0,0 +1,70 @@
package net.simon987.server.assembly.instruction;
import net.simon987.server.assembly.Instruction;
import net.simon987.server.assembly.Status;
import net.simon987.server.assembly.Target;
/**
* +---------------------+
* | |
* CF < 0<0<0<0<0<0<0<0 <-+
*/
public class RclInstruction extends Instruction {
private static final int OPCODE = 39;
public RclInstruction() {
super("rcl", OPCODE);
}
@Override
public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) {
int count = src.get(srcIndex) % 17;
int destination = dst.get(dstIndex);
int signBit = (destination & 0x8000);
if (status.isCarryFlag()) {
destination |= 0x10000;
}
destination = (destination << count) | (destination >>> (17 - count));
status.setCarryFlag((destination & 0x10000) == 0x10000);
if (count == 1) {
status.setOverflowFlag((destination & 0x8000) != signBit); //Set OF if sign bit changed
}
dst.set(dstIndex, destination & 0xFFFF);
return status;
}
@Override
public Status execute(Target dst, int dstIndex, int src, Status status) {
int count = src % 17;
int destination = dst.get(dstIndex);
int signBit = (destination & 0x8000);
if (status.isCarryFlag()) {
destination |= 0x10000;
}
destination = (destination << count) | (destination >>> (17 - count));
status.setCarryFlag((destination & 0x10000) == 0x10000);
if (count == 1) {
status.setOverflowFlag((destination & 0x8000) != signBit); //Set OF if sign bit changed
}
dst.set(dstIndex, destination & 0xFFFF);
return status;
}
}

View File

@@ -0,0 +1,69 @@
package net.simon987.server.assembly.instruction;
import net.simon987.server.assembly.Instruction;
import net.simon987.server.assembly.Status;
import net.simon987.server.assembly.Target;
/**
* +---------------------+
* | |
* CF < 0<0<0<0<0<0<0<0 <-+
*/
public class RcrInstruction extends Instruction {
private static final int OPCODE = 40;
public RcrInstruction() {
super("rcr", OPCODE);
}
@Override
public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) {
int count = src.get(srcIndex) % 17;
int destination = dst.get(dstIndex) << 1;
int signBit = (destination & 0x10000);
if (status.isCarryFlag()) {
destination |= 1;
}
destination = (destination >>> count) | (destination << (17 - count));
status.setCarryFlag((destination & 1) == 1);
if (count == 1) {
status.setOverflowFlag((destination & 0x10000) != signBit); //Set OF if sign bit changed
}
dst.set(dstIndex, destination >> 1);
return status;
}
@Override
public Status execute(Target dst, int dstIndex, int src, Status status) {
int count = src % 17;
int destination = dst.get(dstIndex) << 1;
int signBit = (destination & 0x10000);
if (status.isCarryFlag()) {
destination |= 1;
}
destination = (destination >>> count) | (destination << (17 - count));
status.setCarryFlag((destination & 1) == 1);
if (count == 1) {
status.setOverflowFlag((destination & 0x10000) != signBit); //Set OF if sign bit changed
}
dst.set(dstIndex, destination >> 1);
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;
/**
* +-----------------+
* | |
* CF < 0<0<0<0<0<0<0<0 <-+
*/
public class RolInstruction extends Instruction {
private static final int OPCODE = 35;
public RolInstruction() {
super("rol", OPCODE);
}
@Override
public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) {
int count = src.get(srcIndex) % 16;
int destination = dst.get(dstIndex);
int signBit = (destination & 0x8000);
destination = (destination << count) | (destination >>> (16 - count));
if (count == 1) {
status.setOverflowFlag((destination & 0x8000) != signBit); //Set OF if sign bit changed
}
status.setCarryFlag((destination & 1) == 1);
dst.set(dstIndex, destination);
return status;
}
@Override
public Status execute(Target dst, int dstIndex, int src, Status status) {
int count = src % 16;
int destination = dst.get(dstIndex);
int signBit = (destination & 0x8000);
destination = (destination << count) | (destination >>> (16 - count));
if (count == 1) {
status.setOverflowFlag((destination & 0x8000) != signBit); //Set OF if sign bit changed
}
status.setCarryFlag((destination & 1) == 1);
dst.set(dstIndex, destination);
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;
/**
* +-----------------+
* | |
* +-> 0>0>0>0>0>0>0>0 > CF
*
*/
public class RorInstruction extends Instruction {
private static final int OPCODE = 32;
public RorInstruction() {
super("ror", OPCODE);
}
@Override
public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) {
int count = src.get(srcIndex) % 16;
int destination = dst.get(dstIndex);
int signBit = (destination & 0x8000);
destination = (destination >>> count) | (destination << (16 - count));
if (count == 1) {
status.setOverflowFlag((destination & 0x8000) != signBit); //Set OF if sign bit changed
}
dst.set(dstIndex, destination);
status.setCarryFlag((destination & 0x8000) == 0x8000);
return status;
}
@Override
public Status execute(Target dst, int dstIndex, int src, Status status) {
int count = src % 16;
int destination = dst.get(dstIndex);
int signBit = (destination & 0x8000);
destination = (destination >>> count) | (destination << (16 - count));
if (count == 1) {
status.setOverflowFlag((destination & 0x8000) != signBit); //Set OF if sign bit changed
}
dst.set(dstIndex, destination);
status.setCarryFlag((destination & 0x8000) == 0x8000);
return status;
}
}

View File

@@ -0,0 +1,13 @@
package net.simon987.server.assembly.instruction;
import net.simon987.server.assembly.Instruction;
/**
* Alias of SHL instruction
*/
public class SalInstruction extends Instruction {
public SalInstruction() {
super("sal", ShlInstruction.OPCODE);
}
}

View File

@@ -0,0 +1,67 @@
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 SarInstruction extends Instruction {
private static final int OPCODE = 41;
public SarInstruction() {
super("sar", OPCODE);
}
@Override
public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) {
int count = src.get(srcIndex) % 16;
int destination = dst.get(dstIndex);
if (count == 1) {
status.setOverflowFlag(false); //sign doesn't change
}
if ((destination & 0x8000) == 0x8000) {
destination |= 0xFFFF0000;
}
destination = destination >> (count - 1);
status.setCarryFlag((destination & 1) == 1);
destination = destination >> 1;
status.setZeroFlag(destination == 0);
dst.set(dstIndex, destination);
return status;
}
@Override
public Status execute(Target dst, int dstIndex, int src, Status status) {
int count = src % 16;
int destination = dst.get(dstIndex);
if (count == 1) {
status.setOverflowFlag(false); //sign doesn't change
}
if ((destination & 0x8000) == 0x8000) {
destination |= 0xFFFF0000;
}
destination = destination >> (count - 1);
status.setCarryFlag((destination & 1) == 1);
destination = destination >> 1;
status.setZeroFlag(destination == 0);
dst.set(dstIndex, destination);
return status;
}
}

View File

@@ -5,15 +5,13 @@ import net.simon987.server.assembly.Status;
import net.simon987.server.assembly.Target; import net.simon987.server.assembly.Target;
import net.simon987.server.assembly.Util; import net.simon987.server.assembly.Util;
/**
* Created by Gilbert Fortier on 3/12/2017.
*/
public class XorInstruction extends Instruction { public class XorInstruction extends Instruction {
/** /**
* Opcode of the instruction * Opcode of the instruction
*/ */
public static final int OPCODE = 5; public static final int OPCODE = 38;
public XorInstruction() { public XorInstruction() {
super("xor", OPCODE); super("xor", OPCODE);

View File

@@ -1,5 +1,6 @@
package net.simon987.server.game; package net.simon987.server.game;
import net.simon987.server.assembly.Memory;
import net.simon987.server.user.User; import net.simon987.server.user.User;
import java.util.ArrayList; import java.util.ArrayList;
@@ -14,4 +15,6 @@ public interface ControllableUnit {
ArrayList<Integer> getKeyboardBuffer(); ArrayList<Integer> getKeyboardBuffer();
Memory getFloppyData();
} }

View File

@@ -71,7 +71,14 @@ public abstract class GameObject implements JSONSerialisable {
//Check if out of World bounds / collision //Check if out of World bounds / collision
if(newX < 0) { if(newX < 0) {
//Move object to adjacent World (left) //Move object to adjacent World (left)
World leftWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX() - 1, world.getY()); World leftWorld;
if (world.getX() == 0) {
//Warp around
leftWorld = GameServer.INSTANCE.getGameUniverse().getWorld(
GameServer.INSTANCE.getGameUniverse().getMaxWidth(), world.getY());
} else {
leftWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX() - 1, world.getY());
}
if(leftWorld != null){ if(leftWorld != null){
world.getGameObjects().remove(this); world.getGameObjects().remove(this);
@@ -82,7 +89,13 @@ public abstract class GameObject implements JSONSerialisable {
} }
} else if(newX >= World.WORLD_SIZE) { } else if(newX >= World.WORLD_SIZE) {
//Move object to adjacent World (right) //Move object to adjacent World (right)
World rightWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX() + 1, world.getY()); World rightWorld;
if (world.getX() == GameServer.INSTANCE.getGameUniverse().getMaxWidth()) {
//Warp around
rightWorld = GameServer.INSTANCE.getGameUniverse().getWorld(0, world.getY());
} else {
rightWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX() + 1, world.getY());
}
if(rightWorld != null){ if(rightWorld != null){
world.getGameObjects().remove(this); world.getGameObjects().remove(this);
@@ -92,25 +105,39 @@ public abstract class GameObject implements JSONSerialisable {
x = 0; x = 0;
} }
} else if (newY < 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) //Move object to adjacent World (up)
World upWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX(), world.getY() + 1); World upWorld;
if (world.getY() == 0) {
//Warp around
upWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX(),
GameServer.INSTANCE.getGameUniverse().getMaxWidth());
} else {
upWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX(), world.getY() - 1);
}
if(upWorld != null){ if(upWorld != null){
world.getGameObjects().remove(this); world.getGameObjects().remove(this);
upWorld.getGameObjects().add(this); upWorld.getGameObjects().add(this);
setWorld(upWorld); setWorld(upWorld);
y = World.WORLD_SIZE - 1;
}
} else if (newY >= World.WORLD_SIZE) {
//Move object to adjacent World (down)
World downWorld;
if (world.getY() == GameServer.INSTANCE.getGameUniverse().getMaxWidth()) {
//Warp around
downWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX(), 0);
} else {
downWorld = GameServer.INSTANCE.getGameUniverse().getWorld(world.getX(), world.getY() + 1);
}
if (downWorld != null) {
world.getGameObjects().remove(this);
downWorld.getGameObjects().add(this);
setWorld(downWorld);
y = 0; y = 0;
} }
} }

View File

@@ -29,6 +29,8 @@ public class GameUniverse implements JSONSerialisable{
private int nextObjectId = 0; private int nextObjectId = 0;
private int maxWidth = 0xFFFF;
public GameUniverse(ServerConfiguration config) { public GameUniverse(ServerConfiguration config) {
worlds = new ArrayList<>(32); worlds = new ArrayList<>(32);
@@ -50,15 +52,18 @@ public class GameUniverse implements JSONSerialisable{
} }
} }
//World does not exist if (x >= 0 && x <= maxWidth && y >= 0 && y <= maxWidth) {
LogManager.LOGGER.severe("Trying to read a World that does not exist!"); //World does not exist
LogManager.LOGGER.severe("Trying to read a World that does not exist!");
World world = createWorld(x,y); World world = createWorld(x, y);
worlds.add(world); worlds.add(world);
return world;
return world;
} else {
return null;
}
} }
public World createWorld(int x, int y) { public World createWorld(int x, int y) {
@@ -108,9 +113,11 @@ public class GameUniverse implements JSONSerialisable{
user.getCpu().getMemory().clear(); user.getCpu().getMemory().clear();
//Write assembled code to mem //Write assembled code to mem
user.getCpu().getMemory().write((short) ar.origin, ar.bytes, ar.bytes.length); user.getCpu().getMemory().write((short) ar.origin, ar.bytes, 0, ar.bytes.length);
user.getCpu().setCodeSegmentOffset(ar.origin); user.getCpu().setCodeSegmentOffset(ar.origin);
//Init
} else { } else {
user = new User(null); user = new User(null);
} }
@@ -129,8 +136,17 @@ public class GameUniverse implements JSONSerialisable{
} }
} }
/**
* Get an object by id
* <br>
* ConcurrentModificationException risk when inside game loop
*
* @param id id of the game object
* @return GameObject, null if not found
*/
public GameObject getObject(int id) { public GameObject getObject(int id) {
//
for (World world : worlds) { for (World world : worlds) {
for(GameObject object : world.getGameObjects()){ for(GameObject object : world.getGameObjects()){
if(object.getObjectId() == id){ if(object.getObjectId() == id){
@@ -161,12 +177,14 @@ public class GameUniverse implements JSONSerialisable{
JSONArray worlds = new JSONArray(); JSONArray worlds = new JSONArray();
for(World world : this.worlds){ ArrayList<World> worlds_ = new ArrayList<>(this.worlds);
for (World world : worlds_){
worlds.add(world.serialise()); worlds.add(world.serialise());
} }
JSONArray users = new JSONArray(); JSONArray users = new JSONArray();
for(User user : this.users){ ArrayList<User> users_ = new ArrayList<User>(this.users);
for (User user : users_){
if (!user.isGuest()) { if (!user.isGuest()) {
users.add(user.serialise()); users.add(user.serialise());
} }
@@ -245,4 +263,8 @@ public class GameUniverse implements JSONSerialisable{
users.remove(user); users.remove(user);
} }
public int getMaxWidth() {
return maxWidth;
}
} }

View File

@@ -83,7 +83,8 @@ public class World implements JSONSerialisable{
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
JSONArray objects = new JSONArray(); JSONArray objects = new JSONArray();
for(GameObject obj : gameObjects){ ArrayList<GameObject> gameObjects_ = new ArrayList<>(gameObjects);
for (GameObject obj : gameObjects_) {
objects.add(obj.serialise()); objects.add(obj.serialise());
} }
json.put("objects", objects); json.put("objects", objects);

View File

@@ -3,7 +3,6 @@ package net.simon987.server.logging;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.logging.Formatter; import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
/** /**
@@ -16,24 +15,13 @@ public class GenericFormatter extends Formatter {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (record.getLevel() == Level.FINE) { //Regular record
//Chat message, maximum 50 char per line Date date = new Date();
if (record.getMessage().length() > 50) { SimpleDateFormat sdf = new SimpleDateFormat("MM/dd HH:mm:ss:SSS"); //ex. 11/25 22:03:59:010
sb.append(record.getMessage().substring(0, 50));
sb.append('\n'); sb.append(String.format("[%s] [%s] %s", sdf.format(date), record.getLevel(), record.getMessage()));
sb.append(record.getMessage().substring(50)); sb.append('\n');
} else {
sb.append(record.getMessage());
}
sb.append('\n');
} else {
//Regular record
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd HH:mm:ss:SSS"); //ex. 11/25 22:03:59:010
sb.append(String.format("[%s] [%s] %s", sdf.format(date), record.getLevel(), record.getMessage()));
sb.append('\n');
}
return sb.toString(); return sb.toString();

View File

@@ -28,7 +28,7 @@ public class CodeUploadHandler implements MessageHandler {
user.getUser().getCpu().getMemory().clear(); user.getUser().getCpu().getMemory().clear();
//Write assembled code to mem //Write assembled code to mem
user.getUser().getCpu().getMemory().write((char) ar.origin, ar.bytes, ar.bytes.length); user.getUser().getCpu().getMemory().write((char) ar.origin, ar.bytes, 0, ar.bytes.length);
user.getUser().getCpu().setCodeSegmentOffset(ar.origin); user.getUser().getCpu().setCodeSegmentOffset(ar.origin);
JSONObject response = new JSONObject(); JSONObject response = new JSONObject();

View File

@@ -0,0 +1,36 @@
package net.simon987.server.webserver;
import net.simon987.server.GameServer;
import net.simon987.server.logging.LogManager;
import org.json.simple.JSONObject;
public class FloppyHandler implements MessageHandler {
SocketServerDatabase db = new SocketServerDatabase(GameServer.INSTANCE.getConfig());
@Override
public void handle(OnlineUser user, JSONObject json) {
if (json.get("t").equals("floppyDown")) {
LogManager.LOGGER.info("(WS) Floppy download request from " + user.getUser().getUsername());
//floppy
byte[] bytes = user.getUser().getControlledUnit().getFloppyData().getBytes();
user.getWebSocket().send(bytes);
} else if (json.get("t").equals("floppyUp")) {
LogManager.LOGGER.info("(WS) Floppy upload request from " + user.getUser().getUsername());
//Check newly uploaded file on the database
byte[] bytes = db.getFloppy(user.getUser().getUsername());
if (bytes != null) {
user.getUser().getControlledUnit().getFloppyData().setBytes(bytes);
}
}
}
}

View File

@@ -2,7 +2,7 @@ package net.simon987.server.webserver;
import net.simon987.server.GameServer; import net.simon987.server.GameServer;
import net.simon987.server.game.GameObject; import net.simon987.server.game.GameObject;
import net.simon987.server.io.JSONSerialisable; import net.simon987.server.game.World;
import net.simon987.server.logging.LogManager; import net.simon987.server.logging.LogManager;
import org.json.simple.JSONArray; import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
@@ -18,25 +18,28 @@ public class ObjectsRequestHandler implements MessageHandler {
if (json.get("t").equals("object")) { if (json.get("t").equals("object")) {
LogManager.LOGGER.info("(WS) Objects request from " + user.getUser().getUsername()); LogManager.LOGGER.info("(WS) Objects request from " + user.getUser().getUsername());
int x, y;
try {
x = Long.valueOf((long) json.get("x")).intValue();
y = Long.valueOf((long) json.get("y")).intValue();
} catch (Exception e) {
LogManager.LOGGER.info("(WS) Malformed Objects request from " + user.getUser().getUsername());
return;
}
if (json.containsKey("x") && json.containsKey("y")) { World world = GameServer.INSTANCE.getGameUniverse().getWorld(x, y);
int x = Long.valueOf((long) json.get("x")).intValue();
int y = Long.valueOf((long) json.get("y")).intValue();
ArrayList<GameObject> gameObjects = GameServer.INSTANCE.getGameUniverse().getWorld(x, y).getGameObjects(); if (world != null) {
ArrayList<GameObject> gameObjects = world.getGameObjects();
JSONObject response = new JSONObject(); JSONObject response = new JSONObject();
JSONArray objects = new JSONArray(); JSONArray objects = new JSONArray();
for (GameObject object : gameObjects) { for (GameObject object : gameObjects) {
if (object instanceof JSONSerialisable) {
objects.add(object.serialise()); objects.add(object.serialise());
} }
}
response.put("t", "object"); response.put("t", "object");
response.put("objects", objects); response.put("objects", objects);
@@ -44,8 +47,6 @@ public class ObjectsRequestHandler implements MessageHandler {
if (user.getWebSocket().isOpen()) { if (user.getWebSocket().isOpen()) {
user.getWebSocket().send(response.toJSONString()); user.getWebSocket().send(response.toJSONString());
} }
} else {
LogManager.LOGGER.info("(WS) Malformed Objects request from " + user.getUser().getUsername());
} }
} }
} }

View File

@@ -68,6 +68,7 @@ public class SocketServer extends WebSocketServer {
messageEventDispatcher.addHandler(new CodeUploadHandler()); messageEventDispatcher.addHandler(new CodeUploadHandler());
messageEventDispatcher.addHandler(new CodeRequestHandler()); messageEventDispatcher.addHandler(new CodeRequestHandler());
messageEventDispatcher.addHandler(new KeypressHandler()); messageEventDispatcher.addHandler(new KeypressHandler());
messageEventDispatcher.addHandler(new FloppyHandler());
} }

View File

@@ -4,10 +4,7 @@ import net.simon987.server.ServerConfiguration;
import net.simon987.server.io.DatabaseManager; import net.simon987.server.io.DatabaseManager;
import net.simon987.server.logging.LogManager; import net.simon987.server.logging.LogManager;
import java.sql.Connection; import java.sql.*;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
class SocketServerDatabase extends DatabaseManager { class SocketServerDatabase extends DatabaseManager {
@@ -48,4 +45,39 @@ class SocketServerDatabase extends DatabaseManager {
return null; return null;
} }
byte[] getFloppy(String username) {
Connection connection = null;
try {
connection = getConnection();
PreparedStatement p = connection.prepareStatement("SELECT floppyData FROM mar_user WHERE username=?");
p.setString(1, username);
ResultSet rs = p.executeQuery();
if (rs.next()) {
Blob blob = rs.getBlob("floppyData");
if (blob != null) {
return blob.getBytes(1, (int) blob.length() - 1);
}
}
} catch (SQLException e) {
LogManager.LOGGER.severe("MySQL Error " + e.getErrorCode() + ": " + e.getMessage());
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
} }

View File

@@ -10,13 +10,19 @@ public class TerrainRequestHandler implements MessageHandler {
@Override @Override
public void handle(OnlineUser user, JSONObject json) { public void handle(OnlineUser user, JSONObject json) {
if (json.get("t").equals("terrain")) { if (json.get("t").equals("terrain") && json.containsKey("x") && json.containsKey("y")) {
LogManager.LOGGER.info("Terrain request from " + user.getUser().getUsername()); LogManager.LOGGER.info("Terrain request from " + user.getUser().getUsername());
World world;
try {
world = GameServer.INSTANCE.getGameUniverse().getWorld(
Long.valueOf((long) json.get("x")).intValue(),
Long.valueOf((long) json.get("y")).intValue());
} catch (NullPointerException e) {
LogManager.LOGGER.severe("FIXME: handle TerrainRequestHandler");
return;
}
World world = GameServer.INSTANCE.getGameUniverse().getWorld(
Long.valueOf((long) json.get("x")).intValue(),
Long.valueOf((long) json.get("y")).intValue());
//todo It might be a good idea to cache this //todo It might be a good idea to cache this
if (world != null) { if (world != null) {

View File

@@ -14,26 +14,22 @@ public class UserInfoRequestHandler implements MessageHandler {
if (message.get("t").equals("userInfo")) { if (message.get("t").equals("userInfo")) {
LogManager.LOGGER.info("(WS) User info request from " + user.getUser().getUsername()); LogManager.LOGGER.info("(WS) User info request from " + user.getUser().getUsername());
JSONObject json = new JSONObject();
if(user.isGuest()) { if(user.isGuest()) {
JSONObject json = new JSONObject();
json.put("t", "userInfo");
json.put("worldX", GameServer.INSTANCE.getConfig().getInt("new_user_worldX")); json.put("worldX", GameServer.INSTANCE.getConfig().getInt("new_user_worldX"));
json.put("worldY", GameServer.INSTANCE.getConfig().getInt("new_user_worldY")); json.put("worldY", GameServer.INSTANCE.getConfig().getInt("new_user_worldY"));
user.getWebSocket().send(json.toJSONString());
} else { } else {
GameObject object = (GameObject)user.getUser().getControlledUnit(); GameObject object = (GameObject)user.getUser().getControlledUnit();
JSONObject json = new JSONObject();
json.put("t", "userInfo");
json.put("worldX", object.getWorld().getX()); json.put("worldX", object.getWorld().getX());
json.put("worldY", object.getWorld().getY()); json.put("worldY", object.getWorld().getY());
user.getWebSocket().send(json.toJSONString());
} }
json.put("t", "userInfo");
json.put("maxWidth", GameServer.INSTANCE.getGameUniverse().getMaxWidth());
user.getWebSocket().send(json.toJSONString());
} }

View File

@@ -36,14 +36,14 @@ public class MemoryTest {
Memory memory = new Memory(memorySize); Memory memory = new Memory(memorySize);
assertTrue(memory.write(0, new byte[memorySize], memorySize)); assertTrue(memory.write(0, new byte[memorySize], 0, memorySize));
assertFalse(memory.write(0, new byte[memorySize], memorySize + 1)); assertFalse(memory.write(0, new byte[memorySize], 0, memorySize + 1));
assertFalse(memory.write(0, new byte[memorySize], -1)); assertFalse(memory.write(0, new byte[memorySize], 0, -1));
assertFalse(memory.write(-1, new byte[memorySize], 10)); assertFalse(memory.write(-1, new byte[memorySize], 0, 10));
assertFalse(memory.write(memorySize / 2, new byte[15], 1)); assertFalse(memory.write(memorySize / 2, new byte[15], 0, 1));
assertFalse(memory.write((memorySize / 2) - 5, new byte[11], 11)); assertFalse(memory.write((memorySize / 2) - 5, new byte[11], 0, 11));
assertTrue(memory.write((memorySize / 2) - 5, new byte[11], 10)); assertTrue(memory.write((memorySize / 2) - 5, new byte[11], 0, 10));
} }

Binary file not shown.