RXChip8
Recently I have decided to write a Chip8 Virtual Machine Emulator with rxjs. I had implemented chip8 with typescript before but I wanted to transform it into rxjs! Chip8 is really old programming language that’s run on it’s own virtual machine. It was easy to develop video games in 70s with Chip8. Here is the wikipedia link for who don’t know what chip8 is.
https://en.wikipedia.org/wiki/CHIP-8
Before you read please consider that this is going to be my first post here forgive me if I made any mistakes.
You can find all code snippets and source code in the GITHUB.
chip8 implementation might have flaws I haven't really spend too much time on it.
First of all I am going to write about general non-rxjs stuff (chip8 vm implementation)
RAM class
Just kidding, but I love RAM trucks.
This is a very simple class that handles initialization of the ram, also handles write and read functionalities of the ram.
Since chip8 commonly used in 4k systems. I initialized the ram with 4096 bytes(0x1000).
this.memory = new Uint8Array(0x1000);
I initialized ram with Unsigned 8 Bit arrays to make it little realistic :)
memoryOverflow flag is set when we try to write or read to a location that is not on the boundaries of the ram.
CPU class
Chip8 has 16 8-bit registers. They are named from V0 to VF in the specification so I named the same way in the code.
V0: number;
I initialized each registers as shown below in resetRegisters function.
this.V0 = 0 & 0xff;
I initialized them by ANDing with 0xff because it’s way more cooler than just assigning 0 to them haha. Also it’s easy to see that the register is actually a 8 bit register. (For those who don't know how to represent 8 bit in hexadecimal. You can search in google.)
Since registers goes from 0 to 15 I have written a function to get a register by a number
getRegister(num: number): number
I think another cool implementation of this registers would be by creating a UIntArray8(16) array and we could have hold each register in this array.
Creating stack was straight forward. It’s just an array with 16 slots and stackpointer to make it LIFO. Stack is being used to store return addresses when cpu goes into subroutines.
There are two timers in chip8. Delay timer and sound timer. When they are set to any number, they begin to count down. Specifications says they count down at 60hz but I haven’t implemented anything to make it exactly 60hz.
Sound timer is being used to make sound until it reaches to 0.
There are a 16 possible input. Chip8 listens these key inputs and if specific key is pressed or not pressed it skips an instruction. A litle more explanation of the implementation is in the rxjs stuff.
Display resolution in chip8 is 64x32 pixels and each sprite is 8 pixel wide. Height can be between 1 to 15. I have implemented this display as an 2 dimensional array and called it graphicArray. I initialized with all 0. chip8 draws a sprite on the graphicArray by xor(ing) the sprite with the current pixels on the graphic array. That means only set bits in the sprite will flip the corresponding pixel in graphicArray from unset to set.
I am not going to explain each opcode and routines. But in general Chip8 CPU has Program counter and registers. Registers are places that cpu stores a data temporarily. Program counter is another register but this register stores the location of the instruction in the ram. When that code is fetched it is increased by one or instruction can modify it (for example jump operations). If you want more information about this subject, you can search how cpu works on google.
Renderer class
I have created this class to be responsible for drawing our graphicArray to browser by using canvas. It can also be implemented with openGL etc.. even CSS
This is very straight forward class that has two functions.
One is
drawScreen(graphicArray: any[][])
And other is
drawPixel(x: number, y: number)
drawScreen, first reset our canvas filling it with pitch black. Then goes through the graphicArray and calls drawPixel where ever it finds set bit.
drawPixel is drawing a rectangle to coordinates that has been supplied with. I also added pixelScale constant to make it easier to make screen and pixels bigger if we want to.
RxJS stuff
RxJS is the reason that I wanted to write this article since there are lots of chip8 implementation on the net.
I wanted to store the state in one place so I created stateSubject
private stateSubject$: BehaviorSubject<Partial<CPU>>
Everything that updates the state will call stateSubject.next to update the state partially.
state$ = this.stateSubject$.asObservable().pipe(
scan((state, value) => value ? Object.assign(state, value) : state, this.initialState)
);
Scan operator is very useful here because it basically stores the old state. Everytime I call the stateSubject$.next I am going to have the last state and the value that I want to update in that state. Merging that two value will give me the new state!
If you have noticed that our state is actually CPU object. We are holding the latest state of the CPU.
EVENTS
Events are the one way to update the state as a side effect. For example keyPress event
private keyUp$: Observable<any> = fromEvent(document, 'keyup');
keyPress$: Observable<Uint8Array> = merge(this.keyUp$, this.keyDown$).pipe(
scan((acc, x) => {
return this.keyPressEventHandler(acc, x);
}, new Uint8Array(16)),
distinctUntilChanged(),
tap(keyPad => {
//side effect
this.stateSubject$.next({ keyPad });
})
);
Code above, merges two key events. Using scan to keep keypad’s state and update it.
keyPressEventHandler is creating a new keypad array. For example if I pressed A on the keyboard. It sets 8th index of the array to 1. When keyup event occurs it switches back to 0.
loadGameEvent$ is updating ram with the loaded game data.
Buttons have their own events
startClickEvent$ = fromEvent(document.getElementById('startButton'), 'click').pipe(switchMap(() => test.run$));
startButton event is switching to run$ observable and cpu starts ticking.
RUNNING
Ticker observable is going to emit every time state has been emitted.
ticker$ = this.state$.pipe(
switchMap(state => {
return !state.isPaused ? timer(state.clockSpeed) : NEVER;
})
);
ticker$ observable is a piped state$ observable and switches to a timer observable to delay a little bit. If we are in isPaused state then it will switch to NEVER observable which never emits.
$run observable is going to be derived from ticker observable to run the cycle of cpu and update the state.
// RUNNING - TICKING
run$ = this.ticker$.pipe(
withLatestFrom(this.state$),
tap(([, state]) => {
state.runCycle();
this.stateSubject$.next(state);
}),
takeWhile(([, state]) => !state.ram.memoryOverflow && state.isRunning),
map(([, state]) => state.graphicArray),
distinctUntilChanged((x, y) => {
//written a compare.
for (let i = 0; i < x.length; i++) {
for (let j = 0; j < x[i].length; j++) {
if (x[i][j] !== y[i][j]) {
return true;
return false;
}),
tap(graphicArray => this.renderer.drawScreen(graphicArray))
);
run$ observable, pipes from ticker$ observable and gets latest cpu state and calls runCycle function. After CPU cycles finishes it emits the new state by calling $stateSubject.next and that side effect will update our state and trigger our chain and eventually will come back to runCycle again.
After updating the state with the new state pipe continues with takeWhile.
takeWhile operator is very straight forward as well. It will going to take values from stream while one of those conditions are valid.
Then I map the state to graphicArray because I only need graphicArray to print the screen. I am emitting only if graphicArray is different than the previous one. I have written simple inefficient 2d array comparison for that purpose. In the end I am calling drawScreen method in the renderer.
BONUS
pauseClickEvent is used to pause the game or resume the game.
It’s first taking the current state and using it to change the isPaused field in the state.
pauseClickEvent$ = fromEvent(document.getElementById('pauseButton'), 'click').pipe(
withLatestFrom(this.state$),
tap(([,state]) => {
this.stateSubject$.next({ isPaused: !state.isPaused });
})
);
Merging all events and subscribing to all of them with one single subscribe.
events$ = merge(this.loadGameEvent$, this.keyPress$, this.startClickEvent$, this.stopClickEvent$,this.pauseClickEvent$);
test.events$.subscribe();
Since reading file is a asynchronous task I wanted to read it with observable.
export const readFile = (blob: Blob): Observable<ArrayBuffer> =>
{
if (!(blob instanceof Blob)) {
return throwError(new Error('`blob` must be an instance of File or Blob.'));
}
return Observable.create(obs => {
const reader = new FileReader();
reader.onerror = err => obs.error(err);
reader.onabort = err => obs.error(err);
reader.onload = () => obs.next(reader.result);
reader.onloadend = () => obs.complete();
return reader.readAsArrayBuffer(blob);
});
}
readFile function is taking the blob and returning observable ArrayBuffer.
Observable.create
lets you create an observable. We know that when asynchronous task completes, we push(emit) that value to observer's function by calling next function of the observable. So next function is the function that observer subscribed with.
when reader.readAsArrayBuffer completes it calls it's callback functions such as reader.onload, reader.onerror etc.. so by asigning obs.next to reader.onload we are sure that when reading is complete it will emit that value to observer.
I hope it helped you to learn something new or it helped to refresh your memory. If you find something that I did wrong or found some better ways to implement it. Please let me know. Thanks!
Top comments (0)