Skip to content

Commit

Permalink
Added CLI + setup resources
Browse files Browse the repository at this point in the history
  • Loading branch information
jbfenton committed Jul 19, 2020
1 parent b23f8e6 commit 9f37c1a
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 18 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) [2020] [Joshua Fenton]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
# chipmul8
CHIP-8 Emulator

> CHIP-8 is an interpreted programming language, developed by Joseph Weisbecker.
> It was initially used on the COSMAC VIP and Telmac 1800 8-bit microcomputers in the mid-1970s.
> CHIP-8 programs are run on a CHIP-8 virtual machine.
> It was made to allow video games to be more easily programmed for these computers.
> [CHIP-8](https://en.wikipedia.org/wiki/CHIP-8)
This project aims to replicate the CHIP-8 interpreter using Python, in the hopes that we can play Pong as it ran on the CHIP-8 virtual machine in the 1970's.

## Installation
1. Download / clone the repo
2. Navigate to the chipmul8 directory
3. Run setup.py:

```$ python setup.py install```

## Execution
1. Run the 'chipmul8' command while providing a path to a chip8 rom:

```$ chipmul8 /path/to/rom/pong.c8```

![chipmul8 GUI](media/normal-colors.png "chipmul8 GUI")
2. The '--invert_colors' switch can also be provided to invert the color palette of the display

```$ chipmul8 /path/to/rom/pong.c8 --invert_colors```

![chipmul8 GUI](media/inverted-colors.png "chipmul8 inverted GUI")

## References
The primary reference for this project was [Cowgod's Chip-8 Technical Reference v1.0](http://devernay.free.fr/hacks/chip8/C8TECH10.HTM)
This technical reference is incredibly detailed, the emulator would not have taken shape without it.

For testing [corax89's](https://github.com/corax89/chip8-test-rom) test roms were hugely helpful.
38 changes: 38 additions & 0 deletions chipmul8/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
"""
chipmul8 - A CHIP-8 Emulator.
"""

import os

from click import File, argument, command, echo, option


@command()
@option('--invert_colors/--no-invert_colors', default=False, help="Inverts the black/white values for the display")
@argument("input_file", type=File("rb"), nargs=1)
def cli(invert_colors, input_file):
"""
CLI interface for launching the emulator.
:param invert_colors: Invert display color flag.
:type invert_colors: bool
:param input_file: Rom file.
:type input_file: _io.BufferedReader
:return: None.
:rtype: None
"""

# Suppress PyGame support prompt
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"

from .emulator.engine import GameEngine

rom_file = input_file

echo(f"Loaded rom from path: {input_file.name}")

try:
game = GameEngine(rom_file=rom_file, invert_colors=invert_colors)
game.create_window()
game.start()
except Exception as e:
echo(f"An exception occurred: {e}")

echo("Goodbye, Parzival. Thank you for playing my game.")
3 changes: 3 additions & 0 deletions chipmul8/emulator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Emulator package.
"""
36 changes: 28 additions & 8 deletions chipmul8/engine.py → chipmul8/emulator/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
Emulator game engine.
"""

import sys
from pathlib import Path

import pygame
from OpenGL.GL import glClearColor, glClear, glDrawPixels, GL_RGB, GL_UNSIGNED_BYTE, GL_COLOR_BUFFER_BIT
from pygame.locals import (K_1, K_2, K_3, K_4, K_q, K_w, K_e, K_r, K_a, K_s, K_d, K_f, K_z, K_x, K_c, K_v)

from chipmul8.interpreter import Interpreter
from chipmul8.emulator.interpreter import Interpreter

keymap = {
# 1 2 3 4 => 1 2 3 C
Expand All @@ -25,22 +24,28 @@


class GameEngine:
def __init__(self, rom_path):
def __init__(self, rom_file, invert_colors=False):
"""
Initialise the game engine.
:param rom_path: Path to the rom file.
:type rom_path: Path
:param rom_file: Rom file.
:type rom_file: _io.BufferedReader
:param invert_colors: Invert display color flag
:type invert_colors: bool
"""

self.display_width = 64
self.display_height = 32
self.pixel_size = 10

self._invert_colors = invert_colors

Interpreter.initialize()
self.cpu = Interpreter()

self.cpu.load_rom(rom_path=rom_path)
rom_path = Path(rom_file.name)

self.cpu.load_rom(rom_file)

self.rom_name = rom_path.name[:len(rom_path.suffix) + 2]

Expand Down Expand Up @@ -80,6 +85,21 @@ def create_window(self):

self.clock = pygame.time.Clock()

def _pixel_color(self, value):
"""
Determine pixel color.
:param value: Interpreter display pixel (1 if filled, 0 is not filled).
:type value: int
:return: Pixel color value.
:rtype: int
"""

if self._invert_colors:
return 255 if value == 1 else 0
else:
return 0 if value == 1 else 255

def draw(self):
"""
Render interpreter display buffer to the game screen.
Expand All @@ -94,7 +114,7 @@ def draw(self):
g = r + 1
b = r + 2

pixel_color = 0 if value == 1 else 255
pixel_color = self._pixel_color(value)

self.temp_display[r] = pixel_color
self.temp_display[g] = pixel_color
Expand Down Expand Up @@ -141,7 +161,7 @@ def start(self):
if event.type == pygame.QUIT:
pygame.display.quit()
pygame.quit()
sys.exit()
return
elif event.type == pygame.VIDEORESIZE:
print(event.size)
elif event.type == pygame.KEYDOWN:
Expand Down
11 changes: 5 additions & 6 deletions chipmul8/interpreter.py → chipmul8/emulator/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,19 +155,18 @@ def __init__(self, start_address=0x200):
for index, font_item in enumerate(font_list):
self.ram.set_address(index, font_item)

def load_rom(self, rom_path):
def load_rom(self, rom_file):
"""
Loads a rom into memory.
:param rom_path: Path to rom file.
:type rom_path: str | Path
:param rom_file: Rom file.
:type rom_file: _io.BufferedReader
:return: None
:rtype: None
"""

with open(rom_path, 'rb') as rom_file:
for index, line in enumerate(rom_file.read()):
self.ram.set_address(0x200 + index, line)
for index, line in enumerate(rom_file.read()):
self.ram.set_address(0x200 + index, line)

def emulate(self):
"""
Expand Down
Binary file added media/inverted-colors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/normal-colors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pygame~=1.9.6
click~=7.1.2
numpy~=1.18.1
hexdump~=3.3
setuptools~=40.8.0
26 changes: 26 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Setup script.
"""

from setuptools import find_packages, setup


with open("requirements.txt") as requirements_file:
requirements = requirements_file.read().splitlines()


setup(
name='chipmul8',
version='0.1',
packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
url='https://jbfenton.dev/',
license='MIT License',
author='Josh Fenton',
author_email='[email protected]',
description='',
data_files=[("", ["LICENSE"])],
install_requires=requirements,
entry_points={
"console_scripts": ['chipmul8=chipmul8:cli']
}
)
3 changes: 3 additions & 0 deletions test/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Unit tests.
"""
7 changes: 4 additions & 3 deletions test/test_chipmul8.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""
Chip8 interpreter unit tests
"""

import unittest
from random import Random
from unittest.mock import patch

from chipmul8.interpreter import Interpreter
from chipmul8.emulator.interpreter import Interpreter


class TestOpCodes(unittest.TestCase):
Expand Down Expand Up @@ -459,7 +460,7 @@ def test_op_code_b000(self):
self.assertEqual(0x204, self.cpu.program_counter)
self.assertEqual(0xF3, self.cpu.registers[0x0])

@patch('chipmul8.interpreter.random')
@patch('chipmul8.emulator.interpreter.random')
def test_op_code_c000(self, random):
"""
CXNN
Expand Down Expand Up @@ -519,7 +520,7 @@ def test_op_code_d000(self):
]

for coordinate_x, coordinate_y in sprite_coordinates:
self.assertEqual(0x1, self.cpu.display_memory[coordinate_y, coordinate_x])
self.assertEqual(0x1, self.cpu.display_memory[coordinate_y, -coordinate_x - 1])

self.assertEqual(0x202, self.cpu.program_counter)

Expand Down

0 comments on commit 9f37c1a

Please sign in to comment.