diff --git a/applepy.py b/applepy.py index 2668f39..7b1d211 100644 --- a/applepy.py +++ b/applepy.py @@ -3,7 +3,8 @@ # originally written 2001, updated 2011 -import curses +import pygame +import colorsys def signed(x): if x > 0x7F: @@ -11,6 +12,232 @@ def signed(x): return x +class Display: + + characters = [ + [0b00000, 0b01110, 0b10001, 0b10101, 0b10111, 0b10110, 0b10000, 0b01111], + [0b00000, 0b00100, 0b01010, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001], + [0b00000, 0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110], + [0b00000, 0b01110, 0b10001, 0b10000, 0b10000, 0b10000, 0b10001, 0b01110], + [0b00000, 0b11110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b11110], + [0b00000, 0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111], + [0b00000, 0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000], + [0b00000, 0b01111, 0b10000, 0b10000, 0b10000, 0b10011, 0b10001, 0b01111], + [0b00000, 0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001], + [0b00000, 0b01110, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110], + [0b00000, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b10001, 0b01110], + [0b00000, 0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001], + [0b00000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111], + [0b00000, 0b10001, 0b11011, 0b10101, 0b10101, 0b10001, 0b10001, 0b10001], + [0b00000, 0b10001, 0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001], + [0b00000, 0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110], + [0b00000, 0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000], + [0b00000, 0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101], + [0b00000, 0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001], + [0b00000, 0b01110, 0b10001, 0b10000, 0b01110, 0b00001, 0b10001, 0b01110], + [0b00000, 0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100], + [0b00000, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110], + [0b00000, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100], + [0b00000, 0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b11011, 0b10001], + [0b00000, 0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001], + [0b00000, 0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100], + [0b00000, 0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111], + [0b00000, 0b11111, 0b11000, 0b11000, 0b11000, 0b11000, 0b11000, 0b11111], + [0b00000, 0b00000, 0b10000, 0b01000, 0b00100, 0b00010, 0b00001, 0b00000], + [0b00000, 0b11111, 0b00011, 0b00011, 0b00011, 0b00011, 0b00011, 0b11111], + [0b00000, 0b00000, 0b00000, 0b00100, 0b01010, 0b10001, 0b00000, 0b00000], + [0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111], + [0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000], + [0b00000, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000, 0b00100], + [0b00000, 0b01010, 0b01010, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000], + [0b00000, 0b01010, 0b01010, 0b11111, 0b01010, 0b11111, 0b01010, 0b01010], + [0b00000, 0b00100, 0b01111, 0b10100, 0b01110, 0b00101, 0b11110, 0b00100], + [0b00000, 0b11000, 0b11001, 0b00010, 0b00100, 0b01000, 0b10011, 0b00011], + [0b00000, 0b01000, 0b10100, 0b10100, 0b01000, 0b10101, 0b10010, 0b01101], + [0b00000, 0b00100, 0b00100, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000], + [0b00000, 0b00100, 0b01000, 0b10000, 0b10000, 0b10000, 0b01000, 0b00100], + [0b00000, 0b00100, 0b00010, 0b00001, 0b00001, 0b00001, 0b00010, 0b00100], + [0b00000, 0b00100, 0b10101, 0b01110, 0b00100, 0b01110, 0b10101, 0b00100], + [0b00000, 0b00000, 0b00100, 0b00100, 0b11111, 0b00100, 0b00100, 0b00000], + [0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00100, 0b00100, 0b01000], + [0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000], + [0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00100], + [0b00000, 0b00000, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b00000], + [0b00000, 0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110], + [0b00000, 0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110], + [0b00000, 0b01110, 0b10001, 0b00001, 0b00110, 0b01000, 0b10000, 0b11111], + [0b00000, 0b11111, 0b00001, 0b00010, 0b00110, 0b00001, 0b10001, 0b01110], + [0b00000, 0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010], + [0b00000, 0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110], + [0b00000, 0b00111, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110], + [0b00000, 0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000], + [0b00000, 0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110], + [0b00000, 0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b11100], + [0b00000, 0b00000, 0b00000, 0b00100, 0b00000, 0b00100, 0b00000, 0b00000], + [0b00000, 0b00000, 0b00000, 0b00100, 0b00000, 0b00100, 0b00100, 0b01000], + [0b00000, 0b00010, 0b00100, 0b01000, 0b10000, 0b01000, 0b00100, 0b00010], + [0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b11111, 0b00000, 0b00000], + [0b00000, 0b01000, 0b00100, 0b00010, 0b00001, 0b00010, 0b00100, 0b01000], + [0b00000, 0b01110, 0b10001, 0b00010, 0b00100, 0b00100, 0b00000, 0b00100] + ] + + lores_colours = [ + (0, 0, 0), # black + (208, 0, 48), # magenta / dark red + (0, 0, 128), # dark blue + (255, 0, 255), # purple / violet + (0, 128, 0), # dark green + (128, 128, 128), # gray 1 + (0, 0, 255), # medium blue / blue + (96, 160, 255), # light blue + (128, 80, 0), # brown / dark orange + (255, 128 ,0), # orange + (192, 192, 192), # gray 2 + (255, 144, 128), # pink / light red + (0, 255, 0), # light green / green + (255, 255, 0), # yellow / light orange + (64, 255, 144), # aquamarine / light green + (255, 255, 255), # white + ] + + def __init__(self): + self.screen = pygame.display.set_mode((560, 384)) + pygame.display.set_caption("ApplePy") + self.mix = False + + self.chargen = [] + for c in self.characters: + chars = [[pygame.Surface((14, 16)), pygame.Surface((14, 16))], + [pygame.Surface((14, 16)), pygame.Surface((14, 16))]] + for colour in (0, 1): + hue = (255, 255, 255) if colour else (0, 200, 0) + for inv in (0, 1): + pixels = pygame.PixelArray(chars[colour][inv]) + off = hue if inv else (0, 0, 0) + on = (0, 0, 0) if inv else hue + for row in range(8): + b = c[row] << 1 + for col in range(7): + bit = (b >> (6 - col)) & 1 + pixels[2 * col][2 * row] = on if bit else off + pixels[2 * col + 1][2 * row] = on if bit else off + del pixels + self.chargen.append(chars) + + def txtclr(self): + self.text = False + + def txtset(self): + self.text = True + self.colour = False + + def mixclr(self): + self.mix = False + + def mixset(self): + self.mix = True + self.colour = True + + def lowscr(self): + self.page = 1 + + def hiscr(self): + self.page = 2 + + def lores(self): + self.high_res = False + + def hires(self): + self.high_res = True + + def update(self, address, value): + if self.page == 1: + start_text = 0x400 + start_hires = 0x2000 + elif self.page == 2: + start_text = 0x800 + start_hires = 0x4000 + else: + return + + if start_text <= address <= start_text + 0x3FF: + base = address - start_text + hi, lo = divmod(base, 0x80) + row_group, column = divmod(lo, 0x28) + row = hi + 8 * row_group + + if row_group == 3: + return + + if self.text or not self.mix or not row < 20: + mode, ch = divmod(value, 0x40) + + inv = mode in (0, 1) + + self.screen.blit(self.chargen[ch][self.colour][inv], (2 * (column * 7), 2 * (row * 8))) + else: + pixels = pygame.PixelArray(self.screen) + if not self.high_res: + lower, upper = divmod(value, 0x10) + + for dx in range(14): + for dy in range(8): + x = column * 14 + dx + y = row * 16 + dy + pixels[x][y] = self.lores_colours[upper] + for dy in range(8, 16): + x = column * 14 + dx + y = row * 16 + dy + pixels[x][y] = self.lores_colours[lower] + del pixels + + elif start_hires <= address <= start_hires + 0x1FFF: + if self.high_res: + base = address - start_hires + row8, b = divmod(base, 0x400) + hi, lo = divmod(b, 0x80) + row_group, column = divmod(lo, 0x28) + row = 8 * (hi + 8 * row_group) + row8 + + if self.mix and row >= 160: + return + + if row < 192 and column < 40: + + pixels = pygame.PixelArray(self.screen) + msb = value // 0x80 + + for b in range(7): + c = value & (1 << b) + xx = (column * 7 + b) + x = 2 * xx + y = 2 * row + + if msb: + if xx % 2: + pixels[x][y] = (0, 0, 0) + # orange + pixels[x + 1][y] = (255, 192, 0) if c else (0, 0, 0) + else: + # blue + pixels[x][y] = (0, 128, 224) if c else (0, 0, 0) + pixels[x + 1][y] = (0, 0, 0) + else: + if xx % 2: + pixels[x][y] = (0, 0, 0) + # green + pixels[x + 1][y] = (0, 255, 0) if c else (0, 0, 0) + else: + # violet + pixels[x][y] = (255, 0, 255) if c else (0, 0, 0) + pixels[x + 1][y] = (0, 0, 0) + + pixels[x][y + 1] = (0, 0, 0) + pixels[x + 1][y + 1] = (0, 0, 0) + + del pixels + + class RAM: def __init__(self, start, size): @@ -32,15 +259,36 @@ class RAM: class SoftSwitches: - def __init__(self): + def __init__(self, display): self.kbd = 0x00 + self.display = display def read_byte(self, address): assert 0xC000 <= address <= 0xCFFF if address == 0xC000: return self.kbd - if address == 0xC010: + elif address == 0xC010: self.kbd = self.kbd & 0x7F + elif address == 0xC030: + pass # toggle speaker + elif address == 0xC050: + self.display.txtclr() + elif address == 0xC051: + self.display.txtset() + elif address == 0xC052: + self.display.mixclr() + elif address == 0xC053: + self.display.mixset() + elif address == 0xC054: + self.display.lowscr() + elif address == 0xC055: + self.display.hiscr() + elif address == 0xC056: + self.display.lores() + elif address == 0xC057: + self.display.hires() + else: + pass # print "%04X" % address return 0x00 @@ -67,14 +315,15 @@ class ROM: class Memory: - def __init__(self): + def __init__(self, display=None): + self.display = display self.rom = ROM(0xD000, 0x3000) # available from http://www.easy68k.com/paulrsm/6502/index.html self.rom.load_file(0xD000, "A2ROM.BIN") self.ram = RAM(0x0000, 0xC000) - self.softswitches = SoftSwitches() + self.softswitches = SoftSwitches(display) def load(self, address, data): if address < 0xC000: @@ -100,34 +349,10 @@ class Memory: def write_byte(self, address, value): if address < 0xC000: self.ram.write_byte(address, value) - if 0x400 <= address < 0x800: - self.write_screen(address, value) - - def write_screen(self, address, value): - base = address - 0x400 - hi, lo = divmod(base, 0x80) - row_group, column = divmod(lo, 0x28) - row = hi + 8 * row_group - - # skip if writing to row group 3 - if row_group == 3: - return - - c = chr(0x20 + ((value + 0x20) % 0x40)) - - if value < 0x40: - attr = curses.A_DIM - elif value < 0x80: - attr = curses.A_REVERSE - elif value < 0xA0: - attr = curses.A_UNDERLINE - else: - attr = curses.A_DIM - - try: - self.win.addch(row, column, c, attr) - except curses.error: - pass + if 0x400 <= address < 0x800 and display: + self.display.update(address, value) + if 0x2000 <= address < 0x5FFF and display: + self.display.update(address, value) class Disassemble: @@ -531,41 +756,13 @@ class CPU: def reset(self): self.program_counter = self.memory.read_word(self.RESET_VECTOR) - def dump(self, win, op): - win.move(10, 50) - win.clrtoeol() - win.addstr(10, 50, self.disassemble.disasm(self.program_counter - 1)) - win.addstr(14, 50, "BUFFER:" + - " ".join("%02X" % self.memory.read_byte(m) for m in range(0x200, 0x210)) - ) - win.addstr(11, 50, "A=%02X X=%02X Y=%02X S=%02X V=%02X B=%02X D=%02X I=%02X Z=%02X C=%02X PC=%04X S=%02X" % ( - self.accumulator, - self.x_index, - self.y_index, - self.sign_flag, - self.overflow_flag, - self.break_flag, - self.decimal_mode_flag, - self.interrupt_disable_flag, - self.zero_flag, - self.carry_flag, - self.program_counter - 1, - self.stack_pointer)) - win.addstr(12, 50, "STACK:" + - " ".join("%02X" % self.memory.read_byte(self.STACK_PAGE + i) for i in range(255, self.stack_pointer, -1)) - ) - - def run(self, win): - self.memory.win = win - win.clear() - curses.noecho() - win.nodelay(True) - while True: + def run(self): + update_cycle = 0 + quit = False + while not quit: op = self.read_pc_byte() - # self.dump(win, op) func = self.ops[op] if func is None: - curses.endwin() print "UNKNOWN OP" print hex(self.program_counter - 1) print hex(op) @@ -573,18 +770,21 @@ class CPU: else: self.ops[op]() - try: - key = ord(win.getkey()) - if key == 0xA: - key = 0xD - elif key == 0x7F: - key = 0x8 - # win.addstr(15, 50, hex(key)) - self.memory.softswitches.kbd = 0x80 + key - except curses.error: - pass - except TypeError: - pass + for event in pygame.event.get(): + if event.type == pygame.QUIT: + quit = True + + if event.type == pygame.KEYDOWN: + if event.unicode: + key = ord(event.unicode) + if key == 0x7F: + key = 0x08 + self.memory.softswitches.kbd = 0x80 + key + + update_cycle += 1 + if update_cycle >= 1024: + pygame.display.flip() + update_cycle = 0 #### @@ -967,7 +1167,8 @@ class CPU: if __name__ == "__main__": - mem = Memory() + display = Display() + mem = Memory(display) cpu = CPU(mem) - curses.wrapper(cpu.run) + cpu.run()