Skip to content

Instantly share code, notes, and snippets.

@samneggs
Last active September 2, 2025 04:53
Show Gist options
  • Save samneggs/61c5db91a51dd601fb3e05539dc01c27 to your computer and use it in GitHub Desktop.
Save samneggs/61c5db91a51dd601fb3e05539dc01c27 to your computer and use it in GitHub Desktop.
Robot War on Pi Pico 2 in MicroPython
================================================================================
ROBOT CODE DEBUG - 32-bit Opcode Format
================================================================================
Robot: ROBOT1
Active: True
--------------------------------------------------------------------------------
1: ; SAMPLE ROBOT 'SCANNER' ; (comment/empty)
2: ]START ; LABEL -> addr 0
3: ] DAMAGE TO D ; [ 0] MOV op1: 4 REG op2: 30 imm: 0
4: ]SCAN ; LABEL -> addr 1
5: ] IF DAMAGE # D GOTO MOVE ; [ 1] CMP op1: 30 REG op2: 4 imm: 0
; [ 2] BNE op1: 0 REG op2: 0 imm: 11
6: ] AIM + 4 TO AIM ; [ 3] ADD op1: 27 IMM op2: 0 imm: 4
7: ]SPOT ; LABEL -> addr 4
8: ] AIM TO RADAR ; [ 4] MOV op1: 29 REG op2: 27 imm: 0
9: ] IF RADAR > 0 GOTO SCAN ; [ 5] CMP op1: 29 IMM op2: 0 imm: 0
; [ 6] BGT op1: 0 REG op2: 0 imm: 1
10: ] 0 - RADAR TO SHOT ; [ 7] MOV op1: 40 IMM op2: 0 imm: 0
; [ 8] SUB op1: 40 REG op2: 29 imm: 0
; [ 9] MOV op1: 28 REG op2: 40 imm: 0
11: ] GOTO SPOT ; [ 10] GOTO op1: 0 REG op2: 0 imm: 4
12: ]MOVE ; LABEL -> addr 11
13: ] 230 TO RANDOM ; [ 11] MOV op1: 33 IMM op2: 0 imm: 230
14: ] RANDOM TO H ; [ 12] MOV op1: 8 REG op2: 33 imm: 0
15: ] 150 TO RANDOM ; [ 13] MOV op1: 33 IMM op2: 0 imm: 150
16: ] RANDOM TO V ; [ 14] MOV op1: 22 REG op2: 33 imm: 0
17: ]MOVEX ; LABEL -> addr 15
18: ] H - X * 100 TO SPEEDX ; [ 15] MOV op1: 41 REG op2: 8 imm: 0
; [ 16] SUB op1: 41 REG op2: 24 imm: 0
; [ 17] MUL op1: 41 IMM op2: 0 imm: 100
; [ 18] MOV op1: 31 REG op2: 41 imm: 0
19: ] IF H - X > 10 GOTO MOVEX ; [ 19] MOV op1: 43 REG op2: 8 imm: 0
; [ 20] SUB op1: 43 REG op2: 24 imm: 0
; [ 21] MOV op1: 42 REG op2: 43 imm: 0
; [ 22] CMP op1: 42 IMM op2: 0 imm: 10
; [ 23] BGT op1: 0 REG op2: 0 imm: 15
20: ] IF H - X < -10 GOTO MOVEX ; [ 24] MOV op1: 45 REG op2: 8 imm: 0
; [ 25] SUB op1: 45 REG op2: 24 imm: 0
; [ 26] MOV op1: 44 REG op2: 45 imm: 0
; [ 27] CMP op1: 44 IMM op2: 0 imm: -10
; [ 28] BLT op1: 0 REG op2: 0 imm: 15
21: ] 0 TO SPEEDX ; [ 29] MOV op1: 31 IMM op2: 0 imm: 0
22: ]MOVEY ; LABEL -> addr 30
23: ] V - Y * 100 TO SPEEDY ; [ 30] MOV op1: 46 REG op2: 22 imm: 0
; [ 31] SUB op1: 46 REG op2: 25 imm: 0
; [ 32] MUL op1: 46 IMM op2: 0 imm: 100
; [ 33] MOV op1: 32 REG op2: 46 imm: 0
24: ] IF V - Y > 10 GOTO MOVEY ; [ 34] MOV op1: 40 REG op2: 22 imm: 0
; [ 35] SUB op1: 40 REG op2: 25 imm: 0
; [ 36] MOV op1: 47 REG op2: 40 imm: 0
; [ 37] CMP op1: 47 IMM op2: 0 imm: 10
; [ 38] BGT op1: 0 REG op2: 0 imm: 30
25: ] IF V - Y < -10 GOTO MOVEY ; [ 39] MOV op1: 42 REG op2: 22 imm: 0
; [ 40] SUB op1: 42 REG op2: 25 imm: 0
; [ 41] MOV op1: 41 REG op2: 42 imm: 0
; [ 42] CMP op1: 41 IMM op2: 0 imm: -10
; [ 43] BLT op1: 0 REG op2: 0 imm: 30
26: ] 0 TO SPEEDY ; [ 44] MOV op1: 32 IMM op2: 0 imm: 0
27: ] GOTO START ; [ 45] GOTO op1: 0 REG op2: 0 imm: 0
29: ; (comment/empty)
--------------------------------------------------------------------------------
Total Instructions: 46
================================================================================
; SAMPLE ROBOT 'SCANNER'
]START
] DAMAGE TO D
]SCAN
] IF DAMAGE # D GOTO MOVE
] AIM + 4 TO AIM
]SPOT
] AIM TO RADAR
] IF RADAR > 0 GOTO SCAN
] 0 - RADAR TO SHOT
] GOTO SPOT
]MOVE
] 230 TO RANDOM
] RANDOM TO H
] 150 TO RANDOM
] RANDOM TO V
]MOVEX
] H - X * 100 TO SPEEDX
] IF H - X > 10 GOTO MOVEX
] IF H - X < -10 GOTO MOVEX
] 0 TO SPEEDX
]MOVEY
] V - Y * 100 TO SPEEDY
] IF V - Y > 10 GOTO MOVEY
] IF V - Y < -10 GOTO MOVEY
] 0 TO SPEEDY
] GOTO START
]
# RobotWar 240x160
from st7796 import LCD_3inch5 # Import LCD driver for 3.5" display
from random import randint # Import random number generator
from machine import freq, I2C, Pin # Import machine control functions
import time, _thread, gc, framebuf, array # Import core system modules
from time import sleep_ms, sleep_us # Import timing functions
from draw_number import Draw_number # Import number drawing module
from math import sin, cos, radians # Import math functions for trigonometry
from sys import exit # Import system exit function
import os # Import operating system interface
MAXSCREEN_X = const(240) # Screen width in pixels
MAXSCREEN_Y = const(160) # Screen height in pixels
SCALE = const(13) # Fixed point scaling factor for coordinates
SHOWING = const(0) # Display state flag index
EXIT = const(1) # Exit state flag index
FIRE_COOL_DOWN = const(50) # Gun cooldown period in frames
FULL_HEALTH = const(100) # Maximum robot health points
MAX_DAMAGE = const(30) # Maximum damage per missile hit
COLLISION_DAMAGE = const(25) # Maximum damage per collision
MUZZLE_FLASH_DURATION = const(7) # Muzzle flash display frames
MUZZLE_FLASH_LINES = const(6) # Number of muzzle flash rays
MUZZLE_FLASH_LENGTH = const(12) # Length of muzzle flash rays
BOT_SCALE = const(100)
THREADING = const(1)
BROWN = const(0x6092) # Brown color constant
BLUE = const(0b_11111_00000_00000) # Blue color constant
GREEN = const(0b111_00000_00000_111) # Green color constant
YELLOW = const(0xff) # Yellow color constant
PINK = const(0x1ff8) # Pink color constant
PURPLE = const(0x1188) # Purple color constant
RED = const(0b_00000_11111_00000) # Red color constant
GREY = const(0b000_11000_11000_110) # Grey color constant
WHITE = const(0xffff) # White color constant
LT_BLUE= const(0b_11111_00101_00101) # Light blue color constant
FPS_CORE0 = const(0) # Core 0 FPS counter index
FPS_CORE1 = const(1) # Core 1 FPS counter index
SCORE = const(2) # Score display index
LIVES = const(3) # Lives display index
HEALTH = const(4) # Health display index
MEM_FREE = const(5) # Free memory display index
NUM_VALUES = const(10) # Total number of display values
SPRITES_RAW = bytearray([ # Raw sprite data for robots
0b11111111, # Robot sprite pattern line 0
0b10000001, # Robot sprite pattern line 1
0b10000001, # Robot sprite pattern line 2
0b10000001, # Robot sprite pattern line 3
0b10000001, # Robot sprite pattern line 4
0b10000001, # Robot sprite pattern line 5
0b10000001, # Robot sprite pattern line 6
0b11111111, # Robot sprite pattern line 7
0b11111111, # Alternate robot sprite line 0
0b10000001, # Alternate robot sprite line 1
0b10000001, # Alternate robot sprite line 2
0b10011001, # Alternate robot sprite line 3
0b10011001, # Alternate robot sprite line 4
0b10000001, # Alternate robot sprite line 5
0b10000001, # Alternate robot sprite line 6
0b11111111, # Alternate robot sprite line 7
0b01111110, # Third robot sprite line 0
0b10000001, # Third robot sprite line 1
0b10000001, # Third robot sprite line 2
0b10011001, # Third robot sprite line 3
0b10011001, # Third robot sprite line 4
0b10000001, # Third robot sprite line 5
0b10000001, # Third robot sprite line 6
0b01111110, # Third robot sprite line 7
0b00111100, # Fourth robot sprite line 0
0b01000010, # Fourth robot sprite line 1
0b10000001, # Fourth robot sprite line 2
0b10000001, # Fourth robot sprite line 3
0b10000001, # Fourth robot sprite line 4
0b10000001, # Fourth robot sprite line 5
0b01000010, # Fourth robot sprite line 6
0b00111100, # Fourth robot sprite line 7
0b10111101, # Fifth robot sprite line 0
0b01000010, # Fifth robot sprite line 1
0b10000001, # Fifth robot sprite line 2
0b10000001, # Fifth robot sprite line 3
0b10000001, # Fifth robot sprite line 4
0b10000001, # Fifth robot sprite line 5
0b01000010, # Fifth robot sprite line 6
0b10111101 # Fifth robot sprite line 7
])
NUM_BOTS = const(5) # Number of robots in battle
BOT_X = const(0) # Robot X position parameter index
BOT_Y = const(1) # Robot Y position parameter index
BOT_SPEED_X = const(2) # Robot X velocity parameter index
BOT_SPEED_Y = const(3) # Robot Y velocity parameter index
GUN_AIM = const(4) # Gun aim angle parameter index
GUN_DIST = const(5) # Gun distance parameter index
RADAR_AIM = const(6) # Radar aim angle parameter index
RADAR_DIST = const(7) # Radar distance parameter index
BOT_SPRITE = const(8) # Robot sprite index parameter
BOT_BOUNCE = const(9) # Robot wall bounce flag parameter
FIRE_COOL = const(10) # Gun firing cooldown parameter
DAMAGE = const(11) # Robot damage level parameter
MUZZLE_FLASH_TIMER = const(12) # Muzzle flash timer parameter
BOT_PARAMS = const(14) # Total robot parameters per bot
GAME_TIME = const(0) # Game time counter index
GAME_END = const(1)
GAME_WINNER = const(2)
GAME_PARAMS = const(10) # Total game parameters
NUM_EXPLOSIONS = const(20) # Maximum simultaneous explosions
EXP_X = const(0) # Explosion X position index
EXP_Y = const(1) # Explosion Y position index
EXP_DURATION = const(2) # Explosion duration timer index
EXPLODE_PARAMS = const(3) # Parameters per explosion
MAX_PROGRAM_SIZE = const(256) # Maximum robot program instructions
MAX_REGISTERS = const(35) # Maximum register count
MAX_CALL_STACK = const(16) # Maximum subroutine call depth
INSTRUCTIONS_PER_FRAME = const(1) # Instructions executed per frame
OP_MOV = const(0) # Move value/register to register
OP_ADD = const(1) # Add two operands to destination
OP_SUB = const(2) # Subtract operands to destination
OP_MUL = const(3) # Multiply operands to destination
OP_DIV = const(4) # Divide operands to destination
OP_CMP = const(5) # Compare two values and set flags
OP_BEQ = const(6) # Branch if equal (from last CMP)
OP_BLT = const(7) # Branch if less than (from last CMP)
OP_BGT = const(8) # Branch if greater than (from last CMP)
OP_BNE = const(9) # Branch if not equal (from last CMP)
OP_GOTO = const(10) # Unconditional branch to address
OP_CALL = const(11) # Call subroutine at address
OP_RET = const(12) # Return from subroutine
OP_NOP = const(13) # No operation instruction
REG_A_W = range(1, 24) # Storage registers A through W
REG_X = const(24) # X position register
REG_Y = const(25) # Y position register
REG_Z = const(26) # Storage register Z
REG_AIM = const(27) # Gun aim register
REG_SHOT = const(28) # Fire gun register
REG_RADAR = const(29) # Radar register
REG_DAMAGE = const(30) # Damage sensor register
REG_SPEEDX = const(31) # X velocity register
REG_SPEEDY = const(32) # Y velocity register
REG_RANDOM = const(33) # Random number generator register
REG_INDEX = const(34) # Index register
class Missile:
_X = const(0) # Missile X position index
_Y = const(1) # Missile Y position index
_DEG = const(2) # Missile angle index
_VX = const(3) # Missile X velocity index
_VY = const(4) # Missile Y velocity index
_OWNER = const(5) # Missile owner robot index
_MISS_LIFE = const(7) # Missile lifetime counter index
_MISS_START = const(8) # Missile start position index
_MISS_PARAMS = const(10) # Parameters per missile
_NUM_MISSILES = const(50) # Maximum missiles on screen
_NUM_HITBOXES = const(31) # Maximum collision hitboxes
_HITBOX_PARAMS = const(10) # Parameters per hitbox
_HITBOX_X = const(0) # Hitbox X position index
_HITBOX_Y = const(1) # Hitbox Y position index
_HITBOX_W = const(2) # Hitbox width index
_HITBOX_H = const(3) # Hitbox height index
_HITBOX_ON = const(4) # Hitbox active flag index
def __init__(self): # Initialize missile system
self.P = array.array('i',0 for _ in range(_MISS_PARAMS*_NUM_MISSILES)) # Missile parameters
self.HITBOXES = array.array('i',0 for _ in range(_HITBOX_PARAMS*_NUM_HITBOXES)) # Collision hitboxes
self.HIT = array.array('i',(0,0,0,0)) # Hit detection results
for hb in range(_NUM_HITBOXES): # Initialize all hitboxes
i = hb * _HITBOX_PARAMS # Calculate hitbox parameter offset
self.HITBOXES[i + _HITBOX_W] = 8 # Set hitbox width to 8 pixels
self.HITBOXES[i + _HITBOX_H] = 8 # Set hitbox height to 8 pixels
self.HITBOXES[i + _HITBOX_ON] = 0 # Set hitbox as initially inactive
@micropython.viper
def init_missile(self): # Initialize new missile from parameters
miss = ptr32(self.P) # Get missile parameters pointer
isin = ptr32(ISIN) # Get sine lookup table pointer
icos = ptr32(ICOS) # Get cosine lookup table pointer
x = int(self.P[_X]) # Get missile start X position
y = int(self.P[_Y]) # Get missile start Y position
vx = int(self.P[_VX]) # Get missile X velocity
vy = int(self.P[_VY]) # Get missile Y velocity
owner = int(self.P[_OWNER]) # Get missile owner robot ID
deg = int(self.P[_DEG]) + 180 # Get missile angle plus 180 degrees
if deg >= 360: deg -= 360 # Normalize angle to 0-359 range
for index in range(1,_NUM_MISSILES): # Find free missile slot
i = index * _MISS_PARAMS # Calculate missile parameter offset
if miss[i + _MISS_LIFE] == 0: # Found inactive missile slot
miss[i + _MISS_LIFE] = 200 # Set missile lifetime to 200 frames
miss[i + _X] = icos[deg]*0 + x # Set missile start X position
miss[i + _Y] = isin[deg]*0 + y # Set missile start Y position
miss[i + _VX] = vx # Set missile X velocity
miss[i + _VY] = vy # Set missile Y velocity
miss[i + _OWNER] = owner # Set missile owner robot
return # Exit after initializing missile
@micropython.viper
def move_missile(self)->int: # Update all missile positions and check collisions
miss = ptr32(self.P) # Get missile parameters pointer
isin = ptr32(ISIN) # Get sine lookup table pointer
icos = ptr32(ICOS) # Get cosine lookup table pointer
hitbox = ptr32(self.HITBOXES) # Get hitbox parameters pointer
hit = ptr32(self.HIT) # Get hit detection results pointer
hit_index = 0 # Initialize hit detection index
for index in range(1,_NUM_MISSILES): # Process all active missiles
i = index * _MISS_PARAMS # Calculate missile parameter offset
if miss[i + _MISS_LIFE] > 0: # Process active missiles only
miss[i + _MISS_LIFE] -= 1 # Decrement missile lifetime
owner = miss[i + _OWNER] # Get missile owner robot
x = miss[i + _X] # Get current missile X position
y = miss[i + _Y] # Get current missile Y position
x += miss[i + _VX] # Update X position with velocity
y += miss[i + _VY] # Update Y position with velocity
miss[i + _X] = x # Store new X position
miss[i + _Y] = y # Store new Y position
x = x >> SCALE # Convert to screen coordinates
y = y >> SCALE # Convert to screen coordinates
if 0 < x < MAXSCREEN_X and 0 < y < MAXSCREEN_Y: # Check screen bounds
pass # Missile still on screen
else: # Missile off screen
miss[i + _MISS_LIFE] = 0 # Deactivate missile
for hb in range(0,_NUM_HITBOXES): # Check collision with all hitboxes
hb_idx = hb * _HITBOX_PARAMS # Calculate hitbox parameter offset
if hitbox[hb_idx + _HITBOX_ON]: # Check active hitboxes only
hb_w = hitbox[hb_idx + _HITBOX_W] + 2# Get hitbox width
hb_h = hitbox[hb_idx + _HITBOX_H] + 2# Get hitbox height
hb_x = hitbox[hb_idx + _HITBOX_X] - 1 # - (hb_w>>1) # Calculate hitbox left edge
hb_y = hitbox[hb_idx + _HITBOX_Y] - 1# - (hb_h>>1) # Calculate hitbox top edge
if x >= hb_x and x < hb_x + hb_w and y >= hb_y and y < hb_y + hb_h and owner != hb: # Collision detected
hit_index = hb # Store hit robot index
miss[i + _MISS_LIFE] = 0 # Deactivate missile
hit[0] = x # Store hit X coordinate
hit[1] = y # Store hit Y coordinate
return hit_index # Return hit robot index
return -1 # No collisions detected
@micropython.viper
def draw_missile(self)->int: # Draw all active missiles on screen
miss = ptr32(self.P) # Get missile parameters pointer
for index in range(1,_NUM_MISSILES): # Process all missile slots
i = index * _MISS_PARAMS # Calculate missile parameter offset
if miss[i + _MISS_LIFE] > 0: # Draw active missiles only
x = miss[i + _X] >> SCALE # Convert X to screen coordinates
y = miss[i + _Y] >> SCALE # Convert Y to screen coordinates
if 0 <= x < MAXSCREEN_X and 0 <= y < MAXSCREEN_Y: # Check screen bounds
LCD.fbdraw.pixel(x,y,0xffff) # Draw missile as white pixel
class RobotProgram:
def __init__(self): # Initialize robot program structure
self.bytecode = array.array('i', [OP_NOP << 24] * MAX_PROGRAM_SIZE) # Program bytecode array
self.pc = 0 # Program counter for current instruction
self.registers = array.array('i', [0] * MAX_REGISTERS) # Register storage array
self.call_stack = array.array('i', [0] * MAX_CALL_STACK) # Subroutine call stack
self.stack_ptr = 0 # Call stack pointer
self.random_limit = 2048 # Random number generator limit
self.name = "UNNAMED" # Robot program name
self.active = False # Program active status flag
self.flags = 0 # Condition flags from last CMP
self.temp_registers = array.array('i', [0] * 8) # Temporary calculation registers
# Updated RobotCompiler class with new 32-bit instruction format
class RobotCompiler:
def __init__(self): # Initialize compiler state
self.labels = {} # Label name to address mapping
self.register_names = { # Register name to index mapping
'A':1, 'B':2, 'C':3, 'D':4, 'E':5, 'F':6, 'G':7, 'H':8,
'I':9, 'J':10, 'K':11, 'L':12, 'M':13, 'N':14, 'O':15,
'P':16, 'Q':17, 'R':18, 'S':19, 'T':20, 'U':21, 'V':22,
'W':23, 'X':24, 'Y':25, 'Z':26, 'AIM':27, 'SHOT':28,
'RADAR':29, 'DAMAGE':30, 'SPEEDX':31, 'SPEEDY':32,
'RANDOM':33, 'INDEX':34
}
self.temp_counter = 0 # Temporary register allocation counter
self.temp_base = 40 # Base address for temporary registers
self.forward_refs = [] # List of forward reference patches
def _get_temp_register(self): # Allocate temporary register for calculations
temp_reg = self.temp_base + (self.temp_counter % 8) # Use temp register range 40-47
self.temp_counter += 1 # Increment counter for next allocation
return temp_reg # Return allocated temporary register
def _encode_immediate(self, value): # Encode signed immediate to 12 bits
if value < -2048 or value > 2047: # Check immediate range
value = max(-2048, min(2047, value)) # Clamp to valid range
if value < 0: # Negative value
return (value + 4096) & 0xFFF # Two's complement encoding
return value & 0xFFF # Positive value encoding
def _make_instruction(self, opcode, imm12=0, op1=0, flags=0, op2=0): # Create 32-bit instruction
return (opcode << 28) | ((imm12 & 0xFFF) << 16) | ((op1 & 0xFF) << 8) | ((flags & 0x3) << 6) | (op2 & 0x3F)
def compile(self, source_code): # Compile source code to bytecode
program = RobotProgram() # Create new program instance
lines = source_code.split('\n') # Split source into individual lines
# PASS 1: Parse all lines and collect labels
processed_lines = [] # Store parsed instruction info
instruction_addr = 0 # Track current instruction address
for line in lines: # Process each source line
line = line.strip() # Remove leading/trailing whitespace
if not line or line.startswith(';'): # Skip empty lines and comments
continue
original_line = line # Keep original for processing
label_name = None # Initialize label name
if line.startswith(']'): # Handle RobotWar ] prefix syntax
line = line[1:].strip() # Remove ] and whitespace
if ' ' not in line and line: # Pure label without instruction
self.labels[line] = instruction_addr # Store label address
continue # Skip to next line
elif line: # Label with instruction on same line
if ':' in line: # Handle "LABEL: instruction" format
parts = line.split(':', 1) # Split on first colon
if len(parts) == 2: # Valid label:instruction format
label_name = parts[0].strip() # Extract label name
line = parts[1].strip() # Extract instruction part
if line: # Process non-empty instruction
if label_name: # Store label if found
self.labels[label_name] = instruction_addr
# Parse instruction to determine how many instructions it generates
instr_count = self._count_instructions(line) # Count generated instructions
processed_lines.append((instruction_addr, line, original_line)) # Store line info
instruction_addr += instr_count # Advance by instruction count
# PASS 2: Generate bytecode with resolved labels (no need for forward refs)
for addr, line, original_line in processed_lines: # Process each instruction line
instructions = self._parse_instruction(line, addr) # Pass current address!
for i, instr in enumerate(instructions): # Store each generated instruction
if addr + i < MAX_PROGRAM_SIZE: # Check program size bounds
program.bytecode[addr + i] = instr # Store compiled instruction
program.active = len(processed_lines) > 0 # Mark program as active if has instructions
return program # Return compiled program
def _count_instructions(self, line): # Count how many instructions a line generates
line = line.strip() # Remove whitespace
if ';' in line: # Remove inline comments
line = line[:line.index(';')].strip()
if not line: # Empty line
return 1 # Single NOP
if 'IF' in line: # IF statement
goto_pos = line.find(' GOTO ') # Find GOTO keyword
gosub_pos = line.find(' GOSUB ') # Find GOSUB keyword
if goto_pos != -1: # GOTO found
condition_part = line[3:goto_pos].strip() # Extract condition after IF
elif gosub_pos != -1: # GOSUB found
condition_part = line[3:gosub_pos].strip() # Extract condition after IF
else:
return 1 # Invalid IF, return NOP
operator = None # Find comparison operator
for op in ['#', '=', '<', '>']: # Check each operator
if ' ' + op + ' ' in condition_part: # Found operator with spaces
operator = op # Store operator
break
if operator: # Valid operator found
parts = condition_part.split(' ' + operator + ' ') # Split on operator
if len(parts) == 2: # Valid condition format
left_expr = parts[0].strip() # Get left expression
count = 0 # Initialize instruction count
if any(op in left_expr for op in ['+', '-', '*', '/']): # Complex arithmetic
tokens = self._tokenize_expression(left_expr) # Tokenize expression
num_ops = (len(tokens) - 1) // 2 # Count operators
count += 1 + num_ops + 1 # Load + operations + final move
count += 1 # Add CMP instruction
count += 1 # Add branch instruction
return count
if ' TO ' in line: # Assignment statement
parts = line.split(' TO ') # Split on TO keyword
if len(parts) == 2: # Valid assignment format
expr = parts[0].strip() # Get expression part
target = parts[1].strip() # Get target register name
target_reg = self.register_names.get(target) # Get target register index
return self._count_assignment_instructions(expr, target_reg) # Count assignment instructions
return 1 # Single instruction for other types
def _count_instructions2(self, line): # Count how many instructions a line generates
line = line.strip() # Remove whitespace
if ';' in line: # Remove inline comments
line = line[:line.index(';')].strip()
if 'IF' in line:
return 2
if not line: # Empty line
return 1 # Single NOP
if ' TO ' in line: # Assignment statement
parts = line.split(' TO ') # Split on TO keyword
if len(parts) == 2: # Valid assignment format
expr = parts[0].strip() # Get expression part
target = parts[1].strip() # Get target register name
target_reg = self.register_names.get(target) # Get target register index
return self._count_assignment_instructions(expr, target_reg) # Count assignment instructions
return 1 # Single instruction for other types
def _count_assignment_instructions(self, expr, target_reg=None): # Count instructions for assignment expression
if expr.isdigit() or (expr.startswith('-') and expr[1:].isdigit()): # Simple constant
return 1 # Single MOV instruction
elif expr in self.register_names: # Simple register copy
return 1 # Single MOV instruction
elif any(op in expr for op in ['+', '-', '*', '/']): # Arithmetic expression
tokens = self._tokenize_expression(expr) # Tokenize expression
if len(tokens) == 3 and tokens[0] in self.register_names and target_reg: # Simple binary operation with target
if self.register_names[tokens[0]] == target_reg: # Source equals target register
return 1 # Single accumulator instruction
if len(tokens) <= 1: # Single operand
return 1 # Single instruction
else: # Multi-operand expression
num_ops = (len(tokens) - 1) // 2 # Count operators
return 1 + num_ops + 1 # Load + operations + final move
return 1
def _parse_instruction(self, line, current_addr=0): # Parse single instruction line into bytecode list
line = line.strip() # Remove whitespace
if ';' in line: # Remove inline comments
line = line[:line.index(';')].strip() # Extract code before comment
if not line: # Empty line becomes NOP
return [self._make_instruction(OP_NOP)] # Return NOP instruction
if ' TO ' in line: # Parse TO statements (assignments)
parts = line.split(' TO ') # Split on TO keyword
if len(parts) == 2: # Valid assignment format
expr = parts[0].strip() # Get expression part
target = parts[1].strip() # Get target register
if target in self.register_names: # Validate target register
reg_idx = self.register_names[target] # Get register index
return self._compile_assignment(expr, reg_idx) # Compile assignment
elif line.startswith('IF '): # Parse IF conditional statements
return self._parse_if_statement(line[3:], current_addr) # Pass current address for branch calculation
elif line.startswith('GOTO '): # Parse GOTO statements
label = line[5:].strip() # Get target label
if label in self.labels: # Label already defined (should always be true)
addr = self.labels[label] # Get label address
return [self._make_instruction(OP_GOTO, addr)] # Generate GOTO with address
else: # Shouldn't happen with predefined labels
return [self._make_instruction(OP_GOTO, 0)] # Generate with placeholder address
elif line.startswith('GOSUB '): # Parse GOSUB statements
label = line[6:].strip() # Get subroutine label
if label in self.labels: # Label already defined (should always be true)
addr = self.labels[label] # Get label address
return [self._make_instruction(OP_CALL, addr)] # Generate CALL with address
else: # Shouldn't happen with predefined labels
return [self._make_instruction(OP_CALL, 0)] # Generate with placeholder address
elif line == 'ENDSUB': # Parse ENDSUB statements
return [self._make_instruction(OP_RET)] # Generate return instruction
return [self._make_instruction(OP_NOP)] # Default to NOP for unknown instructions
def _compile_assignment(self, expr, target_reg): # Compile assignment expression to target register
if expr.isdigit() or (expr.startswith('-') and expr[1:].isdigit()): # Simple constant
value = int(expr) # Parse integer value
imm = self._encode_immediate(value) # Encode as 12-bit immediate
return [self._make_instruction(OP_MOV, imm, target_reg, 0x1, 0)] # MOV immediate to register
elif expr in self.register_names: # Simple register copy
src_reg = self.register_names[expr] # Get source register index
return [self._make_instruction(OP_MOV, 0, target_reg, 0x0, src_reg)] # MOV register to register
elif any(op in expr for op in ['+', '-', '*', '/']): # Arithmetic expression
return self._compile_arithmetic(expr, target_reg) # Compile arithmetic operation
return [self._make_instruction(OP_NOP)] # Default if parsing fails
def _compile_arithmetic(self, expr, target_reg): # Compile arithmetic expressions left-to-right
instructions = [] # Instruction list to build
tokens = self._tokenize_expression(expr) # Tokenize the expression
if not tokens: # No valid tokens
return [self._make_instruction(OP_NOP)]
# Check for simple accumulator case: register op immediate/register where target == first operand
if len(tokens) == 3: # Simple binary operation
left_operand = tokens[0] # First operand
operator = tokens[1] # Operator
right_operand = tokens[2] # Second operand
# Check if left operand is a register and matches target
if (left_operand in self.register_names and
self.register_names[left_operand] == target_reg):
op_map = {'+': OP_ADD, '-': OP_SUB, '*': OP_MUL, '/': OP_DIV} # Map operators to opcodes
if operator in op_map: # Valid operator
opcode = op_map[operator] # Get opcode
if right_operand.isdigit() or (right_operand.startswith('-') and right_operand[1:].isdigit()): # Immediate operand
imm = self._encode_immediate(int(right_operand)) # Encode immediate
return [self._make_instruction(opcode, imm, target_reg, 0x1, 0)] # Single accumulator instruction
elif right_operand in self.register_names: # Register operand
src = self.register_names[right_operand] # Get register index
return [self._make_instruction(opcode, 0, target_reg, 0x0, src)] # Single accumulator instruction
# Fall back to complex expression handling with temporaries
temp_reg = self._get_temp_register() # Allocate temporary for accumulator
# Load first operand into temporary
first = tokens[0] # Get first token
if first.isdigit() or (first.startswith('-') and first[1:].isdigit()): # Immediate value
imm = self._encode_immediate(int(first)) # Encode immediate
instructions.append(self._make_instruction(OP_MOV, imm, temp_reg, 0x1, 0)) # MOV immediate to temp
elif first in self.register_names: # Register operand
src = self.register_names[first] # Get register index
instructions.append(self._make_instruction(OP_MOV, 0, temp_reg, 0x0, src)) # MOV register to temp
# Process remaining tokens as operator-operand pairs
i = 1 # Start at second token
while i < len(tokens) - 1: # Process operator-operand pairs
op = tokens[i] # Get operator
operand = tokens[i + 1] # Get operand
op_map = {'+': OP_ADD, '-': OP_SUB, '*': OP_MUL, '/': OP_DIV} # Map operators to opcodes
if op in op_map: # Valid operator
opcode = op_map[op] # Get opcode
if operand.isdigit() or (operand.startswith('-') and operand[1:].isdigit()): # Immediate operand
imm = self._encode_immediate(int(operand)) # Encode immediate
instructions.append(self._make_instruction(opcode, imm, temp_reg, 0x1, 0)) # Accumulator op with immediate
elif operand in self.register_names: # Register operand
src = self.register_names[operand] # Get register index
instructions.append(self._make_instruction(opcode, 0, temp_reg, 0x0, src)) # Accumulator op with register
i += 2 # Move to next operator-operand pair
# Move result from temporary to target
if temp_reg != target_reg:
instructions.append(self._make_instruction(OP_MOV, 0, target_reg, 0x0, temp_reg)) # MOV temp to target
return instructions # Return instruction list
def _tokenize_expression(self, expr): # Tokenize arithmetic expression
tokens = [] # Token list to build
current = '' # Current token being built
for char in expr: # Process each character
if char in '+-*/': # Operator character
if current: # Finish current token
tokens.append(current.strip())
current = ''
tokens.append(char) # Add operator token
elif char == ' ': # Space character
continue # Skip spaces
else: # Regular character
current += char # Add to current token
if current: # Add final token
tokens.append(current.strip())
return tokens # Return token list
def _parse_if_statement(self, condition, current_addr): # Parse IF conditional statements
goto_pos = condition.find(' GOTO ') # Find GOTO keyword position
gosub_pos = condition.find(' GOSUB ') # Find GOSUB keyword position
if goto_pos == -1 and gosub_pos == -1: # No action found
return [self._make_instruction(OP_NOP)] # Return NOP if invalid
if goto_pos != -1: # GOTO found
condition_part = condition[:goto_pos].strip() # Extract condition part
label = condition[goto_pos + 6:].strip() # Extract label after GOTO
action_keyword = 'GOTO' # Set action type
else: # GOSUB found
condition_part = condition[:gosub_pos].strip() # Extract condition part
label = condition[gosub_pos + 7:].strip() # Extract label after GOSUB
action_keyword = 'GOSUB' # Set action type
operator = None # Initialize operator
operator_pos = -1 # Initialize operator position
for op in ['#', '=', '<', '>']: # Check each operator type
pos = condition_part.rfind(' ' + op + ' ') # Find last occurrence with spaces
if pos != -1 and (operator_pos == -1 or pos > operator_pos): # Found valid operator
operator = op # Store operator
operator_pos = pos + 1 # Store position (skip leading space)
if operator is None: # No operator found
return [self._make_instruction(OP_NOP)] # Return NOP if invalid
left_expr = condition_part[:operator_pos].strip() # Extract left expression
right_expr = condition_part[operator_pos + 2:].strip() # Extract right expression (skip operator and space)
instructions = [] # Build instruction list
if any(op in left_expr for op in ['+', '-', '*', '/']): # Complex arithmetic expression
temp_reg = self._get_temp_register() # Allocate temporary for result
arith_instrs = self._compile_arithmetic(left_expr, temp_reg) # Compile arithmetic to temp
instructions.extend(arith_instrs) # Add arithmetic instructions
left_operand = temp_reg # Use temp as left operand
elif left_expr in self.register_names: # Simple register
left_operand = self.register_names[left_expr] # Get register index
else: # Invalid left expression
return [self._make_instruction(OP_NOP)] # Return NOP
if right_expr.isdigit() or (right_expr.startswith('-') and right_expr[1:].isdigit()): # Immediate comparison
imm = self._encode_immediate(int(right_expr)) # Encode immediate value
instructions.append(self._make_instruction(OP_CMP, imm, left_operand, 0x1, 0)) # CMP reg/temp, immediate
elif right_expr in self.register_names: # Register comparison
cmp_reg = self.register_names[right_expr] # Get comparison register
instructions.append(self._make_instruction(OP_CMP, 0, left_operand, 0x0, cmp_reg)) # CMP reg/temp, reg
else: # Invalid right expression
return [self._make_instruction(OP_NOP)] # Return NOP
op_map = {'=': OP_BEQ, '<': OP_BLT, '>': OP_BGT, '#': OP_BNE} # Map operators to branch opcodes
if operator in op_map and action_keyword == 'GOTO': # Valid branch condition
branch_op = op_map[operator] # Get branch opcode
if label in self.labels: # Label already defined
target_addr = self.labels[label] # Get label address
instructions.append(self._make_instruction(branch_op, target_addr)) # Branch with known address
else: # Shouldn't happen with predefined labels
instructions.append(self._make_instruction(branch_op, 0)) # Branch with placeholder
return instructions # Return instruction list
def _parse_if_statement2(self, condition, current_addr): # Parse IF conditional statements
goto_pos = condition.find(' GOTO ') # Find GOTO keyword position
gosub_pos = condition.find(' GOSUB ') # Find GOSUB keyword position
if goto_pos == -1 and gosub_pos == -1: # No action found
return [self._make_instruction(OP_NOP)] # Return NOP if invalid
if goto_pos != -1: # GOTO found
condition_part = condition[:goto_pos].strip() # Extract condition part
label = condition[goto_pos + 6:].strip() # Extract label after GOTO
action_keyword = 'GOTO' # Set action type
else: # GOSUB found
condition_part = condition[:gosub_pos].strip() # Extract condition part
label = condition[gosub_pos + 7:].strip() # Extract label after GOSUB
action_keyword = 'GOSUB' # Set action type
operator = None # Initialize operator
operator_pos = -1 # Initialize operator position
for op in ['#', '=', '<', '>']: # Check each operator type
pos = condition_part.rfind(' ' + op + ' ') # Find last occurrence with spaces
if pos != -1 and (operator_pos == -1 or pos > operator_pos): # Found valid operator
operator = op # Store operator
operator_pos = pos + 1 # Store position (skip leading space)
if operator is None: # No operator found
return [self._make_instruction(OP_NOP)] # Return NOP if invalid
left_expr = condition_part[:operator_pos].strip() # Extract left expression
right_expr = condition_part[operator_pos + 2:].strip() # Extract right expression (skip operator and space)
instructions = [] # Build instruction list
if any(op in left_expr for op in ['+', '-', '*', '/']): # Complex arithmetic expression
temp_reg = self._get_temp_register() # Allocate temporary for result
arith_instrs = self._compile_arithmetic(left_expr, temp_reg) # Compile arithmetic to temp
instructions.extend(arith_instrs) # Add arithmetic instructions
left_operand = temp_reg # Use temp as left operand
elif left_expr in self.register_names: # Simple register
left_operand = self.register_names[left_expr] # Get register index
else: # Invalid left expression
return [self._make_instruction(OP_NOP)] # Return NOP
if right_expr.isdigit() or (right_expr.startswith('-') and right_expr[1:].isdigit()): # Immediate comparison
imm = self._encode_immediate(int(right_expr)) # Encode immediate value
instructions.append(self._make_instruction(OP_CMP, imm, left_operand, 0x1, 0)) # CMP reg/temp, immediate
elif right_expr in self.register_names: # Register comparison
cmp_reg = self.register_names[right_expr] # Get comparison register
instructions.append(self._make_instruction(OP_CMP, 0, left_operand, 0x0, cmp_reg)) # CMP reg/temp, reg
else: # Invalid right expression
return [self._make_instruction(OP_NOP)] # Return NOP
op_map = {'=': OP_BEQ, '<': OP_BLT, '>': OP_BGT, '#': OP_BNE} # Map operators to branch opcodes
if operator in op_map and action_keyword == 'GOTO': # Valid branch condition
branch_op = op_map[operator] # Get branch opcode
if label in self.labels: # Label already defined
target_addr = self.labels[label] # Get label address
instructions.append(self._make_instruction(branch_op, target_addr)) # Branch with known address
else: # Shouldn't happen with predefined labels
instructions.append(self._make_instruction(branch_op, 0)) # Branch with placeholder
return instructions # Return instruction list
@micropython.viper
def execute_robot_program(robot_id: int): # Execute robot program with new 32-bit opcodes
global robot_programs
bot = ptr32(BOT) # Get robot parameters pointer
prog = robot_programs[robot_id] # Get robot's program
if not prog.active: # No program loaded
default_ai(robot_id) # Use default AI instead
return
bytecode = ptr32(prog.bytecode) # Get program bytecode pointer
registers = ptr32(prog.registers) # Get program registers pointer
temp_registers = ptr32(prog.temp_registers) # Get temporary registers pointer
i = robot_id * BOT_PARAMS # Calculate robot parameter offset
registers[REG_X] = bot[BOT_X + i] // BOT_SCALE # Update X position register
registers[REG_Y] = bot[BOT_Y + i] // BOT_SCALE # Update Y position register
registers[REG_DAMAGE] = bot[DAMAGE + i] # Update damage register
registers[REG_AIM] = bot[GUN_AIM + i] # Update gun aim register
registers[REG_SPEEDX] = bot[BOT_SPEED_X + i] # Update X velocity register
registers[REG_SPEEDY] = bot[BOT_SPEED_Y + i] # Update Y velocity register
registers[REG_RADAR] = bot[RADAR_DIST + i] # Update radar distance register
if bot[FIRE_COOL + i] > 0: # Gun cooling down
registers[REG_SHOT] = bot[FIRE_COOL + i] # Show cooldown time
else:
registers[REG_SHOT] = 0 # Gun ready to fire
instructions_executed = 0 # Reset instruction counter
pc = int(prog.pc) # Get program counter
flags = int(prog.flags) # Get condition flags
while instructions_executed < INSTRUCTIONS_PER_FRAME: # Execute instruction batch
if pc >= MAX_PROGRAM_SIZE or pc < 0: # Bounds check program counter
pc = 0 # Reset to start if out of bounds
instruction = bytecode[pc] # Fetch current instruction
opcode = (instruction >> 28) & 0xF # Extract 4-bit opcode
imm12 = (instruction >> 16) & 0xFFF # Extract 12-bit immediate
op1 = (instruction >> 8) & 0xFF # Extract 8-bit operand 1
op_flags = (instruction >> 6) & 0x3 # Extract 2-bit flags
op2 = instruction & 0x3F # Extract 6-bit operand 2
# Sign extend 12-bit immediate if needed
if imm12 & 0x800: # Check sign bit
imm12 = imm12 - 4096 # Convert to negative
if opcode == OP_MOV: # Move operation
if op_flags & 0x1: # Immediate to register
if op1 < MAX_REGISTERS: # Bounds check destination
registers[op1] = imm12 # Move immediate to register
elif 40 <= op1 < 48: # Temporary register
temp_registers[op1 - 40] = imm12 # Move to temporary
else: # Register to register
src = op2 # Source register
dst = op1 # Destination register
if src < MAX_REGISTERS and dst < MAX_REGISTERS: # Bounds check both
registers[dst] = registers[src] # Move register to register
elif src < MAX_REGISTERS and 40 <= dst < 48: # Regular to temporary
temp_registers[dst - 40] = registers[src] # Move to temporary
elif 40 <= src < 48 and dst < MAX_REGISTERS: # Temporary to regular
registers[dst] = temp_registers[src - 40] # Move from temporary
elif 40 <= src < 48 and 40 <= dst < 48: # Temporary to temporary
temp_registers[dst - 40] = temp_registers[src - 40] # Move between temporaries
execute_math(opcode,op_flags,op1,op2,imm12,robot_id)
# Handle special register writes
if opcode == OP_MOV or opcode == OP_ADD:
if op1 == REG_SPEEDX: # X velocity control
if registers[op1] < -255:
bot[BOT_SPEED_X + i] = -255
elif registers[op1] > 255:
bot[BOT_SPEED_X + i] = 255
else:
bot[BOT_SPEED_X + i] = registers[op1] # Update bot X speed
elif op1 == REG_SPEEDY: # Y velocity control
if registers[op1] < -255:
bot[BOT_SPEED_Y + i] = -255
elif registers[op1] > 255:
bot[BOT_SPEED_Y + i] = 255
else:
bot[BOT_SPEED_Y + i] = registers[op1] # Update bot Y speed
elif op1 == REG_AIM: # Gun aim control
bot[GUN_AIM + i] = registers[op1] % 360 # Update gun angle
elif op1 == REG_SHOT and registers[op1] > 0: # Fire gun command
if bot[FIRE_COOL + i] == 0: # Gun ready to fire
fire_missile(robot_id, registers[op1]) # Fire missile
elif op1 == REG_RADAR: # Radar scan command
deg = registers[op1] % 360 # Normalize angle
bot[RADAR_AIM + i] = deg # Set radar direction
bot_x = bot[BOT_X + i] // BOT_SCALE
bot_y = bot[BOT_Y + i] // BOT_SCALE
dist = int(aim_radar(bot_x + 4, bot_y + 4, deg)) # Perform radar scan
bot[RADAR_DIST + i] = dist # Store scan result
registers[REG_RADAR] = dist # Update register
elif op1 == REG_RANDOM: # Random seed control
prog.random_limit = registers[op1] if registers[op1] > 0 else 100 # Set random range
if opcode == OP_CMP: # Compare operation
val1 = 0 # Initialize first value
val2 = 0 # Initialize second value
if op1 < MAX_REGISTERS: # Regular register
val1 = registers[op1] # Get first value
elif 40 <= op1 < 48: # Temporary register
val1 = temp_registers[op1 - 40] # Get from temporary
if op_flags & 0x1: # Compare with immediate
val2 = imm12 # Use immediate value
else: # Compare with register
if op2 < MAX_REGISTERS: # Regular register
val2 = registers[op2] # Get second value
elif 40 <= op2 < 48: # Temporary register
val2 = temp_registers[op2 - 40] # Get from temporary
flags = 0 # Reset flags
if val1 == val2: # Set equal flag
flags |= 1
if val1 < val2: # Set less than flag
flags |= 2
if val1 > val2: # Set greater than flag
flags |= 4
prog.flags = flags # Store comparison flags
elif opcode == OP_BEQ: # Branch if equal
if flags & 1: # Check equal flag
target_addr = imm12 # Get branch target from IMM12
if 0 <= target_addr < MAX_PROGRAM_SIZE: # Bounds check target
pc = target_addr - 1 # Jump to target (-1 for increment)
elif opcode == OP_BLT: # Branch if less than
if flags & 2: # Check less than flag
target_addr = imm12 # Get branch target from IMM12
if 0 <= target_addr < MAX_PROGRAM_SIZE: # Bounds check target
pc = target_addr - 1 # Jump to target (-1 for increment)
elif opcode == OP_BGT: # Branch if greater than
if flags & 4: # Check greater than flag
target_addr = imm12 # Get branch target from IMM12
if 0 <= target_addr < MAX_PROGRAM_SIZE: # Bounds check target
pc = target_addr - 1 # Jump to target (-1 for increment)
elif opcode == OP_BNE: # Branch if not equal
if not (flags & 1): # Check not equal (equal flag clear)
target_addr = imm12 # Get branch target from IMM12
if 0 <= target_addr < MAX_PROGRAM_SIZE: # Bounds check target
pc = target_addr - 1 # Jump to target (-1 for increment)
elif opcode == OP_GOTO: # Unconditional branch
target_addr = imm12 # Get branch target from IMM12
if 0 <= target_addr < MAX_PROGRAM_SIZE: # Bounds check target
pc = target_addr - 1 # Jump to target (-1 for increment)
if opcode == OP_CALL: # Subroutine call
target_addr = imm12 # Get call target from IMM12
if 0 <= target_addr < MAX_PROGRAM_SIZE: # Bounds check target
if int(prog.stack_ptr) < MAX_CALL_STACK: # Check stack space
prog.call_stack[int(prog.stack_ptr)] = pc + 1 # Push return address
prog.stack_ptr = int(prog.stack_ptr) + 1 # Increment stack pointer
pc = target_addr - 1 # Jump to subroutine (-1 for increment)
elif opcode == OP_RET: # Return from subroutine
if int(prog.stack_ptr) > 0: # Check if stack has return address
prog.stack_ptr = int(prog.stack_ptr) - 1 # Decrement stack pointer
pc = int(prog.call_stack[int(prog.stack_ptr)]) # Pop return address
#debug_robot_execution(0, prog)
pc += 1 # Increment program counter
instructions_executed += 1 # Count executed instruction
if pc >= MAX_PROGRAM_SIZE: # Bounds check after increment
pc = 0 # Reset if out of bounds
if registers[REG_RANDOM] == int(prog.random_limit): # Random register accesse
registers[REG_RANDOM] = int(randint(0, int(prog.random_limit))) # Generate new random
prog.pc = pc # Save program counter
prog.flags = flags # Save condition flags
@micropython.viper
def execute_math(opcode:int,op_flags:int,op1:int,op2:int,imm12:int,robot_id:int):
global robot_programs
prog = robot_programs[robot_id] # Get robot's program
registers = ptr32(prog.registers) # Get program registers pointer
temp_registers = ptr32(prog.temp_registers) # Get temporary registers pointer
if opcode == OP_ADD: # Addition operation (accumulator style)
if op_flags & 0x1: # Register + immediate
if op1 < MAX_REGISTERS: # Regular register
registers[op1] = registers[op1] + imm12 # Add immediate to register
elif 40 <= op1 < 48: # Temporary register
temp_registers[op1 - 40] = temp_registers[op1 - 40] + imm12 # Add to temporary
else: # Register + register
if op1 < MAX_REGISTERS and op2 < MAX_REGISTERS: # Both regular registers
registers[op1] = registers[op1] + registers[op2] # Add registers
elif 40 <= op1 < 48 and op2 < MAX_REGISTERS: # Temporary + regular
temp_registers[op1 - 40] = temp_registers[op1 - 40] + registers[op2] # Add to temporary
elif 40 <= op1 < 48 and 40 <= op2 < 48: # Temporary + temporary
temp_registers[op1 - 40] = temp_registers[op1 - 40] + temp_registers[op2 - 40] # Add temporaries
elif opcode == OP_SUB: # Subtraction operation (accumulator style)
if op_flags & 0x1: # Register - immediate
if op1 < MAX_REGISTERS: # Regular register
registers[op1] = registers[op1] - imm12 # Subtract immediate from register
elif 40 <= op1 < 48: # Temporary register
temp_registers[op1 - 40] = temp_registers[op1 - 40] - imm12 # Subtract from temporary
else: # Register - register
if op1 < MAX_REGISTERS and op2 < MAX_REGISTERS: # Both regular registers
registers[op1] = registers[op1] - registers[op2] # Subtract registers
elif 40 <= op1 < 48 and op2 < MAX_REGISTERS: # Temporary - regular
temp_registers[op1 - 40] = temp_registers[op1 - 40] - registers[op2] # Subtract from temporary
elif 40 <= op1 < 48 and 40 <= op2 < 48: # Temporary - temporary
temp_registers[op1 - 40] = temp_registers[op1 - 40] - temp_registers[op2 - 40] # Subtract temporaries
elif opcode == OP_MUL: # Multiplication operation (accumulator style)
if op_flags & 0x1: # Register * immediate
if op1 < MAX_REGISTERS: # Regular register
registers[op1] = registers[op1] * imm12 # Multiply register by immediate
elif 40 <= op1 < 48: # Temporary register
temp_registers[op1 - 40] = temp_registers[op1 - 40] * imm12 # Multiply temporary
else: # Register * register
if op1 < MAX_REGISTERS and op2 < MAX_REGISTERS: # Both regular registers
registers[op1] = registers[op1] * registers[op2] # Multiply registers
elif 40 <= op1 < 48 and op2 < MAX_REGISTERS: # Temporary * regular
temp_registers[op1 - 40] = temp_registers[op1 - 40] * registers[op2] # Multiply temporary
elif 40 <= op1 < 48 and 40 <= op2 < 48: # Temporary * temporary
temp_registers[op1 - 40] = temp_registers[op1 - 40] * temp_registers[op2 - 40] # Multiply temporaries
elif opcode == OP_DIV: # Division operation (accumulator style)
if op_flags & 0x1: # Register / immediate
if imm12 != 0: # Avoid division by zero
if op1 < MAX_REGISTERS: # Regular register
registers[op1] = registers[op1] // imm12 # Divide register by immediate
elif 40 <= op1 < 48: # Temporary register
temp_registers[op1 - 40] = temp_registers[op1 - 40] // imm12 # Divide temporary
else: # Register / register
divisor = 0 # Initialize divisor
if op2 < MAX_REGISTERS: # Regular register divisor
divisor = registers[op2] # Get divisor value
elif 40 <= op2 < 48: # Temporary register divisor
divisor = temp_registers[op2 - 40] # Get divisor from temporary
if divisor != 0: # Avoid division by zero
if op1 < MAX_REGISTERS: # Regular register dividend
registers[op1] = registers[op1] // divisor # Divide register
elif 40 <= op1 < 48: # Temporary register dividend
temp_registers[op1 - 40] = temp_registers[op1 - 40] // divisor # Divide temporary
def print_robot_debug(source_code, program): # Debug output showing compiled bytecode
if program.name != 'ROBOT1' : return
lines = source_code.split('\n') # Split source into lines
print("=" * 80) # Print debug header separator
print("ROBOT CODE DEBUG - 32-bit Opcode Format") # Print debug title
print("=" * 80) # Print debug header separator
print(f"Robot: {program.name}") # Print robot name
print(f"Active: {program.active}") # Print program status
print("-" * 80) # Print section separator
instruction_addr = 0 # Track bytecode address counter
for line_num, line in enumerate(lines, 1): # Process each source line
line_stripped = line.strip() # Remove whitespace from line
if not line_stripped or line_stripped.startswith(';'): # Skip comments and empty lines
print(f"{line_num:3d}: {line:<40} ; (comment/empty)") # Show skipped lines
continue
if line_stripped.startswith(']'): # Handle RobotWar syntax
line_stripped = line_stripped[1:].strip() # Remove ] prefix
if ' ' not in line_stripped and line_stripped: # Pure label line
print(f"{line_num:3d}: {line:<40} ; LABEL -> addr {instruction_addr}") # Show label address
continue
if line_stripped: # Process instruction line
# May have multiple instructions for complex expressions
compiler = RobotCompiler() # Create compiler for parsing
instructions = compiler._parse_instruction(line_stripped) # Parse to get instruction count
for i, _ in enumerate(instructions): # Process each generated instruction
if instruction_addr + i < len(program.bytecode): # Check bounds
bc = program.bytecode[instruction_addr + i] # Get bytecode instruction
opcode = (bc >> 28) & 0xF # Extract 4-bit opcode
imm12 = (bc >> 16) & 0xFFF # Extract 12-bit immediate
op1 = (bc >> 8) & 0xFF # Extract 8-bit operand 1
flags = (bc >> 6) & 0x3 # Extract 2-bit flags
op2 = bc & 0x3F # Extract 6-bit operand 2
# Sign extend immediate for display
if imm12 & 0x800: # Check sign bit
imm12_signed = imm12 - 4096 # Convert to negative
else:
imm12_signed = imm12 # Keep positive
opcode_names = { # Opcode name mapping
0: "MOV", 1: "ADD", 2: "SUB", 3: "MUL", 4: "DIV",
5: "CMP", 6: "BEQ", 7: "BLT", 8: "BGT", 9: "BNE",
10: "GOTO", 11: "CALL", 12: "RET", 13: "NOP"
}
op_name = opcode_names.get(opcode, f"UNK({opcode})") # Get opcode name
flag_str = "IMM" if flags & 0x1 else "REG" # Decode operand type
if i == 0: # First instruction for this line
bytecode_str = f"[{instruction_addr + i:3d}] {op_name:4s} op1:{op1:3d} {flag_str:3s} op2:{op2:3d} imm:{imm12_signed:5d}"
print(f"{line_num:3d}: {line:<40} ; {bytecode_str}")
else: # Additional instructions
bytecode_str = f"[{instruction_addr + i:3d}] {op_name:4s} op1:{op1:3d} {flag_str:3s} op2:{op2:3d} imm:{imm12_signed:5d}"
print(f" {'':<40} ; {bytecode_str}")
instruction_addr += len(instructions) # Advance by instruction count
print("-" * 80) # Print footer separator
print(f"Total Instructions: {instruction_addr}") # Show instruction count
print("=" * 80) # Print footer separator
def debug_robot_execution(robot_id, program): # Debug robot execution state
if program.name != 'ROBOT2': return
# if not program.active: # Check if program is loaded
# print(f"Robot {robot_id}: No active program") # Show inactive status
# return
pc = program.pc # Get current program counter
flags = program.flags # Get current condition flags
if pc < len(program.bytecode): # Check PC bounds
instruction = program.bytecode[pc] # Get current instruction
opcode = (instruction >> 28) & 0xF # Extract 4-bit opcode
imm12 = (instruction >> 16) & 0xFFF # Extract 12-bit immediate
op1 = (instruction >> 8) & 0xFF # Extract 8-bit operand 1
op_flags = (instruction >> 6) & 0x3 # Extract 2-bit flags
op2 = instruction & 0x3F # Extract 6-bit operand 2
# Sign extend immediate for display
if imm12 & 0x800: # Check sign bit
imm12_signed = imm12 - 4096 # Convert to negative
else:
imm12_signed = imm12 # Keep positive
opcode_names = { # Opcode name lookup table
0: "MOV", 1: "ADD", 2: "SUB", 3: "MUL", 4: "DIV",
5: "CMP", 6: "BEQ", 7: "BLT", 8: "BGT", 9: "BNE",
10: "GOTO", 11: "CALL", 12: "RET", 13: "NOP"
}
op_name = opcode_names.get(opcode, f"UNKNOWN({opcode})") # Get opcode name
flag_str = "IMM" if op_flags & 0x1 else "REG" # Decode operand type
print(f"Robot {robot_id} ({program.name}):") # Show robot info
print(f" PC: {pc:3d} FLAGS: EQ:{flags&1} LT:{(flags>>1)&1} GT:{(flags>>2)&1}") # Show execution state
print(f" Next: {op_name:4s} op1:{op1:3d} {flag_str:3s} op2:{op2:3d} imm:{imm12_signed:5d}") # Show next instruction
#print(f" Stack: {program.stack_ptr} deep") # Show call stack depth
# Show key registers and temporaries
key_registers = [24, 25, 27, 28, 29, 30, 31, 32, 33,4,8,22] # Important register indices
reg_names = ["X", "Y", "AIM", "SHOT", "RADAR", "DAMAGE", "SPEEDX", "SPEEDY", "RANDOM","D","H","V"] # Register names
print(" Registers:", end="") # Show key register values
for i, (reg_idx, name) in enumerate(zip(key_registers, reg_names)): # Loop through key registers
if i % 12 == 0 and i > 0: # New line every 5 registers
print(f"\n ", end="")
print(f" {name}:{program.registers[reg_idx]:6d}", end="")
print()
# Show active temporary registers
print("\n Temps:", end="") # Show temporary registers
for i in range(8): # Check all 8 temporaries
if program.temp_registers[i] != 0: # Show non-zero temporaries
print(f" T{i}:{program.temp_registers[i]:6d}", end="")
print() # Final newline
else:
print(f"Robot {robot_id}: PC {pc} out of bounds!") # Show error state
def load_robot_program_org(filename): # Load and compile robot program from file
try:
with open(filename, 'r') as f: # Open robot source file
source_code = f.read() # Read source code
compiler = RobotCompiler() # Create compiler instance
program = compiler.compile(source_code) # Compile source to bytecode
program.name = filename.replace('.txt', '').upper() # Set program name
print_robot_debug(source_code, program) # Print debug information
return program # Return compiled program
except:
return None # Return None if loading failed
def load_robot_program(filename): # Load and compile robot program from file
with open(filename, 'r') as f: # Open robot source file
source_code = f.read() # Read source code
compiler = RobotCompiler() # Create compiler instance
program = compiler.compile(source_code) # Compile source to bytecode
program.name = filename.replace('.txt', '').upper() # Set program name
print_robot_debug(source_code, program) # Print debug information
return program # Return compiled program
def init_game(): # Initialize game systems and data structures
global BOT, SPRITES, ISIN, ICOS, missile, GAME, EXPLODE, robot_programs
BOT = array.array('i',[0] * BOT_PARAMS * NUM_BOTS) # Initialize robot parameter array
EXPLODE = array.array('i',[0] * EXPLODE_PARAMS * NUM_EXPLOSIONS) # Initialize explosion array
GAME = array.array('i',[0] * GAME_PARAMS) # Initialize game parameter array
SPRITES = array.array('H',[0] * 8 * 8 * NUM_BOTS) # Initialize sprite data array
ISIN = array.array('i', int(sin(radians(i)) * (1 << SCALE)) for i in range(360)) # Sine lookup table
ICOS = array.array('i', int(cos(radians(i)) * (1 << SCALE)) for i in range(360)) # Cosine lookup table
missile = Missile() # Initialize missile system
robot_programs = [] # Initialize robot program list
for i in range(NUM_BOTS): # Create program slots for each robot
robot_programs.append(RobotProgram()) # Add empty program to list
robot_files = ['robot1.txt', 'robot2.txt', 'robot3.txt', 'robot4.txt', 'robot5.txt'] # Robot source files
for i, filename in enumerate(robot_files): # Load robot programs from files
if i < NUM_BOTS: # Check robot slot availability
prog = load_robot_program(filename) # Load and compile robot program
if prog and prog.active: # Check if program loaded successfully
robot_programs[i] = prog # Store compiled program
print(f"Loaded {filename} as robot {i}") # Confirm successful load
else:
print(f"Failed to load {filename}, using default AI") # Show load failure
for index in range(NUM_BOTS): # Initialize robot sprites
i = index * 8 * 8 # Calculate sprite offset
sprite_offset = index * 8 * 8 # Calculate sprite array offset
raw_offset = index * 8 # Calculate raw sprite offset
for y in range(8): # Process each sprite row
for x in range(8): # Process each sprite column
raw_addr = y + raw_offset # Calculate raw sprite address
color = (SPRITES_RAW[raw_addr] >> x) & 0b00000001 # Extract pixel color
sprite_addr = y * 8 + x + sprite_offset # Calculate sprite array address
if color == 1: # Set white pixel for sprite bit
SPRITES[sprite_addr] = 0xffff # Store white color
def init_bots(): # Initialize robot starting positions and states
global BOT
for index in range(NUM_BOTS): # Initialize each robot
i = index * BOT_PARAMS # Calculate robot parameter offset
hb_idx = index * _HITBOX_PARAMS # Calculate hitbox parameter offset
#BOT[BOT_X + i] = (index * 50) * BOT_SCALE #randint(0,MAXSCREEN_X - 8) # Random starting X position
#BOT[BOT_Y + i] = ((index * 4) + 50) * BOT_SCALE #randint(0,MAXSCREEN_Y - 8) # Random starting Y position
BOT[BOT_X + i] = randint(0,MAXSCREEN_X - 8) * BOT_SCALE # Random starting X position
BOT[BOT_Y + i] = randint(0,MAXSCREEN_Y - 8) * BOT_SCALE # Random starting Y position
BOT[BOT_SPEED_X + i] = 0 # Start with zero X velocity
BOT[BOT_SPEED_Y + i] = 0 # Start with zero Y velocity
BOT[BOT_BOUNCE + i] = 1 # Enable wall bouncing
BOT[DAMAGE + i] = FULL_HEALTH # Set to full health
missile.HITBOXES[hb_idx + _HITBOX_ON] = 1 # Enable collision detection
missile.HITBOXES[hb_idx + _HITBOX_X] = BOT[BOT_X + i] // BOT_SCALE # Set hitbox X position (in pixel scale)
missile.HITBOXES[hb_idx + _HITBOX_Y] = BOT[BOT_Y + i] // BOT_SCALE # Set hitbox Y position (in pixel scale)
GAME[GAME_END] = 0
@micropython.viper
def fire_missile(robot_id: int, distance: int): # Fire missile from robot
bot = ptr32(BOT) # Get robot parameters pointer
miss = ptr32(missile.P) # Get missile parameters pointer
isin = ptr32(ISIN) # Get sine lookup table pointer
icos = ptr32(ICOS) # Get cosine lookup table pointer
i = robot_id * BOT_PARAMS # Calculate robot parameter offset
bot[FIRE_COOL + i] = FIRE_COOL_DOWN # Set gun cooldown timer
bot[MUZZLE_FLASH_TIMER + i] = MUZZLE_FLASH_DURATION # Set muzzle flash timer
gun_aim = int(bot[GUN_AIM + i]) % 360 # Get gun aim angle
bot_x = bot[BOT_X + i] // BOT_SCALE
bot_y = bot[BOT_Y + i] // BOT_SCALE
miss[_VX] = icos[gun_aim] * 3 # Calculate missile X velocity
miss[_VY] = isin[gun_aim] * 3 # Calculate missile Y velocity
miss[_X] = ((bot_x + 4) << SCALE) + (miss[_VX] * 2) # Set missile start X
miss[_Y] = ((bot_y + 4) << SCALE) + (miss[_VY] * 2) # Set missile start Y
miss[_OWNER] = robot_id # Set missile owner
missile.init_missile() # Initialize missile in system
@micropython.viper
def default_ai(robot_id: int): # Default AI for robots without programs
bot = ptr32(BOT) # Get robot parameters pointer
miss = ptr32(missile.P) # Get missile parameters pointer
isin = ptr32(ISIN) # Get sine lookup table pointer
icos = ptr32(ICOS) # Get cosine lookup table pointer
i = robot_id * BOT_PARAMS # Calculate robot parameter offset
if int(randint(0, 100)) < 5: # Random movement decision
bot[BOT_SPEED_X + i] = int(randint(-3, 3)) # Random X velocity
bot[BOT_SPEED_Y + i] = int(randint(-3, 3)) # Random Y velocity
if bot[FIRE_COOL + i] == 0 and int(randint(0, 100)) < 10: # Random firing decision
bot[FIRE_COOL + i] = FIRE_COOL_DOWN # Set gun cooldown
bot[MUZZLE_FLASH_TIMER + i] = MUZZLE_FLASH_DURATION # Set muzzle flash
gun_aim = int(randint(0, 359)) # Random gun aim angle
bot[GUN_AIM + i] = gun_aim # Set gun angle
miss[_VX] = icos[gun_aim] * 3 # Calculate missile X velocity
miss[_VY] = isin[gun_aim] * 3 # Calculate missile Y velocity
bot_x = bot[BOT_X + i] // BOT_SCALE
bot_y = bot[BOT_Y + i] // BOT_SCALE
miss[_X] = ((bot_x + 4) << SCALE) + (miss[_VX] * 2) # Set missile start X
miss[_Y] = ((bot_y + 4) << SCALE) + (miss[_VY] * 2) # Set missile start Y
miss[_OWNER] = robot_id # Set missile owner
missile.init_missile() # Initialize missile
@micropython.viper
def init_explosion(x:int,y:int): # Initialize explosion at coordinates
exp = ptr32(EXPLODE) # Get explosion parameters pointer
for index in range(NUM_EXPLOSIONS): # Find free explosion slot
i = index * EXPLODE_PARAMS # Calculate explosion parameter offset
if exp[EXP_DURATION + i] == 0: # Found inactive explosion slot
exp[EXP_DURATION + i] = 20 # Set explosion duration
exp[EXP_X + i] = x # Set explosion X position
exp[EXP_Y + i] = y # Set explosion Y position
return # Exit after initializing explosion
@micropython.viper
def aim_radar(x: int, y: int, deg: int) -> int: # Perform radar scan from position and angle
isin = ptr32(ISIN) # Get sine lookup table pointer
icos = ptr32(ICOS) # Get cosine lookup table pointer
hitbox = ptr32(missile.HITBOXES) # Get hitbox parameters pointer
bot = ptr32(BOT) # Get robot parameters pointer
dx = icos[deg] # Get X direction component
dy = isin[deg] # Get Y direction component
min_dist = 999999 # Initialize minimum distance
hit_found = 0 # Initialize hit detection flag
for hb in range(0, _NUM_HITBOXES): # Check all hitboxes
hb_idx = hb * _HITBOX_PARAMS # Calculate hitbox parameter offset
if hitbox[hb_idx + _HITBOX_ON]: # Check active hitboxes only
bot_idx = hb * BOT_PARAMS # Calculate robot parameter offset
if bot[bot_idx + DAMAGE] > 0: # Check if robot is alive
hb_w = hitbox[hb_idx + _HITBOX_W] # Get hitbox width
hb_h = hitbox[hb_idx + _HITBOX_H] # Get hitbox height
#hb_x = (hitbox[hb_idx + _HITBOX_X] + (hb_w>>1)) << SCALE # Calculate hitbox left edge
#hb_y = (hitbox[hb_idx + _HITBOX_Y] + (hb_h>>1)) << SCALE # Calculate hitbox top edge
hb_x = (hitbox[hb_idx + _HITBOX_X] - 0) << SCALE # Calculate hitbox left edge
hb_y = (hitbox[hb_idx + _HITBOX_Y] + 0) << SCALE # Calculate hitbox top edge
hb_x2 = hb_x + (hb_w << SCALE) # Calculate hitbox right edge
hb_y2 = hb_y + (hb_h << SCALE) # Calculate hitbox bottom edge
ray_x = x << SCALE # Convert radar X to fixed point
ray_y = y << SCALE # Convert radar Y to fixed point
t_min = 0 # Initialize minimum intersection time
t_max = 999999 # Initialize maximum intersection time
if dx != 0: # Handle X direction intersection
t1 = (hb_x - ray_x) // dx # Calculate intersection time 1
t2 = (hb_x2 - ray_x) // dx # Calculate intersection time 2
t_near = t1 if t1 < t2 else t2 # Get near intersection time
t_far = t1 if t1 > t2 else t2 # Get far intersection time
if t_near > t_min: t_min = t_near # Update minimum time
if t_far < t_max: t_max = t_far # Update maximum time
elif ray_x < hb_x or ray_x > hb_x2: # Ray misses hitbox in X
continue # Skip this hitbox
if dy != 0: # Handle Y direction intersection
t1 = (hb_y - ray_y) // dy # Calculate intersection time 1
t2 = (hb_y2 - ray_y) // dy # Calculate intersection time 2
t_near = t1 if t1 < t2 else t2 # Get near intersection time
t_far = t1 if t1 > t2 else t2 # Get far intersection time
if t_near > t_min: t_min = t_near # Update minimum time
if t_far < t_max: t_max = t_far # Update maximum time
elif ray_y < hb_y or ray_y > hb_y2: # Ray misses hitbox in Y
continue # Skip this hitbox
if t_min <= t_max and t_min > 0: # Valid intersection found
dist = t_min # Distance is intersection time
if dist < min_dist: # Closer than previous hits
min_dist = dist # Update minimum distance
hit_found = 1 # Mark hit as found
if hit_found: # Robot found by radar
return -min_dist # Return distance to robot
edge_dist = 999999 # Initialize edge distance
if dx > 0: # Ray pointing right
t = ((MAXSCREEN_X - x) << SCALE) // dx # Calculate time to right edge
if t < edge_dist: edge_dist = t # Update edge distance
elif dx < 0: # Ray pointing left
t = (x << SCALE) // (-dx) # Calculate time to left edge
if t < edge_dist: edge_dist = t # Update edge distance
if dy > 0: # Ray pointing down
t = ((MAXSCREEN_Y - y) << SCALE) // dy # Calculate time to bottom edge
if t < edge_dist: edge_dist = t # Update edge distance
elif dy < 0: # Ray pointing up
t = (y << SCALE) // (-dy) # Calculate time to top edge
if t < edge_dist: edge_dist = t # Update edge distance
return edge_dist # Return negative distance to edge
@micropython.viper
def bot_actions(): # Execute all robot actions for current frame
bot = ptr32(BOT) # Get robot parameters pointer
game = ptr32(GAME) # Get game parameters pointer
alive_bots = 0
for index in range(NUM_BOTS): # Process each robot
i = index * BOT_PARAMS # Calculate robot parameter offset
if bot[DAMAGE + i] > 0: # Process living robots only
alive_bots += 1
game[GAME_WINNER] = index
execute_robot_program(index) # Execute robot program or AI
if bot[FIRE_COOL + i] > 0: # Update firing cooldown
bot[FIRE_COOL + i] -= 1 # Decrement cooldown timer
if bot[MUZZLE_FLASH_TIMER + i] > 0: # Update muzzle flash timer
bot[MUZZLE_FLASH_TIMER + i] -= 1 # Decrement flash timer
if alive_bots == 1 and game[GAME_END] == 0:
game[GAME_END] = 1
@micropython.viper
def move_bots(): # Update robot positions and handle collisions
bot = ptr32(BOT) # Get robot parameters pointer
hitbox = ptr32(missile.HITBOXES) # Get hitbox parameters pointer
for index in range(NUM_BOTS): # Process each robot
i = index * BOT_PARAMS # Calculate robot parameter offset
hb_idx = index * _HITBOX_PARAMS # Calculate hitbox parameter offset
if bot[DAMAGE + i] > 0: # Process living robots only
bot_x = bot[BOT_X + i] + bot[BOT_SPEED_X + i] # Calculate new X position
bot_y = bot[BOT_Y + i] + bot[BOT_SPEED_Y + i] # Calculate new Y position
bounce = bot[BOT_BOUNCE + i] # Get wall bounce setting
if 0 < bot_x < (MAXSCREEN_X - 8) * BOT_SCALE : # Check X bounds
bot[BOT_X + i] = bot_x # Update X position
hitbox[_HITBOX_X + hb_idx] = bot_x // BOT_SCALE # Update hitbox X position
elif bounce: # Wall bounce enabled
bot[BOT_SPEED_X + i] *= -1 # Reverse X velocity
if 0 < bot_y < (MAXSCREEN_Y - 8) * BOT_SCALE: # Check Y bounds
bot[BOT_Y + i] = bot_y # Update Y position
hitbox[_HITBOX_Y + hb_idx] = bot_y // BOT_SCALE # Update hitbox Y position
elif bounce: # Wall bounce enabled
bot[BOT_SPEED_Y + i] *= -1 # Reverse Y velocity
for i in range(NUM_BOTS): # Check robot-robot collisions
for j in range(i + 1, NUM_BOTS): # Check each pair once
bot1_idx = i * BOT_PARAMS # Calculate first robot offset
bot2_idx = j * BOT_PARAMS # Calculate second robot offset
if bot[DAMAGE + bot1_idx] > 0 and bot[DAMAGE + bot2_idx] > 0: # Both robots alive
x1 = bot[BOT_X + bot1_idx] # Get robot 1 X position
y1 = bot[BOT_Y + bot1_idx] # Get robot 1 Y position
x2 = bot[BOT_X + bot2_idx] # Get robot 2 X position
y2 = bot[BOT_Y + bot2_idx] # Get robot 2 Y position
dx = x2 - x1 # Calculate X distance
dy = y2 - y1 # Calculate Y distance
if -8 < dx < 8 and -8 < dy < 8: # Collision detected
vx1 = bot[BOT_SPEED_X + bot1_idx] # Get robot 1 X velocity
vy1 = bot[BOT_SPEED_Y + bot1_idx] # Get robot 1 Y velocity
vx2 = bot[BOT_SPEED_X + bot2_idx] # Get robot 2 X velocity
vy2 = bot[BOT_SPEED_Y + bot2_idx] # Get robot 2 Y velocity
rel_vx = vx1 - vx2 # Calculate relative X velocity
rel_vy = vy1 - vy2 # Calculate relative Y velocity
rel_speed = (rel_vx * rel_vx + rel_vy * rel_vy) >> 1 # Calculate relative speed
damage_factor = rel_speed + 2 # Calculate damage factor
if damage_factor > 8: damage_factor = 8 # Limit damage factor
collision_damage = (COLLISION_DAMAGE * damage_factor) >> 3 # Calculate collision damage
abs_dx = dx if dx > 0 else -dx # Get absolute X distance
abs_dy = dy if dy > 0 else -dy # Get absolute Y distance
if abs_dx > abs_dy: # Primary collision in X direction
bot[BOT_SPEED_X + bot1_idx] *= -1 # Reverse robot 1 X velocity
bot[BOT_SPEED_X + bot2_idx] *= -1 # Reverse robot 2 X velocity
else: # Primary collision in Y direction
bot[BOT_SPEED_Y + bot1_idx] *= -1 # Reverse robot 1 Y velocity
bot[BOT_SPEED_Y + bot2_idx] *= -1 # Reverse robot 2 Y velocity
bot[DAMAGE + bot1_idx] -= collision_damage # Apply damage to robot 1
bot[DAMAGE + bot2_idx] -= collision_damage # Apply damage to robot 2
@micropython.viper
def draw_muzzle_flash(): # Draw muzzle flash effects for firing robots
bot = ptr32(BOT) # Get robot parameters pointer
isin = ptr32(ISIN) # Get sine lookup table pointer
icos = ptr32(ICOS) # Get cosine lookup table pointer
for index in range(NUM_BOTS): # Process each robot
i = index * BOT_PARAMS # Calculate robot parameter offset
if bot[DAMAGE + i] > 0 and bot[MUZZLE_FLASH_TIMER + i] > 0: # Robot alive and flashing
gun_deg = int(bot[GUN_AIM + i]) # Get gun aim angle
bot_x = bot[BOT_X + i] // BOT_SCALE # Get robot X position
bot_y = bot[BOT_Y + i] // BOT_SCALE # Get robot Y position
cx = bot_x + 4 # Calculate robot center X
cy = bot_y + 4 # Calculate robot center Y
turret_len = 8 # Gun turret length
tip_x = cx + ((icos[gun_deg] * turret_len) >> SCALE) # Calculate gun tip X
tip_y = cy + ((isin[gun_deg] * turret_len) >> SCALE) # Calculate gun tip Y
flash_spread = 30 # Muzzle flash spread angle
muzzle_flash_length = MUZZLE_FLASH_DURATION - bot[MUZZLE_FLASH_TIMER + i] # Flash length
for line in range(MUZZLE_FLASH_LINES): # Draw each flash line
angle_offset = (line - (MUZZLE_FLASH_LINES // 2)) * (flash_spread * 2 // MUZZLE_FLASH_LINES) # Calculate line angle
flash_deg = (gun_deg + angle_offset) % 360 # Calculate flash ray angle
flash_end_x = tip_x + ((icos[flash_deg] * muzzle_flash_length) >> SCALE) # Calculate flash end X
flash_end_y = tip_y + ((isin[flash_deg] * muzzle_flash_length) >> SCALE) # Calculate flash end Y
LCD.fbdraw.line(tip_x, tip_y, flash_end_x, flash_end_y, YELLOW) # Draw flash line
@micropython.viper
def draw_explosion(): # Draw explosion animations
exp = ptr32(EXPLODE) # Get explosion parameters pointer
for index in range(NUM_EXPLOSIONS): # Process each explosion slot
i = index * EXPLODE_PARAMS # Calculate explosion parameter offset
if exp[EXP_DURATION + i] > 0: # Process active explosions only
exp[EXP_DURATION + i] -= 1 # Decrement explosion timer
x = exp[EXP_X + i] # Get explosion X position
y = exp[EXP_Y + i] # Get explosion Y position
if exp[EXP_DURATION + i] < 10: # Explosion shrinking phase
radius = exp[EXP_DURATION + i] + 1 # Calculate shrinking radius
else: # Explosion expanding phase
radius = 21 - exp[EXP_DURATION + i] # Calculate expanding radius
LCD.fbdraw.ellipse(x,y,radius,radius,0xffff,1) # Draw explosion circle
@micropython.viper
def draw_bots(): # Draw all robots on screen
sprites = ptr16(SPRITES) # Get sprite data pointer
bot = ptr32(BOT) # Get robot parameters pointer
screen = ptr16(LCD.fbdraw) # Get screen buffer pointer
hitbox = ptr32(missile.HITBOXES) # Get hitbox parameters pointer
isin = ptr32(ISIN) # Get sine lookup table pointer
icos = ptr32(ICOS) # Get cosine lookup table pointer
for index in range(NUM_BOTS): # Process each robot
i = index * BOT_PARAMS # Calculate robot parameter offset
if bot[DAMAGE + i] > 0: # Draw living robots only
bot_x = bot[BOT_X + i] // BOT_SCALE # Get robot X position
bot_y = bot[BOT_Y + i] // BOT_SCALE # Get robot Y position
# hb_idx = index * _HITBOX_PARAMS
# hb_w = hitbox[hb_idx + _HITBOX_W] # Get hitbox width
# hb_h = hitbox[hb_idx + _HITBOX_H] # Get hitbox height
# hb_x = hitbox[hb_idx + _HITBOX_X] - 1# (hb_w>>1) # Calculate hitbox left edge
# hb_y = hitbox[hb_idx + _HITBOX_Y] - 1#(hb_h>>1) # Calculate hitbox top edge
#
# LCD.fbdraw.rect(hb_x,hb_y,hb_w,hb_h,YELLOW)
health = bot[DAMAGE + i] // 10 # Calculate health bar length
if health < 3: # Low health
color = RED # Red health bar
elif health < 6: # Medium health
color = YELLOW # Yellow health bar
else: # High health
color = GREEN # Green health bar
LCD.fbdraw.line(bot_x - 2,bot_y - 2,bot_x + health,bot_y - 2,color) # Draw health bar
LCD.fbdraw.line(bot_x - 2,bot_y - 3,bot_x + health,bot_y - 3,color) # Draw health bar
gun_deg = int(bot[GUN_AIM + i]) # Get gun aim angle
cx = bot_x + 4 # Calculate robot center X
cy = bot_y + 4 # Calculate robot center Y
turret_len = 8 # Gun turret length
end_x = cx + ((icos[gun_deg] * turret_len) >> SCALE) # Calculate gun end X
end_y = cy + ((isin[gun_deg] * turret_len) >> SCALE) # Calculate gun end Y
LCD.fbdraw.line(cx, cy, end_x, end_y, WHITE) # Draw gun turret
sprite_offset = index * 8 * 8 # Calculate sprite data offset
for y in range(8): # Draw each sprite row
for x in range(8): # Draw each sprite column
color = sprites[y * 8 + x + sprite_offset] # Get sprite pixel color
screen_addr = ((bot_y + y) * MAXSCREEN_X) + bot_x + x # Calculate screen address
if 0 < screen_addr < MAXSCREEN_X * MAXSCREEN_Y: # Check screen bounds
screen[screen_addr] = color # Draw sprite pixel
@micropython.viper
def draw(): # Main drawing function
status = ptr8(LCD.aux) # Get LCD status pointer
game = ptr32(GAME)
LCD.fill2(LCD.fbdraw,0x0) # Clear screen to black
draw_num.draw(FPS_CORE0,220,0) # Draw core 0 FPS
draw_num.draw(FPS_CORE1,220,10) # Draw core 1 FPS
draw_bots() # Draw all robots
draw_muzzle_flash() # Draw muzzle flash effects
draw_explosion() # Draw explosion animations
missile.draw_missile() # Draw all missiles
if game[GAME_END] > 0:
LCD.fbdraw.text('GAME OVER',70,50,YELLOW)
LCD.fbdraw.text(robot_programs[game[GAME_WINNER]].name + ' WINS!',70,70,YELLOW)
#draw_num.draw(MEM_FREE,190,0,LT_BLUE) # Draw free memory counter
status[SHOWING] = 0 # Clear showing flag
@micropython.viper
def main(): # Main game loop
init_bots() # Initialize robot starting positions
status = ptr8(LCD.aux) # Get LCD status pointer
gc.collect() # Force garbage collection
print(gc.mem_free()) # Print free memory
score_ticks = 0 # Initialize score timing
bot = ptr32(BOT) # Get robot parameters pointer
game = ptr32(GAME)
hitbox = ptr32(missile.HITBOXES) # Get hitbox parameters pointer
hit = ptr32(missile.HIT) # Get hit detection pointer
while not status[EXIT]: # Main game loop
if not THREADING: status[SHOWING] = 1
while not status[SHOWING]: sleep_ms(1) # Wait for display ready
draw_num.fb = LCD.fbdraw # Set number drawing framebuffer
ticks = int(time.ticks_ms()) # Get current time
if not FIRE_BUTTON.value() and ticks - score_ticks >1000: # Fire button pressed
score_ticks = ticks # Update score timing
draw_num.add(SCORE, 100) # Add score points
if not SECOND_BUTTON.value() and ticks - score_ticks >1000: # Second button pressed
score_ticks = ticks # Update score timing
draw_num.add(SCORE, 100,1) # Add score points
status[EXIT] = 1 # Set exit flag
draw_num.update_all() # Update all number displays
bot_hit = int(missile.move_missile()) # Move missiles and check hits
if bot_hit > -1: # Robot was hit by missile
damage = bot[bot_hit * BOT_PARAMS + DAMAGE] - MAX_DAMAGE # Calculate new damage
bot[bot_hit * BOT_PARAMS + DAMAGE] = damage # Apply damage to robot
if damage <= 0: # Robot destroyed
bot[bot_hit * BOT_PARAMS + DAMAGE] = 0 # Set damage to zero
hitbox[bot_hit * _HITBOX_PARAMS + _HITBOX_ON] = 0 # Disable hitbox
init_explosion(hit[0], hit[1]) # Create explosion at hit point
bot_actions() # Execute robot AI and actions
move_bots() # Update robot positions
draw() # Draw everything to screen
#draw_num.set(MEM_FREE, gc.mem_free())
if game[GAME_END] > 0:
game[GAME_END] += 1
if game[GAME_END] > 100:
status[EXIT] = 1
draw_num.set(FPS_CORE0, ticks) # Update core 0 FPS counter
shutdown() # Clean shutdown when exiting
def shutdown(): # Clean shutdown function
LCD.aux[EXIT] = 1 # Set LCD exit flag
sleep_ms(200) # Wait for cleanup
LCD.off() # Turn off LCD
sleep_ms(200) # Wait for LCD shutdown
freq(150_000_000,48_000_000) # Reduce CPU frequency
print('core0 done') # Print shutdown message
@micropython.viper
def core1(): # Core 1 display update function
status = ptr8(LCD.aux) # Get LCD status pointer
sleep_ms(200) # Wait for initialization
while not status[EXIT]: # Display loop
ticks=int(time.ticks_ms()) # Get current time
status[SHOWING] = 1 # Set showing flag
LCD.show_all() # Display framebuffer
LCD.flip() # Flip display buffers
draw_num.set(FPS_CORE1, ticks) # Update core 1 FPS counter
print('core1 done') # Print shutdown message
if __name__=='__main__': # Main program entry point
FIRE_BUTTON = Pin(14, Pin.IN, Pin.PULL_UP) # Initialize fire button
SECOND_BUTTON = Pin(15, Pin.IN, Pin.PULL_UP) # Initialize second button
freq(220_000_000) # Set CPU frequency
machine.mem32[0x40010048] = 1<<11 # Configure memory timing
LCD = LCD_3inch5(MAXSCREEN_X,MAXSCREEN_Y) # Initialize LCD display
draw_num = Draw_number(LCD.fbdraw,MAXSCREEN_X) # Initialize number drawing
init_game() # Initialize game systems
if THREADING:
_thread.start_new_thread(core1, ()) # Start display thread on core 1
sleep_ms(200) # Wait for thread startup
main()
try:
main() # Run main game loop
shutdown() # Clean shutdown
except KeyboardInterrupt: # Handle interrupt
shutdown() # Emergency shutdown
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment