clone

an offline sliding-block puzzle game in gleam and lustre, with an endless ladder of puzzles ranked by search

main
klot.ski / test / klotski / domain_test.gleam
domain_test.gleam 3.1K
1import gleam/list2import gleeunit/should3import klotski/domain.{Down, Goal, Piece, Right, Single}4import klotski/families56pub fn a_family_packing_parses_into_its_pieces_test() {7 let family = families.at(0)8 let pieces =9 domain.pieces_from_grid(families.board(family), families.grid(family))10 pieces |> list.length |> should.equal(10)11}1213/// Every packing but CLASSIC's is a solved one. CLASSIC ships the traditional14/// arrangement instead, which is the whole reason that family is worth having —15/// and it is still surveyable, because the sweep only needs a packing whose16/// component contains a solved state, not a solved packing.17pub fn every_packing_but_the_traditional_one_is_seated_test() {18 families.all()19 |> list.map(fn(family) {20 let pieces =21 domain.pieces_from_grid(families.board(family), families.grid(family))22 #(family.id, domain.is_solved(families.board(family), pieces))23 })24 |> should.equal([25 #("classic", False),26 #("narrow", True),27 #("compact", True),28 #("span", True),29 #("wide", True),30 #("yard", True),31 #("corner", True),32 #("mixed", True),33 ])34}3536pub fn a_piece_moves_only_through_free_cells_test() {37 let family = families.by_id("compact")38 let board = families.board(family)39 let pieces = domain.pieces_from_grid(board, families.grid(family))40 let assert Ok(goal) = list.find(pieces, fn(p) { p.shape == Goal })4142 // COMPACT is seated on the bottom row with a column of air either side of it,43 // so the big block is against the floor and has exactly one cell of travel44 // each way.45 domain.can_move(board, pieces, goal.id, Down) |> should.be_false46 domain.free_cells(board, pieces, goal.id, Right) |> should.equal(1)47}4849pub fn moving_and_reversing_restores_a_board_test() {50 let family = families.at(0)51 let board = families.board(family)52 let pieces = domain.pieces_from_grid(board, families.grid(family))53 // The classic packing's only air is the two cells under the middle singles.54 let assert Ok(single) =55 list.find(pieces, fn(p) { p.shape == Single && p.x == 1 && p.y == 3 })5657 let assert Ok(moved) = domain.move_one(board, pieces, single.id, Down)58 domain.move_unchecked(moved, single.id, domain.opposite(Down))59 |> should.equal(pieces)60}6162pub fn goal_is_solved_only_when_seated_test() {63 let board = domain.Board(4, 4, 1, 2)64 let away = [Piece(0, Goal, 0, 0, 2, 2)]65 let seated = [Piece(0, Goal, 1, 2, 2, 2)]6667 domain.is_solved(board, away) |> should.be_false68 domain.is_solved(board, seated) |> should.be_true69 domain.goal_gap(board, away) |> should.equal(3)70}7172/// The key is the grid of *shape classes*, not of piece identities, because two73/// pieces of the same shape are interchangeable. Swapping two singles has to be74/// the same configuration or the survey counts the same board twice.75pub fn the_key_is_blind_to_piece_identity_test() {76 let board = domain.Board(3, 1, 0, 0)77 let a = [Piece(0, Single, 0, 0, 1, 1), Piece(1, Single, 2, 0, 1, 1)]78 let b = [Piece(1, Single, 0, 0, 1, 1), Piece(0, Single, 2, 0, 1, 1)]79 domain.encode_state(board, a) |> should.equal(domain.encode_state(board, b))80}