Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import pygame
- import random
- import sys
- import time
- import numpy as np
- import tkinter as tk
- from tkinter import filedialog
- import os
- class Chip8:
- def __init__(self):
- self.memory = bytearray(4096)
- self.V = bytearray(16)
- self.I = 0
- self.pc = 0x200
- self.stack = []
- self.delay_timer = 0
- self.sound_timer = 0
- self.keypad = [0] * 16
- self.opcode = 0
- self.draw_flag = False
- self.is_super_chip8 = False
- self.is_xo_chip = False
- self.high_res = False
- self.display_planes = [np.zeros((64, 128), dtype=np.uint8) for _ in range(2)]
- self.flag_registers = bytearray(16)
- self.plane_mask = 1
- self.audio_buffer = bytearray(16)
- self.pitch = 4000
- self.font = bytes([
- 0xF0, 0x90, 0x90, 0x90, 0xF0, # 0
- 0x20, 0x60, 0x20, 0x20, 0x70, # 1
- 0xF0, 0x10, 0xF0, 0x80, 0xF0, # 2
- 0xF0, 0x10, 0xF0, 0x10, 0xF0, # 3
- 0x90, 0x90, 0xF0, 0x10, 0x10, # 4
- 0xF0, 0x80, 0xF0, 0x10, 0xF0, # 5
- 0xF0, 0x80, 0xF0, 0x90, 0xF0, # 6
- 0xF0, 0x10, 0x20, 0x40, 0x40, # 7
- 0xF0, 0x90, 0xF0, 0x90, 0xF0, # 8
- 0xF0, 0x90, 0xF0, 0x10, 0xF0, # 9
- 0xF0, 0x90, 0xF0, 0x90, 0x90, # A
- 0xE0, 0x90, 0xE0, 0x90, 0xE0, # B
- 0xF0, 0x80, 0x80, 0x80, 0xF0, # C
- 0xE0, 0x90, 0x90, 0x90, 0xE0, # D
- 0xF0, 0x80, 0xF0, 0x80, 0xF0, # E
- 0xF0, 0x80, 0xF0, 0x80, 0x80 # F
- ])
- self.memory[:len(self.font)] = self.font
- self.big_font = bytes([
- 0x7C, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x7C, 0x00, # 0
- 0x08, 0x18, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3C, 0x00, # 1
- 0x7C, 0x82, 0x02, 0x02, 0x04, 0x18, 0x20, 0x40, 0xFE, 0x00, # 2
- 0x7C, 0x82, 0x02, 0x02, 0x3C, 0x02, 0x02, 0x82, 0x7C, 0x00, # 3
- 0x84, 0x84, 0x84, 0x84, 0xFE, 0x04, 0x04, 0x04, 0x04, 0x00, # 4
- 0xFE, 0x80, 0x80, 0x80, 0xFC, 0x02, 0x02, 0x82, 0x7C, 0x00, # 5
- 0x7C, 0x82, 0x80, 0x80, 0xFC, 0x82, 0x82, 0x82, 0x7C, 0x00, # 6
- 0xFE, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x20, 0x00, # 7
- 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x82, 0x82, 0x82, 0x7C, 0x00, # 8
- 0x7C, 0x82, 0x82, 0x82, 0x7E, 0x02, 0x02, 0x82, 0x7C, 0x00, # 9
- 0x38, 0x44, 0x82, 0x82, 0x82, 0xFE, 0x82, 0x82, 0x82, 0x00, # A
- 0xFC, 0x82, 0x82, 0x82, 0xFC, 0x82, 0x82, 0x82, 0xFC, 0x00, # B
- 0x7C, 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x82, 0x7C, 0x00, # C
- 0xFC, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0xFC, 0x00, # D
- 0xFE, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, 0xFE, 0x00, # E
- 0xFE, 0x80, 0x80, 0x80, 0xF8, 0x80, 0x80, 0x80, 0x80, 0x00 # F
- ])
- self.memory[len(self.font):len(self.font) + len(self.big_font)] = self.big_font
- self.opcode_table = {
- 0x0000: self._0xxx,
- 0x1000: self._1nnn,
- 0x2000: self._2nnn,
- 0x3000: self._3xnn,
- 0x4000: self._4xnn,
- 0x5000: self._5xy0,
- 0x6000: self._6xnn,
- 0x7000: self._7xnn,
- 0x8000: self._8xxx,
- 0x9000: self._9xy0,
- 0xA000: self._Annn,
- 0xB000: self._Bnnn,
- 0xC000: self._Cxnn,
- 0xD000: self._Dxyn,
- 0xE000: self._Exxx,
- 0xF000: self._Fxxx,
- }
- def load_rom(self, filename):
- with open(filename, 'rb') as f:
- rom_data = f.read()
- self.memory[0x200:0x200 + len(rom_data)] = rom_data
- self.is_super_chip8 = self._detect_super_chip8(rom_data)
- self.is_xo_chip = self._detect_xo_chip(rom_data)
- def _detect_super_chip8(self, rom_data):
- return any(opcode in [0x00FD, 0x00FE, 0x00FF] or (opcode & 0xF000) == 0xF000 and (opcode & 0x00F0) == 0x0030
- for opcode in ((rom_data[i] << 8) | rom_data[i + 1] for i in range(0, len(rom_data), 2)))
- def _detect_xo_chip(self, rom_data):
- return any((opcode & 0xF000) == 0xF000 and (opcode & 0x00FF) in [0x00, 0x01, 0x02, 0x3A]
- for opcode in ((rom_data[i] << 8) | rom_data[i + 1] for i in range(0, len(rom_data), 2)))
- def emulate_cycle(self):
- self.opcode = (self.memory[self.pc] << 8) | self.memory[self.pc + 1]
- self.pc += 2
- op_group = self.opcode & 0xF000
- self.opcode_table[op_group]()
- def _0xxx(self):
- if self.opcode == 0x00E0:
- self.display_planes = [np.zeros((64, 128), dtype=np.uint8) for _ in range(2)]
- self.draw_flag = True
- elif self.opcode == 0x00EE:
- if self.stack:
- self.pc = self.stack.pop()
- else:
- print("Warning: Attempted to return from an empty stack. Ignoring instruction.")
- elif self.is_super_chip8 or self.is_xo_chip:
- if self.opcode == 0x00FB:
- self._scroll_right()
- elif self.opcode == 0x00FC:
- self._scroll_left()
- elif self.opcode == 0x00FD:
- sys.exit()
- elif self.opcode == 0x00FE:
- self._toggle_high_res(False)
- elif self.opcode == 0x00FF:
- self._toggle_high_res(True)
- elif (self.opcode & 0xFFF0) == 0x00C0:
- self._scroll_down(self.opcode & 0x000F)
- elif (self.opcode & 0xFFF0) == 0x00D0 and self.is_xo_chip:
- self._scroll_up(self.opcode & 0x000F)
- def _1nnn(self):
- self.pc = self.opcode & 0x0FFF
- def _2nnn(self):
- self.stack.append(self.pc)
- self.pc = self.opcode & 0x0FFF
- def _3xnn(self):
- x = (self.opcode & 0x0F00) >> 8
- nn = self.opcode & 0x00FF
- if self.V[x] == nn:
- self.pc += 2
- def _4xnn(self):
- x = (self.opcode & 0x0F00) >> 8
- nn = self.opcode & 0x00FF
- if self.V[x] != nn:
- self.pc += 2
- def _5xy0(self):
- x = (self.opcode & 0x0F00) >> 8
- y = (self.opcode & 0x00F0) >> 4
- n = self.opcode & 0x000F
- if n == 0x0:
- if self.V[x] == self.V[y]:
- self.pc += 2
- elif self.is_xo_chip:
- if n == 0x2: # save vx - vy
- self.memory[self.I:self.I + y - x + 1] = self.V[x:y + 1]
- elif n == 0x3: # load vx - vy
- self.V[x:y + 1] = self.memory[self.I:self.I + y - x + 1]
- def _6xnn(self):
- x = (self.opcode & 0x0F00) >> 8
- nn = self.opcode & 0x00FF
- self.V[x] = nn
- def _7xnn(self):
- x = (self.opcode & 0x0F00) >> 8
- nn = self.opcode & 0x00FF
- self.V[x] = (self.V[x] + nn) & 0xFF
- def _8xxx(self):
- x = (self.opcode & 0x0F00) >> 8
- y = (self.opcode & 0x00F0) >> 4
- n = self.opcode & 0x000F
- if n == 0x0:
- self.V[x] = self.V[y]
- elif n == 0x1:
- self.V[x] |= self.V[y]
- elif n == 0x2:
- self.V[x] &= self.V[y]
- elif n == 0x3:
- self.V[x] ^= self.V[y]
- elif n == 0x4:
- sum = self.V[x] + self.V[y]
- self.V[0xF] = 1 if sum > 0xFF else 0
- self.V[x] = sum & 0xFF
- elif n == 0x5:
- self.V[0xF] = 1 if self.V[x] > self.V[y] else 0
- self.V[x] = (self.V[x] - self.V[y]) & 0xFF
- elif n == 0x6:
- self.V[0xF] = self.V[x] & 0x1
- self.V[x] >>= 1
- elif n == 0x7:
- self.V[0xF] = 1 if self.V[y] > self.V[x] else 0
- self.V[x] = (self.V[y] - self.V[x]) & 0xFF
- elif n == 0xE:
- self.V[0xF] = (self.V[x] & 0x80) >> 7
- self.V[x] = (self.V[x] << 1) & 0xFF
- def _9xy0(self):
- x = (self.opcode & 0x0F00) >> 8
- y = (self.opcode & 0x00F0) >> 4
- if self.V[x] != self.V[y]:
- self.pc += 2
- def _Annn(self):
- self.I = self.opcode & 0x0FFF
- def _Bnnn(self):
- self.pc = (self.opcode & 0x0FFF) + self.V[0]
- def _Cxnn(self):
- x = (self.opcode & 0x0F00) >> 8
- nn = self.opcode & 0x00FF
- self.V[x] = random.randint(0, 255) & nn
- def _Dxyn(self):
- x = self.V[(self.opcode & 0x0F00) >> 8]
- y = self.V[(self.opcode & 0x00F0) >> 4]
- n = self.opcode & 0x000F
- self.V[0xF] = 0
- if n == 0 and (self.high_res or self.is_xo_chip):
- sprite = np.frombuffer(self.memory[self.I:self.I + 32], dtype=np.uint8).reshape(16, 2)
- sprite = np.unpackbits(sprite, axis=1).reshape(16, 16)
- height, width = 16, 16
- else:
- sprite = np.unpackbits(np.frombuffer(self.memory[self.I:self.I + n], dtype=np.uint8), axis=0).reshape(n, 8)
- height, width = n, 8
- max_height = min(height, 64 - y if self.high_res else 32 - y)
- max_width = min(width, 128 - x if self.high_res else 64 - x)
- if max_height <= 0 or max_width <= 0:
- return # Nothing to draw if sprite is entirely off-screen
- for plane in range(2):
- if self.plane_mask & (1 << plane):
- display_slice = self.display_planes[plane][y:y+max_height, x:x+max_width]
- sprite_slice = sprite[:max_height, :max_width]
- collision = np.logical_and(display_slice, sprite_slice)
- display_slice ^= sprite_slice
- if collision.any():
- self.V[0xF] = 1
- self.draw_flag = True
- def _Exxx(self):
- x = (self.opcode & 0x0F00) >> 8
- if self.opcode & 0x00FF == 0x9E:
- if self.keypad[self.V[x]]:
- self.pc += 2
- elif self.opcode & 0x00FF == 0xA1:
- if not self.keypad[self.V[x]]:
- self.pc += 2
- def _Fxxx(self):
- x = (self.opcode & 0x0F00) >> 8
- if self.opcode & 0x00FF == 0x07:
- self.V[x] = self.delay_timer
- elif self.opcode & 0x00FF == 0x0A:
- key_pressed = False
- for i in range(16):
- if self.keypad[i]:
- self.V[x] = i
- key_pressed = True
- break
- if not key_pressed:
- self.pc -= 2
- elif self.opcode & 0x00FF == 0x15:
- self.delay_timer = self.V[x]
- elif self.opcode & 0x00FF == 0x18:
- self.sound_timer = self.V[x]
- elif self.opcode & 0x00FF == 0x1E:
- self.I += self.V[x]
- self.V[0xF] = 1 if self.I > 0xFFF else 0
- elif self.opcode & 0x00FF == 0x29:
- self.I = self.V[x] * 5
- elif self.opcode & 0x00FF == 0x30 and (self.is_super_chip8 or self.is_xo_chip):
- self.I = self.V[x] * 10 + len(self.font)
- elif self.opcode & 0x00FF == 0x33:
- self.memory[self.I] = self.V[x] // 100
- self.memory[self.I + 1] = (self.V[x] % 100) // 10
- self.memory[self.I + 2] = self.V[x] % 10
- elif self.opcode & 0x00FF == 0x55:
- self.memory[self.I:self.I + x + 1] = self.V[:x + 1]
- elif self.opcode & 0x00FF == 0x65:
- self.V[:x + 1] = self.memory[self.I:self.I + x + 1]
- elif self.opcode & 0x00FF == 0x75 and (self.is_super_chip8 or self.is_xo_chip):
- self.flag_registers[:x + 1] = self.V[:x + 1]
- elif self.opcode & 0x00FF == 0x85 and (self.is_super_chip8 or self.is_xo_chip):
- self.V[:x + 1] = self.flag_registers[:x + 1]
- elif self.is_xo_chip:
- if self.opcode & 0x00FF == 0x00:
- self.I = (self.memory[self.pc] << 8) | self.memory[self.pc + 1]
- self.pc += 2
- elif self.opcode & 0x00FF == 0x01:
- self.plane_mask = x
- elif self.opcode & 0x00FF == 0x02:
- self.audio_buffer = self.memory[self.I:self.I + 16]
- elif self.opcode & 0x00FF == 0x3A:
- self.pitch = int(4000 * 2**((self.V[x] - 64) / 48))
- def update_timers(self):
- if self.delay_timer > 0:
- self.delay_timer -= 1
- if self.sound_timer > 0:
- self.sound_timer -= 1
- def _toggle_high_res(self, enable):
- self.high_res = enable
- self.draw_flag = True
- def _scroll_down(self, n):
- for plane in range(2):
- if self.plane_mask & (1 << plane):
- self.display_planes[plane] = np.roll(self.display_planes[plane], n, axis=0)
- self.display_planes[plane][:n] = 0
- self.draw_flag = True
- def _scroll_up(self, n):
- for plane in range(2):
- if self.plane_mask & (1 << plane):
- self.display_planes[plane] = np.roll(self.display_planes[plane], -n, axis=0)
- self.display_planes[plane][-n:] = 0
- self.draw_flag = True
- def _scroll_right(self):
- for plane in range(2):
- if self.plane_mask & (1 << plane):
- self.display_planes[plane] = np.roll(self.display_planes[plane], 4, axis=1)
- self.display_planes[plane][:, :4] = 0
- self.draw_flag = True
- def _scroll_left(self):
- for plane in range(2):
- if self.plane_mask & (1 << plane):
- self.display_planes[plane] = np.roll(self.display_planes[plane], -4, axis=1)
- self.display_planes[plane][:, -4:] = 0
- self.draw_flag = True
- def generate_beep(duration_ms=100, freq=440, volume=0.5):
- sample_rate = 44100
- t = np.linspace(0, duration_ms / 1000, int(sample_rate * duration_ms / 1000), False)
- wave = np.sin(2 * np.pi * freq * t) * volume
- sound = np.asarray([wave, wave]).T.astype(np.float32)
- sound = np.ascontiguousarray(sound)
- return pygame.sndarray.make_sound(sound)
- def select_rom():
- root = tk.Tk()
- root.withdraw()
- file_path = filedialog.askopenfilename(
- title="Select CHIP-8, Super CHIP-8, or XO-CHIP ROM",
- filetypes=[("CHIP-8 ROMs", "*.ch8"), ("Super CHIP-8 ROMs", "*.sc8"), ("XO-CHIP ROMs", "*.xo8"), ("All files", "*.*")]
- )
- return file_path
- def draw_text(screen, text, pos, color=(255, 255, 255), font_size=20):
- font = pygame.font.Font(None, font_size)
- text_surface = font.render(text, True, color)
- screen.blit(text_surface, pos)
- def main():
- pygame.init()
- pygame.mixer.init(frequency=44100, size=-16, channels=2)
- screen = pygame.display.set_mode((640, 360))
- pygame.display.set_caption("nono CHIP-8")
- clock = pygame.time.Clock()
- draw_surface = pygame.Surface((640, 320))
- beep_sound = generate_beep()
- key_mapping = {
- pygame.K_1: 0x1, pygame.K_2: 0x2, pygame.K_3: 0x3, pygame.K_4: 0xC,
- pygame.K_q: 0x4, pygame.K_w: 0x5, pygame.K_e: 0x6, pygame.K_r: 0xD,
- pygame.K_a: 0x7, pygame.K_s: 0x8, pygame.K_d: 0x9, pygame.K_f: 0xE,
- pygame.K_z: 0xA, pygame.K_x: 0x0, pygame.K_c: 0xB, pygame.K_v: 0xF
- }
- chip8 = None
- emulation_speed = 500
- paused = False
- while True:
- screen.fill((0, 0, 0))
- if chip8 is None:
- draw_text(screen, "nono CHIP-8 (Coded by nonogamer9)", (240, 100))
- draw_text(screen, "Press SPACE to select a ROM", (180, 150))
- draw_text(screen, "Press ESC to quit", (220, 200))
- else:
- if chip8.draw_flag:
- draw_surface.fill((0, 0, 0))
- scale_x = 5 if chip8.high_res else 10
- scale_y = 5 if chip8.high_res else 10
- height = 64 if chip8.high_res else 32
- width = 128 if chip8.high_res else 64
- combined_display = np.logical_or(chip8.display_planes[0][:height, :width], chip8.display_planes[1][:height, :width])
- pixels = np.where(combined_display)
- for y, x in zip(*pixels):
- color = (255, 0, 0) if chip8.display_planes[0][y, x] else (0, 255, 0)
- if chip8.display_planes[0][y, x] and chip8.display_planes[1][y, x]:
- color = (255, 255, 0)
- pygame.draw.rect(draw_surface, color, (x * scale_x, y * scale_y, scale_x, scale_y))
- chip8.draw_flag = False
- screen.blit(draw_surface, (0, 0))
- draw_text(screen, f"Speed: {emulation_speed}", (10, 330), font_size=16)
- draw_text(screen, "Up/Down: Adjust speed", (200, 330), font_size=16)
- draw_text(screen, "P: Pause/Resume", (450, 330), font_size=16)
- if chip8.is_xo_chip:
- draw_text(screen, "XO-CHIP", (10, 10), font_size=16)
- elif chip8.is_super_chip8:
- draw_text(screen, "Super CHIP-8", (10, 10), font_size=16)
- else:
- draw_text(screen, "CHIP-8", (10, 10), font_size=16)
- pygame.display.flip()
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- pygame.quit()
- sys.exit()
- elif event.type == pygame.KEYDOWN:
- if event.key == pygame.K_ESCAPE:
- pygame.quit()
- sys.exit()
- elif event.key == pygame.K_SPACE and chip8 is None:
- rom_path = select_rom()
- if rom_path:
- chip8 = Chip8()
- chip8.load_rom(rom_path)
- elif event.key == pygame.K_p:
- paused = not paused
- elif event.key == pygame.K_UP:
- emulation_speed = min(1000, emulation_speed + 50)
- elif event.key == pygame.K_DOWN:
- emulation_speed = max(50, emulation_speed - 50)
- elif chip8 and event.key in key_mapping:
- chip8.keypad[key_mapping[event.key]] = 1
- elif event.type == pygame.KEYUP:
- if chip8 and event.key in key_mapping:
- chip8.keypad[key_mapping[event.key]] = 0
- if chip8 and not paused:
- for _ in range(10): # Run multiple instructions per frame
- chip8.emulate_cycle()
- chip8.update_timers()
- if chip8.sound_timer > 0:
- beep_sound.play()
- else:
- beep_sound.stop()
- clock.tick(60) # Cap at 60 FPS
- if __name__ == '__main__':
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement