Esempio n. 1
0
 def test_scaling_factor(self):
     screen = Screen(scaling_factor=3)
     self.assertEqual(64 * 3, screen.surface.get_width())
     self.assertEqual(32 * 3, screen.surface.get_height())
     screen.buffer[1][2] = True
     screen.update()
     for y in range(screen.surface.get_height()):
         for x in range(screen.surface.get_width()):
             if x in [6, 7, 8] and y in [3, 4, 5]:
                 self.assertEqual(WHITE, screen.surface.get_at((x, y)))
             else:
                 self.assertEqual(BLACK, screen.surface.get_at((x, y)))
Esempio n. 2
0
    def __init__(self, scaling_factor: int, cycles_per_frame: int,
                 starting_address: int):
        pygame.init()
        self.screen = Screen(scaling_factor)
        self.keyboard = Keyboard()
        self.sound = Sound()
        self.cpu = CPU(self.screen, self.keyboard, starting_address)
        self.cycles_per_frame = cycles_per_frame

        sixty_hertz_ms = round(1000 / SIXTY_HERTZ)
        pygame.time.set_timer(SIXTY_HERTZ_CLOCK, sixty_hertz_ms)
        print(
            f"Target CPU speed: {cycles_per_frame * SIXTY_HERTZ} instructions per second"
        )
        print(f"Screen scaling factor: {scaling_factor}")
Esempio n. 3
0
    def test_init(self):
        self.assertEqual(cpu.MEMORY_SIZE, len(self.cpu.memory))
        self.assertEqual(bytearray(cpu.font_sprites),
                         self.cpu.memory[0:len(cpu.font_sprites)])
        self.assertEqual([], self.cpu.stack)
        self.assertEqual(0x200, self.cpu.pc)
        self.assertEqual(0x600,
                         CPU(Screen(), Keyboard(), starting_address=0x600).pc)
        self.assertEqual([0x00] * 16, self.cpu.V)
        self.assertEqual(0x000, self.cpu.I)
        self.assertEqual(0x000, self.cpu.I)
        self.assertEqual(0x00, self.cpu.delay_timer)
        self.assertEqual(0x00, self.cpu.sound_timer)

        self.assertEqual(0x042,
                         CPU(Screen(), Keyboard(), starting_address=0x042).pc)
Esempio n. 4
0
 def test_update(self):
     screen = Screen()
     screen.update()
     for row in range(32):
         for col in range(64):
             self.assertEqual(BLACK, screen.surface.get_at((col, row)))
     for row in range(32):
         for col in range(64):
             screen.buffer[row][col] = True
     screen.update()
     for row in range(32):
         for col in range(64):
             self.assertEqual(WHITE, screen.surface.get_at((col, row)))
Esempio n. 5
0
 def test_init(self):
     screen = Screen()
     self.assertEqual(32, len(screen.buffer))
     for row in range(32):
         self.assertEqual([False] * 64, screen.buffer[row])
Esempio n. 6
0
class Chip8:
    screen: Screen
    keyboard: Keyboard
    sound: Sound
    cpu: CPU
    cycles_per_frame: int

    def __init__(self, scaling_factor: int, cycles_per_frame: int,
                 starting_address: int):
        pygame.init()
        self.screen = Screen(scaling_factor)
        self.keyboard = Keyboard()
        self.sound = Sound()
        self.cpu = CPU(self.screen, self.keyboard, starting_address)
        self.cycles_per_frame = cycles_per_frame

        sixty_hertz_ms = round(1000 / SIXTY_HERTZ)
        pygame.time.set_timer(SIXTY_HERTZ_CLOCK, sixty_hertz_ms)
        print(
            f"Target CPU speed: {cycles_per_frame * SIXTY_HERTZ} instructions per second"
        )
        print(f"Screen scaling factor: {scaling_factor}")

    def load(self, rom: bytes):
        self.cpu.load(rom)

    def run(self):
        while True:
            self._handle_events()

    def _handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                self.keyboard.keydown(event)

            elif event.type == pygame.KEYUP:
                key = self.keyboard.keyup(event)
                if self.cpu.waiting_for_keypress and key is not None:
                    self.cpu.key_was_pressed(key)

            elif event.type == SIXTY_HERTZ_CLOCK:
                self.tick()

            elif event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

    def tick(self):
        if self.cpu.waiting_for_keypress:
            return

        has_screen_changed = False
        self.cpu.decrease_timers()
        for _ in range(self.cycles_per_frame):
            try:
                self.cpu.step()
            except UpdateScreen:
                has_screen_changed = True
            except WaitForKeypress:
                break
        self.sound.update(self.cpu.sound_timer)
        if has_screen_changed:
            self.screen.update()
Esempio n. 7
0
 def setUp(self):
     self.screen = Screen()
     self.keyboard = Keyboard()
     self.cpu = CPU(self.screen, self.keyboard)
Esempio n. 8
0
    def __init__(self):
        self.codes = {
            0x00E0: self.clear_display,
            0x00EE: self.return_subroutine,
            0x1: self.jump_subroutine,
            0x2: self.call_subroutine,
            0x3: self.skip_next_if_equal_address,
            0x4: self.skip_next_if_not_equal_address,
            0x5: self.skip_next_if_equal_register,
            0x6: self.set_register,
            0x7: self.add_register,
            0x8: self.registers_ops,
            0x9: self.skip_if_registers_not_equal,
            0xA: self.set_i_to_address_plus,
            0xB: self.jump_to_address_plus_i,
            0xC: self.set_bitwise_and_random,
            0xD: self.draw_sprite,
            0xE: self.key_ops,
            0xF: self.mem_ops
        }

        self.registers_ops = {
            0x0: self.assign_registers,
            0x1: self.bitwise_or,
            0x2: self.bitwise_and,
            0x3: self.bitwise_xor,
            0x4: self.add_with_carry,
            0x5: self.subtract_with_borrow,
            0x6: self.shift_right,
            0x7: self.reversed_subtraction,
            0xE: self.shift_left
        }

        self.key_ops = {
            0xE: self.skip_if_vx_pressed,
            0x1: self.skip_if_not_vx_pressed,
        }

        self.mem_ops = {
            0x07: self.set_register_to_timer,
            0x0A: self.load_key_pressed,
            0x15: self.set_delay_timer,
            0x18: self.set_sound_timer,
            0x1E: self.add_to_memory_index,
            0x29: self.set_memory_index_to_sprite,
            0x33: self.store_binary_coded_decimal,
            0x55: self.dump_registers,
            0x65: self.load_registers
        }

        self.timers = {'delay': 0, 'sound': 0}

        self.fonts = (0xF0, 0x90, 0x90, 0x90, 0xF0, 0x20, 0x60, 0x20, 0x20,
                      0x70, 0xF0, 0x10, 0xF0, 0x80, 0xF0, 0xF0, 0x10, 0xF0,
                      0x10, 0xF0, 0x90, 0x90, 0xF0, 0x10, 0x10, 0xF0, 0x80,
                      0xF0, 0x10, 0xF0, 0xF0, 0x80, 0xF0, 0x90, 0xF0, 0xF0,
                      0x10, 0x20, 0x40, 0x40, 0xF0, 0x90, 0xF0, 0x90, 0xF0,
                      0xF0, 0x90, 0xF0, 0x10, 0xF0, 0xF0, 0x90, 0xF0, 0x90,
                      0x90, 0xE0, 0x90, 0xE0, 0x90, 0xE0, 0xF0, 0x80, 0x80,
                      0x80, 0xF0, 0xE0, 0x90, 0x90, 0x90, 0xE0, 0xF0, 0x80,
                      0xF0, 0x80, 0xF0, 0xF0, 0x80, 0xF0, 0x80, 0x80)

        self.stack_pointer = None
        self.memory = [0] * MEMORY_SIZE
        self.registers = [0] * REGISTERS_SIZE
        self.program_counter = 0x200
        self.stack = []
        self.memory_index = 0
        self.vx = 0
        self.vy = 0
        self.op_code = None

        for index, font in enumerate(self.fonts):
            self.memory[index] = self.fonts[index]

        self.display = Screen()
        pygame.mixer.music.load(SOUND)
Esempio n. 9
0
class Cpu:
    def __init__(self):
        self.codes = {
            0x00E0: self.clear_display,
            0x00EE: self.return_subroutine,
            0x1: self.jump_subroutine,
            0x2: self.call_subroutine,
            0x3: self.skip_next_if_equal_address,
            0x4: self.skip_next_if_not_equal_address,
            0x5: self.skip_next_if_equal_register,
            0x6: self.set_register,
            0x7: self.add_register,
            0x8: self.registers_ops,
            0x9: self.skip_if_registers_not_equal,
            0xA: self.set_i_to_address_plus,
            0xB: self.jump_to_address_plus_i,
            0xC: self.set_bitwise_and_random,
            0xD: self.draw_sprite,
            0xE: self.key_ops,
            0xF: self.mem_ops
        }

        self.registers_ops = {
            0x0: self.assign_registers,
            0x1: self.bitwise_or,
            0x2: self.bitwise_and,
            0x3: self.bitwise_xor,
            0x4: self.add_with_carry,
            0x5: self.subtract_with_borrow,
            0x6: self.shift_right,
            0x7: self.reversed_subtraction,
            0xE: self.shift_left
        }

        self.key_ops = {
            0xE: self.skip_if_vx_pressed,
            0x1: self.skip_if_not_vx_pressed,
        }

        self.mem_ops = {
            0x07: self.set_register_to_timer,
            0x0A: self.load_key_pressed,
            0x15: self.set_delay_timer,
            0x18: self.set_sound_timer,
            0x1E: self.add_to_memory_index,
            0x29: self.set_memory_index_to_sprite,
            0x33: self.store_binary_coded_decimal,
            0x55: self.dump_registers,
            0x65: self.load_registers
        }

        self.timers = {'delay': 0, 'sound': 0}

        self.fonts = (0xF0, 0x90, 0x90, 0x90, 0xF0, 0x20, 0x60, 0x20, 0x20,
                      0x70, 0xF0, 0x10, 0xF0, 0x80, 0xF0, 0xF0, 0x10, 0xF0,
                      0x10, 0xF0, 0x90, 0x90, 0xF0, 0x10, 0x10, 0xF0, 0x80,
                      0xF0, 0x10, 0xF0, 0xF0, 0x80, 0xF0, 0x90, 0xF0, 0xF0,
                      0x10, 0x20, 0x40, 0x40, 0xF0, 0x90, 0xF0, 0x90, 0xF0,
                      0xF0, 0x90, 0xF0, 0x10, 0xF0, 0xF0, 0x90, 0xF0, 0x90,
                      0x90, 0xE0, 0x90, 0xE0, 0x90, 0xE0, 0xF0, 0x80, 0x80,
                      0x80, 0xF0, 0xE0, 0x90, 0x90, 0x90, 0xE0, 0xF0, 0x80,
                      0xF0, 0x80, 0xF0, 0xF0, 0x80, 0xF0, 0x80, 0x80)

        self.stack_pointer = None
        self.memory = [0] * MEMORY_SIZE
        self.registers = [0] * REGISTERS_SIZE
        self.program_counter = 0x200
        self.stack = []
        self.memory_index = 0
        self.vx = 0
        self.vy = 0
        self.op_code = None

        for index, font in enumerate(self.fonts):
            self.memory[index] = self.fonts[index]

        self.display = Screen()
        pygame.mixer.music.load(SOUND)

    def load_rom(self, path):
        with open(path, 'rb') as rom:
            byte = rom.read()
            index = 0
            while byte is not None:
                self.memory[index] = byte
                byte = rom.read()
                index += 1

    def cycle(self):
        while self.program_counter <= len(self.memory):
            self.op_code = self.memory[self.program_counter]
            log.info('Extracted command = {}'.format(self.op_code))
            method = self.codes.get(self.op_code, None)
            if not method:
                try:
                    method_code = (self.op_code & 0xf000) >> 12
                    method = self.codes[method_code]
                except KeyError:
                    log.error('Unknown command')
                    raise TypeError("Command not implemented")
                else:
                    self.vx = (self.op_code & 0x0f00) >> 8
                    self.vy = (self.op_code & 0x00f0) >> 4
                    if callable(method):
                        method()
                    elif self.op_code & 0xf000 == 0xE:
                        method[method_code][self.op_code & 0x00ff]()
                    else:
                        method[method_code][self.op_code & 0x000f]()
                    log.info('Called method {} with vx = {} and vy {}'.format(
                        method, self.vx, self.vy))
            elif callable(method):
                method()
            self.program_counter += 2
            for timer in self.timers:
                if self.timers[timer] > 0:
                    self.timers[timer] -= 1
                    if timer == 'sound' and self.timers[timer] == 0:
                        pygame.mixer.music.play(0)

    def clear_display(self):
        self.display.clear()

    def return_subroutine(self):
        self.program_counter = self.stack.pop()

    def jump_subroutine(self):
        jump_address = self.op_code & 0x0fff
        self.program_counter = jump_address

    def call_subroutine(self):
        self.stack.append(self.program_counter)
        self.program_counter = self.op_code & 0x0fff

    def skip_next_if_equal_address(self):
        compare = self.op_code & 0x00ff
        if self.registers[self.vx] == compare:
            self.program_counter += 2

    def skip_next_if_not_equal_address(self):
        compare = self.op_code & 0x00ff
        if self.registers[self.vx] != compare:
            self.program_counter += 2

    def skip_next_if_equal_register(self):
        if self.registers[self.vx] == self.registers[self.vy]:
            self.program_counter += 2

    def set_register(self):
        value = self.op_code & 0x00ff
        self.registers[self.vx] = value

    def add_register(self):
        value = self.op_code & 0x00ff
        self.registers[self.vx] += value

    def assign_registers(self):
        self.registers[self.vx] = self.registers[self.vy]

    def bitwise_or(self):
        compare_or = self.op_code & 0x00ff
        self.registers[self.vx] |= compare_or

    def bitwise_and(self):
        compare_and = self.op_code & 0x00ff
        self.registers[self.vx] &= compare_and

    def bitwise_xor(self):
        compare_xor = self.op_code & 0x00ff
        self.registers[self.vx] ^= compare_xor

    def shift_right(self):
        self.registers[0xf] = self.registers[self.vx] % 2
        self.registers[self.vx] <<= 1
        self.registers[self.vx] /= 2

    def shift_left(self):
        self.registers[0xf] = self.registers[self.vx] >> 8
        self.registers[self.vx] >>= 1
        self.registers[self.vx] *= 2

    def add_with_carry(self):
        if self.registers[self.vx] + self.registers[self.vy] > 0xff:
            self.registers[self.vx] = 0
            self.registers[0xf] = 1
        else:
            self.registers[self.vx] += self.registers[self.vy]

    def subtract_with_borrow(self):
        if self.registers[self.vx] - self.registers[self.vy] < 0:
            self.registers[self.vx] = 0
            self.registers[0xf] = 1
        else:
            self.registers[self.vx] -= self.registers[self.vy]

    def reversed_subtraction(self):
        if self.registers[self.vy] - self.registers[self.vx] < 0:
            self.registers[self.vy] = 0
            self.registers[0xf] = 1
        else:
            self.registers[
                self.vy] = self.registers[self.vx] - self.registers[self.vy]

    def skip_if_registers_not_equal(self):
        if self.registers[self.vx] != self.registers[self.vy]:
            self.program_counter += 2

    def set_i_to_address_plus(self):
        self.memory_index = self.op_code & 0x0fff

    def jump_to_address_plus_i(self):
        self.program_counter = self.op_code[0] + self.op_code & 0x0fff

    def set_bitwise_and_random(self):
        bitwise_and = self.op_code & 0x00ff
        self.registers[self.vx] = bitwise_and & randint(0, 255)

    def load_key_pressed(self):
        pressed = None
        while not pressed:
            event = pygame.event.poll()
            pressed = KEY_MAPPINGS.get(event.type, None)
        self.vx = pressed

    def set_memory_index_to_sprite(self):
        self.memory_index = 5 * (self.memory[self.vx]) & 0xfff

    def store_binary_coded_decimal(self):
        bcd = self.vx
        self.memory[self.memory_index] = self.vx // 100
        bcd -= self.memory[self.memory_index] * 100
        self.memory[self.memory_index + 1] = bcd // 10
        bcd -= self.memory[self.memory_index + 1] * 10
        self.memory[self.memory_index + 2] = bcd

    def draw_sprite(self):
        self.registers[0xf] = 0
        height = self.op_code & 0x000f
        for i in range(height):
            for bit in bin(self.memory_index + i)[:2]:
                if bit:
                    self.display.set_pixel(self.vx, self.vy)

    def skip_if_vx_pressed(self):
        event = pygame.event.poll()
        pressed = KEY_MAPPINGS.get(event.type, None)
        if self.vx == pressed:
            self.program_counter += 2

    def skip_if_not_vx_pressed(self):
        event = pygame.event.poll()
        pressed = KEY_MAPPINGS.get(event.type, None)
        if self.vx != pressed:
            self.program_counter += 2

    def set_register_to_timer(self):
        self.registers[self.vx] = self.timers['delay']

    def set_delay_timer(self):
        self.timers['delay'] = self.registers[self.vx]

    def set_sound_timer(self):
        self.timers['sound'] = self.registers[self.vx]

    def add_to_memory_index(self):
        self.memory_index += self.registers[self.vx]

    def dump_registers(self):
        for register in self.registers:
            self.memory[self.memory_index] = register
            self.memory_index += 1

    def load_registers(self):
        for register in range(len(self.registers)):
            self.registers[register] = self.memory[self.memory_index]