# include "pixelGameEngine.h"
# include "Polygon.h"
# define OLC_PGEX_SPLASHSCREEN
# include "splash.h"
# include <ios>
# include <stack>
# include <bitset>
# include <random>
using namespace olc ;
bool USE_DEBUG_DISPLAY = true ;
int EMULATOR_SCREEN_WIDTH = 64 ;
int EMULATOR_SCREEN_HEIGHT = 32 ;
int EMULATOR_PIXEL_SIZE = 5 ;
class Chip8Emulator : public olc : : PixelGameEngine
{
public :
//SplashScreen s;
Chip8Emulator ( )
{
sAppName = " CHIP-8! " ;
}
public :
std : : array < uint8_t , 4096 > memory = {
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
//Font starts at 0x50
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
} ;
std : : array < bool , 2048 > display ;
std : : array < uint8_t , 2048 > screen ;
std : : list < uint8_t > stack ;
float pulse = 0 ;
uint8_t delay_timer , sound_timer ;
uint16_t pc = 0x200 ;
uint16_t index ; //One 16-bit index register called “I” which is used to point at locations in memory
std : : array < uint8_t , 16 > reg ;
std : : random_device rd ; //Will be used to obtain a seed for the random number engine
std : : mt19937 gen ; //Standard mersenne_twister_engine seeded with rd()
std : : uniform_int_distribution < > distrib ;
std : : array < Key , 16 > keymap { X , K1 , K2 , K3 , Q , W , E , A , S , D , Z , C , K4 , R , F , V } ;
bool USE_ORIGINAL_CHIP8_SET = true ; //True means use the original CHIP-8 spec (COSMAC VIP emulation). Set to false to use CHIP-48 spec.
bool PAUSED = true ;
long instructionCount = 0 ;
uint16_t watchMemoryAddress = 0 ;
std : : string Display8 ( int number ) {
std : : bitset < 8 > numb ( number ) ;
return numb . to_string ( ) ;
}
std : : string Display16 ( int number ) {
std : : bitset < 16 > numb ( number ) ;
return numb . to_string ( ) ;
}
void advanceTimers ( ) {
if ( delay_timer > 0 ) {
delay_timer - - ;
}
if ( sound_timer > 0 ) {
sound_timer - - ;
}
}
bool OnUserCreate ( ) override
{
gen = std : : mt19937 ( rd ( ) ) ;
distrib = std : : uniform_int_distribution < > ( 0 , 255 ) ;
for ( int i = 0 ; i < display . size ( ) ; i + + ) {
display [ i ] = 0 ;
}
std : : ifstream file ( " ChipWar.ch8 " , std : : ios_base : : binary ) ;
uint16_t counter = 0x200 ;
while ( file . good ( ) ) {
int val = file . get ( ) ;
if ( val ! = - 1 ) {
memory [ counter + + ] = val ;
}
}
/*memory[0x200]=0x00;
memory [ 0x201 ] = 0xE0 ;
memory [ 0x202 ] = 0xA0 ;
memory [ 0x203 ] = 0x50 ;
memory [ 0x204 ] = 0x60 ;
memory [ 0x205 ] = 0x04 ;
memory [ 0x206 ] = 0xD0 ;
memory [ 0x207 ] = 0x05 ;
memory [ 0x208 ] = 0xD0 ;
memory [ 0x209 ] = 0x05 ;
memory [ 0x210 ] = 0x12 ;
memory [ 0x211 ] = 0x10 ; */
//0x200 program start
return true ;
}
void OnTextEntryComplete ( const std : : string & sText ) override {
watchMemoryAddress = std : : stoul ( sText . c_str ( ) , nullptr , 16 ) ;
}
bool OnUserUpdate ( float fElapsedTime ) override
{
if ( ! USE_DEBUG_DISPLAY | | ! PAUSED ) {
pulse + = fElapsedTime ;
if ( pulse > = 1 / 60.f ) {
advanceTimers ( ) ;
pulse - = 1 / 60.f ;
for ( int i = 0 ; i < 10 ; i + + ) {
RunInstruction ( ) ;
}
DrawDisplay ( ) ;
}
} else {
if ( GetKey ( G ) . bPressed ) {
TextEntryEnable ( true ) ;
}
if ( ! IsTextEntryEnabled ( ) ) {
if ( GetKey ( OEM_6 ) . bPressed ) {
RunInstruction ( ) ;
instructionCount + + ;
if ( instructionCount % 10 = = 0 ) {
advanceTimers ( ) ; //After 10 instructions, 1/60th of a second has passed.
}
DrawDisplay ( ) ;
} else
if ( GetKey ( SPACE ) . bHeld ) {
RunInstruction ( ) ;
instructionCount + + ;
if ( instructionCount % 10 = = 0 ) {
advanceTimers ( ) ; //After 10 instructions, 1/60th of a second has passed.
}
DrawDisplay ( ) ;
} else
if ( GetKey ( END ) . bHeld ) {
for ( int i = 0 ; i < 10 ; i + + ) {
RunInstruction ( ) ;
instructionCount + + ;
}
advanceTimers ( ) ; //After 10 instructions, 1/60th of a second has passed.
DrawDisplay ( ) ;
}
}
}
if ( IsTextEntryEnabled ( ) ) {
DrawStringDecal ( { 2 , 2 } , " Goto Memory Address: " + TextEntryGetString ( ) ) ;
}
if ( GetKey ( F12 ) . bPressed ) {
PAUSED = ! PAUSED ;
}
std : : stringstream s ;
s < < " PC: 0x " < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < std : : hex < < pc ;
std : : string ss = s . str ( ) ;
std : : transform ( ss . begin ( ) + 6 , ss . end ( ) , ss . begin ( ) + 6 , : : toupper ) ;
DrawStringDecal ( vi2d { 8 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 16 } , ss ) ;
std : : stringstream instruction ;
instruction < < " 0x " < < std : : setfill ( ' 0 ' ) < < std : : setw ( 2 ) < < std : : hex < < ( int ) memory [ pc ] < < std : : setw ( 2 ) < < ( int ) memory [ pc + 1 ] < < " " < < GetInstructionDescription ( memory [ pc ] < < 8 | memory [ pc + 1 ] ) ;
ss = instruction . str ( ) ;
std : : transform ( ss . begin ( ) + 2 , ss . end ( ) , ss . begin ( ) + 2 , : : toupper ) ;
DrawStringDecal ( vi2d { 12 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 26 } , ss ) ;
for ( int i = 0 ; i < 21 ; i + + ) {
uint16_t tmp = pc + ( i - 10 ) * 2 ;
if ( tmp < memory . size ( ) ) {
std : : stringstream tmp_s ;
tmp_s < < " 0x " < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < std : : hex < < tmp < < " " < < std : : setw ( 2 ) < < ( int ) memory [ tmp ] < < std : : setw ( 2 ) < < ( int ) memory [ tmp + 1 ] < < " " < < GetInstructionDescription ( memory [ tmp ] < < 8 | memory [ tmp + 1 ] ) ;
std : : string ss = tmp_s . str ( ) ;
std : : transform ( ss . begin ( ) + 2 , ss . end ( ) , ss . begin ( ) + 2 , : : toupper ) ;
DrawStringDecal ( vi2d { 14 + EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_WIDTH , 8 + i * 10 } , ss , ( tmp = = pc ) ? YELLOW : WHITE ) ;
}
}
DrawStringDecal ( vi2d { 8 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 44 } , " REG " ) ;
for ( int i = 0 ; i < 16 ; i + + ) {
std : : stringstream s ;
s < < " V " + std : : to_string ( i ) < < std : : right < < std : : setfill ( ' ' ) < < std : : setw ( ( i > = 10 ) ? 3 : 4 ) < < ( int ) reg [ i ] ;
DrawStringDecal ( vi2d { 12 + i / 8 * 64 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 54 + i % 8 * 10 } , s . str ( ) ) ;
}
DrawStringDecal ( vi2d { 8 + 164 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 44 } , " TIMERS " ) ;
DrawStringDecal ( vi2d { 12 + 164 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 54 } , " DELAY " + std : : to_string ( delay_timer ) ) ;
DrawStringDecal ( vi2d { 12 + 164 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 64 } , " SOUND " + std : : to_string ( sound_timer ) ) ;
std : : stringstream index_s ;
index_s < < " 0x " < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < index ;
DrawStringDecal ( vi2d { 8 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 64 + 10 * 8 } , " INDEX: " + index_s . str ( ) ) ;
DrawStringDecal ( vi2d { 8 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 84 + 10 * 8 } , " STACK: " ) ;
int offsetX = 0 ;
bool first = true ;
uint8_t prevVal ;
bool firstVal = true ;
for ( uint8_t & val : stack ) {
if ( first ) {
first = false ;
prevVal = val ;
} else {
first = true ;
std : : stringstream hexStack ;
hexStack < < " 0x " < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < ( val < < 8 | prevVal ) ;
std : : string ss = hexStack . str ( ) ;
std : : transform ( ss . begin ( ) + 2 , ss . end ( ) , ss . begin ( ) + 2 , : : toupper ) ;
DrawStringDecal ( vi2d { 12 + offsetX , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 94 + 10 * 8 } , ( ( ! firstVal ) ? " <- " : " " ) + ss ) ;
if ( firstVal ) {
offsetX + = 6 * 8 ;
} else {
offsetX + = 10 * 8 ;
}
firstVal = false ;
}
}
std : : stringstream hexMemoryWatch ;
hexMemoryWatch < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < watchMemoryAddress ;
DrawStringDecal ( vi2d { 14 + EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_WIDTH , 18 + 21 * 10 } , " Memory Region [0x " + hexMemoryWatch . str ( ) + " ]: " ) ;
for ( int j = 0 ; j < 12 ; j + + ) {
std : : stringstream memj ;
memj < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < watchMemoryAddress + 16 * j ;
DrawStringDecal ( vi2d { 14 + EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_WIDTH , 18 + 21 * 10 + j * 10 + 10 } , " 0x " + memj . str ( ) ) ;
for ( int i = 0 ; i < 16 ; i + + ) {
uint16_t mem = i + j * 16 + watchMemoryAddress ;
if ( mem < memory . size ( ) ) {
std : : stringstream hexval ;
hexval < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 2 ) < < ( int ) memory [ mem ] ;
std : : string sss = hexval . str ( ) ;
std : : transform ( sss . begin ( ) , sss . end ( ) , sss . begin ( ) , : : toupper ) ;
DrawStringDecal ( vi2d { 14 + EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_WIDTH + 8 * 8 + i * 3 * 8 , 18 + 21 * 10 + j * 10 + 10 } , sss ) ;
}
}
}
return true ;
}
bool OnUserDestroy ( ) override {
return true ;
}
void DrawDisplay ( ) {
if ( USE_DEBUG_DISPLAY ) {
Clear ( VERY_DARK_BLUE ) ;
for ( int x = 0 ; x < EMULATOR_SCREEN_WIDTH ; x + + ) {
for ( int y = 0 ; y < EMULATOR_SCREEN_HEIGHT ; y + + ) {
if ( display [ y * EMULATOR_SCREEN_WIDTH + x ] ) {
screen [ y * EMULATOR_SCREEN_WIDTH + x ] = 255 ;
FillRect ( vi2d { x * EMULATOR_PIXEL_SIZE , y * EMULATOR_PIXEL_SIZE } + vi2d { 8 , 8 } , vi2d { EMULATOR_PIXEL_SIZE , EMULATOR_PIXEL_SIZE } ) ;
} else {
screen [ y * EMULATOR_SCREEN_WIDTH + x ] = std : : max ( 0 , screen [ y * EMULATOR_SCREEN_WIDTH + x ] - 36 ) ;
FillRect ( vi2d { x * EMULATOR_PIXEL_SIZE , y * EMULATOR_PIXEL_SIZE } + vi2d { 8 , 8 } , vi2d { EMULATOR_PIXEL_SIZE , EMULATOR_PIXEL_SIZE } , { screen [ y * EMULATOR_SCREEN_WIDTH + x ] , screen [ y * EMULATOR_SCREEN_WIDTH + x ] , screen [ y * EMULATOR_SCREEN_WIDTH + x ] } ) ;
}
}
}
} else {
Clear ( VERY_DARK_BLUE ) ;
for ( int x = 0 ; x < EMULATOR_SCREEN_WIDTH ; x + + ) {
for ( int y = 0 ; y < EMULATOR_SCREEN_HEIGHT ; y + + ) {
if ( display [ y * EMULATOR_SCREEN_WIDTH + x ] ) {
screen [ y * EMULATOR_SCREEN_WIDTH + x ] = 255 ;
Draw ( { x , y } , WHITE ) ;
} else {
screen [ y * EMULATOR_SCREEN_WIDTH + x ] = std : : max ( 0 , screen [ y * EMULATOR_SCREEN_WIDTH + x ] - 36 ) ;
Draw ( { x , y } , { screen [ y * EMULATOR_SCREEN_WIDTH + x ] , screen [ y * EMULATOR_SCREEN_WIDTH + x ] , screen [ y * EMULATOR_SCREEN_WIDTH + x ] } ) ;
}
}
}
}
}
std : : string GetInstructionDescription ( uint16_t opcode ) {
uint8_t nibble1 = opcode > > 12 ;
uint8_t X = opcode > > 8 & 0xF ;
uint8_t Y = opcode > > 4 & 0xF ;
uint8_t N = opcode & 0xF ;
uint8_t NN = opcode & 0x00FF ;
uint16_t NNN = opcode & 0x0FFF ;
switch ( nibble1 ) {
case 0x0 : {
switch ( NNN ) {
case 0x0E0 : { //Clear screen.
return " CLEAR SCREEN " ;
} break ;
case 0x0EE : { //Return from subroutine.
return " <<<RETURN " ;
} break ;
}
} break ;
case 0x1 : { //Jump.
std : : stringstream jump ;
jump < < " JUMP PC=0x " < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < NNN ;
return jump . str ( ) ;
} break ;
case 0x2 : { //calls the subroutine at memory location NNN
std : : stringstream call ;
call < < " CALL ( " < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < NNN < < " ) " ;
return call . str ( ) ;
} break ;
case 0x3 : { //If register X is equal to NN, skip instruction.
return " IF V " + std : : to_string ( X ) + " != " + std : : to_string ( NN ) ;
} break ;
case 0x4 : { //If register X is not equal to NN, skip instruction.
return " IF V " + std : : to_string ( X ) + " == " + std : : to_string ( NN ) ;
} break ;
case 0x5 : { //If register X is equal to register Y, skip instruction.
return " IF V " + std : : to_string ( X ) + " !=V " + std : : to_string ( Y ) ;
} break ;
case 0x6 : { //Set the register X to NN.
return " SET V " + std : : to_string ( X ) + " = " + std : : to_string ( NN ) ;
} break ;
case 0x7 : { //Add w/no carry
return " ADD_NC V " + std : : to_string ( X ) + " += " + std : : to_string ( NN ) ;
} break ;
case 0x8 : {
switch ( N ) {
case 0x0 : { //VX is set to the value of VY.
return " SET V " + std : : to_string ( X ) + " =V " + std : : to_string ( Y ) ;
} break ;
case 0x1 : { //Binary OR VX w/VY.
return " OR V " + std : : to_string ( X ) + " |=V " + std : : to_string ( Y ) ;
} break ;
case 0x2 : { //Binary AND VX w/VY.
return " AND V " + std : : to_string ( X ) + " &=V " + std : : to_string ( Y ) ;
} break ;
case 0x3 : { //Logical XOR VX w/VY.
reg [ X ] ^ = reg [ Y ] ;
return " XOR V " + std : : to_string ( X ) + " ^=V " + std : : to_string ( Y ) ;
} break ;
case 0x4 : { //Add VY to VX, set carry bit if overflow occurs.
return " ADD V " + std : : to_string ( X ) + " +=V " + std : : to_string ( Y ) ;
} break ;
case 0x5 : { // sets VX to the result of VX - VY.
return " SUBTRACT V " + std : : to_string ( X ) + " -=V " + std : : to_string ( Y ) ;
} break ;
case 0x6 : { //Shift Right
return " RSHIFT V " + std : : to_string ( X ) + " >>1 " ;
} break ;
case 0x7 : { //sets VX to the result of VY - VX. It's a reverse subtraction.
return " SUBTRACT V " + std : : to_string ( X ) + " =V " + std : : to_string ( Y ) + " -V " + std : : to_string ( X ) ;
} break ;
case 0xE : { //Shift Left
return " LSHIFT V " + std : : to_string ( X ) + " <<1 " ;
} break ;
}
} break ;
case 0x9 : { //If register X is not equal to register Y, skip instruction.
return " IF V " + std : : to_string ( X ) + " ==V " + std : : to_string ( Y ) ;
} break ;
case 0xA : { //This sets the index register I to the value NNN.
std : : stringstream set ;
set < < " SET I=0x " < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < NNN ;
return set . str ( ) ;
} break ;
case 0xB : { //Jump w/Offset
std : : stringstream jump ;
jump < < " JUMP_OFFSET PC " < < " =0x " < < std : : hex < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < NNN < < " +[V0] " ;
return jump . str ( ) ;
} break ;
case 0xC : { //Random number from 0 to NN.
return " RAND (0- " + std : : to_string ( NN ) + " ) SET TO V " + std : : to_string ( X ) ;
} break ;
case 0xD : { //Display
return " DISPLAY X:[V " + std : : to_string ( X ) + " ],Y:[V " + std : : to_string ( Y ) + " ] H: " + std : : to_string ( N ) ;
} break ;
case 0xE : {
switch ( NN ) {
case 0x9E : { //EX9E: Skip an instruction if key in VX is pressed.
return " IF KEY_UNHELD [V " + std : : to_string ( X ) + " ] " ;
} break ;
case 0xA1 : { //EXA1: Skip an instruction if key in VX is not pressed.
return " IF KEY_HELD [V " + std : : to_string ( X ) + " ] " ;
} break ;
}
} break ;
case 0xF : {
switch ( NN ) {
case 0x07 : { //sets VX to the current value of the delay timer
return " GET DELAY_TIMER -> V " + std : : to_string ( X ) ;
} break ;
case 0x15 : { //sets the delay timer to the value in VX
return " SET DELAY_TIMER = [V " + std : : to_string ( X ) + " ] " ;
} break ;
case 0x18 : { //sets the sound timer to the value in VX
return " SET SOUND_TIMER = [V " + std : : to_string ( X ) + " ] " ;
} break ;
case 0x1E : { //The index register I will get the value in VX added to it.
return " ADD I+=[V " + std : : to_string ( X ) + " ] " ;
} break ;
case 0x0A : { //This instruction “blocks”; it stops executing instructions and waits for key input
return " WAIT KEY_INPUT " ;
} break ;
case 0x29 : { //The index register I is set to the address of the hexadecimal character in VX.
return " SET I = FONT([V " + std : : to_string ( X ) + " ]) " ;
} break ;
case 0x33 : { //Binary-coded decimal conversion
return " SET [I~I+3] = BCD([V " + std : : to_string ( X ) + " ]) " ;
} break ;
case 0x55 : { //Stores registers from V0 to VX in memory pointed by index.
return " STORE V0~V " + std : : to_string ( X ) + " in I~I+ " + std : : to_string ( X ) ;
} break ;
case 0x65 : { //Retrieves registers from V0 to VX in memory pointed by index.
return " LOAD V0~V " + std : : to_string ( X ) + " in I~I+ " + std : : to_string ( X ) ;
} break ;
}
} break ;
}
return " ??? " ;
}
void RunInstruction ( ) {
//FETCH
uint16_t opcode = memory [ pc ] < < 8 | memory [ pc + 1 ] ;
pc + = 2 ;
//std::cout<<"Next instruction is 0x"<<std::hex<<pc<<std::endl;
//DECODE
uint8_t nibble1 = opcode > > 12 ;
uint8_t X = opcode > > 8 & 0xF ;
uint8_t Y = opcode > > 4 & 0xF ;
uint8_t N = opcode & 0xF ;
uint8_t NN = opcode & 0x00FF ;
uint16_t NNN = opcode & 0x0FFF ;
//std::cout<<"Opcode 0x"<<std::hex<<opcode<<std::dec<<": "<<Display8(nibble1)<<", "<<Display8(X)<<"/"<<Display8(Y)<<"/"<<Display8(N)<<"/"<<Display8(NN)<<"/"<<Display16(NNN)<<std::endl;
switch ( nibble1 ) {
case 0x0 : {
switch ( NNN ) {
case 0x0E0 : { //Clear screen.
for ( int i = 0 ; i < display . size ( ) ; i + + ) {
display [ i ] = 0 ;
}
} break ;
case 0x0EE : { //Return from subroutine.
uint8_t byte1 , byte2 ;
byte1 = stack . front ( ) ;
stack . pop_front ( ) ;
byte2 = stack . front ( ) ;
stack . pop_front ( ) ;
pc = byte2 < < 8 | byte1 ;
//std::cout<<"Exiting subroutine to 0x"<<std::hex<<pc<<std::endl;
} break ;
}
} break ;
case 0x1 : { //Jump.
pc = NNN ;
} break ;
case 0x2 : { //calls the subroutine at memory location NNN
stack . push_front ( pc > > 8 ) ;
stack . push_front ( pc & 0xFF ) ;
//std::cout<<"Pushed 0x"<<std::hex<<pc<<" onto stack"<<std::endl;
pc = NNN ;
//std::cout<<"Entering subroutine @ 0x"<<std::hex<<NNN<<std::endl;
} break ;
case 0x3 : { //If register X is equal to NN, skip instruction.
if ( reg [ X ] = = NN ) {
pc + = 2 ;
}
} break ;
case 0x4 : { //If register X is not equal to NN, skip instruction.
if ( reg [ X ] ! = NN ) {
pc + = 2 ;
}
} break ;
case 0x5 : { //If register X is equal to register Y, skip instruction.
if ( reg [ X ] = = reg [ Y ] ) {
pc + = 2 ;
}
} break ;
case 0x6 : { //Set the register X to NN.
reg [ X ] = NN ;
//std::cout<<(int)reg[X]<<std::endl;
} break ;
case 0x7 : { //Add w/no carry
reg [ X ] + = NN ;
//std::cout<<(int)reg[X]<<std::endl;
} break ;
case 0x8 : {
switch ( N ) {
case 0x0 : { //VX is set to the value of VY.
reg [ X ] = reg [ Y ] ;
} break ;
case 0x1 : { //Binary OR VX w/VY.
reg [ X ] | = reg [ Y ] ;
} break ;
case 0x2 : { //Binary AND VX w/VY.
reg [ X ] & = reg [ Y ] ;
} break ;
case 0x3 : { //Logical XOR VX w/VY.
reg [ X ] ^ = reg [ Y ] ;
} break ;
case 0x4 : { //Add VY to VX, set carry bit if overflow occurs.
bool carryFlag = 0 ;
if ( ( int ) reg [ X ] + ( int ) reg [ Y ] > 255 ) {
carryFlag = 1 ;
}
reg [ X ] + = reg [ Y ] ;
reg [ 0xF ] = carryFlag ;
} break ;
case 0x5 : { // sets VX to the result of VX - VY.
bool carryFlag = false ;
if ( reg [ X ] > = reg [ Y ] ) {
//reg[0xF]=1;
carryFlag = true ;
}
reg [ X ] - = reg [ Y ] ;
reg [ 0xF ] = carryFlag ;
} break ;
case 0x6 : { //Shift Right
if ( USE_ORIGINAL_CHIP8_SET ) {
/*
In the CHIP - 8 interpreter for the original COSMAC VIP , this instruction did the following : It put the value of VY into VX , and then shifted the value in VX 1 bit to the right ( 8 XY6 ) or left ( 8 XYE ) . VY was not affected , but the flag register VF would be set to the bit that was shifted out .
However , starting with CHIP - 48 and SUPER - CHIP in the early 1990 s , these instructions were changed so that they shifted VX in place , and ignored the Y completely .
*/
reg [ X ] = reg [ Y ] ;
}
bool carryFlag = reg [ X ] & 0x1 ;
reg [ X ] > > = 1 ;
reg [ 0xF ] = carryFlag ;
} break ;
case 0x7 : { //sets VX to the result of VY - VX. It's a reverse subtraction.
//reg[0xF]=0;
bool carryFlag = false ;
if ( reg [ Y ] > = reg [ X ] ) {
//reg[0xF]=1;
carryFlag = true ;
}
reg [ X ] = reg [ Y ] - reg [ X ] ;
reg [ 0xF ] = carryFlag ;
} break ;
case 0xE : { //Shift Left
//std::cout<<"Y is: "<<(int)Y<<std::endl;
if ( USE_ORIGINAL_CHIP8_SET ) {
/*
In the CHIP - 8 interpreter for the original COSMAC VIP , this instruction did the following : It put the value of VY into VX , and then shifted the value in VX 1 bit to the right ( 8 XY6 ) or left ( 8 XYE ) . VY was not affected , but the flag register VF would be set to the bit that was shifted out .
However , starting with CHIP - 48 and SUPER - CHIP in the early 1990 s , these instructions were changed so that they shifted VX in place , and ignored the Y completely .
*/
reg [ X ] = reg [ Y ] ;
}
bool carryFlag = ( reg [ X ] & 0x80 ) > > 7 ;
reg [ X ] < < = 1 ;
reg [ 0xF ] = carryFlag ;
} break ;
}
} break ;
case 0x9 : { //If register X is not equal to register Y, skip instruction.
if ( reg [ X ] ! = reg [ Y ] ) {
pc + = 2 ;
}
} break ;
case 0xA : { //This sets the index register I to the value NNN.
index = NNN ;
} break ;
case 0xB : { //Jump w/Offset
pc = NNN + reg [ 0x0 ] ;
} break ;
case 0xC : { //Random number from 0 to NN.
reg [ X ] = distrib ( gen ) & NN ;
} break ;
case 0xD : { //Display
uint8_t start_X = reg [ X ] % EMULATOR_SCREEN_WIDTH ;
uint8_t start_Y = reg [ Y ] % EMULATOR_SCREEN_HEIGHT ;
reg [ 0xF ] = 0 ;
for ( uint8_t y = 0 ; y < N ; y + + ) {
uint16_t spriteRow = memory [ index + y ] ;
//std::cout<<spriteRow<<std::endl;
for ( uint8_t x = 0 ; x < 8 ; x + + ) {
if ( start_X + x < EMULATOR_SCREEN_WIDTH & & start_Y + y < EMULATOR_SCREEN_HEIGHT ) {
//Memory loc: 0x50
//0xF0,0x90,0x90,0x90,0xF0
bool pixel = spriteRow > > ( 8 - x - 1 ) & 0x1 ;
if ( display [ ( start_Y + y ) * EMULATOR_SCREEN_WIDTH + ( start_X + x ) ] & & pixel ) {
//std::cout<<"Double on."<<std::endl;
reg [ 0xF ] = 0 ;
}
display [ ( start_Y + y ) * EMULATOR_SCREEN_WIDTH + ( start_X + x ) ] = display [ ( start_Y + y ) * EMULATOR_SCREEN_WIDTH + ( start_X + x ) ] ^ pixel ;
}
}
}
} break ;
case 0xE : {
switch ( NN ) {
case 0x9E : { //EX9E: Skip an instruction if key in VX is pressed.
if ( GetKey ( keymap [ reg [ X ] ] ) . bHeld ) {
pc + = 2 ;
}
} break ;
case 0xA1 : { //EXA1: Skip an instruction if key in VX is not pressed.
if ( ! GetKey ( keymap [ reg [ X ] ] ) . bHeld ) {
pc + = 2 ;
}
} break ;
}
} break ;
case 0xF : {
switch ( NN ) {
case 0x07 : { //sets VX to the current value of the delay timer
reg [ X ] = delay_timer ;
} break ;
case 0x15 : { //sets the delay timer to the value in VX
delay_timer = reg [ X ] ;
} break ;
case 0x18 : { //sets the sound timer to the value in VX
sound_timer = reg [ X ] ;
} break ;
case 0x1E : { //The index register I will get the value in VX added to it.
//reg[0xF]=0;
/*bool carryFlag=false;
if ( index + reg [ X ] > = 0x1000 ) {
//reg[0xF]=1;
carryFlag = true ;
} */
index + = reg [ X ] ;
//reg[0xF]=carryFlag;
} break ;
case 0x0A : { //This instruction “blocks”; it stops executing instructions and waits for key input
for ( int i = 0 ; i < keymap . size ( ) ; i + + ) {
if ( GetKey ( keymap [ i ] ) . bHeld ) {
reg [ X ] = i ;
goto pass ;
}
}
pc - = 2 ;
pass : ;
} break ;
case 0x29 : { //The index register I is set to the address of the hexadecimal character in VX.
index = 0x50 + ( reg [ X ] & 0xF ) * 5 ;
} break ;
case 0x33 : { //Binary-coded decimal conversion
uint8_t numb = reg [ X ] ; //156
memory [ index + 2 ] = numb % 10 ;
numb / = 10 ;
memory [ index + 1 ] = numb % 10 ;
numb / = 10 ;
memory [ index ] = numb % 10 ;
} break ;
case 0x55 : { //Stores registers from V0 to VX in memory pointed by index.
for ( int i = 0 ; i < = X ; i + + ) {
memory [ index + i ] = reg [ i ] ;
}
if ( USE_ORIGINAL_CHIP8_SET ) {
/*The original CHIP-8 interpreter for the COSMAC VIP actually incremented the I register while it worked. Each time it stored or loaded one register, it incremented I. After the instruction was finished, I would be set to the new value I + X + 1.*/
index + = X + 1 ;
}
} break ;
case 0x65 : { //Retrieves registers from V0 to VX in memory pointed by index.
for ( int i = 0 ; i < = X ; i + + ) {
reg [ i ] = memory [ index + i ] ;
}
if ( USE_ORIGINAL_CHIP8_SET ) {
/*The original CHIP-8 interpreter for the COSMAC VIP actually incremented the I register while it worked. Each time it stored or loaded one register, it incremented I. After the instruction was finished, I would be set to the new value I + X + 1.*/
index + = X + 1 ;
}
} break ;
}
} break ;
}
}
} ;
int main ( )
{
Chip8Emulator demo ;
if ( USE_DEBUG_DISPLAY ) {
if ( demo . Construct ( 800 , 360 , 2 , 2 ) )
demo . Start ( ) ;
} else {
if ( demo . Construct ( 64 , 32 , 10 , 10 ) )
demo . Start ( ) ;
}
return 0 ;
}