Newer
Older
use chip_ahoyto::chip8::{Chip8, SCREEN_PIXEL_HEIGHT, SCREEN_PIXEL_WIDTH};
use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum, surface::Surface, image::LoadSurface};
use std::{fs::File, io::Read};
const PIXEL_SET: [u8; 3] = [80, 203, 147];
const SYSTEM_HZ: u32 = 240;
const SCREEN_SCALE: f32 = 15.0;
const TITLE: &str = "Drag and drop the ROM file to play";
fn main() {
let sdl = sdl2::init().unwrap();
let video_subsystem = sdl.video().unwrap();
let mut timer_subsystem = sdl.timer().unwrap();
// creates the system window that is going to be used to
// show the emulator and sets it to the central are o screen
.window(
TITLE,
SCREEN_SCALE as u32 * SCREEN_PIXEL_WIDTH as u32,
SCREEN_SCALE as u32 * SCREEN_PIXEL_HEIGHT as u32,
)
.resizable()
.position_centered()
.build()
.unwrap();
// updates the icon of the window to reflect the image
// and style of the emulator
let surface_ref = Surface::from_file("./resources/icon.png").unwrap();
let mut canvas = window.into_canvas().build().unwrap();
canvas.set_scale(SCREEN_SCALE, SCREEN_SCALE).unwrap();
canvas.clear();
canvas.present();
let texture_creator = canvas.texture_creator();
let mut texture = texture_creator
.create_texture_streaming(
PixelFormatEnum::RGB24,
SCREEN_PIXEL_WIDTH as u32,
SCREEN_PIXEL_HEIGHT as u32,
)
.unwrap();
let mut chip8 = Chip8::new();
let mut game_loaded = false;
let tick_interval = 1000 / SYSTEM_HZ;
let mut last_update_time = 0;
let mut event_pump = sdl.event_pump().unwrap();
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
'main: loop {
while let Some(event) = event_pump.poll_event() {
match event {
Event::Quit { .. } => break 'main,
Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => break 'main,
Event::DropFile { filename, .. } => {
let rom = read_file(&filename);
chip8 = Chip8::new();
chip8.load_rom(&rom);
chip8.reset();
game_loaded = true;
canvas
.window_mut()
.set_title(&format!("{} [Currently playing: {}]", TITLE, filename))
.unwrap();
None
}
Event::KeyDown {
keycode: Some(keycode),
..
} if game_loaded => key_to_btn(keycode).map(|btn| chip8.key_press(btn)),
Event::KeyUp {
keycode: Some(keycode),
..
} if game_loaded => key_to_btn(keycode).map(|btn| chip8.key_lift(btn)),
_ => None,
};
}
let current_time = timer_subsystem.ticks();
let delta_t = current_time - last_update_time;
if game_loaded && tick_interval > delta_t {
// runs the tick operation in the CHIP-8 system,
// effectively changing the logic state of the machine
chip8.clock();
chip8.clock_dt();
chip8.clock_st();
timer_subsystem.delay(tick_interval - delta_t);
// @todo this looks to be very slow!
// we should use a callback on pixel buffer change
// to make this a faster thing
let mut rgb_pixels = vec![];
for p in chip8.pixels() {
rgb_pixels.extend_from_slice(&[
p * PIXEL_SET[0],
p * PIXEL_SET[1],
p * PIXEL_SET[2],
])
}
// creates a texture based on the RGB pixel buffer
// and copies that to the canvas for presentation
texture
.update(None, &rgb_pixels, SCREEN_PIXEL_WIDTH as usize * 3)
.unwrap();
canvas.copy(&texture, None, None).unwrap();
canvas.present();
println!("CENAS");
println!("{}", chip8);
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
}
last_update_time = current_time;
}
}
fn key_to_btn(keycode: Keycode) -> Option<u8> {
match keycode {
Keycode::Num1 => Some(0x01),
Keycode::Num2 => Some(0x02),
Keycode::Num3 => Some(0x03),
Keycode::Num4 => Some(0x0C),
Keycode::Q => Some(0x04),
Keycode::W => Some(0x05),
Keycode::E => Some(0x06),
Keycode::R => Some(0x0D),
Keycode::A => Some(0x07),
Keycode::S => Some(0x08),
Keycode::D => Some(0x09),
Keycode::F => Some(0x0E),
Keycode::Z => Some(0x0A),
Keycode::X => Some(0x00),
Keycode::C => Some(0x0B),
Keycode::V => Some(0x0F),
_ => None,
}
}
fn read_file(path: &str) -> Vec<u8> {
let mut file = File::open(path).unwrap();
let mut rom = Vec::new();
file.read_to_end(&mut rom).unwrap();
rom
}