asmcup
is a game where players create small and limited programs
to power robots in a virtual environment to compete for prizes.
It is currently in active alpha development.
The quickest way to get started is to download asmcup.jar and run it. This will launch the Sandbox which allows you to write, compile, and debug your robot. You need to have Java installed to run the Jar file.
You can find sample bots to try out over here.
asmcup.jar also has command line tools:
asmcup.compiler.Main
compiles assembly source into binariesasmcup.decompiler.Main
decompiles binary files into sourceasmcup.runtime.Main
simulates a game world via the command line
Made a robot you think can hold it's own on our servers?
Note we aren't ready for uploading until come November
Robots are powered by a simple virtual machine with a RISC instruction set for managing memory via a stack. The instruction language supports native operations on 8-bit integers and 32-bit floats.
All programs are 256 bytes in size and are mapped into an 8-bit address space. While there are instructions to operate 32-bit float data the address bus itself is 8-bits.
The stack is located in memory at 0xFF
and grows downwards as
you push data onto it. Since the memory size is limited to 256 bytes
this means your stack can overflow into your memory if not careful.
Note that there are no registers, although you can of course reserve
a part of the stack for this purpose.
Robots are powered via their internal battery used to power each instruction. If the battery reaches zero they will die. Robots can control their CPU clock speed from 1 hz (1 instruction per second) to 1 khz (1024 instructions per second). The internal battery currently holds enough charge for executing 86400 instructions.
Every instruction consists of an opcode (2 least significant bits) and a data part (6 remaining bits). It may be followed by a data block to hold e.g. a float constant.
There are four basic types of operations:
FUNC
executes functions on the stackPUSH
loads data onto the stackPOP
saves data from the stackBRANCH
jumps if the top 8-bits of the stack are non-zero
Literal values are denoted by a #
in front of them. Float values can only be literals,
so the #
can be/must be (not yet decided) omitted for float literals.
Any values that are not literals are taken to be memory addresses.
Values preceded by a $
are interpreted as hexadecimal, otherwise they
will be treated as decimals.
For example, pushf #1.0
and pushf 1.0
will push the float value 1.0 to the stack.
pushf 1
would however push the content of the float (4 bytes of memory starting at)
address 1 to the stack, as would pushf $01
. Another legal example would be
push8 #$ff
, which pushes the literal value 255 to the stack.
There are 63 total functions. Note that each of these instructions are the same size (1 byte) but that they can modify the stack differently. The In column is the number of bytes popped from the stack. The Out column is the number of bytes pushed to the stack.
Value | Command | In | Out | Notes |
---|---|---|---|---|
0 | nop | 0 | 0 | No Operation |
1 | b2f | 1 | 4 | Byte to Float |
2 | f2b | 4 | 1 | Float to Byte |
3 | not8 | 1 | 1 | Byte NOT |
4 | or8 | 2 | 1 | Byte OR |
5 | and8 | 2 | 1 | Byte AND |
6 | xor8 | 2 | 1 | Byte XOR |
7 | shl8 | 1 | 1 | Byte Shift Left |
8 | shr8 | 1 | 1 | Byte Shift Right |
9 | add8 | 2 | 1 | Byte Add |
10 | sub8 | 2 | 1 | Byte Subtract |
11 | div8 | 2 | 1 | Byte Divide |
12 | mul8 | 2 | 1 | Byte Multiply |
13 | madd8 | 3 | 1 | Byte Multiply with Add |
14 | negf | 4 | 4 | Float Negate |
15 | addf | 8 | 4 | Float Add |
16 | subf | 8 | 4 | Float Subtract |
17 | divf | 8 | 4 | Float Divide |
18 | mulf | 8 | 4 | Float Multiply |
19 | maddf | 12 | 4 | Float Multiply with Add |
20 | cosf | 4 | 4 | Float Cosine |
21 | sinf | 4 | 4 | Float Sine |
22 | tanf | 4 | 4 | Float Tangent |
23 | acosf | 4 | 4 | Float Arc Cosine |
24 | asinf | 4 | 4 | Float Arc Sine |
25 | atanf | 4 | 4 | Float Arc Tangent |
26 | absf | 4 | 4 | Float Absolute Value |
27 | minf | 8 | 4 | Float Minimum Value |
28 | maxf | 8 | 4 | Float Maximum Value |
29 | powf | 8 | 4 | Float Raise Power |
30 | logf | 4 | 4 | Float Natural Logarithm |
31 | log10f | 4 | 4 | Float Logorithm Base 10 |
32 | if_eq8 | 2 | 1 | Byte Equal |
33 | if_ne8 | 2 | 1 | Byte Not Equal |
34 | if_lt8 | 2 | 1 | Byte Less Than |
35 | if_lte8 | 2 | 1 | Byte Less Than or Equal |
36 | if_gt8 | 2 | 1 | Byte Greater Than |
37 | if_gte8 | 2 | 1 | Byte Greater Than or Equal |
38 | if_ltf | 8 | 1 | Float Less Than |
39 | if_ltef | 8 | 1 | Float Less Than or Equal |
40 | if_gtf | 8 | 1 | Float Greater Than |
41 | if_gtef | 8 | 1 | Float Greater Than or Equal |
42 | c_0 | 0 | 1 | Push Byte 0x00 |
43 | c_1 | 0 | 1 | Push Byte 0x01 |
44 | c_2 | 0 | 1 | Push Byte 0x02 |
45 | c_3 | 0 | 1 | Push Byte 0x03 |
46 | c_4 | 0 | 1 | Push Byte 0x04 |
47 | c_255 | 0 | 1 | Push Byte 0xFF |
48 | c_0f | 0 | 4 | Push Float 0.0f |
49 | c_1f | 0 | 4 | Push Float 1.0f |
50 | c_2f | 0 | 4 | Push Float 2.0f |
51 | c_3f | 0 | 4 | Push Float 3.0f |
52 | c_4f | 0 | 4 | Push Float 4.0f |
53 | c_inf | 0 | 4 | Push Float Infinity |
54 | if_nan | 4 | 1 | Float NaN Check |
55 | dup8 | 1 | 2 | Byte Duplicate |
56 | dupf | 4 | 8 | Float Duplicate |
57 | Unused 1 | |||
58 | Unused 2 | |||
59 | Unused 3 | |||
60 | Unused 4 | |||
61 | Unused 5 | |||
62 | Unused 6 | |||
63 | io | ? | ? | Input / Output (IO) |
Here are the basic ways to push data onto the stack:
Statement | Size | Description |
---|---|---|
pushf 0.1 | 5 | Push immediate float to stack |
push8 #42 | 2 | Push immediate byte to stack |
push8 $f0 | 2 | Push value at memory address 0xf0 to stack |
push8r $f0 | 1 | Push value at memory address 0xf0. Only legal if executed within 31 bytes of the target address |
push8r
stores the relative location of the target address in its data bytes,
thereby saving ROM space but being restricted to nearby addresses.
Note that the compiler will transform pushes of common constants into function calls
as appropriate. For example, pushf 0.0
would be translated into function call c_0f
instead of the push, thereby only using 1 byte of memory instead of 5.
Popping data from the stack can be done using multiple variants:
Statement | Size | Description |
---|---|---|
popf $f0 | 2 | Pop float from stack to memory address 0xf0-0xf3 |
pop8 $f0 | 2 | Pop byte from stack to memory address 0xf0 |
pop8r $f0 | 1 | Pop byte from stack to memory address 0xf0. Only legal if executed within 31 bytes of the target address |
pop8r
stores the relative location of the target address in its data bytes,
thereby saving ROM space but being restricted to nearby addresses.
Statement | Size | Description |
---|---|---|
jmp $f0 | 2 | Jump Always |
jnz $f0 | 2 | Jump Not Zero |
jnz [$f0] | 2 | Jump Not Zero Indirect |
jnzr $f0 | 1 | Jump Not Zero Relative |
jnzr
stores the relative location of the target address in its data bytes,
thereby saving ROM space but being restricted to nearby addresses.
The jne
and jner
commands are equivalent to jnz
and jnzr
, respectively.
Line contents after a semicolon (;) are ignored by the compiler.
You may define labels (e.g. start:
) and refer to them using their name (e.g.
jmp start
), which will be replaced by the memory location in the compiled code
that they were defined at. A statement (including more labels) may follow on
the same line.
The compiler also accepts statements of these forms:
Statement | Size | Description |
---|---|---|
db8 #$f0 | 1 | Data byte (replaced by 0xf0 in ROM) |
db #$f0 | 1 | Same as db8 |
dbf 0.1 | 4 | Data bytes from float |
A common idiom is using myVar: db8 #0
to create named variables.
This allows using statements like push8 myVar
.
While the VM itself allows you to construct arbitrary programs the IO
controls the robot itself. The io
command takes the top value from the stack
and executes a command:
Value | Constant | Function |
---|---|---|
0 | IO_SENSOR | Beam Sensor |
1 | IO_MOTOR | Control Motor |
2 | IO_STEER | Control Steering |
3 | IO_OVERCLOCK | CPU Clock Control |
4 | IO_LASER | Laser Attack (not yet implemented) |
5 | IO_BATTERY | Read Battery |
6 | IO_MARK | Mark ("pee") (not yet implemented) |
7 | IO_MARK_READ | Mark Read ("smell") (not yet implemented) |
8 | IO_ACCELEROMETER | Accelerometer |
9 | IO_RADIO | (Planned) Set radio strength |
10 | IO_SEND | (Planned) Emit data via radio |
10 | IO_RECV | (Planned) Receive data via radio |
Casts a beam at the current looking direction.
push8 #IO_SENSOR
io
popf distance
After io
the stack will contain a float of how far the beam
traveled until it hit an obstacle. If no obstacle was hit the value on the
stack will be 256.0
.
A planned change to the beam sensor will allow it to return whether an and
which item was hit.
; Maximum Speed
pushf 1.0
push8 #IO_MOTOR
io
; Unpower motor
pushf 0.0
push8 #IO_MOTOR
io
; Reverse
pushf -1.0
push8 #IO_MOTOR
io
; Steer right
pushf 1.0
push8 #IO_STEER
io
pushf -1.0
push8 #IO_STEER
io
The CPU speed of the robot can be "overclocked" by pushing two
bytes to the stack and calling io
:
push8 #100
push8 #IO_OVERCLOCK
io
The maximum CPU speed is 100, setting any number higher is the same as setting to 100. The game operates at 10 frames per second meaning a fully overclocked CPU will execute 1000 instructions per second, 100 per frame.
Not yet implemented
Will deal battery damage to other bots.
push8 #IO_ACCELEROMETER
io
popf relY
popf relX
The world consists of tiles of size 32x32. Aside from normal ground tiles, there are tiles with items, hazards, obstacles and walls/rooms.
It is planned that multiple robots will be able to compete in the same world, including the ability to fight each other.
There are currently 4 levels of hazards:
- Mud pit (low battery damage)
- Water pit (medium battery damage)
- Fire pit (severe battery damage)
- Deep pit (instant death)
There are currently no functional differences between the 4 types of obstacles. Only their frequency and distribution vary.
Walls form rectangular rooms with one or more entrances. Gold can (currently) only be found in rooms
Item pickup is WIP.
Gold: Points!
Battery: Recharges the internal battery. Vital to keep a bot running.