@ -9,8 +9,10 @@
using namespace olc ;
using namespace olc ;
# define WIDTH 640
bool USE_DEBUG_DISPLAY = true ;
# define HEIGHT 480
int EMULATOR_SCREEN_WIDTH = 64 ;
int EMULATOR_SCREEN_HEIGHT = 32 ;
int EMULATOR_PIXEL_SIZE = 5 ;
class Chip8Emulator : public olc : : PixelGameEngine
class Chip8Emulator : public olc : : PixelGameEngine
{
{
@ -62,6 +64,7 @@ public:
std : : uniform_int_distribution < > distrib ;
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 } ;
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 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 ;
std : : string Display8 ( int number ) {
std : : string Display8 ( int number ) {
std : : bitset < 8 > numb ( number ) ;
std : : bitset < 8 > numb ( number ) ;
@ -80,7 +83,7 @@ public:
for ( int i = 0 ; i < display . size ( ) ; i + + ) {
for ( int i = 0 ; i < display . size ( ) ; i + + ) {
display [ i ] = 0 ;
display [ i ] = 0 ;
}
}
std : : ifstream file ( " output .ch8" , std : : ios_base : : binary ) ;
std : : ifstream file ( " ChipWar .ch8" , std : : ios_base : : binary ) ;
uint16_t counter = 0x200 ;
uint16_t counter = 0x200 ;
while ( file . good ( ) ) {
while ( file . good ( ) ) {
int val = file . get ( ) ;
int val = file . get ( ) ;
@ -106,282 +109,473 @@ public:
bool OnUserUpdate ( float fElapsedTime ) override
bool OnUserUpdate ( float fElapsedTime ) override
{
{
pulse + = fElapsedTime ;
if ( ! USE_DEBUG_DISPLAY | | ! PAUSED ) {
if ( pulse > = 1 / 60.f ) {
pulse + = fElapsedTime ;
if ( delay_timer > 0 ) {
if ( pulse > = 1 / 60.f ) {
delay_timer - - ;
if ( delay_timer > 0 ) {
delay_timer - - ;
}
if ( sound_timer > 0 ) {
sound_timer - - ;
}
pulse - = 1 / 60.f ;
for ( int i = 0 ; i < 10 ; i + + ) {
RunInstruction ( ) ;
}
DrawDisplay ( ) ;
}
} else {
if ( GetKey ( OEM_6 ) . bPressed ) {
RunInstruction ( ) ;
DrawDisplay ( ) ;
}
}
std : : stringstream s ;
s < < " PC: 0x " < < std : : setfill ( ' 0 ' ) < < std : : setw ( 4 ) < < std : : hex < < pc ;
DrawStringDecal ( vi2d { 8 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 16 } , s . str ( ) ) ;
std : : stringstream instruction ;
instruction < < " 0x " < < std : : setfill ( ' 0 ' ) < < std : : setw ( 2 ) < < std : : hex < < ( int ) memory [ pc ] < < std : : setw ( 2 ) < < ( int ) memory [ pc + 1 ] ;
DrawStringDecal ( vi2d { 12 , EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_HEIGHT + 26 } , instruction . str ( ) ) ;
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 ( ) , ss . end ( ) , ss . begin ( ) , : : toupper ) ;
DrawStringDecal ( vi2d { 14 + EMULATOR_PIXEL_SIZE * EMULATOR_SCREEN_WIDTH , 8 + i * 10 } , ss , ( tmp = = pc ) ? YELLOW : WHITE ) ;
}
}
if ( sound_timer > 0 ) {
}
sound_timer - - ;
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 ] } ) ;
}
}
}
}
pulse - = 1 / 60.f ;
}
}
for ( int i = 0 ; i < 10 ; i + + ) {
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 ) + " ) " ;
} 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 " ??? " ;
}
//FETCH
void RunInstruction ( ) {
uint16_t opcode = memory [ pc ] < < 8 | memory [ pc + 1 ] ;
//FETCH
pc + = 2 ;
uint16_t opcode = memory [ pc ] < < 8 | memory [ pc + 1 ] ;
//std::cout<<"Next instruction is 0x"<<std::hex<<pc<<std::endl;
pc + = 2 ;
//std::cout<<"Next instruction is 0x"<<std::hex<<pc<<std::endl;
//DECODE
//DECODE
uint8_t nibble1 = opcode > > 12 ;
uint8_t nibble1 = opcode > > 12 ;
uint8_t X = opcode > > 8 & 0xF ;
uint8_t X = opcode > > 8 & 0xF ;
uint8_t Y = opcode > > 4 & 0xF ;
uint8_t Y = opcode > > 4 & 0xF ;
uint8_t N = opcode & 0xF ;
uint8_t N = opcode & 0xF ;
uint8_t NN = opcode & 0x00FF ;
uint8_t NN = opcode & 0x00FF ;
uint16_t NNN = opcode & 0x0FFF ;
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;
//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 ) {
switch ( nibble1 ) {
case 0x0 : {
case 0x0 : {
switch ( NNN ) {
switch ( NNN ) {
case 0x0E0 : { //Clear screen.
case 0x0E0 : { //Clear screen.
for ( int i = 0 ; i < display . size ( ) ; i + + ) {
for ( int i = 0 ; i < display . size ( ) ; i + + ) {
display [ i ] = 0 ;
display [ i ] = 0 ;
}
} break ;
case 0x0EE : { //Return from subroutine.
uint8_t byte1 , byte2 ;
byte1 = stack . top ( ) ;
stack . pop ( ) ;
byte2 = stack . top ( ) ;
stack . pop ( ) ;
pc = byte2 < < 8 | byte1 ;
//std::cout<<"Exiting subroutine to 0x"<<std::hex<<pc<<std::endl;
} break ;
}
}
} break ;
} break ;
case 0x1 : { //Jump.
case 0x0EE : { //Return from subroutine.
pc = NNN ;
uint8_t byte1 , byte2 ;
byte1 = stack . top ( ) ;
stack . pop ( ) ;
byte2 = stack . top ( ) ;
stack . pop ( ) ;
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 ( pc > > 8 ) ;
stack . push ( 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 ;
} break ;
case 0x2 : { //calls the subroutine at memory location NNN
case 0x2 : { //Binary AND VX w/VY.
stack . push ( pc > > 8 ) ;
reg [ X ] & = reg [ Y ] ;
stack . push ( 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 ;
} break ;
case 0x3 : { //If register X is equal to NN, skip instruction.
case 0x3 : { //Logical XOR VX w/VY.
if ( reg [ X ] = = NN ) {
reg [ X ] ^ = reg [ Y ] ;
pc + = 2 ;
} break ;
case 0x4 : { //Add VY to VX, set carry bit if overflow occurs.
reg [ 0xF ] = 0 ;
if ( ( int ) reg [ X ] + ( int ) reg [ Y ] > 255 ) {
reg [ 0xF ] = 1 ;
}
}
reg [ X ] + = reg [ Y ] ;
} break ;
} break ;
case 0x4 : { //If register X is not equal to NN, skip instruction.
case 0x5 : { // sets VX to the result of VX - VY.
if ( reg [ X ] ! = NN ) {
reg [ 0xF ] = 0 ;
pc + = 2 ;
if ( reg [ X ] > reg [ Y ] ) {
reg [ 0xF ] = 1 ;
}
}
reg [ X ] - = reg [ Y ] ;
} break ;
} break ;
case 0x5 : { //If register X is equal to register Y, skip instruction.
case 0x6 : { //Shift Right
if ( reg [ X ] = = reg [ Y ] ) {
if ( USE_ORIGINAL_CHIP8_SET ) {
pc + = 2 ;
/*
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 ] ;
}
}
reg [ 0xF ] = reg [ X ] & 0x1 ;
reg [ X ] > > = 1 ;
} break ;
} break ;
case 0x6 : { //Set the register X to NN.
case 0x7 : { //sets VX to the result of VY - VX. It's a reverse subtraction.
reg [ X ] = NN ;
reg [ 0xF ] = 0 ;
//std::cout<<(int)reg[X]<<std::endl;
if ( reg [ Y ] > reg [ X ] ) {
} break ;
reg [ 0xF ] = 1 ;
case 0x7 : { //Add w/no carry
}
reg [ X ] + = NN ;
reg [ X ] = reg [ Y ] - reg [ X ] ;
//std::cout<<(int)reg[X]<<std::endl;
} break ;
} break ;
case 0xE : { //Shift Left
case 0x8 : {
//std::cout<<"Y is: "<<(int)Y<<std::endl;
switch ( N ) {
if ( USE_ORIGINAL_CHIP8_SET ) {
case 0x0 : { //VX is set to the value of VY.
/*
reg [ X ] = reg [ Y ] ;
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 .
} 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.
reg [ 0xF ] = 0 ;
if ( ( int ) reg [ X ] + ( int ) reg [ Y ] > 255 ) {
reg [ 0xF ] = 1 ;
}
reg [ X ] + = reg [ Y ] ;
} break ;
case 0x5 : { // sets VX to the result of VX - VY.
reg [ 0xF ] = 0 ;
if ( reg [ X ] > reg [ Y ] ) {
reg [ 0xF ] = 1 ;
}
reg [ X ] - = reg [ Y ] ;
} 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 .
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 ] ;
reg [ X ] = reg [ Y ] ;
}
}
reg [ 0xF ] = reg [ X ] & 0x1 ;
reg [ 0xF ] = ( reg [ X ] & 0x80 ) > > 7 ;
reg [ X ] > > = 1 ;
reg [ X ] < < = 1 ;
} break ;
} break ;
case 0x7 : { //sets VX to the result of VY - VX. It's a reverse subtraction.
}
} 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 ;
reg [ 0xF ] = 0 ;
if ( reg [ Y ] > reg [ X ] ) {
}
reg [ 0xF ] = 1 ;
display [ ( start_Y + y ) * EMULATOR_SCREEN_WIDTH + ( start_X + x ) ] = display [ ( start_Y + y ) * EMULATOR_SCREEN_WIDTH + ( start_X + x ) ] ^ pixel ;
}
}
reg [ X ] = reg [ Y ] - reg [ X ] ;
}
} break ;
}
case 0xE : { //Shift Left
} break ;
//std::cout<<"Y is: "<<(int)Y<<std::endl;
case 0xE : {
if ( USE_ORIGINAL_CHIP8_SET ) {
switch ( NN ) {
/*
case 0x9E : { //EX9E: Skip an instruction if key in VX is pressed.
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 .
if ( GetKey ( keymap [ reg [ X ] ] ) . bHeld ) {
pc + = 2 ;
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 ] ;
}
reg [ 0xF ] = ( reg [ X ] & 0x80 ) > > 7 ;
reg [ X ] < < = 1 ;
} break ;
}
}
} break ;
} break ;
case 0x9 : { //If register X is not equal to register Y, skip instruction.
case 0xA1 : { //EXA1: Skip an instruction if key in VX is not pressed.
if ( reg [ X ] ! = reg [ Y ] ) {
if ( ! GetKey ( keymap [ reg [ X ] ] ) . bHeld ) {
pc + = 2 ;
pc + = 2 ;
}
}
} break ;
} break ;
case 0xA : { //This sets the index register I to the value NNN.
}
index = NNN ;
} break ;
case 0xF : {
switch ( NN ) {
case 0x07 : { //sets VX to the current value of the delay timer
reg [ X ] = delay_timer ;
} break ;
} break ;
case 0xB : { //Jump w/Offset
case 0x15 : { //sets the delay timer to the value in VX
pc = NNN + reg [ 0x0 ] ;
delay_timer = reg [ X ] ;
} break ;
} break ;
case 0xC : { //Random number from 0 to NN.
case 0x18 : { //sets the sound timer to the value in VX
reg [ X ] = distrib ( gen ) & NN ;
sound_timer = reg [ X ] ;
} break ;
} break ;
case 0xD : { //Display
case 0x1E : { //The index register I will get the value in VX added to it.
uint8_t start_X = reg [ X ] % ScreenWidth ( ) ;
uint8_t start_Y = reg [ Y ] % ScreenHeight ( ) ;
reg [ 0xF ] = 0 ;
reg [ 0xF ] = 0 ;
for ( uint8_t y = 0 ; y < N ; y + + ) {
if ( index + reg [ X ] > = 0x1000 ) {
uint16_t spriteRow = memory [ index + y ] ;
reg [ 0xF ] = 1 ;
//std::cout<<spriteRow<<std::endl;
}
for ( uint8_t x = 0 ; x < 8 ; x + + ) {
index + = reg [ X ] ;
if ( start_X + x < ScreenWidth ( ) & & start_Y + y < ScreenHeight ( ) ) {
} break ;
//Memory loc: 0x50
case 0x0A : { //This instruction “blocks”; it stops executing instructions and waits for key input
//0xF0,0x90,0x90,0x90,0xF0
for ( int i = 0 ; i < keymap . size ( ) ; i + + ) {
bool pixel = spriteRow > > ( 8 - x - 1 ) & 0x1 ;
if ( GetKey ( keymap [ i ] ) . bHeld ) {
if ( display [ ( start_Y + y ) * ScreenWidth ( ) + ( start_X + x ) ] & & pixel ) {
reg [ X ] = i ;
//std::cout<<"Double on."<<std::endl;
goto pass ;
reg [ 0xF ] = 0 ;
}
display [ ( start_Y + y ) * ScreenWidth ( ) + ( start_X + x ) ] = display [ ( start_Y + y ) * ScreenWidth ( ) + ( start_X + x ) ] ^ pixel ;
}
}
}
}
}
pc - = 2 ;
pass : ;
} break ;
} break ;
case 0xE : {
case 0x29 : { //The index register I is set to the address of the hexadecimal character in VX.
switch ( NN ) {
index = 0x50 + ( reg [ X ] & 0xF ) * 5 ;
case 0x9E : { //EX9E: Skip an instruction if key in VX is pressed.
} break ;
if ( GetKey ( keymap [ reg [ X ] ] ) . bHeld ) {
case 0x33 : { //Binary-coded decimal conversion
pc + = 2 ;
uint8_t numb = reg [ X ] ; //156
}
memory [ index + 2 ] = numb % 10 ;
} break ;
numb / = 10 ;
case 0xA1 : { //EXA1: Skip an instruction if key in VX is not pressed.
memory [ index + 1 ] = numb % 10 ;
if ( ! GetKey ( keymap [ reg [ X ] ] ) . bHeld ) {
numb / = 10 ;
pc + = 2 ;
memory [ index ] = numb % 10 ;
}
} break ;
} 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 ;
} break ;
case 0xF : {
case 0x65 : { //Retrieves registers from V0 to VX in memory pointed by index.
switch ( NN ) {
for ( int i = 0 ; i < = X ; i + + ) {
case 0x07 : { //sets VX to the current value of the delay timer
reg [ i ] = memory [ index + i ] ;
reg [ X ] = delay_timer ;
}
} break ;
if ( USE_ORIGINAL_CHIP8_SET ) {
case 0x15 : { //sets the delay timer to the value in VX
/*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.*/
delay_timer = reg [ X ] ;
index + = X + 1 ;
} 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 ;
if ( index + reg [ X ] > = 0x1000 ) {
reg [ 0xF ] = 1 ;
}
index + = reg [ X ] ;
} 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 ;
} break ;
}
}
}
} break ;
Clear ( VERY_DARK_BLUE ) ;
for ( int x = 0 ; x < ScreenWidth ( ) ; x + + ) {
for ( int y = 0 ; y < ScreenHeight ( ) ; y + + ) {
if ( display [ y * ScreenWidth ( ) + x ] ) {
screen [ y * ScreenWidth ( ) + x ] = 255 ;
Draw ( { x , y } , WHITE ) ;
} else {
screen [ y * ScreenWidth ( ) + x ] = std : : max ( 0 , screen [ y * ScreenWidth ( ) + x ] - 36 ) ;
Draw ( { x , y } , { screen [ y * ScreenWidth ( ) + x ] , screen [ y * ScreenWidth ( ) + x ] , screen [ y * ScreenWidth ( ) + x ] } ) ;
}
}
}
}
}
return true ;
}
bool OnUserDestroy ( ) override {
return true ;
}
}
} ;
} ;
int main ( )
int main ( )
{
{
Chip8Emulator demo ;
Chip8Emulator demo ;
if ( demo . Construct ( 64 , 32 , 10 , 10 ) )
if ( USE_DEBUG_DISPLAY ) {
demo . Start ( ) ;
if ( demo . Construct ( 800 , 360 , 2 , 2 ) )
demo . Start ( ) ;
} else {
if ( demo . Construct ( 64 , 32 , 10 , 10 ) )
demo . Start ( ) ;
}
return 0 ;
return 0 ;
}
}