#![deny(missing_docs)]
use handwired_rs::dsl::HwType;
use handwired_rs::handwired;
#[derive(Clone, Copy, Debug, PartialEq, HwType)]
pub struct Pig {
#[range(0, 100)]
pub scores: [u8; 2],
#[range(0, 1)]
pub turn: u8,
#[range(0, 30)]
pub pot: u8,
}
#[handwired]
pub fn new_game(#[range(0, 1)] first: u8) -> Pig {
Pig { scores: [0, 0], turn: first, pot: 0 }
}
#[handwired]
pub fn pig_step(state: Pig, #[range(1, 6)] roll: u8, hold: bool) -> Pig {
let mut next = state;
if hold {
next.scores[state.turn as usize] += state.pot;
next.pot = 0;
next.turn = 1 - state.turn;
} else if roll == 1 {
next.pot = 0;
next.turn = 1 - state.turn;
} else {
next.pot += roll;
}
next
}
#[test]
fn a_game_state_is_a_parameter_a_local_and_a_result() {
let compiled = new_game_circuit();
assert_eq!(compiled.eval(&(1,)).unwrap(), Pig { scores: [0, 0], turn: 1, pot: 0 });
let compiled = pig_step_circuit();
assert_eq!(compiled.verify(20_000).unwrap().skipped, 0);
let mut state = Pig { scores: [0, 0], turn: 0, pot: 0 };
let moves = [(4, false), (5, false), (3, true), (1, false), (6, false), (2, true), (6, false), (6, true)];
for (roll, hold) in moves {
let next = compiled.eval(&(state, roll, hold)).unwrap();
assert_eq!(next, pig_step(state, roll, hold));
state = next;
}
assert_eq!(state, Pig { scores: [15, 6], turn: 0, pot: 0 });
let ranges: Vec<(f64, f64)> = (0..4)
.map(|cell| {
let range = compiled.circuit.range(compiled.circuit.source(cell));
(range.min, range.max)
})
.collect();
assert_eq!(ranges, [(0.0, 100.0), (0.0, 100.0), (0.0, 1.0), (0.0, 30.0)]);
assert_eq!(<Pig as HwType>::CELLS, 4);
}
#[derive(Clone, Copy, Debug, PartialEq, HwType)]
pub struct Unit {
#[range(1, 9)]
pub damage: u8,
#[range(0, 40)]
pub health: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, HwType)]
pub struct Board {
pub units: [Unit; 3],
#[range(0, 10)]
pub attacks: u8,
}
#[handwired]
pub fn attack(board: Board, #[range(0, 2)] attacker: u8, #[range(0, 2)] target: u8) -> Board {
let mut next = board;
let damage = board.units[attacker as usize].damage;
let health = board.units[target as usize].health;
next.units[target as usize].health = health.saturating_sub(damage);
next.attacks += 1;
next
}
#[test]
fn nested_structs_and_arrays_of_structs_at_computed_indices() {
let compiled = attack_circuit();
assert_eq!(compiled.verify(20_000).unwrap().skipped, 0);
let board = Board {
units: [Unit { damage: 3, health: 10 }, Unit { damage: 7, health: 5 }, Unit { damage: 1, health: 40 }],
attacks: 0,
};
let records: Vec<(Board, u8, u8)> = (0..9u8).map(|pair| (board, pair / 3, pair % 3)).collect();
for (record, result) in records.iter().zip(compiled.run_batch(&records).unwrap()) {
assert_eq!(result, attack(record.0, record.1, record.2));
}
assert_eq!(compiled.eval(&(board, 1, 0)).unwrap().units[0].health, 3);
}
#[handwired]
pub fn total_health(#[ranges([(1, 2), (0, 7), (1, 2), (0, 7), (1, 2), (0, 7), (0, 0)])] board: Board) -> u16 {
board.units[0].health as u16 + board.units[1].health as u16 + board.units[2].health as u16
}
#[test]
fn parameter_ranges_override_field_ranges() {
let compiled = total_health_circuit();
let health = compiled.circuit.range(compiled.circuit.source(1));
assert_eq!((health.min, health.max), (0.0, 7.0));
assert_eq!(compiled.verify(2_000).unwrap().skipped, 0);
}
#[handwired]
pub fn resolve(state: Pig, #[range(1, 6)] roll: u8, hold: bool) -> Pig {
if state.scores[0] >= 100 {
return Pig { pot: 0, ..state };
}
let busted: Pig = Pig { pot: 0, turn: 1 - state.turn, ..state };
let after = if roll == 1 { busted } else { Pig { pot: state.pot + roll, ..state } };
if hold { Pig { pot: 0, turn: 1 - state.turn, ..state } } else { after }
}
#[test]
fn structs_in_if_values_updates_and_early_returns() {
let compiled = resolve_circuit();
assert_eq!(compiled.verify(20_000).unwrap().skipped, 0);
}
#[test]
fn struct_results_decode_from_batches() {
let compiled = pig_step_circuit();
let records: Vec<(Pig, u8, bool)> = (0..300u32)
.map(|n| {
(
Pig { scores: [(n % 90) as u8, (n * 7 % 90) as u8], turn: (n % 2) as u8, pot: (n % 25) as u8 },
(1 + n % 6) as u8,
n % 3 == 0,
)
})
.collect();
for (record, result) in records.iter().zip(compiled.run_batch(&records).unwrap()) {
assert_eq!(result, pig_step(record.0, record.1, record.2));
}
}