diff --git a/README.MD b/README.MD index fdac6e8..eb9be9e 100644 --- a/README.MD +++ b/README.MD @@ -18,16 +18,16 @@ fun to have an emulator 2000 lines of boring, by the book code. Would it? :-) Additional Requirements ------------------------------ - + 1. Pygame 2. Numpy - + Instructions ------------- Run with python orao.py, enter BASIC using "BC". When asked about the memory size, press ENTER unless you don't want BASIC to use the entire available -memory. +memory. WAV files containing programs / games should be located in the wav folder. @@ -37,7 +37,7 @@ will be created. Load it back using LOAD "filename". DMEM "filename", ADDR, LENGTH can also be used to save a fragment of memory, beginning at address ADDR and of length LENGTH (in decimal). LMEM "filename" loads it back. - + Clicking the POWER button does a warm reboot. You can then go back to BASIC with BW to keep the memory intact, or re-initialize with BC. The enclosed PDF describes the monitor mode and commands, but it is in Croatian only (Google @@ -62,7 +62,5 @@ Then press F8 to load and start program. Known bugs ---------- -- Pygame and Pulseaudio crash occasionally. - it seems that this - fork is less prone to lock-ups and **pygame*** crashes. - Please report any issues you have. +- Pygame and Pulseaudio crash occasionally. Please report any issues you have. - no error checking for input `.prg` file diff --git a/orao.py b/orao.py index eb09c45..b9a7aa2 100755 --- a/orao.py +++ b/orao.py @@ -1,5 +1,5 @@ -#!/usr/bin/python2 -# -*- coding: utf8 -*- +#!/usr/bin/env python +# -*- coding: utf-8 -*- import pygame, numpy, sys, datetime from orao.cpu import CPU @@ -27,7 +27,7 @@ pygame.init() pygame.time.set_timer(pygame.USEREVENT + 1, 40) # create CPU -cpu = CPU(bytearray([0xFF]*0xC000) + bytearray(open('ORAO13.ROM', 'rb').read())) +cpu = CPU(bytearray([0xFF] * 0xC000) + bytearray(open("ORAO13.ROM", "rb").read())) cpu.channel = pygame.mixer.Channel(0) cpu.store_mem_listeners.append(video_mem_listener) cpu.store_mem_listeners.append(timer_mem_listener) @@ -38,39 +38,46 @@ chargen_init(cpu.memory[0xE000:]) view_cpu_state = CPUState() # ram zero page & stack -view_zp = MicroMemView(start_addr=0x0000, size=0x0200, caption='ZP & stack', disp_width=64) +view_zp = MicroMemView( + start_addr=0x0000, size=0x0200, caption="ZP & stack", disp_width=64 +) view_zp.listen(cpu) # user ram view -view_ram = MicroMemView(start_addr=0x0200, size=0x5E00, caption='RAM', disp_width=128) +view_ram = MicroMemView(start_addr=0x0200, size=0x5E00, caption="RAM", disp_width=128) view_ram.listen(cpu) # rom access view -view_rom = MicroMemView(start_addr=0xC000, size=0x4000, caption='ROM', disp_width=256) +view_rom = MicroMemView(start_addr=0xC000, size=0x4000, caption="ROM", disp_width=256) view_rom.listen(cpu) view_screen = MicroMemView(start_addr=0x6000, size=0x2000, disp_width=32) view_screen.listen(cpu) # status lines -status_line = pygame.Surface((64 * 8, 4*8), depth=24) +status_line = pygame.Surface((64 * 8, 4 * 8), depth=24) status_line.fill((0, 0, 0)) -chargen_draw_str(status_line, 0, 0, 'Orao Emulator v0.1') +chargen_draw_str(status_line, 0, 0, "Orao Emulator v0.1") # setup screen -screen = pygame.display.set_mode(( - terminal.get_width() * 2 + 2 + int(max(view_rom.width, view_cpu_state.width * 2)), - terminal.get_height() * 2 + 3*8 + 2 + 30 -)) -pygame.display.set_caption('Orao Emulator v0.1') +screen = pygame.display.set_mode( + ( + terminal.get_width() * 2 + + 2 + + int(max(view_rom.width, view_cpu_state.width * 2)), + terminal.get_height() * 2 + 3 * 8 + 2 + 30, + ) +) +pygame.display.set_caption("Orao Emulator v0.1") -lc = (0xff, 0xcc, 0x00) -chargen_draw_str(status_line, 0, 16, 'F12:', color=lc) -chargen_draw_str(status_line, 24+8, 16, ' SCREENSHOT') +lc = (0xFF, 0xCC, 0x00) +chargen_draw_str(status_line, 0, 16, "F12:", color=lc) +chargen_draw_str(status_line, 24 + 8, 16, " SCREENSHOT") if MEM_LOAD_PRG is not None: - chargen_draw_str(status_line, 0, 24, 'F8:', color=lc) - chargen_draw_str(status_line, 24, 24, ' %s' % MEM_LOAD_PRG) + chargen_draw_str(status_line, 0, 24, "F8:", color=lc) + chargen_draw_str(status_line, 24, 24, " %s" % MEM_LOAD_PRG) + def render_frame(frame_time_ms): view_cpu_state.render(cpu, frame_time_ms) @@ -82,8 +89,8 @@ def render_frame(frame_time_ms): # blit screen.fill((0, 0, 0)) screen.blit(pygame.transform.scale(terminal, (512, 512)), [0, 0]) - chargen_draw_str(status_line, 0, 8, 'Speed: {0:.2f} MHz'.format(ratio)) - screen.blit(status_line, [0, 512+1]) + chargen_draw_str(status_line, 0, 8, "Speed: {0:.2f} MHz".format(ratio)) + screen.blit(status_line, [0, 512 + 1]) x = 512 + 1 y = 0 cx, y = view_cpu_state.blit(screen, [x, y], scale=2) @@ -95,12 +102,13 @@ def render_frame(frame_time_ms): y2 += 5 _, _ = view_rom.blit(screen, [x, y2], scale=1) - screen.blit(pygame.transform.scale(view_screen.read_map.surf, (512,512)), [0,0]) - screen.blit(pygame.transform.scale(view_screen.write_map.surf, (512,512)), [0,0]) + screen.blit(pygame.transform.scale(view_screen.read_map.surf, (512, 512)), [0, 0]) + screen.blit(pygame.transform.scale(view_screen.write_map.surf, (512, 512)), [0, 0]) # finish rendering pygame.display.flip() + clock = pygame.time.Clock() while running: @@ -109,7 +117,7 @@ while running: for i in range(5000): cpu.step() - time_elapsed = (datetime.datetime.now()-before).microseconds + 1 + time_elapsed = (datetime.datetime.now() - before).microseconds + 1 clock.tick() for event in pygame.event.get(): @@ -118,8 +126,8 @@ while running: if event.type == pygame.MOUSEBUTTONDOWN: x, y = event.pos - if 650 < x < 700 and 720 < y < 790: # Reset button - cpu.__init__(cpu.memory[:]) # Warm reset + if 650 < x < 700 and 720 < y < 790: # Reset button + cpu.__init__(cpu.memory[:]) # Warm reset orao_kbd_listener(event, cpu) @@ -131,13 +139,13 @@ while running: if pkeys[pygame.K_F8]: if MEM_LOAD_PRG is None: break - print("LOADING: %s" % MEM_LOAD_PRG) + print(("LOADING: %s" % MEM_LOAD_PRG)) ba = bytearray(open(MEM_LOAD_PRG, "rb").read()) # read load address addr = ba[1] * 256 + ba[0] ba = ba[2:] - print('Loadaddr: %04x' % addr) + print(("Loadaddr: %04x" % addr)) # load file to memory for i in range(0, len(ba)): @@ -150,22 +158,33 @@ while running: if pkeys[pygame.K_F12]: now = datetime.datetime.now() # current date and time - pygame.image.save(screen, "assets/screenshot-%s.png" % now.strftime("%Y%m%d-%H%M%S")) + pygame.image.save( + screen, "assets/screenshot-%s.png" % now.strftime("%Y%m%d-%H%M%S") + ) if event.type == pygame.USEREVENT + 1: render_frame(clock.get_time()) - cpu.tape_out = None if cpu.cycles - cpu.last_sound_cycles > 20000 else cpu.tape_out + cpu.tape_out = ( + None if cpu.cycles - cpu.last_sound_cycles > 20000 else cpu.tape_out + ) - if len(cpu.sndbuf) > 4096 or cpu.sndbuf and cpu.cycles - cpu.last_sound_cycles > 20000: + if ( + len(cpu.sndbuf) > 4096 + or cpu.sndbuf + and cpu.cycles - cpu.last_sound_cycles > 20000 + ): while cpu.channel.get_queue(): - if time_elapsed > 10000: break + if time_elapsed > 10000: + break cpu.channel.queue(pygame.sndarray.make_sound(numpy.uint8(cpu.sndbuf))) cpu.sndbuf = [] overshoot = cpu.cycles - previous_loop_cycles - time_elapsed - pygame.time.wait((overshoot > 0) * overshoot // 1000) # Pričekaj da budemo cycle exact + pygame.time.wait( + (overshoot > 0) * overshoot // 1000 + ) # Pričekaj da budemo cycle exact ratio = 1.0 * (cpu.cycles - previous_loop_cycles) / time_elapsed diff --git a/orao/chargen.py b/orao/chargen.py index c2fe4dc..8601fca 100644 --- a/orao/chargen.py +++ b/orao/chargen.py @@ -1,22 +1,22 @@ +# -*- coding: utf-8 -*- chars = None def chargen_init(char_data): - global chars - chars = char_data - + global chars + chars = char_data def chargen_draw_char(surf, px, py, ch, color=(0, 255, 0), bg=(0, 0, 0)): - char_loc = (ord(ch) - 32) * 8 - for y in range(8): - c = chars[char_loc + y] - for x in range(8): - if (c >> x) & 1: - surf.set_at((px + x, py + y), color) - else: - surf.set_at((px + x, py + y), bg) + char_loc = (ord(ch) - 32) * 8 + for y in range(8): + c = chars[char_loc + y] + for x in range(8): + if (c >> x) & 1: + surf.set_at((px + x, py + y), color) + else: + surf.set_at((px + x, py + y), bg) def chargen_draw_str(surf, px, py, str, color=(0, 255, 0), bg=(0, 0, 0)): - for x in range(len(str)): - chargen_draw_char(surf, px + x * 8, py, str[x], color=color, bg=bg) + for x in range(len(str)): + chargen_draw_char(surf, px + x * 8, py, str[x], color=color, bg=bg) diff --git a/orao/cpu.py b/orao/cpu.py index b0db759..22c3c2e 100755 --- a/orao/cpu.py +++ b/orao/cpu.py @@ -1,61 +1,196 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- import wave class CPU(object): - CARRY, ZERO, INTERRUPT, DECIMAL, BREAK, UNUSED, OVERFLOW, NEGATIVE = [2**i for i in range(8)] + CARRY, ZERO, INTERRUPT, DECIMAL, BREAK, UNUSED, OVERFLOW, NEGATIVE = [ + 2**i for i in range(8) + ] alphaarray = None store_mem_listeners = [] read_mem_listeners = [] def __init__(self, memory): s, self.tape_out, self.filename, self.samples = self, None, None, 0 - s.memory, s.tape, s.flipflop, s.last_sound_cycles, s.sndbuf = memory, None, 0, -20000, [] + s.memory, s.tape, s.flipflop, s.last_sound_cycles, s.sndbuf = ( + memory, + None, + 0, + -20000, + [], + ) s.cycles, s.pc, s.flags, s.sp, s.a, s.x, s.y = 0, 0xFF89, 48, 0xFF, 0, 0, 0 s.executed = [] - s._opcodes = { 0x00:(s.BRK,s.no,7), 0x01:(s.ORA,s.ix,6), 0x05:(s.ORA,s.zp,3), - 0x06:(s.ASL,s.zp,5), 0x08:(s.PHP,s.no,3), 0x09:(s.ORA,s.im,2), 0x0a:(s.ASL,s.no,2), - 0x0d:(s.ORA,s.ab,4), 0x0e:(s.ASL,s.ab,6), 0x10:(s.BPL,s.re,2), 0x11:(s.ORA,s.iy,6), - 0x15:(s.ORA,s.zx,4), 0x16:(s.ASL,s.zx,6), 0x18:(s.CLC,s.no,2), 0x19:(s.ORA,s.ay,5), - 0x1d:(s.ORA,s.ax,5), 0x1e:(s.ASL,s.ax,7), 0x20:(s.JSR,s.jm,6), 0x21:(s.AND,s.ix,6), - 0x24:(s.BIT,s.zp,3), 0x25:(s.AND,s.zp,3), 0x26:(s.ROL,s.zp,5), 0x28:(s.PLP,s.no,4), - 0x29:(s.AND,s.im,2), 0x2a:(s.ROL,s.no,2), 0x2c:(s.BIT,s.ab,4), 0x2d:(s.AND,s.ab,4), - 0x2e:(s.ROL,s.ab,6), 0x30:(s.BMI,s.re,2), 0x31:(s.AND,s.iy,6), 0x35:(s.AND,s.zx,4), - 0x36:(s.ROL,s.zx,6), 0x38:(s.SEC,s.no,2), 0x39:(s.AND,s.ay,5), 0x3d:(s.AND,s.ax,5), - 0x3e:(s.ROL,s.ax,7), 0x40:(s.RTI,s.no,6), 0x41:(s.EOR,s.ix,6), 0x45:(s.EOR,s.zp,3), - 0x46:(s.LSR,s.zp,5), 0x48:(s.PHA,s.no,3), 0x49:(s.EOR,s.im,2), 0x4a:(s.LSR,s.no,2), - 0x4c:(s.JMP,s.jm,3), 0x4d:(s.EOR,s.ab,4), 0x4e:(s.LSR,s.ab,6), 0x50:(s.BVC,s.re,2), - 0x51:(s.EOR,s.iy,6), 0x55:(s.EOR,s.zx,4), 0x56:(s.LSR,s.zx,6), 0x58:(s.CLI,s.no,2), - 0x59:(s.EOR,s.ay,5), 0x5d:(s.EOR,s.ax,5), 0x5e:(s.LSR,s.ax,7), 0x60:(s.RTS,s.no,6), - 0x61:(s.ADC,s.ix,6), 0x65:(s.ADC,s.zp,3), 0x66:(s.ROR,s.zp,5), 0x68:(s.PLA,s.no,4), - 0x69:(s.ADC,s.im,2), 0x6a:(s.ROR,s.no,2), 0x6c:(s.JMP,s.id,5), 0x6d:(s.ADC,s.ab,4), - 0x6e:(s.ROR,s.ab,6), 0x70:(s.BVS,s.re,2), 0x71:(s.ADC,s.iy,6), 0x75:(s.ADC,s.zx,4), - 0x76:(s.ROR,s.zx,6), 0x78:(s.SEI,s.no,2), 0x79:(s.ADC,s.ay,5), 0x7d:(s.ADC,s.ax,5), - 0x7e:(s.ROR,s.ax,7), 0x81:(s.STA,s.ix,6), 0x84:(s.STY,s.zp,3), 0x85:(s.STA,s.zp,3), - 0x86:(s.STX,s.zp,3), 0x88:(s.DEY,s.no,2), 0x8a:(s.TXA,s.no,2), 0x8c:(s.STY,s.ab,4), - 0x8d:(s.STA,s.ab,4), 0x8e:(s.STX,s.ab,4), 0x90:(s.BCC,s.re,2), 0x91:(s.STA,s.iy,6), - 0x94:(s.STY,s.zx,4), 0x95:(s.STA,s.zx,4), 0x96:(s.STX,s.zy,4), 0x98:(s.TYA,s.no,2), - 0x99:(s.STA,s.ay,5), 0x9a:(s.TXS,s.no,2), 0x9d:(s.STA,s.ax,5), 0xa0:(s.LDY,s.im,2), - 0xa1:(s.LDA,s.ix,6), 0xa2:(s.LDX,s.im,2), 0xa4:(s.LDY,s.zp,3), 0xa5:(s.LDA,s.zp,3), - 0xa6:(s.LDX,s.zp,3), 0xa8:(s.TAY,s.no,2), 0xa9:(s.LDA,s.im,2), 0xaa:(s.TAX,s.no,2), - 0xac:(s.LDY,s.ab,4), 0xad:(s.LDA,s.ab,4), 0xae:(s.LDX,s.ab,4), 0xb0:(s.BCS,s.re,2), - 0xb1:(s.LDA,s.iy,6), 0xb4:(s.LDY,s.zx,4), 0xb5:(s.LDA,s.zx,4), 0xb6:(s.LDX,s.zy,4), - 0xb8:(s.CLV,s.no,2), 0xb9:(s.LDA,s.ay,4), 0xba:(s.TSX,s.no,2), 0xbc:(s.LDY,s.ax,4), - 0xbd:(s.LDA,s.ax,4), 0xbe:(s.LDX,s.ay,4), 0xc0:(s.CPY,s.im,2), 0xc1:(s.CMP,s.ix,6), - 0xc4:(s.CPY,s.zp,3), 0xc5:(s.CMP,s.zp,3), 0xc6:(s.DEC,s.zp,5), 0xc8:(s.INY,s.no,2), - 0xc9:(s.CMP,s.im,2), 0xca:(s.DEX,s.no,2), 0xcc:(s.CPY,s.ab,4), 0xcd:(s.CMP,s.ab,4), - 0xce:(s.DEC,s.ab,6), 0xd0:(s.BNE,s.re,2), 0xd1:(s.CMP,s.iy,6), 0xd5:(s.CMP,s.zx,4), - 0xd6:(s.DEC,s.zx,6), 0xd8:(s.CLD,s.no,2), 0xd9:(s.CMP,s.ay,5), 0xdd:(s.CMP,s.ax,5), - 0xde:(s.DEC,s.ax,7), 0xe0:(s.CPX,s.im,2), 0xe1:(s.SBC,s.ix,6), 0xe4:(s.CPX,s.zp,3), - 0xe5:(s.SBC,s.zp,3), 0xe6:(s.INC,s.zp,5), 0xe8:(s.INX,s.no,2), 0xe9:(s.SBC,s.im,2), - 0xea:(s.NOP,s.no,2), 0xec:(s.CPX,s.ab,4), 0xed:(s.SBC,s.ab,4), 0xee:(s.INC,s.ab,6), - 0xf0:(s.BEQ,s.re,2), 0xf1:(s.SBC,s.iy,6), 0xf5:(s.SBC,s.zx,4), 0xf6:(s.INC,s.zx,6), - 0xf8:(s.SED,s.no,2), 0xf9:(s.SBC,s.ay,5), 0xfd:(s.SBC,s.ax,5), 0xfe:(s.INC,s.ax,7)} + s._opcodes = { + 0x00: (s.BRK, s.no, 7), + 0x01: (s.ORA, s.ix, 6), + 0x05: (s.ORA, s.zp, 3), + 0x06: (s.ASL, s.zp, 5), + 0x08: (s.PHP, s.no, 3), + 0x09: (s.ORA, s.im, 2), + 0x0A: (s.ASL, s.no, 2), + 0x0D: (s.ORA, s.ab, 4), + 0x0E: (s.ASL, s.ab, 6), + 0x10: (s.BPL, s.re, 2), + 0x11: (s.ORA, s.iy, 6), + 0x15: (s.ORA, s.zx, 4), + 0x16: (s.ASL, s.zx, 6), + 0x18: (s.CLC, s.no, 2), + 0x19: (s.ORA, s.ay, 5), + 0x1D: (s.ORA, s.ax, 5), + 0x1E: (s.ASL, s.ax, 7), + 0x20: (s.JSR, s.jm, 6), + 0x21: (s.AND, s.ix, 6), + 0x24: (s.BIT, s.zp, 3), + 0x25: (s.AND, s.zp, 3), + 0x26: (s.ROL, s.zp, 5), + 0x28: (s.PLP, s.no, 4), + 0x29: (s.AND, s.im, 2), + 0x2A: (s.ROL, s.no, 2), + 0x2C: (s.BIT, s.ab, 4), + 0x2D: (s.AND, s.ab, 4), + 0x2E: (s.ROL, s.ab, 6), + 0x30: (s.BMI, s.re, 2), + 0x31: (s.AND, s.iy, 6), + 0x35: (s.AND, s.zx, 4), + 0x36: (s.ROL, s.zx, 6), + 0x38: (s.SEC, s.no, 2), + 0x39: (s.AND, s.ay, 5), + 0x3D: (s.AND, s.ax, 5), + 0x3E: (s.ROL, s.ax, 7), + 0x40: (s.RTI, s.no, 6), + 0x41: (s.EOR, s.ix, 6), + 0x45: (s.EOR, s.zp, 3), + 0x46: (s.LSR, s.zp, 5), + 0x48: (s.PHA, s.no, 3), + 0x49: (s.EOR, s.im, 2), + 0x4A: (s.LSR, s.no, 2), + 0x4C: (s.JMP, s.jm, 3), + 0x4D: (s.EOR, s.ab, 4), + 0x4E: (s.LSR, s.ab, 6), + 0x50: (s.BVC, s.re, 2), + 0x51: (s.EOR, s.iy, 6), + 0x55: (s.EOR, s.zx, 4), + 0x56: (s.LSR, s.zx, 6), + 0x58: (s.CLI, s.no, 2), + 0x59: (s.EOR, s.ay, 5), + 0x5D: (s.EOR, s.ax, 5), + 0x5E: (s.LSR, s.ax, 7), + 0x60: (s.RTS, s.no, 6), + 0x61: (s.ADC, s.ix, 6), + 0x65: (s.ADC, s.zp, 3), + 0x66: (s.ROR, s.zp, 5), + 0x68: (s.PLA, s.no, 4), + 0x69: (s.ADC, s.im, 2), + 0x6A: (s.ROR, s.no, 2), + 0x6C: (s.JMP, s.id, 5), + 0x6D: (s.ADC, s.ab, 4), + 0x6E: (s.ROR, s.ab, 6), + 0x70: (s.BVS, s.re, 2), + 0x71: (s.ADC, s.iy, 6), + 0x75: (s.ADC, s.zx, 4), + 0x76: (s.ROR, s.zx, 6), + 0x78: (s.SEI, s.no, 2), + 0x79: (s.ADC, s.ay, 5), + 0x7D: (s.ADC, s.ax, 5), + 0x7E: (s.ROR, s.ax, 7), + 0x81: (s.STA, s.ix, 6), + 0x84: (s.STY, s.zp, 3), + 0x85: (s.STA, s.zp, 3), + 0x86: (s.STX, s.zp, 3), + 0x88: (s.DEY, s.no, 2), + 0x8A: (s.TXA, s.no, 2), + 0x8C: (s.STY, s.ab, 4), + 0x8D: (s.STA, s.ab, 4), + 0x8E: (s.STX, s.ab, 4), + 0x90: (s.BCC, s.re, 2), + 0x91: (s.STA, s.iy, 6), + 0x94: (s.STY, s.zx, 4), + 0x95: (s.STA, s.zx, 4), + 0x96: (s.STX, s.zy, 4), + 0x98: (s.TYA, s.no, 2), + 0x99: (s.STA, s.ay, 5), + 0x9A: (s.TXS, s.no, 2), + 0x9D: (s.STA, s.ax, 5), + 0xA0: (s.LDY, s.im, 2), + 0xA1: (s.LDA, s.ix, 6), + 0xA2: (s.LDX, s.im, 2), + 0xA4: (s.LDY, s.zp, 3), + 0xA5: (s.LDA, s.zp, 3), + 0xA6: (s.LDX, s.zp, 3), + 0xA8: (s.TAY, s.no, 2), + 0xA9: (s.LDA, s.im, 2), + 0xAA: (s.TAX, s.no, 2), + 0xAC: (s.LDY, s.ab, 4), + 0xAD: (s.LDA, s.ab, 4), + 0xAE: (s.LDX, s.ab, 4), + 0xB0: (s.BCS, s.re, 2), + 0xB1: (s.LDA, s.iy, 6), + 0xB4: (s.LDY, s.zx, 4), + 0xB5: (s.LDA, s.zx, 4), + 0xB6: (s.LDX, s.zy, 4), + 0xB8: (s.CLV, s.no, 2), + 0xB9: (s.LDA, s.ay, 4), + 0xBA: (s.TSX, s.no, 2), + 0xBC: (s.LDY, s.ax, 4), + 0xBD: (s.LDA, s.ax, 4), + 0xBE: (s.LDX, s.ay, 4), + 0xC0: (s.CPY, s.im, 2), + 0xC1: (s.CMP, s.ix, 6), + 0xC4: (s.CPY, s.zp, 3), + 0xC5: (s.CMP, s.zp, 3), + 0xC6: (s.DEC, s.zp, 5), + 0xC8: (s.INY, s.no, 2), + 0xC9: (s.CMP, s.im, 2), + 0xCA: (s.DEX, s.no, 2), + 0xCC: (s.CPY, s.ab, 4), + 0xCD: (s.CMP, s.ab, 4), + 0xCE: (s.DEC, s.ab, 6), + 0xD0: (s.BNE, s.re, 2), + 0xD1: (s.CMP, s.iy, 6), + 0xD5: (s.CMP, s.zx, 4), + 0xD6: (s.DEC, s.zx, 6), + 0xD8: (s.CLD, s.no, 2), + 0xD9: (s.CMP, s.ay, 5), + 0xDD: (s.CMP, s.ax, 5), + 0xDE: (s.DEC, s.ax, 7), + 0xE0: (s.CPX, s.im, 2), + 0xE1: (s.SBC, s.ix, 6), + 0xE4: (s.CPX, s.zp, 3), + 0xE5: (s.SBC, s.zp, 3), + 0xE6: (s.INC, s.zp, 5), + 0xE8: (s.INX, s.no, 2), + 0xE9: (s.SBC, s.im, 2), + 0xEA: (s.NOP, s.no, 2), + 0xEC: (s.CPX, s.ab, 4), + 0xED: (s.SBC, s.ab, 4), + 0xEE: (s.INC, s.ab, 6), + 0xF0: (s.BEQ, s.re, 2), + 0xF1: (s.SBC, s.iy, 6), + 0xF5: (s.SBC, s.zx, 4), + 0xF6: (s.INC, s.zx, 6), + 0xF8: (s.SED, s.no, 2), + 0xF9: (s.SBC, s.ay, 5), + 0xFD: (s.SBC, s.ax, 5), + 0xFE: (s.INC, s.ax, 7), + } - - s.ticks = {s.im: 1, s.zp: 1, s.zx: 1, s.zy: 1, s.ab: 2, s.ax: 2, s.no: 0, - s.ay: 2, s.jm: 2, s.id: 2, s.ix: 1, s.iy: 1, s.re: 1} + s.ticks = { + s.im: 1, + s.zp: 1, + s.zx: 1, + s.zy: 1, + s.ab: 2, + s.ax: 2, + s.no: 0, + s.ay: 2, + s.jm: 2, + s.id: 2, + s.ix: 1, + s.iy: 1, + s.re: 1, + } s.addr_fmt = { s.im: lambda addr: "#$%02X" % s.memory[addr], @@ -73,7 +208,8 @@ class CPU(object): s.re: lambda addr: "$%04X" % (addr + self.byte2signed(s.memory[addr]) + 1), } - def get_flag(self, flag): return self.flags & flag != 0 + def get_flag(self, flag): + return self.flags & flag != 0 def set_flag(self, flag, boolean): self.flags = self.flags | flag if boolean else self.flags & ~(flag) @@ -83,32 +219,38 @@ class CPU(object): self.set_flag(self.NEGATIVE, src & 0x80) return src - def get_word(self, addr): return 256 * self.get_byte(addr + 1) + self.get_byte(addr) + def get_word(self, addr): + return 256 * self.get_byte(addr + 1) + self.get_byte(addr) - def get_filename(self): return 'wav/{0}.WAV'.format(str(self.memory[592:602]).rstrip()) + def get_filename(self): + return "wav/{0}.WAV".format(str(self.memory[592:602]).rstrip()) def speaker(self): self.flipflop ^= 1 self.samples = int((self.cycles - self.last_sound_cycles) / 22.675) - if 1 < self.samples < 1000: # Ogranici frekventno podrucje + if 1 < self.samples < 1000: # Ogranici frekventno podrucje self.sndbuf += [255] * self.samples + [0] * self.samples self.last_sound_cycles = self.cycles def get_byte(self, addr): - if addr == 0x87FF: # Adresa ulaza kasetofona + if addr == 0x87FF: # Adresa ulaza kasetofona if not self.tape: - self.tape = (255*(ord(j)>128) for i in \ - wave.open(self.get_filename()).readframes(2**24) for j in 2*i) + self.tape = ( + 255 * (ord(j) > 128) + for i in wave.open(self.get_filename()).readframes(2**24) + for j in 2 * i + ) try: - return self.tape.next() + return next(self.tape) except StopIteration: self.tape = None return 0x00 - if addr == 0x8800: self.speaker() # Zvucnik + if addr == 0x8800: + self.speaker() # Zvucnik if addr is not None: for listener in self.read_mem_listeners: @@ -117,11 +259,12 @@ class CPU(object): return self.memory[addr] if addr is not None else self.a def store_byte(self, addr, val): - if addr is None: # Akumulator + if addr is None: # Akumulator self.a = val & 0xFF return - if addr == 0x8800: self.speaker() # Zvucnik + if addr == 0x8800: + self.speaker() # Zvucnik self.memory[addr] = val & 0xFF @@ -136,23 +279,49 @@ class CPU(object): self.sp = (self.sp + 1) & 0xFF return self.get_byte(256 + self.sp) - def stack_push_word(self, val): map(self.stack_push, [(val >> 8) & 0xFF, val & 0xFF]) + def stack_push_word(self, val): + list(map(self.stack_push, [(val >> 8) & 0xFF, val & 0xFF])) + + def stack_pop_word(self): + return self.stack_pop() + (self.stack_pop() << 8) - def stack_pop_word(self): return self.stack_pop() + (self.stack_pop() << 8) ########################################################################### # Adresni nacini - def im(self): return self.pc - def zp(self): return self.get_byte(self.pc) - def zx(self): return (self.zp() + self.x) & 0xFF - def zy(self): return (self.zp() + self.y) & 0xFF - def ab(self): return self.get_word(self.pc) - def ax(self): return self._ab(self.x) - def ay(self): return self._ab(self.y) - def ix(self): return self.get_word((self.zp() + self.x) & 0xFF) - def iy(self): return (self.get_word(self.zp()) + self.y) & 0xFFFF - def id(self): return self.get_word(self.ab()) - def jm(self): return self.ab() - def no(self): return None + def im(self): + return self.pc + + def zp(self): + return self.get_byte(self.pc) + + def zx(self): + return (self.zp() + self.x) & 0xFF + + def zy(self): + return (self.zp() + self.y) & 0xFF + + def ab(self): + return self.get_word(self.pc) + + def ax(self): + return self._ab(self.x) + + def ay(self): + return self._ab(self.y) + + def ix(self): + return self.get_word((self.zp() + self.x) & 0xFF) + + def iy(self): + return (self.get_word(self.zp()) + self.y) & 0xFFFF + + def id(self): + return self.get_word(self.ab()) + + def jm(self): + return self.ab() + + def no(self): + return None def _ab(self, diff): a1 = self.ab() @@ -171,39 +340,89 @@ class CPU(object): ########################################################################### # Instrukcije - def TAX(self, d): self.x = self.set_nz(self.a) - def TXA(self, d): self.a = self.set_nz(self.x) - def TAY(self, d): self.y = self.set_nz(self.a) - def TYA(self, d): self.a = self.set_nz(self.y) - def TSX(self, d): self.x = self.set_nz(self.sp) - def TXS(self, d): self.sp = self.x + def TAX(self, d): + self.x = self.set_nz(self.a) - def LDA(self, addr): self.a = self.set_nz(self.get_byte(addr)) - def LDX(self, addr): self.x = self.set_nz(self.get_byte(addr)) - def LDY(self, addr): self.y = self.set_nz(self.get_byte(addr)) - def STA(self, addr): self.store_byte(addr, self.a) - def STX(self, addr): self.store_byte(addr, self.x) - def STY(self, addr): self.store_byte(addr, self.y) + def TXA(self, d): + self.a = self.set_nz(self.x) - def AND(self, addr): self.a = self.set_nz(self.get_byte(addr) & self.a) - def ORA(self, addr): self.a = self.set_nz(self.get_byte(addr) | self.a) - def EOR(self, addr): self.a = self.set_nz(self.get_byte(addr) ^ self.a) + def TAY(self, d): + self.y = self.set_nz(self.a) - def CLC(self, d): self.set_flag(self.CARRY, False) - def SEC(self, d): self.set_flag(self.CARRY, True) - def CLD(self, d): self.set_flag(self.DECIMAL, False) - def SED(self, d): self.set_flag(self.DECIMAL, True) - def CLI(self, d): self.set_flag(self.INTERRUPT, False) - def SEI(self, d): self.set_flag(self.INTERRUPT, True) - def CLV(self, d): self.set_flag(self.OVERFLOW, False) + def TYA(self, d): + self.a = self.set_nz(self.y) - def INX(self, d): self.x = self.set_nz((self.x + 1) & 0xFF) - def INY(self, d): self.y = self.set_nz((self.y + 1) & 0xFF) - def DEX(self, d): self.x = self.set_nz((self.x - 1) & 0xFF) - def DEY(self, d): self.y = self.set_nz((self.y - 1) & 0xFF) + def TSX(self, d): + self.x = self.set_nz(self.sp) - def INC(self, addr): self.store_byte(addr, self.set_nz(self.get_byte(addr) + 1)) - def DEC(self, addr): self.store_byte(addr, self.set_nz(self.get_byte(addr) - 1)) + def TXS(self, d): + self.sp = self.x + + def LDA(self, addr): + self.a = self.set_nz(self.get_byte(addr)) + + def LDX(self, addr): + self.x = self.set_nz(self.get_byte(addr)) + + def LDY(self, addr): + self.y = self.set_nz(self.get_byte(addr)) + + def STA(self, addr): + self.store_byte(addr, self.a) + + def STX(self, addr): + self.store_byte(addr, self.x) + + def STY(self, addr): + self.store_byte(addr, self.y) + + def AND(self, addr): + self.a = self.set_nz(self.get_byte(addr) & self.a) + + def ORA(self, addr): + self.a = self.set_nz(self.get_byte(addr) | self.a) + + def EOR(self, addr): + self.a = self.set_nz(self.get_byte(addr) ^ self.a) + + def CLC(self, d): + self.set_flag(self.CARRY, False) + + def SEC(self, d): + self.set_flag(self.CARRY, True) + + def CLD(self, d): + self.set_flag(self.DECIMAL, False) + + def SED(self, d): + self.set_flag(self.DECIMAL, True) + + def CLI(self, d): + self.set_flag(self.INTERRUPT, False) + + def SEI(self, d): + self.set_flag(self.INTERRUPT, True) + + def CLV(self, d): + self.set_flag(self.OVERFLOW, False) + + def INX(self, d): + self.x = self.set_nz((self.x + 1) & 0xFF) + + def INY(self, d): + self.y = self.set_nz((self.y + 1) & 0xFF) + + def DEX(self, d): + self.x = self.set_nz((self.x - 1) & 0xFF) + + def DEY(self, d): + self.y = self.set_nz((self.y - 1) & 0xFF) + + def INC(self, addr): + self.store_byte(addr, self.set_nz(self.get_byte(addr) + 1)) + + def DEC(self, addr): + self.store_byte(addr, self.set_nz(self.get_byte(addr) - 1)) def ASL(self, addr): operand = self.get_byte(addr) @@ -217,11 +436,20 @@ class CPU(object): self.set_flag(self.NEGATIVE, op & self.NEGATIVE) self.set_flag(self.OVERFLOW, op & self.OVERFLOW) - def PHP(self, d): self.stack_push(self.flags | self.BREAK | self.UNUSED) - def PHA(self, d): self.stack_push(self.a) - def PLP(self, d): self.flags = self.stack_pop() - def PLA(self, d): self.a = self.set_nz(self.stack_pop()) - def NOP(self, d): pass + def PHP(self, d): + self.stack_push(self.flags | self.BREAK | self.UNUSED) + + def PHA(self, d): + self.stack_push(self.a) + + def PLP(self, d): + self.flags = self.stack_pop() + + def PLA(self, d): + self.a = self.set_nz(self.stack_pop()) + + def NOP(self, d): + pass def ROR(self, addr): value = self.get_byte(addr) >> 1 | self.get_flag(self.CARRY) * 128 @@ -240,14 +468,17 @@ class CPU(object): self.SEI(0) self.pc = self.get_word(0xFFFE) # IRQ - def JMP(self, addr): self.pc = addr - 2 + def JMP(self, addr): + self.pc = addr - 2 def JSR(self, addr): - if addr == 0xE7B7 and self.pc > 0xC000: # samo i jedino ako rutinu poziva ROM + if addr == 0xE7B7 and self.pc > 0xC000: # samo i jedino ako rutinu poziva ROM if not self.tape_out: - self.tape_out = wave.open(self.get_filename(), 'w') - self.tape_out.setparams((1, 1, 44100, 0, 'NONE', 'not compressed')) - self.tape_out.writeframes(chr(255 * self.flipflop) * (1 + (self.y > 15)) * 10) + self.tape_out = wave.open(self.get_filename(), "w") + self.tape_out.setparams((1, 1, 44100, 0, "NONE", "not compressed")) + self.tape_out.writeframes( + chr(255 * self.flipflop) * (1 + (self.y > 15)) * 10 + ) self.stack_push_word((self.pc + 1) & 0xFFFF) self.pc = addr - 2 @@ -267,49 +498,80 @@ class CPU(object): self.pc = (self.pc + 1) & 0xFFFF def ADDITION(self, arg): - result = (arg & 0XFF) + self.a + self.get_flag(self.CARRY) + result = (arg & 0xFF) + self.a + self.get_flag(self.CARRY) self.set_flag(self.OVERFLOW, (~(arg ^ self.a)) & (self.a ^ result) & 0x80) self.set_flag(self.CARRY, result > 255) self.a = self.set_nz(result) & 0xFF - def ADC(self, addr): self.ADDITION(self.get_byte(addr)) - def SBC(self, addr): self.ADDITION(~self.get_byte(addr)) + def ADC(self, addr): + self.ADDITION(self.get_byte(addr)) + + def SBC(self, addr): + self.ADDITION(~self.get_byte(addr)) def COMPARE(self, what, addr): self.set_flag(self.CARRY, self.set_nz(what - self.get_byte(addr)) >= 0) - def CMP(self, addr): self.COMPARE(self.a, addr) - def CPX(self, addr): self.COMPARE(self.x, addr) - def CPY(self, addr): self.COMPARE(self.y, addr) + def CMP(self, addr): + self.COMPARE(self.a, addr) + + def CPX(self, addr): + self.COMPARE(self.x, addr) + + def CPY(self, addr): + self.COMPARE(self.y, addr) def BRANCH(self, addr, flag, condition): if self.get_flag(flag) is condition: self.pc = addr - self.cycles += 1 # Ako se grana, to je 1 ekstra ciklus + self.cycles += 1 # Ako se grana, to je 1 ekstra ciklus + def BCS(self, addr): + self.BRANCH(addr, self.CARRY, True) - def BCS(self, addr): self.BRANCH(addr, self.CARRY, True) - def BCC(self, addr): self.BRANCH(addr, self.CARRY, False) - def BEQ(self, addr): self.BRANCH(addr, self.ZERO, True) - def BNE(self, addr): self.BRANCH(addr, self.ZERO, False) - def BMI(self, addr): self.BRANCH(addr, self.NEGATIVE, True) - def BPL(self, addr): self.BRANCH(addr, self.NEGATIVE, False) - def BVS(self, addr): self.BRANCH(addr, self.OVERFLOW, True) - def BVC(self, addr): self.BRANCH(addr, self.OVERFLOW, False) + def BCC(self, addr): + self.BRANCH(addr, self.CARRY, False) + + def BEQ(self, addr): + self.BRANCH(addr, self.ZERO, True) + + def BNE(self, addr): + self.BRANCH(addr, self.ZERO, False) + + def BMI(self, addr): + self.BRANCH(addr, self.NEGATIVE, True) + + def BPL(self, addr): + self.BRANCH(addr, self.NEGATIVE, False) + + def BVS(self, addr): + self.BRANCH(addr, self.OVERFLOW, True) + + def BVC(self, addr): + self.BRANCH(addr, self.OVERFLOW, False) # dissasemble address def disasm(self, addr): instruction, addressing, cycles = self._opcodes[self.memory[addr]] - return "%04X %s %s" % (addr, instruction.__name__.lower(), self.addr_fmt[addressing](addr+1)), addr + self.ticks[addressing] + 1 + return ( + "%04X %s %s" + % (addr, instruction.__name__.lower(), self.addr_fmt[addressing](addr + 1)), + addr + self.ticks[addressing] + 1, + ) def step(self): opcode = self.memory[self.pc] if opcode not in self._opcodes: - print('HALT') + print("HALT") for addr in self.executed: code, _ = self.disasm(addr) - print(" - %s" % code) - print(" > %04x %02x %02x" % (self.pc, self.memory[self.pc], self.memory[self.pc+1])) + print((" - %s" % code)) + print( + ( + " > %04x %02x %02x" + % (self.pc, self.memory[self.pc], self.memory[self.pc + 1]) + ) + ) self.executed.append(self.pc) self.pc = self.pc + 1 & 0xFFFF @@ -322,4 +584,3 @@ class CPU(object): self.executed = self.executed[1:] self.cycles += cycles - diff --git a/orao/keyboard.py b/orao/keyboard.py index efc1fa2..3d8a97b 100644 --- a/orao/keyboard.py +++ b/orao/keyboard.py @@ -1,26 +1,37 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- # defines orao keyboard import pygame, numpy _kbd = { - 0x83FE: (112, 240, 185, 39), 0x83FF: (45, 48), # [p đ š ;] [- 0] - 0x85FE: (232, 230, 190, 43), 0x85FF: (8, 94), # [č ć ž :] [BS ^] - 0x86FE: (102, 104, 103, 110), 0x86FF: (98, 118), # [f h g n] [b v] - 0x877E: (100, 97, 115, 122), 0x877F: (120, 99), # [d a s z] [x c] - 0x87BE: (108, 106, 107, 109), 0x87BF: (44, 46), # [l j k m] [, .] - 0x87DE: (101, 113, 119, 49), 0x87DF: (50, 51), # [e q w l] [2 3] - 0x87EE: (111, 105, 117, 55), 0x87EF: (56, 57), # [o i u 7] [8 9] - 0x87F6: (114, 121, 116, 54), 0x87F7: (53, 52), # [r y t 6] [5 4] - 0x87FA: (282, 283, 284, pygame.K_RCTRL), 0x87FD: (13, pygame.K_LCTRL), # [f1f2f3f4] [cr l_ctrl] - 0x87FC: (pygame.K_LEFT, pygame.K_UP, pygame.K_DOWN, pygame.K_RIGHT), - 0x87FB: (32, pygame.K_RSHIFT) # [spc l_shift] + 0x83FE: (112, 240, 185, 39), + 0x83FF: (45, 48), # [p đ š ;] [- 0] + 0x85FE: (232, 230, 190, 43), + 0x85FF: (8, 94), # [č ć ž :] [BS ^] + 0x86FE: (102, 104, 103, 110), + 0x86FF: (98, 118), # [f h g n] [b v] + 0x877E: (100, 97, 115, 122), + 0x877F: (120, 99), # [d a s z] [x c] + 0x87BE: (108, 106, 107, 109), + 0x87BF: (44, 46), # [l j k m] [, .] + 0x87DE: (101, 113, 119, 49), + 0x87DF: (50, 51), # [e q w l] [2 3] + 0x87EE: (111, 105, 117, 55), + 0x87EF: (56, 57), # [o i u 7] [8 9] + 0x87F6: (114, 121, 116, 54), + 0x87F7: (53, 52), # [r y t 6] [5 4] + 0x87FA: (282, 283, 284, pygame.K_RCTRL), + 0x87FD: (13, pygame.K_LCTRL), # [f1f2f3f4] [cr l_ctrl] + 0x87FC: (pygame.K_LEFT, pygame.K_UP, pygame.K_DOWN, pygame.K_RIGHT), + 0x87FB: (32, pygame.K_RSHIFT), # [spc l_shift] } -def listener(event, cpu): - if event.type in [pygame.KEYDOWN, pygame.KEYUP]: - for address, keycodes in _kbd.iteritems(): - keys = map(pygame.key.get_pressed().__getitem__, keycodes) - cpu.memory[address] = ~numpy.dot(keys, [16, 32, 64, 128][:len(keys)]) & 0xFF +def listener(event, cpu): + if event.type in [pygame.KEYDOWN, pygame.KEYUP]: + for address, keycodes in _kbd.items(): + keys = list(map(pygame.key.get_pressed().__getitem__, keycodes)) + cpu.memory[address] = ( + ~numpy.dot(keys, [16, 32, 64, 128][: len(keys)]) & 0xFF + ) diff --git a/orao/timer.py b/orao/timer.py index abef45b..d0fc486 100644 --- a/orao/timer.py +++ b/orao/timer.py @@ -1,14 +1,25 @@ +# -*- coding: utf-8 -*- timer = {} timer_pos = {} def mem_listener(addr, val, cpu): - if addr >= 0xa000 and addr <= 0xa0ff: - timer_ix = addr & 0xff - if timer_ix in timer: - print('timer(%3d:%04x-%04x):duration %d cy' % (timer_ix, timer_pos[timer_ix], cpu.pc-1, cpu.cycles - timer[timer_ix] - 4)) - del timer[timer_ix] - del timer_pos[timer_ix] - else: - timer[timer_ix] = cpu.cycles - timer_pos[timer_ix] = cpu.pc+2 + if addr >= 0xA000 and addr <= 0xA0FF: + timer_ix = addr & 0xFF + if timer_ix in timer: + print( + ( + "timer(%3d:%04x-%04x):duration %d cy" + % ( + timer_ix, + timer_pos[timer_ix], + cpu.pc - 1, + cpu.cycles - timer[timer_ix] - 4, + ) + ) + ) + del timer[timer_ix] + del timer_pos[timer_ix] + else: + timer[timer_ix] = cpu.cycles + timer_pos[timer_ix] = cpu.pc + 2 diff --git a/orao/video.py b/orao/video.py index cb50a3d..2cfd7a6 100644 --- a/orao/video.py +++ b/orao/video.py @@ -1,11 +1,14 @@ +# -*- coding: utf-8 -*- + import pygame terminal = pygame.Surface((256, 256), pygame.SRCALPHA, depth=32) terminal.fill((255, 255, 255)) alphaarray = pygame.surfarray.pixels_alpha(terminal) + def mem_listener(addr, val, cpu): - if 0x6000 <= addr <= 0x7FFF: # Video RAM - y, x = divmod((addr - 0x6000) * 8, 256) - for i in range(8): - alphaarray[x + i, y] = 255 if (val >> i) & 1 else 40 # Transparency mask + if 0x6000 <= addr <= 0x7FFF: # Video RAM + y, x = divmod((addr - 0x6000) * 8, 256) + for i in range(8): + alphaarray[x + i, y] = 255 if (val >> i) & 1 else 40 # Transparency mask diff --git a/orao/views/access_map.py b/orao/views/access_map.py index c7514b9..6e4d45e 100644 --- a/orao/views/access_map.py +++ b/orao/views/access_map.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import pygame from .view import View diff --git a/orao/views/cpu_state.py b/orao/views/cpu_state.py index 4e1a44e..6800b05 100644 --- a/orao/views/cpu_state.py +++ b/orao/views/cpu_state.py @@ -1,10 +1,11 @@ +# -*- coding: utf-8 -*- + import pygame from .view import View from ..chargen import chargen_draw_str class CPUState(View): - def __init__(self): self.init_surface(pygame.Surface((2 * 8 * 8, 3 * 8), depth=24)) #self.set_smooth_scale() diff --git a/orao/views/heatmap.py b/orao/views/heatmap.py index fe630ff..12af756 100644 --- a/orao/views/heatmap.py +++ b/orao/views/heatmap.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import pygame import numpy from .view import View @@ -30,4 +32,3 @@ class MemHeatmap(View): mem = cpu.memory[self.start_addr:self.start_addr + (w * h)] arr = numpy.reshape(mem, (h, w)) pygame.surfarray.blit_array(self.surf, numpy.transpose(arr)) - diff --git a/orao/views/micro_mem_view.py b/orao/views/micro_mem_view.py index 13d1d5b..ac56da4 100644 --- a/orao/views/micro_mem_view.py +++ b/orao/views/micro_mem_view.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from .heatmap import MemHeatmap from .access_map import AccessMap diff --git a/orao/views/text_label.py b/orao/views/text_label.py index 10944c5..3fb5602 100644 --- a/orao/views/text_label.py +++ b/orao/views/text_label.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import pygame from .view import View from ..chargen import chargen_draw_str diff --git a/orao/views/view.py b/orao/views/view.py index 1e858ef..703884a 100644 --- a/orao/views/view.py +++ b/orao/views/view.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import pygame class View: