Redcode is the assembly language of Core War. It is small: 16 opcodes, 7 modifiers and 8 addressing modes. Every warrior, from the one-line Imp to a tournament winner, is built from these pieces. This page shows what each piece does, one animation at a time. At the end there is a debugger where you can watch any warrior run, step by step, and go back in time.
All the animations run on the same simulator as the arena (ICWS'94 rules, an 8000-cell core), so what you see here is exactly what happens in a battle.
An instruction#
Every memory cell holds exactly one instruction. There are no bytes, no registers and no separate data memory: numbers are stored in the two fields of an instruction, usually a DAT.
Addresses are relative, and the core is a ring#
There is no address 0 in Redcode. Every number is a distance from the instruction that is running. 1 is the next cell, -1 is the previous one. The core is circular, so counting past the last cell brings you back to the first. A warrior can be loaded anywhere and still work, because it never refers to a fixed place.
How one instruction runs#
The simulator runs every instruction in the same five phases. The animations below show each phase with the same colours:
- Fetch. The warrior whose turn it is takes the first process from its queue and reads the instruction at that address.
- A-operand (amber). Work out which cell the A-operand points to, following pointers if the mode is indirect.
- B-operand (violet). The same for B.
- Effect. Do the work: copy, add, compare, jump… Green marks a write, white a jump, red a dying process.
- Re-queue. Put the process back at the end of the queue with its next address. A process that ran a
DATis not put back: it is dead.
In every demo, Step ▸ runs one instruction and animates its phases. Tick pause at each phase to go through them at your own pace. Coloured bars on the left of an address show which warrior wrote that cell, and the round markers on the right are the processes waiting there.
Addressing modes#
The mode is the character in front of a number. It decides how the number is turned into an address.
| Mode | Name | Where the operand points |
|---|---|---|
# | immediate | at the instruction itself; the number is just a value |
$ | direct | the cell number cells away (the default) |
* | A-indirect | go to that cell, then go on by the value in its A-field |
@ | B-indirect | the same, using the B-field |
{ | A-predecrement | like *, but first subtract 1 from that A-field |
< | B-predecrement | like @, but first subtract 1 from that B-field |
} | A-postincrement | like *, and add 1 to that A-field afterwards |
> | B-postincrement | like @, and add 1 to that B-field afterwards |
The demo copies (MOV.I) something into dest. Only the A-mode changes, and each mode picks a different source. The pointer cell ptr holds #3 in its A-field and #1 in its B-field. Pick a mode and press Step ▸:
The indirect modes are what make Redcode programs compact. MOV bomb, @bomb drops a bomb wherever bomb’s own B-field points. The increment modes turn a cell into a moving pointer: MOV >-1, }-1 copies a line and advances two pointers in a single instruction.
Modifiers#
The modifier after the dot says which fields of the source (A) instruction are used and which fields of the destination (B) instruction are changed.
| Modifier | From A | To B |
|---|---|---|
.A | A-field | A-field |
.B | B-field | B-field |
.AB | A-field | B-field |
.BA | B-field | A-field |
.F | both fields | both fields |
.X | both fields | both fields, crossed (A→B, B→A) |
.I | the whole instruction | the whole instruction (for arithmetic, the same as .F) |
The source here is SPL #7, #9 and the destination DAT #10, #20, so you can see which number goes where. Try MOV.X, ADD.F and SEQ.I:
If you leave the modifier out, the assembler picks one from the opcode and the modes. For example MOV 0, 1 becomes MOV.I, and ADD #4, 3 becomes ADD.AB. Writing modifiers explicitly makes code easier to read.
The instructions#
DAT: data, and death#
DAT holds numbers. A process that tries to execute it is removed. A warrior with no processes left loses. Most bombs in Core War are DATs.
MOV: copy#
MOV copies from the A-address to the B-address. With .I it copies the whole instruction, which is how warriors copy themselves and drop bombs. The Imp is a single MOV.I 0, 1: it copies itself to the next cell and then runs that copy. Press Run and watch it walk.
ADD, SUB, MUL, DIV, MOD: arithmetic#
Arithmetic changes fields of the B-instruction, using the A-values. All results wrap around the core size (8000), so there are no negative numbers inside the core: -1 is stored as 7999. Dividing by zero kills the process.
JMP: jump#
The process continues at the A-address. The B-operand is not used, but it is still evaluated, so JMP 0, <5 jumps and decrements a pointer at the same time.
JMZ and JMN: conditional jumps#
JMZ jumps to A if the B-value is zero. JMN jumps if it is not zero. With .F or .I both fields are tested.
DJN: decrement and jump#
DJN subtracts 1 from the B-target and jumps to A if the result is not zero. It is the classic loop counter, and because the decrement is a write, DJN can also be used to damage enemy code.
SEQ, SNE, SLT: compare and skip#
Comparisons don’t jump. If the test holds, they skip the next instruction. SEQ (also written CMP) tests for equality, SNE for inequality and SLT for “A is less than B”. Scanners use them to tell empty cells from enemy code.
SPL: a new process#
SPL starts a new process at the A-address. The new process goes to the end of the warrior’s queue, after the current one, which continues with the next instruction. A warrior can have up to 8000 processes.
NOP#
NOP does nothing. Its operands are still evaluated, so the increment and decrement modes still work.
Processes and turns#
Each warrior has a queue of processes. The simulator alternates between warriors: one instruction of warrior 1, one of warrior 2, and so on. A warrior with more processes does not get more turns. Its turns are shared among its processes, so each of them runs more slowly. Here warrior 1 splits into four processes and warrior 2 has one. Watch the queues:
Putting it together: the Dwarf#
dwarf ADD.AB #4, bomb ; move the target 4 cells further
MOV.I bomb, @bomb ; drop the bomb where its B-field points
JMP dwarf
bomb DAT #0, #0Three instructions in a loop. ADD.AB adds 4 to the B-field of bomb. MOV.I bomb, @bomb follows that B-field (B-indirect) and copies the DAT there. JMP goes back. Press Run and watch the bombs land 4, 8, 12… cells away. When the target leaves the window, the arrow points off the edge and shows the address.
The debugger#
Load any warrior, optionally with an opponent, and take it apart:
- Step ▸ (→) runs one instruction with the full animation. At the phase by phase speed, each click shows one phase.
- ◂ Back (←) goes back one instruction. The debugger saves the core every 1000 steps and replays the rest, so you can rewind a long way.
- Run ▶ (Space) animates at the slow speeds and runs without animation at the fast ones. It stops at breakpoints: click the dot column next to an address.
- Process queues show every warrior’s processes in turn order. The highlighted one runs next. Click an address to jump there.
- Core map shows all 8000 cells. Hover to read a cell, click to open it in the listing. The white frame is the part shown in the listing.
- Recent steps lists the last instructions of both warriors and what they did.
- Edit the source to change a warrior and load it again.
Things to try:
- Load Mice and step through its copy loop at the phase by phase speed. Watch
<destmove the pointer backwards before each copy. - Load Paper, run it at 2 000 / s for a moment, then step back and find the line where the first copy starts running.
- Load Scanner against Dwarf, set a breakpoint on the
MOV.I trap, @ptrline and see what the scanner found when it stops.