diff --git a/README.md b/README.md index a2fed2e..bddb510 100644 --- a/README.md +++ b/README.md @@ -314,8 +314,8 @@ Color values are specified by using HTML hex values such as `AABBCC` without the leading `#`. There are currently 4 color values that can be set: * `--color_0` specifies the background color. This defaults to `000000`. -* `--color_1` specifies bitplane 1 color. This defaults to `666666`. -* `--color_2` specifies bitplane 2 color. This defaults to `BBBBBB`. +* `--color_1` specifies bitplane 1 color. This defaults to `FF33CC`. +* `--color_2` specifies bitplane 2 color. This defaults to `33CCFF`. * `--color_3` specifies bitplane 1 and 2 overlap color. This defaults to `FFFFFF`. For Chip8 and SuperChip 8 programs, only the background color `color_0` (for pixels diff --git a/chip8/cpu.py b/chip8/cpu.py index adf5117..de32865 100644 --- a/chip8/cpu.py +++ b/chip8/cpu.py @@ -114,7 +114,7 @@ def __init__( 0x2: self.jump_to_subroutine, # 2nnn - CALL nnn 0x3: self.skip_if_reg_equal_val, # 3snn - SKE Vs, nn 0x4: self.skip_if_reg_not_equal_val, # 4snn - SKNE Vs, nn - 0x5: self.skip_if_reg_equal_reg, # 5st0 - SKE Vs, Vt + 0x5: self.save_skip_routines, # see subfunctions below 0x6: self.move_value_to_reg, # 6snn - LOAD Vs, nn 0x7: self.add_value_to_reg, # 7snn - ADD Vs, nn 0x8: self.execute_logical_instruction, # see subfunctions below @@ -127,6 +127,12 @@ def __init__( 0xF: self.misc_routines, # see subfunctions below } + self.save_skip_lookup = { + 0x0: self.skip_if_reg_equal_reg, # 5xy0 - SKE Vx, Vy + 0x2: self.store_subset_regs_in_memory, # 5xy2 - STORSUB x, y + 0x3: self.read_subset_regs_in_memory, # 5xy3 - LOADSUB x, y + } + self.clear_routines = { 0xE0: self.clear_screen, # 00E0 - CLS 0xEE: self.return_from_subroutine, # 00EE - RTS @@ -257,21 +263,25 @@ def keyboard_routines(self): if self.memory[self.pc - 2] == 0xF0 and self.memory[self.pc - 1] == 0x00: self.pc += 2 + def save_skip_routines(self): + """ + Will execute either a register save or skip routine. + """ + operation = self.operand & 0x000F + try: + self.save_skip_lookup[operation]() + except KeyError: + raise UnknownOpCodeException(self.operand) + def misc_routines(self): """ Will execute one of the routines specified in misc_routines. """ operation = self.operand & 0x00FF - sub_operation = self.operand & 0x000F - if sub_operation == 0x2: - self.store_subset_regs_in_memory() - elif sub_operation == 0x3: - self.read_subset_regs_in_memory() - else: - try: - self.misc_routine_lookup[operation]() - except KeyError: - raise UnknownOpCodeException(self.operand) + try: + self.misc_routine_lookup[operation]() + except KeyError: + raise UnknownOpCodeException(self.operand) def clear_return(self): """ @@ -282,7 +292,7 @@ def clear_return(self): sub_operation = operation & 0x00F0 if sub_operation == 0x00C0: num_lines = self.operand & 0x000F - self.screen.scroll_down(num_lines) + self.screen.scroll_down(num_lines, self.bitplane) self.last_op = f"Scroll Down {num_lines:01X}" else: try: @@ -296,7 +306,7 @@ def clear_screen(self): Clears the screen """ - self.screen.clear_screen() + self.screen.clear_screen(self.bitplane) self.last_op = "CLS" def return_from_subroutine(self): @@ -318,7 +328,7 @@ def scroll_right(self): Scrolls the screen right by 4 pixels. """ - self.screen.scroll_right() + self.screen.scroll_right(self.bitplane) self.last_op = "Scroll Right" def scroll_left(self): @@ -327,7 +337,7 @@ def scroll_left(self): Scrolls the screen left by 4 pixels. """ - self.screen.scroll_left() + self.screen.scroll_left(self.bitplane) self.last_op = "Scroll Left" def exit(self): @@ -450,6 +460,58 @@ def skip_if_reg_equal_reg(self): self.pc += 2 self.last_op = f"SKE V{x:01X}, V{y:01X}" + def store_subset_regs_in_memory(self): + """ + 5xy2 - STORSUB [I], Vx, Vy + + Store a subset of registers from x to y in memory starting at index. + The x and y calculation is as follows: + + Bits: 15-12 11-8 7-4 3-0 + F x y 2 + + If x is larger than y, then they will be stored in reverse order. + """ + x = (self.operand & 0x0F00) >> 8 + y = (self.operand & 0x00F0) >> 4 + pointer = 0 + if y >= x: + for z in range(x, y+1): + self.memory[self.index + pointer] = self.v[z] + pointer += 1 + else: + for z in range(x, y-1, -1): + self.memory[self.index + pointer] = self.v[z] + pointer += 1 + + self.last_op = f"STORSUB [I], {x:01X}, {y:01X}" + + def read_subset_regs_in_memory(self): + """ + 5xy3 - LOADSUB [I], Vx, Vy + + Load a subset of registers from x to y in memory starting at index. + The x and y calculation is as follows: + + Bits: 15-12 11-8 7-4 3-0 + F x y 2 + + If x is larger than y, then they will be loaded in reverse order. + """ + x = (self.operand & 0x0F00) >> 8 + y = (self.operand & 0x00F0) >> 4 + pointer = 0 + if y >= x: + for z in range(x, y+1): + self.v[z] = self.memory[self.index + pointer] + pointer += 1 + else: + for z in range(x, y-1, -1): + self.v[z] = self.memory[self.index + pointer] + pointer += 1 + + self.last_op = f"LOADSUB [I], {x:01X}, {y:01X}" + def move_value_to_reg(self): """ 6xnn - LOAD Vx, nn @@ -781,19 +843,28 @@ def draw_sprite(self): self.v[0xF] = 0 if num_bytes == 0: - self.draw_extended(x_pos, y_pos) + if self.bitplane == 3: + self.draw_extended(x_pos, y_pos, 1) + self.draw_extended(x_pos, y_pos, 2) + else: + self.draw_extended(x_pos, y_pos, self.bitplane) self.last_op = f"DRAWEX" else: - self.draw_normal(x_pos, y_pos, num_bytes) + if self.bitplane == 3: + self.draw_normal(x_pos, y_pos, num_bytes, 1) + self.draw_normal(x_pos, y_pos, num_bytes, 2) + else: + self.draw_normal(x_pos, y_pos, num_bytes, self.bitplane) self.last_op = f"DRAW V{x_source:01X}, V{y_source:01X}" - def draw_normal(self, x_pos, y_pos, num_bytes): + def draw_normal(self, x_pos, y_pos, num_bytes, bitplane): """ Draws a sprite on the screen while in NORMAL mode. :param x_pos: the X position of the sprite :param y_pos: the Y position of the sprite :param num_bytes: the number of bytes to draw + :param bitplane: the bitplane to draw to """ for y_index in range(num_bytes): color_byte = self.memory[self.index + y_index] @@ -806,13 +877,13 @@ def draw_normal(self, x_pos, y_pos, num_bytes): if not self.clip_quirks or (self.clip_quirks and x_coord < self.screen.get_width()): x_coord = x_coord % self.screen.get_width() turned_on = (color_byte & mask) > 0 - current_on = self.screen.get_pixel(x_coord, y_coord) + current_on = self.screen.get_pixel(x_coord, y_coord, bitplane) self.v[0xF] |= 1 if turned_on and current_on else 0 - self.screen.draw_pixel(x_coord, y_coord, turned_on ^ current_on) + self.screen.draw_pixel(x_coord, y_coord, turned_on ^ current_on, bitplane) mask = mask >> 1 self.screen.update() - def draw_extended(self, x_pos, y_pos): + def draw_extended(self, x_pos, y_pos, bitplane): """ Draws a sprite on the screen while in EXTENDED mode. Sprites in this mode are assumed to be 16x16 pixels. This means that two bytes will @@ -821,6 +892,7 @@ def draw_extended(self, x_pos, y_pos): :param x_pos: the X position of the sprite :param y_pos: the Y position of the sprite + :param bitplane: the bitplane to draw to """ for y_index in range(16): for x_byte in range(2): @@ -834,9 +906,9 @@ def draw_extended(self, x_pos, y_pos): if not self.clip_quirks or (self.clip_quirks and x_coord < self.screen.get_width()): x_coord = x_coord % self.screen.get_width() turned_on = (color_byte & mask) > 0 - current_on = self.screen.get_pixel(x_coord, y_coord) + current_on = self.screen.get_pixel(x_coord, y_coord, bitplane) self.v[0xF] += 1 if turned_on and current_on else 0 - self.screen.draw_pixel(x_coord, y_coord, turned_on ^ current_on) + self.screen.draw_pixel(x_coord, y_coord, turned_on ^ current_on, bitplane) mask = mask >> 1 else: self.v[0xF] += 1 @@ -873,58 +945,6 @@ def set_bitplane(self): self.bitplane = (self.operand & 0x0F00) >> 8 self.last_op = f"BITPLANE {self.bitplane:01X}" - def store_subset_regs_in_memory(self): - """ - Fxy2 - STORSUB [I], Vx, Vy - - Store a subset of registers from x to y in memory starting at index. - The x and y calculation is as follows: - - Bits: 15-12 11-8 7-4 3-0 - F x y 2 - - If x is larger than y, then they will be stored in reverse order. - """ - x = (self.operand & 0x0F00) >> 8 - y = (self.operand & 0x00F0) >> 4 - pointer = 0 - if y >= x: - for z in range(x, y+1): - self.memory[self.index + pointer] = self.v[z] - pointer += 1 - else: - for z in range(x, y-1, -1): - self.memory[self.index + pointer] = self.v[z] - pointer += 1 - - self.last_op = f"STORSUB [I], {x:01X}, {y:01X}" - - def read_subset_regs_in_memory(self): - """ - Fxy3 - LOADSUB [I], Vx, Vy - - Load a subset of registers from x to y in memory starting at index. - The x and y calculation is as follows: - - Bits: 15-12 11-8 7-4 3-0 - F x y 2 - - If x is larger than y, then they will be loaded in reverse order. - """ - x = (self.operand & 0x0F00) >> 8 - y = (self.operand & 0x00F0) >> 4 - pointer = 0 - if y >= x: - for z in range(x, y+1): - self.v[z] = self.memory[self.index + pointer] - pointer += 1 - else: - for z in range(x, y-1, -1): - self.v[z] = self.memory[self.index + pointer] - pointer += 1 - - self.last_op = f"LOADSUB [I], {x:01X}, {y:01X}" - def move_delay_timer_into_reg(self): """ Fx07 - LOAD Vx, DELAY diff --git a/chip8/screen.py b/chip8/screen.py index 162c300..419caff 100644 --- a/chip8/screen.py +++ b/chip8/screen.py @@ -31,8 +31,8 @@ def __init__( self, scale_factor, color_0="000000", - color_1="666666", - color_2="BBBBBB", + color_1="ff33cc", + color_2="33ccff", color_3="FFFFFF" ): """ @@ -67,10 +67,10 @@ def init_display(self): HWSURFACE | DOUBLEBUF, SCREEN_DEPTH) display.set_caption('CHIP8 Emulator') - self.clear_screen() + self.clear_screen(3) self.update() - def draw_pixel(self, x_pos, y_pos, turn_on): + def draw_pixel(self, x_pos, y_pos, turn_on, bitplane): """ Turn a pixel on or off at the specified location on the screen. Note that the pixel will not automatically be drawn on the screen, you @@ -81,29 +81,49 @@ def draw_pixel(self, x_pos, y_pos, turn_on): :param x_pos: the x coordinate to place the pixel :param y_pos: the y coordinate to place the pixel :param turn_on: whether to turn the pixel on or off + :param bitplane: the bitplane where the pixel is located """ + if bitplane == 0: + return + + other_bitplane = 2 if bitplane == 1 else 1 + other_pixel_on = self.get_pixel(x_pos, y_pos, other_bitplane) + mode_scale = 1 if self.mode == SCREEN_MODE_EXTENDED else 2 - pixel_color = 1 if turn_on else 0 x_base = (x_pos * mode_scale) * self.scale_factor y_base = (y_pos * mode_scale) * self.scale_factor + + if turn_on and other_pixel_on: + pixel_color = 3 + elif turn_on and not other_pixel_on: + pixel_color = bitplane + elif not turn_on and other_pixel_on: + pixel_color = other_bitplane + else: + pixel_color = 0 + draw.rect(self.surface, self.pixel_colors[pixel_color], (x_base, y_base, mode_scale * self.scale_factor, mode_scale * self.scale_factor)) - def get_pixel(self, x_pos, y_pos): + def get_pixel(self, x_pos, y_pos, bitplane): """ Returns whether the pixel is on (1) or off (0) at the specified - location. + location for the specified bitplane. :param x_pos: the x coordinate to check :param y_pos: the y coordinate to check - :return: true if the pixel is currently turned on + :param bitplane: the bitplane where the pixel is located + :return: True if the pixel is currently turned on, False otherwise """ + if bitplane == 0: + return False + mode_scale = 1 if self.mode == SCREEN_MODE_EXTENDED else 2 x_scale = (x_pos * mode_scale) * self.scale_factor y_scale = (y_pos * mode_scale) * self.scale_factor pixel_color = self.surface.get_at((x_scale, y_scale)) - return pixel_color == self.pixel_colors[1] + return pixel_color == self.pixel_colors[bitplane] or pixel_color == self.pixel_colors[3] def get_width(self): """ @@ -121,11 +141,24 @@ def get_height(self): """ return 64 if self.mode == SCREEN_MODE_EXTENDED else 32 - def clear_screen(self): + def clear_screen(self, bitplane): """ - Turns off all the pixels on the screen (writes color 0 to all pixels). + Turns off all the pixels on the specified bitplane. + + :param bitplane: the bitplane to clear """ - self.surface.fill(self.pixel_colors[0]) + if bitplane == 0: + return + + if bitplane == 3: + self.surface.fill(self.pixel_colors[0]) + return + + max_x = self.get_width() + max_y = self.get_height() + for x in range(max_x): + for y in range(max_y): + self.draw_pixel(x, y, False, bitplane) @staticmethod def update(): @@ -149,37 +182,133 @@ def set_normal(self): """ self.mode = SCREEN_MODE_NORMAL - def scroll_down(self, num_lines): + def scroll_down(self, num_lines, bitplane): """ Scroll the screen down by num_lines. :param num_lines: the number of lines to scroll down + :param bitplane: the bitplane to scroll """ + if bitplane == 0: + return + mode_scale = 1 if self.mode == SCREEN_MODE_EXTENDED else 2 actual_lines = num_lines * mode_scale * self.scale_factor - self.surface.scroll(0, actual_lines) - self.surface.fill(self.pixel_colors[0], (0, 0, self.width * mode_scale * self.scale_factor, actual_lines)) - self.update() + if bitplane == 3: + self.surface.scroll(0, actual_lines) + self.surface.fill(self.pixel_colors[0], (0, 0, self.width * mode_scale * self.scale_factor, actual_lines)) + self.update() + return + + max_x = self.get_width() + max_y = self.get_height() + + # Blank out any pixels in the bottom n lines that we will copy to + for x in range(max_x): + for y in range(max_y - num_lines, max_y): + self.draw_pixel(x, y, False, bitplane) - def scroll_left(self): + # Start copying pixels from the top to the bottom and shift by 4 pixels + for x in range(max_x): + for y in range(max_y - num_lines - 1, -1, -1): + current_pixel = self.get_pixel(x, y, bitplane) + self.draw_pixel(x, y, False, bitplane) + self.draw_pixel(x, y + num_lines, current_pixel, bitplane) + + # Blank out any pixels in the first num_lines horizontal lines + for x in range(max_x): + for y in range(num_lines): + self.draw_pixel(x, y, False, bitplane) + + def scroll_left(self, bitplane): """ Scroll the screen left 4 pixels. + + :param bitplane: the bitplane to scroll """ - mode_scale = 1 if self.mode == SCREEN_MODE_EXTENDED else 2 - actual_lines = 4 * mode_scale * self.scale_factor - left = (self.width * mode_scale * self.scale_factor) - actual_lines - self.surface.scroll(-actual_lines, 0) - self.surface.fill(self.pixel_colors[0], (left, 0, actual_lines, self.height * mode_scale * self.scale_factor)) + if bitplane == 0: + return + + other_bitplane = 1 if bitplane == 2 else 2 + max_x = self.get_width() + max_y = self.get_height() + + if bitplane == 3: + # Blank out any pixels in the left 4 vertical lines that we will copy to + for x in range(4): + for y in range(max_y): + self.draw_pixel(x, y, False, 1) + self.draw_pixel(x, y, False, 2) + + # Start copying pixels from the right to the left and shift by 4 pixels + for x in range(4, max_x): + for y in range(max_y): + current_pixel = self.get_pixel(x, y, 1) + self.draw_pixel(x, y, False, 1) + self.draw_pixel(x - 4, y, current_pixel, 1) + current_pixel = self.get_pixel(x, y, 2) + self.draw_pixel(x, y, False, 2) + self.draw_pixel(x - 4, y, current_pixel, 2) + + # Blank out any pixels in the right 4 vertical lines + for x in range(max_x - 4, max_x): + for y in range(max_y): + self.draw_pixel(x, y, False, 1) + self.draw_pixel(x, y, False, 2) + else: + for x in range(4): + for y in range(max_y): + self.draw_pixel(x, y, False, bitplane) + + # Start copying pixels from the right to the left and shift by 4 pixels + for x in range(4, max_x): + for y in range(max_y): + current_pixel = self.get_pixel(x, y, bitplane) + self.draw_pixel(x, y, False, bitplane) + self.draw_pixel(x - 4, y, current_pixel, bitplane) + + # Blank out any pixels in the right 4 vertical lines + for x in range(max_x - 4, max_x): + for y in range(max_y): + self.draw_pixel(x, y, False, bitplane) self.update() - def scroll_right(self): + def scroll_right(self, bitplane): """ Scroll the screen right 4 pixels. + + :param bitplane: the bitplane to scroll """ + if bitplane == 0: + return + mode_scale = 1 if self.mode == SCREEN_MODE_EXTENDED else 2 actual_lines = 4 * mode_scale * self.scale_factor - self.surface.scroll(actual_lines, 0) - self.surface.fill(self.pixel_colors[0], (0, 0, actual_lines, self.height * mode_scale * self.scale_factor)) - self.update() + + if bitplane == 3: + self.surface.scroll(actual_lines, 0) + self.surface.fill(self.pixel_colors[0], (0, 0, actual_lines, self.height * mode_scale * self.scale_factor)) + self.update() + return + + max_x = self.get_width() + max_y = self.get_height() + + # Blank out any pixels in the right vertical lines that we will copy to + for x in range(max_x - 4, max_x): + for y in range(max_y): + self.draw_pixel(x, y, False, bitplane) + + # Start copying pixels from the left to the right and shift by 4 pixels + for x in range(max_x - 4 - 1, -1, -1): + for y in range(max_y): + current_pixel = self.get_pixel(x, y, bitplane) + self.draw_pixel(x, y, False, bitplane) + self.draw_pixel(x + 4, y, current_pixel, bitplane) + + # Blank out any pixels in the left 4 vertical lines + for x in range(4): + for y in range(max_y): + self.draw_pixel(x, y, False, bitplane) # E N D O F F I L E ######################################################## diff --git a/test/test_chip8cpu.py b/test/test_chip8cpu.py index 2f88938..f71b520 100644 --- a/test/test_chip8cpu.py +++ b/test/test_chip8cpu.py @@ -689,7 +689,7 @@ def test_decrement_timers_does_not_go_negative(self): def test_clear_screen(self): self.cpu.operand = 0xE0 self.cpu.clear_return() - self.screen.clear_screen.assert_called_with() + self.screen.clear_screen.assert_called_with(1) def test_clear_return_from_subroutine(self): self.cpu.operand = 0xEE @@ -811,10 +811,16 @@ def test_misc_routines_raises_exception_on_unknown_op_codes(self): self.cpu.misc_routines() self.assertEqual("Unknown op-code: F0FF", str(context.exception)) + def test_save_skip_routines_raises_exception_on_unknown_op_codes(self): + self.cpu.operand = 0x50FF + with self.assertRaises(UnknownOpCodeException) as context: + self.cpu.misc_routines() + self.assertEqual("Unknown op-code: 50FF", str(context.exception)) + def test_scroll_down_called(self): self.cpu.operand = 0x00C4 self.cpu.clear_return() - self.screen.scroll_down.assert_called_with(4) + self.screen.scroll_down.assert_called_with(4, 1) def test_scroll_right_called(self): self.cpu.operand = 0x00FB @@ -859,17 +865,17 @@ def test_draw_sprite_draws_correct_sprite(self): screen_mock.width = 64 self.cpu = Chip8CPU(screen_mock) self.cpu.memory[0] = 0xAA - self.cpu.draw_normal(0, 0, 1) + self.cpu.draw_normal(0, 0, 1, 1) with patch('chip8.screen.Chip8Screen.draw_pixel'): screen_mock.draw_pixel.assert_has_calls([ - call(0, 0, 1), - call(1, 0, 0), - call(2, 0, 1), - call(3, 0, 0), - call(4, 0, 1), - call(5, 0, 0), - call(6, 0, 1), - call(7, 0, 0) + call(0, 0, 1, 1), + call(1, 0, 0, 1), + call(2, 0, 1, 1), + call(3, 0, 0, 1), + call(4, 0, 1, 1), + call(5, 0, 0, 1), + call(6, 0, 1, 1), + call(7, 0, 0, 1) ]) def test_draw_sprite_turns_off_pixels(self): @@ -880,26 +886,26 @@ def test_draw_sprite_turns_off_pixels(self): screen_mock.width = 64 self.cpu = Chip8CPU(screen_mock) self.cpu.memory[0] = 0xAA - self.cpu.draw_normal(0, 0, 1) - self.cpu.draw_normal(0, 0, 1) + self.cpu.draw_normal(0, 0, 1, 1) + self.cpu.draw_normal(0, 0, 1, 1) with patch('chip8.screen.Chip8Screen.draw_pixel'): screen_mock.draw_pixel.assert_has_calls([ - call(0, 0, 1), - call(1, 0, 0), - call(2, 0, 1), - call(3, 0, 0), - call(4, 0, 1), - call(5, 0, 0), - call(6, 0, 1), - call(7, 0, 0), - call(0, 0, 0), - call(1, 0, 0), - call(2, 0, 0), - call(3, 0, 0), - call(4, 0, 0), - call(5, 0, 0), - call(6, 0, 0), - call(7, 0, 0) + call(0, 0, 1, 1), + call(1, 0, 0, 1), + call(2, 0, 1, 1), + call(3, 0, 0, 1), + call(4, 0, 1, 1), + call(5, 0, 0, 1), + call(6, 0, 1, 1), + call(7, 0, 0, 1), + call(0, 0, 0, 1), + call(1, 0, 0, 1), + call(2, 0, 0, 1), + call(3, 0, 0, 1), + call(4, 0, 0, 1), + call(5, 0, 0, 1), + call(6, 0, 0, 1), + call(7, 0, 0, 1) ]) def test_draw_sprite_does_not_turn_off_pixels(self): @@ -910,27 +916,27 @@ def test_draw_sprite_does_not_turn_off_pixels(self): screen_mock.width = 64 self.cpu = Chip8CPU(screen_mock) self.cpu.memory[0] = 0xAA - self.cpu.draw_normal(0, 0, 1) + self.cpu.draw_normal(0, 0, 1, 1) self.cpu.memory[0] = 0x55 - self.cpu.draw_normal(0, 0, 1) + self.cpu.draw_normal(0, 0, 1, 1) with patch('chip8.screen.Chip8Screen.draw_pixel'): screen_mock.draw_pixel.assert_has_calls([ - call(0, 0, 1), - call(1, 0, 0), - call(2, 0, 1), - call(3, 0, 0), - call(4, 0, 1), - call(5, 0, 0), - call(6, 0, 1), - call(7, 0, 0), - call(0, 0, 1), - call(1, 0, 1), - call(2, 0, 1), - call(3, 0, 1), - call(4, 0, 1), - call(5, 0, 1), - call(6, 0, 1), - call(7, 0, 1) + call(0, 0, 1, 1), + call(1, 0, 0, 1), + call(2, 0, 1, 1), + call(3, 0, 0, 1), + call(4, 0, 1, 1), + call(5, 0, 0, 1), + call(6, 0, 1, 1), + call(7, 0, 0, 1), + call(0, 0, 1, 1), + call(1, 0, 1, 1), + call(2, 0, 1, 1), + call(3, 0, 1, 1), + call(4, 0, 1, 1), + call(5, 0, 1, 1), + call(6, 0, 1, 1), + call(7, 0, 1, 1) ]) def test_load_index_with_sprite(self): @@ -975,7 +981,7 @@ def test_store_subset_regs_one_two(self): self.cpu.v[1] = 5 self.cpu.v[2] = 6 self.cpu.index = 0x5000 - self.cpu.operand = 0xF122 + self.cpu.operand = 0x5122 self.cpu.store_subset_regs_in_memory() self.assertEqual(5, self.cpu.memory[0x5000]) self.assertEqual(6, self.cpu.memory[0x5001]) @@ -984,7 +990,7 @@ def test_store_subset_regs_one_one(self): self.cpu.v[1] = 5 self.cpu.v[2] = 6 self.cpu.index = 0x5000 - self.cpu.operand = 0xF112 + self.cpu.operand = 0x5112 self.cpu.store_subset_regs_in_memory() self.assertEqual(5, self.cpu.memory[0x5000]) self.assertEqual(0, self.cpu.memory[0x5001]) @@ -994,7 +1000,7 @@ def test_store_subset_regs_three_one(self): self.cpu.v[2] = 6 self.cpu.v[3] = 7 self.cpu.index = 0x5000 - self.cpu.operand = 0xF312 + self.cpu.operand = 0x5312 self.cpu.store_subset_regs_in_memory() self.assertEqual(7, self.cpu.memory[0x5000]) self.assertEqual(6, self.cpu.memory[0x5001]) @@ -1005,7 +1011,7 @@ def test_store_subset_regs_integration(self): self.cpu.v[2] = 6 self.cpu.v[3] = 7 self.cpu.index = 0x5000 - self.cpu.memory[0x0200] = 0xF3 + self.cpu.memory[0x0200] = 0x53 self.cpu.memory[0x0201] = 0x12 self.cpu.execute_instruction() self.assertEqual(7, self.cpu.memory[0x5000]) @@ -1016,7 +1022,7 @@ def test_readsubset_regs_one_two(self): self.cpu.v[1] = 5 self.cpu.v[2] = 6 self.cpu.index = 0x5000 - self.cpu.operand = 0xF123 + self.cpu.operand = 0x5123 self.cpu.memory[0x5000] = 7 self.cpu.memory[0x5001] = 8 self.cpu.read_subset_regs_in_memory() @@ -1027,7 +1033,7 @@ def test_read_subset_regs_one_one(self): self.cpu.v[1] = 5 self.cpu.v[2] = 6 self.cpu.index = 0x5000 - self.cpu.operand = 0xF113 + self.cpu.operand = 0x5113 self.cpu.memory[0x5000] = 7 self.cpu.memory[0x5001] = 8 self.cpu.read_subset_regs_in_memory() @@ -1039,7 +1045,7 @@ def test_read_subset_regs_three_one(self): self.cpu.v[2] = 6 self.cpu.v[3] = 7 self.cpu.index = 0x5000 - self.cpu.operand = 0xF313 + self.cpu.operand = 0x5313 self.cpu.memory[0x5000] = 8 self.cpu.memory[0x5001] = 9 self.cpu.memory[0x5002] = 10 @@ -1053,7 +1059,7 @@ def test_read_subset_regs_integration(self): self.cpu.v[2] = 6 self.cpu.v[3] = 7 self.cpu.index = 0x5000 - self.cpu.memory[0x0200] = 0xF3 + self.cpu.memory[0x0200] = 0x53 self.cpu.memory[0x0201] = 0x13 self.cpu.memory[0x5000] = 8 self.cpu.memory[0x5001] = 9 diff --git a/test/test_chip8screen.py b/test/test_chip8screen.py index 336c6f3..60f3335 100644 --- a/test/test_chip8screen.py +++ b/test/test_chip8screen.py @@ -8,9 +8,7 @@ import unittest -from chip8.screen import ( - Chip8Screen, SCREEN_MODE_NORMAL -) +from chip8.screen import Chip8Screen # C L A S S E S ############################################################### @@ -43,53 +41,264 @@ def test_all_pixels_off_on_screen_init(self): self.screen.init_display() for x_pos in range(64): for y_pos in range(32): - self.assertEqual(0, self.screen.get_pixel(x_pos, y_pos)) + self.assertEqual(0, self.screen.get_pixel(x_pos, y_pos, 1)) + self.assertEqual(0, self.screen.get_pixel(x_pos, y_pos, 2)) - def test_write_pixel_turns_on_pixel(self): + def test_get_pixel_on_bitplane_0_returns_false(self): self.screen.init_display() for xpos in range(64): for ypos in range(32): - self.screen.draw_pixel(xpos, ypos, 1) - self.assertEqual(1, self.screen.get_pixel(xpos, ypos)) + self.screen.draw_pixel(xpos, ypos, 1, 1) + self.screen.draw_pixel(xpos, ypos, 1, 2) + self.assertFalse(self.screen.get_pixel(xpos, ypos, 0)) - def test_clear_screen_clears_pixels(self): + def test_write_pixel_turns_on_pixel_on_bitplane(self): + self.screen.init_display() + for xpos in range(64): + for ypos in range(32): + self.screen.draw_pixel(xpos, ypos, 1, 1) + self.assertTrue(self.screen.get_pixel(xpos, ypos, 1)) + self.assertFalse(self.screen.get_pixel(xpos, ypos, 2)) + + def test_write_pixel_turns_on_pixel_on_both_bitplanes(self): + self.screen.init_display() + for xpos in range(64): + for ypos in range(32): + self.screen.draw_pixel(xpos, ypos, 1, 3) + self.assertTrue(self.screen.get_pixel(xpos, ypos, 1)) + self.assertTrue(self.screen.get_pixel(xpos, ypos, 2)) + + def test_write_pixel_does_nothing_on_bitplane_0(self): + self.screen.init_display() + for xpos in range(64): + for ypos in range(32): + self.screen.draw_pixel(xpos, ypos, 1, 0) + self.assertFalse(self.screen.get_pixel(xpos, ypos, 1)) + self.assertFalse(self.screen.get_pixel(xpos, ypos, 2)) + + def test_clear_screen_clears_pixels_on_bitplane_1(self): self.screen.init_display() for x_pos in range(64): for y_pos in range(32): - self.screen.draw_pixel(x_pos, y_pos, 1) - self.screen.clear_screen() + self.screen.draw_pixel(x_pos, y_pos, 1, 1) + self.screen.clear_screen(1) for x_pos in range(64): for y_pos in range(32): - self.assertEqual(0, self.screen.get_pixel(x_pos, y_pos)) - - def test_scroll_down(self): - self.screen.init_display() - self.screen.draw_pixel(0, 0, 1) - self.assertEqual(1, self.screen.get_pixel(0, 0)) - self.screen.scroll_down(1) - self.assertEqual(0, self.screen.get_pixel(0, 0)) - self.assertEqual(1, self.screen.get_pixel(0, 1)) - - def test_scroll_right(self): - self.screen.init_display() - self.screen.draw_pixel(0, 0, 1) - self.screen.scroll_right() - self.assertEqual(0, self.screen.get_pixel(0, 0)) - self.assertEqual(0, self.screen.get_pixel(1, 0)) - self.assertEqual(0, self.screen.get_pixel(2, 0)) - self.assertEqual(0, self.screen.get_pixel(3, 0)) - self.assertEqual(1, self.screen.get_pixel(4, 0)) - - @unittest.skip("scroll left test bug") - def test_scroll_left(self): - self.screen.init_display() - self.screen.draw_pixel(63, 0, 1) - self.screen.scroll_left() - self.assertEqual(0, self.screen.get_pixel(63, 0)) - self.assertEqual(0, self.screen.get_pixel(62, 0)) - self.assertEqual(0, self.screen.get_pixel(61, 0)) - self.assertEqual(0, self.screen.get_pixel(60, 0)) - self.assertEqual(1, self.screen.get_pixel(59, 0)) + self.assertFalse(self.screen.get_pixel(x_pos, y_pos, 1)) + self.assertFalse(self.screen.get_pixel(x_pos, y_pos, 2)) + + def test_clear_screen_clears_pixels_on_bitplane_1_only_when_both_set(self): + self.screen.init_display() + for x_pos in range(64): + for y_pos in range(32): + self.screen.draw_pixel(x_pos, y_pos, 1, 1) + self.screen.draw_pixel(x_pos, y_pos, 1, 2) + self.screen.clear_screen(1) + for x_pos in range(64): + for y_pos in range(32): + self.assertFalse(self.screen.get_pixel(x_pos, y_pos, 1)) + self.assertTrue(self.screen.get_pixel(x_pos, y_pos, 2)) + + def test_clear_screen_on_bitplane_0_does_nothing(self): + self.screen.init_display() + for xpos in range(64): + for ypos in range(32): + self.screen.draw_pixel(xpos, ypos, 1, 1) + self.screen.draw_pixel(xpos, ypos, 1, 2) + self.screen.clear_screen(0) + for xpos in range(64): + for ypos in range(32): + self.assertTrue(self.screen.get_pixel(xpos, ypos, 1)) + self.assertTrue(self.screen.get_pixel(xpos, ypos, 2)) + + def test_scroll_down_bitplane_0_does_nothing(self): + self.screen.init_display() + self.screen.draw_pixel(0, 0, 1, 1) + self.screen.draw_pixel(0, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.screen.scroll_down(1, 0) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.assertFalse(self.screen.get_pixel(0, 1, 1)) + self.assertFalse(self.screen.get_pixel(0, 1, 2)) + + def test_scroll_down_bitplane_1(self): + self.screen.init_display() + self.screen.draw_pixel(0, 0, 1, 1) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(0, 0, 2)) + self.screen.scroll_down(1, 1) + self.assertFalse(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(0, 0, 2)) + self.assertTrue(self.screen.get_pixel(0, 1, 1)) + self.assertFalse(self.screen.get_pixel(0, 1, 2)) + + def test_scroll_down_bitplane_1_both_pixels_active(self): + self.screen.init_display() + self.screen.draw_pixel(0, 0, 1, 1) + self.screen.draw_pixel(0, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.screen.scroll_down(1, 1) + self.assertFalse(self.screen.get_pixel(0, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.assertTrue(self.screen.get_pixel(0, 1, 1)) + self.assertFalse(self.screen.get_pixel(0, 1, 2)) + + def test_scroll_down_bitplane_3_both_pixels_active(self): + self.screen.init_display() + self.screen.draw_pixel(0, 0, 1, 1) + self.screen.draw_pixel(0, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.screen.scroll_down(1, 3) + self.assertFalse(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(0, 0, 2)) + self.assertTrue(self.screen.get_pixel(0, 1, 1)) + self.assertTrue(self.screen.get_pixel(0, 1, 2)) + + def test_scroll_right_bitplane_0_does_nothing(self): + self.screen.init_display() + self.screen.draw_pixel(0, 0, 1, 1) + self.screen.draw_pixel(0, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.screen.scroll_right(0) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(1, 0, 1)) + self.assertFalse(self.screen.get_pixel(2, 0, 1)) + self.assertFalse(self.screen.get_pixel(3, 0, 1)) + self.assertFalse(self.screen.get_pixel(4, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.assertFalse(self.screen.get_pixel(1, 0, 2)) + self.assertFalse(self.screen.get_pixel(2, 0, 2)) + self.assertFalse(self.screen.get_pixel(3, 0, 2)) + self.assertFalse(self.screen.get_pixel(4, 0, 2)) + + def test_scroll_right_bitplane_1(self): + self.screen.init_display() + self.screen.draw_pixel(0, 0, 1, 1) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(0, 0, 2)) + self.screen.scroll_right(1) + self.assertFalse(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(1, 0, 1)) + self.assertFalse(self.screen.get_pixel(2, 0, 1)) + self.assertFalse(self.screen.get_pixel(3, 0, 1)) + self.assertTrue(self.screen.get_pixel(4, 0, 1)) + self.assertFalse(self.screen.get_pixel(0, 0, 2)) + self.assertFalse(self.screen.get_pixel(1, 0, 2)) + self.assertFalse(self.screen.get_pixel(2, 0, 2)) + self.assertFalse(self.screen.get_pixel(3, 0, 2)) + self.assertFalse(self.screen.get_pixel(4, 0, 2)) + + def test_scroll_right_bitplane_1_both_pixels_active(self): + self.screen.init_display() + self.screen.draw_pixel(0, 0, 1, 1) + self.screen.draw_pixel(0, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.screen.scroll_right(1) + self.assertFalse(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(1, 0, 1)) + self.assertFalse(self.screen.get_pixel(2, 0, 1)) + self.assertFalse(self.screen.get_pixel(3, 0, 1)) + self.assertTrue(self.screen.get_pixel(4, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.assertFalse(self.screen.get_pixel(1, 0, 2)) + self.assertFalse(self.screen.get_pixel(2, 0, 2)) + self.assertFalse(self.screen.get_pixel(3, 0, 2)) + self.assertFalse(self.screen.get_pixel(4, 0, 2)) + + def test_scroll_right_bitplane_3_both_pixels_active(self): + self.screen.init_display() + self.screen.draw_pixel(0, 0, 1, 1) + self.screen.draw_pixel(0, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertTrue(self.screen.get_pixel(0, 0, 2)) + self.screen.scroll_right(3) + self.assertFalse(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(1, 0, 1)) + self.assertFalse(self.screen.get_pixel(2, 0, 1)) + self.assertFalse(self.screen.get_pixel(3, 0, 1)) + self.assertTrue(self.screen.get_pixel(4, 0, 1)) + self.assertFalse(self.screen.get_pixel(0, 0, 2)) + self.assertFalse(self.screen.get_pixel(1, 0, 2)) + self.assertFalse(self.screen.get_pixel(2, 0, 2)) + self.assertFalse(self.screen.get_pixel(3, 0, 2)) + self.assertTrue(self.screen.get_pixel(4, 0, 2)) + + def test_scroll_left_bitplane_0_does_nothing(self): + self.screen.init_display() + self.screen.draw_pixel(63, 0, 1, 1) + self.screen.draw_pixel(63, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(63, 0, 1)) + self.assertTrue(self.screen.get_pixel(63, 0, 2)) + self.screen.scroll_left(0) + self.assertTrue(self.screen.get_pixel(63, 0, 1)) + self.assertFalse(self.screen.get_pixel(62, 0, 1)) + self.assertFalse(self.screen.get_pixel(61, 0, 1)) + self.assertFalse(self.screen.get_pixel(60, 0, 1)) + self.assertFalse(self.screen.get_pixel(59, 0, 1)) + self.assertTrue(self.screen.get_pixel(63, 0, 2)) + self.assertFalse(self.screen.get_pixel(62, 0, 2)) + self.assertFalse(self.screen.get_pixel(61, 0, 2)) + self.assertFalse(self.screen.get_pixel(60, 0, 2)) + self.assertFalse(self.screen.get_pixel(59, 0, 2)) + + def test_scroll_left_bitplane_1(self): + self.screen.init_display() + self.screen.draw_pixel(63, 0, 1, 1) + self.assertTrue(self.screen.get_pixel(63, 0, 1)) + self.assertFalse(self.screen.get_pixel(63, 0, 2)) + self.screen.scroll_left(1) + self.assertFalse(self.screen.get_pixel(63, 0, 1)) + self.assertFalse(self.screen.get_pixel(62, 0, 1)) + self.assertFalse(self.screen.get_pixel(61, 0, 1)) + self.assertFalse(self.screen.get_pixel(60, 0, 1)) + self.assertTrue(self.screen.get_pixel(59, 0, 1)) + self.assertFalse(self.screen.get_pixel(63, 0, 2)) + self.assertFalse(self.screen.get_pixel(62, 0, 2)) + self.assertFalse(self.screen.get_pixel(61, 0, 2)) + self.assertFalse(self.screen.get_pixel(60, 0, 2)) + self.assertFalse(self.screen.get_pixel(59, 0, 2)) + + def test_scroll_left_bitplane_1_both_pixels_active(self): + self.screen.init_display() + self.screen.draw_pixel(63, 0, 1, 1) + self.screen.draw_pixel(63, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(63, 0, 1)) + self.assertTrue(self.screen.get_pixel(63, 0, 2)) + self.screen.scroll_left(1) + self.assertFalse(self.screen.get_pixel(63, 0, 1)) + self.assertFalse(self.screen.get_pixel(62, 0, 1)) + self.assertFalse(self.screen.get_pixel(61, 0, 1)) + self.assertFalse(self.screen.get_pixel(60, 0, 1)) + self.assertTrue(self.screen.get_pixel(59, 0, 1)) + self.assertTrue(self.screen.get_pixel(63, 0, 2)) + self.assertFalse(self.screen.get_pixel(62, 0, 2)) + self.assertFalse(self.screen.get_pixel(61, 0, 2)) + self.assertFalse(self.screen.get_pixel(60, 0, 2)) + self.assertFalse(self.screen.get_pixel(59, 0, 2)) + + def test_scroll_left_bitplane_3_both_pixels_active(self): + self.screen.init_display() + self.screen.draw_pixel(63, 0, 1, 1) + self.screen.draw_pixel(63, 0, 1, 2) + self.assertTrue(self.screen.get_pixel(63, 0, 1)) + self.assertTrue(self.screen.get_pixel(63, 0, 2)) + self.screen.scroll_left(3) + self.assertFalse(self.screen.get_pixel(63, 0, 1)) + self.assertFalse(self.screen.get_pixel(62, 0, 1)) + self.assertFalse(self.screen.get_pixel(61, 0, 1)) + self.assertFalse(self.screen.get_pixel(60, 0, 1)) + self.assertTrue(self.screen.get_pixel(59, 0, 1)) + self.assertFalse(self.screen.get_pixel(63, 0, 2)) + self.assertFalse(self.screen.get_pixel(62, 0, 2)) + self.assertFalse(self.screen.get_pixel(61, 0, 2)) + self.assertFalse(self.screen.get_pixel(60, 0, 2)) + self.assertTrue(self.screen.get_pixel(59, 0, 2)) def test_set_normal(self): self.screen.init_display() diff --git a/yac8e.py b/yac8e.py index bcfa639..51d0424 100755 --- a/yac8e.py +++ b/yac8e.py @@ -69,12 +69,12 @@ def parse_arguments(): dest="color_0", default="000000" ) parser.add_argument( - "--color_1", help="the hex color to use for bitplane 1 (default=666666)", - dest="color_1", default="666666" + "--color_1", help="the hex color to use for bitplane 1 (default=ff33cc)", + dest="color_1", default="ff33cc" ) parser.add_argument( - "--color_2", help="the hex color to use for bitplane 2 (default=BBBBBB)", - dest="color_2", default="BBBBBB" + "--color_2", help="the hex color to use for bitplane 2 (default=33ccff)", + dest="color_2", default="33ccff" ) parser.add_argument( "--color_3", help="the hex color to use for bitplane overlaps (default=FFFFFF)",