mod aggregate;
use aggregate::{BlobGame, TileState};
use distributed::{AggregateBuilder, HashMapRepository};
const P: TileState = TileState::Player;
const U: TileState = TileState::Unvisited;
const H: TileState = TileState::Hole;
fn easy_test_level() -> Vec<Vec<TileState>> {
vec![
vec![P, U, U, U, U, U, U, U, U, H],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
]
}
fn no_holes_level() -> Vec<Vec<TileState>> {
vec![
vec![P, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
vec![U, U, U, U, U, U, U, U, U, U],
]
}
fn die_mother_clucka() -> Vec<Vec<TileState>> {
vec![
vec![P, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
vec![H, H, H, H, H, H, H, H, H, H],
]
}
fn win_no_holes_level(game: &mut BlobGame) {
let height = 10;
let width = 10;
for col in 0..width {
if col % 2 == 0 {
for _ in 0..(height - 1) {
game.down(None).unwrap();
}
} else {
for _ in 0..(height - 1) {
game.up(None).unwrap();
}
}
if col < width - 1 {
game.right(None).unwrap();
}
}
}
#[test]
fn should_construct_blob_game_model() {
let game = BlobGame::new();
assert!(!game.is_player_dead());
assert!(game.is_current_level_completed()); assert_eq!(game.score(), 0);
}
#[test]
fn should_not_move_before_game_initialized() {
let mut game = BlobGame::new();
game.initialize(
"game-1".into(),
"0x0000".into(),
"minigame-1".into(),
false,
None,
)
.unwrap();
let events_before = game.entity.events().len();
game.up(None).unwrap();
game.down(None).unwrap();
game.left(None).unwrap();
game.right(None).unwrap();
assert_eq!(game.entity.events().len(), events_before);
}
#[test]
fn should_not_start_level_before_initialized() {
let mut game = BlobGame::new();
let events_before = game.entity.events().len();
game.start_next_level(easy_test_level()).unwrap();
assert!(game.entity.events().len() > events_before);
}
#[test]
fn should_work_with_normal_gameplay_simulation() {
let mut game = BlobGame::new();
game.initialize(
"blob-game-gameplay".into(),
"0x0000test0000".into(),
"test-minigame-id-gameplay".into(),
false,
None,
)
.unwrap();
assert_eq!(game.address(), "0x0000test0000");
assert_eq!(game.minigame_id(), "test-minigame-id-gameplay");
assert_eq!(game.score(), 0);
game.start_next_level(easy_test_level()).unwrap();
assert_eq!(game.score(), 0);
assert!(!game.is_current_level_completed());
let events_before = game.entity.events().len();
game.up(None).unwrap();
game.left(None).unwrap();
assert_eq!(game.entity.events().len(), events_before);
assert_eq!(game.score(), 0);
for i in 0..9 {
game.down(None).unwrap();
assert_eq!(game.score(), (i + 1) as u32);
}
let events_before = game.entity.events().len();
game.down(None).unwrap();
assert_eq!(game.entity.events().len(), events_before);
for _ in 0..9 {
game.right(None).unwrap();
}
let events_before = game.entity.events().len();
game.right(None).unwrap();
assert_eq!(game.entity.events().len(), events_before);
game.up(None).unwrap();
game.left(None).unwrap();
game.down(None).unwrap();
assert!(game.is_player_dead());
assert_eq!(game.score(), 20);
}
#[test]
fn should_die_when_moving_into_self() {
let mut game = BlobGame::new();
game.initialize(
"blob-game-suicide".into(),
"0x0000test0000".into(),
"test-minigame-id-suicide".into(),
false,
None,
)
.unwrap();
game.start_next_level(easy_test_level()).unwrap();
assert!(!game.is_player_dead());
game.down(None).unwrap();
game.up(None).unwrap(); assert!(game.is_player_dead());
assert!(!game.is_current_level_completed());
}
#[test]
fn should_win_when_all_blocks_visited_except_holes() {
let mut game = BlobGame::new();
game.initialize(
"blob-game-win".into(),
"0x0000test0000".into(),
"test-minigame-id-win".into(),
false,
None,
)
.unwrap();
game.start_next_level(no_holes_level()).unwrap();
assert!(!game.is_current_level_completed());
win_no_holes_level(&mut game);
assert!(game.is_current_level_completed());
assert_eq!(game.score(), 99); }
#[test]
fn should_die_when_falling_in_hole() {
let mut game = BlobGame::new();
game.initialize(
"blob-game-hole".into(),
"0x0000test0000".into(),
"test-minigame-id-hole".into(),
false,
None,
)
.unwrap();
game.start_next_level(die_mother_clucka()).unwrap();
game.right(None).unwrap(); assert!(game.is_player_dead());
assert!(!game.is_current_level_completed());
assert_eq!(game.score(), 0);
}
#[test]
fn should_be_able_to_add_new_levels_each_time_you_win() {
let mut game = BlobGame::new();
game.initialize(
"blob-game-multi".into(),
"0x0000test0000".into(),
"test-minigame-id-multi".into(),
false,
None,
)
.unwrap();
game.start_next_level(no_holes_level()).unwrap();
assert!(!game.is_current_level_completed());
win_no_holes_level(&mut game);
assert!(game.is_current_level_completed());
game.start_next_level(no_holes_level()).unwrap();
assert!(!game.is_current_level_completed());
win_no_holes_level(&mut game);
assert!(game.is_current_level_completed());
game.start_next_level(no_holes_level()).unwrap();
assert!(!game.is_current_level_completed());
win_no_holes_level(&mut game);
assert!(game.is_current_level_completed());
assert_eq!(game.score(), 99 * 3);
game.start_next_level(die_mother_clucka()).unwrap();
assert!(!game.is_current_level_completed());
game.right(None).unwrap();
assert!(!game.is_current_level_completed());
assert!(game.is_player_dead());
assert_eq!(game.score(), 99 * 3); }
#[test]
fn should_not_start_next_level_in_middle_of_current_level() {
let mut game = BlobGame::new();
game.initialize(
"blob-game-mid".into(),
"0x0000test0000".into(),
"test-minigame-id-mid".into(),
false,
None,
)
.unwrap();
game.start_next_level(no_holes_level()).unwrap();
assert!(!game.is_current_level_completed());
let events_before = game.entity.events().len();
game.start_next_level(no_holes_level()).unwrap();
assert_eq!(game.entity.events().len(), events_before);
assert!(!game.is_current_level_completed());
}
#[test]
fn should_work_with_timer_mode() {
let start_time = 1000u64;
let mut game = BlobGame::new();
game.initialize(
"blob-game-timed".into(),
"0x0000test0000".into(),
"test-minigame-id-timed".into(),
true,
Some(start_time),
)
.unwrap();
game.start_next_level(no_holes_level()).unwrap();
game.right(Some(start_time + 1000)).unwrap();
game.right(Some(start_time + 2000)).unwrap();
game.right(Some(start_time + 3000)).unwrap();
game.right(Some(start_time + 4000)).unwrap();
assert_eq!(game.score(), 4);
assert!(!game.is_player_dead());
let six_minutes_ms = 6 * 60 * 1000;
game.right(Some(start_time + six_minutes_ms)).unwrap();
assert!(game.is_player_dead());
assert!(!game.is_current_level_completed());
assert_eq!(game.score(), 4); }
#[tokio::test]
async fn replay_restores_game_state() {
let repo = HashMapRepository::new().aggregate::<BlobGame>();
let mut game = BlobGame::new();
game.initialize(
"game-replay".into(),
"player".into(),
"mg-1".into(),
false,
None,
)
.unwrap();
game.start_next_level(no_holes_level()).unwrap();
game.down(None).unwrap();
game.down(None).unwrap();
game.right(None).unwrap();
repo.commit(&mut game).await.unwrap();
let restored = repo.get("game-replay").await.unwrap().unwrap();
assert_eq!(restored.score(), 3);
assert!(!restored.is_current_level_completed());
assert!(!restored.is_player_dead());
let mut restored = restored;
restored.down(None).unwrap();
assert_eq!(restored.score(), 4);
}
#[test]
fn snapshot_captures_full_state() {
let mut game = BlobGame::new();
game.initialize(
"game-snap".into(),
"player-addr".into(),
"mg-1".into(),
false,
None,
)
.unwrap();
game.start_next_level(no_holes_level()).unwrap();
game.right(None).unwrap();
game.down(None).unwrap();
let snapshot = game.snapshot();
assert_eq!(snapshot.id, "game-snap");
assert_eq!(snapshot.address, "player-addr");
assert_eq!(snapshot.minigame_id, "mg-1");
assert_eq!(snapshot.current_level, 1);
assert_eq!(snapshot.score, 2);
assert!(!snapshot.player_dead);
assert!(!snapshot.current_level_completed);
assert_eq!(snapshot.levels.len(), 1);
}