├── .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 | [![Build Status](https://travis-ci.org/asmcup/runtime.svg)](https://travis-ci.org/asmcup/runtime) 2 | [![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asmcup/Lobby) 3 | 4 | Buy Me A
 6 | Coffee 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 | ![](http://i.imgur.com/Snvjuon.gif) 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 | --------------------------------------------------------------------------------