Skip to content

Commit ea5f57a

Browse files
committed
Squashed commit of the following:
commit da3b132 Author: kirjavascript <[email protected]> Date: Fri Jan 5 02:05:22 2024 +0000 test wording commit c8e92b4 Author: kirjavascript <[email protected]> Date: Fri Jan 5 01:11:45 2024 +0000 test sps commit ae0eb46 Merge: afd957f 1ca31c1 Author: kirjavascript <[email protected]> Date: Thu Jan 4 23:52:42 2024 +0000 Merge branch 'tests' commit 1ca31c1 Author: kirjavascript <[email protected]> Date: Thu Jan 4 23:52:25 2024 +0000 add SPS tests commit 178875d Author: kirjavascript <[email protected]> Date: Tue Jan 2 00:28:20 2024 +0000 SPS to tests commit 91e45bc Author: kirjavascript <[email protected]> Date: Sat Dec 30 17:45:19 2023 +0000 read addresses from debug info commit b9a086e Author: kirjavascript <[email protected]> Date: Sat Dec 30 03:42:06 2023 +0000 add pushdown points test commit 79686df Author: kirjavascript <[email protected]> Date: Sat Dec 30 03:01:52 2023 +0000 basic emulator testing framework commit afd957f Author: kirjavascript <[email protected]> Date: Fri Dec 29 22:48:11 2023 +0000 readme note commit d49df68 Author: kirjavascript <[email protected]> Date: Fri Dec 29 22:39:52 2023 +0000 initial test framework
1 parent 4e7f6da commit ea5f57a

File tree

10 files changed

+403
-1
lines changed

10 files changed

+403
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ tetris.lbl
99
tetris.map
1010
tetris.dbg
1111
release/*
12+
target

tests/Cargo.lock

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "gym-tests"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
gumdrop = "0.8.1"
8+
rusticnes-core = { git = "https://github.com/zeta0134/rusticnes-core.git", version = "0.2.0" }

tests/src/block.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#[derive(Debug, PartialEq)]
2+
pub enum Block {
3+
T, J, Z, O, S, L, I, Unknown(u8)
4+
}
5+
6+
impl From<u8> for Block {
7+
fn from(value: u8) -> Self {
8+
match value {
9+
0x2 => Block::T,
10+
0x7 => Block::J,
11+
0x8 => Block::Z,
12+
0xA => Block::O,
13+
0xB => Block::S,
14+
0xE => Block::L,
15+
0x12 => Block::I,
16+
_ => Block::Unknown(value),
17+
}
18+
}
19+
}
20+
21+
impl From<char> for Block {
22+
fn from(value: char) -> Self {
23+
match value {
24+
'T' => Block::T,
25+
'J' => Block::J,
26+
'Z' => Block::Z,
27+
'O' => Block::O,
28+
'S' => Block::S,
29+
'L' => Block::L,
30+
'I' => Block::I,
31+
_ => Block::Unknown(value as u8),
32+
}
33+
}
34+
}

tests/src/labels.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use std::collections::HashMap;
2+
use std::sync::OnceLock;
3+
4+
fn parse_debug_line(line: &str) -> (String, u16) {
5+
let pairs: Vec<&str> = line.split(',').collect();
6+
7+
let mut name = String::new();
8+
let mut val = 0;
9+
10+
for pair in pairs {
11+
let key_val: Vec<&str> = pair.split('=').collect();
12+
if key_val.len() == 2 {
13+
let key = key_val[0].trim();
14+
let value = key_val[1].trim();
15+
16+
match key {
17+
"name" => name = value.trim_matches('"').to_string(),
18+
"val" => {
19+
if let Ok(hex_value) = u16::from_str_radix(value.trim_start_matches("0x"), 16) {
20+
val = hex_value;
21+
}
22+
}
23+
_ => {}
24+
}
25+
}
26+
}
27+
28+
(name, val)
29+
}
30+
31+
fn labels() -> &'static HashMap<String, u16> {
32+
static LABELS: OnceLock<HashMap<String, u16>> = OnceLock::new();
33+
LABELS.get_or_init(|| {
34+
let text = include_str!("../../tetris.dbg");
35+
let mut labels: HashMap<String, u16> = HashMap::new();
36+
37+
for line in text.lines() {
38+
let (name, val) = parse_debug_line(line);
39+
40+
if !name.is_empty() {
41+
labels.insert(name, val);
42+
}
43+
}
44+
45+
labels
46+
})
47+
}
48+
49+
fn addrs() -> &'static HashMap<u16, String> {
50+
static LABELS: OnceLock<HashMap<u16, String>> = OnceLock::new();
51+
LABELS.get_or_init(|| {
52+
let text = include_str!("../../tetris.dbg");
53+
let mut labels: HashMap<u16, String> = HashMap::new();
54+
55+
for line in text.lines() {
56+
let (name, val) = parse_debug_line(line);
57+
58+
if !name.is_empty() {
59+
labels.insert(val, name);
60+
}
61+
}
62+
63+
labels
64+
})
65+
}
66+
67+
pub fn get(label: &str) -> u16 {
68+
*labels().get(label).expect(&format!("label {} not found", label))
69+
}
70+
71+
pub fn from_addr(addr: u16) -> Option<&'static String> {
72+
addrs().get(&addr)
73+
}

tests/src/main.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
mod labels;
2+
mod pushdown;
3+
mod util;
4+
mod block;
5+
mod sps;
6+
7+
use gumdrop::Options;
8+
9+
fn parse_hex(s: &str) -> Result<u32, std::num::ParseIntError> {
10+
u32::from_str_radix(s, 16)
11+
}
12+
13+
#[derive(Debug, Options)]
14+
struct TestOptions {
15+
help: bool,
16+
#[options(help = "run tests")]
17+
test: bool,
18+
#[options(help = "set SPS seed", parse(try_from_str = "parse_hex"))]
19+
sps_seed: u32,
20+
#[options(help = "print SPS pieces")]
21+
sps_qty: u32,
22+
}
23+
24+
fn main() {
25+
let options = TestOptions::parse_args_default_or_exit();
26+
27+
// run tests
28+
if options.test {
29+
sps::test();
30+
println!("sps is the same!");
31+
pushdown::test();
32+
println!("pushdown works!");
33+
}
34+
35+
// print SPS sequences
36+
if options.sps_qty > 0 {
37+
let mut blocks = sps::SPS::new();
38+
39+
blocks.set_input((
40+
((options.sps_seed >> 16) & 0xFF) as u8,
41+
((options.sps_seed >> 8) & 0xFF) as u8,
42+
(options.sps_seed& 0xFF) as u8,
43+
));
44+
45+
for i in 0..options.sps_qty {
46+
print!("{:?}", blocks.next());
47+
}
48+
}
49+
50+
// TODO: cycle counts for modes
51+
}

tests/src/pushdown.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use crate::{labels, util};
2+
3+
pub fn test() {
4+
let mut emu = util::emulator(None);
5+
6+
for pushdown in 2..15 {
7+
[0..1000, 24500..25500].into_iter().for_each(|range| {
8+
for score in range {
9+
util::set_score(&mut emu, score);
10+
11+
emu.registers.pc = labels::get("addPushDownPoints");
12+
emu.memory.iram_raw[labels::get("holdDownPoints") as usize] = pushdown;
13+
14+
util::run_to_return(&mut emu, false);
15+
16+
let reference = pushdown_impl(pushdown, score as u16) as u32;
17+
18+
assert_eq!(reference, util::get_score(&mut emu) - score);
19+
}
20+
});
21+
}
22+
}
23+
24+
// reference implementation - tested against the original game
25+
// may seem weird - designed to be translated to assembly
26+
fn pushdown_impl(pushdown: u8, score: u16) -> u16 {
27+
let ones = score % 10;
28+
let hundredths = score % 100;
29+
let mut newscore = ones as u8 + (pushdown - 1);
30+
if newscore & 0xF > 9 {
31+
newscore += 6;
32+
}
33+
34+
let low = (newscore & 0xF) as u16;
35+
let high = ((newscore & 0xF0) / 16 * 10) as u16;
36+
37+
let mut newscore = high + (hundredths - ones);
38+
let nextscore = newscore + low;
39+
40+
if nextscore <= 100 {
41+
newscore = nextscore;
42+
}
43+
44+
newscore + (score - hundredths) - score
45+
}

tests/src/sps.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use crate::{block, labels, util};
2+
use rusticnes_core::nes::NesState;
3+
4+
pub struct SPS {
5+
emu: NesState,
6+
}
7+
8+
impl SPS {
9+
pub fn new() -> Self {
10+
Self {
11+
emu: util::emulator(None),
12+
}
13+
}
14+
15+
pub fn set_input(&mut self, seed: (u8, u8, u8)) {
16+
self.emu.memory.iram_raw[(labels::get("set_seed_input") + 0) as usize] = seed.0;
17+
self.emu.memory.iram_raw[(labels::get("set_seed") + 0) as usize] = seed.0;
18+
self.emu.memory.iram_raw[(labels::get("set_seed_input") + 1) as usize] = seed.1;
19+
self.emu.memory.iram_raw[(labels::get("set_seed") + 1) as usize] = seed.1;
20+
self.emu.memory.iram_raw[(labels::get("set_seed_input") + 2) as usize] = seed.2;
21+
self.emu.memory.iram_raw[(labels::get("set_seed") + 2) as usize] = seed.2;
22+
}
23+
24+
pub fn next(&mut self) -> block::Block {
25+
self.emu.registers.pc = labels::get("pickTetriminoSeed");
26+
27+
util::run_to_return(&mut self.emu, false);
28+
29+
self.emu.memory.iram_raw[labels::get("spawnID") as usize].into()
30+
}
31+
}
32+
33+
pub fn test() {
34+
let mut blocks = SPS::new();
35+
36+
blocks.set_input((0x10, 0x10, 0x10));
37+
"ZJOTLTZJLZJSZISIJOLITJSILZJILITSISZOITIZSZJLLTIOZJZSZISIJZTIZJTSOJSJISJOOTSJTOTZSZTZSLTZTOTSIZJZIJIL".chars().for_each(|block| {
38+
assert_eq!(blocks.next(), block.into());
39+
});
40+
41+
blocks.set_input((0x12, 0x34, 0x56));
42+
"ZTZIJIJOZTSOSZJZOSLIOIJIJSTZSTTJISSTOIZJITJOZJITSOSZSJLTISJOITTLSLJTZTZOZSLJTJZSLTSOTLOJLSJSJTJILOJS".chars().for_each(|block| {
43+
assert_eq!(blocks.next(), block.into());
44+
});
45+
46+
blocks.set_input((0x87, 0xAB, 0x12));
47+
"OZIJSOTZSJTSTJZLOLJOJISOZOIOZJITILSSJZLOIJSTITLSOJILTSOOLZOOIJOZLTLSISIJIJTOLSIJILSLOLJLTOSOSLOIZSIS".chars().for_each(|block| {
48+
assert_eq!(blocks.next(), block.into());
49+
});
50+
51+
blocks.set_input((0x13, 0x37, 0x02));
52+
"OJSTZSIOLSIJTSZILJZJJLZLISISJTLZTSZTJOJOSJSZLITJOIOTITILTOSTJSZTSOOIOJSIITLJOZSIOJOTZTLJLIOJLITSSLSLIJIIOLOISLZJLIJJTIZIJOJISLTIJTOTZIOILSTTLTZIZJSOLOZOZOLOTZTZOTZOSIOTJJTSIZSOLTOLIZSOZOTZISLJTSZLOISO".chars().for_each(|block| {
53+
assert_eq!(blocks.next(), block.into());
54+
});
55+
}

0 commit comments

Comments
 (0)