diff --git a/chip8/cpu.py b/chip8/cpu.py index de18b58..5f1597f 100644 --- a/chip8/cpu.py +++ b/chip8/cpu.py @@ -317,6 +317,10 @@ def clear_return(self): num_lines = self.operand & 0x000F self.screen.scroll_down(num_lines, self.bitplane) self.last_op = f"Scroll Down {num_lines:01X}" + elif sub_operation == 0x00D0: + num_lines = self.operand & 0x000F + self.screen.scroll_up(num_lines, self.bitplane) + self.last_op = f"Scroll Up {num_lines:01X}" else: try: self.clear_routines[operation]() diff --git a/chip8/screen.py b/chip8/screen.py index 419caff..45bd92e 100644 --- a/chip8/screen.py +++ b/chip8/screen.py @@ -208,7 +208,7 @@ def scroll_down(self, num_lines, bitplane): for y in range(max_y - num_lines, max_y): self.draw_pixel(x, y, False, bitplane) - # Start copying pixels from the top to the bottom and shift by 4 pixels + # Start copying pixels from the top to the bottom and shift by n 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) @@ -220,6 +220,52 @@ def scroll_down(self, num_lines, bitplane): for y in range(num_lines): self.draw_pixel(x, y, False, bitplane) + def scroll_up(self, num_lines, bitplane): + """ + Scroll the screen up by num_lines. + + :param num_lines: the number of lines to scroll up + :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 + if bitplane == 3: + self.surface.scroll(0, -actual_lines) + self.surface.fill( + self.pixel_colors[0], + ( + 0, + self.height * mode_scale * self.scale_factor - actual_lines, + self.width * mode_scale * self.scale_factor, + 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 top n lines that we will copy to + for x in range(max_x): + for y in range(num_lines): + self.draw_pixel(x, y, False, bitplane) + + # Start copying pixels from the top to the bottom and shift up by n pixels + for x in range(max_x): + for y in range(num_lines, max_y): + 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 bottom num_lines horizontal lines + 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, bitplane): """ Scroll the screen left 4 pixels. diff --git a/test/test_chip8cpu.py b/test/test_chip8cpu.py index bbc8bde..cb4e756 100644 --- a/test/test_chip8cpu.py +++ b/test/test_chip8cpu.py @@ -852,6 +852,11 @@ def test_scroll_down_called(self): self.cpu.clear_return() self.screen.scroll_down.assert_called_with(4, 1) + def test_scroll_up_called(self): + self.cpu.operand = 0x00D4 + self.cpu.clear_return() + self.screen.scroll_up.assert_called_with(4, 1) + def test_scroll_right_called(self): self.cpu.operand = 0x00FB self.cpu.clear_return() diff --git a/test/test_chip8screen.py b/test/test_chip8screen.py index 60f3335..d5d6e49 100644 --- a/test/test_chip8screen.py +++ b/test/test_chip8screen.py @@ -123,6 +123,18 @@ def test_scroll_down_bitplane_0_does_nothing(self): self.assertFalse(self.screen.get_pixel(0, 1, 1)) self.assertFalse(self.screen.get_pixel(0, 1, 2)) + def test_scroll_up_bitplane_0_does_nothing(self): + self.screen.init_display() + self.screen.draw_pixel(0, 1, 1, 1) + self.screen.draw_pixel(0, 1, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 1, 1)) + self.assertTrue(self.screen.get_pixel(0, 1, 2)) + self.screen.scroll_up(1, 0) + 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_down_bitplane_1(self): self.screen.init_display() self.screen.draw_pixel(0, 0, 1, 1) @@ -134,6 +146,17 @@ def test_scroll_down_bitplane_1(self): self.assertTrue(self.screen.get_pixel(0, 1, 1)) self.assertFalse(self.screen.get_pixel(0, 1, 2)) + def test_scroll_up_bitplane_1(self): + self.screen.init_display() + self.screen.draw_pixel(0, 1, 1, 1) + self.assertTrue(self.screen.get_pixel(0, 1, 1)) + self.assertFalse(self.screen.get_pixel(0, 1, 2)) + self.screen.scroll_up(1, 1) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(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_both_pixels_active(self): self.screen.init_display() self.screen.draw_pixel(0, 0, 1, 1) @@ -146,6 +169,18 @@ def test_scroll_down_bitplane_1_both_pixels_active(self): self.assertTrue(self.screen.get_pixel(0, 1, 1)) self.assertFalse(self.screen.get_pixel(0, 1, 2)) + def test_scroll_up_bitplane_1_both_pixels_active(self): + self.screen.init_display() + self.screen.draw_pixel(0, 1, 1, 1) + self.screen.draw_pixel(0, 1, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 1, 1)) + self.assertTrue(self.screen.get_pixel(0, 1, 2)) + self.screen.scroll_up(1, 1) + self.assertTrue(self.screen.get_pixel(0, 0, 1)) + self.assertFalse(self.screen.get_pixel(0, 0, 2)) + self.assertFalse(self.screen.get_pixel(0, 1, 1)) + self.assertTrue(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) @@ -158,6 +193,18 @@ def test_scroll_down_bitplane_3_both_pixels_active(self): self.assertTrue(self.screen.get_pixel(0, 1, 1)) self.assertTrue(self.screen.get_pixel(0, 1, 2)) + def test_scroll_up_bitplane_3_both_pixels_active(self): + self.screen.init_display() + self.screen.draw_pixel(0, 1, 1, 1) + self.screen.draw_pixel(0, 1, 1, 2) + self.assertTrue(self.screen.get_pixel(0, 1, 1)) + self.assertTrue(self.screen.get_pixel(0, 1, 2)) + self.screen.scroll_up(1, 3) + 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_right_bitplane_0_does_nothing(self): self.screen.init_display() self.screen.draw_pixel(0, 0, 1, 1)