diff --git a/C++ProjectTemplate b/C++ProjectTemplate index 4382771..f8a08dc 100755 Binary files a/C++ProjectTemplate and b/C++ProjectTemplate differ diff --git a/ChipWar.ch8 b/ChipWar.ch8 new file mode 100644 index 0000000..e043ab1 Binary files /dev/null and b/ChipWar.ch8 differ diff --git a/main.cpp b/main.cpp index f2eb671..4e14175 100644 --- a/main.cpp +++ b/main.cpp @@ -9,8 +9,10 @@ using namespace olc; -#define WIDTH 640 -#define HEIGHT 480 +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 { @@ -62,6 +64,7 @@ public: std::uniform_int_distribution<> distrib; std::arraykeymap{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; std::string Display8(int number){ std::bitset<8>numb(number); @@ -80,7 +83,7 @@ public: for (int i=0;i=1/60.f){ - if (delay_timer>0){ - delay_timer--; + if (!USE_DEBUG_DISPLAY||!PAUSED){ + pulse+=fElapsedTime; + if (pulse>=1/60.f){ + 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"<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>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 "<<>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"< 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 - uint16_t opcode = memory[pc]<<8|memory[pc+1]; - pc+=2; - //std::cout<<"Next instruction is 0x"<>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"<>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"<>8); + stack.push(pc&0xFF); + //std::cout<<"Pushed 0x"<>8); - stack.push(pc&0xFF); - //std::cout<<"Pushed 0x"<255){ + reg[0xF]=1; } + reg[X]+=reg[Y]; }break; - case 0x4:{//If register X is not equal to NN, skip instruction. - if(reg[X]!=NN){ - pc+=2; + 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 0x5:{//If register X is equal to register Y, skip instruction. - if(reg[X]==reg[Y]){ - pc+=2; + 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 (8XY6) or left (8XYE). 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 1990s, 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; - case 0x6:{ //Set the register X to NN. - reg[X]=NN; - //std::cout<<(int)reg[X]<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 (8XY6) or left (8XYE). VY was not affected, but the flag register VF would be set to the bit that was shifted out. + case 0x7:{//sets VX to the result of VY - VX. It's a reverse subtraction. + reg[0xF]=0; + if (reg[Y]>reg[X]){ + reg[0xF]=1; + } + reg[X]=reg[Y]-reg[X]; + }break; + case 0xE:{//Shift Left + //std::cout<<"Y is: "<<(int)Y<>=1; - }break; - case 0x7:{//sets VX to the result of VY - VX. It's a reverse subtraction. + However, starting with CHIP-48 and SUPER-CHIP in the early 1990s, 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; + 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>(8-x-1)&0x1; + if (display[(start_Y+y)*EMULATOR_SCREEN_WIDTH+(start_X+x)]&&pixel){ + //std::cout<<"Double on."<reg[X]){ - reg[0xF]=1; - } - reg[X]=reg[Y]-reg[X]; - }break; - case 0xE:{//Shift Left - //std::cout<<"Y is: "<<(int)Y<>7; - reg[X]<<=1; - }break; + } + 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 0x9:{//If register X is not equal to register Y, skip instruction. - if(reg[X]!=reg[Y]){ + case 0xA1:{//EXA1: Skip an instruction if key in VX is not pressed. + if (!GetKey(keymap[reg[X]]).bHeld){ pc+=2; } }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; - case 0xB:{//Jump w/Offset - pc=NNN+reg[0x0]; + case 0x15:{//sets the delay timer to the value in VX + delay_timer=reg[X]; }break; - case 0xC:{//Random number from 0 to NN. - reg[X]=distrib(gen)&NN; + case 0x18:{//sets the sound timer to the value in VX + sound_timer=reg[X]; }break; - case 0xD:{ //Display - uint8_t start_X=reg[X]%ScreenWidth(); - uint8_t start_Y=reg[Y]%ScreenHeight(); + case 0x1E:{//The index register I will get the value in VX added to it. reg[0xF]=0; - for (uint8_t y=0;y>(8-x-1)&0x1; - if (display[(start_Y+y)*ScreenWidth()+(start_X+x)]&&pixel){ - //std::cout<<"Double on."<=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=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