├── .gitignore
├── .project
├── .travis.yml
├── LICENSE.md
├── README.md
├── SPEC.md
├── build.gradle
├── src
├── about.txt
├── asmcup
│ ├── compiler
│ │ ├── Compiler.java
│ │ ├── Main.java
│ │ ├── RobotConstsTable.java
│ │ └── VMFuncTable.java
│ ├── decompiler
│ │ ├── Decompiler.java
│ │ └── Main.java
│ ├── evaluation
│ │ ├── Evaluator.java
│ │ ├── EvaluatorFrontPanel.java
│ │ ├── EvaluatorWindow.java
│ │ ├── SpawnEvaluator.java
│ │ ├── Spawns.java
│ │ └── SpawnsWindow.java
│ ├── genetics
│ │ ├── GAFrontPanel.java
│ │ ├── Gene.java
│ │ ├── GeneticAlgorithm.java
│ │ ├── Genetics.java
│ │ ├── GeneticsMenu.java
│ │ └── Spawn.java
│ ├── runtime
│ │ ├── Cell.java
│ │ ├── Generator.java
│ │ ├── Item.java
│ │ ├── Main.java
│ │ ├── PlaybackRobot.java
│ │ ├── PlaybackVM.java
│ │ ├── RecordedRobot.java
│ │ ├── RecordedVM.java
│ │ ├── Recorder.java
│ │ ├── Robot.java
│ │ ├── TILE.java
│ │ └── World.java
│ ├── sandbox
│ │ ├── Canvas.java
│ │ ├── CanvasMenu.java
│ │ ├── CodeEditor.java
│ │ ├── Debugger.java
│ │ ├── DefaultContextMenu.java
│ │ ├── FrontPanel.java
│ │ ├── LoadWorldDialog.java
│ │ ├── Main.java
│ │ ├── Menu.java
│ │ ├── Mouse.java
│ │ ├── Sandbox.java
│ │ └── Utils.java
│ └── vm
│ │ ├── VM.java
│ │ ├── VMConsts.java
│ │ └── VMFuncs.java
├── battery.png
├── debugger.png
├── dna.png
├── floor.png
├── gauge.png
├── gold.png
├── ground.png
├── hazards.png
├── notepad.png
├── obstacles.png
├── plus.png
├── robot.png
├── wall.png
└── world.png
└── test
└── asmcup
├── compiler
└── CompilerTest.java
├── decompiler
└── DecompilerTest.java
├── runtime
├── ItemTest.java
├── RobotTest.java
└── TileTest.java
└── vm
└── VMTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/*
2 | /bin/
3 | .gradle/
4 | gradle/
5 | build/
6 | .idea/
7 | gradlew
8 | gradlew.bat
9 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | asmcup
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 |
5 | script:
6 | - gradle test
7 | - gradle findbugsMain findbugsTest
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright © `2016` `Assembly Cup Developers`
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the “Software”), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/asmcup/runtime)
2 | [](https://gitter.im/asmcup/Lobby)
3 |
4 |
7 |
8 | # asmcup
9 |
10 | `asmcup` is a game where players create small and limited programs
11 | to power robots in a virtual environment to compete for prizes.
12 |
13 | It is currently in active beta development.
14 |
15 | ## Screenshot
16 |
17 | 
18 |
19 | ## Getting Started
20 |
21 |
22 | The quickest way to get started is to download
23 | [asmcup.jar](https://github.com/asmcup/runtime/releases)
24 | and run it. This will launch the Sandbox which allows you to write, compile,
25 | and debug your robot. **You need to have Java 8 installed to run the Jar file.**
26 |
27 | You can find sample bots to try out over [here](https://github.com/asmcup/bots),
28 | and there's a [first steps](https://github.com/asmcup/runtime/wiki/Programming) guide.
29 |
30 | asmcup.jar also has command line tools:
31 |
32 | * `asmcup.compiler.Main` compiles assembly source into binaries
33 | * `asmcup.decompiler.Main` decompiles binary files into source
34 | * `asmcup.runtime.Main` simulates a game world via the command line
35 |
36 | If you want to improve the Sandbox or make changes to the game code itself
37 | you can either import the project into Eclipse or build using `gradle jar`.
38 |
39 | ## Compete
40 |
41 | Made a robot you think can hold its own on our servers?
42 |
43 | *Note we aren't ready for uploading until come November*
44 |
45 | * [Upload your robot](https://asmcup.github.io)
46 |
47 |
48 | ## Specifications
49 |
50 | If you want to code a robot, you should take a look at the
51 | [SPEC.md](https://github.com/asmcup/runtime/blob/master/SPEC.md) file for details
52 | on the available instructions and operations.
53 |
54 | ## Game Basics
55 |
56 | The game world is randomly generated based on a seed value. This allows anyone
57 | to easily generate a new world and test their robot within it. When running the
58 | code on our servers, the seed value will be randomized and all bots will have to
59 | discover the world they have been placed into and compete for resources.
60 |
61 | The world consists of tiles of size 32x32. Aside from normal ground tiles,
62 | there are tiles with items, hazards, obstacles and walls/rooms. Robots have
63 | multiple ways of interacting with the world, ranging from controls for motor
64 | and steering over a beam sensor, a laser, a compass and more to being able to
65 | (*WIP*) communicating with each other.
66 |
67 | It is planned that multiple robots will be able to compete in the same world,
68 | including the ability to fight each other.
69 |
70 | ### Items
71 |
72 | The main goal of each robot is to collect gold, which occurs as item in the world.
73 | Players will receive prizes based on the amount of gold their robots collected.
74 | Robots can also pick up battery items to recharge their internal battery, which is
75 | consumed when executing instructions and taking damage.
76 |
77 | ### Hazards
78 |
79 | There are currently 4 different hazards that penalize robots for standing in them:
80 |
81 | * Mud pit (low damage)
82 | * Water pit (medium damage)
83 | * Fire pit (severe damage)
84 | * Deep pit (instant death)
85 |
86 | ### Obstacles
87 |
88 | There are four types of obstacles, with the first two just being basic doodads
89 | in the game world like stumps and bushes. The last two are rocks which can be
90 | destroyed with a laser.
91 |
92 | ### Rooms
93 |
94 | Some areas of the game world spawn "rooms", which are walled off areas, sometimes
95 | with rocks blocking their entrances. Some rooms can have hazards as walls, these
96 | have more loot in them.
97 |
98 |
--------------------------------------------------------------------------------
/SPEC.md:
--------------------------------------------------------------------------------
1 |
2 | ## Virtual Machine
3 |
4 | Robots are powered by a simple virtual machine with a RISC instruction
5 | set for managing memory via a stack. The instruction language supports native
6 | operations on 8-bit integers and 32-bit floats.
7 |
8 | ## Memory Size
9 |
10 | All programs are 256 bytes in size and are mapped into an 8-bit address
11 | space. While there are instructions to operate 32-bit float data the address
12 | bus itself is 8-bits.
13 |
14 | ## Stack
15 |
16 | The stack is located in memory at `0xFF` and grows downwards as
17 | you push data onto it. Since the memory size is limited to 256 bytes
18 | this means your stack can overflow into your memory if not careful.
19 | Note that there are no registers, although you can of course reserve
20 | a part of the stack for this purpose.
21 |
22 | ## Battery
23 |
24 | Robots are powered via their internal battery. Each instruction executed consumes
25 | one unit of power. Once a robot's battery reaches zero, it dies. Note that robots
26 | may also gain (and lose) charges through interaction with their environment.
27 | Robots can control their CPU clock speed from one instruction per world tick to 100
28 | instructions per world tick.
29 | The internal battery currently holds enough charge for executing 86400 instructions.
30 |
31 | ## Instructions
32 |
33 | Every instruction consists of an opcode (2 least significant bits) and a data part
34 | (6 remaining bits). It may be followed by a data block to hold e.g. a float constant.
35 |
36 | There are four basic types of operations:
37 |
38 | * `FUNC` executes functions on the stack
39 | * `PUSH` loads data onto the stack
40 | * `POP` saves data from the stack
41 | * `BRANCH` jumps if the top 8-bits of the stack are non-zero
42 |
43 | ## Values and Addressing
44 |
45 | Literal values are denoted by a `#` in front of them.
46 | Any values that are not literals are taken to be memory addresses.
47 | Values preceded by a `$` are interpreted as hexadecimal, otherwise they
48 | will be treated as decimals.
49 |
50 | For example, `pushf #1.0` and `pushf 1.0` will push the float value 1.0 to the stack.
51 | `pushf 1` would however push the content of the float (4 bytes of memory) starting at
52 | address 1 to the stack, as would `pushf $01`. Another legal example would be
53 | `push8 #$ff`, which pushes the literal value 255 to the stack.
54 |
55 | ## FUNC opcode
56 |
57 | There are 63 total functions. Note that each of these instructions
58 | are the same size (1 byte) but that they can modify the stack differently.
59 | The *In* column is the number of bytes popped from the stack.
60 | The *Out* column is the number of bytes pushed to the stack.
61 |
62 | Value | Command | In | Out | Notes
63 | ------|-----------|----|-----|-------------------------
64 | 0 | nop | 0 | 0 | No Operation
65 | 1 | b2f | 1 | 4 | Byte to Float
66 | 2 | f2b | 4 | 1 | Float to Byte
67 | 3 | not | 1 | 1 | Byte NOT
68 | 4 | or | 2 | 1 | Byte OR
69 | 5 | and | 2 | 1 | Byte AND
70 | 6 | xor | 2 | 1 | Byte XOR
71 | 7 | shl | 1 | 1 | Byte Shift Left
72 | 8 | shr | 1 | 1 | Byte Shift Right
73 | 9 | add8 | 2 | 1 | Byte Add
74 | 10 | sub8 | 2 | 1 | Byte Subtract
75 | 11 | div8 | 2 | 1 | Byte Divide
76 | 12 | mul8 | 2 | 1 | Byte Multiply
77 | 13 | madd8 | 3 | 1 | Byte Multiply with Add
78 | 14 | negf | 4 | 4 | Float Negate
79 | 15 | addf | 8 | 4 | Float Add
80 | 16 | subf | 8 | 4 | Float Subtract
81 | 17 | divf | 8 | 4 | Float Divide
82 | 18 | mulf | 8 | 4 | Float Multiply
83 | 19 | maddf | 12 | 4 | Float Multiply with Add
84 | 20 | cosf | 4 | 4 | Float Cosine
85 | 21 | sinf | 4 | 4 | Float Sine
86 | 22 | tanf | 4 | 4 | Float Tangent
87 | 23 | acosf | 4 | 4 | Float Arc Cosine
88 | 24 | asinf | 4 | 4 | Float Arc Sine
89 | 25 | atanf | 4 | 4 | Float Arc Tangent
90 | 26 | absf | 4 | 4 | Float Absolute Value
91 | 27 | minf | 8 | 4 | Float Minimum Value
92 | 28 | maxf | 8 | 4 | Float Maximum Value
93 | 29 | powf | 8 | 4 | Float Raise Power
94 | 30 | logf | 4 | 4 | Float Natural Logarithm
95 | 31 | log10f | 4 | 4 | Float Logorithm Base 10
96 | 32 | if_eq8 | 2 | 1 | Byte Equal
97 | 33 | if_ne8 | 2 | 1 | Byte Not Equal
98 | 34 | if_lt8 | 2 | 1 | Byte Less Than
99 | 35 | if_lte8 | 2 | 1 | Byte Less Than or Equal
100 | 36 | if_gt8 | 2 | 1 | Byte Greater Than
101 | 37 | if_gte8 | 2 | 1 | Byte Greater Than or Equal
102 | 38 | if_ltf | 8 | 1 | Float Less Than
103 | 39 | if_ltef | 8 | 1 | Float Less Than or Equal
104 | 40 | if_gtf | 8 | 1 | Float Greater Than
105 | 41 | if_gtef | 8 | 1 | Float Greater Than or Equal
106 | 42 | c_0 | 0 | 1 | Push Byte `0x00`
107 | 43 | c_1 | 0 | 1 | Push Byte `0x01`
108 | 44 | c_2 | 0 | 1 | Push Byte `0x02`
109 | 45 | c_3 | 0 | 1 | Push Byte `0x03`
110 | 46 | c_4 | 0 | 1 | Push Byte `0x04`
111 | 47 | c_255 | 0 | 1 | Push Byte `0xFF`
112 | 48 | c_0f | 0 | 4 | Push Float `0.0f`
113 | 49 | c_1f | 0 | 4 | Push Float `1.0f`
114 | 50 | c_2f | 0 | 4 | Push Float `2.0f`
115 | 51 | c_3f | 0 | 4 | Push Float `3.0f`
116 | 52 | c_m1f | 0 | 4 | Push Float `-1.0f`
117 | 53 | c_inf | 0 | 4 | Push Float Infinity
118 | 54 | if_nan | 4 | 1 | Float NaN Check
119 | 55 | dup8 | 1 | 2 | Byte Duplicate
120 | 56 | dupf | 4 | 8 | Float Duplicate
121 | 57 | jsr | 1 | 1 | Jump Subroutine
122 | 58 | ret | 1 | 0 | Return
123 | 59 | ft8 | 1 | 1 | Fetch Byte
124 | 60 | ftf | 1 | 4 | Fetch Float
125 | 61 | | | | Unused 5
126 | 62 | | | | Unused 6
127 | 63 | io | ? | ? | Input / Output (IO)
128 |
129 | ## PUSH opcode
130 |
131 | Here are the basic ways to push data onto the stack:
132 |
133 | Statement | Size | Description
134 | --------------|------|--------------------------
135 | pushf 0.1 | 5 | Push immediate float to stack
136 | push8 #42 | 2 | Push immediate byte to stack
137 | push8 $f0 | 2 | Push value at memory address 0xf0 to stack
138 | push8r $f0 | 1 | Push value at memory address 0xf0. Only legal if executed within 31 bytes of the target address
139 |
140 | `push8r` stores the relative location of the target address in its data bytes,
141 | thereby saving ROM space but being restricted to nearby addresses.
142 |
143 | Note that the compiler will transform pushes of common constants into function calls
144 | as appropriate. For example, `pushf 0.0` would be translated into function call c_0f
145 | instead of the push, thereby only using 1 byte of memory instead of 5.
146 |
147 | ## POP opcode
148 |
149 | Popping data from the stack can be done using multiple variants:
150 |
151 | Statement | Size | Description
152 | --------------|------|--------------------------
153 | popf $f0 | 2 | Pop float from stack to memory address 0xf0-0xf3
154 | pop8 $f0 | 2 | Pop byte from stack to memory address 0xf0
155 | pop8r $f0 | 1 | Pop byte from stack to memory address 0xf0. Only legal if executed within 31 bytes of the target address
156 |
157 | `pop8r` stores the relative location of the target address in its data bytes,
158 | thereby saving ROM space but being restricted to nearby addresses.
159 |
160 | ## BRANCH opcode
161 |
162 | Statement | Size | Description
163 | --------------|------|--------------------------
164 | jmp $f0 | 2 | Jump Always
165 | jnz $f0 | 2 | Jump Not Zero
166 | jmp [$f0] | 2 | Jump Always Indirect
167 | jnzr $f0 | 1 | Jump Not Zero Relative. Only legal if executed within 31 bytes of the target address
168 |
169 | `jnzr` stores the relative location of the target address in its data bytes,
170 | thereby saving ROM space but being restricted to nearby addresses.
171 | The `jne` and `jner` commands are equivalent to `jnz` and `jnzr`, respectively.
172 |
173 | ## Further compiler information
174 |
175 | Line contents after a semicolon (`;`) are ignored by the compiler.
176 |
177 | You may define labels (e.g. `start:`) and refer to them using their name (e.g.
178 | `jmp start`), which will be replaced by the memory location (address) in the
179 | compiled code that they were defined at. A statement (including more labels)
180 | may follow on the same line.
181 | The literal value of the label address can be obtained with the `&` operator.
182 | See below (JSR and RET) for the use of this.
183 |
184 | The compiler also accepts statements of these forms:
185 |
186 | Statement | Size | Description
187 | --------------|------|--------------------------
188 | db8 #$f0 | 1 | Data byte (replaced by 0xf0 in ROM)
189 | db #$f0 | 1 | Same as db8
190 | dbf 0.1 | 4 | Data bytes from float
191 |
192 | A common idiom is using `myVar: db8 #0` to create named variables.
193 | This allows using statements like `push8 myVar`.
194 |
195 | ### JSR and RET
196 |
197 | The `ret` function pops and jumps to the address at the top of the stack. `jsr` does
198 | the same, but also pushes the address of the instruction following it. Consider this
199 | example:
200 |
201 | ```
202 | push8 &half_speed
203 | jsr
204 |
205 | half_speed:
206 | pushf 0.5
207 | push8 #IO_MOTOR
208 | io
209 | ret
210 | ```
211 |
212 | ### Fetches
213 |
214 | The `ft8` and `ftf` functions allow retrieving values from the stack at an offset
215 | from the current stack pointer. The offset is a byte value popped from the stack.
216 |
217 | ```
218 | push8 #13
219 | push8 #37
220 | push8 #1
221 | ft8
222 | ; The stack is now (from top to bottom): 13 37 13
223 |
224 | pushf 1.3
225 | pushf 3.7
226 | push8 #4 ; !
227 | ftf
228 | ; Same as above, with floats.
229 |
230 | ; These are useless (but helpful for understanding):
231 | push8 #0
232 | ft8
233 | ; This did the exact same thing as dup8, just more expensive.
234 |
235 | push8 #-1
236 | ft8 ; This instruction does literally nothing.
237 | ```
238 |
239 | ## IO
240 |
241 | While the VM itself allows you to construct arbitrary programs, the IO
242 | controls the robot itself. The `io` command takes the top value from the stack
243 | and executes a command:
244 |
245 | Value | Constant | Function
246 | -------|--------------|----------
247 | 0 | IO_SENSOR | Beam Sensor
248 | 1 | IO_MOTOR | Control Motor
249 | 2 | IO_STEER | Control Steering
250 | 3 | IO_OVERCLOCK | CPU Clock Control
251 | 4 | IO_LASER | Laser Attack (not yet implemented)
252 | 5 | IO_BATTERY | Read Battery
253 | 6 | IO_MARK | Mark ("pee")
254 | 7 | IO_MARK_READ | Mark Read ("smell")
255 | 8 | IO_ACCELEROMETER | Accelerometer
256 | 9 | IO_RADIO | (Planned) Set radio strength
257 | 10 | IO_SEND | (Planned) Emit data via radio
258 | 10 | IO_RECV | (Planned) Receive data via radio
259 |
260 | ### Beam Sensor
261 |
262 | Casts a sensor beam at the current looking direction.
263 |
264 | ```
265 | push8 #IO_SENSOR
266 | io
267 | pop8 type
268 | popf distance
269 | ```
270 |
271 | After `io` the stack will contain a byte on the top with a float below it.
272 | The float is the distance the beam travelled (maximum range is `256.0`).
273 | The byte specifies which type of thing was hit by the beam:
274 |
275 | Value | Meaning
276 | ------|---------
277 | 0 | Nothing
278 | 1 | Solid
279 | 2 | Hazard
280 | 4 | Gold
281 | 8 | Battery
282 |
283 | By default the beam will hit anything listed above and return what was hit.
284 | This can be changed by using the `IO_SENSOR_CONFIG` command:
285 |
286 | ```
287 | push8 what_to_ignore
288 | push8 #IO_SENSOR_CONFIG
289 | io
290 | ```
291 |
292 | In the example above *what_to_ignore* is a bitmask of things to have the
293 | beam ignore. For example, to have the beam hit everything but gold:
294 |
295 | ```
296 | push8 #4
297 | push8 #IO_SENSOR_CONFIG
298 | io
299 | ```
300 |
301 | Another way to change the sensor (and laser) beam behavior is to set the angle
302 | at which it is cast:
303 |
304 | ```
305 | pushf -1.0
306 | push8 #IO_BEAM_DIRECTION
307 | io
308 | ```
309 |
310 | Acceptable values range from `-1.0` (90° to the left of the robot's facing)
311 | to `1.0` (90° to the right of the robot's facing). This affects both the beam
312 | sensor and the laser!
313 |
314 | ### Motor Control
315 |
316 | ```
317 | ; Maximum Speed
318 | pushf 1.0
319 | push8 #IO_MOTOR
320 | io
321 |
322 | ; Unpower motor
323 | pushf 0.0
324 | push8 #IO_MOTOR
325 | io
326 |
327 | ; Reverse
328 | pushf -1.0
329 | push8 #IO_MOTOR
330 | io
331 | ```
332 |
333 | ### Steering Control
334 |
335 | ```
336 | ; Steer right
337 | pushf 1.0
338 | push8 #IO_STEER
339 | io
340 |
341 | pushf -1.0
342 | push8 #IO_STEER
343 | io
344 | ```
345 |
346 |
347 | ### CPU Clock Control
348 |
349 | The CPU speed of the robot can be "overclocked" like this:
350 |
351 | ```
352 | push8 #100
353 | push8 #IO_OVERCLOCK
354 | io
355 | ```
356 |
357 | The maximum CPU speed is 100, setting any number higher is the same as setting
358 | to 100. The game operates at 10 frames per second, meaning a fully overclocked
359 | CPU will execute 1000 instructions per second, 100 per frame.
360 |
361 | ### Battery Check
362 |
363 | A robot can query how much battery it has:
364 |
365 | ```
366 | push8 #IO_BATTERY
367 | io
368 | popf batteryLevel
369 | ```
370 |
371 | The float on the stack will scale between `0.0` (completely empty) and `1.0`
372 | (starting amount) to indicate the amount of battery remaining.
373 |
374 | ### Laser
375 |
376 | Robots get a laser beam which can damage some obstacles and other robots in
377 | combat. Currently a laser can break rocks which may be blocking doors. Having
378 | the laser on costs up to `256` battery per game frame, less if the laser is
379 | not fully powered (which results in reduced range) or hits something early.
380 |
381 | ```
382 | ; Full power laser
383 | pushf 1.0
384 | push8 #IO_LASER
385 | io
386 |
387 | ; Laser off
388 | pushf 0.0
389 | push8 #IO_LASER
390 | io
391 | ```
392 |
393 | Note that the laser beam's angle relative to the robot can be changed. See the
394 | Beam Sensor section for details.
395 |
396 | ### Accelerometer
397 |
398 | The accelerometer allows a robot to detect relative changes in its position.
399 | Each time you use the IO_ACCELEROMETER command, the last position is saved and
400 | the difference is pushed on the stack as two floats.
401 |
402 | ```
403 | push8 #IO_ACCELEROMETER
404 | io
405 | popf relY
406 | popf relX
407 | ```
408 |
409 | ### Compass
410 |
411 | The compass allows a robot to determine which direction it is facing. Facing
412 | is always positive and ranges from `0` (west) over `PI/2` (north) etc. up to
413 | `2 PI`.
414 |
415 | ```
416 | push8 #IO_COMPASS
417 | io
418 | popf facing
419 | ```
420 |
421 | ### World Marking
422 |
423 | Robots can "mark" the world which is kind of like how animals can pee and smell
424 | the pee. A robot uses `IO_MARK` to write bytes to the current tile and can
425 | use `IO_MARK_READ` to read data of the current tile. Each tile has 8 bytes
426 | of storage.
427 |
428 | ```
429 | push8 #00 ; Offset (index) of byte to write
430 | push8 #42 ; Value to save
431 | push8 #IO_MARK
432 | io
433 | ```
434 |
435 | You can read the same data back using:
436 |
437 | ```
438 | push8 #00 ; Offset (index) of byte to read
439 | push8 #IO_MARK_READ
440 | io
441 | pop8 tileData
442 | ```
443 |
444 | Note that other robots (if in a shared world) may also read or (over)write
445 | this data from/to the same tile.
446 |
447 | ### Radio
448 |
449 | *Note the radio isn't implemented entirely yet*
450 |
451 | The radio allows robots to send and receive messages with one another. Robots
452 | "tune" their radio using the `IO_RADIO` command setting a frequency and
453 | transmit power. The `IO_SEND` and `IO_RECV` to send and receive messages.
454 |
455 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | repositories {
4 | jcenter()
5 | }
6 |
7 | sourceSets {
8 | main {
9 | java {
10 | srcDirs = ["src/"]
11 | }
12 | resources {
13 | srcDirs = ["src/"]
14 | exclude '**/*.java'
15 | }
16 | }
17 |
18 | test {
19 | java {
20 | srcDirs = ['test/']
21 | }
22 | }
23 | }
24 |
25 | jar {
26 | manifest {
27 | attributes 'Implementation-Title': 'ASMCup Runtime',
28 | 'Main-Class': 'asmcup.sandbox.Main'
29 | }
30 | }
31 |
32 | dependencies {
33 | testCompile "junit:junit:4.12"
34 | }
35 |
36 | // ------------------ code coverage ------------------------------ //
37 |
38 | apply plugin: 'jacoco'
39 |
40 | jacoco {
41 | toolVersion = "0.7.6.201602180812"
42 | reportsDir = file("$buildDir/codecoverage")
43 | }
44 |
45 | jacocoTestReport {
46 | reports {
47 | xml.enabled false
48 | csv.enabled false
49 | html.destination "${buildDir}/codecoverage"
50 | }
51 | }
52 |
53 | // ------------------ static analysis ---------------------------- //
54 |
55 | apply plugin: 'findbugs'
56 |
57 | findbugs {
58 | reportLevel = "high"
59 | }
60 |
61 | tasks.withType(FindBugs) {
62 | reports {
63 | xml.enabled false
64 | html.enabled true
65 | }
66 | }
--------------------------------------------------------------------------------
/src/about.txt:
--------------------------------------------------------------------------------
1 | Version: 1.7.0 (Beta)
2 | Homepage: asmcup.github.io
3 | Project: github.com/asmcup/runtime
4 |
5 | Creator:
6 | Kristopher Ives
7 |
8 | Special Thanks:
9 | Max Langhof
10 | Tim Siebels
11 | Roy McNealy
12 |
--------------------------------------------------------------------------------
/src/asmcup/compiler/Main.java:
--------------------------------------------------------------------------------
1 | package asmcup.compiler;
2 |
3 | import java.io.*;
4 | import java.nio.file.*;
5 | import java.util.List;
6 |
7 | public class Main {
8 | public static void main(String[] args) throws IOException {
9 | if (args.length < 2) {
10 | System.err.printf("USAGE: asmcup-compile %n");
11 | System.exit(1);
12 | return;
13 | }
14 |
15 | File inFile = new File(args[0]);
16 | File outFile = new File(args[1]);
17 | String[] lines = readLines(inFile.toPath());
18 | FileOutputStream output = new FileOutputStream(outFile);
19 |
20 | Compiler compiler = new Compiler();
21 | byte[] program = compiler.compile(lines);
22 | output.write(program);
23 | output.close();
24 | }
25 |
26 | protected static String[] readLines(Path path) throws IOException {
27 | List list = Files.readAllLines(path);
28 | return list.toArray(new String[list.size()]);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/asmcup/compiler/RobotConstsTable.java:
--------------------------------------------------------------------------------
1 | package asmcup.compiler;
2 |
3 | import java.lang.reflect.Field;
4 | import java.util.*;
5 |
6 | import asmcup.runtime.Robot;
7 |
8 | public class RobotConstsTable {
9 | private static final Map consts;
10 |
11 | static {
12 | consts = new HashMap<>();
13 |
14 | for (Field field : Robot.class.getDeclaredFields()) {
15 | if (isConst(field.getName())) {
16 | try {
17 | add(field);
18 | } catch (Exception e) {
19 | throw new RuntimeException("Failed to access static member via reflection");
20 | }
21 | }
22 | }
23 | }
24 |
25 | private static boolean isConst(String name) {
26 | return name.startsWith("IO_") || name.startsWith("SENSOR_");
27 | }
28 |
29 | private static void add(Field field) throws Exception {
30 | consts.put(field.getName(), field.getInt(null));
31 | }
32 |
33 | public static boolean contains(String s) {
34 | return consts.containsKey(s);
35 | }
36 |
37 | public static int get(String s) {
38 | return consts.get(s);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/asmcup/compiler/VMFuncTable.java:
--------------------------------------------------------------------------------
1 | package asmcup.compiler;
2 |
3 | import java.lang.reflect.Field;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | import asmcup.vm.VMFuncs;
8 |
9 | public class VMFuncTable {
10 | private static final Map funcs;
11 | private static final Map reverse;
12 |
13 | static {
14 | funcs = new HashMap<>();
15 | reverse = new HashMap<>();
16 |
17 | for (Field field : VMFuncs.class.getDeclaredFields()) {
18 | try {
19 | bind(field);
20 | } catch (Exception e) {
21 | throw new RuntimeException("Failed to access static member via reflection");
22 | }
23 | }
24 | }
25 |
26 | private static void bind(Field field) throws Exception {
27 | String s = field.getName();
28 |
29 | if (!s.startsWith("F_")) {
30 | return;
31 | }
32 |
33 | int value = field.getInt(null);
34 | s = s.substring("F_".length()).toLowerCase();
35 | bind(s, value);
36 | }
37 |
38 | private static void bind(String name, int code) {
39 | funcs.put(name, code);
40 | reverse.put(code, name);
41 | }
42 |
43 | public static int parse(String s) {
44 | return funcs.get(s.toLowerCase().trim());
45 | }
46 |
47 | public static String unparse(int index) {
48 | return reverse.get(index);
49 | }
50 |
51 | public static boolean exists(String name) {
52 | return funcs.containsKey(name.toLowerCase().trim());
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/asmcup/decompiler/Decompiler.java:
--------------------------------------------------------------------------------
1 | package asmcup.decompiler;
2 |
3 | import java.io.*;
4 |
5 | import asmcup.compiler.VMFuncTable;
6 | import asmcup.vm.VMConsts;
7 |
8 | public class Decompiler implements VMConsts {
9 |
10 | private final PrintStream out;
11 |
12 | public Decompiler() {
13 | out = System.out;
14 | }
15 |
16 | public Decompiler(PrintStream out) {
17 | this.out = out;
18 | }
19 |
20 | public void decompile(byte[] ram) {
21 | int pc = 0;
22 | int end = 255;
23 |
24 | while (read8(ram, end) == 0 && end > 0) {
25 | end--;
26 | }
27 |
28 | while (pc <= end) {
29 | pc += decompileCommand(ram, pc);
30 | }
31 | }
32 |
33 | public int read8(byte[] ram, int pc) {
34 | return ram[pc & 0xFF] & 0xFF;
35 | }
36 |
37 | public int read16(byte[] ram, int pc) {
38 | return read8(ram, pc) | (read8(ram, pc + 1) << 8);
39 | }
40 |
41 | public int read32(byte[] ram, int pc) {
42 | return read16(ram, pc) | (read16(ram, pc + 2) << 16);
43 | }
44 |
45 | public float readFloat(byte[] ram, int pc) {
46 | return Float.intBitsToFloat(read32(ram, pc));
47 | }
48 |
49 | public void dump(int pc, String s) {
50 | out.printf("L%02x: %s%n", pc, s);
51 | }
52 |
53 | public int decompileCommand(byte[] ram, int pc) {
54 | int bits = ram[pc & 0xFF] & 0xFF;
55 | int opcode = bits & 0b11;
56 | int data = bits >> 2;
57 |
58 | switch (opcode) {
59 | case OP_BRANCH:
60 | return decompileBranch(ram, pc, data);
61 | case OP_PUSH:
62 | return decompilePush(ram, pc, data);
63 | case OP_POP:
64 | return decompilePop(ram, pc, data);
65 | case OP_FUNC:
66 | return decompileFunc(ram, pc, data);
67 | }
68 |
69 | return 1;
70 | }
71 |
72 | public int decompileFunc(byte[] ram, int pc, int data) {
73 | dump(pc, VMFuncTable.unparse(data));
74 | return 1;
75 | }
76 |
77 | public int decompilePop(byte[] ram, int pc, int data) {
78 | int addr;
79 |
80 | switch (data) {
81 | case MAGIC_POP_BYTE:
82 | addr = read8(ram, pc + 1);
83 | dump(pc, String.format("pop8 $%02x", addr));
84 | return 2;
85 | case MAGIC_POP_BYTE_INDIRECT:
86 | addr = read8(ram, pc + 1);
87 | dump(pc, String.format("pop8 [$%02x]", addr));
88 | return 2;
89 | case MAGIC_POP_FLOAT:
90 | addr = read8(ram, pc + 1);
91 | dump(pc, String.format("popf $%02x", addr));
92 | return 2;
93 | case MAGIC_POP_FLOAT_INDIRECT:
94 | addr = read8(ram, pc + 1);
95 | dump(pc, String.format("popf [$%02x]", addr));
96 | return 2;
97 | }
98 |
99 | int r = data - 32;
100 | addr = (pc + r + 1) & 0xFF;
101 | dump(pc, String.format("pop8r $%02x ; relative %d", addr, r));
102 | return 1;
103 | }
104 |
105 | public int decompileBranch(byte[] ram, int pc, int data) {
106 | int addr;
107 |
108 | switch (data) {
109 | case MAGIC_BRANCH_ALWAYS:
110 | addr = read8(ram, pc + 1);
111 | dump(pc, String.format("jmp $%02x", addr));
112 | return 2;
113 | case MAGIC_BRANCH_IMMEDIATE:
114 | addr = read8(ram, pc + 1);
115 | dump(pc, String.format("jnz $%02x", addr));
116 | return 2;
117 | case MAGIC_BRANCH_INDIRECT:
118 | addr = read8(ram, pc + 1);
119 | dump(pc, String.format("jmp [$%02x]", addr));
120 | return 2;
121 | }
122 |
123 | int r = data - 32;
124 | addr = (pc + r + 1) & 0xFF;
125 | dump(pc, String.format("jnzr $%02x ; relative %d", addr, r));
126 | return 1;
127 | }
128 |
129 | public int decompilePush(byte[] ram, int pc, int data) {
130 | int addr;
131 | float f;
132 |
133 | switch (data) {
134 | case MAGIC_PUSH_BYTE_IMMEDIATE:
135 | return verbosePushByte(ram, pc);
136 | case MAGIC_PUSH_BYTE_MEMORY:
137 | addr = read8(ram, pc + 1);
138 | dump(pc, String.format("push8 $%02x", addr));
139 | return 2;
140 | case MAGIC_PUSH_FLOAT_IMMEDIATE:
141 | return verbosePushFloat(ram, pc);
142 | case MAGIC_PUSH_FLOAT_MEMORY:
143 | addr = read8(ram, pc + 1);
144 | dump(pc, String.format("pushf $%02x", addr));
145 | return 2;
146 | }
147 |
148 | int r = data - 32;
149 | addr = (pc + r + 1) & 0xFF;
150 | dump(pc, String.format("push8r $%02x ; relative %d", addr, r));
151 | return 1;
152 | }
153 |
154 | protected int verbosePushByte(byte[] ram, int pc) {
155 | // We just read a 2 byte push8 that could get condensed into the 1 byte
156 | // function call by the compiler. This may break programs when they are
157 | // decompiled and then recompiled because addresses may change.
158 | int value = read8(ram, pc + 1);
159 |
160 | switch (value) {
161 | case 0:
162 | case 1:
163 | case 2:
164 | case 3:
165 | case 4:
166 | case 255:
167 | int instruction = (MAGIC_PUSH_BYTE_IMMEDIATE << 2) + OP_PUSH;
168 | dump(pc, String.format("db8 #$%02x ; verbose push8 #value", instruction));
169 | dump(pc+1, String.format("db8 #$%02x ; (continued)", value));
170 | break;
171 | default:
172 | dump(pc, String.format("push8 #$%02x", value));
173 | }
174 | return 2;
175 | }
176 |
177 | protected int verbosePushFloat(byte[] ram, int pc) {
178 | // We just read a 5 byte pushf that could get condensed into the 1 byte
179 | // function call by the compiler. This may break programs when they are
180 | // decompiled and then recompiled because addresses may change.
181 | float value = readFloat(ram, pc + 1);
182 |
183 | if (value == -1.0f || value == 0.0f ||
184 | value == 1.0f || value == 2.0f ||
185 | value == 3.0f || Float.isInfinite(value)) {
186 | int instruction = (MAGIC_PUSH_FLOAT_IMMEDIATE << 2) + OP_PUSH;
187 | dump(pc, String.format("db8 #$%02x ; verbose pushf #value", instruction));
188 | dump(pc+1, String.format("dbf #%.9e ; (continued)", value));
189 | } else {
190 | dump(pc, String.format("pushf #%.9e", value));
191 | }
192 | return 5;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/asmcup/decompiler/Main.java:
--------------------------------------------------------------------------------
1 | package asmcup.decompiler;
2 |
3 | import java.io.*;
4 | import java.nio.file.Files;
5 |
6 | public class Main {
7 | public static void main(String[] args) throws IOException {
8 | if (args.length != 1) {
9 | System.err.printf("USAGE: asmcup-decompiler %n");
10 | System.exit(1);
11 | return;
12 | }
13 |
14 | File in = new File(args[0]);
15 | byte[] ram = Files.readAllBytes(in.toPath());
16 |
17 | if (ram.length != 256) {
18 | System.err.printf("ERROR: Program must be 256 bytes not %d%n", ram.length);
19 | System.exit(1);
20 | return;
21 | }
22 |
23 | Decompiler decompiler = new Decompiler();
24 | decompiler.decompile(ram);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/asmcup/evaluation/Evaluator.java:
--------------------------------------------------------------------------------
1 | package asmcup.evaluation;
2 |
3 | import java.util.HashSet;
4 |
5 | import asmcup.genetics.Spawn;
6 | import asmcup.runtime.Robot;
7 | import asmcup.runtime.World;
8 | import asmcup.vm.VM;
9 |
10 | //TODO: Add Evaluator test(s)!
11 | //- Evaluate same spawn x times
12 |
13 | public class Evaluator {
14 |
15 | final public boolean simplified;
16 |
17 | public int maxSimFrames;
18 | public int directionsPerSpawn;
19 | public int extraWorldCount;
20 | public int idleMax;
21 | public int idleIoMax;
22 | public int exploreReward;
23 | public int ramPenalty;
24 | public int goldReward;
25 | public int batteryReward;
26 | public int forceStack;
27 | public boolean temporal;
28 | public boolean forceIO;
29 |
30 | public long scoringCount = 0;
31 |
32 | public int baseSeed = 0;
33 |
34 | public Evaluator(boolean simplified) {
35 | this.simplified = simplified;
36 |
37 | maxSimFrames = 10 * 60;
38 | directionsPerSpawn = simplified ? 1 : 8;
39 | extraWorldCount = 0;
40 | idleMax = 0;
41 | idleIoMax = 0;
42 | exploreReward = simplified ? 0 : 4;
43 | ramPenalty = simplified ? 0 : 2;
44 | goldReward = 50;
45 | batteryReward = simplified ? 0 : 50;
46 | temporal = simplified ? false : true;
47 | forceIO = false;
48 | }
49 |
50 | // TODO: Make more transparent for threading...
51 | public float score(byte[] ram) {
52 | scoringCount = 0;
53 | Scorer scorer = new Scorer();
54 | float score = 0.0f;
55 |
56 | for (int i = 0; i < extraWorldCount; i++) {
57 | score += scorer.calculate360(ram, Spawn.randomFromSeed(baseSeed + i));
58 | }
59 |
60 | return score / Math.max(scoringCount, 1);
61 | }
62 |
63 | protected class Scorer {
64 | private Robot robot;
65 | private VM vm;
66 | private World world;
67 |
68 | private int lastGold;
69 | private int lastBattery;
70 |
71 | private HashSet rammed;
72 | private HashSet explored;
73 | private int lastExplored;
74 |
75 | public float calculate360(byte[] ram, Spawn spawn) {
76 | float score = 0.0f;
77 |
78 | for (float turn = 0; turn < 360f; turn += 360f / directionsPerSpawn) {
79 | score += calculate(ram, spawn, (float)Math.toRadians(turn));
80 | }
81 |
82 | return score;
83 | }
84 |
85 | public float calculate(byte[] ram, Spawn spawn, float turn) {
86 | vm = new VM(ram.clone());
87 | robot = new Robot(1, vm);
88 | world = spawn.getNewWorld();
89 |
90 | world.addRobot(robot);
91 | robot.position(spawn.x, spawn.y);
92 | robot.setFacing(spawn.facing + turn);
93 |
94 | float score = 0.0f;
95 |
96 | lastGold = 0;
97 | lastBattery = robot.getBattery();
98 | explored = new HashSet<>();
99 | rammed = new HashSet<>();
100 | lastExplored = 0;
101 |
102 | for (int frame = 0; frame < maxSimFrames; frame++) {
103 | world.tick();
104 |
105 | if (violatesStackRules()) {
106 | break;
107 | }
108 |
109 | if (violatesIoRules()) {
110 | break;
111 | }
112 |
113 | if (robot.isDead()) {
114 | break;
115 | }
116 |
117 | float t = getTimeBenefitFactor(frame);
118 |
119 | score += rewardGoldCollection(t);
120 | score += rewardBatteryCollection(t);
121 |
122 | int tileKey = getTileKey();
123 | score += rewardExploration(tileKey, t, frame);
124 | score -= penaliseRamming(tileKey, t);
125 |
126 | lastGold = robot.getGold();
127 | lastBattery = robot.getBattery();
128 |
129 | if (idledTooLong(frame)) {
130 | break;
131 | }
132 |
133 | if (ioIdledTooLong(frame)) {
134 | break;
135 | }
136 |
137 | score += 0.001f;
138 | }
139 |
140 | scoringCount++;
141 | return score;
142 | }
143 |
144 | private boolean violatesIoRules() {
145 | return forceIO && robot.getLastInvalidIO() > 0;
146 | }
147 |
148 | private boolean violatesStackRules() {
149 | if (forceStack <= 0) {
150 | return false;
151 | }
152 |
153 | int stackLimit = (0xFF - forceStack);
154 | int sp = vm.getStackPointer();
155 | int pc = vm.getProgramCounter();
156 | return sp < stackLimit || pc > stackLimit;
157 | }
158 |
159 | private float getTimeBenefitFactor(int frame) {
160 | return (temporal ? 1.0f - (float)frame / (float)maxSimFrames : 1.0f);
161 | }
162 |
163 | private float rewardGoldCollection(float t) {
164 | return (robot.getGold() == lastGold) ? 0 : t * goldReward;
165 | }
166 |
167 | private float rewardBatteryCollection(float t) {
168 | return (robot.getBattery() <= lastBattery) ? 0 : t * batteryReward;
169 | }
170 |
171 | private int getTileKey() {
172 | int col = (int)(robot.getX() / World.TILE_SIZE);
173 | int row = (int)(robot.getY() / World.TILE_SIZE);
174 | return col | (row << 16);
175 | }
176 |
177 | private float rewardExploration(int key, float t, int frame) {
178 | if (explored.add(key)) {
179 | lastExplored = frame;
180 | return t * exploreReward;
181 | }
182 |
183 | return 0;
184 | }
185 |
186 | private float penaliseRamming(int tileKey, float t) {
187 | if (ramPenalty == 0) {
188 | return 0;
189 | }
190 |
191 | if (robot.isRamming()) {
192 | if (rammed.add(tileKey)) {
193 | return t * ramPenalty;
194 | } else {
195 | return t * ramPenalty * 0.01f;
196 | }
197 | }
198 |
199 | return 0;
200 | }
201 |
202 | private boolean idledTooLong(int frame) {
203 | return idleMax > 0 && (frame - lastExplored) > idleMax;
204 | }
205 |
206 | private boolean ioIdledTooLong(int frame) {
207 | return idleIoMax > 0 && (frame - robot.getLastIO()) > idleIoMax;
208 | }
209 | }
210 | }
--------------------------------------------------------------------------------
/src/asmcup/evaluation/EvaluatorFrontPanel.java:
--------------------------------------------------------------------------------
1 | package asmcup.evaluation;
2 |
3 | import javax.swing.*;
4 |
5 | import asmcup.sandbox.*;
6 |
7 | public class EvaluatorFrontPanel extends FrontPanel {
8 | protected final Evaluator evaluator;
9 | // Note: These initial values are not authoritative, the ones in Evaluator are.
10 | protected JSpinner frameSpinner = createSpinner(10 * 60, 1, 10 * 60 * 60 * 24);
11 | protected JSpinner extraWorldSpinner = createSpinner(0, 0, 100);
12 | protected JSpinner orientationSpinner = createSpinner(0, 0, 100);
13 | protected JSpinner idleSpinner = createSpinner(0, 0, 1000 * 1000);
14 | protected JSpinner idleIoSpinner = createSpinner(0, 0, 1000 * 1000);
15 | protected JSpinner exploreSpinner = createSpinner(4, -1000, 1000);
16 | protected JSpinner rammingSpinner = createSpinner(2, -1000, 1000);
17 | protected JSpinner goldSpinner = createSpinner(50, -1000, 1000);
18 | protected JSpinner batterySpinner = createSpinner(100, -1000, 1000);
19 | protected JCheckBox temporalCheckBox = createCheckBox();
20 | protected JSpinner stackSpinner = createSpinner(0, 0, 256);
21 | protected JCheckBox ioCheckBox = createCheckBox();
22 |
23 | public EvaluatorFrontPanel(Evaluator evaluator) {
24 | this.evaluator = evaluator;
25 |
26 | setBorder(BorderFactory.createTitledBorder("Evaluation Metrics"));
27 |
28 | addRow("Random Tests:", extraWorldSpinner, "Bots are evaluated in this many additional random worlds");
29 | addRow("Orientations:", orientationSpinner, "Amount of facing directions evaluated for each start point");
30 | addRow("Frames:", frameSpinner, "Maximum number of frames for the simulation (10 frames = 1 second)");
31 | addRow("Gold Reward:", goldSpinner, "Number of points earned by collecting a gold item");
32 | addRow("Battery Reward:", batterySpinner, "Number of points earned by collecting a battery item");
33 | addRow("Explore Reward:", exploreSpinner, "Number of points earned by touching a new tile");
34 | addRow("Collide Penalty:", rammingSpinner, "Number of points lost by ramming a tile for the first time");
35 | if (!evaluator.simplified) {
36 | addRow("Idle Timeout:", idleSpinner, "Number of frames without movement before a bot is killed (0 is disabled)");
37 | addRow("IO Idle Timeout:", idleIoSpinner, "Number of frames without IO before a bot is killed (0 is disabled)");
38 | addRow("Force Stack:", stackSpinner, "Kill a bot if the stack pointer ever goes beyond this (0 is disabled)");
39 | addRow("Force IO:", ioCheckBox, "Kill a bot if it ever generates an invalid IO command");
40 | }
41 | addRow("Early Reward:", temporalCheckBox, "Scale points so earlier activity is worth more");
42 | updateSliders();
43 | }
44 |
45 | public void updateSliders() {
46 | extraWorldSpinner.setValue(evaluator.extraWorldCount);
47 | orientationSpinner.setValue(evaluator.directionsPerSpawn);
48 | frameSpinner.setValue(evaluator.maxSimFrames);
49 | goldSpinner.setValue(evaluator.goldReward);
50 | batterySpinner.setValue(evaluator.batteryReward);
51 | exploreSpinner.setValue(evaluator.exploreReward);
52 | idleSpinner.setValue(evaluator.idleMax);
53 | idleIoSpinner.setValue(evaluator.idleIoMax);
54 | rammingSpinner.setValue(evaluator.ramPenalty);
55 | stackSpinner.setValue(evaluator.forceStack);
56 | ioCheckBox.setSelected(evaluator.forceIO);
57 | temporalCheckBox.setSelected(evaluator.temporal);
58 | }
59 |
60 | public void updateEvaluator() {
61 | evaluator.extraWorldCount = getInt(extraWorldSpinner);
62 | evaluator.directionsPerSpawn = getInt(orientationSpinner);
63 | evaluator.maxSimFrames = getInt(frameSpinner);
64 | evaluator.goldReward = getInt(goldSpinner);
65 | evaluator.batteryReward = getInt(batterySpinner);
66 | evaluator.exploreReward = getInt(exploreSpinner);
67 | evaluator.idleMax = getInt(idleSpinner);
68 | evaluator.idleIoMax = getInt(idleIoSpinner);
69 | evaluator.ramPenalty = getInt(rammingSpinner);
70 | evaluator.forceStack = getInt(stackSpinner);
71 | evaluator.forceIO = ioCheckBox.isSelected();
72 | evaluator.temporal = temporalCheckBox.isSelected();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/asmcup/evaluation/EvaluatorWindow.java:
--------------------------------------------------------------------------------
1 | package asmcup.evaluation;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.imageio.ImageIO;
6 | import javax.swing.JButton;
7 | import javax.swing.JFrame;
8 | import javax.swing.JLabel;
9 |
10 | import asmcup.sandbox.FrontPanel;
11 | import asmcup.sandbox.Sandbox;
12 |
13 | public class EvaluatorWindow extends JFrame {
14 | protected final Sandbox sandbox;
15 | public final SpawnEvaluator evaluator;
16 | public final EvaluatorFrontPanel evalPanel;
17 | public final FrontPanel panel = new FrontPanel();
18 |
19 | protected JButton startButton = new JButton("Start");
20 | protected JButton stopButton = new JButton("Stop");
21 | protected JLabel scoreLabel = new JLabel("0");
22 |
23 | public EvaluatorWindow(Sandbox sandbox) throws IOException {
24 | this.sandbox = sandbox;
25 | evaluator = new SpawnEvaluator(sandbox.spawns, true);
26 | evalPanel = new EvaluatorFrontPanel(evaluator);
27 |
28 | panel.addWideItem(evalPanel);
29 |
30 | panel.addItems(startButton, stopButton);
31 | panel.addRow("ROM score:", scoreLabel);
32 | startButton.addActionListener(e -> evaluate());
33 | stopButton.addActionListener(e -> stop());
34 |
35 | setContentPane(panel);
36 |
37 | setTitle("User Bot Evaluator");
38 | setResizable(false);
39 | setIconImage(ImageIO.read(getClass().getResource("/gauge.png")));
40 | pack();
41 | }
42 |
43 | public void evaluate() {
44 | evalPanel.updateEvaluator();
45 | if (!quickScoring()) {
46 | // TODO: Threading, don't want to block the UI.
47 | // Bit difficult with how Evaluator works though.
48 | }
49 | // (else)
50 | float score = evaluator.score(sandbox.getROM());
51 | scoreLabel.setText(String.valueOf(score));
52 | }
53 |
54 | public void stop() {
55 |
56 | }
57 |
58 | protected boolean quickScoring() {
59 | return evaluator.maxSimFrames * evaluator.extraWorldCount < 2000;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/asmcup/evaluation/SpawnEvaluator.java:
--------------------------------------------------------------------------------
1 | package asmcup.evaluation;
2 |
3 | import asmcup.genetics.Spawn;
4 |
5 | public class SpawnEvaluator extends Evaluator {
6 | final protected Spawns spawns;
7 |
8 | public SpawnEvaluator(Spawns spawns, boolean simplified) {
9 | super(simplified);
10 | this.spawns = spawns;
11 | }
12 |
13 | @Override
14 | public float score(byte[] ram) {
15 | baseSeed = spawns.getCombinedSeed();
16 | float score = super.score(ram) * scoringCount;
17 |
18 | Scorer scorer = new Scorer();
19 | for (Spawn spawn : spawns.getIterable()) {
20 | score += scorer.calculate360(ram, spawn);
21 | }
22 |
23 | return score / Math.max(scoringCount, 1);
24 | }
25 | }
--------------------------------------------------------------------------------
/src/asmcup/evaluation/Spawns.java:
--------------------------------------------------------------------------------
1 | package asmcup.evaluation;
2 |
3 | import java.util.*;
4 |
5 | import javax.swing.ListModel;
6 | import javax.swing.event.*;
7 |
8 | import asmcup.genetics.Spawn;
9 | import asmcup.runtime.*;
10 | import asmcup.sandbox.*;
11 |
12 | public class Spawns implements ListModel {
13 | private ArrayList spawns = new ArrayList<>();
14 | protected final Sandbox sandbox;
15 |
16 | ArrayList listeners = new ArrayList<>();
17 |
18 | public Spawns(Sandbox sandbox) {
19 | this.sandbox = sandbox;
20 | }
21 |
22 | public AbstractCollection getIterable() {
23 | return new ArrayList(spawns);
24 | }
25 |
26 | public void addSpawnAtMouse() {
27 | Mouse mouse = sandbox.mouse;
28 | Robot robot = sandbox.getRobot();
29 | World world = sandbox.getWorld();
30 | Spawn spawn = new Spawn(mouse.getWorldX(), mouse.getWorldY(), robot.getFacing(), world.getSeed());
31 | add(spawn);
32 | }
33 | public void addSpawnAtRobot() {
34 | Robot robot = sandbox.getRobot();
35 | World world = sandbox.getWorld();
36 | Spawn spawn = new Spawn(robot.getX(), robot.getY(), robot.getFacing(), world.getSeed());
37 | add(spawn);
38 | }
39 |
40 | public boolean add(Spawn spawn) {
41 | spawns.add(spawn);
42 | notifyListeners(spawns.size()-1, spawns.size()-1);
43 | return true;
44 | }
45 |
46 | public Spawn remove(int index) {
47 | if (index < 0 || index >= spawns.size()) {
48 | return null;
49 | }
50 | Spawn ret = spawns.remove(index);
51 | notifyListeners(index, size());
52 | return ret;
53 | }
54 |
55 | public void clear() {
56 | int previousSize = spawns.size();
57 | spawns.clear();
58 | notifyListeners(0, previousSize-1);
59 | }
60 |
61 | public int getCombinedSeed() {
62 | int seed = 0;
63 |
64 | for (Spawn spawn : spawns) {
65 | seed += spawn.seed;
66 | }
67 | return seed;
68 | }
69 |
70 | public int size() {
71 | return spawns.size();
72 | }
73 |
74 | public int getSize() {
75 | return spawns.size();
76 | }
77 |
78 | public Spawn getElementAt(int index) {
79 | if (index < 0 || index >= size()) {
80 | return null;
81 | }
82 | return spawns.get(index);
83 | }
84 |
85 | private void notifyListeners(int index0, int index1) {
86 | for (ListDataListener l : listeners) {
87 | // Yeah, lazy, I know.
88 | l.contentsChanged(new ListDataEvent(this,
89 | ListDataEvent.CONTENTS_CHANGED, index0, index1));
90 | }
91 | sandbox.redraw();
92 | }
93 |
94 | @Override
95 | public void addListDataListener(ListDataListener l) {
96 | listeners.add(l);
97 | }
98 |
99 | @Override
100 | public void removeListDataListener(ListDataListener l) {
101 | listeners.remove(l);
102 |
103 | }
104 | }
--------------------------------------------------------------------------------
/src/asmcup/evaluation/SpawnsWindow.java:
--------------------------------------------------------------------------------
1 | package asmcup.evaluation;
2 |
3 | import java.awt.Dimension;
4 | import java.io.IOException;
5 |
6 | import javax.imageio.ImageIO;
7 | import javax.swing.*;
8 |
9 | import asmcup.genetics.Spawn;
10 | import asmcup.sandbox.*;
11 |
12 | public class SpawnsWindow extends JFrame {
13 | protected final Sandbox sandbox;
14 | protected final Spawns spawns;
15 | protected final FrontPanel panel = new FrontPanel();
16 | protected JList spawnList;
17 |
18 | protected JButton addButton = new JButton("Add current");
19 | protected JButton deleteButton = new JButton("Delete");
20 | protected JButton applyButton = new JButton("Show");
21 | protected JButton clearButton = new JButton("Clear");
22 |
23 | public SpawnsWindow(Sandbox sandbox) throws IOException {
24 | this.sandbox = sandbox;
25 | spawns = sandbox.spawns;
26 | //listModel = new SpawnListModel();
27 | spawnList = new JList(spawns);
28 | spawnList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
29 | spawnList.setLayoutOrientation(JList.VERTICAL);
30 | spawnList.setVisibleRowCount(-1);
31 | JScrollPane listScroller = new JScrollPane(spawnList);
32 | listScroller.setPreferredSize(new Dimension(420, 240));
33 |
34 | addButton.addActionListener(e -> addCurrent());
35 | deleteButton.addActionListener(e -> deleteOne());
36 | applyButton.addActionListener(e -> applyOne());
37 | clearButton.addActionListener(e -> clear());
38 |
39 | panel.addWideItem(listScroller);
40 | panel.addItems(addButton, deleteButton);
41 | panel.addItems(applyButton, clearButton);
42 |
43 | setContentPane(panel);
44 | setTitle("Spawn Manager");
45 | setIconImage(ImageIO.read(getClass().getResource("/plus.png")));
46 | setResizable(false);
47 | pack();
48 | }
49 |
50 | public void addCurrent() {
51 | spawns.addSpawnAtRobot();
52 | spawnList.setSelectedIndex(spawns.size() - 1);
53 | }
54 |
55 | public void deleteOne() {
56 | selectLastIfNone();
57 | int index = spawnList.getSelectedIndex();
58 | // Error handling happens in there. Can't trust JList apparently.
59 | spawns.remove(index);
60 | selectLastIfNone();
61 | }
62 |
63 | public void applyOne() {
64 | selectLastIfNone();
65 | Spawn spawn = spawnList.getSelectedValue();
66 | if (spawn != null) {
67 | sandbox.loadSpawn(spawn);
68 | }
69 | }
70 |
71 | public void selectLastIfNone() {
72 | if (spawnList.getSelectedValue() == null) {
73 | spawnList.setSelectedIndex(spawns.size() - 1);
74 | }
75 | }
76 |
77 | public void clear() {
78 | spawns.clear();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/asmcup/genetics/GAFrontPanel.java:
--------------------------------------------------------------------------------
1 | package asmcup.genetics;
2 |
3 | import javax.swing.*;
4 |
5 | import asmcup.sandbox.FrontPanel;
6 |
7 | public class GAFrontPanel extends FrontPanel {
8 | public final GeneticAlgorithm ga;
9 | protected JSpinner popSpinner = createSpinner(100, 1, 1000 * 1000);
10 | protected JSpinner mutationSpinner = createSpinner(100, 0, 100);
11 | protected JSpinner sizeSpinner = createSpinner(256, 1, 256);
12 | protected JSpinner chunkSpinner = createSpinner(4, 0, 256);
13 | protected JLabel bestLabel = new JLabel("0");
14 | protected JLabel worstLabel = new JLabel("0");
15 | protected JLabel genLabel = new JLabel("0");
16 | protected JLabel mutationLabel = new JLabel("0");
17 |
18 | public GAFrontPanel(GeneticAlgorithm ga) {
19 | this.ga = ga;
20 |
21 | setBorder(BorderFactory.createTitledBorder("Gene Pool"));
22 |
23 | addRow("Population:", popSpinner, "Number of robots that are kept in the gene pool");
24 | addRow("Mutation Chance:", mutationSpinner, "Maximum chance that mutation will occur during mating");
25 | addRow("Mutation Size:", chunkSpinner, "Maximum number of bytes that will be changed per mutation");
26 | addRow("Program Size:", sizeSpinner, "Number of bytes in the ROM that will be used");
27 | addRow("Best:", bestLabel, "Highest score in the gene pool");
28 | addRow("Worst:", worstLabel, "Lowest score in the gene pool");
29 | addRow("Mutation:", mutationLabel, "Current chance of mutation");
30 | addRow("Generation:", genLabel, "Current generation of gene pool");
31 | }
32 |
33 | public void update() {
34 | ga.maxMutationRate = getInt(mutationSpinner);
35 | ga.dnaLength = getInt(sizeSpinner);
36 | ga.mutationSize = getInt(chunkSpinner);
37 |
38 | //ga.resizePopulation(getInt(popSpinner));
39 | }
40 |
41 | public void updateStats() {
42 | worstLabel.setText(String.valueOf(ga.getWorstScore()));
43 | bestLabel.setText(String.valueOf(ga.getBestScore()));
44 | genLabel.setText(String.valueOf(ga.generation));
45 | mutationLabel.setText(String.valueOf(ga.mutationRate) + "%");
46 | }
47 |
48 | public int getPopulationSize() {
49 | return getInt(popSpinner);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/asmcup/genetics/Gene.java:
--------------------------------------------------------------------------------
1 | package asmcup.genetics;
2 |
3 | import java.util.Objects;
4 |
5 | public class Gene implements Comparable {
6 | public final byte[] dna;
7 | public final float score;
8 | public final int gen;
9 |
10 | public Gene(byte[] dna, int gen, float score) {
11 | this.dna = dna;
12 | this.gen = gen;
13 | this.score = score;
14 | }
15 |
16 | @Override
17 | public int compareTo(Gene other) {
18 | float d = score - other.score;
19 |
20 | if (d == 0) {
21 | return 0;
22 | } else if (d < 0) {
23 | return 1;
24 | }
25 |
26 | return -1;
27 | }
28 |
29 | @Override
30 | public boolean equals(Object obj) {
31 | return obj instanceof Gene && compareTo((Gene) obj) == 0;
32 | }
33 |
34 | @Override
35 | public int hashCode() {
36 | // explicit default implementation to hashCode the internal pointer
37 | return Objects.hashCode(this);
38 | }
39 | }
--------------------------------------------------------------------------------
/src/asmcup/genetics/GeneticAlgorithm.java:
--------------------------------------------------------------------------------
1 | package asmcup.genetics;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Random;
6 |
7 | import asmcup.evaluation.Evaluator;
8 |
9 | public class GeneticAlgorithm {
10 | public final ArrayList pinned;
11 | public final Random random;
12 | public final Evaluator evaluator;
13 | public Gene[] population;
14 | public int generation;
15 | public int mutationRate = 50;
16 | public int minMutationRate = 1;
17 | public int maxMutationRate = 100;
18 | public int mutationSize = 4;
19 | public int dnaLength = 256;
20 |
21 | // FIXME: Handle DNA length changes gracefully
22 | // TODO: Meaningful initial population?
23 |
24 | public GeneticAlgorithm(Evaluator evaluator) {
25 | this.evaluator = evaluator;
26 |
27 | random = new Random();
28 | population = new Gene[0];
29 | pinned = new ArrayList<>();
30 | }
31 |
32 | public Gene createGene(byte[] rom) {
33 | return new Gene(rom, generation, evaluator.score(rom));
34 | }
35 |
36 | public void initializePopulation(int populationSize) {
37 | population = new Gene[populationSize];
38 |
39 | for (int i=0; i < population.length; i++) {
40 | population[i] = randomGene();
41 | }
42 | }
43 |
44 | public void resizePopulation(int newSize) {
45 | Gene[] newPop = new Gene[newSize];
46 |
47 | for (int i=0; i < newSize; i++) {
48 | newPop[i] = randomGene();
49 | }
50 |
51 | population = newPop;
52 | }
53 |
54 | public void nextGeneration() {
55 | adjustMutationRate();
56 |
57 | int halfPoint = population.length / 2;
58 | int pin = pinned.size();
59 |
60 | for (int i=halfPoint; i < population.length; i++) {
61 | if (pin > 0) {
62 | pin--;
63 | population[i] = cross(pinned.get(pin), selectRandomGene());
64 | } else {
65 | population[i] = cross();
66 | }
67 | }
68 |
69 | Arrays.sort(population);
70 | generation++;
71 | }
72 |
73 | private byte randomByte() {
74 | return (byte)random.nextInt(256);
75 | }
76 |
77 | private Gene randomGene() {
78 | return createGene(randomDNA());
79 | }
80 |
81 | private byte[] randomDNA() {
82 | byte[] dna = new byte[256];
83 |
84 | for (int i = 0; i < dnaLength; i++) {
85 | dna[i] = randomByte();
86 | }
87 |
88 | return dna;
89 | }
90 |
91 | public Gene selectRandomGene() {
92 | int i = random.nextInt(population.length / 2);
93 | return population[i];
94 | }
95 |
96 | public Gene cross() {
97 | int a, b;
98 |
99 | do {
100 | a = random.nextInt(population.length / 2);
101 | b = random.nextInt(population.length / 2);
102 | } while (a == b);
103 |
104 | return cross(population[a], population[b]);
105 | }
106 |
107 | public Gene cross(Gene mom, Gene dad) {
108 | byte[] dna = mom.dna.clone();
109 |
110 | switch (random.nextInt(2)) {
111 | case 0:
112 | crossTwoPoint(mom, dad, dna);
113 | break;
114 | case 1:
115 | default:
116 | crossUniform(mom, dad, dna);
117 | break;
118 | }
119 |
120 | if (random.nextInt(100) <= mutationRate) {
121 | mutate(dna);
122 | }
123 |
124 | return createGene(dna);
125 | }
126 |
127 | protected void crossTwoPoint(Gene mom, Gene dad, byte[] dna) {
128 | int dest, src, size;
129 |
130 | src = random.nextInt(dnaLength);
131 | dest = random.nextInt(dnaLength);
132 | size = 1 + random.nextInt(dnaLength);
133 |
134 | for (int i = 0; i < size; i++) {
135 | dna[(dest + i) % dnaLength] = dad.dna[(src + i) % dnaLength];
136 | }
137 | }
138 |
139 | protected void crossUniform(Gene mom, Gene dad, byte[] dna) {
140 | for (int i=0; i < dnaLength; i++) {
141 | if (random.nextBoolean()) {
142 | dna[i] = dad.dna[i];
143 | }
144 | }
145 | }
146 |
147 | protected void mutate(byte[] dna) {
148 | int dest = random.nextInt(dnaLength);
149 | int size = 1 + random.nextInt(mutationSize);
150 | int gap = 1 + random.nextInt(mutationSize);
151 |
152 | for (int i=0; i < size; i += gap) {
153 | dna[(dest + i) % dnaLength] = randomByte();
154 | }
155 | }
156 |
157 | public byte[] getBestDNA() {
158 | return population[0].dna.clone();
159 | }
160 |
161 | public float getBestScore() {
162 | return population[0].score;
163 | }
164 |
165 | public float getWorstScore() {
166 | return population[population.length / 2 - 1].score;
167 | }
168 |
169 | public void adjustMutationRate() {
170 | float p = getWorstScore() / getBestScore();
171 | // TODO: That's the laziest lerp I've ever seen...
172 | mutationRate = minMutationRate + (int)(p * maxMutationRate);
173 | mutationRate = Math.max(minMutationRate, mutationRate);
174 | mutationRate = Math.min(maxMutationRate, mutationRate);
175 | }
176 |
177 | public void pin() {
178 | pinned.add(population[0]);
179 | }
180 |
181 | public void clearPinned() {
182 | pinned.clear();
183 | }
184 |
185 | public void pin(byte[] dna) {
186 | pinned.add(createGene(dna));
187 | }
188 | }
--------------------------------------------------------------------------------
/src/asmcup/genetics/Genetics.java:
--------------------------------------------------------------------------------
1 | package asmcup.genetics;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.imageio.ImageIO;
6 | import javax.swing.*;
7 |
8 | import asmcup.evaluation.EvaluatorFrontPanel;
9 | import asmcup.evaluation.SpawnEvaluator;
10 | import asmcup.sandbox.*;
11 |
12 | public class Genetics extends JFrame {
13 | public final Sandbox sandbox;
14 | public final GeneticsMenu menu;
15 | public final GeneticAlgorithm ga;
16 | public final SpawnEvaluator evaluator;
17 | public final FrontPanel panel = new FrontPanel();
18 | public final EvaluatorFrontPanel evalPanel;
19 | public final GAFrontPanel gaPanel;
20 | protected JButton startButton = new JButton("Start");
21 | protected JButton stopButton = new JButton("Stop");
22 | protected Thread thread;
23 | protected boolean running = false;
24 |
25 | public Genetics(Sandbox sandbox) throws IOException {
26 | this.sandbox = sandbox;
27 |
28 | menu = new GeneticsMenu(this);
29 | evaluator = new SpawnEvaluator(sandbox.spawns, false);
30 | ga = new GeneticAlgorithm(evaluator);
31 | evalPanel = new EvaluatorFrontPanel(evaluator);
32 | gaPanel = new GAFrontPanel(ga);
33 |
34 | panel.addWideItem(evalPanel);
35 | panel.addWideItem(gaPanel);
36 | panel.addItems(stopButton, startButton);
37 |
38 | startButton.addActionListener(e -> start());
39 | stopButton.addActionListener(e -> stop());
40 |
41 | // The order is important here!
42 | evalPanel.updateEvaluator();
43 | gaPanel.update();
44 |
45 | setTitle("Genetics");
46 | setResizable(false);
47 | setIconImage(ImageIO.read(getClass().getResource("/dna.png")));
48 | setContentPane(panel);
49 | pack();
50 | }
51 |
52 | public GeneticsMenu getMenu() {
53 | return menu;
54 | }
55 |
56 | public void start() {
57 | if (thread != null && thread.isAlive()) {
58 | return;
59 | }
60 |
61 | if (sandbox.spawns.size() <= 0) {
62 | sandbox.spawns.addSpawnAtRobot();
63 | }
64 |
65 | startButton.setEnabled(false);
66 | stopButton.setEnabled(true);
67 | evalPanel.setComponentsEnabled(false);
68 | gaPanel.setComponentsEnabled(false);
69 |
70 | evalPanel.updateEvaluator();
71 | gaPanel.update();
72 |
73 | thread = new Thread(new Runnable() {
74 | public void run() {
75 | ga.resizePopulation(gaPanel.getPopulationSize());
76 |
77 | while (running) {
78 | ga.nextGeneration();
79 | gaPanel.updateStats();
80 | }
81 | }
82 | });
83 |
84 | running = true;
85 | thread.start();
86 | }
87 |
88 | public void stop() {
89 | running = false;
90 | startButton.setEnabled(true);
91 | stopButton.setEnabled(false);
92 | evalPanel.setComponentsEnabled(true);
93 | gaPanel.setComponentsEnabled(true);
94 | }
95 |
96 | public void flash() {
97 | sandbox.loadROM(ga.getBestDNA());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/asmcup/genetics/GeneticsMenu.java:
--------------------------------------------------------------------------------
1 | package asmcup.genetics;
2 |
3 | import java.awt.event.*;
4 |
5 | import javax.swing.*;
6 |
7 | public class GeneticsMenu extends JMenu {
8 | public final Genetics genetics;
9 |
10 | public GeneticsMenu(Genetics genetics) {
11 | super("Genetics");
12 | this.genetics = genetics;
13 |
14 | add("Flash Best", e -> genetics.flash());
15 | add("Pin Best", e -> genetics.ga.pin());
16 | add("Pin ROM", e -> genetics.ga.pin(genetics.sandbox.getROM()));
17 | addSeparator();
18 | add("Start Training", e -> genetics.start());
19 | add("Stop Training", e-> genetics.stop());
20 | addSeparator();
21 | add("Modify Parameters", e -> genetics.setVisible(true));
22 | addSeparator();
23 | add("Clear Pinned", e -> genetics.ga.clearPinned());
24 | }
25 |
26 | protected void add(String label, ActionListener listener) {
27 | JMenuItem item = new JMenuItem(label);
28 | item.addActionListener(listener);
29 | add(item);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/asmcup/genetics/Spawn.java:
--------------------------------------------------------------------------------
1 | package asmcup.genetics;
2 |
3 | import java.util.Random;
4 |
5 | import asmcup.runtime.World;
6 |
7 | public class Spawn {
8 | public final float x, y, facing;
9 | public final int seed;
10 |
11 | public Spawn(float x, float y, float facing, int seed) {
12 | this.x = x;
13 | this.y = y;
14 | this.facing = facing;
15 | this.seed = seed;
16 | }
17 |
18 | public World getNewWorld() {
19 | return new World(seed);
20 | }
21 |
22 | public static Spawn randomFromSeed(int seed) {
23 | Random random = new Random(seed);
24 | float sx = random.nextFloat() * World.SIZE;
25 | float sy = random.nextFloat() * World.SIZE;
26 | float facing = random.nextFloat() * (float)Math.PI * 2;
27 | World world = new World(seed);
28 |
29 | // Wiggle around until the start position is fair.
30 | // TODO ? Make this the job of world ("deterministic" random)?
31 | // TODO needs to check for an isSpawnable
32 |
33 | while (!world.canSpawnRobotAt(sx, sy)) {
34 | sx += (random.nextFloat() - 0.5f) * World.CELL_SIZE;
35 | sy += (random.nextFloat() - 0.5f) * World.CELL_SIZE;
36 | }
37 |
38 | return new Spawn(sx, sy, facing, seed);
39 | }
40 |
41 | @Override
42 | public String toString() {
43 | return "(" + x + ", " + y + " -> " + facing + ") in " + seed;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/Cell.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import java.util.*;
4 |
5 | public class Cell {
6 | protected final World world;
7 | protected final int cellX, cellY;
8 | protected final int[] tiles = new int[World.TILES_PER_CELL * World.TILES_PER_CELL];
9 | protected final ArrayList- items = new ArrayList<>();
10 |
11 | public Cell(World world, int cellX, int cellY) {
12 | this.world = world;
13 | this.cellX = cellX;
14 | this.cellY = cellY;
15 | }
16 |
17 | public void generate() {
18 | Generator gen = new Generator(world, this);
19 |
20 | if (cellX == 0 || cellY == 0 || cellX == World.CELL_COUNT || cellY == World.CELL_COUNT) {
21 | gen.square(gen.same(TILE.HAZARD, 3), 0, 0, World.TILES_PER_CELL);
22 | return;
23 | }
24 |
25 | gen.square(gen.variantRare(TILE.GROUND), 0, 0, World.TILES_PER_CELL);
26 |
27 | if (gen.chance(33)) {
28 | gen.room();
29 | } else {
30 | gen.openArea();
31 | }
32 | }
33 |
34 | public int getX() {
35 | return cellX;
36 | }
37 |
38 | public int getY() {
39 | return cellY;
40 | }
41 |
42 | public int getKey() {
43 | return key(cellX, cellY);
44 | }
45 |
46 | public static int key(int cellX, int cellY) {
47 | return clampCell(cellX) | (clampCell(cellY) << 16);
48 | }
49 |
50 | protected static int clampCell(int i) {
51 | return StrictMath.max(0, StrictMath.min(World.CELL_COUNT, i));
52 | }
53 |
54 | public void addItem(Item item) {
55 | if (item == null) {
56 | throw new NullPointerException();
57 | }
58 |
59 | items.add(item);
60 | }
61 |
62 | public Iterable
- getItems() {
63 | return items;
64 | }
65 |
66 | public Item getItem(float x, float y) {
67 | for (Item item : items) {
68 | if (item.withinDistance(x, y)) {
69 | return item;
70 | }
71 | }
72 |
73 | return null;
74 | }
75 |
76 | public void removeItem(Item item) {
77 | items.remove(item);
78 | }
79 |
80 | protected int getTile(int col, int row) {
81 | return tiles[clampTile(col) + (clampTile(row) * World.TILES_PER_CELL)];
82 | }
83 |
84 | private static int clampTile(int i) {
85 | return StrictMath.max(0, StrictMath.min(World.TILES_PER_CELL - 1, i));
86 | }
87 |
88 | public void setTile(int col, int row, int value) {
89 | if (col < 0 || row < 0) {
90 | throw new IllegalArgumentException("Tile coordinates cannot be negative");
91 | }
92 |
93 | if (col >= World.TILES_PER_CELL || row >= World.TILES_PER_CELL) {
94 | throw new IllegalArgumentException("Tile coordinates outside of bounds");
95 | }
96 |
97 | tiles[col + (row * World.TILES_PER_CELL)] = value;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/Generator.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import java.util.Random;
4 |
5 | public class Generator {
6 | protected final World world;
7 | protected final Cell cell;
8 | protected final Random random;
9 | protected int wpad, hpad;
10 | protected int width, height;
11 | protected int left, right, top, bottom;
12 | protected TileFunc wall;
13 | protected boolean room;
14 | protected int itemBonus;
15 |
16 | public Generator(World world, Cell cell) {
17 | this.world = world;
18 | this.cell = cell;
19 | this.random = new Random(world.getSeed() ^ cell.getKey());
20 | }
21 |
22 | public int nextInt(int bound) {
23 | return random.nextInt(bound);
24 | }
25 |
26 | public float nextFloat() {
27 | return random.nextFloat();
28 | }
29 |
30 | public int nextRare() {
31 | return chance(66) ? 0 : (1 + nextInt(3));
32 | }
33 |
34 | public boolean chance(int p) {
35 | return nextInt(100) < p;
36 | }
37 |
38 | public TileFunc same(int type) {
39 | int variant = nextInt(4) << 3;
40 | return (col, row) -> type | variant;
41 | }
42 |
43 | public TileFunc same(int type, int variant) {
44 | return (col, row) -> type | (variant << 3);
45 | }
46 |
47 | public TileFunc variant(int type) {
48 | return (col, row) -> type | (nextInt(4) << 3);
49 | }
50 |
51 | public TileFunc variantRare(int type) {
52 | return (col, row) -> type | (nextRare() << 3);
53 | }
54 |
55 | public void set(TileFunc f, int col, int row) {
56 | cell.setTile(col, row, f.tile(col, row));
57 | }
58 |
59 | public void hline(TileFunc f, int col, int row, int w) {
60 | for (int i=0; i < w; i++) {
61 | set(f, col + i, row);
62 | }
63 | }
64 |
65 | public void vline(TileFunc f, int col, int row, int h) {
66 | for (int i=0; i < h; i++) {
67 | set(f, col, row + i);
68 | }
69 | }
70 |
71 | public void rect(TileFunc f, int col, int row, int w, int h) {
72 | for (int r=0; r < h; r++) {
73 | for (int c=0; c < w; c++) {
74 | set(f, col + c, row + r);
75 | }
76 | }
77 | }
78 |
79 | public void square(TileFunc f, int col, int row, int s) {
80 | rect(f, col, row, s, s);
81 | }
82 |
83 | public void outline(TileFunc f, int col, int row, int w, int h) {
84 | hline(f, col, row, w);
85 | hline(f, col, row + h - 1, w);
86 | vline(f, col, row, h);
87 | vline(f, col + w - 1, row, h);
88 | }
89 |
90 | public void openArea() {
91 | room = false;
92 | hpad = 0;
93 | wpad = 0;
94 | width = World.TILES_PER_CELL;
95 | height = World.TILES_PER_CELL;
96 | left = 0;
97 | top = 0;
98 | right = width;
99 | bottom = height;
100 |
101 | int count = nextInt(15);
102 |
103 | for (int i = 0; i < count; i++) {
104 | int col = nextInt(World.TILES_PER_CELL);
105 | int row = nextInt(World.TILES_PER_CELL);
106 | int p = nextInt(100);
107 |
108 | if (p < 10) {
109 | hazards(col, row);
110 | } else if (p < 33) {
111 | rubble(col, row);
112 | } else {
113 | set(variant(TILE.OBSTACLE), col, row);
114 | }
115 | }
116 |
117 | items();
118 | }
119 |
120 | public void rubble(int col, int row) {
121 | int count = 1 + nextInt(20);
122 |
123 | for (int i=0; i < count; i++) {
124 | set(variant(TILE.WALL), col, row);
125 |
126 | if (chance(50)) {
127 | col = wiggle(col);
128 | } else {
129 | row = wiggle(row);
130 | }
131 | }
132 | }
133 |
134 | public void hazards(int col, int row) {
135 | int count = 3 + nextInt(15);
136 | int variant = nextInt(4);
137 |
138 | switch (variant) {
139 | case 0:
140 | count = 3 + nextInt(10);
141 | break;
142 | case 1:
143 | count = 2 + nextInt(5);
144 | break;
145 | case 2:
146 | count = 1 + nextInt(3);
147 | break;
148 | case 3:
149 | count = 1;
150 | break;
151 | }
152 |
153 | for (int i=0; i < count; i++) {
154 | set(variantRare(TILE.HAZARD), col, row);
155 | col = wiggle(col);
156 | row = wiggle(row);
157 | }
158 | }
159 |
160 | protected int wiggle(int x) {
161 | x += nextInt(3) - 1;
162 | x = StrictMath.min(x, World.TILES_PER_CELL - 1);
163 | x = StrictMath.max(x, 0);
164 | return x;
165 | }
166 |
167 | public void room() {
168 | wpad = 1 + nextInt(3);
169 | hpad = 1 + nextInt(3);
170 | width = World.TILES_PER_CELL - wpad * 2;
171 | width = StrictMath.max(5, width);
172 | height = World.TILES_PER_CELL - hpad * 2;
173 | height = StrictMath.max(5, height);
174 | left = wpad + 1;
175 | top = hpad + 1;
176 | right = left + width - 2;
177 | bottom = top + height - 2;
178 | room = true;
179 | itemBonus = 10;
180 |
181 | if (chance(80)) {
182 | wall = same(TILE.WALL);
183 | } else {
184 | wall = same(TILE.HAZARD);
185 | itemBonus += 3 * ((wall.tile(0, 0) >> 3) & 0b11);
186 | }
187 |
188 | rect(same(TILE.FLOOR), wpad, hpad, width, height);
189 | outline(wall, wpad, hpad, width, height);
190 | maze();
191 | exits();
192 | items();
193 | }
194 |
195 | public int roomCol(int spacing) {
196 | return wpad + 1 + spacing + nextInt(width - spacing * 2 - 2);
197 | }
198 |
199 | public int roomRow(int spacing) {
200 | return hpad + 1 + spacing + nextInt(height - spacing * 2 - 2);
201 | }
202 |
203 | public void maze() {
204 | if (chance(50)) {
205 | return;
206 | }
207 |
208 | if (chance(50)) {
209 | hmaze();
210 | } else {
211 | vmaze();
212 | }
213 | }
214 |
215 | public void hmaze() {
216 | TileFunc floor = same(TILE.FLOOR);
217 | int row = hpad + 2 + nextInt(3);
218 | int bottom = hpad + height - 2;
219 |
220 | while (row < bottom) {
221 | hline(wall, wpad, row, width);
222 | int t = 1 + nextInt(width - 3);
223 | set(floor, wpad + t, row);
224 | row += 2 + nextInt(5);
225 | }
226 | }
227 |
228 | public void vmaze() {
229 | TileFunc floor = same(TILE.FLOOR);
230 | int col = wpad + 2 + nextInt(3);
231 | int right = wpad + width - 2;
232 |
233 | while (col < right) {
234 | vline(wall, col, hpad, height);
235 | int t = 1 + nextInt(height - 3);
236 | set(floor, col, hpad + t);
237 | col += 2 + nextInt(5);
238 | }
239 | }
240 |
241 | public void exits() {
242 | int count = 1 + nextInt(4);
243 |
244 | for (int i=0; i < count; i++) {
245 | exit();
246 | }
247 | }
248 |
249 | public boolean exit() {
250 | int col, row;
251 |
252 | switch (nextInt(4)) {
253 | case 0:
254 | col = wpad + 1 + nextInt(width - 2);
255 | row = hpad;
256 | break;
257 | case 1:
258 | col = wpad;
259 | row = hpad + 1 + nextInt(height - 2);
260 | break;
261 | case 2:
262 | col = wpad + 1 + nextInt(width - 2);
263 | row = World.TILES_PER_CELL - 1 - hpad;
264 | break;
265 | default:
266 | col = World.TILES_PER_CELL - 1 - wpad;
267 | row = hpad + 1 + nextInt(height - 2);
268 | break;
269 | }
270 |
271 | if (isBlocked(col, row)) {
272 | return false;
273 | }
274 |
275 | if (chance(33)) {
276 | set(same(TILE.OBSTACLE, 2 + nextInt(2)), col, row);
277 | itemBonus += 5;
278 | } else {
279 | set(variant(TILE.GROUND), col, row);
280 | }
281 |
282 | return true;
283 | }
284 |
285 | public boolean isBlocked(int col, int row) {
286 | for (int i=-1; i < 2; i += 2) {
287 | if (isSolidRoom(col + i, row) || isSolidRoom(col, row + i)) {
288 | return true;
289 | }
290 | }
291 |
292 | return false;
293 | }
294 |
295 | public boolean isSolidRoom(int col, int row) {
296 | return isInsideRoom(col, row) && world.isSolid(col, row);
297 | }
298 |
299 | public boolean isInsideRoom(int col, int row) {
300 | return col >= left && row >= top && col < right && row < bottom;
301 | }
302 |
303 | public void items() {
304 | int count = 1 + nextInt(3 + itemBonus) + itemBonus / 2;
305 |
306 | for (int i = 0; i < count; i++) {
307 | Item item;
308 |
309 | switch (nextInt(2)) {
310 | case 0:
311 | item = gold();
312 | break;
313 | default:
314 | item = battery();
315 | break;
316 | }
317 |
318 | spawnItem(item);
319 | }
320 | }
321 |
322 | public void spawnItem(Item item) {
323 | int left = cell.getX() * World.CELL_SIZE + World.TILE_HALF;
324 | int top = cell.getY() * World.CELL_SIZE + World.TILE_HALF;
325 | int x = 0, y = 0;
326 |
327 | for (int i = 0; i < 20; i++) {
328 | x = left + roomCol(1) * World.TILE_SIZE;
329 | y = top + roomRow(1) * World.TILE_SIZE;
330 |
331 | if (world.checkTile(TILE.IS_SPAWNABLE, x, y)) {
332 | break;
333 | }
334 | }
335 |
336 | item.position(x, y);
337 | cell.addItem(item);
338 | }
339 |
340 | public Item.Gold gold() {
341 | return new Item.Gold();
342 | }
343 |
344 | public Item.Battery battery() {
345 | return new Item.Battery();
346 | }
347 |
348 | public static interface TileFunc {
349 | public int tile(int col, int row);
350 | }
351 | }
352 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/Item.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | public abstract class Item {
4 | protected float x, y;
5 |
6 | public float getX() {
7 | return x;
8 | }
9 |
10 | public float getY() {
11 | return y;
12 | }
13 |
14 | public void position(float x, float y) {
15 | this.x = x;
16 | this.y = y;
17 | }
18 |
19 | public boolean withinDistance(float tx, float ty) {
20 | float dx = tx - x;
21 | float dy = ty - y;
22 | return StrictMath.sqrt(dx * dx + dy * dy) <= 20;
23 | }
24 |
25 | public abstract void collect(Robot robot);
26 |
27 | public static class Battery extends Item {
28 | protected int value;
29 |
30 | public Battery() {
31 | this(DEFAULT_VALUE);
32 | }
33 |
34 | public Battery(int value) {
35 | this.value = value;
36 | }
37 |
38 | public void collect(Robot robot) {
39 | robot.addBattery(value * 100);
40 | }
41 |
42 | public int getVariant() {
43 | return value / 25;
44 | }
45 |
46 | public static final int DEFAULT_VALUE = 50;
47 | }
48 |
49 | public static class Gold extends Item {
50 | protected int value;
51 |
52 | public Gold() {
53 | this(DEFAULT_VALUE);
54 | }
55 |
56 | public Gold(int value) {
57 | this.value = value;
58 | }
59 |
60 | public int getValue() {
61 | return value;
62 | }
63 |
64 | public int getVariant() {
65 | return value / 25;
66 | }
67 |
68 | public void collect(Robot robot) {
69 | robot.addGold(value);
70 | }
71 |
72 | public static final int DEFAULT_VALUE = 50;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/Main.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import java.io.*;
4 | import java.nio.charset.Charset;
5 | import java.util.Base64;
6 |
7 | import asmcup.vm.VM;
8 |
9 | public class Main implements Recorder {
10 | protected World world;
11 | protected int frames;
12 | protected boolean recording;
13 |
14 | public static void main(String[] args) throws IOException {
15 | Main main = new Main();
16 |
17 | if (args.length > 1) {
18 | System.err.printf("USAGE: asmcup-runtime [file]%n");
19 | System.exit(1);
20 | return;
21 | }
22 |
23 | InputStream input;
24 |
25 | if (args.length > 0) {
26 | input = new FileInputStream(new File(args[0]));
27 | } else {
28 | input = System.in;
29 | }
30 |
31 | main.configure(input);
32 | main.run();
33 | }
34 |
35 | public void configure(InputStream input) throws IOException {
36 | InputStreamReader streamReader = new InputStreamReader(input, Charset.forName("US-ASCII"));
37 | BufferedReader reader = new BufferedReader(streamReader);
38 | String line;
39 |
40 | while ((line = reader.readLine()) != null) {
41 | configure(line);
42 | }
43 |
44 | reader.close();
45 | }
46 |
47 | public void configure(String line) {
48 | String command;
49 | String[] parts;
50 |
51 | line = line.trim();
52 |
53 | if (line.isEmpty()) {
54 | return;
55 | }
56 |
57 | parts = line.split("[\\s\\t]+");
58 |
59 | if (parts.length <= 0) {
60 | return;
61 | }
62 |
63 | switch (command = parts[0].toLowerCase()) {
64 | case "frames":
65 | setFrames(parts[1]);
66 | break;
67 | case "seed":
68 | setSeed(parts[1]);
69 | break;
70 | case "bot":
71 | addBot(parts[1], parts[2]);
72 | break;
73 | case "record":
74 | setRecording(parts[1]);
75 | break;
76 | default:
77 | throw new IllegalArgumentException("Unknown command " + command);
78 | }
79 | }
80 |
81 | public void setFrames(String count) {
82 | frames = Integer.parseInt(count);
83 | }
84 |
85 | public void setSeed(String seed) {
86 | world = new World(Integer.parseInt(seed));
87 | }
88 |
89 | public void setRecording(String enabled) {
90 | recording = Integer.parseInt(enabled) > 0;
91 | }
92 |
93 | public void addBot(String id, String encoded) {
94 | addBot(Integer.parseInt(id), encoded);
95 | }
96 |
97 | public void addBot(int id, String encoded) {
98 | byte[] rom = Base64.getDecoder().decode(encoded.trim());
99 | addBot(id, rom);
100 | }
101 |
102 | public void addBot(int id, byte[] rom) {
103 | Robot robot;
104 |
105 | if (recording) {
106 | robot = new RecordedRobot(this, id, rom);
107 | } else {
108 | robot = new Robot(id, new VM(rom));
109 | }
110 |
111 | world.addRobot(robot);
112 | }
113 |
114 | public void run() {
115 | for (int i=0; i < frames; i++) {
116 | world.tick();
117 | }
118 | }
119 |
120 | public void record(RecordedRobot robot, byte[] data) {
121 | String encoded = Base64.getEncoder().encodeToString(data);
122 | System.out.printf("io %d %d %s%n", world.getFrame(), robot.id, encoded);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/PlaybackRobot.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import java.util.HashMap;
4 |
5 | public class PlaybackRobot extends Robot {
6 | protected final PlaybackVM vm;
7 | protected final HashMap frames;
8 |
9 | public PlaybackRobot(int id, PlaybackVM vm) {
10 | super(id, vm);
11 | this.vm = vm;
12 | this.frames = new HashMap<>();
13 | }
14 |
15 | @Override
16 | public void tick(World world) {
17 | vm.data = frames.get(world.getFrame());
18 | super.tick(world);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/PlaybackVM.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import asmcup.vm.VM;
4 |
5 | public class PlaybackVM extends VM {
6 | protected byte[] data = {};
7 |
8 | @Override
9 | public void tick() {
10 | if (data == null) {
11 | setIO(false);
12 | return;
13 | }
14 |
15 | setIO(data.length > 0);
16 |
17 | for (int i=0 ; i < data.length; i++) {
18 | push8(data[i]);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/RecordedRobot.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | public class RecordedRobot extends Robot {
4 | protected final Recorder recorder;
5 | protected RecordedVM vm;
6 |
7 | public RecordedRobot(Recorder recorder, int id, byte[] rom) {
8 | this(recorder, id, new RecordedVM(rom));
9 | }
10 |
11 | public RecordedRobot(Recorder recorder, int id, RecordedVM vm) {
12 | super(id, vm);
13 | this.recorder = recorder;
14 | this.vm = vm;
15 | }
16 |
17 | @Override
18 | protected void handleIO(World world) {
19 | vm.trapIO = true;
20 | super.handleIO(world);
21 | vm.trapIO = false;
22 | }
23 |
24 | @Override
25 | public void tick(World world) {
26 | super.tick(world);
27 |
28 | if (vm.hasRecordedIO()) {
29 | recorder.record(this, vm.getRecordedIO());
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/asmcup/runtime/RecordedVM.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import java.io.ByteArrayOutputStream;
4 |
5 | import asmcup.vm.VM;
6 |
7 | public class RecordedVM extends VM {
8 | protected boolean trapIO;
9 | protected ByteArrayOutputStream output;
10 |
11 | public RecordedVM(byte[] rom) {
12 | super(rom);
13 | output = new ByteArrayOutputStream();
14 | }
15 |
16 | public boolean hasRecordedIO() {
17 | return output.size() > 0;
18 | }
19 |
20 | public byte[] getRecordedIO() {
21 | byte[] data = output.toByteArray();
22 | output.reset();
23 | return data;
24 | }
25 |
26 | @Override
27 | public int pop8() {
28 | int value = super.pop8();
29 |
30 | if (trapIO) {
31 | output.write(value);
32 | }
33 |
34 | return value;
35 | }
36 | }
--------------------------------------------------------------------------------
/src/asmcup/runtime/Recorder.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | public interface Recorder {
4 | public void record(RecordedRobot robot, byte[] data);
5 | }
6 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/Robot.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import asmcup.vm.VM;
4 |
5 | public class Robot {
6 | protected final int id;
7 | protected VM vm;
8 | protected float x, y;
9 | protected float facing;
10 | protected int overclock;
11 | protected int battery;
12 | protected float motor;
13 | protected float steer;
14 | protected float lazer;
15 | protected float lazerEnd;
16 | protected float lastX, lastY;
17 | protected float frequency;
18 | protected int gold;
19 | protected float sensor;
20 | protected float beamDirection;
21 | protected int sensorIgnore;
22 | protected int sensorFrame;
23 | protected boolean ramming;
24 | protected int lastValidIO, lastInvalidIO;
25 |
26 | public Robot(int id) {
27 | this(id, new VM());
28 | }
29 |
30 | public Robot(int id, byte[] rom) {
31 | this(id, new VM(rom.clone()));
32 | }
33 |
34 | public Robot(int id, VM vm) {
35 | this.id = id;
36 | this.vm = vm;
37 | this.battery = BATTERY_MAX;
38 | }
39 |
40 | public VM getVM() {
41 | return vm;
42 | }
43 |
44 | public float getX() {
45 | return x;
46 | }
47 |
48 | public float getY() {
49 | return y;
50 | }
51 |
52 | public int getColumn() {
53 | return (int)(x / World.TILE_SIZE);
54 | }
55 |
56 | public int getRow() {
57 | return (int)(y / World.TILE_SIZE);
58 | }
59 |
60 | public int getCellX() {
61 | return getColumn() / World.TILES_PER_CELL;
62 | }
63 |
64 | public int getCellY() {
65 | return getRow() / World.TILES_PER_CELL;
66 | }
67 |
68 | public int getCellKey() {
69 | return getCellX() | (getCellY() << 16);
70 | }
71 |
72 | public float getFacing() {
73 | return facing;
74 | }
75 |
76 | public float getMotor() {
77 | return motor;
78 | }
79 |
80 | public float getSteer() {
81 | return steer;
82 | }
83 |
84 | public int getBattery() {
85 | return battery;
86 | }
87 |
88 | public int getOverclock() {
89 | return overclock;
90 | }
91 |
92 | public float getSensor() {
93 | return sensor;
94 | }
95 |
96 | public float getBeamAngle() {
97 | return (float)(facing + beamDirection * StrictMath.PI / 2);
98 | }
99 |
100 | public int getSensorFrame() {
101 | return sensorFrame;
102 | }
103 |
104 | public float getLazer() {
105 | return lazer;
106 | }
107 |
108 | public float getLazerEnd() {
109 | return lazerEnd;
110 | }
111 |
112 | public void setMotor(float f) {
113 | motor = clampSafe(f, -1, 1);
114 | }
115 |
116 | public void setSteer(float f) {
117 | steer = clampSafe(f, -1, 1);
118 | }
119 |
120 | public void setLazer(float f) {
121 | lazer = clampSafe(f, 0, 1.0f);
122 | }
123 |
124 | public void setOverclock(int v) {
125 | overclock = StrictMath.min(100, StrictMath.max(0, v));
126 | }
127 |
128 | public void setFacing(float facing) {
129 | this.facing = facing;
130 | }
131 |
132 | public void position(float x, float y) {
133 | this.x = x;
134 | this.y = y;
135 | lastX = x;
136 | lastY = y;
137 | }
138 |
139 | public boolean isRamming() {
140 | return ramming;
141 | }
142 |
143 | public int getLastIO() {
144 | return lastValidIO;
145 | }
146 |
147 | public int getLastInvalidIO() {
148 | return lastInvalidIO;
149 | }
150 |
151 | public void kill() {
152 | battery = 0;
153 | }
154 |
155 | public void damage(int dmg) {
156 | if (dmg < 0) {
157 | throw new IllegalArgumentException("Damage cannot be negative");
158 | }
159 |
160 | battery -= dmg;
161 | }
162 |
163 | public void addBattery(int charge) {
164 | if (charge < 0) {
165 | throw new IllegalArgumentException("Recharge amount cannot be negative");
166 | }
167 |
168 | battery += charge;
169 | }
170 |
171 | public boolean isDead() {
172 | return battery <= 0;
173 | }
174 |
175 | public int getGold() {
176 | return gold;
177 | }
178 |
179 | public void addGold(int g) {
180 | if (g < 0) {
181 | throw
182 | new IllegalArgumentException("Gold amount cannot be negetive");
183 | }
184 |
185 | gold += g;
186 | }
187 |
188 | public void tick(World world) {
189 | tickSoftware(world);
190 | tickHardware(world);
191 | }
192 |
193 | protected void tickSoftware(World world) {
194 | int cyclesUsed = 0;
195 |
196 | while (cyclesUsed <= overclock) {
197 | vm.tick();
198 | handleIO(world);
199 | cyclesUsed++;
200 | battery--;
201 | }
202 | }
203 |
204 | protected void tickHardware(World world) {
205 | tickSteer(world);
206 | tickMotor(world);
207 | tickLazer(world);
208 | }
209 |
210 | protected void tickSteer(World world) {
211 | if (StrictMath.abs(steer) <= 0.01f) {
212 | steer = 0.0f;
213 | }
214 |
215 | facing += steer * STEER_RATE;
216 | }
217 |
218 | protected void tickMotor(World world) {
219 | float s;
220 |
221 | if (StrictMath.abs(motor) <= 0.01f) {
222 | motor = 0.0f;
223 | return;
224 | }
225 |
226 | if (motor < 0) {
227 | s = motor * 0.5f * SPEED_MAX;
228 | } else {
229 | s = motor * SPEED_MAX;
230 | }
231 |
232 | float tx = x + (float)StrictMath.cos(facing) * s;
233 | float ty = y + (float)StrictMath.sin(facing) * s;
234 |
235 | if (world.canRobotGoTo(tx, ty)) {
236 | x = tx;
237 | y = ty;
238 | ramming = true;
239 | } else if (world.canRobotGoTo(tx, y)) {
240 | x = tx;
241 | ramming = true;
242 | } else if (world.canRobotGoTo(x, ty)) {
243 | y = ty;
244 | ramming = true;
245 | } else {
246 | ramming = false;
247 | }
248 | }
249 |
250 | protected void tickLazer(World world) {
251 | if (lazer <= 0) {
252 | lazerEnd = 0;
253 | return;
254 | }
255 |
256 | float cos = (float)StrictMath.cos(getBeamAngle());
257 | float sin = (float)StrictMath.sin(getBeamAngle());
258 |
259 | for (int i=0; i < RAY_STEPS; i++) {
260 | if ((i * RAY_INTERVAL) >= (lazer * LAZER_RANGE)) {
261 | lazerEnd = lazer * LAZER_RANGE;
262 | return;
263 | }
264 |
265 | battery -= LAZER_BATTERY_COST;
266 |
267 | float tx = x + cos * (i * RAY_INTERVAL);
268 | float ty = y + sin * (i * RAY_INTERVAL);
269 | int tile = world.getTileXY(tx, ty);
270 | int type = tile & 0b111;
271 | int variant = (tile >> 3) & 0b11;
272 |
273 | if (type == TILE.WALL) {
274 | lazerEnd = i * RAY_INTERVAL;
275 | return;
276 | }
277 |
278 | if (type == TILE.OBSTACLE) {
279 | if (variant >= 2) {
280 | world.setTileXY(tx, ty, TILE.GROUND);
281 | }
282 |
283 | lazerEnd = i * RAY_INTERVAL;
284 | return;
285 | }
286 |
287 | Robot robot = world.getRobot(tx, ty);
288 | if (robot != null && robot != this) {
289 | robot.damage(LAZER_DAMAGE);
290 | return;
291 | }
292 | }
293 |
294 | lazerEnd = RAY_INTERVAL * RAY_STEPS;
295 | }
296 |
297 | protected void handleIO(World world) {
298 | if (!vm.checkIO()) {
299 | return;
300 | }
301 |
302 | int offset, value;
303 |
304 | value = vm.pop8();
305 |
306 | switch (value) {
307 | case IO_MOTOR:
308 | motor = popFloatSafe(-1.0f, 1.0f);
309 | break;
310 | case IO_STEER:
311 | steer = popFloatSafe(-1.0f, 1.0f);
312 | break;
313 | case IO_SENSOR:
314 | sensorRay(world);
315 | break;
316 | case IO_SENSOR_CONFIG:
317 | sensorIgnore = vm.pop8();
318 | break;
319 | case IO_OVERCLOCK:
320 | setOverclock(vm.pop8());
321 | break;
322 | case IO_LAZER:
323 | lazer = popFloatSafe(0.0f, 1.0f);
324 | break;
325 | case IO_BATTERY:
326 | vm.pushFloat((float)battery / BATTERY_MAX);
327 | break;
328 | case IO_MARK:
329 | value = vm.pop8();
330 | offset = vm.pop8();
331 | world.mark(this, offset, value);
332 | break;
333 | case IO_MARK_READ:
334 | offset = vm.pop8();
335 | value = world.markRead(this, offset);
336 | vm.push8(value);
337 | break;
338 | case IO_ACCELEROMETER:
339 | vm.pushFloat(x - lastX);
340 | vm.pushFloat(y - lastY);
341 | lastX = x;
342 | lastY = y;
343 | break;
344 | case IO_RADIO:
345 | frequency = popFloatSafe(-FREQUENCY_MAX, FREQUENCY_MAX);
346 | break;
347 | case IO_SEND:
348 | world.send(this, frequency, vm.pop8());
349 | break;
350 | case IO_RECV:
351 | vm.push8(world.recv(this, frequency));
352 | break;
353 | case IO_COMPASS:
354 | vm.pushFloat(floatModPositive(facing, (float)(StrictMath.PI * 2)));
355 | break;
356 | case IO_BEAM_DIRECTION:
357 | beamDirection = popFloatSafe(-1.0f, 1.0f);
358 | break;
359 | default:
360 | lastInvalidIO = world.getFrame();
361 | return;
362 | }
363 |
364 | lastValidIO = world.getFrame();
365 | }
366 |
367 | protected void sensorRay(World world) {
368 | float cos = (float)StrictMath.cos(getBeamAngle());
369 | float sin = (float)StrictMath.sin(getBeamAngle());
370 | sensorFrame = world.getFrame();
371 |
372 | for (int i = 0; i < RAY_STEPS; i++) {
373 | float sx = x + (cos * i * RAY_INTERVAL);
374 | float sy = y + (sin * i * RAY_INTERVAL);
375 | int hit = sensorPoint(world, sx, sy);
376 |
377 | if (hit != 0) {
378 | sensor = i * RAY_INTERVAL;
379 | vm.pushFloat(sensor);
380 | vm.push8(hit);
381 | return;
382 | }
383 | }
384 |
385 | sensor = RAY_RANGE;
386 | vm.pushFloat(sensor);
387 | vm.push8(0);
388 | }
389 |
390 | protected int sensorPoint(World world, float sx, float sy) {
391 | int tileVariation = world.getTileXY(sx, sy) & TILE.VARIATION_BITS;
392 | // In the tile, variation is stored in the 4th and 5th bit.
393 | // We need it at the 7th and 8th bit.
394 | tileVariation = tileVariation << 3;
395 |
396 | if (world.checkTile(TILE.IS_WALL, sx, sy)) {
397 | if ((sensorIgnore & SENSOR_WALL) == 0) {
398 | return SENSOR_WALL | tileVariation;
399 | } else {
400 | return 0;
401 | }
402 | }
403 |
404 | if ((sensorIgnore & SENSOR_HAZARD) == 0) {
405 | if (world.checkTile(TILE.IS_HAZARD, sx, sy)) {
406 | return SENSOR_HAZARD | tileVariation;
407 | }
408 | }
409 |
410 | if ((sensorIgnore & SENSOR_OBSTACLE) == 0) {
411 | if (world.checkTile(TILE.IS_OBSTACLE, sx, sy)) {
412 | return SENSOR_OBSTACLE | tileVariation;
413 | }
414 | }
415 |
416 | Item item = world.getItem(sx, sy);
417 |
418 | if ((sensorIgnore & SENSOR_GOLD) == 0) {
419 | if (item instanceof Item.Gold) {
420 | return SENSOR_GOLD;
421 | }
422 | }
423 |
424 | if ((sensorIgnore & SENSOR_BATTERY) == 0) {
425 | if (item instanceof Item.Battery) {
426 | return SENSOR_BATTERY;
427 | }
428 | }
429 |
430 | Robot robot = world.getRobot(sx, sy);
431 |
432 | if ((sensorIgnore & SENSOR_ROBOT) == 0) {
433 | if (robot != null && robot != this) {
434 | // TODO: Add alliance once implemented!
435 | return SENSOR_ROBOT;
436 | }
437 | }
438 |
439 | return 0;
440 | }
441 |
442 | protected float popFloatSafe(float min, float max) {
443 | return clampSafe(vm.popFloat(), min, max);
444 | }
445 |
446 | protected static float clampSafe(float f, float min, float max) {
447 | if (f > max) {
448 | return max;
449 | } else if (f < min) {
450 | return min;
451 | } else if (Float.isNaN(f)) {
452 | return 0;
453 | }
454 |
455 | return f;
456 | }
457 |
458 | protected static float floatModPositive(float dividend, float divisor) {
459 | return ((dividend % divisor) + divisor) % divisor;
460 | }
461 |
462 | public static final int IO_SENSOR = 0;
463 | public static final int IO_MOTOR = 1;
464 | public static final int IO_STEER = 2;
465 | public static final int IO_OVERCLOCK = 3;
466 | public static final int IO_LAZER = 4;
467 | public static final int IO_LASER = 4;
468 | public static final int IO_BATTERY = 5;
469 | public static final int IO_MARK = 6;
470 | public static final int IO_MARK_READ = 7;
471 | public static final int IO_ACCELEROMETER = 8;
472 | public static final int IO_RADIO = 9;
473 | public static final int IO_SEND = 10;
474 | public static final int IO_RECV = 11;
475 | public static final int IO_RECEIVE = 11;
476 | public static final int IO_SENSOR_CONFIG = 12;
477 | public static final int IO_COMPASS = 13;
478 | public static final int IO_BEAM_DIRECTION = 14;
479 |
480 | public static final float SPEED_MAX = 8;
481 | public static final float STEER_RATE = (float)(StrictMath.PI * 0.1);
482 | public static final int BATTERY_MAX = 60 * 60 * 24;
483 | public static final int OVERCLOCK_MAX = 100;
484 | public static final float FREQUENCY_MAX = 1000 * 10;
485 | public static final int LAZER_RANGE = 100;
486 | public static final int LAZER_BATTERY_COST = 4;
487 | public static final int LAZER_DAMAGE = 1024;
488 |
489 | public static final int SENSOR_WALL = 1;
490 | public static final int SENSOR_HAZARD = 2;
491 | public static final int SENSOR_GOLD = 4;
492 | public static final int SENSOR_BATTERY = 8;
493 | public static final int SENSOR_OBSTACLE = 16;
494 | public static final int SENSOR_ROBOT = 32;
495 |
496 | public static final int RAY_INTERVAL = 4;
497 | public static final int RAY_STEPS = 64;
498 | public static final int RAY_RANGE = RAY_INTERVAL * RAY_STEPS;
499 |
500 | public static final int COLLIDE_RANGE = 10;
501 | }
502 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/TILE.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | // This class houses constants related to tiles as well as tile classification
4 | // functionality.
5 | // Please be aware that actual tiles in the code are represented as int.
6 | // This class cannot (and should not) be instantiated.
7 | public enum TILE {; // Courtesy of http://stackoverflow.com/a/9618724
8 |
9 | public static final int GROUND = 0;
10 | public static final int HAZARD = 1;
11 | public static final int WALL = 2;
12 | public static final int OBSTACLE = 3;
13 | public static final int FLOOR = 4;
14 |
15 | public static final int TYPE_BITS = 0b111;
16 | public static final int VARIATION_BITS = 0b11000;
17 |
18 | public interface TileProperty {
19 | public boolean presentIn(int tile);
20 | }
21 |
22 | public static final TileProperty IS_GROUND = isType(GROUND);
23 | public static final TileProperty IS_HAZARD = isType(HAZARD);
24 | public static final TileProperty IS_WALL = isType(WALL);
25 | public static final TileProperty IS_OBSTACLE = isType(OBSTACLE);
26 | public static final TileProperty IS_FLOOR = isType(FLOOR);
27 |
28 | public static TileProperty isType(int type) {
29 | return (int tile) -> (tile & TYPE_BITS) == type;
30 | }
31 |
32 | public static final TileProperty IS_SOLID = isSolid();
33 | public static final TileProperty IS_SPAWNABLE = isSpawnable();
34 | public static final TileProperty IS_UNSPAWNABLE = not(IS_SPAWNABLE);
35 |
36 | public static TileProperty isSolid() {
37 | return (int tile) -> {
38 | switch (tile & TYPE_BITS) {
39 | case WALL:
40 | case OBSTACLE:
41 | return true;
42 | }
43 | return false;
44 | };
45 | }
46 |
47 | public static TileProperty isSpawnable() {
48 | return (int tile) -> {
49 | switch (tile & TYPE_BITS) {
50 | case HAZARD:
51 | case WALL:
52 | case OBSTACLE:
53 | return false;
54 | }
55 | return true;
56 | };
57 | }
58 |
59 | public static TileProperty not(TileProperty prop) {
60 | return (int tile) -> !prop.presentIn(tile);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/asmcup/runtime/World.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import java.util.*;
4 |
5 | public class World {
6 | protected final ArrayList robots;
7 | protected final HashMap cells;
8 | protected final HashMap tileData;
9 | protected final int seed;
10 | protected int frame;
11 |
12 | private static final Random random = new Random();
13 |
14 | public World() {
15 | this(random.nextInt());
16 | }
17 |
18 | public World(int seed) {
19 | this.robots = new ArrayList<>();
20 | this.cells = new HashMap<>();
21 | this.tileData = new HashMap<>();
22 | this.seed = seed;
23 | this.frame = 0;
24 | }
25 |
26 | public Iterable getRobots() {
27 | return robots;
28 | }
29 |
30 | public int getSeed() {
31 | return seed;
32 | }
33 |
34 | public int getFrame() {
35 | return frame;
36 | }
37 |
38 | public void addRobot(Robot robot) {
39 | robots.add(robot);
40 | }
41 |
42 | public void removeRobot(Robot robot) {
43 | robots.remove(robot);
44 | }
45 |
46 | public Cell getCell(int cellCol, int cellRow) {
47 | int key = Cell.key(cellCol, cellRow);
48 | Cell cell = cells.get(key);
49 |
50 | if (cell == null) {
51 | cell = new Cell(this, cellCol, cellRow);
52 | cells.put(key, cell);
53 | cell.generate();
54 | }
55 |
56 | return cell;
57 | }
58 |
59 | public Cell getCellXY(float x, float y) {
60 | return getCell((int)(x / CELL_SIZE), (int)(y / CELL_SIZE));
61 | }
62 |
63 | public int getTile(int tileCol, int tileRow) {
64 | int cellCol = tileCol / TILES_PER_CELL;
65 | int cellRow = tileRow / TILES_PER_CELL;
66 | Cell cell = getCell(cellCol, cellRow);
67 | return cell.getTile(tileCol - cellCol * TILES_PER_CELL,
68 | tileRow - cellRow * TILES_PER_CELL);
69 | }
70 |
71 | public int getTileXY(float x, float y) {
72 | if (x < 0 || y < 0 || x > SIZE || y > SIZE) {
73 | return TILE.WALL;
74 | }
75 | return getTile((int)(x / TILE_SIZE), (int)(y / TILE_SIZE));
76 | }
77 |
78 | public boolean checkTile(TILE.TileProperty prop, float x, float y) {
79 | int tile = getTileXY(x, y);
80 | return prop.presentIn(tile);
81 | }
82 |
83 | public boolean isSolid(float x, float y) {
84 | return checkTile(TILE.IS_SOLID, x, y);
85 | }
86 |
87 | public boolean isHazard(float x, float y) {
88 | return checkTile(TILE.IS_HAZARD, x, y);
89 | }
90 |
91 | public boolean isObstacle(float x, float y) {
92 | return checkTile(TILE.IS_OBSTACLE, x, y);
93 | }
94 |
95 | public boolean checkTileNear(TILE.TileProperty prop, float x, float y, float r) {
96 | return checkTile(prop, x, y)
97 | || checkTile(prop, x - r, y - r) || checkTile(prop, x + r, y + r)
98 | || checkTile(prop, x - r, y + r) || checkTile(prop, x + r, y - r);
99 | }
100 |
101 | public boolean isSolidNear(float x, float y, float r) {
102 | return checkTileNear(TILE.IS_SOLID, x, y, r);
103 | }
104 |
105 | public boolean isUnspawnableNear(float x, float y, float r) {
106 | return checkTileNear(TILE.IS_UNSPAWNABLE, x, y, r);
107 | }
108 |
109 | public boolean canRobotGoTo(float x, float y) {
110 | return !isSolidNear(x, y, Robot.COLLIDE_RANGE);
111 | }
112 |
113 | public boolean canSpawnRobotAt(float x, float y) {
114 | return !isUnspawnableNear(x, y, Robot.COLLIDE_RANGE);
115 | }
116 |
117 | public int getHazard(float x, float y) {
118 | int tile = getTileXY(x, y);
119 |
120 | if ((tile & 0b111) != TILE.HAZARD) {
121 | return -1;
122 | }
123 |
124 | return tile >> 3;
125 | }
126 |
127 | public void setTileXY(float x, float y, int value) {
128 | Cell cell = getCellXY(x, y);
129 | int col = (int)(x / TILE_SIZE - cell.getX() * TILES_PER_CELL);
130 | int row = (int)(y / TILE_SIZE - cell.getY() * TILES_PER_CELL);
131 |
132 | col = StrictMath.max(col, 0);
133 | row = StrictMath.max(row, 0);
134 | col = StrictMath.min(col, TILES_PER_CELL * CELL_COUNT - 1);
135 | row = StrictMath.min(row, TILES_PER_CELL * CELL_COUNT - 1);
136 |
137 | cell.setTile(col, row, value);
138 | }
139 |
140 | public void randomizePosition(Robot robot)
141 | {
142 | int x, y;
143 | do {
144 | x = (int)(StrictMath.random() * SIZE);
145 | y = (int)(StrictMath.random() * SIZE);
146 | } while (!canSpawnRobotAt(x, y));
147 | robot.position(x, y);
148 | }
149 |
150 | public void tick() {
151 | for (Robot robot : robots) {
152 | robot.tick(this);
153 | tickItems(robot);
154 | tickHazards(robot);
155 | }
156 |
157 | frame++;
158 | }
159 |
160 | protected void tickHazards(Robot robot) {
161 | switch (getHazard(robot.getX(), robot.getY())) {
162 | case 0:
163 | robot.damage(Robot.BATTERY_MAX / 100);
164 | break;
165 | case 1:
166 | robot.damage(Robot.BATTERY_MAX / 75);
167 | break;
168 | case 2:
169 | robot.damage(Robot.BATTERY_MAX / 50);
170 | break;
171 | case 3:
172 | robot.kill();
173 | break;
174 | }
175 | }
176 |
177 | public Item getItem(float x, float y) {
178 | return getCellXY(x, y).getItem(x, y);
179 | }
180 |
181 | public void addItem(float x, float y, Item item) {
182 | item.position(x, y);
183 | getCellXY(x, y).addItem(item);
184 | }
185 |
186 | protected void tickItems(Robot robot) {
187 | Cell cell = getCellXY(robot.getX(), robot.getY());
188 | Item item = cell.getItem(robot.getX(), robot.getY());
189 |
190 | if (item == null) {
191 | return;
192 | }
193 |
194 | item.collect(robot);
195 | cell.removeItem(item);
196 | }
197 |
198 | public Robot getRobot(float x, float y) {
199 | for (Robot robot : robots) {
200 | if (Math.abs(robot.getX() - x) < Robot.COLLIDE_RANGE
201 | && Math.abs(robot.getY() - y) < Robot.COLLIDE_RANGE) {
202 | return robot;
203 | }
204 | }
205 | return null;
206 | }
207 |
208 | public void mark(Robot robot, int offset, int value) {
209 | int key = robot.getColumn() | (robot.getRow() << 16);
210 | byte[] data = tileData.get(key);
211 |
212 | if (data == null) {
213 | data = new byte[8];
214 | tileData.put(key, data);
215 | }
216 |
217 | data[offset & 0b11] = (byte)(value & 0xFF);
218 | }
219 |
220 | public int markRead(Robot robot, int offset) {
221 | int key = robot.getColumn() | (robot.getRow() << 16);
222 | byte[] data = tileData.get(key);
223 | return (data == null) ? 0 : data[offset & 0b11];
224 | }
225 |
226 | public void send(Robot robot, float frequency, int data) {
227 | // TODO send data on radio
228 | }
229 |
230 | public int recv(Robot robot, float frequency) {
231 | // TODO read data on radio
232 | return 0;
233 | }
234 |
235 | public static final int TILE_SIZE = 32;
236 | public static final int TILE_HALF = TILE_SIZE / 2;
237 | public static final int TILES_PER_CELL = 20;
238 | public static final int CELL_SIZE = TILES_PER_CELL * TILE_SIZE;
239 | public static final int CELL_COUNT = 0xFF;
240 | public static final int SIZE = TILE_SIZE * TILES_PER_CELL * (CELL_COUNT + 1);
241 | public static final int CENTER = SIZE / 2;
242 | }
243 |
--------------------------------------------------------------------------------
/src/asmcup/sandbox/Canvas.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.awt.*;
4 |
5 | import javax.swing.JComponent;
6 |
7 | public class Canvas extends JComponent {
8 | public final Sandbox sandbox;
9 | public final CanvasMenu menu;
10 |
11 | public Canvas(Sandbox sandbox) {
12 | this.sandbox = sandbox;
13 | this.menu = new CanvasMenu(this);
14 |
15 | setPreferredSize(new Dimension(Sandbox.WIDTH, Sandbox.HEIGHT));
16 | setComponentPopupMenu(menu);
17 | }
18 |
19 | @Override
20 | protected void paintComponent(Graphics g) {
21 | Image backBuffer = sandbox.getBackBuffer();
22 |
23 | if (backBuffer == null) {
24 | return;
25 | }
26 |
27 | synchronized (backBuffer) {
28 | g.drawImage(backBuffer, 0, 0, null);
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/asmcup/sandbox/CanvasMenu.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.awt.event.*;
4 |
5 | import javax.swing.*;
6 |
7 | import asmcup.runtime.*;
8 |
9 | public class CanvasMenu extends JPopupMenu {
10 | public final Sandbox sandbox;
11 | public final Canvas canvas;
12 |
13 | public CanvasMenu(Canvas canvas) {
14 | this.canvas = canvas;
15 | this.sandbox = canvas.sandbox;
16 |
17 | add("Teleport", e -> teleport(e));
18 | add("Flash & Teleport", e -> flashAndTeleport(e));
19 | addSeparator();
20 | add("Add Gold Item", e -> addGoldItem(e));
21 | add("Add Battery Item", e -> addBatteryItem(e));
22 | addSeparator();
23 | add("Add Genetics Spawn", e -> addGeneticSpawn(e));
24 | add("Add Genetics Reward", e -> addGeneticReward(e));
25 | }
26 |
27 | public void teleport(ActionEvent e) {
28 | Mouse mouse = sandbox.mouse;
29 | World world = sandbox.getWorld();
30 | Robot robot = sandbox.getRobot();
31 |
32 | synchronized (world) {
33 | robot.position(mouse.getWorldX(), mouse.getWorldY());
34 | sandbox.redraw();
35 | }
36 | }
37 |
38 | public void flashAndTeleport(ActionEvent e) {
39 | sandbox.flash();
40 | teleport(e);
41 | }
42 |
43 | public void addGoldItem(ActionEvent e) {
44 | Mouse mouse = sandbox.mouse;
45 | Item gold = new Item.Gold();
46 | sandbox.getWorld().addItem(mouse.getWorldX(), mouse.getWorldY(), gold);
47 | }
48 |
49 | public void addBatteryItem(ActionEvent e) {
50 | Mouse mouse = sandbox.mouse;
51 | Item battery = new Item.Battery();
52 | sandbox.getWorld().addItem(mouse.getWorldX(), mouse.getWorldY(), battery);
53 | }
54 |
55 | public void addGeneticSpawn(ActionEvent e) {
56 | sandbox.spawns.addSpawnAtMouse();
57 | }
58 |
59 | public void addGeneticReward(ActionEvent e) {
60 |
61 | }
62 |
63 | protected void add(String label, ActionListener listener) {
64 | JMenuItem item = new JMenuItem(label);
65 | item.addActionListener(listener);
66 | add(item);
67 | }
68 | }
--------------------------------------------------------------------------------
/src/asmcup/sandbox/CodeEditor.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.awt.*;
4 | import java.awt.datatransfer.*;
5 | import java.awt.dnd.*;
6 | import java.awt.event.*;
7 | import java.io.*;
8 | import java.util.List;
9 |
10 | import javax.imageio.ImageIO;
11 | import javax.swing.*;
12 | import javax.swing.border.BevelBorder;
13 | import javax.swing.text.PlainDocument;
14 |
15 | import asmcup.compiler.Compiler;
16 | import asmcup.decompiler.Decompiler;
17 |
18 | public class CodeEditor extends JFrame {
19 | protected final Sandbox sandbox;
20 | protected JEditorPane editor;
21 | protected JLabel statusLabel;
22 | protected Menu menu;
23 | protected byte[] ram = new byte[256];
24 | protected File currentFile;
25 |
26 | public CodeEditor(Sandbox sandbox) throws IOException {
27 | this.sandbox = sandbox;
28 | this.editor = new JEditorPane();
29 | this.statusLabel = new JLabel();
30 | this.menu = new Menu();
31 |
32 | JPanel statusBar = new JPanel();
33 | statusBar.add(statusLabel);
34 | statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));
35 |
36 | JPanel contentPanel = new JPanel();
37 | contentPanel.setLayout(new BorderLayout());
38 | contentPanel.add(new JScrollPane(editor), BorderLayout.CENTER);
39 | contentPanel.add(statusBar, BorderLayout.PAGE_END);
40 |
41 | setTitle("Code Editor");
42 | setSize(400, 400);
43 | setContentPane(contentPanel);
44 | setJMenuBar(menu);
45 | setIconImage(ImageIO.read(getClass().getResource("/notepad.png")));
46 |
47 | editor.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
48 | editor.getDocument().putProperty(PlainDocument.tabSizeAttribute, 2);
49 | new DefaultContextMenu(editor);
50 |
51 | editor.setDropTarget(new DropTarget() {
52 | public synchronized void drop(DropTargetDropEvent e) {
53 | try {
54 | dropFiles(e);
55 | } catch (Exception ex) {
56 | ex.printStackTrace();
57 | }
58 | }
59 | });
60 | }
61 |
62 | public void dropFiles(DropTargetDropEvent e) throws Exception {
63 | e.acceptDrop(DnDConstants.ACTION_COPY);
64 | Object obj = e.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
65 |
66 | @SuppressWarnings("unchecked")
67 | List droppedFiles = (List) obj;
68 |
69 | if (droppedFiles.size() != 0) {
70 | openFile(droppedFiles.get(0));
71 | }
72 | }
73 |
74 | public void openFile() {
75 | if (currentFile == null) {
76 | currentFile = findFileOpen();
77 | }
78 | if (currentFile == null) {
79 | return;
80 | }
81 |
82 | try {
83 | String text = Utils.readAsString(currentFile);
84 | editor.setText(text);
85 | } catch (Exception e) {
86 | e.printStackTrace();
87 | }
88 |
89 | setTitle(currentFile.getName() + " - Code Editor");
90 | }
91 |
92 | public void openFile(File file) {
93 | currentFile = file;
94 | openFile();
95 | }
96 |
97 | public void closeFile() {
98 | editor.setText("");
99 | setTitle("Code Editor");
100 | }
101 |
102 | public void closeEditor() {
103 | setVisible(false);
104 | }
105 |
106 | public boolean compile() {
107 | Compiler compiler = new Compiler();
108 | try {
109 | ram = compiler.compile(editor.getText());
110 | statusLabel.setText(String.format("Bytes used: %d", compiler.getBytesUsed()));
111 | } catch (Exception e) {
112 | e.printStackTrace();
113 | JOptionPane.showMessageDialog(this, e.getMessage());
114 | return false;
115 | }
116 |
117 | return true;
118 | }
119 |
120 | public void flash() {
121 | synchronized (sandbox.getWorld()) {
122 | sandbox.loadROM(ram.clone());
123 | }
124 | }
125 |
126 | public void compileAndFlash() {
127 | if (compile()) {
128 | flash();
129 | }
130 | }
131 |
132 | public void decompile() {
133 | ByteArrayOutputStream out = new ByteArrayOutputStream();
134 | try {
135 | Decompiler decompiler = new Decompiler(new PrintStream(out, false, "UTF-8"));
136 | decompiler.decompile(sandbox.getROM());
137 | editor.setText(out.toString("UTF-8"));
138 | } catch (UnsupportedEncodingException e) {
139 | sandbox.showError("Unsupported encoding...");
140 | }
141 | }
142 |
143 | public File findFileSave() {
144 | return Utils.findFileSave(sandbox.frame, "asm", "Source File (.asm)");
145 | }
146 |
147 | public File findFileOpen() {
148 | return Utils.findFileOpen(sandbox.frame, "asm", "Source File (.asm)");
149 | }
150 |
151 | public void saveFile() {
152 | if (currentFile == null) {
153 | currentFile = findFileSave();
154 | }
155 |
156 | save(currentFile);
157 | }
158 |
159 | public void save(File file) {
160 | if (file == null) {
161 | return;
162 | }
163 |
164 | try {
165 | Utils.write(file, editor.getText());
166 | } catch (IOException e) {
167 | e.printStackTrace();
168 | }
169 | }
170 |
171 | public void saveFileAs() {
172 | save(findFileSave());
173 | }
174 |
175 | protected class Menu extends JMenuBar {
176 | public Menu() {
177 | addFileMenu();
178 | addCompileMenu();
179 | }
180 |
181 | protected JMenuItem item(String label, ActionListener f,
182 | KeyStroke shortcut) {
183 | JMenuItem item = new JMenuItem();
184 | item.setAction(new AbstractAction(label) {
185 | public void actionPerformed(ActionEvent e) {
186 | f.actionPerformed(e);
187 | }
188 | });
189 | if (shortcut != null) {
190 | item.setAccelerator(shortcut);
191 | }
192 | return item;
193 | }
194 |
195 | protected void addFileMenu() {
196 | JMenu menu = new JMenu("File");
197 | menu.add(item("New Code", e -> closeFile(),
198 | KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK)));
199 | menu.add(item("Open Code...", e -> openFile(null),
200 | KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK)));
201 | menu.add(item("Reload Code", e -> openFile(),
202 | KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.CTRL_MASK)));
203 | menu.addSeparator();
204 | menu.add(item("Save Code", e -> saveFile(),
205 | KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK)));
206 | menu.add(item("Save Code As...", e -> saveFileAs(),
207 | KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK | ActionEvent.CTRL_MASK)));
208 | menu.addSeparator();
209 | menu.add(item("Close Editor", (e) -> closeEditor(),
210 | KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK)));
211 | add(menu);
212 | }
213 |
214 | protected void addCompileMenu() {
215 | JMenu menu = new JMenu("Tools");
216 | menu.add(item("Compile & Flash", e -> compileAndFlash(),
217 | KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.CTRL_MASK)));
218 | menu.add(item("Compile", e -> compile(), null));
219 | menu.add(item("Flash", e -> flash(), null));
220 | menu.addSeparator();
221 | menu.add(item("Decompile current ROM", e -> decompile(), null));
222 | add(menu);
223 | }
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/asmcup/sandbox/Debugger.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.awt.*;
4 | import java.awt.event.*;
5 | import java.io.IOException;
6 |
7 | import javax.imageio.ImageIO;
8 | import javax.swing.*;
9 |
10 | import asmcup.runtime.Robot;
11 |
12 | public class Debugger extends JFrame {
13 | protected final Sandbox sandbox;
14 |
15 | protected MemoryPane memPane;
16 | protected JScrollPane scrollPane;
17 |
18 | protected JSlider motorSlider, steerSlider, overclockSlider;
19 | protected JSlider lazerSlider;
20 | protected JProgressBar batteryBar, sensorBar;
21 | protected JLabel goldLabel;
22 | protected FrontPanel bottomPane;
23 |
24 | protected JPanel panel;
25 |
26 | volatile protected boolean updating;
27 |
28 | public Debugger(Sandbox sandbox) throws IOException {
29 | this.sandbox = sandbox;
30 |
31 | memPane = new MemoryPane();
32 | scrollPane = new JScrollPane(memPane);
33 | motorSlider = slider(-100, 100);
34 | steerSlider = slider(-100, 100);
35 | lazerSlider = slider(0, 100);
36 | overclockSlider = slider(0, 100);
37 | sensorBar = bar(0, 256);
38 | batteryBar = bar(0, Robot.BATTERY_MAX);
39 | goldLabel = new JLabel("0");
40 |
41 | panel = new JPanel(new BorderLayout());
42 | bottomPane = new FrontPanel();
43 | //bottomPane.minimizeLabels();
44 | bottomPane.addRow("Motor:", motorSlider);
45 | bottomPane.addRow("Steer:", steerSlider);
46 | bottomPane.addRow("Lazer:", lazerSlider);
47 | bottomPane.addRow("Clock:", overclockSlider);
48 | bottomPane.addRow("Battery:", batteryBar);
49 | bottomPane.addRow("Sensor:", sensorBar);
50 | bottomPane.addRow("Gold:", goldLabel);
51 | updating = false;
52 |
53 | panel.add(scrollPane, BorderLayout.CENTER);
54 | panel.add(bottomPane, BorderLayout.SOUTH);
55 |
56 | setTitle("Debugger");
57 | setIconImage(ImageIO.read(getClass().getResource("/debugger.png")));
58 | setResizable(false);
59 | setContentPane(panel);
60 | pack();
61 | }
62 |
63 | protected JSlider slider(int min, int max) {
64 | JSlider slider = new JSlider(min, max, 0);
65 | slider.addChangeListener((e) -> updateControls());
66 | return slider;
67 | }
68 |
69 | protected JProgressBar bar(int min, int max) {
70 | JProgressBar bar = new JProgressBar(min, max);
71 | bar.setStringPainted(true);
72 | return bar;
73 | }
74 |
75 | public void updateDebugger() {
76 | Robot robot = sandbox.getRobot();
77 | updating = true;
78 | motorSlider.setValue((int)(robot.getMotor() * 100));
79 | steerSlider.setValue((int)(robot.getSteer() * 100));
80 | lazerSlider.setValue((int)(robot.getLazer() * 100));
81 | overclockSlider.setValue(robot.getOverclock());
82 | sensorBar.setValue((int)robot.getSensor());
83 | batteryBar.setValue(robot.getBattery());
84 | goldLabel.setText(String.valueOf(robot.getGold()));
85 | updating = false;
86 | repaint();
87 | }
88 |
89 | public void updateControls() {
90 | if (updating) {
91 | return;
92 | }
93 | synchronized (sandbox.getWorld()) {
94 | Robot robot = sandbox.getRobot();
95 | robot.setMotor(motorSlider.getValue() / 100.0f);
96 | robot.setSteer(steerSlider.getValue() / 100.0f);
97 | robot.setLazer(lazerSlider.getValue() / 100.0f);
98 | robot.setOverclock(overclockSlider.getValue());
99 | }
100 | }
101 |
102 | protected class MemoryPane extends JComponent {
103 | protected Font font;
104 | protected int start, end;
105 | protected Mouse mouse = new Mouse();
106 |
107 | public MemoryPane() {
108 | font = new Font(Font.MONOSPACED, Font.PLAIN, 12);
109 | setPreferredSize(new Dimension(17 * 16, 16 * 16));
110 | addMouseListener(mouse);
111 | addMouseMotionListener(mouse);
112 | }
113 |
114 | public int transform(int x, int y) {
115 | int col = Math.max(0, (x - 16) / 16);
116 | int row = Math.max(0, y / 16);
117 | return Math.min(255, row * 16 + col);
118 | }
119 |
120 | public int transform(MouseEvent e) {
121 | return transform(e.getX(), e.getY());
122 | }
123 |
124 | public void select(int a, int b) {
125 | start = Math.min(a, b);
126 | end = Math.max(a, b);
127 | }
128 |
129 | @Override
130 | protected void paintComponent(Graphics g) {
131 | Rectangle c = g.getClipBounds();
132 | Robot robot = sandbox.getRobot();
133 |
134 | g.setColor(Color.WHITE);
135 | g.fillRect(c.x, c.y, c.width, c.height);
136 |
137 | g.setFont(font);
138 |
139 | for (int row=0; row < 16; row++) {
140 | int y = 12 + row * 16;
141 |
142 | g.setColor(Color.DARK_GRAY);
143 | g.fillRect(0, row * 16, 16, 16);
144 | g.setColor(Color.WHITE);
145 | g.drawString(String.format("%02x", row * 16), 0, y);
146 |
147 | for (int col=0; col < 16; col++) {
148 | int x = 16 + col * 16;
149 | int addr = row * 16 + col;
150 | int value = robot.getVM().read8(addr);
151 |
152 | if (addr >= start && addr <= end) {
153 | g.setColor(Color.BLACK);
154 | g.fillRect(x, y - 12, 16, 16);
155 | g.setColor(Color.WHITE);
156 | } else {
157 | g.setColor(Color.BLACK);
158 | }
159 |
160 | if (addr == robot.getVM().getProgramCounter()) {
161 | g.setColor(Color.RED);
162 | }
163 |
164 | g.drawString(String.format("%02x", value), x + 1, y);
165 |
166 | if (addr == robot.getVM().getStackPointer()) {
167 | g.setColor(Color.RED);
168 | g.drawRect(x, y - 12, 16, 16);
169 | }
170 | }
171 | }
172 | }
173 |
174 | protected class Mouse extends MouseAdapter {
175 | protected boolean dragging = false;
176 | protected int dragStart;
177 |
178 | @Override
179 | public void mousePressed(MouseEvent e) {
180 | switch (e.getButton()) {
181 | case MouseEvent.BUTTON1:
182 | dragStart = transform(e);
183 | dragging = true;
184 | repaint();
185 | break;
186 | }
187 | }
188 |
189 | @Override
190 | public void mouseReleased(MouseEvent e) {
191 | switch (e.getButton()) {
192 | case MouseEvent.BUTTON1:
193 | mouseDragged(e);
194 | dragging = false;
195 | break;
196 | }
197 | }
198 |
199 | @Override
200 | public void mouseDragged(MouseEvent e) {
201 | if (dragging) {
202 | select(dragStart, transform(e));
203 | repaint();
204 | }
205 | }
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/asmcup/sandbox/DefaultContextMenu.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import javax.swing.*;
4 | import javax.swing.text.JTextComponent;
5 | import javax.swing.undo.UndoManager;
6 | import java.awt.*;
7 | import java.awt.datatransfer.Clipboard;
8 | import java.awt.datatransfer.DataFlavor;
9 | import java.awt.event.KeyAdapter;
10 | import java.awt.event.KeyEvent;
11 | import java.awt.event.MouseAdapter;
12 | import java.awt.event.MouseEvent;
13 |
14 | public class DefaultContextMenu extends JPopupMenu
15 | {
16 | private Clipboard clipboard;
17 |
18 | private UndoManager undoManager;
19 |
20 | private JMenuItem undo;
21 | private JMenuItem redo;
22 | private JMenuItem cut;
23 | private JMenuItem copy;
24 | private JMenuItem paste;
25 | private JMenuItem delete;
26 | private JMenuItem selectAll;
27 |
28 | private JTextComponent jTextComponent;
29 |
30 | public DefaultContextMenu(JTextComponent jTextComponent)
31 | {
32 | setTextComponent(jTextComponent);
33 | undoManager = new UndoManager();
34 | clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
35 |
36 | undo = new JMenuItem("Undo");
37 | undo.setEnabled(false);
38 | undo.setAccelerator(KeyStroke.getKeyStroke("control Z"));
39 | undo.addActionListener(event -> undoManager.undo());
40 |
41 | add(undo);
42 |
43 | redo = new JMenuItem("Redo");
44 | redo.setEnabled(false);
45 | redo.setAccelerator(KeyStroke.getKeyStroke("control Y"));
46 | redo.addActionListener(event -> undoManager.redo());
47 |
48 | add(redo);
49 |
50 | add(new JSeparator());
51 |
52 | cut = new JMenuItem("Cut");
53 | cut.setEnabled(false);
54 | cut.setAccelerator(KeyStroke.getKeyStroke("control X"));
55 | cut.addActionListener(event -> jTextComponent.cut());
56 |
57 | add(cut);
58 |
59 | copy = new JMenuItem("Copy");
60 | copy.setEnabled(false);
61 | copy.setAccelerator(KeyStroke.getKeyStroke("control C"));
62 | copy.addActionListener(event -> jTextComponent.copy());
63 |
64 | add(copy);
65 |
66 | paste = new JMenuItem("Paste");
67 | paste.setEnabled(false);
68 | paste.setAccelerator(KeyStroke.getKeyStroke("control V"));
69 | paste.addActionListener(event -> jTextComponent.paste());
70 |
71 | add(paste);
72 |
73 | delete = new JMenuItem("Delete");
74 | delete.setEnabled(false);
75 | delete.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
76 | delete.addActionListener(event -> jTextComponent.replaceSelection(""));
77 |
78 | add(delete);
79 |
80 | add(new JSeparator());
81 |
82 | selectAll = new JMenuItem("Select All");
83 | selectAll.setEnabled(false);
84 | selectAll.setAccelerator(KeyStroke.getKeyStroke("control A"));
85 | selectAll.addActionListener(event -> jTextComponent.selectAll());
86 |
87 | add(selectAll);
88 | }
89 |
90 | private void setTextComponent(JTextComponent jTextComponent)
91 | {
92 | this.jTextComponent = jTextComponent;
93 |
94 | jTextComponent.addKeyListener(new KeyAdapter()
95 | {
96 | @Override
97 | public void keyPressed(KeyEvent pressedEvent)
98 | {
99 | if ((pressedEvent.getKeyCode() == KeyEvent.VK_Z)
100 | && ((pressedEvent.getModifiers() & KeyEvent.CTRL_MASK) != 0))
101 | {
102 | if (undoManager.canUndo())
103 | {
104 | undoManager.undo();
105 | }
106 | }
107 |
108 | if ((pressedEvent.getKeyCode() == KeyEvent.VK_Y)
109 | && ((pressedEvent.getModifiers() & KeyEvent.CTRL_MASK) != 0))
110 | {
111 | if (undoManager.canRedo())
112 | {
113 | undoManager.redo();
114 | }
115 | }
116 | }
117 | });
118 |
119 | jTextComponent.addMouseListener(new MouseAdapter()
120 | {
121 | @Override
122 | public void mouseReleased(MouseEvent releasedEvent)
123 | {
124 | if (releasedEvent.getButton() == MouseEvent.BUTTON3)
125 | {
126 | processClick(releasedEvent);
127 | }
128 | }
129 | });
130 |
131 | jTextComponent.getDocument().addUndoableEditListener(event -> undoManager.addEdit(event.getEdit()));
132 | }
133 |
134 | private void processClick(MouseEvent event)
135 | {
136 | jTextComponent = (JTextComponent) event.getSource();
137 | jTextComponent.requestFocus();
138 |
139 | boolean enableUndo = undoManager.canUndo();
140 | boolean enableRedo = undoManager.canRedo();
141 | boolean enableCut = false;
142 | boolean enableCopy = false;
143 | boolean enablePaste = false;
144 | boolean enableDelete = false;
145 | boolean enableSelectAll = false;
146 |
147 | String selectedText = jTextComponent.getSelectedText();
148 | String text = jTextComponent.getText();
149 |
150 | if (text != null)
151 | {
152 | if (text.length() > 0)
153 | {
154 | enableSelectAll = true;
155 | }
156 | }
157 |
158 | if (selectedText != null)
159 | {
160 | if (selectedText.length() > 0)
161 | {
162 | enableCut = true;
163 | enableCopy = true;
164 | enableDelete = true;
165 | }
166 | }
167 |
168 | if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor) && jTextComponent.isEnabled())
169 | {
170 | enablePaste = true;
171 | }
172 |
173 | undo.setEnabled(enableUndo);
174 | redo.setEnabled(enableRedo);
175 | cut.setEnabled(enableCut);
176 | copy.setEnabled(enableCopy);
177 | paste.setEnabled(enablePaste);
178 | delete.setEnabled(enableDelete);
179 | selectAll.setEnabled(enableSelectAll);
180 |
181 | show(jTextComponent, event.getX(), event.getY());
182 | }
183 | }
--------------------------------------------------------------------------------
/src/asmcup/sandbox/FrontPanel.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.awt.*;
4 | import java.util.ArrayList;
5 |
6 | import javax.swing.*;
7 |
8 | public class FrontPanel extends JPanel {
9 | protected GridBagLayout gridLayout = new GridBagLayout();
10 | protected GridBagConstraints cComponent = new GridBagConstraints();
11 | protected GridBagConstraints cLabel = new GridBagConstraints();
12 | protected GridBagConstraints cWideItem = new GridBagConstraints();
13 | protected GridBagConstraints cItemLeft = new GridBagConstraints();
14 | protected GridBagConstraints cItemRight = new GridBagConstraints();
15 | protected ArrayList components = new ArrayList<>();
16 | protected int currentRow = 0;
17 |
18 | public FrontPanel() {
19 | setLayout(gridLayout);
20 | cLabel.gridx = 0;
21 | cLabel.fill = GridBagConstraints.HORIZONTAL;
22 | cComponent.gridx = 1;
23 | cComponent.weightx = 1;
24 | cComponent.fill = GridBagConstraints.HORIZONTAL;
25 | cItemLeft.gridx = 0;
26 | cItemLeft.weightx = 1;
27 | cItemLeft.fill = GridBagConstraints.HORIZONTAL;
28 | cItemRight.gridx = 1;
29 | cItemRight.weightx = 1;
30 | cItemRight.fill = GridBagConstraints.HORIZONTAL;
31 | cWideItem.gridx = 0;
32 | cWideItem.gridwidth = 2;
33 | normalLabels();
34 | }
35 |
36 | public void minimizeLabels() {
37 | cLabel.weightx = 0;
38 | }
39 |
40 | public void normalLabels() {
41 | cLabel.weightx = 1;
42 | }
43 |
44 | @SuppressWarnings("rawtypes")
45 | public JSpinner createSpinner(Number value, Comparable min, Comparable max) {
46 | return createSpinner(value, min, max, 1);
47 | }
48 |
49 | @SuppressWarnings("rawtypes")
50 | public JSpinner createSpinner(Number value, Comparable min, Comparable max, Number step) {
51 | SpinnerModel model = new SpinnerNumberModel(value, min, max, step);
52 | JSpinner spinner = new JSpinner(model);
53 | components.add(spinner);
54 | return spinner;
55 | }
56 |
57 | public JCheckBox createCheckBox() {
58 | JCheckBox checkbox = new JCheckBox();
59 | components.add(checkbox);
60 | return checkbox;
61 | }
62 |
63 | public void setComponentsEnabled(boolean enabled) {
64 | for (JComponent component : components) {
65 | component.setEnabled(enabled);
66 | }
67 | }
68 |
69 | public int getInt(JSpinner spinner) {
70 | return (Integer)spinner.getValue();
71 | }
72 |
73 | public float getFloat(JSpinner spinner) {
74 | return (Float)spinner.getValue();
75 | }
76 |
77 | public void addRow(String label, JComponent component) {
78 | addRow(label, component, "");
79 | }
80 |
81 | public void addRow(String labelText, JComponent component, String hint) {
82 | JLabel label = new JLabel(labelText);
83 | component.setToolTipText(hint);
84 | label.setToolTipText(hint);
85 | addLabelledItem(label, component);
86 | }
87 |
88 | public void addWideItem(JComponent item) {
89 | cWideItem.gridy = currentRow;
90 | add(item, cWideItem);
91 | currentRow++;
92 | }
93 |
94 | public void addLabelledItem(JLabel label, JComponent component) {
95 | cLabel.gridy = currentRow;
96 | cComponent.gridy = currentRow;
97 | add(label, cLabel);
98 | add(component, cComponent);
99 | currentRow++;
100 | }
101 |
102 | public void addItems(JComponent left, JComponent right) {
103 | cItemLeft.gridy = currentRow;
104 | cItemRight.gridy = currentRow;
105 | add(left, cItemLeft);
106 | add(right, cItemRight);
107 | currentRow++;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/asmcup/sandbox/LoadWorldDialog.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import javax.swing.JFrame;
4 | import javax.swing.JButton;
5 | import javax.swing.JSpinner;
6 |
7 | import java.io.IOException;
8 |
9 | import javax.imageio.ImageIO;
10 | import javax.swing.BorderFactory;
11 |
12 | import asmcup.genetics.Spawn;
13 | import asmcup.runtime.World;
14 |
15 | public class LoadWorldDialog extends JFrame {
16 | protected final Sandbox sandbox;
17 | protected final FrontPanel panel = new FrontPanel();
18 |
19 | protected JSpinner spinnerSeed;
20 | protected JSpinner spinnerX, spinnerY;
21 | protected JSpinner spinnerFacing;
22 | protected JButton loadButton = new JButton("Load");
23 | protected JButton showButton = new JButton("Get current");
24 |
25 | public LoadWorldDialog(Sandbox sandbox) throws IOException {
26 | this.sandbox = sandbox;
27 |
28 | spinnerSeed = panel.createSpinner(0, Integer.MIN_VALUE, Integer.MAX_VALUE);
29 | spinnerX = panel.createSpinner(0.0f, 0f, (float)World.SIZE);
30 | spinnerY = panel.createSpinner(0.0f, 0f, (float)World.SIZE);
31 | spinnerFacing = panel.createSpinner(0.0f, -(float)Math.PI, (float)Math.PI * 2, 0.1f);
32 | panel.addRow("World Seed:", spinnerSeed);
33 | panel.addRow("Robot X:", spinnerX);
34 | panel.addRow("Robot Y:", spinnerY);
35 | panel.addRow("Robot Facing:", spinnerFacing);
36 | panel.setBorder(BorderFactory.createTitledBorder("Spawn Location"));
37 |
38 | loadButton.addActionListener(e -> load());
39 | showButton.addActionListener(e -> update());
40 | panel.addItems(loadButton, showButton);
41 |
42 | setTitle("Load World");
43 | setResizable(false);
44 | setIconImage(ImageIO.read(getClass().getResource("/world.png")));
45 | setContentPane(panel);
46 | pack();
47 | }
48 |
49 | public void load() {
50 | Spawn spawn = new Spawn(panel.getFloat(spinnerX),
51 | panel.getFloat(spinnerY),
52 | panel.getFloat(spinnerFacing),
53 | panel.getInt(spinnerSeed));
54 | sandbox.loadSpawn(spawn);
55 | }
56 |
57 | public void update() {
58 | spinnerSeed.setValue(sandbox.getWorld().getSeed());
59 | spinnerX.setValue(sandbox.getRobot().getX());
60 | spinnerY.setValue(sandbox.getRobot().getY());
61 | spinnerFacing.setValue(sandbox.getRobot().getFacing());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/asmcup/sandbox/Main.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.swing.UIManager;
6 |
7 | public class Main {
8 | public static void main(String[] args) throws IOException {
9 | try {
10 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
11 | } catch (Exception e) {
12 | // Don't care if theme can't be set
13 | }
14 |
15 | Sandbox sandbox = new Sandbox();
16 | sandbox.run();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/asmcup/sandbox/Menu.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.awt.*;
4 | import java.awt.datatransfer.*;
5 | import java.awt.event.*;
6 | import java.net.URI;
7 | import java.util.Base64;
8 |
9 | import javax.swing.*;
10 |
11 | public class Menu extends JMenuBar {
12 | protected final Sandbox sandbox;
13 | protected JDialog about;
14 |
15 | public Menu(Sandbox sandbox) {
16 | this.sandbox = sandbox;
17 |
18 | addWorldMenu();
19 | addRobotMenu();
20 | addViewMenu();
21 | addToolsMenu();
22 | addGeneticsMenu();
23 | addHelpMenu();
24 | }
25 |
26 | protected JMenuItem item(String label, ActionListener f, KeyStroke k) {
27 | JMenuItem item = new JMenuItem(label);
28 | item.addActionListener(f);
29 |
30 | if (k != null) {
31 | item.setAccelerator(k);
32 | }
33 |
34 | return item;
35 | }
36 |
37 | protected JMenuItem item(String label, ActionListener f) {
38 | return item(label, f, null);
39 | }
40 |
41 | protected JMenuItem item(String label, ActionListener f, int key) {
42 | return item(label, f, KeyStroke.getKeyStroke(key, 0));
43 | }
44 |
45 | public void showLoadWorld() {
46 | sandbox.loadWorld.setVisible(true);
47 | }
48 |
49 | public void singleTick() {
50 | sandbox.singleTick();
51 | }
52 |
53 | public void setSpeed(float speed) {
54 | sandbox.setFramerate(Sandbox.DEFAULT_FRAMERATE * speed);
55 | }
56 |
57 | public void showCodeEditor() {
58 | sandbox.codeEditor.setVisible(true);
59 | }
60 |
61 | public void showDebugger() {
62 | sandbox.debugger.setVisible(true);
63 | }
64 |
65 | public void showSpawns() {
66 | sandbox.spawnsWindow.setVisible(true);
67 | }
68 |
69 | public void showEvaluator() {
70 | sandbox.evaluator.setVisible(true);
71 | }
72 |
73 | public void showGenetics() {
74 | sandbox.genetics.setVisible(true);
75 | }
76 |
77 | public void showAbout() {
78 | if (about == null) {
79 | createAbout();
80 | }
81 |
82 | about.setVisible(true);
83 | }
84 |
85 | protected void createAbout() {
86 | String text;
87 |
88 | try {
89 | text = Utils.readAsString(getClass().getResourceAsStream("/about.txt"));
90 | } catch (Exception e) {
91 | e.printStackTrace();
92 | sandbox.showError("Unable to load about.txt");
93 | return;
94 | }
95 |
96 | JTextArea textArea = new JTextArea();
97 | textArea.setEditable(false);
98 | textArea.setText(text);
99 |
100 | about = new JDialog(sandbox.frame, "About");
101 | about.setSize(500, 350);
102 | about.setLocationRelativeTo(sandbox.frame);
103 | about.setContentPane(new JScrollPane(textArea));
104 | }
105 |
106 | public void showGithub() {
107 | browse("https://github.com/asmcup/runtime");
108 | }
109 |
110 | public void showHomepage() {
111 | browse("https://asmcup.github.io");
112 | }
113 |
114 | public void browse(String url) {
115 | try {
116 | Desktop.getDesktop().browse(new URI(url));
117 | } catch (Exception e) {
118 | e.printStackTrace();
119 | sandbox.showError("Unable to open browser");
120 | }
121 | }
122 |
123 | public void loadROM() {
124 | try {
125 | byte[] rom = Utils.readAsBytes(sandbox.frame, "bin", "Program Binary");
126 | sandbox.loadROM(rom);
127 | } catch (Exception e) {
128 | e.printStackTrace();
129 | sandbox.showError("Unable to load ROM: " + e.getMessage());
130 | }
131 | }
132 |
133 | public void saveROM() {
134 | try {
135 | Utils.write(sandbox.frame, "bin", "Program Binary", sandbox.getROM());
136 | } catch (Exception e) {
137 | e.printStackTrace();
138 | sandbox.showError("Unable to save ROM: " + e.getMessage());
139 | }
140 | }
141 |
142 | public void copyROM() {
143 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
144 | String encoded = Base64.getEncoder().encodeToString(sandbox.getROM());
145 |
146 | try {
147 | clipboard.setContents(new StringSelection(encoded), null);
148 | } catch (Exception e) {
149 | e.printStackTrace();
150 | sandbox.showError("Unable to copy ROM: " + e.getMessage());
151 | }
152 | }
153 |
154 | public void pasteROM() {
155 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
156 | String text = "";
157 | byte[] rom;
158 |
159 | try {
160 | text = (String)clipboard.getData(DataFlavor.stringFlavor);
161 | rom = Base64.getDecoder().decode(text);
162 |
163 | if (rom == null || rom.length != 256) {
164 | sandbox.showError("Program must be 256 bytes");
165 | return;
166 | }
167 | } catch (Exception e) {
168 | e.printStackTrace();
169 | sandbox.showError("Unable to paste: " + e.getMessage());
170 | return;
171 | }
172 |
173 | sandbox.loadROM(rom);
174 | }
175 |
176 | protected void addRobotMenu() {
177 | JMenu menu = new JMenu("Robot");
178 | menu.add(item("Load ROM...", e-> loadROM()));
179 | menu.add(item("Save ROM...", e-> saveROM()));
180 | menu.add(item("Paste ROM", e-> pasteROM()));
181 | menu.add(item("Copy ROM", e -> copyROM()));
182 | menu.addSeparator();
183 | menu.add(item("Flash", e -> sandbox.flash(), KeyEvent.VK_F));
184 | add(menu);
185 | }
186 |
187 | protected void addWorldMenu() {
188 | JMenu menu = new JMenu("World");
189 | menu.add(item("Generate New", e -> sandbox.reseed(), KeyEvent.VK_N));
190 | menu.add(item("Reset", e -> sandbox.resetWorld(), KeyEvent.VK_R));
191 | menu.add(item("Load World", e -> showLoadWorld(), KeyEvent.VK_L));
192 | menu.addSeparator();
193 | menu.add(item("Pause/Resume", e -> sandbox.togglePaused(), KeyEvent.VK_P));
194 | menu.add(item("Single tick", e -> singleTick(), KeyEvent.VK_T));
195 | addSpeedMenu(menu);
196 | menu.addSeparator();
197 | menu.add(item("Quit", e -> sandbox.quit(), KeyEvent.VK_ESCAPE));
198 | add(menu);
199 | }
200 |
201 | private void addSpeedMenu(JMenu menu) {
202 | JMenu speedMenu = new JMenu("Simulation speed");
203 | speedMenu.add(item("0.5x", e -> setSpeed(0.5f)));
204 | speedMenu.add(item("1x", e -> setSpeed(1f)));
205 | speedMenu.add(item("2x", e -> setSpeed(2f)));
206 | speedMenu.add(item("4x", e -> setSpeed(4f)));
207 | speedMenu.add(item("10x", e -> setSpeed(10f)));
208 | menu.add(speedMenu);
209 | }
210 |
211 | protected void addViewMenu() {
212 | JMenu menu = new JMenu("View");
213 | menu.add(item("Toggle Grid", e -> sandbox.toggleGrid(),
214 | KeyStroke.getKeyStroke(KeyEvent.VK_G, ActionEvent.CTRL_MASK)));
215 | menu.addSeparator();
216 | menu.add(item("Center Camera", e -> sandbox.centerView(), KeyEvent.VK_SPACE));
217 | menu.add(item("Lock Camera", e -> sandbox.toggleLockCenter(), KeyEvent.VK_C));
218 | add(menu);
219 | }
220 |
221 | protected void addToolsMenu() {
222 | JMenu menu = new JMenu("Tools");
223 | menu.add(item("Code Editor", e -> showCodeEditor(), KeyEvent.VK_E));
224 | menu.add(item("Debugger", e -> showDebugger(), KeyEvent.VK_D));
225 | menu.add(item("Spawns", e -> showSpawns(), KeyEvent.VK_S));
226 | menu.add(item("Evaluator", e -> showEvaluator(), KeyEvent.VK_V));
227 | menu.add(item("Genetics", e-> showGenetics(), KeyEvent.VK_G));
228 | add(menu);
229 | }
230 |
231 | protected void addHelpMenu() {
232 | JMenu menu = new JMenu("Help");
233 | menu.add(item("Homepage", e -> showHomepage()));
234 | menu.add(item("Github Project", e -> showGithub()));
235 | menu.add(item("About", e -> showAbout()));
236 | add(menu);
237 | }
238 |
239 | protected void addGeneticsMenu() {
240 | add(sandbox.genetics.getMenu());
241 | }
242 | }
--------------------------------------------------------------------------------
/src/asmcup/sandbox/Mouse.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.awt.event.*;
4 |
5 | public class Mouse extends MouseAdapter {
6 | public final Sandbox sandbox;
7 | protected boolean panning;
8 | protected int screenX, screenY;
9 | protected int worldX, worldY;
10 | protected int panStartX, panStartY;
11 |
12 | public Mouse(Sandbox sandbox) {
13 | this.sandbox = sandbox;
14 | }
15 |
16 | public int getWorldX() {
17 | return worldX;
18 | }
19 |
20 | public int getWorldY() {
21 | return worldY;
22 | }
23 |
24 | protected void update(MouseEvent e) {
25 | screenX = e.getX();
26 | screenY = e.getY();
27 | worldX = sandbox.getPanX() + screenX - Sandbox.WIDTH / 2;
28 | worldY = sandbox.getPanY() + screenY - Sandbox.HEIGHT / 2;
29 | }
30 |
31 | protected void startPanning() {
32 | panStartX = screenX;
33 | panStartY = screenY;
34 | panning = true;
35 | sandbox.redraw();
36 | }
37 |
38 | protected void pan() {
39 | int dx = panStartX - screenX;
40 | int dy = panStartY - screenY;
41 | sandbox.pan(dx, dy);
42 | sandbox.redraw();
43 |
44 | panStartX = screenX;
45 | panStartY = screenY;
46 | }
47 |
48 | protected void finishPanning() {
49 | panning = false;
50 | sandbox.redraw();
51 | }
52 |
53 | @Override
54 | public void mousePressed(MouseEvent e) {
55 | update(e);
56 |
57 | switch (e.getButton()) {
58 | case MouseEvent.BUTTON1:
59 | startPanning();
60 | break;
61 | }
62 | }
63 |
64 | @Override
65 | public void mouseReleased(MouseEvent e) {
66 | update(e);
67 |
68 | switch (e.getButton()) {
69 | case MouseEvent.BUTTON1:
70 | finishPanning();
71 | break;
72 | }
73 | }
74 |
75 | @Override
76 | public void mouseDragged(MouseEvent e) {
77 | update(e);
78 |
79 | if (panning) {
80 | pan();
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/asmcup/sandbox/Sandbox.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.awt.*;
4 | import java.awt.geom.AffineTransform;
5 | import java.awt.image.BufferedImage;
6 | import java.io.IOException;
7 | import java.net.URL;
8 |
9 | import javax.imageio.ImageIO;
10 | import javax.swing.*;
11 |
12 | import asmcup.evaluation.EvaluatorWindow;
13 | import asmcup.evaluation.Spawns;
14 | import asmcup.evaluation.SpawnsWindow;
15 | import asmcup.genetics.*;
16 | import asmcup.runtime.*;
17 | import asmcup.runtime.Robot;
18 | import asmcup.runtime.TILE;
19 |
20 | public class Sandbox {
21 | public final Mouse mouse;
22 | public final Menu menu;
23 | public final Canvas canvas;
24 | public final JFrame frame;
25 | public final LoadWorldDialog loadWorld;
26 | public final CodeEditor codeEditor;
27 | public final Debugger debugger;
28 | public final EvaluatorWindow evaluator;
29 | public final Genetics genetics;
30 | public final Spawns spawns;
31 | public final SpawnsWindow spawnsWindow;
32 | protected Image backBuffer;
33 | protected World world;
34 | protected Robot robot;
35 | protected int panX, panY;
36 | protected boolean paused = false;
37 | protected float frameRate = DEFAULT_FRAMERATE;
38 | protected Image[] ground, wall, obstacles, hazards, floor;
39 | protected Image[] coins, batteryImg;
40 | protected Image bot;
41 | protected boolean showGrid;
42 | protected boolean lockCenter;
43 | protected byte[] rom = new byte[256];
44 |
45 | public Sandbox() throws IOException {
46 | reseed();
47 |
48 | // Core components
49 | mouse = new Mouse(this);
50 | canvas = new Canvas(this);
51 | frame = new JFrame("Sandbox");
52 |
53 | // Modules
54 | loadWorld = new LoadWorldDialog(this);
55 | codeEditor = new CodeEditor(this);
56 | debugger = new Debugger(this);
57 | spawns = new Spawns(this);
58 | spawnsWindow = new SpawnsWindow(this);
59 |
60 | evaluator = new EvaluatorWindow(this);
61 | genetics = new Genetics(this);
62 |
63 | // Menu gets built last
64 | menu = new Menu(this);
65 |
66 | canvas.addMouseListener(mouse);
67 | canvas.addMouseMotionListener(mouse);
68 |
69 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
70 | frame.setResizable(false);
71 | frame.setJMenuBar(menu);
72 | frame.setContentPane(canvas);
73 | frame.pack();
74 |
75 | ground = loadImage("/ground.png");
76 | wall = loadImage("/wall.png");
77 | obstacles = loadImage("/obstacles.png");
78 | hazards = loadImage("/hazards.png");
79 | bot = ImageIO.read(getClass().getResource("/robot.png"));
80 | coins = loadImage("/gold.png");
81 | floor = loadImage("/floor.png");
82 | batteryImg = loadImage("/battery.png");
83 |
84 | frame.setIconImage(bot);
85 | }
86 |
87 | public int getPanX() {
88 | return panX;
89 | }
90 |
91 | public int getPanY() {
92 | return panY;
93 | }
94 |
95 | public World getWorld() {
96 | return world;
97 | }
98 |
99 | public Robot getRobot() {
100 | return robot;
101 | }
102 |
103 | public byte[] getROM() {
104 | return rom;
105 | }
106 |
107 | public void setROM(byte[] rom) {
108 | if (rom.length != 256) {
109 | throw new IllegalArgumentException("ROM must be 256 bytes");
110 | }
111 |
112 | this.rom = rom;
113 | }
114 |
115 | public void flash() {
116 | synchronized (world) {
117 | Robot old = robot;
118 | world.removeRobot(old);
119 |
120 | robot = new Robot(1, rom);
121 | robot.position(old.getX(), old.getY());
122 | robot.setFacing(old.getFacing());
123 | world.addRobot(robot);
124 | }
125 | }
126 |
127 | public void loadROM(byte[] rom) {
128 | setROM(rom);
129 | flash();
130 | }
131 |
132 | public void showError(String msg) {
133 | JOptionPane.showMessageDialog(frame, msg);
134 | }
135 |
136 | public void pan(int dx, int dy) {
137 | panX += dx;
138 | panY += dy;
139 | }
140 |
141 | protected Image[] loadImage(String path) throws IOException {
142 | URL url = getClass().getResource(path);
143 | Image sheet = ImageIO.read(url);
144 | Image[] variants = new Image[4];
145 |
146 | for (int i=0; i < 4; i++) {
147 | Image img = new BufferedImage(World.TILE_SIZE, World.TILE_SIZE,
148 | BufferedImage.TYPE_INT_ARGB);
149 | Graphics g = img.getGraphics();
150 | g.drawImage(sheet, -i * World.TILE_SIZE, 0, null);
151 | variants[i] = img;
152 | }
153 |
154 | return variants;
155 | }
156 |
157 | public void run() {
158 | long lastTick;
159 |
160 | frame.setVisible(true);
161 |
162 | while (frame.isVisible()) {
163 | lastTick = System.currentTimeMillis();
164 |
165 | if (!paused) {
166 | tick();
167 | }
168 |
169 | tickWait(lastTick);
170 | }
171 | }
172 |
173 | protected void tick() {
174 | synchronized (world) {
175 | if (lockCenter) {
176 | centerView();
177 | }
178 |
179 | world.tick();
180 | debugger.updateDebugger();
181 | redraw();
182 | }
183 | }
184 |
185 | protected void tickWait(long lastTick) {
186 | long now = System.currentTimeMillis();
187 | long span = now - lastTick;
188 | int msPerFrame = Math.round(1000 / frameRate);
189 | int wait = (int)(msPerFrame - span);
190 | sleep(wait);
191 | }
192 |
193 | public void togglePaused()
194 | {
195 | paused = !paused;
196 | redraw();
197 | }
198 |
199 | public void singleTick()
200 | {
201 | if (paused) {
202 | tick();
203 | }
204 | }
205 |
206 | public void setFramerate(float f) {
207 | frameRate = f;
208 | }
209 |
210 | public void reseed() {
211 | world = new World();
212 |
213 | if (robot == null) {
214 | robot = new Robot(1);
215 | }
216 |
217 | world.randomizePosition(robot);
218 | centerView();
219 | world.addRobot(robot);
220 | redraw();
221 | }
222 |
223 | public void resetWorld() {
224 | resetWorld(world.getSeed());
225 | }
226 |
227 | public void resetWorld(int seed) {
228 | synchronized (this) {
229 | world = new World(seed);
230 | world.addRobot(robot);
231 | }
232 |
233 | redraw();
234 | }
235 |
236 | public void loadSpawn(Spawn spawn) {
237 | robot.position(spawn.x, spawn.y);
238 | robot.setFacing(spawn.facing);
239 | resetWorld(spawn.seed);
240 | centerView();
241 | }
242 |
243 | public void centerView() {
244 | panX = (int)robot.getX();
245 | panY = (int)robot.getY();
246 | redraw();
247 | }
248 |
249 | public void draw() {
250 | if (backBuffer == null) {
251 | return;
252 | }
253 |
254 | Graphics2D g = (Graphics2D)backBuffer.getGraphics();
255 |
256 | g.setColor(Color.BLACK);
257 | g.fillRect(0, 0, WIDTH, HEIGHT);
258 |
259 | AffineTransform originalTransform = g.getTransform();
260 | g.translate(WIDTH/2 - panX, HEIGHT/2 - panY);
261 |
262 | int left = (int)Math.floor((panX - WIDTH/2.0) / World.CELL_SIZE);
263 | int right = (int)Math.ceil((panX + WIDTH/2.0) / World.CELL_SIZE);
264 | int top = (int)Math.floor((panY - HEIGHT/2.0) / World.CELL_SIZE);
265 | int bottom = (int)Math.ceil((panY + HEIGHT/2.0) / World.CELL_SIZE);
266 |
267 | left = Math.max(0, left);
268 | right = Math.max(0, right);
269 | top = Math.max(0, top);
270 | bottom = Math.max(0, bottom);
271 |
272 | for (int cellY=top; cellY < bottom; cellY++) {
273 | for (int cellX=left; cellX < right; cellX++) {
274 | drawCell(g, world.getCell(cellX, cellY));
275 | }
276 | }
277 |
278 | for (Robot robot : world.getRobots()) {
279 | drawRobot(g, robot);
280 | }
281 |
282 | g.setColor(Color.PINK);
283 |
284 | for (Spawn spawn : spawns.getIterable()) {
285 | int x = (int)spawn.x;
286 | int y = (int)spawn.y;
287 |
288 | g.drawLine(x - 4, y, x + 4, y);
289 | g.drawLine(x, y - 4, x, y + 4);
290 | }
291 |
292 | g.setTransform(originalTransform);
293 |
294 | if (paused) {
295 | g.setColor(Color.WHITE);
296 | g.drawString("PAUSED", 25, 50);
297 | }
298 | }
299 |
300 | protected void drawCell(Graphics2D g, Cell cell) {
301 | int left = cell.getX() * World.TILES_PER_CELL;
302 | int right = left + World.TILES_PER_CELL;
303 | int top = cell.getY() * World.TILES_PER_CELL;
304 | int bottom = top + World.TILES_PER_CELL;
305 |
306 | for (int row=top; row < bottom; row++) {
307 | for (int col=left; col < right; col++) {
308 | int tile = world.getTile(col, row);
309 | drawTile(g, col, row, tile);
310 | }
311 | }
312 |
313 | for (Item item : cell.getItems()) {
314 | drawItem(g, item);
315 | }
316 |
317 | if (showGrid) {
318 | g.setColor(Color.WHITE);
319 | int x = left * World.TILE_SIZE;
320 | int y = top * World.TILE_SIZE;
321 | g.drawRect(x, y, World.CELL_SIZE, World.CELL_SIZE);
322 |
323 | String msg = String.format("%d, %d", cell.getX(), cell.getY());
324 | g.drawString(msg, x + 100, y + 100);
325 |
326 | msg = String.format("%x", cell.getKey());
327 | g.drawString(msg, x + 100, y + 150);
328 | }
329 | }
330 |
331 | protected void drawTile(Graphics2D g, int col, int row, int tile) {
332 | int x = col * World.TILE_SIZE;
333 | int y = row * World.TILE_SIZE;
334 | int variant = (tile >> 3) & 0b11;
335 |
336 | switch (tile & 0b111) {
337 | case TILE.GROUND:
338 | drawVariant(g, ground, x, y, variant);
339 | break;
340 | case TILE.OBSTACLE:
341 | drawVariant(g, ground, x, y, variant ^ 0b11);
342 | drawVariant(g, obstacles, x, y, variant);
343 | break;
344 | case TILE.WALL:
345 | drawVariant(g, wall, x, y, variant);
346 | break;
347 | case TILE.HAZARD:
348 | drawVariant(g, hazards, x, y, variant);
349 | break;
350 | case TILE.FLOOR:
351 | drawVariant(g, floor, x, y, variant);
352 | break;
353 | }
354 | }
355 |
356 | protected void drawRobot(Graphics2D g, Robot robot) {
357 | int rx = (int)robot.getX();
358 | int ry = (int)robot.getY();
359 | AffineTransform t = g.getTransform();
360 |
361 | g.rotate(robot.getFacing(), rx, ry);
362 | g.drawImage(bot, rx - World.TILE_HALF, ry - World.TILE_HALF, null);
363 | g.setTransform(t);
364 |
365 | g.rotate(robot.getBeamAngle(), rx, ry);
366 |
367 | if (robot.getLazerEnd() > 0) {
368 | int w = (int)(robot.getLazerEnd());
369 | g.setColor(Color.RED);
370 | g.drawLine(rx, ry, rx + w, ry);
371 | }
372 |
373 | if (world.getFrame() - robot.getSensorFrame() < 3) {
374 | int w = (int)(robot.getSensor());
375 | g.setColor(Color.BLUE);
376 | g.drawLine(rx, ry, rx + w, ry);
377 | }
378 |
379 | g.setTransform(t);
380 |
381 | if (showGrid) {
382 | g.setColor(Color.RED);
383 | g.fillRect(rx - 1, ry - 1, 3, 3);
384 | }
385 | }
386 |
387 | protected void drawItem(Graphics2D g, Item item) {
388 | if (item instanceof Item.Battery) {
389 | drawItemBattery(g, (Item.Battery)item);
390 | } else if (item instanceof Item.Gold) {
391 | drawItemGold(g, (Item.Gold)item);
392 | }
393 |
394 | if (showGrid) {
395 | g.setColor(Color.RED);
396 | g.fillRect((int)item.getX(), (int)item.getY(), 2, 2);
397 | }
398 | }
399 |
400 | protected void drawItemBattery(Graphics2D g, Item.Battery battery) {
401 | int x = (int)battery.getX();
402 | int y = (int)battery.getY();
403 | drawVariant(g, batteryImg, x - 16, y - 16, battery.getVariant());
404 | }
405 |
406 | protected void drawItemGold(Graphics2D g, Item.Gold gold) {
407 | int x = (int)gold.getX();
408 | int y = (int)gold.getY();
409 | drawVariant(g, coins, x - 16, y - 16, gold.getVariant());
410 | }
411 |
412 | protected void drawVariant(Graphics2D g, Image[] imgs, int x, int y, int variant) {
413 | g.drawImage(imgs[variant], x, y, null);
414 | }
415 |
416 | public static void sleep(int ms) {
417 | if (ms <= 0) {
418 | return;
419 | }
420 |
421 | try {
422 | Thread.sleep(ms);
423 | } catch (InterruptedException e) {
424 |
425 | }
426 | }
427 |
428 | protected Image createBackBuffer() {
429 | Image img = frame.createVolatileImage(WIDTH, HEIGHT);
430 |
431 | if (img == null) {
432 | return null;
433 | }
434 |
435 | Graphics g = img.getGraphics();
436 | g.setColor(Color.BLACK);
437 | g.fillRect(0, 0, WIDTH, HEIGHT);
438 |
439 | return img;
440 | }
441 |
442 | public void redraw() {
443 | if (backBuffer != null) {
444 | synchronized (backBuffer) {
445 | draw();
446 | }
447 |
448 | frame.repaint();
449 | }
450 | }
451 |
452 | public Image getBackBuffer() {
453 | if (backBuffer == null) {
454 | backBuffer = createBackBuffer();
455 | }
456 |
457 | return backBuffer;
458 | }
459 |
460 | public void quit() {
461 | System.exit(0);
462 | }
463 |
464 | public void toggleGrid() {
465 | showGrid = !showGrid;
466 | redraw();
467 | }
468 |
469 | public void toggleLockCenter() {
470 | lockCenter = !lockCenter;
471 | }
472 |
473 | public static final int WIDTH = 800;
474 | public static final int HEIGHT = 600;
475 | public static final int DEFAULT_FRAMERATE = 10;
476 | }
477 |
--------------------------------------------------------------------------------
/src/asmcup/sandbox/Utils.java:
--------------------------------------------------------------------------------
1 | package asmcup.sandbox;
2 |
3 | import java.io.*;
4 | import java.nio.charset.Charset;
5 | import java.nio.file.*;
6 |
7 | import javax.swing.*;
8 | import javax.swing.filechooser.FileNameExtensionFilter;
9 |
10 | public class Utils {
11 | public static String readAsString(String path) throws IOException {
12 | return readAsString(new File(path));
13 | }
14 |
15 | public static String readAsString(File file) throws IOException {
16 | return readAsString(file.toPath());
17 | }
18 |
19 | public static String readAsString(Path path) throws IOException {
20 | return new String(Files.readAllBytes(path), Charset.forName("US-ASCII"));
21 | }
22 |
23 | public static String readAsString(JFrame frame, String ext, String desc) throws IOException {
24 | File file = findFileOpen(frame, ext, desc);
25 |
26 | if (file == null) {
27 | return null;
28 | }
29 |
30 | return readAsString(file);
31 | }
32 |
33 | public static String readAsString(InputStream input) throws IOException {
34 | String line;
35 | StringBuilder builder = new StringBuilder();
36 | InputStreamReader streamReader = new InputStreamReader(input, Charset.forName("US-ASCII"));
37 | BufferedReader reader = new BufferedReader(streamReader);
38 |
39 | while ((line = reader.readLine()) != null) {
40 | builder.append(line);
41 | builder.append("\n");
42 | }
43 |
44 | return builder.toString();
45 | }
46 |
47 | public static byte[] readAsBytes(JFrame frame, String ext, String desc) throws IOException {
48 | File file = findFileOpen(frame, ext, desc);
49 |
50 | if (file == null) {
51 | return null;
52 | }
53 |
54 | return Files.readAllBytes(file.toPath());
55 | }
56 |
57 | public static void write(Path path, String text) throws IOException {
58 | Files.write(path, text.getBytes("ASCII"));
59 | }
60 |
61 | public static void write(File file, String text) throws IOException {
62 | write(file.toPath(), text);
63 | }
64 |
65 | public static void write(JFrame frame, String ext, String desc, String text) throws IOException {
66 | File file = findFileSave(frame, ext, desc);
67 |
68 | if (file == null) {
69 | return;
70 | }
71 |
72 | write(file, text);
73 | }
74 |
75 | public static void write(JFrame frame, String ext, String desc, byte[] data) throws IOException {
76 | File file = findFileSave(frame, ext, desc);
77 |
78 | if (file == null) {
79 | return;
80 | }
81 |
82 | Files.write(file.toPath(), data);
83 | }
84 |
85 | public static File findFileOpen(JFrame frame, String ext, String desc) {
86 | JFileChooser chooser = new JFileChooser();
87 | chooser.setFileFilter(new FileNameExtensionFilter(desc, ext));
88 |
89 | if (chooser.showOpenDialog(frame) != JFileChooser.APPROVE_OPTION) {
90 | return null;
91 | }
92 |
93 | return chooser.getSelectedFile();
94 | }
95 |
96 | public static File findFileSave(JFrame frame, String ext, String desc) {
97 | JFileChooser chooser = new JFileChooser();
98 | chooser.setFileFilter(new FileNameExtensionFilter(desc, ext));
99 |
100 | if (chooser.showSaveDialog(frame) != JFileChooser.APPROVE_OPTION) {
101 | return null;
102 | }
103 |
104 | return getSelectedFileWithExtension(chooser);
105 | }
106 |
107 | /**
108 | * Returns the selected file from a JFileChooser, including the extension from
109 | * the file filter.
110 | * From: http://stackoverflow.com/a/18984561
111 | */
112 | public static File getSelectedFileWithExtension(JFileChooser c) {
113 | File file = c.getSelectedFile();
114 | if (c.getFileFilter() instanceof FileNameExtensionFilter) {
115 | String[] exts = ((FileNameExtensionFilter)c.getFileFilter()).getExtensions();
116 | String nameLower = file.getName().toLowerCase();
117 | for (String ext : exts) { // check if it already has a valid extension
118 | if (nameLower.endsWith('.' + ext.toLowerCase())) {
119 | return file; // if yes, return as-is
120 | }
121 | }
122 | // if not, append the first extension from the selected filter
123 | file = new File(file.toString() + '.' + exts[0]);
124 | }
125 | return file;
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/src/asmcup/vm/VM.java:
--------------------------------------------------------------------------------
1 | package asmcup.vm;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.util.Arrays;
7 | import java.util.Objects;
8 |
9 | public class VM implements VMConsts {
10 | private final byte[] ram;
11 | private int pc, sp;
12 | private boolean io;
13 |
14 | public VM() {
15 | this.ram = new byte[256];
16 | }
17 |
18 | public VM(byte[] ram) {
19 | if (ram.length != 256) {
20 | throw new IllegalArgumentException("Memory must be 256 bytes");
21 | }
22 |
23 | this.ram = ram;
24 | }
25 |
26 | public VM(DataInputStream stream) throws IOException {
27 | ram = new byte[256];
28 | stream.readFully(ram);
29 | pc = stream.readUnsignedByte() & 0xFF;
30 | sp = stream.readUnsignedByte() & 0xFF;
31 | io = stream.readBoolean();
32 | }
33 |
34 | @Override
35 | public boolean equals(Object obj) {
36 | if (!(obj instanceof VM)) return false;
37 |
38 | VM vm = (VM) obj;
39 | return Arrays.equals(getMemory(), vm.getMemory()) &&
40 | getProgramCounter() == vm.getProgramCounter() &&
41 | getStackPointer() == vm.getStackPointer() &&
42 | io == vm.io;
43 | }
44 |
45 | @Override
46 | public int hashCode() {
47 | return Objects.hash(getMemory(), getProgramCounter(), getStackPointer(), io);
48 | }
49 |
50 | public void save(DataOutputStream stream) throws IOException {
51 | stream.write(ram);
52 | stream.writeByte(pc);
53 | stream.writeByte(sp);
54 | stream.writeBoolean(io);
55 | }
56 |
57 | public int getProgramCounter() {
58 | return pc;
59 | }
60 |
61 | public int getStackPointer() {
62 | return 0xFF - sp;
63 | }
64 |
65 | public byte[] getMemory() {
66 | return ram;
67 | }
68 |
69 | public int read8() {
70 | int value = ram[pc] & 0xFF;
71 | pc = (pc + 1) & 0xFF;
72 | return value;
73 | }
74 |
75 | public int read8(int addr) {
76 | return ram[addr & 0xFF] & 0xFF;
77 | }
78 |
79 | public int read8indirect() {
80 | return ram[read8()] & 0xFF;
81 | }
82 |
83 | public int read16() {
84 | return read8() | (read8() << 8);
85 | }
86 |
87 | public int read16(int addr) {
88 | return read8(addr) | (read8(addr + 1) << 8);
89 | }
90 |
91 | public int read32() {
92 | return read16() | (read16() << 16);
93 | }
94 |
95 | public int read32(int addr) {
96 | return read16(addr) | (read16(addr + 2) << 16);
97 | }
98 |
99 | public float readFloat() {
100 | return Float.intBitsToFloat(read32());
101 | }
102 |
103 | public float readFloatIndirect() {
104 | return Float.intBitsToFloat(read32(read8()));
105 | }
106 |
107 | public void write8(int addr, int value) {
108 | ram[addr & 0xFF] = (byte)value;
109 | }
110 |
111 | public void write16(int addr, int value) {
112 | write8(addr, value);
113 | write8(addr + 1, value >> 8);
114 | }
115 |
116 | public void write32(int addr, int value) {
117 | write16(addr, value);
118 | write16(addr + 2, value >> 16);
119 | }
120 |
121 | public void writeFloat(int addr, float value) {
122 | write32(addr, Float.floatToRawIntBits(value));
123 | }
124 |
125 | public void push8(int x) {
126 | ram[0xFF - sp] = (byte) x;
127 | sp = (sp + 1) & 0xFF;
128 | }
129 |
130 | public void push8(boolean x) {
131 | push8(x ? 1 : 0);
132 | }
133 |
134 | public void push16(int x) {
135 | push8(x);
136 | push8(x >> 8);
137 | }
138 |
139 | public void push32(int x) {
140 | push16(x);
141 | push16(x >> 16);
142 | }
143 |
144 | public void pushFloat(float x) {
145 | push32(Float.floatToRawIntBits(x));
146 | }
147 |
148 | public int pop8() {
149 | sp = (sp - 1) & 0xFF;
150 | return ram[0xFF - sp] & 0xFF;
151 | }
152 |
153 | public int pop16() {
154 | return (pop8() << 8) | pop8();
155 | }
156 |
157 | public int pop32() {
158 | return (pop16() << 16) | pop16();
159 | }
160 |
161 | public float popFloat() {
162 | return Float.intBitsToFloat(pop32());
163 | }
164 |
165 | public int peek8() {
166 | return peek8(0);
167 | }
168 |
169 | public int peek8(int r) {
170 | return ram[0xFF - ((sp - r - 1) & 0xFF)] & 0xFF;
171 | }
172 |
173 | public int peek16(int r) {
174 | return peek8(r + 1) | (peek8(r) << 8);
175 | }
176 |
177 | public int peek32(int r) {
178 | return peek16(r + 2) | (peek16(r) << 16);
179 | }
180 |
181 | public float peekFloat() {
182 | return peekFloat(0);
183 | }
184 |
185 | public float peekFloat(int r) {
186 | return Float.intBitsToFloat(peek32(r));
187 | }
188 |
189 | public boolean checkIO() {
190 | boolean x = io;
191 | io = false;
192 | return x;
193 | }
194 |
195 | public void setIO(boolean io) {
196 | this.io = io;
197 | }
198 |
199 | public void tick() {
200 | int bits = read8();
201 | int opcode = bits & 0b11;
202 | int data = bits >> 2;
203 |
204 | switch (opcode) {
205 | case OP_FUNC:
206 | op_func(data);
207 | break;
208 | case OP_PUSH:
209 | op_push(data);
210 | break;
211 | case OP_POP:
212 | op_pop(data);
213 | break;
214 | case OP_BRANCH:
215 | op_branch(data);
216 | break;
217 | }
218 | }
219 |
220 | public void op_func(int data) {
221 | switch (data) {
222 | case F_NOP:
223 | break;
224 |
225 | case F_B2F:
226 | pushFloat(pop8());
227 | break;
228 | case F_F2B:
229 | push8((int)popFloat());
230 | break;
231 |
232 | case F_NOT:
233 | push8(~pop8());
234 | break;
235 | case F_OR:
236 | push8(pop8() | pop8());
237 | break;
238 | case F_AND:
239 | push8(pop8() & pop8());
240 | break;
241 | case F_XOR:
242 | push8(pop8() ^ pop8());
243 | break;
244 | case F_SHL:
245 | push8(pop8() << 1);
246 | break;
247 | case F_SHR:
248 | push8(pop8() >> 1);
249 | break;
250 | case F_ADD8:
251 | push8(pop8() + pop8());
252 | break;
253 | case F_SUB8:
254 | push8(pop8() - pop8());
255 | break;
256 | case F_MUL8:
257 | push8(pop8() * pop8());
258 | break;
259 | case F_DIV8:
260 | int a = pop8();
261 | int b = pop8();
262 | push8(b == 0 ? 0 : a / b);
263 | break;
264 | case F_MADD8:
265 | push8(pop8() * pop8() + pop8());
266 | break;
267 |
268 | case F_NEGF:
269 | pushFloat(-popFloat());
270 | break;
271 | case F_ADDF:
272 | pushFloat(popFloat() + popFloat());
273 | break;
274 | case F_SUBF:
275 | pushFloat(popFloat() - popFloat());
276 | break;
277 | case F_MULF:
278 | pushFloat(popFloat() * popFloat());
279 | break;
280 | case F_DIVF:
281 | pushFloat(popFloat() / popFloat());
282 | break;
283 | case F_MADDF:
284 | pushFloat(popFloat() * popFloat() + popFloat());
285 | break;
286 |
287 | case F_COS:
288 | pushFloat((float) StrictMath.cos(popFloat()));
289 | break;
290 | case F_SIN:
291 | pushFloat((float) StrictMath.sin(popFloat()));
292 | break;
293 | case F_TAN:
294 | pushFloat((float) StrictMath.tan(popFloat()));
295 | break;
296 | case F_ACOS:
297 | pushFloat((float) StrictMath.acos(popFloat()));
298 | break;
299 | case F_ASIN:
300 | pushFloat((float) StrictMath.asin(popFloat()));
301 | break;
302 | case F_ATAN:
303 | pushFloat((float) StrictMath.atan(popFloat()));
304 | break;
305 | case F_ABSF:
306 | pushFloat(StrictMath.abs(popFloat()));
307 | break;
308 | case F_MINF:
309 | pushFloat(StrictMath.min(popFloat(), popFloat()));
310 | break;
311 | case F_MAXF:
312 | pushFloat(StrictMath.max(popFloat(), popFloat()));
313 | break;
314 | case F_POW:
315 | pushFloat((float) StrictMath.pow(popFloat(), popFloat()));
316 | break;
317 | case F_LOG:
318 | pushFloat((float) StrictMath.log(popFloat()));
319 | break;
320 | case F_LOG10:
321 | pushFloat((float) StrictMath.log10(popFloat()));
322 | break;
323 |
324 | case F_IF_EQ8:
325 | push8(pop8() == pop8());
326 | break;
327 | case F_IF_NE8:
328 | push8(pop8() != pop8());
329 | break;
330 | case F_IF_LT8:
331 | push8(pop8() < pop8());
332 | break;
333 | case F_IF_LTE8:
334 | push8(pop8() <= pop8());
335 | break;
336 |
337 | case F_IF_LTF:
338 | push8(popFloat() < popFloat());
339 | break;
340 | case F_IF_LTEF:
341 | push8(popFloat() <= popFloat());
342 | break;
343 | case F_IF_GTF:
344 | push8(popFloat() > popFloat());
345 | break;
346 | case F_IF_GTEF:
347 | push8(popFloat() <= popFloat());
348 | break;
349 |
350 | case F_C_0:
351 | push8(0);
352 | break;
353 | case F_C_1:
354 | push8(1);
355 | break;
356 | case F_C_2:
357 | push8(2);
358 | break;
359 | case F_C_3:
360 | push8(3);
361 | break;
362 | case F_C_4:
363 | push8(4);
364 | break;
365 | case F_C_255:
366 | push8(0xFF);
367 | break;
368 |
369 | case F_C_M1F:
370 | pushFloat(-1.0f);
371 | break;
372 | case F_C_0F:
373 | pushFloat(0.0f);
374 | break;
375 | case F_C_1F:
376 | pushFloat(1.0f);
377 | break;
378 | case F_C_2F:
379 | pushFloat(2.0f);
380 | break;
381 | case F_C_3F:
382 | pushFloat(3.0f);
383 | break;
384 | case F_C_INF:
385 | pushFloat(Float.POSITIVE_INFINITY);
386 | break;
387 | case F_ISNAN:
388 | push8(Float.isNaN(popFloat()));
389 | break;
390 |
391 | case F_DUP8:
392 | push8(peek8());
393 | break;
394 | case F_DUPF:
395 | pushFloat(peekFloat());
396 | break;
397 |
398 | case F_JSR:
399 | int ret = pc;
400 | pc = pop8();
401 | push8(ret);
402 | break;
403 | case F_RET:
404 | pc = pop8();
405 | break;
406 |
407 | case F_FT8:
408 | push8(peek8(pop8()));
409 | break;
410 | case F_FTF:
411 | pushFloat(peekFloat(pop8()));
412 | break;
413 |
414 | case F_IO:
415 | io = true;
416 | break;
417 | }
418 | }
419 |
420 | public void op_push(int data) {
421 | switch (data) {
422 | case MAGIC_PUSH_BYTE_IMMEDIATE:
423 | push8(read8());
424 | break;
425 | case MAGIC_PUSH_BYTE_MEMORY:
426 | push8(read8indirect());
427 | break;
428 | case MAGIC_PUSH_FLOAT_IMMEDIATE:
429 | pushFloat(readFloat());
430 | break;
431 | case MAGIC_PUSH_FLOAT_MEMORY:
432 | pushFloat(readFloatIndirect());
433 | break;
434 | default:
435 | push8(read8(pc + data - 32));
436 | break;
437 | }
438 | }
439 |
440 | public void op_pop(int data) {
441 | switch (data) {
442 | case MAGIC_POP_BYTE:
443 | write8(read8(), pop8());
444 | break;
445 | case MAGIC_POP_FLOAT:
446 | writeFloat(read8(), popFloat());
447 | break;
448 | case MAGIC_POP_BYTE_INDIRECT:
449 | write8(read8indirect(), pop8());
450 | break;
451 | case MAGIC_POP_FLOAT_INDIRECT:
452 | writeFloat(read8indirect(), popFloat());
453 | break;
454 | default:
455 | write8(pc + data - 32, pop8());
456 | break;
457 | }
458 | }
459 |
460 | public void op_branch(int data) {
461 | int addr;
462 |
463 | switch (data) {
464 | case MAGIC_BRANCH_ALWAYS:
465 | pc = read8();
466 | break;
467 | case MAGIC_BRANCH_IMMEDIATE:
468 | addr = read8();
469 |
470 | if (pop8() != 0) {
471 | pc = addr;
472 | }
473 |
474 | break;
475 | case MAGIC_BRANCH_INDIRECT:
476 | pc = read8indirect();
477 | break;
478 | default:
479 | if (pop8() != 0) {
480 | pc = (pc + data - 32) & 0xFF;
481 | }
482 |
483 | break;
484 | }
485 | }
486 | }
487 |
--------------------------------------------------------------------------------
/src/asmcup/vm/VMConsts.java:
--------------------------------------------------------------------------------
1 | package asmcup.vm;
2 |
3 | public interface VMConsts extends VMFuncs {
4 | public static final int OP_FUNC = 0;
5 | public static final int OP_PUSH = 1;
6 | public static final int OP_POP = 2;
7 | public static final int OP_BRANCH = 3;
8 |
9 | public static final int MAGIC_PUSH_BYTE_MEMORY = 0;
10 | public static final int MAGIC_PUSH_FLOAT_MEMORY = 31;
11 | public static final int MAGIC_PUSH_BYTE_IMMEDIATE = 32;
12 | public static final int MAGIC_PUSH_FLOAT_IMMEDIATE = 33;
13 |
14 | public static final int MAGIC_POP_BYTE = 0;
15 | public static final int MAGIC_POP_FLOAT = 31;
16 | public static final int MAGIC_POP_BYTE_INDIRECT = 32;
17 | public static final int MAGIC_POP_FLOAT_INDIRECT = 33;
18 |
19 | public static final int MAGIC_BRANCH_ALWAYS = 31;
20 | public static final int MAGIC_BRANCH_IMMEDIATE = 32;
21 | public static final int MAGIC_BRANCH_INDIRECT = 33;
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/asmcup/vm/VMFuncs.java:
--------------------------------------------------------------------------------
1 | package asmcup.vm;
2 |
3 | public interface VMFuncs {
4 | public static final int F_NOP = 0;
5 |
6 | // Casting
7 | public static final int F_B2F = 1;
8 | public static final int F_F2B = 2;
9 |
10 | // Bitwise (8-bit)
11 | public static final int F_NOT = 3;
12 | public static final int F_OR = 4;
13 | public static final int F_AND = 5;
14 | public static final int F_XOR = 6;
15 | public static final int F_SHL = 7;
16 | public static final int F_SHR = 8;
17 |
18 | // Arithmetic (8-bit)
19 | public static final int F_ADD8 = 9;
20 | public static final int F_SUB8 = 10;
21 | public static final int F_MUL8 = 11;
22 | public static final int F_DIV8 = 12;
23 | public static final int F_MADD8 = 13;
24 |
25 | // Arithmetic (floating)
26 | public static final int F_NEGF = 14;
27 | public static final int F_ADDF = 15;
28 | public static final int F_SUBF = 16;
29 | public static final int F_MULF = 17;
30 | public static final int F_DIVF = 18;
31 | public static final int F_MADDF = 19;
32 |
33 | // Math (floating)
34 | public static final int F_COS = 20;
35 | public static final int F_SIN = 21;
36 | public static final int F_TAN = 22;
37 | public static final int F_ACOS = 23;
38 | public static final int F_ASIN = 24;
39 | public static final int F_ATAN = 25;
40 | public static final int F_ABSF = 26;
41 | public static final int F_MINF = 27;
42 | public static final int F_MAXF = 28;
43 | public static final int F_POW = 29;
44 | public static final int F_LOG = 30;
45 | public static final int F_LOG10 = 31;
46 |
47 | // Conditional
48 | public static final int F_IF_EQ8 = 32;
49 | public static final int F_IF_NE8 = 33;
50 | public static final int F_IF_LT8 = 34;
51 | public static final int F_IF_LTE8 = 35;
52 | public static final int F_IF_GT8 = 36;
53 | public static final int F_IF_GTE8 = 37;
54 |
55 | public static final int F_IF_LTF = 38;
56 | public static final int F_IF_LTEF = 39;
57 | public static final int F_IF_GTF = 40;
58 | public static final int F_IF_GTEF = 41;
59 |
60 | // Integer constants
61 | public static final int F_C_0 = 42;
62 | public static final int F_C_1 = 43;
63 | public static final int F_C_2 = 44;
64 | public static final int F_C_3 = 45;
65 | public static final int F_C_4 = 46;
66 | public static final int F_C_255 = 47;
67 |
68 | // Float constants
69 | public static final int F_C_0F = 48;
70 | public static final int F_C_1F = 49;
71 | public static final int F_C_2F = 50;
72 | public static final int F_C_3F = 51;
73 | public static final int F_C_M1F = 52;
74 | public static final int F_C_INF = 53;
75 | public static final int F_ISNAN = 54;
76 |
77 | public static final int F_DUP8 = 55;
78 | public static final int F_DUPF = 56;
79 |
80 | public static final int F_JSR = 57;
81 | public static final int F_RET = 58;
82 |
83 | public static final int F_FT8 = 59;
84 | public static final int F_FTF = 60;
85 |
86 | public static final int F_NOP61 = 61;
87 | public static final int F_NOP62 = 62;
88 |
89 | public static final int F_IO = 63;
90 | }
91 |
--------------------------------------------------------------------------------
/src/battery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/battery.png
--------------------------------------------------------------------------------
/src/debugger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/debugger.png
--------------------------------------------------------------------------------
/src/dna.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/dna.png
--------------------------------------------------------------------------------
/src/floor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/floor.png
--------------------------------------------------------------------------------
/src/gauge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/gauge.png
--------------------------------------------------------------------------------
/src/gold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/gold.png
--------------------------------------------------------------------------------
/src/ground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/ground.png
--------------------------------------------------------------------------------
/src/hazards.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/hazards.png
--------------------------------------------------------------------------------
/src/notepad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/notepad.png
--------------------------------------------------------------------------------
/src/obstacles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/obstacles.png
--------------------------------------------------------------------------------
/src/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/plus.png
--------------------------------------------------------------------------------
/src/robot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/robot.png
--------------------------------------------------------------------------------
/src/wall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/wall.png
--------------------------------------------------------------------------------
/src/world.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmcup/runtime/5e1f5eb8c2c2598cd52e211b77f65ad0d198fe12/src/world.png
--------------------------------------------------------------------------------
/test/asmcup/compiler/CompilerTest.java:
--------------------------------------------------------------------------------
1 | package asmcup.compiler;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.nio.ByteBuffer;
7 | import java.nio.ByteOrder;
8 |
9 | import static org.junit.Assert.*;
10 |
11 | public class CompilerTest {
12 |
13 | private Compiler compiler = null;
14 |
15 | @Before
16 | public void setUp() {
17 | compiler = new Compiler();
18 | compiler.init();
19 | }
20 |
21 | @Test
22 | public void testUndefinedLabel() {
23 | try {
24 | compiler.compile("push8 notfoundlabel");
25 | fail("Compiler did not fail on undefined label.");
26 | } catch (IllegalArgumentException e) {
27 | assert(e.getMessage().startsWith("Cannot find label 'notfoundlabel'"));
28 | }
29 | }
30 |
31 | @Test
32 | public void testUndefinedFunction() {
33 | try {
34 | compiler.compile("undefinedfunction #0");
35 | fail("Compiler did not fail on undefined function.");
36 | } catch (IllegalArgumentException e) {
37 | assert(e.getMessage().startsWith("Unknown function undefinedfunction"));
38 | }
39 | }
40 |
41 | @Test
42 | public void testRedefinedLabel() {
43 | try {
44 | compiler.compile("label:\nlabel:");
45 | fail("Compiler did not fail on redefined label.");
46 | } catch (IllegalArgumentException e) {
47 | assert(e.getMessage().startsWith("Redefined label 'label'"));
48 | }
49 | }
50 |
51 | @Test
52 | public void testCurrentLine() {
53 | try {
54 | compiler.compile("valid:\n\nundefinedfunction #0");
55 | fail("Compiler did not fail on undefined function.");
56 | } catch (IllegalArgumentException e) {
57 | assertEquals(3, compiler.getCurrentLine());
58 | }
59 | }
60 |
61 | @Test
62 | public void testBytesUsed() {
63 | compiler.compile("push8 #1");
64 | assertEquals(1, compiler.getBytesUsed()); // turned into c_1
65 |
66 | compiler.init();
67 | compiler.compile("push8 #42");
68 | assertEquals(2, compiler.getBytesUsed()); // one byte opcode, one byte literal 42
69 |
70 | compiler.init();
71 | compiler.compile("push8 #1\npush8 #0");
72 | assertEquals(2, compiler.getBytesUsed()); // 1 byte c_1, 1 byte c_0
73 |
74 | compiler.init();
75 | compiler.compile("start:end:");
76 | assertEquals(0, compiler.getBytesUsed());
77 |
78 | compiler.init();
79 | compiler.compile("start: \n jmp start");
80 | assertEquals(2, compiler.getBytesUsed()); // 1 byte opcode, 1 byte label addr
81 | }
82 |
83 | @Test
84 | public void testOutputSize() {
85 | assertEquals(256, compiler.compile("").length);
86 | assertEquals(256, compiler.compile(stringRepeat("push8 #0\n", 10)).length);
87 | // No error when overflowing?
88 | assertEquals(256, compiler.compile(stringRepeat("push8 #0\n", 290)).length);
89 | }
90 |
91 | @Test
92 | public void testTooFewArguments() {
93 | try {
94 | compiler.compile("push8");
95 | fail("Compiler did not fail on missing argument.");
96 | } catch (IllegalArgumentException e) {
97 | assert(e.getMessage().startsWith("Wrong number of arguments"));
98 | }
99 | }
100 |
101 | @Test
102 | public void testTooManyArguments() {
103 | try {
104 | compiler.compile("push8 #12, #13");
105 | fail("Compiler did not fail on too many arguments.");
106 | } catch (IllegalArgumentException e) {
107 | assert(e.getMessage().startsWith("Wrong number of arguments"));
108 | }
109 | }
110 |
111 | @Test
112 | public void testUnexpectedArgument() {
113 | try {
114 | compiler.compile("ret #12");
115 | fail("Compiler did not fail on unexpected argument.");
116 | } catch (IllegalArgumentException e) {
117 | assert(e.getMessage().startsWith("Too many arguments"));
118 | }
119 | }
120 |
121 | @Test
122 | public void testAcceptWhitespace() {
123 | byte[] ram1 = compiler.compile("\t push8 \t #0 \t");
124 | byte[] ram2 = compiler.compile("push8 #0");
125 | assert(ramEquals(ram1, ram2));
126 | }
127 |
128 | @Test
129 | public void testWrite8() {
130 | compiler.write8(0xff);
131 | assertEquals((byte) 0xff, compiler.ram[0]);
132 | assertEquals(0x00, compiler.ram[1]); // We're not overflowing
133 |
134 | compiler.init();
135 | compiler.write8(0xcace);
136 | assertEquals((byte) 0xce, compiler.ram[0]); // we're only writing one byte
137 | assertEquals(0x00, compiler.ram[1]);
138 |
139 | // check pc
140 | compiler.write8(0xb00c);
141 | assertEquals(0x0c, compiler.ram[1]);
142 | }
143 |
144 | @Test
145 | public void testWrite16() {
146 | ByteBuffer bb = getByteBuffer(compiler.ram);
147 | compiler.write16(0xffff);
148 | assertEquals((short) 0xffff, bb.getShort(0));
149 | assertEquals(0x00, bb.getShort(2)); // We're not overflowing
150 |
151 | compiler.init();
152 | bb = getByteBuffer(compiler.ram);
153 | compiler.write16(0xfaceb00c);
154 | assertEquals((short) 0xb00c, bb.getShort(0)); // we're only writing two bytes
155 | assertEquals(0x00, bb.getShort(2));
156 |
157 | // check pc
158 | compiler.write16(0xb00c);
159 | assertEquals((short) 0xb00c, bb.getShort(2));
160 | }
161 |
162 | @Test
163 | public void testWrite32() {
164 | ByteBuffer bb = getByteBuffer(compiler.ram);
165 | compiler.write32(0xffffffff);
166 | assertEquals(0xffffffff, bb.getInt(0));
167 | assertEquals(0x00, bb.getInt(4)); // We're not overflowing
168 |
169 | // check pc
170 | compiler.write32(0xfaceb00c);
171 | assertEquals(0xfaceb00c, bb.getInt(4));
172 | }
173 |
174 | @Test
175 | public void testWriteFloat() {
176 | ByteBuffer bb = getByteBuffer(compiler.ram);
177 | compiler.writeFloat(1.23456f);
178 | assertEquals(1.23456f, bb.getFloat(0), 0.1);
179 | assertEquals(0x00, bb.getInt(4)); // We're not overflowing
180 |
181 | // check pc
182 | compiler.writeFloat(2.7182f);
183 | assertEquals(2.7182f, bb.getFloat(4), 0.1);
184 | }
185 |
186 | @Test(expected = IllegalArgumentException.class)
187 | public void testWriteOpcodeTooLarge() {
188 | compiler.writeOp(0b100, 0);
189 | }
190 |
191 | @Test(expected = IllegalArgumentException.class)
192 | public void testWriteOpcodeDataTooLarge() {
193 | compiler.writeOp(0, 0b1000000);
194 | }
195 |
196 | @Test
197 | public void testWriteOpcode() {
198 | compiler.writeOp(0b11, 0b111111);
199 | assertEquals((byte) 0xff, compiler.ram[0]);
200 |
201 | compiler.writeOp(0, 0);
202 | assertEquals((byte) 0, compiler.ram[1]);
203 |
204 | compiler.writeOp(0b11, 0b000000);
205 | assertEquals((byte) 0b00000011, compiler.ram[2]);
206 |
207 | compiler.writeOp(0b00, 0b111111);
208 | assertEquals((byte) 0b11111100, compiler.ram[3]);
209 | }
210 |
211 | @Test(expected = IllegalArgumentException.class)
212 | public void testParseLiteralError() {
213 | Compiler.parseLiteralByte("notaliteral");
214 | }
215 |
216 | @Test(expected = IllegalArgumentException.class)
217 | public void testParseLiteralNAN() {
218 | Compiler.parseLiteralByte("#nan");
219 | }
220 |
221 | @Test
222 | public void testParseLiteral() {
223 | assertEquals(42, Compiler.parseLiteralByte("#42"));
224 | }
225 |
226 | @Test
227 | public void testParseLiteralHex() {
228 | assertEquals(0xff, Compiler.parseLiteralByte("#$ff"));
229 | }
230 |
231 | @Test
232 | public void testParseLiteralNegative() {
233 | assertEquals(0xff, Compiler.parseLiteralByte("#-1"));
234 | }
235 |
236 | @Test
237 | public void testParseLiteralFloats() {
238 | assertEquals(1.23456f, Compiler.parseLiteralFloat("#1.23456"), 0.0001);
239 | assertEquals(12000f, Compiler.parseLiteralFloat("#12e3"), 0.1);
240 | }
241 |
242 | @Test
243 | public void testParseIndirection() {
244 | assertTrue(Compiler.isIndirect("[anything]"));
245 | assertTrue(Compiler.isIndirect("(anything)"));
246 | }
247 |
248 | @Test
249 | public void testPushes() {
250 | compiler.compile("push8 #13 \n push8 37 \n push8 #$ab \n push8 $cd \n" +
251 | "pushf #1.2 \n pushf 12 \n pushf $34");
252 | }
253 |
254 | public void testPushesRelative() {
255 | compiler.compile("label: dbf #0.0 \n push8r label");
256 | compiler.compile("push8r 5 \n push8r $fa");
257 | }
258 |
259 | @Test
260 | public void testPops() {
261 | compiler.compile("pop8 37 \n pop8 $ab \n popf 13 \n popf $3d");
262 | }
263 |
264 | @Test
265 | public void testPopsRelative() {
266 | compiler.compile("label: dbf #0.0 \n pop8r label");
267 | compiler.compile("pop8r 7 \n pop8r $fa");
268 | }
269 |
270 | @Test
271 | public void testPopsIndirect() {
272 | compiler.compile("pop8 [13] \n pop8 [$cd] \n popf [13] \n popf [$cd]");
273 | }
274 |
275 | @Test
276 | public void testJumps() {
277 | compiler.compile("jmp 37 \n jnz 13 \n jmp [11]");
278 | compiler.compile("jmp $37 \n jnz $13 \n jmp [$11]");
279 | }
280 |
281 | @Test
282 | public void testJumpsRelative() {
283 | compiler.compile("label: dbf #0.0 \n jnzr label");
284 | compiler.compile("jnzr 07 \n jnzr $fa");
285 | }
286 |
287 | @Test
288 | public void testPushrLiteralFail() {
289 | try {
290 | compiler.compile("push8r #1");
291 | fail("Compiler did not fail on referencing a literal.");
292 | } catch (IllegalArgumentException e) {
293 | assert(e.getMessage().startsWith("Cannot address a literal"));
294 | }
295 | }
296 |
297 | @Test
298 | public void testPopLiteralFail() {
299 | try {
300 | compiler.compile("pop8 #1");
301 | fail("Compiler did not fail on referencing a literal.");
302 | } catch (IllegalArgumentException e) {
303 | assert(e.getMessage().startsWith("Cannot address a literal"));
304 | }
305 | }
306 |
307 | @Test
308 | public void testNonLiteralFloatDbf() {
309 | try {
310 | compiler.compile("dbf 13.1");
311 | fail("Compiler did not fail on non-literal float.");
312 | } catch (IllegalArgumentException e) {
313 | assert(e.getMessage().startsWith("Float literals must"));
314 | }
315 | }
316 |
317 | @Test
318 | public void testNonLiteralFloatPushf() {
319 | try {
320 | compiler.compile("pushf 13.1");
321 | fail("Compiler did not fail on non-literal float.");
322 | } catch (IllegalArgumentException e) {
323 | assert(e.getMessage().startsWith("Invalid value"));
324 | }
325 | }
326 |
327 | @Test
328 | public void testIllegalFloatPush8() {
329 | try {
330 | compiler.compile("push8 #13.1");
331 | fail("Compiler did not fail on push8 with a float.");
332 | } catch (IllegalArgumentException e) {
333 | assert(e.getMessage().startsWith("Invalid value"));
334 | }
335 | }
336 |
337 | @Test
338 | public void testIllegalIndirect() {
339 | try {
340 | compiler.compile("push8 [0]");
341 | fail("Compiler did not fail on indirect push.");
342 | } catch (IllegalArgumentException e) {
343 | assert(e.getMessage().startsWith("Invalid value"));
344 | }
345 | }
346 |
347 | @Test
348 | public void testPush8LabelAddress() {
349 | byte[] ram = compiler.compile("db8 #0 \n labelAt1: db8 #0 \n push8 &labelAt1");
350 | // Note: If the compiler ever figures out that this can be collapsed to
351 | // one of the constant functions, this test will fail.
352 | assertEquals(1, ram[3]);
353 | }
354 |
355 | @Test
356 | public void testPush8NonlabelAddress() {
357 | try {
358 | compiler.compile("push8 &2");
359 | fail("Compiler did not fail on invalid label referencing.");
360 | } catch (IllegalArgumentException e) {
361 | assert(e.getMessage().startsWith("Invalid label"));
362 | }
363 | }
364 |
365 | @Test
366 | public void testParseComments() {
367 | assertEquals("not a comment", Compiler.parseComments(" not a comment"));
368 | assertEquals("text before", Compiler.parseComments("text before ; this comment"));
369 | assertEquals("multiple", Compiler.parseComments("multiple ; comment ; another"));
370 | }
371 |
372 | @Test
373 | public void testParseLabels() {
374 | assertEquals("", compiler.parseLabels("start:"));
375 | assertEquals(1, compiler.statements.size());
376 | assertEquals("", compiler.parseLabels(" two:more:"));
377 | assertEquals(3, compiler.statements.size());
378 | assertEquals("", compiler.parseLabels(" with : spaces\t: "));
379 | assertEquals(5, compiler.statements.size());
380 | assertEquals("kein label", compiler.parseLabels("kein label"));
381 | }
382 |
383 | @Test
384 | public void testInvalidLabelSpaces() {
385 | try {
386 | compiler.parseLabels("label with spaces:");
387 | fail("Compiler did not fail on invalid label name.");
388 | } catch (IllegalArgumentException e) {
389 | assert(e.getMessage().startsWith("Invalid label name"));
390 | }
391 | }
392 |
393 | @Test
394 | public void testInvalidLabelNumbers() {
395 | try {
396 | compiler.parseLabels("123labelsMustntStartWithNumbers:");
397 | fail("Compiler did not fail on invalid label name.");
398 | } catch (IllegalArgumentException e) {
399 | assert(e.getMessage().startsWith("Invalid label name"));
400 | }
401 | }
402 |
403 | @Test
404 | public void testParseArgs() {
405 | // Parse args assumes that parseLabels and parseComments already ran.
406 | assertArrayEquals(new String[]{}, Compiler.parseArgs(""));
407 | assertArrayEquals(new String[]{"argument1"}, Compiler.parseArgs("argument1"));
408 | assertArrayEquals(new String[]{"one argument"}, Compiler.parseArgs("one argument"));
409 | assertArrayEquals(new String[]{"two", "arguments"}, Compiler.parseArgs("two, arguments"));
410 | }
411 |
412 | @Test
413 | public void testOverflowCode() {
414 | for (int i = 0; i < 256; ++i) {
415 | compiler.write8(0xff); // Fill ram to 100%
416 | }
417 | compiler.write8(42); // should overflow to addr 0
418 |
419 | assertEquals(42, compiler.ram[0]);
420 | }
421 |
422 | /**
423 | * Returns a little endian byte buffer for given ram
424 | * @param ram ram to wrap byte buffer around
425 | * @return little endian byte buffer
426 | */
427 | private ByteBuffer getByteBuffer(byte[] ram) {
428 | ByteBuffer bb = ByteBuffer.wrap(ram);
429 | bb.order(ByteOrder.LITTLE_ENDIAN);
430 |
431 | return bb;
432 | }
433 |
434 |
435 | /**
436 | * Repeats str
437 | *
438 | * @param str string to repeat
439 | * @param times number of times
440 | * @return repeated string
441 | */
442 | private static String stringRepeat(String str, int times) {
443 | StringBuilder builder = new StringBuilder();
444 | for (int i = 0; i < times; ++i) {
445 | builder.append(str);
446 | }
447 |
448 | return builder.toString();
449 | }
450 |
451 | private static boolean ramEquals(byte[] a, byte[] b)
452 | {
453 | if (a.length != b.length) {
454 | return false;
455 | }
456 | for (int i = 0; i < a.length; i++) {
457 | if (a[i] != b[i]) {
458 | return false;
459 | }
460 | }
461 | return true;
462 | }
463 | }
464 |
--------------------------------------------------------------------------------
/test/asmcup/decompiler/DecompilerTest.java:
--------------------------------------------------------------------------------
1 | package asmcup.decompiler;
2 |
3 |
4 | import org.junit.After;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.PrintStream;
10 | import java.io.UnsupportedEncodingException;
11 | import java.nio.charset.Charset;
12 |
13 | import asmcup.compiler.Compiler;
14 |
15 | import static org.junit.Assert.*;
16 |
17 | public class DecompilerTest {
18 | private Decompiler decompiler = null;
19 | private final ByteArrayOutputStream out = new ByteArrayOutputStream();
20 |
21 | @Before
22 | public void setUp() throws UnsupportedEncodingException {
23 | decompiler = new Decompiler(new PrintStream(out, false, "UTF-8"));
24 | }
25 |
26 | @Test
27 | public void testDump() throws UnsupportedEncodingException {
28 | decompiler.dump(0xff, "blabla");
29 | assertEquals(String.format("Lff: blabla%n"), out.toString("UTF-8"));
30 | }
31 |
32 | @Test
33 | public void testDecompile() throws UnsupportedEncodingException {
34 | // This code is not sensible and does not produce a good or valid program
35 | byte[] ram = (new Compiler()).compile(getProgram(
36 | "start:",
37 | "push8 #0",
38 | "push8 #42",
39 | "pushf #42.0",
40 | "pushf start",
41 | "popf $fa",
42 | "pop8 $fb",
43 | "push8 $fc",
44 | "jmp start",
45 | "jnz start",
46 | "jmp ($cc)",
47 | "popf ($a)",
48 | "pop8 [$b]",
49 | "pushf $ab"
50 | ));
51 |
52 | decompiler.decompile(ram);
53 |
54 | assertEquals(getProgram(
55 | // "start:" label produces no output!
56 | "L00: c_0",
57 | "L01: push8 #$2a",
58 | "L03: pushf #4.200000000e+01",
59 | "L08: pushf $00",
60 | "L0a: popf $fa",
61 | "L0c: pop8 $fb",
62 | "L0e: push8 $fc",
63 | "L10: jmp $00",
64 | "L12: jnz $00",
65 | "L14: jmp [$cc]",
66 | "L16: popf [$0a]",
67 | "L18: pop8 [$0b]",
68 | "L1a: pushf $ab"
69 | ), out.toString("UTF-8"));
70 | }
71 |
72 | @Test
73 | public void testIdentityByteConstants() throws UnsupportedEncodingException {
74 | byte[] ram = (new Compiler()).compile(getProgram(
75 | "push8 #-1",
76 | "push8 #0",
77 | "push8 #1",
78 | "push8 #2",
79 | "push8 #3",
80 | "push8 #4",
81 | "push8 #5",
82 | "push8 #6",
83 | "push8 #255",
84 | "push8 #256",
85 | "push8 #257",
86 | "push8 #-1000",
87 | "push8 #1001"
88 | ));
89 |
90 | checkDecompileCompileIdentity(ram);
91 | }
92 |
93 | @Test
94 | public void testIdentityFloatConstants() throws UnsupportedEncodingException {
95 | byte[] ram = (new Compiler()).compile(getProgram(
96 | "pushf #1.0",
97 | "pushf #-0.0",
98 | "pushf #42",
99 | "pushf #NaN",
100 | "pushf #0.1",
101 | "pushf #7.0",
102 | "pushf #7.0e20",
103 | "pushf #13.37e-20",
104 | "pushf #Infinity",
105 | "pushf #-Infinity"
106 | ));
107 |
108 | checkDecompileCompileIdentity(ram);
109 | }
110 |
111 | @Test
112 | public void testFuzzedPattern0() throws UnsupportedEncodingException {
113 | testFuzzedWithPadding(0);
114 | }
115 |
116 | @Test
117 | public void testFuzzedPattern1() throws UnsupportedEncodingException {
118 | testFuzzedWithPadding(1);
119 | }
120 |
121 | @Test
122 | public void testFuzzedPattern4() throws UnsupportedEncodingException {
123 | testFuzzedWithPadding(4);
124 | }
125 |
126 | public void testFuzzedWithPadding(int pad) throws UnsupportedEncodingException {
127 | int step = pad + 1;
128 | byte[] ram = new byte[256];
129 |
130 | int instruction = 0;
131 | for (int repeats = 0; repeats < step; repeats++) {
132 | for (int i = 0; i < 256; i++) {
133 | if (i % step == 0) {
134 | ram[i] = (byte)(instruction++ & 0xFF);
135 | } else {
136 | ram[i] = 0;
137 | }
138 | }
139 |
140 | checkDecompileCompileIdentity(ram);
141 | }
142 | }
143 |
144 | private void checkDecompileCompileIdentity(byte[] ram)
145 | throws UnsupportedEncodingException {
146 | Compiler compiler = new Compiler();
147 | out.reset();
148 |
149 | decompiler.decompile(ram);
150 | byte[] newRam = compiler.compile(out.toString("UTF-8"));
151 |
152 | for (int i = 0; i < 256; i++) {
153 | assertEquals(String.format("Memory differs at %02x", i), ram[i], newRam[i]);
154 | }
155 | }
156 |
157 | private static String getProgram(String... lines) {
158 | StringBuilder ret = new StringBuilder();
159 | for (String line : lines) {
160 | ret.append(line);
161 | ret.append(System.lineSeparator());
162 | }
163 |
164 | return ret.toString();
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/test/asmcup/runtime/ItemTest.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 |
4 | import org.junit.Test;
5 |
6 | import static org.junit.Assert.assertEquals;
7 | import static org.junit.Assert.assertTrue;
8 |
9 | public class ItemTest {
10 | @Test
11 | public void testWithinDistance() {
12 | Item item = new Item.Gold(42);
13 | item.position(42f, 42f);
14 | final int distance = 20;
15 | for (int i = 42 - distance; i < 42 + distance; ++i) {
16 | assertTrue("Failed: (" + i + ",42)", item.withinDistance(i, 42));
17 | assertTrue("Failed: (42, " + i + ")", item.withinDistance(42, i));
18 | }
19 | }
20 |
21 | @Test
22 | public void testCollectGold() {
23 | Robot robot = new Robot(42);
24 | Item.Gold gold = new Item.Gold(10);
25 | assertEquals(0, robot.getGold());
26 | gold.collect(robot);
27 | assertEquals(10, robot.getGold());
28 | }
29 |
30 | @Test
31 | public void testCollectBattery() {
32 | Robot robot = new Robot(42);
33 | Item.Battery battery = new Item.Battery(10);
34 | robot.battery = 0;
35 | assertEquals(0, robot.getBattery());
36 | battery.collect(robot);
37 | assertEquals(1000, robot.getBattery());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/asmcup/runtime/RobotTest.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import asmcup.vm.VM;
4 |
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import java.util.Arrays;
9 |
10 | import static org.junit.Assert.assertEquals;
11 | import static org.junit.Assert.assertNotEquals;
12 | import static org.junit.Assert.assertTrue;
13 |
14 | public class RobotTest {
15 | Robot robot = null;
16 |
17 | @Before
18 | public void setUp() {
19 | robot = new Robot(42);
20 | robot.position(42, 21);
21 | }
22 |
23 | @Test
24 | public void testAccelerometer() {
25 | World world = generateEmptyWorld((int)robot.getX(), (int)robot.getY(), 50);
26 | // Read Accelerometer
27 | VM vm = robot.getVM();
28 | vm.push8(Robot.IO_ACCELEROMETER);
29 | vm.setIO(true);
30 | robot.handleIO(world);
31 |
32 | // We did not drive, so we should expect 0 values.
33 | float x = vm.popFloat();
34 | float y = vm.popFloat();
35 | assertEquals(0, x, 0.001f);
36 | assertEquals(0, y, 0.001f);
37 | assertEquals(x, y, 0.001f);
38 |
39 | // Drive a little bit
40 | robot.setMotor(1.0f);
41 | robot.setSteer(0.5f);
42 | robot.tickHardware(world);
43 |
44 |
45 | vm.push8(Robot.IO_ACCELEROMETER);
46 | vm.setIO(true);
47 | robot.handleIO(world);
48 |
49 | // We drove, so we should not expect 0 values.
50 | x = vm.popFloat();
51 | y = vm.popFloat();
52 | assertNotEquals(0f, x, 0.001f);
53 | assertNotEquals(0f, y, 0.001f);
54 | assertNotEquals(x, y, 0.001f);
55 | }
56 |
57 | @Test
58 | public void testSteer() {
59 | World world = new World();
60 | float previous = robot.getFacing();
61 | robot.tickSteer(world);
62 | assertEquals("Robot steered", previous, robot.getFacing(), 0.0001f);
63 |
64 | robot.setSteer(1.0f);
65 | robot.tickSteer(world);
66 | assertTrue("Robot did not steer.", previous < robot.getFacing());
67 | }
68 |
69 | @Test
70 | public void testMotor() {
71 | World world = generateEmptyWorld((int)robot.getX(), (int)robot.getY(), 50);
72 | float x = robot.getX();
73 | float y = robot.getY();
74 | robot.setMotor(1.0f);
75 | robot.setFacing(0.5f);
76 | robot.tickMotor(world);
77 |
78 | assertNotEquals(x, robot.getX(), 0.00001f);
79 | assertNotEquals(y, robot.getY(), 0.00001f);
80 | }
81 |
82 | @Test
83 | public void testIOMotor() {
84 | World world = new World();
85 | VM vm = robot.getVM();
86 | vm.pushFloat(0.5f);
87 | vm.push8(Robot.IO_MOTOR);
88 | vm.setIO(true);
89 | robot.handleIO(world);
90 | assertEquals(0.5f, robot.getMotor(), 0.0001f);
91 |
92 | vm.pushFloat(1.5f);
93 | vm.push8(Robot.IO_MOTOR);
94 | vm.setIO(true);
95 | robot.handleIO(world);
96 | assertEquals(1.0f, robot.getMotor(), 0.0001f);
97 | }
98 |
99 | @Test
100 | public void testIOSteer() {
101 | World world = new World();
102 | VM vm = robot.getVM();
103 | vm.pushFloat(0.5f);
104 | vm.push8(Robot.IO_STEER);
105 | vm.setIO(true);
106 | robot.handleIO(world);
107 | assertEquals(0.5f, robot.getSteer(), 0.0001f);
108 |
109 | vm.pushFloat(1.5f);
110 | vm.push8(Robot.IO_STEER);
111 | vm.setIO(true);
112 | robot.handleIO(world);
113 | assertEquals(1.0f, robot.getSteer(), 0.0001f);
114 | }
115 |
116 | @Test
117 | public void testIOOverclock() {
118 | World world = new World();
119 | VM vm = robot.getVM();
120 | vm.push8(200);
121 | vm.push8(Robot.IO_OVERCLOCK);
122 | vm.setIO(true);
123 | robot.handleIO(world);
124 | assertEquals(100, robot.getOverclock());
125 |
126 | vm.push8(50);
127 | vm.push8(Robot.IO_OVERCLOCK);
128 | vm.setIO(true);
129 | robot.handleIO(world);
130 | assertEquals(50, robot.getOverclock());
131 | }
132 |
133 | @Test
134 | public void testIOBattery() {
135 | World world = new World();
136 | VM vm = robot.getVM();
137 | robot.battery = Robot.BATTERY_MAX;
138 | vm.push8(Robot.IO_BATTERY);
139 | vm.setIO(true);
140 | robot.handleIO(world);
141 | assertEquals(1.0f, vm.popFloat(), 0.0001f);
142 | }
143 |
144 | @Test
145 | public void testIOLazer() {
146 | World world = new World();
147 | VM vm = robot.getVM();
148 | vm.pushFloat(1.0f);
149 | vm.push8(Robot.IO_LASER);
150 | vm.setIO(true);
151 | robot.handleIO(world);
152 | assertEquals(1.0f, robot.getLazer(), 0.0001f);
153 | }
154 |
155 | @Test
156 | public void testIOCompass() {
157 | World world = new World();
158 | VM vm = robot.getVM();
159 | robot.setFacing(0.0f);
160 | vm.push8(Robot.IO_COMPASS);
161 | vm.setIO(true);
162 | robot.handleIO(world);
163 | assertEquals(0.0f, vm.popFloat(), 0.0001f);
164 |
165 | robot.setFacing((float) Math.PI * 2);
166 | vm.push8(Robot.IO_COMPASS);
167 | vm.setIO(true);
168 | robot.handleIO(world);
169 | assertEquals(0.0f, vm.popFloat(), 0.0001f);
170 | }
171 |
172 | @Test
173 | public void testMark() {
174 | World world = new World();
175 | VM vm = robot.getVM();
176 | vm.push8(0);
177 | vm.push8(42);
178 | vm.push8(Robot.IO_MARK);
179 | vm.setIO(true);
180 | robot.handleIO(world);
181 |
182 | vm.push8(0);
183 | vm.push8(Robot.IO_MARK_READ);
184 | vm.setIO(true);
185 | robot.handleIO(world);
186 | assertEquals(42, vm.pop8());
187 |
188 | // Test reading mark, when not marked
189 | robot.position(100, 100);
190 | vm.push8(0);
191 | vm.push8(Robot.IO_MARK_READ);
192 | vm.setIO(true);
193 | robot.handleIO(world);
194 | assertEquals(0, vm.pop8());
195 | }
196 |
197 | @Test
198 | public void testMarkOffset() {
199 | World world = new World();
200 | VM vm = robot.getVM();
201 | // Mark offset 0
202 | vm.push8(0);
203 | vm.push8(42);
204 | vm.push8(Robot.IO_MARK);
205 | vm.setIO(true);
206 | robot.handleIO(world);
207 | // Mark offset 1
208 | vm.push8(1);
209 | vm.push8(4);
210 | vm.push8(Robot.IO_MARK);
211 | vm.setIO(true);
212 | robot.handleIO(world);
213 |
214 | // Read both
215 | vm.push8(1);
216 | vm.push8(Robot.IO_MARK_READ);
217 | vm.setIO(true);
218 | robot.handleIO(world);
219 | assertEquals(4, vm.pop8());
220 |
221 | vm.push8(0);
222 | vm.push8(Robot.IO_MARK_READ);
223 | vm.setIO(true);
224 | robot.handleIO(world);
225 | assertEquals(42, vm.pop8());
226 | }
227 |
228 | @Test
229 | public void testSensorNothing() {
230 | World world = generateEmptyWorld((int)robot.getX(), (int)robot.getY(), Robot.RAY_RANGE + 10);
231 | world.addRobot(robot);
232 | VM vm = robot.getVM();
233 |
234 | vm.push8(Robot.IO_SENSOR);
235 | vm.setIO(true);
236 | robot.handleIO(world);
237 |
238 | assertEquals(vm.pop8() & 0b111111, 0);
239 | assertEquals(vm.popFloat(), Robot.RAY_RANGE, 5.0f);
240 | }
241 |
242 | @Test
243 | public void testSensorWall() {
244 | testSensorHitTile(TILE.WALL, Robot.SENSOR_WALL);
245 | }
246 |
247 | @Test
248 | public void testSensorObstacle() {
249 | testSensorHitTile(TILE.OBSTACLE, Robot.SENSOR_OBSTACLE);
250 | }
251 |
252 | @Test
253 | public void testSensorHazard() {
254 | testSensorHitTile(TILE.HAZARD, Robot.SENSOR_HAZARD);
255 | }
256 |
257 | protected void testSensorHitTile(int tile, int expectedSensorValue) {
258 | World world = generateEmptyWorld((int)robot.getX(), (int)robot.getY(), 100);
259 | world.addRobot(robot);
260 | VM vm = robot.getVM();
261 | float xOffset = 60.0f;
262 | world.setTileXY(robot.getX() + xOffset, robot.getY(), tile);
263 |
264 | vm.push8(Robot.IO_SENSOR);
265 | vm.setIO(true);
266 | robot.handleIO(world);
267 | // Saw a wall...
268 | assertEquals(vm.pop8() & 0b111111, expectedSensorValue);
269 | // ...closeby.
270 | assertEquals(vm.popFloat(), xOffset, World.TILE_SIZE);
271 | }
272 |
273 | @Test
274 | public void testSensorOtherBot() {
275 | World world = generateEmptyWorld((int)robot.getX(), (int)robot.getY(), 50);
276 | world.addRobot(robot);
277 | VM vm = robot.getVM();
278 |
279 | float xOffset = 30.0f;
280 |
281 | Robot dummy = new Robot(13);
282 | dummy.position(robot.getX() + xOffset, robot.getY());
283 | world.addRobot(dummy);
284 |
285 | vm.push8(Robot.IO_SENSOR);
286 | vm.setIO(true);
287 | robot.handleIO(world);
288 | // Saw a robot...
289 | assertEquals(vm.pop8() & 0b111111, Robot.SENSOR_ROBOT);
290 | // ...closeby.
291 | assertEquals(vm.popFloat(), xOffset, Robot.COLLIDE_RANGE);
292 | }
293 |
294 | @Test
295 | public void testBeamAngle() {
296 | World world = generateEmptyWorld((int)robot.getX(), (int)robot.getY(), 50);
297 | VM vm = robot.getVM();
298 | // Face west
299 | robot.setFacing(0);
300 | // Beam south?
301 | vm.pushFloat(1.0f);
302 | vm.push8(Robot.IO_BEAM_DIRECTION);
303 | vm.setIO(true);
304 | robot.handleIO(world);
305 | assertEquals((float)Math.PI/2, robot.getBeamAngle(), 0.0001f);
306 |
307 | // Face north
308 | robot.setFacing(-(float)Math.PI/2);
309 | // Beam north-west?
310 | vm.pushFloat(-0.5f);
311 | vm.push8(Robot.IO_BEAM_DIRECTION);
312 | vm.setIO(true);
313 | robot.handleIO(world);
314 | assertEquals((float)-Math.PI * 0.75f, robot.getBeamAngle(), 0.0001f);
315 | }
316 |
317 | @Test
318 | public void testLazerDamage() {
319 | World world = generateEmptyWorld((int)robot.getX(), (int)robot.getY(), 50);
320 | Robot dummy = new Robot(13);
321 | dummy.position(robot.getX() + 30, robot.getY());
322 | world.addRobot(robot);
323 | world.addRobot(dummy);
324 |
325 | int initialBattery = dummy.getBattery();
326 | robot.setLazer(1.0f);
327 | robot.tick(world);
328 | assert(dummy.getBattery() < initialBattery);
329 | }
330 |
331 | private World generateEmptyWorld(int x, int y, int radius) {
332 | World world = new World();
333 | for (int i = x - radius; i < x + radius; i += World.TILE_SIZE) {
334 | for (int j = y - radius; j < y + radius; j += World.TILE_SIZE) {
335 | world.setTileXY(i, j, TILE.GROUND);
336 | }
337 | }
338 |
339 | return world;
340 | }
341 | }
342 |
--------------------------------------------------------------------------------
/test/asmcup/runtime/TileTest.java:
--------------------------------------------------------------------------------
1 | package asmcup.runtime;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import static org.junit.Assert.assertEquals;
9 |
10 | public class TileTest {
11 | @Test
12 | public void testIsSolid() {
13 | Map map = new HashMap() {{
14 | put(TILE.GROUND, false);
15 | put(TILE.HAZARD, false);
16 | put(TILE.WALL, true);
17 | put(TILE.OBSTACLE, true);
18 | put(TILE.FLOOR, false);
19 | }};
20 | World world = new World();
21 | for (Map.Entry entry : map.entrySet()) {
22 | world.setTileXY(0, 0, entry.getKey());
23 | assertEquals("Failed key:" + entry.getKey(), entry.getValue(), world.isSolid(0, 0));
24 | assertEquals("Failed key:" + entry.getKey(), entry.getValue(),
25 | !world.canRobotGoTo(World.TILE_HALF, World.TILE_HALF));
26 | }
27 | }
28 |
29 | @Test
30 | public void testIsSpawnable() {
31 | Map map = new HashMap() {{
32 | put(TILE.GROUND, true);
33 | put(TILE.HAZARD, false);
34 | put(TILE.WALL, false);
35 | put(TILE.OBSTACLE, false);
36 | put(TILE.FLOOR, true);
37 | }};
38 | World world = new World();
39 | for (Map.Entry entry : map.entrySet()) {
40 | world.setTileXY(0, 0, entry.getKey());
41 | assertEquals("Failed entry: " + entry.getKey(), entry.getValue(),
42 | world.canSpawnRobotAt(World.TILE_HALF, World.TILE_HALF));
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/asmcup/vm/VMTest.java:
--------------------------------------------------------------------------------
1 | package asmcup.vm;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import java.io.*;
8 | import java.nio.ByteBuffer;
9 | import java.nio.ByteOrder;
10 |
11 | import static org.junit.Assert.assertEquals;
12 |
13 | public class VMTest {
14 |
15 | private VM vm = null;
16 |
17 |
18 | @Before
19 | public void setUp() {
20 | vm = new VM();
21 | }
22 |
23 | @Test(expected = IllegalArgumentException.class)
24 | public void testCtorWithRamError() {
25 | new VM(new byte[255]);
26 | }
27 |
28 | @Test
29 | public void testCtorWithRam() {
30 | byte[] ram = new byte[256];
31 | vm = new VM(ram);
32 | assert(vm.getMemory() == ram); // point to the same array
33 | }
34 |
35 | @Test
36 | public void testSave() throws IOException {
37 | byte[] ram = new byte[256];
38 | for (int i = 0; i < ram.length; ++i) {
39 | ram[i] = (byte) i;
40 | }
41 |
42 | vm = new VM(ram);
43 | ByteArrayOutputStream baos = new ByteArrayOutputStream(260);
44 | DataOutputStream out = new DataOutputStream(baos);
45 | vm.save(out);
46 |
47 | ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
48 | VM saved = new VM(new DataInputStream(bais));
49 | Assert.assertEquals(vm, saved);
50 | }
51 |
52 | @Test
53 | public void testWrite8() {
54 | vm.write8(0, 0xff);
55 | assertEquals((byte) 0xff, vm.getMemory()[0]);
56 | assertEquals(0x00, vm.getMemory()[1]); // We're not overflowing
57 |
58 | vm = new VM();
59 | vm.write8(0, 0xcace);
60 | assertEquals((byte) 0xce, vm.getMemory()[0]); // we're only writing one byte
61 | assertEquals(0x00, vm.getMemory()[1]);
62 | }
63 |
64 | @Test
65 | public void testWrite16() {
66 | ByteBuffer bb = getByteBuffer(vm.getMemory());
67 | vm.write16(0, 0xffff);
68 | assertEquals((short) 0xffff, bb.getShort(0));
69 | assertEquals(0x00, bb.getShort(2)); // We're not overflowing
70 |
71 | vm = new VM();
72 | bb = getByteBuffer(vm.getMemory());
73 | vm.write16(0, 0xfaceb00c);
74 | assertEquals((short) 0xb00c, bb.getShort(0)); // we're only writing two bytes
75 | assertEquals(0x00, bb.getShort(2));
76 | }
77 |
78 | @Test
79 | public void testWrite32() {
80 | ByteBuffer bb = getByteBuffer(vm.getMemory());
81 | vm.write32(0, 0xffffffff);
82 | assertEquals(0xffffffff, bb.getInt(0));
83 | assertEquals(0x00, bb.getInt(4)); // We're not overflowing
84 | }
85 |
86 | @Test
87 | public void testWriteFloat() {
88 | ByteBuffer bb = getByteBuffer(vm.getMemory());
89 | vm.writeFloat(0, 1.23456f);
90 | assertEquals(1.23456f, bb.getFloat(0), 0.1);
91 | assertEquals(0x00, bb.getInt(4)); // We're not overflowing
92 | }
93 |
94 | @Test
95 | public void testRead8() {
96 | vm.write8(0, 0xff);
97 | vm.write8(1, 0xee);
98 | assertEquals(0xff, vm.read8());
99 | assertEquals(0xff, vm.read8(0));
100 | assertEquals(0xee, vm.read8()); // increasing pc
101 | assertEquals(0xff, vm.read8(0));
102 | }
103 |
104 | @Test
105 | public void testRead8Indirect() {
106 | vm.write8(0, 42);
107 | vm.write8(42, 0xff);
108 | assertEquals(0xff, vm.read8indirect());
109 | }
110 |
111 | @Test
112 | public void testRead16() {
113 | vm.write16(0, 0xffff);
114 | assertEquals(0xffff, vm.read16());
115 | }
116 |
117 | @Test
118 | public void testRead32() {
119 | vm.write32(0, 0xffffffff);
120 | assertEquals(0xffffffff, vm.read32());
121 | }
122 |
123 | @Test
124 | public void testReadFloat() {
125 | vm.writeFloat(0, 1.23456f);
126 | assertEquals(1.23456f, vm.readFloat(), 0.00001f);
127 | }
128 |
129 | @Test
130 | public void testReadFloatIndirect() {
131 | vm.write8(0, 42);
132 | vm.writeFloat(42, 1.23456f);
133 | assertEquals(1.23456f, vm.readFloatIndirect(), 0.0001f);
134 | }
135 |
136 |
137 | @Test
138 | public void testPush8() {
139 | vm.push8(0xff);
140 | vm.push8(0xee);
141 | assertEquals(0xee, vm.pop8());
142 | assertEquals(0xff, vm.pop8());
143 | }
144 |
145 | @Test
146 | public void testPush16() {
147 | vm.push16(0xffff);
148 | vm.push16(0xeeee);
149 | assertEquals(0xeeee, vm.pop16());
150 | assertEquals(0xffff, vm.pop16());
151 | }
152 |
153 | @Test
154 | public void testPush32() {
155 | vm.push32(0xffffffff);
156 | vm.push32(0xeeeeeeee);
157 | assertEquals(0xeeeeeeee, vm.pop32());
158 | assertEquals(0xffffffff, vm.pop32());
159 | }
160 |
161 | @Test
162 | public void testPushFloat() {
163 | vm.pushFloat(1.23456f);
164 | vm.pushFloat(2.7182f);
165 | assertEquals(2.7182f, vm.popFloat(), 0.0001f);
166 | assertEquals(1.23456f, vm.popFloat(), 0.0001f);
167 | }
168 |
169 | @Test
170 | public void testStackPointer() {
171 | assertEquals(0xff, vm.getStackPointer());
172 | vm.push8(0xff);
173 | assertEquals(0xfe, vm.getStackPointer());
174 | vm.push8(0xff);
175 | vm.pop8();
176 | assertEquals(0xfe, vm.getStackPointer());
177 | vm.pop8();
178 | assertEquals(0xff, vm.getStackPointer());
179 | }
180 |
181 | @Test
182 | public void testStackOpsAdd() {
183 | vm.push8(13);
184 | vm.push8(37);
185 | vm.op_func(VMFuncs.F_ADD8);
186 | Assert.assertEquals(vm.pop8(), 50);
187 | }
188 |
189 | @Test
190 | public void testStackOpsXor() {
191 | vm.push8(0b001101);
192 | vm.push8(0b010111);
193 | vm.op_func(VMFuncs.F_XOR);
194 | Assert.assertEquals(vm.pop8(), 0b011010);
195 | }
196 |
197 | @Test
198 | public void testStackOpsDup8() {
199 | vm.push8(0x2A);
200 | vm.op_func(VMFuncs.F_DUP8);
201 | Assert.assertEquals(vm.pop8(), 0x2A);
202 | Assert.assertEquals(vm.pop8(), 0x2A);
203 | }
204 |
205 | @Test
206 | public void testStackOpsDupf() {
207 | vm.pushFloat(0.1f);
208 | vm.op_func(VMFuncs.F_DUPF);
209 | // Yes, binary equivalence should remain.
210 | Assert.assertTrue(vm.popFloat() == 0.1f);
211 | Assert.assertTrue(vm.popFloat() == 0.1f);
212 | }
213 |
214 | @Test
215 | public void testStackOpsSubroutine() {
216 | int startPC = vm.getProgramCounter();
217 | vm.push8(0xab);
218 | vm.op_func(VMFuncs.F_JSR);
219 | Assert.assertEquals(vm.getProgramCounter(), 0xab);
220 | vm.op_func(VMFuncs.F_RET);
221 | Assert.assertEquals(vm.getProgramCounter(), startPC);
222 | }
223 |
224 | @Test
225 | public void testFetch8() {
226 | vm.push8(13);
227 | vm.push8(37);
228 | vm.push8(1);
229 | vm.op_func(VMFuncs.F_FT8);
230 | Assert.assertTrue(vm.pop8() == 13);
231 | }
232 |
233 | @Test
234 | public void testFetchFloat() {
235 | vm.pushFloat(1.3f);
236 | vm.pushFloat(3.7f);
237 | vm.push8(4);
238 | vm.op_func(VMFuncs.F_FTF);
239 | Assert.assertTrue(vm.popFloat() == 1.3f);
240 | }
241 |
242 | /**
243 | * Returns a little endian byte buffer for given ram
244 | * @param ram ram to wrap byte buffer around
245 | * @return little endian byte buffer
246 | */
247 | private ByteBuffer getByteBuffer(byte[] ram) {
248 | ByteBuffer bb = ByteBuffer.wrap(ram);
249 | bb.order(ByteOrder.LITTLE_ENDIAN);
250 |
251 | return bb;
252 | }
253 | }
254 |
--------------------------------------------------------------------------------