Mercurial > hg > tilerswift
view nes.py @ 121:bfced396acca draft
NES_ROM: style fix
Switch around the order of a statment so it look like the other branch
of the if.
author | Jordi Gutiérrez Hermoso <jordigh@octave.org> |
---|---|
date | Wed, 02 Oct 2019 09:09:16 -0400 |
parents | 0c40b4e8270e |
children | d3ee52820f6b |
line wrap: on
line source
from zipfile import ZipFile, BadZipFile from PyQt5 import QtGui as QG from colours import TILE_PALETTES, palette_to_qt from exceptions import ROMOpeningError class NES_ROM(object): def __init__(self, filename): self.filename = filename # To track circular rotation of the PRG bytes, in case the # tiles aren't aligned to 16-byte boundaries and the user # moves it. self.initial_position = 0 self.read_rom() def read_rom(self): if self.filename.lower().endswith('.nes'): with open(self.filename, 'rb') as rom: self.ines = rom.read() elif self.filename.lower().endswith('.zip'): try: with ZipFile(self.filename, 'r') as rom: nesfiles = [f for f in rom.filelist if not f.is_dir() and f.filename.lower().endswith(".nes")] if len(nesfiles) != 1: raise ROMOpeningError( "Zipfile does not contain exactly one NES file" ) self.ines = rom.open(nesfiles[0]).read() except BadZipFile as exc: raise ROMOpeningError(f"Problem opening zip file:\n{exc}") else: raise ROMOpeningError( f"Cannot deduce file type from file name:\n\n" f"{self.filename.split('/')[-1]}" ) self.header = self.ines[0:16] body = self.ines[16:] PRG_size = self.header[4]*16384 CHR_size = self.header[5]*8192 self.PRG = body[0:PRG_size] if CHR_size == 0: # No chr, just read the whole ROM as tiles self.CHR = None self.tile_data = list(body) else: self.CHR = body[PRG_size:PRG_size+CHR_size] self.tile_data = list(self.CHR) self.tiles = [Tile(idx, self.tile_data) for idx in range(0, len(self.tile_data)//16)] def update_tiles(self): for tile in self.tiles: tile.clear_caches() def rotate_tile_data(self, steps): """ Rotates the tile data by given number of steps, either positively or negatively. The maximum rotation in either direction is 8 steps. """ if steps > 0: steps = min(8 - self.initial_position, steps) elif steps < 0: steps = max(-8 - self.initial_position, steps) if steps == 0: return self.tile_data[:] = self.tile_data[steps:] + self.tile_data[:steps] self.initial_position += steps def byte_forward(self): self.rotate_tile_data(1) self.update_tiles() def byte_backward(self): self.rotate_tile_data(-1) self.update_tiles() class Tile(object): """ A tile is a view into a part of a NES ROM (either its PRG or its CHR). A tile just keeps an index into the ROM position by 16-byte offsets as well as individual palette information for this one view. """ default_palette = TILE_PALETTES["Primary"] def __init__(self, index, tile_data): super().__init__() self.index = index self.tile_data = tile_data self.clear_caches() self.set_palette(self.default_palette.copy()) def __repr__(self): return f"Tile(bytes={self.raw_tile}, palette={self.palette})" def __str__(self): num_to_ascii = {0: " ", 1: ".", 2: "o", 3: "#"} return "_"*10 + "\n" + "\n".join( "[" + "".join([num_to_ascii[val] for val in row]) + "]" for row in self.tile ) + "\n" + "-"*10 def clear_caches(self): self._raw_tile = None self._tile = None self._pixmap = None def set_palette(self, new_palette): self.palette = new_palette.copy() self.update_pixmap() def update_pixmap(self): img_data = bytes(sum(self.tile, [])) image = QG.QImage(img_data, 8, 8, QG.QImage.Format_Indexed8) image.setColorTable(palette_to_qt(self.palette)) self._pixmap = QG.QPixmap(image) def get_pixmap(self): if self._pixmap is None: self.update_pixmap() return self._pixmap def set_pixmap(self, pixmap): # Not implemented pass pixmap = property(get_pixmap, set_pixmap, clear_caches, "Pixmap representation of tile") def get_raw_tile(self): if self._raw_tile is None: self._raw_tile = self.tile_data[16*self.index:16*(self.index + 1)] return self._raw_tile def set_raw_tile(self, raw_tile): self.clear_caches() self.tile_data[16*self.index:16*(self.index + 1)] = raw_tile raw_tile = property(get_raw_tile, set_raw_tile, clear_caches, "Raw tile bytes as found in ROM") def get_tile(self): if self._tile is None: self._tile = self.parse_tile(self.raw_tile) return self._tile def set_tile(self, tile): self.clear_caches() self.raw_tile = self.unparse_tile(tile) tile = property(get_tile, set_tile, clear_caches, "A tile parsed as an 8x8 array of ints") @classmethod def parse_tile(cls, tile): """Given a raw tile's bytes, convert it to an 8x8 array of integers.""" lowplane, hiplane = tile[0:8], tile[8:16] rows = [] for lowbyte, hibyte in zip(lowplane, hiplane): row = [ ((lowbyte >> idx) & 1) + 2*((hibyte >> idx) & 1) for idx in range(7, -1, -1) ] rows.append(row) return rows @classmethod def unparse_tile(cls, rows): """Given an 8x8 array of integers, convert it to a raw tile's bytes.""" lowplane = bytes([int(''.join(str(num & 1) for num in row), 2) for row in rows]) hiplane = bytes([int(''.join(str((num & 2) >> 1) for num in row), 2) for row in rows]) return lowplane + hiplane