Last active
September 2, 2025 04:53
-
-
Save samneggs/61c5db91a51dd601fb3e05539dc01c27 to your computer and use it in GitHub Desktop.
Robot War on Pi Pico 2 in MicroPython
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
================================================================================ | |
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 | |
================================================================================ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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 | |
] | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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