class Chip8Widget(QWidget): def __init__(self, machine: Machine, sound: bool=False, instruction_per_second: int = 500): super().__init__() self._machine = machine self.init_ui() self._sound = QSound('beep.wav') self._sound_support = sound self._machine_update_timer = Timer(interval=1.0 / instruction_per_second) self._machine_update_timer.add_handler(self._execute_instruction) self._machine_update_timer.start() self._machine_sound_delay_timer = Timer(interval=1.0 / 60) # 60 Hz self._machine_sound_delay_timer.add_handler(self._update_sound_delay) self._machine_sound_delay_timer.start() self._key_dict = { Qt.Key_1: 1, Qt.Key_2: 2, Qt.Key_3: 3, Qt.Key_4: 0xC, Qt.Key_Q: 4, Qt.Key_W: 5, Qt.Key_E: 6, Qt.Key_R: 0xD, Qt.Key_A: 7, Qt.Key_S: 8, Qt.Key_D: 9, Qt.Key_F: 0xE, Qt.Key_Z: 0xA, Qt.Key_X: 0x0, Qt.Key_C: 0xB, Qt.Key_V: 0xF } def init_ui(self): screen = Chip8ScreenWidget(self._machine.Screen, self) self.setWindowTitle('Chip 8') self.show() def keyPressEvent(self, event: QtGui.QKeyEvent): if event.key() in self._key_dict: self._machine.Keyboard.key_down(self._key_dict[event.key()]) def keyReleaseEvent(self, event: QtGui.QKeyEvent): if event.key() in self._key_dict: self._machine.Keyboard.key_up(self._key_dict[event.key()]) def _execute_instruction(self): self._machine.execute_next_instruction() def _update_sound_delay(self): if self._machine.SoundTimer.get_count() != 0 and self._sound_support: self._sound.play() self._machine.DelayTimer.decrease() self._machine.SoundTimer.decrease()
class Chip8ScreenWidget(QWidget): def __init__(self, screen: Screen, parent: QWidget = None): super().__init__(parent) self._screen = screen screen_size = QDesktopWidget().screenGeometry(-1) self._pixel_width = int(screen_size.width() * 1 / 100) self._pixel_height = self._pixel_width self.init_ui() self._draw_timer = Timer(interval=1.0 / 30) self._draw_timer.add_handler(self._draw_screen_event) self._draw_timer.start() def init_ui(self): self.setFixedSize(self._screen.width() * self._pixel_width, self._screen.height() * self._pixel_height) self.show() def paintEvent(self, e): qp = QPainter() qp.begin(self) self._draw_screen(qp) qp.end() def keyPressEvent(self, a0: QtGui.QKeyEvent): self.parent().keyPressEvent(a0) def _draw_screen_event(self): self.update() def _draw_screen(self, qp): qp.setPen(QColor(0, 0, 0)) for y in range(self._screen.height()): for x in range(self._screen.width()): self._draw_pixel(y, x, qp) def _draw_pixel(self, y, x, qp): qp.fillRect( x * self._pixel_width, y * self._pixel_height, self._pixel_width, self._pixel_height, QColor(0, 0, 0) if self._screen.get_pixel(y, x) == 0 else QColor( 255, 255, 255))
class Chip8MachineStateWidget(QWidget): def __init__(self, machine: Machine, parent: QWidget=None): super().__init__(parent) self._machine = machine self._init_ui() self._draw_timer = Timer(interval=1.0 / 30) self._draw_timer.add_handler(self._draw_state_event) self._draw_timer.start() self._instruction_factory = InstructionFactory() def _init_ui(self): screen_size = QDesktopWidget().screenGeometry(-1) self._pixel_width = int(screen_size.width() * 1 / 100) self._pixel_height = self._pixel_width self.setFixedHeight(self._pixel_height * 32) self.setFixedWidth(self._pixel_width * 35) self._text_size = int(self._pixel_height * 0.7) self.show() def paintEvent(self, e): qp = QPainter() qp.begin(self) self._draw_state(qp) qp.end() def _draw_state_event(self): self.update() def keyPressEvent(self, a0: QtGui.QKeyEvent): self.parent().keyPressEvent(a0) def _draw_state(self, qp): qp.setFont(QFont('Noto Sans', self._text_size)) t = self._text_size // 2 qp.drawText(0, self._text_size, 'V: {}'.format(list(map(to_hex, self._machine.VRegisters)))) qp.drawText(0, 2 * self._text_size + t, 'PC: {}, I: {}, DT: {}, ST: {}' .format(to_hex(self._machine.PC), to_hex(self._machine.AddressRegister), to_hex(self._machine.DelayTimer.get_count()), to_hex(self._machine.SoundTimer.get_count()))) qp.drawText(0, 3 * self._text_size + 2 * t, 'Stack: {}'.format(list(map(to_hex, self._machine.Stack.items())))) qp.drawText(0, 4 * self._text_size + 3 * t, 'Memory at 10-radius of PC:') y = 5 * self._text_size + 4 * t for i in range(-10, 11): index = self._machine.PC + i * 2 if index < 0 or index > self._machine.MemorySize: qp.drawText(0, y, '') continue opcode = self._get_opcode_at(index) try: instruction = self._instruction_factory.from_opcode(opcode) qp.drawText(0, y, '{}{} : {}, {} {} {}' .format('-> ' if i == 0 else ' ', to_hex(index), list(map(to_hex, opcode)), instruction.__class__.__name__, list((map(to_hex, instruction.arg_registers))), to_hex(instruction.arg_constant) if instruction.arg_constant is not None else '-')) except InstructionFactoryError: qp.drawText(0, y, '{}{} : {}, [unknown]' .format('-> ' if i == 0 else ' ', to_hex(index), list(map(to_hex, opcode)))) y += self._text_size + t qp.drawText(0, y, 'Machine blocked' if self._machine.Block else 'Machine not blocked') def _get_opcode_at(self, i): return bytearray([self._machine.Memory[i], self._machine.Memory[i + 1]])
class ProgressBar: def __init__(self, size, length=40): if length <= 2: raise ValueError('percent line length must be greater than 2') self.__size = size self.__current = 0 self.__timer = Timer() self.__max_len = 1 self.__length = length @property def length(self): return self.__length @property def timer(self): return self.__timer @property def elapsed_time(self): return self.__timer.elapsed def append(self, size): self.__current += size @property def current_size(self): return self.__current @property def size(self): return self.__size @property def bar(self): speed = self.__current / (self.elapsed_time + 1e-6) if self.__size is not None: percent = self.__current / self.__size * 100 true_length = self.__length - 2 filled_count = int(percent / 100 * true_length) bar = '[{}] {:.1f}% Speed: {:.2f} KB/s'.format( '#' * filled_count + ' ' * (true_length - filled_count), percent, speed / 1024) else: bar = 'Progressing with speed {:.2f} KB/s...'.format(speed / 1024) self.__max_len = max((self.__max_len, len(bar))) return bar @property def clearing_string(self): return ' ' * self.__max_len @property def statistic(self): if self.__size: return '{} of {} bytes from {:.2f} seconds'.format( self.__current, self.__size, self.elapsed_time) return '{} bytes from {:.2f} second'.format(self.__current, self.elapsed_time) def print_with_clearing(self, writer: Writer = None): if writer is None: return space = ' ' * self.__max_len bar = self.bar writer.write(f'\r{space}\r{bar}', end='') def __enter__(self): self.__timer.start() return self def __exit__(self, exc_type, exc_val, exc_tb): self.__timer.kill()