марио на питоне код
gym-super-mario-bros 7.3.2
pip install gym-super-mario-bros Copy PIP instructions
Released: Jun 12, 2020
Super Mario Bros. for OpenAI Gym
Navigation
Project links
Statistics
View statistics for this project via Libraries.io, or by using our public dataset on Google BigQuery
License: Free For Educational Use (Proprietary)
Tags OpenAI-Gym, NES, Super-Mario-Bros, Lost-Levels, Reinforcement-Learning-Environment
Maintainers
Classifiers
Project description
gym-super-mario-bros
An OpenAI Gym environment for Super Mario Bros. & Super Mario Bros. 2 (Lost Levels) on The Nintendo Entertainment System (NES) using the nes-py emulator.
Installation
The preferred installation of gym-super-mario-bros is from pip :
Usage
Python
NOTE: gym_super_mario_bros.make is just an alias to gym.make for convenience.
NOTE: remove calls to render in training code for a nontrivial speedup.
Command Line
gym_super_mario_bros features a command line interface for playing environments using either the keyboard, or uniform random movement.
Environments
These environments allow 3 attempts (lives) to make it through the 32 stages in the game. The environments only send reward-able game-play frames to agents; No cut-scenes, loading screens, etc. are sent from the NES emulator to an agent nor can an agent perform actions during these instances. If a cut-scene is not able to be skipped by hacking the NES’s RAM, the environment will lock the Python process until the emulator is ready for the next action.
Environment | Game | ROM | Screenshot |
---|---|---|---|
SuperMarioBros-v0 | SMB | standard | |
SuperMarioBros-v1 | SMB | downsample | |
SuperMarioBros-v2 | SMB | pixel | |
SuperMarioBros-v3 | SMB | rectangle | |
SuperMarioBros2-v0 | SMB2 | standard | |
SuperMarioBros2-v1 | SMB2 | downsample |
Individual Stages
These environments allow a single attempt (life) to make it through a single stage of the game.
Random Stage Selection
Info about the rewards and info returned by the step method.
Reward Function
The reward function assumes the objective of the game is to move as far right as possible (increase the agent’s x value), as fast as possible, without dying. To model this game, three separate variables compose the reward:
The info dictionary returned by the step method contains the following keys:
Key | Type | Description |
---|---|---|
coins | int | The number of collected coins |
flag_get | bool | True if Mario reached a flag or ax |
life | int | The number of lives left, i.e., |
score | int | The cumulative in-game score |
stage | int | The current stage, i.e., |
status | str | Mario’s status, i.e., |
time | int | The time left on the clock |
world | int | The current world, i.e., |
x_pos | int | Mario’s x position in the stage (from the left) |
y_pos | int | Mario’s y position in the stage (from the bottom) |
Citation
Please cite gym-super-mario-bros if you use it in your research.
Mario Game In Python With Source Code
Mario Game In Python With Source Code
The Mario Game In Python is written in Python, This Mario Game Code In Python is design in Graphical User Interface (GUI) that uses PyGame library. Talking about the gameplay, it’s a single player game, where the player (Mario) has to dodge fireballs coming out from the dragon. Each level comes with more difficulties, the area gets smaller and smaller as soon as there’s an increment in level. In this Super Mario Python Tutorial you can learn on How To Make Super Mario Game In Python.
A Mario Game Program In Python simple and clean GUI is provided for easy gameplay. The gameplay design is so simple that the user won’t find it difficult to use and understand. Different images are used in the development of this game project, the gaming environment is just like the Mario game.
Watch the video here to see the full running super mario game in python.
Anyway if you want level up your knowledge in programming especially games in python, try this new article I’ve made for you Code For Game in Python: Python Game Projects With Source Code
This Super Mario In Python also includes a downloadable Mario Game In Python source code for free, just find the downloadable source code below and click to start downloading.
To start creating a Mario Game In Python, make sure that you have PyCharm IDE installed in your computer.
By the way if you are new to python programming and you don’t know what would be the the Python IDE to use, I have here a list of Best Python IDE for Windows, Linux, Mac OS that will suit for you. I also have here How to Download and Install Latest Version of Python on Windows.
Steps on how to create a Mario Game In Python
You are free to copy the code given below and download the full source code below.
Пишем платформер на Python. Часть 2. Подчасть 1, подготовка к созданию редактора уровней
Привет, друзья!
Продолжаем разбираться с нашим МариоБоем. Начало тут, продолжение тут. В этой подчасти второй части мы сделаем приготовление для создания редактора уровней, а именно: добавим турбо режим бега герою, смертельно опасные платформы, движущиеся монстры, телепортеры, принцессу и парсер уровней, дабы во второй подчасти не отвлекаться на всё это.
Upgrade героя
Добавим нашему герою возможность ускоряться. Для этого немного изменим код метода update.
Для начала, добавим констант
Далее, добавим анимации движения влево — вправо в ускоренном режиме. Мы вставим те же картинки, но с другой скоростью смены кадров
Теперь займемся самим методом update
Добавим входной параметр running
Изменим обработку движений персонажа, добавив поведение при ускорении.
И в основном файле добавим обработку события нажатия левого шифта.
И не забываем добавить аргументы при вызове метода hero.update()
Смотрим результаты ( я изменил цвет фона на черный, брутальный цвет для брутального МариоБоя)
Без ускорения
Прыжок с ускорением
Смертельные шипы
Создаем класс, наследующийся от Platform.
Далее, добавим поведение героя при соприкосновении с ним. Для этого, добавим 2 метода в класс персонажа. Первый метод — поведение при смерти, второй — перемещение по указанным координатам(который пригодится нам еще раз чуть ниже)
Т.е. когда мы умираем, игра замирает на некоторое время, затем мы перемещаемся в начало уровня и играем дальше.
Ну и описываем само поведение при пересечении с блоком смерти в методе collide()
Теперь, в основном классе изменим уровень
И добавим создание блока смерти, если в уровне есть символ «*»
Результат:
Порталы
Какой современный сантехник обходится без телепорта? Так давайте и нашего героя не будем делать белой вороной.
Создаём новый тип блока. Работаем в файле blocks.py
Cперва добавляем константы
Затем создаем новый класс.
Тут нет ни чего нового. При создании задаются не только координаты расположения портала, но и координаты перемещения героя, при попадании в телепортер.
Далее, добавим нашему герою поведение при соприкосновении с порталом
И добавим один портал на карту. Только теперь будем описывать координаты вручную. Когда сделаем редактор уровней — будет легче.
Добавим еще одну группу спрайтов, которая будет содержать анимированные блоки
И создаем телепортер.
В конце, добавим вызов метода update() у всех анимированных спрайтов
Как-то так
Монстры
Страшные, передвигающиеся, смертельно опасные огоньки.
Отличие монстров от смертельных блоков в том, что монстры могут двигаться.
Будем работать в новом файле, дабы не запутаться. Назовем его очень оригинально — monsters.py
Создадим новый класс Monster. В нём нет ничего такого, чего мы не применяли ранее.
Содержимое всего файла
При создании монстра необходимо указать 6 аргументов: х, y — координаты, left — скорость перемещения по горизонтали, up — скорость перемещения по вертикали, maxLengthLeft — максимальное расстояние в одну сторону, которое может пройти монстр, maxLengthUp — аналогично предыдущему, но по вертикали.
И не забываем добавить импорт с файла monsters.py
И, конечно же, добавим создание монстра в основной файл.
Создадим еще одну группу спрайтов, в которую будем помещать наших монстриков.
Вопрос: Для чего нам еще одна группа? Почему не хватило предыдущей? Ведь в группе спрайтов animatedEntities мы вызываем метод update()
Ответ: В предыдущей группе мы вызываем метод update()без аргументов, а в группе monsters этот метод будет вызывать с аргументом.
Создаем самого монстра.
Смотрим на результат.
Принцесса
Дело чести любого сантехника — спасти принцессу.
Класс принцессы не содержит что-либо нам интересное, поэтому код его показывать не буду. Кто заинтересуется — искать в файле blocks.py
Нашему персонажу добавим свойство winner, по которому будем судить, что пора завершать уровень.
И внесем изменения в метод collide()
И далее, напишем код создания принцессы
Не забыв вставить символ «P» в уровень.
Смотрим
Уровень
Наконец-то мы добрались до парсинга уровня. Их мы будем держать в каталоге levels. Привожу пример уровня из файла 1.txt
Что мы тут видим? Ни чего такого, чего бы не рассматривали в этом посте (включая первую часть). Сперва генерирум статические платформы, посредствам символов «[«,»-«, «*»,»]»,»|»
Где «[« — показывает парсеру начало уровня
«]» — соответсвенно, конец уровня
«|» — конец строки
«-« — обычная платформа
«*» — шипованная платформа
Затем, в строчке «player 55 44» мы указываем начальные координаты нашего героя
«portal 128 512 900 35» — первые два числа — координаты портала, вторые — координаты перемещения
«monster 150 200 1 2 150 100» — первые два числа, аналогично, координаты монстра, затем, вторые два — скорость горизонтальная и вертикальная, и последние — максимальное расстояние в одну сторону по горизонтали и вертикали.
Как вы уже заметили, как порталов, так и монстров может быть столько, сколько вам захочется.
Символ «/» означает конец файла. Все данные, после него, считаны не будут.
Теперь, давайте, напишем сам парсер.
Работаем в основном файле.
Для начала, перенесем все массивы и группы из функции main() в тело основной программы
Затем, убираем уровень, переменная должна быть пустой. Так же, удаляем создание монстров и порталов.
И добавляем новую функцию
Не забываем вызвать эту функцию и указать переменные startX и startY как стартовые координаты нашему герою.
Пишем платформер на Python, используя pygame
Сразу оговорюсь, что здесь написано для самых маленькихначинающих.
Давно хотел попробовать себя в качестве игродела, и недавно выпал случай изучить Python и исполнить давнюю мечту.
Что такое платформер?
Платформер(platformer)— жанр компьютерных игр, в которых основной чертой игрового процесса является прыгание по платформам, лазанье по лестницам, собирание предметов, обычно необходимых для завершения уровня.
Вики
Одними из моих любимых игр данного жанра являются «Super Mario Brothers» и «Super Meat Boy». Давайте попробуем создать нечто среднее между ними.
Самое — самое начало.
Внимание! Используем python ветки 2.х, с 3.х обнаружены проблемы запуска нижеописанных скриптов!
Наверное, не только игры, да и все приложения, использующие pygame начинаются примерно так:
Игра будет «крутиться» в цикле ( while 1), каждую итерацию необходимо перерисовывать всё (фон, платформы, монстров, цифровые сообщения и т.д). Важно заметить, что рисование идет последовательно, т.е. если сперва нарисовать героя, а потом залить фон, то героя видно не будет, учтите это на будущее.
Запустив этот код, мы увидим окно, залитое зелененьким цветом.
(Картинка кликабельна)
Ну что же, начало положено, идём дальше.
Уровень.
А как без него? Под словом «уровень» будем подразумевать ограниченную область виртуального двумерного пространства, заполненную всякой — всячиной, и по которой будет передвигаться наш персонаж.
Для построения уровня создадим двумерный массив m на n. Каждая ячейка (m,n) будет представлять из себя прямоугольник. Прямоугольник может в себе что-то содержать, а может и быть пустым. Мы в прямоугольниках будем рисовать платформы.
Добавим еще константы
Затем добавим объявление уровня в функцию main
И в основной цикл добавим следующее:
Т.е. Мы перебираем двумерный массив level, и, если находим символ «-», то по координатам (x * PLATFORM_WIDTH, y * PLATFORM_HEIGHT), где x,y — индекс в массиве level
Запустив, мы увидим следующее:
Персонаж
Просто кубики на фоне — это очень скучно. Нам нужен наш персонаж, который будет бегать и прыгать по платформам.
Создаём класс нашего героя.
Для удобства, будем держать нашего персонажа в отдельном файле player.py
Что тут интересного?
Начнём с того, что мы создаём новый класс, наследуясь от класса pygame.sprite.Sprite, тем самым наследую все характеристики спрайта.
Cпрайт — это движущееся растровое изображение. Имеет ряд полезных методов и свойств.
self.rect = Rect(x, y, WIDTH, HEIGHT), в этой строчке мы создаем фактические границы нашего персонажа, прямоугольник, по которому мы будем не только перемещать героя, но и проверять его на столкновения. Но об этом чуть ниже.
Метод update(self, left, right)) используется для описания поведения объекта. Переопределяет родительский update(*args) → None. Может вызываться в группах спрайтов.
Метод draw(self, screen) используется для вывода персонажа на экран. Далее мы уберем этот метод и будем использовать более интересный способ отображения героя.
Добавим нашего героя в основную часть программы.
Перед определением уровня добавим определение героя и переменные его перемещения.
В проверку событий добавим следующее:
Т.е. Если нажали на клавишу «лево», то идём влево. Если отпустили — останавливаемся. Так же с кнопкой «право»
Само передвижение вызывается так: (добавляем после перерисовки фона и платформ)
Но, как мы видим, наш серый блок слишком быстро перемещается, добавим ограничение в количестве кадров в секунду. Для этого после определения уровня добавим таймер
И в начало основного цикла добавим следующее:
Завис в воздухе
Да, наш герой в безвыходном положении, он завис в воздухе.
Добавим гравитации и возможности прыгать.
И так, работаем в файле player.py
Добавим еще констант
В метод _init_ добавляем строки:
Добавляем входной аргумент в метод update
def update(self, left, right, up):
И в начало метода добавляем:
И перед строчкой self.rect.x += self.xvel
Добавляем
И добавим в основную часть программы:
После строчки left = right = False
Добавим переменную up
В проверку событий добавим
И изменим вызов метода update, добавив новый аргумент up:
hero.update(left, right)
на
Здесь мы создали силу гравитации, которая будет тянуть нас вниз, постоянно наращивая скорость, если мы не стоим на земле, и прыгать в полете мы не умеем. А мы пока не можем твердо встать на что-то, поэтому на следующей анимации наш герой падает далеко за границы видимости.
Встань обеими ногами на землю свою.
Как узнать, что мы на земле или другой твердой поверхности? Ответ очевиден — использовать проверку на пересечение, но для этого изменим создание платформ.
Создадим еще один файл blocks.py, и перенесем в него описание платформы.
Дальше создадим класс, наследуясь от pygame.sprite.Sprite
Тут нет ни чего нам уже не знакомого, идём дальше.
В основной файле произведем изменения, перед описанием массива level добавим
Группа спрайтов entities будем использовать для отображения всех элементов этой группы.
Массив platforms будем использовать для проверки на пересечение с платформой.
Т.е. создаём экземплр класса Platform, добавляем его в группу спрайтов entities и массив platforms. В entities, чтобы для каждого блока не писать логику отображения. В platforms добавили, чтобы потом проверить массив блоков на пересечение с игроком.
Дальше, весь код генерации уровня выносим из цикла.
И так же строчку
hero.draw(screen) # отображение
Заменим на
Запустив, мы увидим, что ни чего не изменилось. Верно. Ведь мы не проверяем нашего героя на столкновения. Начнём это исправлять.
Работаем в файле player.py
Удаляем метод draw, он нам больше не нужен. И добавляем новый метод collide
В этом методе происходит проверка на пересечение координат героя и платформ, если таковое имеется, то выше описанной логике происходит действие.
Ну, и для того, что бы это всё происходило, необходимо вызывать этот метод.
Изменим число аргументов для метода update, теперь он выглядит так:
И не забудьте изменить его вызов в основном файле.
Т.е. передвинули героя вертикально, проверили на пересечение по вертикали, передвинули горизонтально, снова проверили на пересечение по горизонтали.
Вот, что получится, когда запустим.
Фу[у]! Движущийся прямоугольник — не красиво!
Давайте немного приукрасим нашего МариоБоя.
Начнем с платформ. Для этого в файле blocks.py сделаем небольшие изменения.
Заменим заливку цветом на картинку, для этого строчку
self.image.fill(Color(PLATFORM_COLOR))
Заменим на
Мы загружаем картинку вместо сплошного цвета. Разумеется, файл «platform.png» должен находиться в папке «blocks», которая должна располагаться в каталоге с исходными кодами.
Вот, что получилось
Сперва добавим в блок констант.
Тут, думаю, понятно, анимация разных действий героя.
Теперь добавим следующее в метод __init__
Здесь для каждого действия мы создаем набор анимаций, и включаем их(т.е. Включаем смену кадров).
))
Каждый кадр имеет картинку и время показа.
Осталось в нужный момент показать нужную анимацию.
Добавим смену анимаций в метод update.
Вуаля!
Больше, нужно больше места
Ограничение в размере окна мы преодолеем созданием динамической камеры.
Для этого создадим класс Camera
Далее, добавим начальное конфигурирование камеры
Создадим экземпляр камеры, добавим перед основным циклом:
Мы создали внутри большого прямоугольника, размеры которого вычисляются так:
меньший прямоугольник, размером, идентичным размеру окна.
Меньший прямоугольник центрируется относительно главного персонажа(метод update), и все объекты рисуются в меньшем прямоугольнике (метод apply), за счет чего создаётся впечатление движения камеры.
Для работы вышеописанного, нужно изменить рисование объектов.
Заменим строчку
entities.draw(screen) # отображение
На
И перед ней добавим
Теперь можем изменить уровень.
Вот, собственно, и результат
Результат можно скачать, ссылка на GitHub
В следующей части, если будет востребовано сообществом, мы создадим свой генератор уровней с блэкджеком и шлюхами с разными типами платформ, монстрами, телепортами, и конечно же, принцессой.
upd pygame можно скачать отсюда, спасибо, Chris_Griffin за замечание
upd1 Вторая часть
Извлекаем уровни из Super Mario Bros с помощью Python
Введение
Для нового проекта мне понадобилось извлечь данные уровней из классической видеоигры 1985 года Super Mario Bros (SMB). Если конкретнее, то я хотел извлечь фоновую графику каждого уровня игры без интерфейса, подвижных спрайтов и т.п.
Разумеется, я просто мог склеить изображения из игры и, возможно, автоматизировать процесс с помощью техник машинного зрения. Но мне показался более интересным описанный ниже метод, позволяющий исследовать те элементы уровней, которые нельзя получить с помощью скриншотов.
На первом этапе проекта мы изучим язык ассемблера 6502 и написанный на Python эмулятор. Полный исходный код выложен здесь.
Анализ исходного кода
Реверс-инжиниринг любой программы намного проще, если есть её исходный код, а у нас имеются исходники SMB в виде 17 тысяч строк ассемблерного кода 6502 (процессора NES), опубликованных doppelganger. Поскольку Nintendo так и не выпустила официального релиза исходников, код был создан дизассемблированием машинного кода SMB, с мучительной расшифровкой значения каждой части, добавлением комментариев и осмысленных символьных названий.
Выполнив быстрый поиск по файлу, я нашёл нечто, похожее на нужные нам данные уровней:
Если вы незнакомы с ассемблером, то я объясню: всё это просто означает «вставить такой набор байтов в скомпилированную программу, а потом позволить другим частям программы ссылаться на него с помощью символа L_GroundArea6 ». Можно воспринимать этот фрагмент как массив, в котором каждый элемент является байтом.
Сокращённый граф вызовов для AreaParserCore
Процедура выполняет запись в MetatileBuffer : раздел памяти длиной 13 байтов, представляющий собой один столбец блоков в уровне, каждый байт которого обозначает отдельный блок. Метатайл (metatile) — это блок 16×16, из которого составляются фоны игры SMB:
Уровень с прямоугольниками, описанными вокруг метатайлов
Они называются метатайлами, потому что каждый состоит из четырёх тайлов размером 8×8 пикселей, но подробнее об этом ниже.
То, что декодер работает с заранее заданными объектами, объясняет маленький размер уровня: данные уровня должны ссылаться только на типы объектов и их расположение, например «расположить трубу в точке (20, 16), ряд блоков в точке (10, 5), …». Однако это означает, что для превращения сырых данных уровня в метатайлы требуется много кода.
Портирование такого объёма кода для создания собственного распаковщика уровней заняло бы слишком много времени, поэтому давайте попробуем другой подход.
py65emu
Если бы у нас был интерфейс между Python и языком ассемблера 6502, мы могли бы вызывать подпроцедуру AreaParserCore для каждого столбца уровня, а затем использовать более понятный Python для преобразования информации блоков в нужное изображение.
Тут на сцене появляется py65emu — лаконичный эмулятор 6502 с интерфейсом Python. Вот как в py65emu настраивается та же конфигурация памяти, что и в NES:
Стоит заметить, что это всего лишь эмулятор процессора NES: он не эмулирует другие аппаратные части, такие как PPU (Picture Processing Unit), поэтому его нельзя использовать для эмуляции всей игры. Однако его должно быть достаточно для вызова подпроцедуры парсинга, потому что она не использует никаких других аппаратных устройств, кроме ЦП и памяти.
Но перед этим нам нужно скомпилировать листинг на ассемблере в машинный код.
Как указано в исходном коде, ассемблер компилируется с помощью x816. x816 — это ассемблер 6502 под MS-DOS, используемый сообществом разработчиков самодельных игр (homebrew) для NES и ROM-хакеров. Он замечательно работает в DOSBox.
Наряду с ROM программы, который необходим для py65emu, ассемблер x816 создаёт символьный файл, привязывающий символы к расположению их в памяти в адресном пространстве ЦП. Вот фрагмент файла:
Для удобства я написал парсер символьного файла, который сопоставляет названия символов и адреса:
Подпроцедуры
Как сказано в представленном выше плане, мы хотим научиться вызывать подпроцедуру AreaParserCore из Python.
Чтобы понять механику подпроцедуры, давайте изучим короткую подпроцедуру и соответствующий ей вызов:
Замечательное свойство подпроцедур заключается в том, что можно создавать встроенные вызовы, то есть вызовы подпроцедур внутри подпроцедур. Адреса возврата будут добавляться в стек и извлекаться в правильном порядке, таким же образом, как это происходит с вызовами функций в языках высокого уровня.
Вот код для выполнения подпроцедуры из Python:
Однако мы кое о чём забыли: как передавать входные значения для этой подпроцедуры? Нам нужно сообщить процедуре, какой уровень мы хотим отрендерить и какой столбец нам нужно парсить.
Valgrind для NES?
На самом деле написать простую версию memcheck для py65emu очень легко:
Вот какие результаты даёт MMU в обёртке при вызове execute_subroutine(sym_file[‘AREAPARSERCORE’]) :
Uninitialized read! 0x0728 (BACKLOADINGFLAG):
Uninitialized read! 0x0742 (BACKGROUNDSCENERY):
Uninitialized read! 0x0741 (FOREGROUNDSCENERY):
Uninitialized read! 0x074e (AREATYPE):
Uninitialized read! 0x075f (WORLDNUMBER):
Uninitialized read! 0x0743 (CLOUDTYPEOVERRIDE):
Uninitialized read! 0x0727 (TERRAINCONTROL):
Uninitialized read! 0x0743 (CLOUDTYPEOVERRIDE):
Uninitialized read! 0x074e (AREATYPE):
.
Очевидно, что metatile_data явно соответствует информации фона.
Графика метатайлов
(Чтобы посмотреть конечный результат, можно сразу перейти к разделу «Соединяем всё вместе».)
Теперь давайте разберёмся, как превратить полученные числа метатайлов в настоящие изображения. Описанные ниже этапы придуманы благодаря анализу исходников и чтению документации с потрясающей Nesdev Wiki.
Чтобы понять, как рендерить каждый метатайл, нам сначала нужно поговорить о цветовых палитрах NES. PPU консоли NES способен в общем рендерить 64 разных цвета, однако чёрный цвет несколько раз дублируется (подробности см. в Nesdev):
Каждый уровень Mario может использовать для фона только 10 из этих 64 цветов, разделённых на на 4 четырёхцветных палитры; первый цвет всегда одинаков. Вот четыре палитры для World 1-1:
Давайте теперь рассмотрим пример номера метатайла, представленный в двоичном виде. Вот номер метатайла тайла камней с трещинами, который является землёй уровня World 1-1:
Индекс палитры говорит нам, какой палитрой пользоваться при рендеринге метатайла (в нашем случае палитрой 1). Индекс палитры также является индексом двух следующих массивов:
.db >Palette0_MTiles, >Palette1_MTiles, >Palette2_MTiles, >Palette3_MTiles
Сочетание этих двух массивов даёт нам 16-битный адрес, который в нашем примере указывает на Palette1_Mtiles :
Четыре записи этой строки на самом деле являются идентификаторами тайлов: каждый метатайл состоит из четырёх тайлов размером 8×8 пикселей, выстроенных в следующем порядке — верхний левый, нижний левый, верхний правый и нижний правый. Эти идентификаторы передаются непосредственно в PPU консоли NES. Идентификатор ссылается на 16 байтов данных в CHR-ROM консоли, а каждая запись начинается с адреса 0x1000 + 16 * :
0x1000 + 16 * 0xb4: 0b01111111 0x1000 + 16 * 0xb5: 0b11011110
0x1001 + 16 * 0xb4: 0b10000000 0x1001 + 16 * 0xb5: 0b01100001
0x1002 + 16 * 0xb4: 0b10000000 0x1002 + 16 * 0xb5: 0b01100001
0x1003 + 16 * 0xb4: 0b10000000 0x1003 + 16 * 0xb5: 0b01100001
0x1004 + 16 * 0xb4: 0b10000000 0x1004 + 16 * 0xb5: 0b01110001
0x1005 + 16 * 0xb4: 0b10000000 0x1005 + 16 * 0xb5: 0b01011110
0x1006 + 16 * 0xb4: 0b10000000 0x1006 + 16 * 0xb5: 0b01111111
0x1007 + 16 * 0xb4: 0b10000000 0x1007 + 16 * 0xb5: 0b01100001
0x1008 + 16 * 0xb4: 0b10000000 0x1008 + 16 * 0xb5: 0b01100001
0x1009 + 16 * 0xb4: 0b01111111 0x1009 + 16 * 0xb5: 0b11011111
0x100a + 16 * 0xb4: 0b01111111 0x100a + 16 * 0xb5: 0b11011111
0x100b + 16 * 0xb4: 0b01111111 0x100b + 16 * 0xb5: 0b11011111
0x100c + 16 * 0xb4: 0b01111111 0x100c + 16 * 0xb5: 0b11011111
0x100d + 16 * 0xb4: 0b01111111 0x100d + 16 * 0xb5: 0b11111111
0x100e + 16 * 0xb4: 0b01111111 0x100e + 16 * 0xb5: 0b11000001
0x100f + 16 * 0xb4: 0b01111111 0x100f + 16 * 0xb5: 0b11011111
0x1000 + 16 * 0xb6: 0b10000000 0x1000 + 16 * 0xb7: 0b01100001
0x1001 + 16 * 0xb6: 0b10000000 0x1001 + 16 * 0xb7: 0b01100001
0x1002 + 16 * 0xb6: 0b11000000 0x1002 + 16 * 0xb7: 0b11000001
0x1003 + 16 * 0xb6: 0b11110000 0x1003 + 16 * 0xb7: 0b11000001
0x1004 + 16 * 0xb6: 0b10111111 0x1004 + 16 * 0xb7: 0b10000001
0x1005 + 16 * 0xb6: 0b10001111 0x1005 + 16 * 0xb7: 0b10000001
0x1006 + 16 * 0xb6: 0b10000001 0x1006 + 16 * 0xb7: 0b10000011
0x1007 + 16 * 0xb6: 0b01111110 0x1007 + 16 * 0xb7: 0b11111110
0x1008 + 16 * 0xb6: 0b01111111 0x1008 + 16 * 0xb7: 0b11011111
0x1009 + 16 * 0xb6: 0b01111111 0x1009 + 16 * 0xb7: 0b11011111
0x100a + 16 * 0xb6: 0b11111111 0x100a + 16 * 0xb7: 0b10111111
0x100b + 16 * 0xb6: 0b00111111 0x100b + 16 * 0xb7: 0b10111111
0x100c + 16 * 0xb6: 0b01001111 0x100c + 16 * 0xb7: 0b01111111
0x100d + 16 * 0xb6: 0b01110001 0x100d + 16 * 0xb7: 0b01111111
0x100e + 16 * 0xb6: 0b01111111 0x100e + 16 * 0xb7: 0b01111111
0x100f + 16 * 0xb6: 0b11111111 0x100f + 16 * 0xb7: 0b01111111
CHR-ROM — это фрагмент памяти read-only, к которому может получать доступ только PPU. Он отделён от PRG-ROM, в котором хранится код программы. Поэтому приведённые выше данные отсутствуют в исходном коде и их нужно получать из дампа ROM игры.
16 байта для каждого тайла составляют 2-битный тайл размером 8×8: первый бит — это первые 8 байтов, а второй — вторые 8 байтов:
21111111 13211112
12222222 23122223
12222222 23122223
12222222 23122223
12222222 23132223
12222222 23233332
12222222 23111113
12222222 23122223
12222222 23122223
12222222 23122223
33222222 31222223
11332222 31222223
12113333 12222223
12221113 12222223
12222223 12222233
23333332 13333332
Выполняем привязку этих данных к палитре 1:
…и объединяем куски:
Наконец мы получили отрендеренный тайл.
Соединяем всё вместе
Повторив эту процедуру для каждого метатайла, мы получим полностью отрендеренный уровень.