Created
October 21, 2025 14:55
-
-
Save samneggs/813f37f6773493a7fbbcb371f71fe39d to your computer and use it in GitHub Desktop.
Star Raiders 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
| # wav player | |
| # https://antirez.com/news/143 | |
| # ffmpeg -i ohno.wav -ar 24000 -acodec pcm_u8 -f u8 def_fire.raw | |
| from machine import Pin, PWM, mem32, mem8 | |
| from time import sleep_us, sleep | |
| from rp2 import PIO, StateMachine, asm_pio | |
| from uctypes import addressof | |
| from rp2 import DMA | |
| import gc, array, sys | |
| sys.path.append("/gauntlet") | |
| sys.path.append("/starraiders") | |
| PIO0_BASE = const(0x50200000) | |
| PIO1_BASE = const(0x50300000) | |
| TXF0 = const(0x010) | |
| DMA_BASE = const(0x50000000) | |
| CH0_READ = const(0x00) | |
| CH0_WRITE = const(0x04) | |
| CH0_TRANS_COUNT = const(0x08) | |
| CH0_CTRL_TRIG = const(0x0c) | |
| CH1_READ = const(0x40) | |
| CH1_WRITE = const(0x44) | |
| CH1_TRANS_COUNT = const(0x48) | |
| CH1_CTRL_TRIG = const(0x4c) | |
| CH2_READ = const(0x80) | |
| CH2_WRITE = const(0x84) | |
| CH2_TRANS_COUNT = const(0x88) | |
| CH2_CTRL_TRIG = const(0x8c) | |
| CH1_AL1_TRANS_COUNT_TRIG = const(0x05c) | |
| CH1_DBG_TCR = const(0x844) | |
| CHAN_ABORT = const(0x464) | |
| DREQ_PIO0_TX0 = const(0) | |
| DREQ_PIO1_TX0 = const(8) | |
| @asm_pio(sideset_init=PIO.OUT_LOW) | |
| def pwm_prog(): | |
| pull(noblock) .side(0) | |
| out(x,8) | |
| mov(y, isr) # ISR must be preloaded with PWM count max | |
| label("pwmloop") | |
| jmp(x_not_y, "skip") | |
| nop() .side(1) | |
| label("skip") | |
| jmp(y_dec, "pwmloop") | |
| class PIOPWM: | |
| def __init__(self, sm_id, pin , max_count, count_freq): | |
| self._sm = StateMachine(sm_id, pwm_prog, freq=2 * count_freq, sideset_base=Pin(pin)) | |
| # Use exec() to load max count into ISR | |
| self.sm_id = sm_id | |
| self._sm.put(max_count) | |
| self._sm.exec("pull()") | |
| self._sm.exec("mov(isr, osr)") | |
| self._sm.active(1) | |
| self._max_count = max_count | |
| self.addr = array.array('I',(0,0)) | |
| def set(self, value): | |
| # Minimum value is -1 (completely turn off), 0 actually still produces narrow pulse | |
| value = max(value, -1) | |
| value = min(value, self._max_count) | |
| self._sm.put(value) | |
| @micropython.viper | |
| def effect(self,datainptr:ptr32,length:int): | |
| dma_base = ptr32(DMA_BASE) | |
| addrptr = ptr32(self.addr) | |
| addrptr[0] = int(datainptr) | |
| if int(self.sm_id) == 0: | |
| dma_base[CHAN_ABORT//4] = 0b01 | |
| if dma_base[CHAN_ABORT//4] != 0: pass | |
| dma_base[CH0_READ//4] = int(datainptr) | |
| dma_base[CH0_WRITE//4] = int(PIO0_BASE + TXF0 ) | |
| dma_base[CH0_TRANS_COUNT//4] = length | |
| # EN DATA_SIZE INC_READ INC_WRITE CHAIN_TO DREQ | |
| dma_base[CH0_CTRL_TRIG//4] = 1 | 0<<2 | 1<<4 | 0<<6 | 0<<13 | DREQ_PIO0_TX0<<17 | |
| else: | |
| dma_base[CHAN_ABORT//4] = 0b10 | |
| if dma_base[CHAN_ABORT//4] != 0: pass | |
| dma_base[CH1_READ//4] = int(datainptr) | |
| dma_base[CH1_WRITE//4] = int(PIO1_BASE + TXF0 ) | |
| dma_base[CH1_TRANS_COUNT//4] = length | int(1<<28) | |
| dma_base[CH2_READ//4] = int(addrptr) # pointer to CH1_READ | |
| dma_base[CH2_WRITE//4] = int(DMA_BASE+CH1_READ) # reset CH1_READ | |
| dma_base[CH2_TRANS_COUNT//4] = 1 | |
| dma_base[CH2_CTRL_TRIG//4] = 1 | 2<<2 | 0<<4 | 0<<6 | 1<<13 | 0x3f<<17 | |
| # EN DATA_SIZE INC_READ INC_WRITE CHAIN_TO DREQ | |
| dma_base[CH1_CTRL_TRIG//4] = 1 | 0<<2 | 1<<4 | 0<<6 | 2<<13 | DREQ_PIO1_TX0<<17 | |
| @micropython.viper | |
| def set_volume(volume:int): # 0-9 | |
| buff_in = ptr8(buf2) | |
| buff_out = ptr8(BUFF_OUTPUT) | |
| # new_sample = 128 + ((sample - 128) * volume) // 9 | |
| for i in range(int(len(buf2))): | |
| sample = buff_in[i] | |
| buff_out[i] = 128 + ((sample - 128) * volume * 1000) // (9 * 300) | |
| if __name__=='__main__': | |
| pwm1 = PIOPWM(0, 20, max_count=(1 << 10) - 1, count_freq=20_000_000) | |
| pwm2 = PIOPWM(4, 21, max_count=(1 << 10) - 1, count_freq=20_000_000) | |
| FIRE_BUTTON = Pin(14, Pin.IN, Pin.PULL_UP) | |
| BOMB_BUTTON = Pin(15, Pin.IN, Pin.PULL_UP) | |
| f = open("/starraiders/engines.raw","rb") | |
| buf2 = f.read() #+ bytearray(100_000) | |
| fadein = bytearray(range(0,128)) | |
| fadeout = bytearray(range(128,0,-1)) | |
| #buf2 = fadein + buf2 + fadeout | |
| f.close() | |
| BUFF_OUTPUT = bytearray(len(buf2)) | |
| f = open("/starraiders/command.raw","rb") | |
| buf1 = f.read() | |
| buf1 = fadein + buf1 + fadeout | |
| f.close() | |
| #machine.freq(220_000_000) #220 | |
| #machine.mem32[0x40010048] = 1<<11 # enable peri_ctrl clock | |
| length1 = len(buf1) | |
| length2 = len(buf2) | |
| print(length2) | |
| print('buf1 ', hex(addressof(buf1))) | |
| print('buf2 ', hex(addressof(buf2))) | |
| print('ch0 write error',mem32[DMA_BASE + CH0_CTRL_TRIG] & (1<<29)) # write error? | |
| mem32[DMA_BASE + CH1_CTRL_TRIG] = (1<<30) # clear error | |
| print('ch0 read error',mem32[DMA_BASE + CH0_CTRL_TRIG] & (1<<30)) # read error? | |
| print('ch1 write error',mem32[DMA_BASE + CH1_CTRL_TRIG] & (1<<29)) # write error? | |
| print('ch1 read error',mem32[DMA_BASE + CH1_CTRL_TRIG] & (1<<30)) # read error? | |
| while True: | |
| if FIRE_BUTTON.value() == 0: | |
| pwm1.effect(buf1,length1) | |
| if BOMB_BUTTON.value() == 0: | |
| for volume in range(10): | |
| #volume = (volume + 1) % 10 | |
| set_volume(volume) | |
| pwm2.effect(BUFF_OUTPUT,length2) | |
| sleep(.3) | |
| #mem32[0x5000000c] = 0 | |
| sleep_us(250_000) | |
| while mem32[DMA_BASE + CH0_CTRL_TRIG] & (1<<26):pass # wait till done | |
| while mem32[DMA_BASE + CH1_CTRL_TRIG] & (1<<26):pass # wait till done | |
| pwm1._sm.active(0) | |
| pwm2._sm.active(0) | |
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
| # Star Raiders 240x160 on core 1 | |
| from st7796 import LCD_3inch5 | |
| from gt911 import GT911 | |
| from random import randint | |
| from machine import freq, I2C, Pin | |
| import time, _thread, gc, framebuf , array, math, sys | |
| from time import sleep_ms, sleep_us | |
| from math import sin, cos, radians | |
| from draw_number import Draw_number | |
| from joystick import Joystick | |
| sys.path.append("/starraiders") | |
| from spritedata import * | |
| from pwm_dma5 import PIOPWM | |
| SOUND_ON = const(1) | |
| MAXSCREEN_X = const(240) | |
| MAXSCREEN_Y = const(160) | |
| SCALE = const(16) | |
| SHOWING = const(0) | |
| EXIT = const(1) | |
| BROWN = const(0x6092) | |
| BLUE = const(0b_11111_00000_00000) | |
| GREEN = const(0b111_00000_00000_111) | |
| DK_GREEN = const(0b111_00000_00000_011) | |
| YELLOW = const(0xff) | |
| PINK = const(0x1ff8) | |
| LT_PINK= const(0b000_11111_11111_110) | |
| PURPLE = const(0x1188) | |
| RED = const(0b_00000_11111_00000) | |
| GREY = const(0b000_11000_11000_110) | |
| WHITE = const(0xffff) | |
| LT_GREY= const(0b_000_10000_10000_100) | |
| LT_BLUE= const(0b_100_11111_10000_101) | |
| FPS_CORE0 = const(0) | |
| FPS_CORE1 = const(1) | |
| SCORE = const(2) | |
| LIVES = const(3) | |
| HEALTH = const(4) | |
| MEM_FREE = const(5) | |
| TEMP1 = const(6) | |
| TEMP2 = const(7) | |
| NUM_VALUES = const(10) | |
| NUM_STARS = const(200) | |
| NUM_EXPLODE = const(70) | |
| PLAYER_X = const(0) | |
| PLAYER_Y = const(1) | |
| PLAYER_Z = const(2) | |
| PLAYER_SPEED = const(3) | |
| PLAYER_VX = const(4) | |
| PLAYER_VY = const(5) | |
| PLAYER_VZ = const(6) | |
| PLAYER_DEG = const(7) | |
| PLAYER_FIRE = const(8) | |
| PLAYER_KILLED = const(9) | |
| PLAYER_ENERGY = const(10) | |
| PLAYER_HUD = const(11) | |
| PLAYER_LAST_ENERGY_UPDATE = const(12) | |
| PLAYER_PITCH = const(13) | |
| PLAYER_YAW = const(14) | |
| PLAYER_CLOSE = const(15) | |
| PLAYER_RANGE = const(16) | |
| PLAYER_THETA = const(17) | |
| PLAYER_PHI = const(18) | |
| PLAYER_CHART_X = const(19) | |
| PLAYER_CHART_Y = const(20) | |
| PLAYER_HIT = const(21) | |
| PLAYER_CONTROLS= const(22) | |
| PLAYER_PARAMS = const(23) | |
| CONTROLS_ATTACK = const(0b01) | |
| CONTROLS_SHIELD = const(0b10) | |
| HUD_ATTACK = const(0b000_0001) | |
| HUD_CROSS = const(0b000_0010) | |
| HUD_SCAN = const(0b000_0100) | |
| HUD_CHART = const(0b000_1000) | |
| HUD_SHIELD = const(0b001_0000) | |
| HUD_TOUCH = const(0b010_0000) | |
| HUD_HYPER = const(0b100_0000) | |
| HUD_TEXT1_Y = const(141) | |
| HUD_TEXT2_Y = const(150) | |
| CROSS_VX = const(MAXSCREEN_X // 2) | |
| CROSS_VY1 = const(MAXSCREEN_Y // 2 - 12) | |
| CROSS_VY2 = const(MAXSCREEN_Y // 2 - 5) | |
| CROSS_VY3 = const(MAXSCREEN_Y // 2 + 5) | |
| CROSS_VY4 = const(MAXSCREEN_Y // 2 + 12) | |
| CROSS_HY = const(MAXSCREEN_Y // 2) | |
| CROSS_HX1 = const(MAXSCREEN_X // 2 - 20) | |
| CROSS_HX2 = const(MAXSCREEN_X // 2 - 5) | |
| CROSS_HX3 = const(MAXSCREEN_X // 2 + 5) | |
| CROSS_HX4 = const(MAXSCREEN_X // 2 + 20) | |
| ACOMP_R1X = const(180) | |
| ACOMP_R1Y = const(110) | |
| ACOMP_R1W = const(45) | |
| ACOMP_R1H = const(30) | |
| ACOMP_R2X = const(ACOMP_R1X + 10) | |
| ACOMP_R2Y = const(ACOMP_R1Y + 10) | |
| ACOMP_R2W = const(ACOMP_R1W - 20) | |
| ACOMP_R2H = const(ACOMP_R1H - 20) | |
| ACOMP_VX = const(ACOMP_R1X + ACOMP_R1W // 2) | |
| ACOMP_VY1 = const(ACOMP_R1Y) | |
| ACOMP_VY2 = const(ACOMP_R2Y) | |
| ACOMP_VY3 = const(ACOMP_R2Y + ACOMP_R2H) | |
| ACOMP_VY4 = const(ACOMP_R1Y + ACOMP_R1H - 1) | |
| ACOMP_HY = const(ACOMP_R1Y + ACOMP_R1H // 2) | |
| ACOMP_HX1 = const(ACOMP_R1X) | |
| ACOMP_HX2 = const(ACOMP_R2X) | |
| ACOMP_HX3 = const(ACOMP_R2X + ACOMP_R2W) | |
| ACOMP_HX4 = const(ACOMP_R1X + ACOMP_R1W - 1) | |
| # Galactic chart sector constants | |
| SECTOR_EMPTY = const(0) # Empty sector | |
| SECTOR_STARBASE = const(26) # Starbase sector (custom char) | |
| SECTOR_4ZYLON = const(27) # 4 Zylon fighter sector | |
| SECTOR_3ZYLON = const(28) # 3 Zylon fighter sector | |
| SECTOR_2ZYLON = const(29) # 2 Zylon fighter sector | |
| CHART_WIDTH = const(16) # Chart grid width | |
| CHART_HEIGHT = const(8) # Chart grid height | |
| CHART_SECTORS = const(128) # Total sectors (16x8) | |
| CHART_X = const(6) # galactic chart grid | |
| CHART_Y = const(20) | |
| CHART_SIZE = const(14) # | |
| TYPE = const(4) | |
| TYPE_NONE = const(0) | |
| TYPE_STAR = const(1) | |
| TYPE_EXP = const(2) | |
| # game parameters | |
| SHOW_FPS = const(0) | |
| CHART_X_TEMP = const(1) | |
| CHART_Y_TEMP = const(2) | |
| SECTOR_ENEMY_COUNT = const(4) # Track remaining enemies in current sector | |
| SECTOR_STARBASE_PARTS = const(5) # Track starbase parts (bit flags: 1=left, 2=center, 4=right) | |
| HYPER_STAGE = const(6) # 0=off, 1-200=init, 201-500=control, 501-600=re-entry | |
| HYPER_X = const(7) | |
| HYPER_Y = const(8) | |
| ENEMY_TORPEDOS = const(9) | |
| GAME_PARAMS = const(10) | |
| COORD_SCALE = const(16) # 16 | |
| MAX_OBJECTS = const(300) | |
| COORD_RANGE = const(2000000) | |
| OBJ_NONE = const(0) | |
| OBJ_TORPEDO = const(1) | |
| OBJ_FIGHTER = const(2) | |
| OBJ_STARBASER = const(3) | |
| OBJ_STARBASEC = const(4) | |
| OBJ_STARBASEL = const(5) | |
| OBJ_TRANSFER = const(6) | |
| OBJ_METEOR = const(7) | |
| OBJ_CRUISER = const(8) | |
| OBJ_BASESTAR = const(9) | |
| OBJ_WARPTARGET = const(10) | |
| OBJ_STAR = const(11) | |
| OBJ_EXPLOSION = const(12) | |
| OBJ_ENEMY_TORPEDO = const(13) | |
| OBJ_COLORS = array.array('H',(LT_PINK,GREY,YELLOW,YELLOW,YELLOW,GREY,LT_BLUE,GREY,DK_GREEN,LT_BLUE)) | |
| # object parameters | |
| X_COORD = const(0) | |
| Y_COORD = const(1) | |
| Z_COORD = const(2) | |
| VX_COORD = const(3) | |
| VY_COORD = const(4) | |
| VZ_COORD = const(5) | |
| OBJ_TYPE = const(6) | |
| OBJ_LIFE = const(7) | |
| OBJ_PARAMS = const(8) | |
| ENGINE_ENERGY_DRAIN = array.array('i',([ | |
| 0, # Speed 0: 0 units/sec | |
| 10, # Speed 1: 1.0 units/sec | |
| 15, # Speed 2: 1.5 units/sec | |
| 20, # Speed 3: 2.0 units/sec | |
| 25, # Speed 4: 2.5 units/sec | |
| 35, # Speed 5: 3.5 units/sec | |
| 50, # Speed 6: 5.0 units/sec | |
| 75, # Speed 7: 7.5 units/sec | |
| 112, # Speed 8: 11.25 units/sec | |
| 150 # Speed 9: 15.0 units/sec | |
| ])) | |
| ENERGY_SCALE = const(10) | |
| SPRITE_WIDTH = const(7) | |
| SPRITE_HEIGHT = const(7) | |
| SPRITE_FIGHTER = bytearray([ | |
| 0,0,0,0,0,0,0, | |
| 1,0,0,0,0,0,1, | |
| 1,0,1,1,1,0,1, | |
| 1,1,1,1,1,1,1, | |
| 1,1,1,1,1,1,1, | |
| 1,0,1,1,1,0,1, | |
| 1,0,0,0,0,0,1 | |
| ]) | |
| CHAR_MAP = bytearray(( | |
| 0x00, 0x7F, 0x47, 0x47, 0x47, 0x47, 0x47, 0x7F, # 0 | |
| 0x00, 0x30, 0x10, 0x10, 0x10, 0x38, 0x38, 0x38, # 1 | |
| 0x00, 0x78, 0x08, 0x08, 0x78, 0x40, 0x40, 0x78, # 2 | |
| 0x00, 0x78, 0x08, 0x08, 0x7C, 0x0C, 0x0C, 0x7C, # 3 | |
| 0x00, 0x60, 0x60, 0x60, 0x6C, 0x7C, 0x0C, 0x0C, # 4 | |
| 0x00, 0x78, 0x40, 0x40, 0x78, 0x08, 0x08, 0x78, # 5 | |
| 0x00, 0x78, 0x48, 0x40, 0x40, 0x7E, 0x42, 0x7E, # 6 | |
| 0x00, 0x7C, 0x44, 0x04, 0x1C, 0x10, 0x10, 0x10, # 7 | |
| 0x00, 0x38, 0x28, 0x28, 0x7C, 0x6C, 0x6C, 0x7C, # 8 | |
| 0x00, 0x7C, 0x44, 0x44, 0x7C, 0x0C, 0x0C, 0x0C, # 9 | |
| 0x38, 0x38, 0x38, 0x00, 0x00, 0x38, 0x38, 0x38, # Custom character ':' | |
| 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF, # Custom character 'BORDER SOUTHWEST' | |
| 0x00, 0x3C, 0x20, 0x20, 0x78, 0x60, 0x60, 0x7C, # Custom character 'E' | |
| 0x00, 0x66, 0x99, 0x99, 0x99, 0x66, 0x00, 0x00, # Custom character 'INFINITY' | |
| 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, # Custom character '-' | |
| 0x00, 0x18, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x18, # Custom character '+' | |
| 0x00, 0x18, 0x7E, 0xDB, 0x99, 0xDB, 0x7E, 0x18, # Custom character 'PHI' | |
| 0x66, 0x66, 0x66, 0x66, 0x66, 0x2C, 0x38, 0x30, # Custom character 'V' | |
| 0x00, 0x7C, 0x44, 0x44, 0x7C, 0x68, 0x6C, 0x6C, # Custom character 'R' | |
| 0x00, 0x1C, 0x3E, 0x63, 0x5D, 0x63, 0x3E, 0x1C, # Custom character 'THETA' | |
| 0x00, 0x46, 0x46, 0x44, 0x7C, 0x64, 0x66, 0x66, # Custom character 'K' | |
| 0xFE, 0x92, 0x10, 0x18, 0x18, 0x18, 0x18, 0x18, # Custom character 'T' | |
| 0xFC, 0x8C, 0x8C, 0x80, 0x80, 0x80, 0x84, 0xFC, # Custom character 'C' | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, # Custom character 'BORDER SOUTH' | |
| 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, # Custom character 'BORDER WEST' | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, # Custom character 'CORNER SOUTHWEST' | |
| 0x80, 0xAA, 0x9C, 0xBE, 0x9C, 0xAA, 0x80, 0xFF, # Custom character 'STARBASE SECTOR' | |
| 0x80, 0x98, 0x80, 0xB6, 0x80, 0x8C, 0x80, 0xFF, # Custom character '4-ZYLON SECTOR' | |
| 0x80, 0x8E, 0x80, 0xB8, 0x80, 0x9C, 0x80, 0xFF, # Custom character '3-CYCLON SECTOR' | |
| 0x80, 0xB0, 0x98, 0xBE, 0x98, 0xB0, 0x80, 0xFF)) # Custom character '2-ZYLON SECTOR' | |
| CHAR_INFINITY = const(13) | |
| CHAR_PHI = const(16) | |
| CHAR_THETA = const(19) | |
| @micropython.viper | |
| def draw_viper(num: int, x_offset: int, y_offset: int, color: int, size: int): | |
| char_ptr = ptr8(CHAR_MAP) | |
| screen_ptr = ptr16(LCD.fbdraw) | |
| char = 0 | |
| width = MAXSCREEN_X | |
| offset = width * (y_offset - 1) + x_offset | |
| first = 1 | |
| negative = 0 | |
| if num < 0: | |
| num *= -1 | |
| negative = 1 | |
| while num > 0 or first: | |
| first = 0 | |
| total = num // 10 | |
| digit = num - (total * 10) | |
| num = total | |
| for y in range(8): | |
| row_data = char_ptr[digit * 8 + y] | |
| for x in range(8): | |
| if row_data & (1 << (7-x)) > 0: | |
| addr = size * y * width + x - (char * 8) + offset | |
| screen_ptr[addr] = color | |
| if size > 1: | |
| screen_ptr[width + addr] = color | |
| if size > 2: | |
| screen_ptr[2 * width + addr] = color | |
| char += 1 | |
| if negative: | |
| addr -= 8 + 4 * width | |
| for i in range(4): | |
| screen_ptr[addr-i] = color | |
| @micropython.viper | |
| def draw_char(num: int, x_offset: int, y_offset: int, color: int): | |
| char_ptr = ptr8(CHAR_MAP) # Get character bitmap data pointer | |
| screen_ptr = ptr16(LCD.fbdraw) # Get screen buffer pointer | |
| width = MAXSCREEN_X # Screen width for address calculation | |
| offset = width * y_offset + x_offset # Calculate starting screen address | |
| for y in range(8): # Loop through 8 rows of character | |
| row_data = char_ptr[num * 8 + y] # Get bitmap row for this character | |
| for x in range(8): # Loop through 8 bits in row | |
| if row_data & (1 << (7-x)) > 0: # Check if pixel should be drawn | |
| addr = y * width + x + offset # Calculate pixel screen address | |
| screen_ptr[addr] = color # Draw pixel with specified color | |
| def init_game(): | |
| global GAME, ASTEROID, PLAYER, SPACE, ISIN, ICOS, POLY_SMALL_BUTTON, POLY_LARGE_BUTTON, OBJECTS | |
| PLAYER = array.array('i', [0] * PLAYER_PARAMS) | |
| ASTEROID = array.array('i', [randint(-100, 100), randint(-50, 50), randint(50, 150), randint(2, 5)]) | |
| GAME = array.array('i', 0 for _ in range(GAME_PARAMS)) | |
| OBJECTS = array.array('i', [0] * (MAX_OBJECTS * OBJ_PARAMS)) | |
| ISIN = array.array('i', int(sin(radians(i)) * (1 << SCALE)) for i in range(360)) | |
| ICOS = array.array('i', int(cos(radians(i)) * (1 << SCALE)) for i in range(360)) | |
| size = 12 | |
| POLY_SMALL_BUTTON = array.array('h',[0,0,size,0, # - | |
| size+size//2,size, # \ | |
| size,size*2, # / | |
| 0,size*2, # - | |
| -size//2,size, # \ | |
| 0,0]) # / | |
| hsize = 130 | |
| vsize = 10 | |
| POLY_LARGE_BUTTON = array.array('h',[0,0,hsize,0,# - | |
| hsize+vsize//2,vsize, # \ | |
| hsize,vsize*2, # / | |
| 0,vsize*2, # - | |
| -vsize//2,vsize, # \ | |
| 0,0]) # / | |
| GAME[SHOW_FPS] = 36 * 3 | |
| PLAYER[PLAYER_SPEED] = 0 | |
| PLAYER[PLAYER_ENERGY] = 99990 | |
| PLAYER[PLAYER_X] = 0 | |
| PLAYER[PLAYER_Y] = 0 | |
| PLAYER[PLAYER_Z] = 0 | |
| PLAYER[PLAYER_VX] = 0 | |
| PLAYER[PLAYER_VY] = 0 | |
| PLAYER[PLAYER_VZ] = 0 | |
| PLAYER[PLAYER_PITCH] = 0 | |
| PLAYER[PLAYER_YAW] = 0 | |
| #PLAYER[PLAYER_HUD] |= HUD_CROSS | |
| #PLAYER[PLAYER_HUD] |= HUD_ATTACK | |
| #PLAYER[PLAYER_HUD] |= HUD_SHIELD | |
| PLAYER[PLAYER_LAST_ENERGY_UPDATE] = int(time.ticks_ms()) | |
| PLAYER[PLAYER_CHART_X] = randint(0,15) | |
| PLAYER[PLAYER_CHART_Y] = randint(0,7) | |
| GAME[CHART_X_TEMP] = PLAYER[PLAYER_CHART_X] | |
| GAME[CHART_Y_TEMP] = PLAYER[PLAYER_CHART_Y] | |
| def init_sounds(warp=False): | |
| global SOUND1, SOUND2, COMMAND_SOUND, PHOTON_SOUND, EXPLOSION_SOUND, ENGINE_SOUND, ENGINE_SOUND2 | |
| gc.collect() | |
| fadein = bytearray(range(0,128)) | |
| fadeout = bytearray(range(128,0,-1)) | |
| f = open("/starraiders/command.raw","rb") | |
| COMMAND_SOUND = fadein + f.read() + fadeout | |
| f.close() | |
| f = open("/starraiders/photon3.raw","rb") | |
| PHOTON_SOUND = fadein + f.read() + fadeout | |
| f.close() | |
| f = open("/starraiders/explosion.raw","rb") | |
| EXPLOSION_SOUND = fadein + f.read(40_000) + fadeout #60k | |
| f.close() | |
| f = open("/starraiders/engines.raw","rb") | |
| ENGINE_SOUND = f.read() | |
| f.close() | |
| ENGINE_SOUND2 = ENGINE_SOUND[:] | |
| SOUND1 = PIOPWM(0, 20, max_count=(1 << 10) - 1, count_freq=20_000_000) | |
| SOUND2 = PIOPWM(4, 21, max_count=(1 << 10) - 1, count_freq=20_000_000) | |
| if SOUND_ON == 0: | |
| SOUND1._sm.active(0) | |
| SOUND2._sm.active(0) | |
| def init_stars3d(): | |
| for i in range(30,NUM_STARS): | |
| x = randint(-10000,10000) | |
| y = randint(-10000,10000) | |
| z = randint( 10000,20000) | |
| create_object(i, OBJ_STAR, x,y,z) | |
| @micropython.viper | |
| def init_hyperstars(): | |
| isin = ptr32(ISIN) | |
| icos = ptr32(ICOS) | |
| for i in range(30,NUM_STARS): | |
| deg = int(randint(0,359)) | |
| x = icos[deg] * int(randint(-1000,1000)) >> SCALE | |
| y = isin[deg] * int(randint(-1000,1000)) >> SCALE | |
| z = randint( 30000,50000) | |
| create_object(i, OBJ_STAR, x,y,z) | |
| def init_sector(): # Initialize sector based on chart position | |
| global SPACE, PLAYER, GALACTIC_CHART # Access global variables | |
| for i in range(1, 20): # Clear existing major objects | |
| base = i * OBJ_PARAMS | |
| OBJECTS[base + OBJ_TYPE] = OBJ_NONE | |
| chart_x = PLAYER[PLAYER_CHART_X] # Get current chart X position | |
| chart_y = PLAYER[PLAYER_CHART_Y] # Get current chart Y position | |
| sector_idx = chart_y * CHART_WIDTH + chart_x # Calculate sector index | |
| sector_content = GALACTIC_CHART[sector_idx] # Get sector content type | |
| if sector_content == SECTOR_STARBASE: # Starbase sector | |
| create_starbase(1) # Create starbase at slot 1-3 | |
| GAME[SECTOR_STARBASE_PARTS] = 1 | |
| elif sector_content == SECTOR_4ZYLON: # 4 enemy fighters | |
| create_fighter(1, OBJ_FIGHTER) # Create fighter at slot 1 | |
| create_fighter(2, OBJ_FIGHTER) # Create fighter at slot 2 | |
| create_fighter(3, OBJ_CRUISER) # Create cruiser at slot 3 | |
| create_fighter(4, OBJ_BASESTAR) # Create basestar at slot 4 | |
| GAME[SECTOR_ENEMY_COUNT] = 4 | |
| elif sector_content == SECTOR_3ZYLON: # 3 enemy fighters | |
| create_fighter(1, OBJ_FIGHTER) # Create fighter at slot 1 | |
| create_fighter(2, OBJ_FIGHTER) # Create fighter at slot 2 | |
| create_fighter(3, OBJ_CRUISER) # Create cruiser at slot 3 | |
| GAME[SECTOR_ENEMY_COUNT] = 3 | |
| elif sector_content == SECTOR_2ZYLON: # 2 enemy fighters | |
| create_fighter(1, OBJ_FIGHTER) # Create fighter at slot 1 | |
| create_fighter(2, OBJ_FIGHTER) # Create fighter at slot 2 | |
| GAME[SECTOR_ENEMY_COUNT] = 2 | |
| if GAME[SECTOR_ENEMY_COUNT] > 0: | |
| PLAYER[PLAYER_HIT] = 45 | |
| for i in range(2): # Add random meteors | |
| create_meteor(15 + i) # Create meteor at slot 15+ | |
| def init_galactic_chart(): | |
| """Initialize galactic chart with random sector populations""" | |
| global GALACTIC_CHART | |
| GALACTIC_CHART = array.array('i', [SECTOR_EMPTY] * CHART_SECTORS) # Initialize empty sectors | |
| # Populate sectors with random content | |
| for sector in range(CHART_SECTORS): | |
| rand_val = randint(0, 100) # Random percentage for sector type | |
| if rand_val < 5: # 5% chance of starbase | |
| GALACTIC_CHART[sector] = SECTOR_STARBASE | |
| elif rand_val < 10: # 10% chance of 4 enemy fighters | |
| GALACTIC_CHART[sector] = SECTOR_4ZYLON | |
| elif rand_val < 20: # 20% chance of 3 enemy fighters | |
| GALACTIC_CHART[sector] = SECTOR_3ZYLON | |
| elif rand_val < 30: # 30% chance of 2 enemy fighters | |
| GALACTIC_CHART[sector] = SECTOR_2ZYLON | |
| else: # 25% chance of empty sector | |
| GALACTIC_CHART[sector] = SECTOR_EMPTY | |
| @micropython.viper | |
| def set_volume(volume:int): # 0-9 | |
| buff_in = ptr8(ENGINE_SOUND) | |
| buff_out = ptr8(ENGINE_SOUND2) | |
| for i in range(int(len(ENGINE_SOUND))): | |
| sample = buff_in[i] | |
| buff_out[i] = 128 + ((sample - 128) * volume * 1000) // (9 * 300) | |
| def create_object(index, obj_type, x=0, y=0, z=0, vx=0, vy=0, vz=0): | |
| """Create a space object at given index with coordinates and velocity""" | |
| if 0 <= index < MAX_OBJECTS: | |
| base = index * OBJ_PARAMS | |
| OBJECTS[base + X_COORD] = x | |
| OBJECTS[base + Y_COORD] = y | |
| OBJECTS[base + Z_COORD] = z | |
| OBJECTS[base + VX_COORD] = vx | |
| OBJECTS[base + VY_COORD] = vy | |
| OBJECTS[base + VZ_COORD] = vz | |
| OBJECTS[base + OBJ_TYPE] = obj_type | |
| return True | |
| return False | |
| def create_fighter(index, ship_type): | |
| base_range = 30000 | |
| spread = 20000 | |
| x = randint(-spread, spread) | |
| y = randint(-spread//2, spread//2) | |
| z = base_range + randint(-10000, 5000) | |
| vx = randint(-100, 100) | |
| vy = randint(-100, 100) | |
| vz = randint(-100, 100) | |
| create_object(index, ship_type, x, y, z, vx, vy, vz) | |
| def create_starbase(base_index): | |
| base_x = randint(-80000, 80000) # 40 | |
| base_y = randint(-50000, 50000) # 20 | |
| base_z = randint(-200000, 200000) | |
| create_object(base_index + 1, OBJ_STARBASEC, base_x, base_y, base_z) | |
| def create_meteor(index): | |
| range_limit = 60000 | |
| x = randint(-range_limit, range_limit) | |
| y = randint(-range_limit//2, range_limit//2) | |
| z = randint(-range_limit, range_limit) | |
| vx = randint(-100, 100) | |
| vy = randint(-50, 50) | |
| vz = randint(-200, -50) | |
| create_object(index, OBJ_METEOR, x, y, z, vx, vy, vz) | |
| @micropython.viper | |
| def create_photon(): # Fixed photon creation | |
| objects = ptr32(OBJECTS) # Get objects array pointer | |
| player = ptr32(PLAYER) # Get player data pointer | |
| SOUND1.effect(PHOTON_SOUND,int(len(PHOTON_SOUND))) | |
| player[PLAYER_FIRE] ^= 1 | |
| if player[PLAYER_FIRE]: | |
| photon_x = 1000 | |
| else: | |
| photon_x = -1300 | |
| photon_y = 500 | |
| photon_z = 0 | |
| # Photon moves forward in player's current direction | |
| photon_vx = player[PLAYER_YAW] << 2 # X velocity based on yaw | |
| photon_vy = player[PLAYER_PITCH] << 2 # Y velocity based on pitch | |
| photon_vz = 1000 # Fast forward velocity | |
| torpedo_count = 0 # Initialize counter | |
| for i in range(MAX_OBJECTS): # Check all objects | |
| base = i * OBJ_PARAMS # Calculate base offset | |
| if objects[base + OBJ_TYPE] == OBJ_TORPEDO: # Found existing torpedo | |
| torpedo_count += 1 # Increment counter | |
| # Find empty slot for photon | |
| for i in range(MAX_OBJECTS): # Search all slots | |
| base = i * OBJ_PARAMS # Calculate base offset | |
| if (objects[base + OBJ_TYPE] == OBJ_TORPEDO and torpedo_count == 2) or (objects[base + OBJ_TYPE] == OBJ_NONE and torpedo_count < 2) : # Found empty slot | |
| objects[base + X_COORD] = photon_x # Set X position | |
| objects[base + Y_COORD] = photon_y # Set Y position | |
| objects[base + Z_COORD] = photon_z # Set Z position | |
| objects[base + VX_COORD] = photon_vx # Set X velocity | |
| objects[base + VY_COORD] = photon_vy # Set Y velocity | |
| objects[base + VZ_COORD] = photon_vz # Set Z velocity | |
| objects[base + OBJ_TYPE] = OBJ_TORPEDO # Set object type | |
| objects[base + OBJ_LIFE] = 200 | |
| return # Exit after creating one | |
| # TODO | |
| @micropython.viper | |
| def create_enemy_torpedo(enemy_index:int): # Enemy fires at player | |
| objects = ptr32(OBJECTS) | |
| game = ptr32(GAME) | |
| base = enemy_index * OBJ_PARAMS | |
| if game[ENEMY_TORPEDOS] > 2 :return # max enemy torpedo | |
| enemy_x = objects[base + X_COORD] | |
| enemy_y = objects[base + Y_COORD] | |
| enemy_z = objects[base + Z_COORD] # Get enemy position | |
| # Calculate vector to player (at 0,0,0) | |
| target_vx = -enemy_x // 100 # Aim toward player X | |
| target_vy = -enemy_y // 100 # Aim toward player Y | |
| target_vz = -enemy_z // 50 # Aim toward player Z | |
| for i in range(MAX_OBJECTS): # Search all slots | |
| base = i * OBJ_PARAMS # Calculate base offset | |
| if objects[base + OBJ_TYPE] == OBJ_NONE: | |
| objects[base + OBJ_LIFE] = 200 | |
| create_object(i, # available slot | |
| OBJ_ENEMY_TORPEDO, # Enemy torpedo type | |
| enemy_x, enemy_y, enemy_z, # Start at enemy position | |
| target_vx, target_vy, target_vz) # Velocity toward player | |
| game[ENEMY_TORPEDOS] += 1 | |
| return | |
| @micropython.viper | |
| def create_explosion(x:int,y:int, z:int): | |
| SOUND1.effect(EXPLOSION_SOUND,int(len(EXPLOSION_SOUND))) | |
| objects = ptr32(OBJECTS) | |
| total = 0 | |
| for i in range(MAX_OBJECTS): # Process all objects | |
| base = i * OBJ_PARAMS # Calculate base offset | |
| obj_type = objects[base + OBJ_TYPE] | |
| if obj_type == OBJ_NONE: | |
| objects[base + OBJ_TYPE] = OBJ_EXPLOSION | |
| objects[base + OBJ_LIFE] = 200 | |
| objects[base + X_COORD] = x + int(randint(-500, 500)) # Random X position | |
| objects[base + Y_COORD] = y + int(randint(-500, 500)) # Random Y position | |
| objects[base + Z_COORD] = z + int(randint(-500, 500)) # Random Z distance | |
| objects[base + VX_COORD] = int(randint(-50, 50)) | |
| objects[base + VY_COORD] = int(randint(-50, 50)) | |
| objects[base + VZ_COORD] = int(randint(-50, 50)) | |
| total += 1 | |
| if total == NUM_EXPLODE: return | |
| @micropython.viper | |
| def check_collision(torpedo_index: int) -> int: # Check torpedo collision with all objects | |
| player = ptr32(PLAYER) | |
| game = ptr32(GAME) | |
| objects = ptr32(OBJECTS) # Get objects array pointer | |
| torpedo_base = torpedo_index * OBJ_PARAMS # Calculate torpedo base offset | |
| if objects[torpedo_base + OBJ_TYPE] != OBJ_TORPEDO: # Not a torpedo | |
| return 0 # No collision possible | |
| torpedo_x = objects[torpedo_base + X_COORD] # Get torpedo X position | |
| torpedo_y = objects[torpedo_base + Y_COORD] # Get torpedo Y position | |
| torpedo_z = objects[torpedo_base + Z_COORD] # Get torpedo Z position | |
| for i in range(MAX_OBJECTS): # Check all objects | |
| if i == torpedo_index: # Skip self | |
| continue # Move to next object | |
| target_base = i * OBJ_PARAMS # Calculate target base offset | |
| target_type = objects[target_base + OBJ_TYPE] # Get target object type | |
| if target_type == OBJ_NONE or target_type == OBJ_TORPEDO: # Skip empty slots and torpedoes | |
| continue # Move to next object | |
| if target_type == OBJ_STAR or target_type == OBJ_EXPLOSION: # Skip non-collidable objects | |
| continue # Move to next object | |
| target_x = objects[target_base + X_COORD] # Get target X position | |
| target_y = objects[target_base + Y_COORD] # Get target Y position | |
| target_z = objects[target_base + Z_COORD] # Get target Z position | |
| dx = (torpedo_x - target_x) >> 2 # Calculate X distance | |
| dy = (torpedo_y - target_y) >> 2 # Calculate Y distance | |
| dz = (torpedo_z - target_z) >> 2 # Calculate Z distance | |
| distance_sq = dx*dx + dy*dy + dz*dz # Calculate distance squared | |
| collision_threshold = 1000000 # Collision distance threshold | |
| if target_type == OBJ_FIGHTER or target_type == OBJ_CRUISER: # Small ships | |
| collision_threshold = 80000 # Smaller collision radius | |
| elif target_type >= OBJ_STARBASEL and target_type <= OBJ_STARBASER: # Starbase parts | |
| collision_threshold = 150000 # Larger collision radius | |
| elif target_type == OBJ_BASESTAR: # Large enemy ship | |
| collision_threshold = 200000 # Largest collision radius | |
| elif target_type == OBJ_METEOR: # Space rocks | |
| collision_threshold = 120000 # Medium collision radius | |
| if 0 < distance_sq <= collision_threshold: # Collision detected | |
| objects[torpedo_base + OBJ_TYPE] = OBJ_NONE # Remove torpedo | |
| objects[target_base + OBJ_TYPE] = OBJ_NONE | |
| create_explosion(target_x,target_y,target_z) | |
| player[PLAYER_KILLED] += 1 | |
| if target_type == OBJ_FIGHTER or target_type == OBJ_CRUISER or target_type == OBJ_BASESTAR: | |
| game[SECTOR_ENEMY_COUNT] -= 1 # Decrement enemy count | |
| if game[SECTOR_ENEMY_COUNT] < 0: | |
| game[SECTOR_ENEMY_COUNT] = 0 | |
| if target_type == OBJ_STARBASEC: | |
| game[SECTOR_STARBASE_PARTS] -= 1 # Decrement base count | |
| if game[SECTOR_STARBASE_PARTS] < 0: | |
| game[SECTOR_STARBASE_PARTS] = 0 | |
| update_galactic_chart() # Update chart in real-time | |
| return target_type # Return hit object type | |
| return 0 # No collision found | |
| @micropython.viper | |
| def read_joystick(): | |
| player = ptr32(PLAYER) | |
| joy = ptr32(JOY.movement) | |
| game = ptr32(GAME) | |
| JOY.read_movement() | |
| x = joy[0] | |
| y = joy[1] | |
| if -32 < x < 32: # Reduced deadband for finer control | |
| x = 0 | |
| if -32 < y < 32: # Reduced deadband for finer control | |
| y = 0 | |
| amt = 5 # | |
| if player[PLAYER_HUD] & HUD_CHART: | |
| game[CHART_X_TEMP] = (game[CHART_X_TEMP] + int(x > 0) - int(x < 0)) % 16 | |
| game[CHART_Y_TEMP] = (game[CHART_Y_TEMP] - int(y > 0) + int(y < 0)) % 8 | |
| elif 0 < game[HYPER_STAGE] < 200: | |
| pass | |
| else: | |
| player[PLAYER_YAW] = -x >> amt | |
| player[PLAYER_PITCH] = (y >> amt) | |
| if game[HYPER_STAGE] > 0: | |
| game[HYPER_X] += int(randint(-60000,60000)) - (x * 100) | |
| game[HYPER_Y] += int(randint(-60000,60000)) + (y * 100) | |
| def read_touch(): | |
| n, points = touch.read_points() | |
| x_touch = points[0][0] | |
| y_touch = 320 - points[0][1] - 20 | |
| if PLAYER[PLAYER_HUD] & HUD_CHART: return # return if showing chart | |
| if n > 0 and x_touch > 375 and y_touch > 50 and not(PLAYER[PLAYER_HUD] & HUD_TOUCH): # show touch screen | |
| PLAYER[PLAYER_HUD] ^= HUD_TOUCH | |
| return | |
| if n > 0 and (PLAYER[PLAYER_HUD] & HUD_TOUCH): | |
| SOUND1.effect(COMMAND_SOUND,int(len(COMMAND_SOUND))) | |
| #print(x_touch,y_touch) | |
| if y_touch < 50: # speed | |
| speed = x_touch // 46 | |
| if speed < 0: speed = 0 | |
| if speed > 9: speed = 9 | |
| PLAYER[PLAYER_HUD] ^= HUD_TOUCH | |
| PLAYER[PLAYER_SPEED] = speed | |
| set_volume(speed) | |
| SOUND2.effect(ENGINE_SOUND2,int(len(ENGINE_SOUND2))) | |
| elif 100 < y_touch < 140 and x_touch < 330: # attack computer | |
| PLAYER[PLAYER_HUD] ^= HUD_ATTACK | |
| PLAYER[PLAYER_HUD] ^= HUD_TOUCH | |
| elif 150 < y_touch < 190 and 330 < x_touch < 380: # shields | |
| PLAYER[PLAYER_HUD] ^= HUD_SHIELD | |
| PLAYER[PLAYER_HUD] ^= HUD_TOUCH | |
| elif 190 < y_touch < 230 and x_touch < 330: # long range scan | |
| PLAYER[PLAYER_HUD] ^= HUD_SCAN | |
| PLAYER[PLAYER_HUD] ^= HUD_TOUCH | |
| elif y_touch > 210 and 330 < x_touch < 380: # galactic chart | |
| PLAYER[PLAYER_HUD] ^= HUD_TOUCH | |
| PLAYER[PLAYER_HUD] |= HUD_CHART | |
| @micropython.asm_thumb | |
| def calculate_true_distance(r0, r1, r2) -> int: | |
| vmov(s0, r0) # Move x to floating point register s0 | |
| vcvt_f32_s32(s0, s0) # Convert x to float | |
| vmul(s0, s0, s0) # Calculate x*x | |
| vmov(s1, r1) # Move y to floating point register s1 | |
| vcvt_f32_s32(s1, s1) # Convert y to float | |
| vmul(s1, s1, s1) # Calculate y*y | |
| vmov(s2, r2) # Move z to floating point register s2 | |
| vcvt_f32_s32(s2, s2) # Convert z to float | |
| vmul(s2, s2, s2) # Calculate z*z | |
| vadd(s0, s0, s1) # Add x*x + y*y | |
| vadd(s0, s0, s2) # Add (x*x + y*y) + z*z | |
| vsqrt(s0, s0) # Calculate square root of sum | |
| vcvt_s32_f32(s0, s0) # Convert result back to integer | |
| vmov(r0, s0) # Move result to r0 for return | |
| @micropython.viper | |
| def update_all_objects(): # Unified update function | |
| game = ptr32(GAME) | |
| objects = ptr32(OBJECTS) # Get objects array pointer | |
| isin = ptr32(ISIN) # Get sine lookup table | |
| icos = ptr32(ICOS) # Get cosine lookup table | |
| player = ptr32(PLAYER) # Get player data pointer | |
| player_speed = player[PLAYER_SPEED] # Current forward speed | |
| player_pitch = player[PLAYER_PITCH] # Current pitch rotation | |
| player_yaw = player[PLAYER_YAW] # Current yaw rotation | |
| forward_velocity = player_speed << 5 # Calculate forward movement | |
| min_distance = 1<<29 | |
| close_obj = 0 | |
| for i in range(MAX_OBJECTS): # Process all objects | |
| base = i * OBJ_PARAMS # Calculate base offset | |
| if objects[base + OBJ_TYPE] != OBJ_NONE: # Skip empty slots | |
| rel_x = objects[base + X_COORD] # Get current X position | |
| rel_y = objects[base + Y_COORD] # Get current Y position | |
| rel_z = objects[base + Z_COORD] # Get current Z position | |
| obj_type = objects[base + OBJ_TYPE] # Get object type | |
| if obj_type == OBJ_METEOR: # Check meteor collision with player | |
| distance = int(calculate_true_distance(rel_x >> 2, rel_y >> 2, rel_z >> 2)) | |
| if distance < 1000: # Collision threshold | |
| player[PLAYER_HIT] = 5 # Flash damage indicator | |
| objects[base + OBJ_TYPE] = OBJ_NONE # Remove meteor | |
| create_explosion(rel_x,rel_y,rel_z) # Create explosion effect | |
| continue | |
| if obj_type == OBJ_ENEMY_TORPEDO: # Check enemy torpedo collision with player | |
| distance = int(calculate_true_distance(rel_x >> 2, rel_y >> 2, rel_z >> 2)) | |
| if distance < 1000: # Collision threshold (same as meteor) | |
| player[PLAYER_HIT] = 5 # Flash damage indicator | |
| player[PLAYER_ENERGY] -= 1000 # Reduce player energy | |
| objects[base + OBJ_TYPE] = OBJ_NONE # Remove torpedo | |
| game[ENEMY_TORPEDOS] -= 1 # Decrement enemy torpedo count | |
| create_explosion(rel_x, rel_y, rel_z) # Create explosion effect | |
| continue # Skip to next object | |
| if obj_type == OBJ_STARBASEC: # Check meteor collision with player | |
| distance = int(calculate_true_distance(rel_x >> 2, rel_y >> 2, rel_z >> 2)) | |
| if distance < 1000: # Collision threshold | |
| player[PLAYER_ENERGY] = 99999 # refuel | |
| if 1 < obj_type < 10 and obj_type != 7: # Track targetable objects | |
| distance = int(calculate_true_distance(rel_x >> 2, rel_y >> 2, rel_z >> 2)) | |
| if distance < min_distance: # Find closest object | |
| min_distance = distance | |
| close_obj = i | |
| player[PLAYER_THETA] = rel_x // 100 # Store angle to target | |
| player[PLAYER_PHI] = rel_y // 100 # Store angle to target | |
| # Apply rotation only if joystick input detected | |
| if player_yaw != 0: # Horizontal rotation (yaw) | |
| cos_yaw = int(icos[(-player_yaw) % 360]) # Get cosine value | |
| sin_yaw = int(isin[(-player_yaw) % 360]) # Get sine value | |
| rel_x_scaled = rel_x >> 8 # Pre-scale to prevent overflow | |
| rel_z_scaled = rel_z >> 8 # Pre-scale to prevent overflow | |
| new_x = ((rel_x_scaled * cos_yaw - rel_z_scaled * sin_yaw) >> SCALE) << 8 # Rotate and scale back | |
| new_z = ((rel_x_scaled * sin_yaw + rel_z_scaled * cos_yaw) >> SCALE) << 8 # Rotate and scale back | |
| rel_x = new_x # Update X position | |
| rel_z = new_z # Update Z position | |
| if player_pitch != 0: # Vertical rotation (pitch) | |
| cos_pitch = icos[(-player_pitch) % 360] # Get cosine value | |
| sin_pitch = isin[(-player_pitch) % 360] # Get sine value | |
| rel_y_scaled = rel_y >> 8 # Pre-scale to prevent overflow | |
| rel_z_scaled = rel_z >> 8 # Pre-scale to prevent overflow | |
| new_y = ((rel_y_scaled * cos_pitch - rel_z_scaled * sin_pitch) >> SCALE) << 8 # Rotate and scale back | |
| new_z = ((rel_y_scaled * sin_pitch + rel_z_scaled * cos_pitch) >> SCALE) << 8 # Rotate and scale back | |
| rel_y = new_y # Update Y position | |
| rel_z = new_z # Update Z position | |
| # Apply forward movement (player moving through space) | |
| rel_z -= forward_velocity # Move object away from player | |
| # Apply object's own velocity (intrinsic movement) | |
| rel_x += objects[base + VX_COORD] # Add object X velocity | |
| rel_y += objects[base + VY_COORD] # Add object Y velocity | |
| rel_z += objects[base + VZ_COORD] # Add object Z velocity | |
| # Update final positions | |
| objects[base + X_COORD] = rel_x # Store new X position | |
| objects[base + Y_COORD] = rel_y # Store new Y position | |
| objects[base + Z_COORD] = rel_z # Store new Z position | |
| # Handle objects that moved behind player | |
| if rel_z <= 0: # Object passed behind player | |
| if obj_type == OBJ_STAR or obj_type == OBJ_METEOR: # Stars get repositioned | |
| objects[base + X_COORD] = int(randint(-20000, 20000)) # Random X position | |
| objects[base + Y_COORD] = int(randint(-20000, 20000)) # Random Y position | |
| objects[base + Z_COORD] = int(randint(10000, 50000)) # Random Z distance | |
| objects[base + OBJ_LIFE] = 1 # Reset star life | |
| elif (obj_type == OBJ_FIGHTER or obj_type == OBJ_CRUISER or obj_type == OBJ_BASESTAR) and int(randint(0,100)) > 90: # enemy (front) fire torpedo | |
| create_enemy_torpedo(i) | |
| # Check torpedo collisions TODO | |
| if obj_type == OBJ_TORPEDO or obj_type == OBJ_ENEMY_TORPEDO: # Process torpedo | |
| objects[base + OBJ_LIFE] -= 1 # Decrement torpedo life | |
| if objects[base + OBJ_LIFE] <= 0: # Torpedo expired | |
| objects[base + OBJ_TYPE] = OBJ_NONE # Remove torpedo | |
| if obj_type == OBJ_ENEMY_TORPEDO: | |
| game[ENEMY_TORPEDOS] -= 1 | |
| hit_type = int(check_collision(i)) # Check for collisions | |
| if hit_type != 0: # Hit detected | |
| pass # Score handled in check_collision | |
| if obj_type == OBJ_EXPLOSION and objects[base + OBJ_LIFE] > 0: # Process explosion | |
| if objects[base + OBJ_LIFE] == 1 or objects[base + Z_COORD] < 0: # Explosion ended | |
| objects[base + OBJ_LIFE] = 0 # Clear life | |
| objects[base + OBJ_TYPE] = OBJ_NONE # Remove explosion | |
| else: | |
| objects[base + OBJ_LIFE] -= 1 # Decrement explosion life | |
| if obj_type == OBJ_STAR and objects[base + OBJ_LIFE] > 0: # Process star streaking | |
| objects[base + OBJ_LIFE] += 1 # Increment star trail | |
| player[PLAYER_CLOSE] = close_obj # Store closest object index | |
| if min_distance == 1<<29 : # No objects in range | |
| player[PLAYER_RANGE] = 0 # Clear range indicator | |
| player[PLAYER_THETA] = 0 # Clear theta angle | |
| player[PLAYER_PHI] = 0 # Clear phi angle | |
| else: | |
| player[PLAYER_RANGE] = min_distance // 10 # Store range to closest object | |
| @micropython.viper | |
| def update_energy(): | |
| player = ptr32(PLAYER) | |
| ticks = int(time.ticks_ms()) | |
| dt = ticks - player[PLAYER_LAST_ENERGY_UPDATE] | |
| speed = player[PLAYER_SPEED] | |
| if speed < 0 or speed > 9: | |
| speed = 0 | |
| engine_drain = int(ENGINE_ENERGY_DRAIN[speed]) | |
| total_drain = engine_drain + 5 | |
| if player[PLAYER_HUD] & HUD_ATTACK: | |
| total_drain += 5 | |
| if player[PLAYER_HUD] & HUD_SHIELD: | |
| total_drain += 20 | |
| energy_loss = (total_drain * dt) // 1000 | |
| player[PLAYER_ENERGY] -= energy_loss | |
| player[PLAYER_LAST_ENERGY_UPDATE] = ticks | |
| if player[PLAYER_ENERGY] < 0: | |
| player[PLAYER_ENERGY] = 0 | |
| def update_galactic_chart(): # Update chart when enemies destroyed | |
| global GALACTIC_CHART, PLAYER, GAME | |
| chart_x = PLAYER[PLAYER_CHART_X] # Get current chart position | |
| chart_y = PLAYER[PLAYER_CHART_Y] | |
| sector_idx = chart_y * CHART_WIDTH + chart_x | |
| current_sector = GALACTIC_CHART[sector_idx] | |
| enemy_count = GAME[SECTOR_ENEMY_COUNT] | |
| starbase_parts = GAME[SECTOR_STARBASE_PARTS] | |
| if current_sector == SECTOR_STARBASE: # Starbase sector | |
| if starbase_parts == 0: # All starbase parts destroyed | |
| if enemy_count <= 0: # No enemies left | |
| GALACTIC_CHART[sector_idx] = SECTOR_EMPTY | |
| elif current_sector == SECTOR_4ZYLON: # 4-enemy sector | |
| if enemy_count == 3: | |
| GALACTIC_CHART[sector_idx] = SECTOR_3ZYLON | |
| elif enemy_count == 2: | |
| GALACTIC_CHART[sector_idx] = SECTOR_2ZYLON | |
| elif enemy_count <= 1: | |
| GALACTIC_CHART[sector_idx] = SECTOR_EMPTY | |
| elif current_sector == SECTOR_3ZYLON: # 3-enemy sector | |
| if enemy_count == 2: | |
| GALACTIC_CHART[sector_idx] = SECTOR_2ZYLON | |
| elif enemy_count <= 1: | |
| GALACTIC_CHART[sector_idx] = SECTOR_EMPTY | |
| elif current_sector == SECTOR_2ZYLON: # 2-enemy sector | |
| if enemy_count <= 1: | |
| GALACTIC_CHART[sector_idx] = SECTOR_EMPTY | |
| @micropython.viper | |
| def update_hyper(): | |
| game = ptr32(GAME) | |
| player = ptr32(PLAYER) | |
| if game[HYPER_STAGE] == 1: | |
| game[HYPER_X] = 0 | |
| game[HYPER_Y] = 0 | |
| init_hyperstars() | |
| player[PLAYER_SPEED] = 9 | |
| game[HYPER_STAGE] += 1 | |
| if 1 < game[HYPER_STAGE] < 300: | |
| speed = 1 + game[HYPER_STAGE] // 10 # 13 | |
| set_volume(speed) | |
| if game[HYPER_STAGE] > 300: | |
| SOUND1.effect(COMMAND_SOUND,int(len(COMMAND_SOUND))) | |
| player[PLAYER_SPEED] = 2 | |
| set_volume(2) | |
| game[HYPER_STAGE] = 0 | |
| offset_x = (game[HYPER_X] >> SCALE) // 20 | |
| offset_y = (game[HYPER_Y] >> SCALE) // 20 | |
| #print(offset_x,offset_y) | |
| #player[PLAYER_CHART_X] = (player[PLAYER_CHART_X] + offset_x) % 16 | |
| #player[PLAYER_CHART_Y] = (player[PLAYER_CHART_Y] + offset_y) % 8 | |
| init_sector() | |
| @micropython.viper | |
| def draw_hyper_target(): | |
| game = ptr32(GAME) # Get game state pointer | |
| screen = ptr16(LCD.fbdraw) # Get screen buffer pointer | |
| shape_tab = ptr8(PLSHAP1TAB) # Get shape bitmap data | |
| offset_tab = ptr8(PLSHAPOFFTAB) # Get shape offset lookup | |
| height_tab = ptr8(PLSHAPHEIGHTTAB) # Get shape height lookup | |
| target_x_scaled = game[HYPER_X] # Get target X coordinate (SCALE value) | |
| target_y_scaled = game[HYPER_Y] # Get target Y coordinate (SCALE value) | |
| screen_x = (target_x_scaled >> SCALE) + MAXSCREEN_X // 2 - 6 # Convert to screen X | |
| screen_y = (target_y_scaled >> SCALE) + MAXSCREEN_Y // 2 - 5 # Convert to screen Y | |
| if screen_x < 0 or screen_x >= MAXSCREEN_X or screen_y < 0 or screen_y >= MAXSCREEN_Y: # Check bounds | |
| return # Exit if off screen | |
| offset = 0xc1 | |
| height = 0x0c | |
| color = 0xff # Set target color | |
| for row in range(height): # Loop through shape rows | |
| byte_idx = offset + row # Calculate byte index | |
| pixel_data = int(shape_tab[byte_idx]) # Get pixel row data | |
| for bit in range(8): # Loop through bits in row | |
| if pixel_data & (1 << (7 - bit)): # Check if pixel is set | |
| x = screen_x + bit * 2 # Calculate pixel X position | |
| y = screen_y + row # Calculate pixel Y position | |
| if 0 <= x < MAXSCREEN_X and 0 <= y < MAXSCREEN_Y: # Check pixel bounds | |
| addr = y * MAXSCREEN_X + x # Calculate screen address | |
| screen[addr] = color # Draw left pixel | |
| screen[addr + 1] = color # Draw right pixel (2x width) | |
| @micropython.viper | |
| def draw_long_range(): | |
| objects = ptr32(OBJECTS) | |
| screen = ptr16(LCD.fbdraw) | |
| sprite = ptr8(PLSHAP2TAB) | |
| player = ptr32(PLAYER) | |
| offset = 0xb1 | |
| height = 5 | |
| screen_x = MAXSCREEN_X//2 - 6 | |
| screen_y = MAXSCREEN_Y//2 - 5 | |
| color = WHITE | |
| for row in range(height): # Loop through shape rows | |
| byte_idx = offset + row # Calculate byte index | |
| pixel_data = int(sprite[byte_idx]) # Get pixel row data | |
| for bit in range(8): # Loop through bits in row | |
| if pixel_data & (1 << (7 - bit)): # Check if pixel is set | |
| x = screen_x + bit * 2 # Calculate pixel X position | |
| y = screen_y + row * 2 # Calculate pixel Y position | |
| if 0 <= x < MAXSCREEN_X and 0 <= y < MAXSCREEN_Y: # Check pixel bounds | |
| addr = y * MAXSCREEN_X + x # Calculate screen address | |
| screen[addr] = color # Draw left pixel | |
| screen[addr + 1] = color # Draw right pixel (2x width) | |
| screen[addr+MAXSCREEN_X] = color # Draw left pixel | |
| screen[addr+MAXSCREEN_X + 1] = color # Draw right pixel (2x width) | |
| @micropython.viper | |
| def draw_long_objects(): # Draw objects in long-range scanner view | |
| objects = ptr32(OBJECTS) # Get objects array pointer | |
| screen = ptr16(LCD.fbdraw) # Get screen buffer pointer | |
| player = ptr32(PLAYER) # Get player data pointer | |
| center_x = MAXSCREEN_X // 2 # Scanner center X position | |
| center_y = MAXSCREEN_Y // 2 # Scanner center Y position | |
| for i in range(MAX_OBJECTS): # Loop through all objects | |
| base = i * OBJ_PARAMS # Calculate base offset | |
| obj_type = objects[base + OBJ_TYPE] # Get object type | |
| if obj_type != OBJ_NONE and obj_type != OBJ_STAR and obj_type != OBJ_EXPLOSION and obj_type != OBJ_METEOR : # Skip empty, stars, explosions | |
| x = objects[base + X_COORD] # Get object X coordinate | |
| y = objects[base + Y_COORD] # Get object Y coordinate | |
| z = objects[base + Z_COORD] # Get object Z coordinate | |
| scanner_x = center_x + (x >> 12) # Scale X coordinate (divide by 4096) | |
| scanner_y = center_y - (z >> 12) # Scale Z coordinate (flip for top-down view) | |
| if 0 <= scanner_x < MAXSCREEN_X and 0 <= scanner_y < MAXSCREEN_Y: # Check if pixel is within screen bounds | |
| addr = scanner_y * MAXSCREEN_X + scanner_x # Calculate screen address | |
| screen[addr] = WHITE # Draw white pixel for object | |
| @micropython.viper | |
| def draw_attack(): | |
| objects = ptr32(OBJECTS) # Get objects array pointer | |
| screen = ptr16(LCD.fbdraw) # Get screen buffer pointer | |
| sprite = ptr8(PLSHAP2TAB) # Get sprite data pointer | |
| player = ptr32(PLAYER) # Get player data pointer | |
| if player[PLAYER_RANGE] == 0: return # No objects in range | |
| close_obj = player[PLAYER_CLOSE] # Get closest object index | |
| if close_obj <= 0: return # Invalid object index | |
| base = close_obj * OBJ_PARAMS # Calculate object base offset | |
| obj_z = objects[base + Z_COORD] # Get object Z coordinate | |
| if obj_z <= 0: return # Object is behind player, don't draw | |
| offset = 13 # Sprite offset in data | |
| height = 8 # Sprite height | |
| screen_x = 200 + player[PLAYER_THETA] // 15 # Calculate screen X from angle | |
| screen_y = 120 + player[PLAYER_PHI] // 15 # Calculate screen Y from angle | |
| if screen_x > 220: screen_x = 220 | |
| if screen_y > 135: screen_y = 135 | |
| if screen_x < 180: screen_x = 180 | |
| if screen_y < 110: screen_y = 110 | |
| color = WHITE # Set drawing color | |
| for row in range(height): # Loop through sprite rows | |
| byte_idx = offset + row # Calculate byte index | |
| pixel_data = int(sprite[byte_idx]) # Get pixel row data | |
| for bit in range(8): # Loop through bits in row | |
| if pixel_data & (1 << (7 - bit)): # Check if pixel should be drawn | |
| x = screen_x + bit * 1 # Calculate pixel X position | |
| y = screen_y + row # Calculate pixel Y position | |
| if 0 <= x < MAXSCREEN_X and 0 <= y < MAXSCREEN_Y: # Check bounds | |
| addr = y * MAXSCREEN_X + x # Calculate screen address | |
| screen[addr] = color # Draw pixel | |
| @micropython.viper | |
| def draw_all_objects(): | |
| objects = ptr32(OBJECTS) # Get objects array pointer | |
| screen = ptr16(LCD.fbdraw) # Get screen buffer pointer | |
| game = ptr32(GAME) | |
| for index in range(MAX_OBJECTS): # Process all objects | |
| i = index * OBJ_PARAMS # Calculate base offset | |
| obj_type = objects[OBJ_TYPE + i] # Get object type | |
| if obj_type != OBJ_NONE: # Skip empty slots | |
| x = objects[X_COORD + i] # Get X coordinate | |
| y = objects[Y_COORD + i] # Get Y coordinate | |
| z = objects[Z_COORD + i] # Get Z coordinate | |
| if z > 0: # Only draw objects in front | |
| focal_length = 200 # Perspective focal length | |
| scale = (focal_length << COORD_SCALE) // z # Calculate perspective scale | |
| screen_x = (x * scale >> COORD_SCALE) + MAXSCREEN_X // 2 # Convert to screen X | |
| screen_y = (y * scale >> COORD_SCALE) + MAXSCREEN_Y // 2 # Convert to screen Y | |
| # Check if object is visible on screen | |
| if 0 <= screen_x < MAXSCREEN_X and 0 <= screen_y < MAXSCREEN_Y: | |
| distance = (int(calculate_true_distance(x>>3, y>>3, z>>3))) >> 7 # Calculate distance | |
| if obj_type == OBJ_STAR or obj_type == OBJ_EXPLOSION: # Render stars | |
| if distance < 40: # Only bright enough stars | |
| brightness = 40 - distance # Calculate brightness | |
| if brightness > 0: # Valid brightness | |
| if brightness > 31: brightness = 31 # Clamp maximum | |
| color1 = (brightness << 11) | (brightness << 6) | brightness # Create color | |
| color = ((color1 << 8) & 0xff00) | ((color1 >> 8) & 0xff) # Byte swap | |
| screen[screen_y * MAXSCREEN_X + screen_x] = color # Draw pixel | |
| if game[HYPER_STAGE] > 0: | |
| LCD.fbdraw.line(MAXSCREEN_X//2, MAXSCREEN_Y//2,screen_x,screen_y,color) | |
| else: # Render other objects | |
| distance >>= 2 # Scale down distance | |
| size = 7 if distance > 6 else distance # Calculate size | |
| if size >= 0 and game[HYPER_STAGE] == 0: # Valid size | |
| if obj_type == OBJ_STARBASEC: | |
| draw_object_shape(OBJ_STARBASEL, screen_x, screen_y+4, size) # Draw object | |
| draw_object_shape(OBJ_STARBASEC, screen_x+16, screen_y, size) # Draw object | |
| draw_object_shape(OBJ_STARBASER, screen_x+32, screen_y+4, size) # Draw object | |
| else: | |
| draw_object_shape(obj_type, screen_x, screen_y, size) # Draw object | |
| # Shape type 0 (PHOTON TORPEDO) into PLSHAP1TAB | |
| # Shape type 1 (ZYLON FIGHTER) into PLSHAP2TAB | |
| # Shape type 2 (STARBASE RIGHT) into PLSHAP2TAB | |
| # Shape type 3 (STARBASE CENTER) into PLSHAP1TAB | |
| # Shape type 4 (STARBASE LEFT) into PLSHAP2TAB | |
| # Shape type 5 (TRANSFER VESSEL) into PLSHAP1TAB | |
| # Shape type 6 (METEOR) into PLSHAP1TAB | |
| # Shape type 7 (ZYLON CRUISER) into PLSHAP2TAB | |
| # Shape type 8 (ZYLON BASESTAR) into PLSHAP2TAB | |
| # Shape type 9 (HYPERWARP TARGET MARKER) into PLSHAP1TAB | |
| @micropython.viper | |
| def draw_object_shape(obj_type: int, screen_x: int, screen_y: int, size: int): | |
| colors = ptr16(OBJ_COLORS) | |
| screen = ptr16(LCD.fbdraw) | |
| offset_tab = ptr8(PLSHAPOFFTAB) | |
| height_tab = ptr8(PLSHAPHEIGHTTAB) | |
| if obj_type == 13: | |
| obj_type = 1 | |
| if (obj_type < 1 or obj_type > 10 or size < 0 or size > 7): | |
| return | |
| type_idx = (obj_type - 1) * 8 | |
| offset = int(offset_tab[type_idx + size]) | |
| height = int(height_tab[type_idx + size]) + 1 | |
| if height == 0: | |
| return | |
| use_shap2 = 0 | |
| if obj_type == 2 or obj_type == 5: | |
| use_shap2 = 1 | |
| elif obj_type == 3 or obj_type == 8 or obj_type == 9: | |
| use_shap2 = 1 | |
| if use_shap2: | |
| shape_tab = ptr8(PLSHAP2TAB) | |
| else: | |
| shape_tab = ptr8(PLSHAP1TAB) | |
| color = colors[obj_type - 1] | |
| for row in range(height): | |
| byte_idx = offset + row | |
| pixel_data = int(shape_tab[byte_idx]) | |
| if obj_type == 1: | |
| pixel_data &= int(randint(0,0xff)) | |
| for bit in range(8): | |
| if pixel_data & (1 << (7 - bit)): | |
| x = screen_x + bit * 2 | |
| y = screen_y + row | |
| if 0 <= x < MAXSCREEN_X and 0 <= y < MAXSCREEN_Y: | |
| addr = y * MAXSCREEN_X + x | |
| screen[addr] = color | |
| screen[addr+1] = color | |
| @micropython.viper | |
| def draw_touch(): | |
| player = ptr32(PLAYER) | |
| speed = player[PLAYER_SPEED] | |
| #LCD.fill2(LCD.fbdraw,0b0011_00000_00000_000) | |
| LCD.fbdraw.text('ENGINE CONTROL',70,0,0xff) # ggg_bbbbb_rrrrr_ggg | |
| LCD.fbdraw.poly(speed * 24 + 5, 9,POLY_SMALL_BUTTON,0b000_11111_00000_000,1) | |
| for i in range(0,10): | |
| draw_viper(i, i * 24 + 6, 13, 0xff,2) | |
| #LCD.fbdraw.rect(i * 24, 10, 24, 20,LT_BLUE) | |
| LCD.fbdraw.poly(i * 24 + 5, 9,POLY_SMALL_BUTTON,LT_BLUE) | |
| if player[PLAYER_HUD] & HUD_ATTACK: | |
| LCD.fbdraw.poly(140, 0 * 20 + 54,POLY_SMALL_BUTTON,0b000_11111_00000_000,1) # attack | |
| if player[PLAYER_HUD] & HUD_SHIELD: | |
| LCD.fbdraw.poly(170, 1 * 20 + 54,POLY_SMALL_BUTTON,0b000_11111_00000_000,1) # shields | |
| if player[PLAYER_HUD] & HUD_SCAN: | |
| LCD.fbdraw.poly(140, 2 * 20 + 54,POLY_SMALL_BUTTON,0b000_11111_00000_000,1) # long range scan | |
| for i in range(2): | |
| LCD.fbdraw.poly(i*30+140, i * 20 + 52,POLY_SMALL_BUTTON,LT_BLUE) | |
| LCD.fbdraw.poly(i*30+140, i * 20 + 92,POLY_SMALL_BUTTON,LT_BLUE) | |
| LCD.fbdraw.text('ATTACK COMPUTER',10,60,0xff) | |
| LCD.fbdraw.text(' SHIELDS',40,80,0xff) | |
| LCD.fbdraw.text('LONG RANGE SCAN',10,100,0xff) | |
| LCD.fbdraw.text(' GALACTIC CHART',10,120,0xff) | |
| @micropython.viper | |
| def draw_chart_enhanced(): | |
| player = ptr32(PLAYER) # Get player data pointer | |
| chart = ptr32(GALACTIC_CHART) # Get chart data pointer | |
| char_map = ptr8(CHAR_MAP) # Get character bitmap data | |
| screen = ptr16(LCD.fbdraw) # Get screen buffer pointer | |
| game = ptr32(GAME) | |
| chart_x = game[CHART_X_TEMP] # Current cursor X position | |
| chart_y = game[CHART_Y_TEMP] # Current cursor Y position | |
| LCD.fbdraw.text('GALACTIC CHART', 60, 0, LT_BLUE) # Draw title | |
| # Draw grid lines and sector content | |
| for y in range(CHART_HEIGHT + 1): # Draw horizontal grid lines | |
| line_y = CHART_Y + CHART_SIZE * y # Calculate line Y position | |
| LCD.fbdraw.line(CHART_X, line_y, CHART_X + CHART_SIZE * CHART_WIDTH, line_y, LT_BLUE) | |
| for x in range(CHART_WIDTH + 1): # Draw vertical grid lines | |
| line_x = CHART_X + CHART_SIZE * x # Calculate line X position | |
| LCD.fbdraw.line(line_x, CHART_Y, line_x, CHART_Y + CHART_SIZE * CHART_HEIGHT, LT_BLUE) | |
| # Draw sector content using custom characters | |
| for sector_y in range(CHART_HEIGHT): # Loop through chart rows | |
| for sector_x in range(CHART_WIDTH): # Loop through chart columns | |
| sector_idx = sector_y * CHART_WIDTH + sector_x # Calculate sector index | |
| sector_content = chart[sector_idx] # Get sector content | |
| if sector_content != SECTOR_EMPTY: # Non-empty sector | |
| pixel_x = CHART_X + sector_x * CHART_SIZE - 1 # Center X in cell | |
| pixel_y = CHART_Y + sector_y * CHART_SIZE + 0 # Center Y in cell | |
| # Draw custom character bitmap for sector content | |
| for char_y in range(1,7): # Character height | |
| char_row = int(char_map[sector_content * 8 + char_y]) # Get character row | |
| for char_x in range(1,7): # Character width | |
| if char_row & (1 << (7 - char_x)): # Pixel is set | |
| draw_x = pixel_x + char_x * 2 # Calculate pixel X | |
| draw_y = pixel_y + (char_y * 2) # Calculate pixel Y | |
| if 0 <= draw_x < MAXSCREEN_X and 0 <= draw_y < MAXSCREEN_Y: | |
| addr = draw_y * MAXSCREEN_X + draw_x # Calculate screen address | |
| screen[addr] = YELLOW # Draw pixel in yellow | |
| screen[addr+1] = YELLOW # Draw pixel in yellow | |
| screen[addr+MAXSCREEN_X] = YELLOW # Draw pixel in yellow | |
| screen[addr+MAXSCREEN_X+1] = YELLOW # Draw pixel in yellow | |
| # Draw cursor highlight | |
| cursor_x = CHART_X + chart_x * CHART_SIZE # Cursor X position | |
| cursor_y = CHART_Y + chart_y * CHART_SIZE # Cursor Y position | |
| LCD.fbdraw.rect(cursor_x, cursor_y, CHART_SIZE, CHART_SIZE, WHITE) # Draw cursor rectangle | |
| current_x = CHART_X + player[PLAYER_CHART_X] * CHART_SIZE # Cursor X position | |
| current_y = CHART_Y + player[PLAYER_CHART_Y] * CHART_SIZE # Cursor Y position | |
| LCD.fbdraw.rect(current_x, current_y, CHART_SIZE, CHART_SIZE, GREEN) # Draw current rectangle | |
| def test_draw_objects(): | |
| for j in range(7): | |
| for i in range(1,11): | |
| draw_object_shape(i, i * 20,j * 20,j) | |
| @micropython.viper | |
| def draw(): | |
| game = ptr32(GAME) | |
| player = ptr32(PLAYER) | |
| status = ptr8(LCD.aux) | |
| joy = ptr32(JOY.movement) | |
| if player[PLAYER_HIT] > 0: # Check if hit flash active | |
| player[PLAYER_HIT] -= 1 # Decrement timer | |
| hit_val = player[PLAYER_HIT] # Get current timer value | |
| if hit_val < 5: # Original hit flash (values 1-5) | |
| LCD.fill2(LCD.fbdraw, RED) # Show red screen | |
| elif hit_val == 9: | |
| hit_val = 0 | |
| else: # Warp flash sequence (values 10+) | |
| cycle = (60 - hit_val) // 15 # Which flash cycle (0-5) | |
| phase = (60 - hit_val) % 20 # Position in cycle (0-9) | |
| if cycle < 6 and phase < 5: # Show flash for first 5 ticks of each cycle | |
| LCD.fill2(LCD.fbdraw, RED) # Show red screen | |
| else: # Off period | |
| LCD.fill2(LCD.fbdraw, 0x0) # Show black screen | |
| else: | |
| LCD.fill2(LCD.fbdraw, 0x0) # Normal black background | |
| if not player[PLAYER_HUD] & HUD_SCAN: | |
| draw_all_objects() | |
| if game[SHOW_FPS] > 0: | |
| game[SHOW_FPS] -= 1 | |
| draw_num.draw(FPS_CORE0,220,0) | |
| draw_num.draw(FPS_CORE1,220,10) | |
| if player[PLAYER_HUD] & HUD_ATTACK and not(player[PLAYER_HUD] & HUD_TOUCH or player[PLAYER_HUD] & HUD_CHART or player[PLAYER_HUD] & HUD_SCAN): # crosshairs | |
| LCD.fbdraw.line(CROSS_VX, CROSS_VY1, CROSS_VX, CROSS_VY2, LT_BLUE) | |
| LCD.fbdraw.line(CROSS_VX, CROSS_VY3, CROSS_VX, CROSS_VY4, LT_BLUE) | |
| LCD.fbdraw.line(CROSS_HX1, CROSS_HY, CROSS_HX2, CROSS_HY, LT_BLUE) | |
| LCD.fbdraw.line(CROSS_HX3, CROSS_HY, CROSS_HX4, CROSS_HY, LT_BLUE) | |
| if player[PLAYER_HUD] & HUD_ATTACK and not(player[PLAYER_HUD] & HUD_TOUCH or player[PLAYER_HUD] & HUD_CHART or player[PLAYER_HUD] & HUD_SCAN): # attack computer | |
| LCD.fbdraw.rect(ACOMP_R1X, ACOMP_R1Y, ACOMP_R1W, ACOMP_R1H, LT_BLUE) | |
| LCD.fbdraw.rect(ACOMP_R2X, ACOMP_R2Y, ACOMP_R2W, ACOMP_R2H, LT_BLUE) | |
| LCD.fbdraw.line(ACOMP_VX, ACOMP_VY1, ACOMP_VX, ACOMP_VY2, LT_BLUE) | |
| LCD.fbdraw.line(ACOMP_VX, ACOMP_VY3, ACOMP_VX, ACOMP_VY4, LT_BLUE) | |
| LCD.fbdraw.line(ACOMP_HX1, ACOMP_HY, ACOMP_HX2, ACOMP_HY, LT_BLUE) | |
| LCD.fbdraw.line(ACOMP_HX3, ACOMP_HY, ACOMP_HX4, ACOMP_HY, LT_BLUE) | |
| draw_attack() | |
| if player[PLAYER_HUD] & HUD_TOUCH: | |
| draw_touch() | |
| if player[PLAYER_HUD] & HUD_CHART: | |
| draw_chart_enhanced() | |
| if game[HYPER_STAGE] > 0: | |
| draw_hyper_target() | |
| if player[PLAYER_HUD] & HUD_SCAN and not(player[PLAYER_HUD] & HUD_TOUCH or player[PLAYER_HUD] & HUD_CHART) : | |
| draw_long_range() | |
| draw_long_objects() | |
| LCD.fbdraw.text('V: K: E: T:',0,HUD_TEXT1_Y,0xff) | |
| draw_viper(player[PLAYER_SPEED],20,HUD_TEXT1_Y,0xffff,1) | |
| draw_viper(player[PLAYER_KILLED],80,HUD_TEXT1_Y,0xffff,1) | |
| draw_viper(player[PLAYER_ENERGY] // ENERGY_SCALE ,150,HUD_TEXT1_Y,0xffff,1) | |
| draw_viper(player[PLAYER_CLOSE],190,HUD_TEXT1_Y,0xffff,1) | |
| draw_char(CHAR_PHI,0,HUD_TEXT2_Y,0xff) | |
| draw_char(CHAR_THETA,55,HUD_TEXT2_Y,0xff) | |
| LCD.fbdraw.text(' : : R:',0,HUD_TEXT2_Y,0xff) | |
| if -300 < player[PLAYER_THETA] < 300 and player[PLAYER_CLOSE] > 0: | |
| draw_viper(player[PLAYER_THETA],40,HUD_TEXT2_Y,0xffff,1) | |
| else: | |
| draw_char(CHAR_INFINITY,20,HUD_TEXT2_Y,0xffff) | |
| if -300 < player[PLAYER_PHI] < 300 and player[PLAYER_CLOSE] > 0: | |
| draw_viper(player[PLAYER_PHI],100,HUD_TEXT2_Y,0xffff,1) | |
| else: | |
| draw_char(CHAR_INFINITY,80,HUD_TEXT2_Y,0xffff) | |
| if -9999 < player[PLAYER_RANGE] < 9999 and player[PLAYER_CLOSE] > 0: | |
| draw_viper(player[PLAYER_RANGE],150,HUD_TEXT2_Y,0xffff,1) | |
| else: | |
| draw_char(CHAR_INFINITY,130,HUD_TEXT2_Y,0xffff) | |
| status[SHOWING] = 0 | |
| @micropython.viper | |
| def main(): # Fixed main loop | |
| init_sounds() | |
| init_game() # Initialize game state | |
| init_stars3d() # Create background stars | |
| init_galactic_chart() | |
| init_sector() | |
| status = ptr8(LCD.aux) # Get LCD status pointer | |
| player = ptr32(PLAYER) # Get player data pointer | |
| game = ptr32(GAME) | |
| gc.collect() # Clean up memory | |
| print(gc.mem_free()) # Show available memory | |
| set_volume(0) | |
| SOUND2.effect(ENGINE_SOUND2,int(len(ENGINE_SOUND2))) | |
| # Timing variables | |
| ticks_joystick = 0 # Last joystick update time | |
| ticks_button = 0 # Last button press time | |
| ticks_energy = 0 # Last energy update time | |
| ticks_touch = 0 # Last touch update time | |
| while not status[EXIT]: # Main game loop | |
| while not status[SHOWING]: sleep_ms(1) # Wait for display ready | |
| draw_num.fb = LCD.fbdraw # Set number drawing buffer | |
| ticks = int(time.ticks_ms()) # Get current time | |
| joystick_delay = 10 | |
| if player[PLAYER_HUD] & HUD_CHART: | |
| joystick_delay = 200 | |
| if JOY.is_second_pressed(): | |
| player[PLAYER_HUD] ^= HUD_CHART | |
| if player[PLAYER_CHART_X] != game[CHART_X_TEMP] or player[PLAYER_CHART_Y] != game[CHART_Y_TEMP]: | |
| player[PLAYER_CHART_X] = game[CHART_X_TEMP] | |
| player[PLAYER_CHART_Y] = game[CHART_Y_TEMP] | |
| game[HYPER_STAGE] = 1 | |
| if ticks - ticks_joystick > joystick_delay: # joystick update | |
| ticks_joystick = ticks # Update timer | |
| read_joystick() # Read joystick position | |
| # Handle fire button | |
| if ticks - ticks_button > 300 and JOY.is_fire_pressed(): # Fire rate limit | |
| ticks_button = ticks # Update timer | |
| create_photon() # Create photon torpedo | |
| # Update energy consumption | |
| if ticks - ticks_energy > 100: # 10Hz energy update | |
| ticks_energy = ticks # Update timer | |
| update_energy() # Update energy levels | |
| # Read touch input | |
| if ticks - ticks_touch > 200: # 5Hz touch update | |
| ticks_touch = ticks # Update timer | |
| read_touch() # Read touch screen | |
| # Check for low energy warning | |
| if player[PLAYER_ENERGY] < 1000: # Energy critical | |
| pass # Add warning logic here | |
| if game[HYPER_STAGE] > 0: | |
| update_hyper() | |
| draw_num.update_all() # Update display numbers | |
| update_all_objects() # Update all object positions | |
| draw() # Render frame | |
| draw_num.set(FPS_CORE0, ticks) # Update FPS counter | |
| def shutdown(): | |
| LCD.aux[EXIT] = 1 | |
| sleep_ms(200) | |
| LCD.off() | |
| sleep_ms(200) | |
| freq(150_000_000,48_000_000) | |
| print('core0 done') | |
| @micropython.viper | |
| def core1(): | |
| status = ptr8(LCD.aux) | |
| sleep_ms(300) | |
| while not status[EXIT]: | |
| ticks=int(time.ticks_ms()) | |
| status[SHOWING] = 1 | |
| LCD.show_all() | |
| LCD.flip() | |
| draw_num.set(FPS_CORE1, ticks) | |
| print('core1 done') | |
| if __name__=='__main__': | |
| JOY = Joystick() | |
| JOY.pot_scale = 8 | |
| touch = GT911(I2C(0, scl=9,sda=8,freq=400_000), reset_pin=7, irq_pin=0, touch_points=3) | |
| n, points = touch.read_points() | |
| gc.collect() | |
| sleep_ms(500) | |
| print(gc.mem_free()) | |
| freq(220_000_000) | |
| machine.mem32[0x40010048] = 1<<11 | |
| LCD = LCD_3inch5(MAXSCREEN_X,MAXSCREEN_Y) | |
| draw_num = Draw_number(LCD.fbdraw,MAXSCREEN_X) | |
| _thread.start_new_thread(core1, ()) | |
| try: | |
| main() | |
| shutdown() | |
| except KeyboardInterrupt : | |
| shutdown() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment