Skip to content

Commit

Permalink
Completed decode(), added String state parsing/checking
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-wsp committed Apr 21, 2024
1 parent 50d62bb commit 51c7ae6
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 19 deletions.
48 changes: 35 additions & 13 deletions src/game/crossteaser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const ORIENTATION_MAP: [u64; 24] = [
/// from face x to face 5 - x.
/// Format: each rotation is 5 bits of format abc_de
/// abc: axis on which the rotation is performed
/// de: direction of the rotation. 01 = cw, 11 = ccw.
/// de: direction of the rotation. 0b01 = cw, 0b10 = 180, 0b11 = ccw.
/// Order of transformations is right to left.
/// Used for symmetries.
/// NOTE: This can likely be improved by combining transformations in a
Expand Down Expand Up @@ -154,10 +154,6 @@ const TRANSFORM_MAP: [u64; 24] = [
// game states.
const FACE_BITS: u64 = 3;
const FACE_BITMASK: u64 = 0b111;
const EMPTY_BITS: u64 = 4;
const EMPTY_BITMASK: u64 = 0b1111;
const PIECE_BITS: u64 = 5;
const PIECE_BITMASK: u64 = 0b11111;
const PIECE_SIZE: u64 = 24;

/// Converts an Orientation struct into its corresponding orientation hash,
Expand Down Expand Up @@ -301,6 +297,8 @@ impl Session {
return new_state;
}

/// Rotates entire board clockwise 90 degrees. Used only for
/// combined flip board + rotate 90 symmetry.
fn board_cw(&self, s: &UnhashedState) -> UnhashedState {
if self.width != self.length {
panic!("Cannot rotate board with unequal dimensions 90 degrees");
Expand All @@ -325,18 +323,21 @@ impl Session {
};
}

/// Rotates entire board 180 degrees
fn board_180(&self, s: &UnhashedState) -> UnhashedState {
let mut rep: Vec<Orientation> = Vec::new();
for i in (0..s.pieces.len()).rev() {
rep.push(mov::front_180(&s.pieces[i]));
let num_pieces: u64 = self.get_pieces();
for i in (0..num_pieces).rev() {
rep.push(mov::front_180(&s.pieces[i as usize]));
}
let f: u64 = self.get_pieces() - s.free;
let f: u64 = num_pieces - s.free;
return UnhashedState {
pieces: rep,
free: f,
};
}

/// Flips board over from left to right
fn flip_board(&self, s: &UnhashedState) -> UnhashedState {
let mut rep: Vec<Orientation> = Vec::new();
for row in 0..self.length {
Expand All @@ -358,6 +359,8 @@ impl Session {
};
}

/// Combines flipping board plus 90 degree rotation, this is a
/// valid symmetry due to the perpedicular slot orentation on front and back
fn flip_90(&self, s: &UnhashedState) -> UnhashedState {
if self.width == self.length {
return self.board_cw(&self.flip_board(s));
Expand All @@ -369,6 +372,11 @@ impl Session {
/// Applies a sequence of transformations on an orientation
/// See TRANSFORM_MAP for details on these sequences
/// Uses cw_on_axis() to apply transformations equivalently to any piece
/// This means it will always apply the transformations on the axis
/// speicified by TRANSFORM_MAP, no matter the piece orientation.
/// This allows us to reduce to an equivalent state
/// Where equivalent means the same sequence of moves will reach a
/// terminal state.
fn apply_transformations(&self, o: &Orientation, t: u64) -> Orientation {
let mut t_list: u64 = t;
let mut transform: u64;
Expand All @@ -394,10 +402,11 @@ impl Session {
return new_o;
}

/// Finds the canonical position of a given state
/// It does so by reducing the last piece to orientation 0,
/// Reduces the state to one with last piece orientation of 0
/// and adjusting the rest of the pieces equivalently
/// Uses apply_transformations() to properly adjust pieces
/// Note that this does not mean applying the exact same transformation
/// to every piece.
/// See apply_transformation() for additional details.
fn reduce(&self, s: &UnhashedState) -> UnhashedState {
let mut new_pieces: Vec<Orientation> = Vec::new();
let pos: u64 = hash_orientation(&s.pieces.last().unwrap());
Expand All @@ -414,6 +423,10 @@ impl Session {
};
}

/// Applies 4 physical board symmetries and finds the canonical of the set
/// -Rotate 180
/// -Flip board & rotate 90
/// -Flip board & rotate 270
fn board_sym(&self, s: &UnhashedState) -> UnhashedState {
let mut sym_list: Vec<UnhashedState> = Vec::new();
sym_list.push(s.deep_copy());
Expand All @@ -435,6 +448,7 @@ impl Session {
return sym_list.remove(min_i);
}

/// Applies 4 board symmetries and then reduces state to canonical
fn canonical(&self, s: &UnhashedState) -> UnhashedState {
return self.reduce(&self.board_sym(s));
}
Expand Down Expand Up @@ -543,6 +557,8 @@ mod mov {
};
}

/// Transforms an individual piece orientation as if it was rotated
/// 180 degrees along the front-back axis. Used for symmetries.
pub fn front_180(o: &Orientation) -> Orientation {
return Orientation {
front: o.front,
Expand All @@ -551,6 +567,8 @@ mod mov {
};
}

/// Transforms an individual piece orientation as if it was rotated
/// 180 degrees along the top-bottom axis. Used for symmetries.
pub fn top_180(o: &Orientation) -> Orientation {
return Orientation {
front: 5 - o.front,
Expand All @@ -559,6 +577,8 @@ mod mov {
};
}

/// Transforms an individual piece orientation as if it was rotated
/// 180 degrees along the right-left axis. Used for symmetries.
pub fn right_180(o: &Orientation) -> Orientation {
return Orientation {
front: 5 - o.front,
Expand All @@ -567,7 +587,7 @@ mod mov {
};
}

/// Performs a clowckwise orientation on an orientation
/// Performs a clowckwise rotation on an orientation
/// along the specified axis.
/// I am trying to figure out a way to make this faster.
pub fn cw_on_axis(o: &Orientation, axis: u64) -> Orientation {
Expand All @@ -588,6 +608,8 @@ mod mov {
}
}

/// Performs a 180 degree rotation on an orientation
/// along the specified axis.
pub fn axis_180(o: &Orientation, axis: u64) -> Orientation {
if axis == o.front || axis == 5 - o.front {
return front_180(o);
Expand Down Expand Up @@ -697,7 +719,7 @@ impl Bounded for Session {

impl Codec for Session {
fn decode(&self, string: String) -> Result<State> {
todo!()
parse_state(string, self).context("Malformed state")
}

fn encode(&self, state: State) -> String {
Expand Down
132 changes: 126 additions & 6 deletions src/game/crossteaser/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,138 @@
//! - Michael Setchko Palmerlee, 4/18/2024 ([email protected])
pub const STATE_DEFAULT: &'static str = "|0-0-0|0-X-0|0-0-0|";
pub const STATE_PATTERN: &'static str = r"^([|]([\dX]-)+[\dX])+[|]$";
pub const STATE_PATTERN: &'static str = r"^([|]([\dX]+-)+[\dX]+)+[|]";
pub const STATE_PROTOCOL: &'static str =
"Rows are separated by |, columns are separated by -, empty space is X. \
Integers 0-24 are a piece orientation as defined by ORIENTATION_MAP";

use regex::Regex;

use crate::game::crossteaser::*;
use crate::game::error::GameError;
use crate::model::State;

pub fn parse_state(
state: String,
session: &Session,
) -> Result<State, GameError> {
check_state_pattern(&state)?;
let v: Vec<String> = parse_pieces(&state);
for piece in &v {
check_valid_piece(piece)?;
}
check_free_spaces(&v, session)?;
check_num_pieces(&v, session)?;
let mut rep: Vec<Orientation> = Vec::new();
let mut empty: u64 = 0;
for (i, piece) in v.iter().enumerate() {
match piece.parse::<u64>() {
Ok(n) => rep.push(unhash_orientation(n)),
Err(_) => empty = i as u64,
}
}
Ok(session.hash(&UnhashedState {
pieces: rep,
free: empty,
}))
}

fn check_free_spaces(
v: &Vec<String>,
session: &Session,
) -> Result<(), GameError> {
if v.iter()
.filter(|s| *s == "X")
.count()
== session.free as usize
{
Ok(())
} else {
Err(GameError::StateMalformed {
game_name: NAME,
hint: format!(
"String does not match the pattern '{}'.",
STATE_PATTERN
),
})
}
}

fn check_num_pieces(
v: &Vec<String>,
session: &Session,
) -> Result<(), GameError> {
if v.len() == (session.width * session.length) as usize {
Ok(())
} else {
Err(GameError::StateMalformed {
game_name: NAME,
hint: format!(
"String does not match the pattern '{}'.",
STATE_PATTERN
),
})
}
}

fn check_state_pattern(state: &String) -> Result<(), GameError> {
let re = Regex::new(STATE_PATTERN).unwrap();
if !re.is_match(&state) {
Err(GameError::StateMalformed {
game_name: NAME,
hint: format!(
"String does not match the pattern '{}'.",
STATE_PATTERN
),
})
} else {
Ok(())
}
}

fn check_valid_piece(piece: &String) -> Result<(), GameError> {
match piece.parse::<u64>() {
Ok(n) => {
if n < 24 {
Ok(())
} else {
Err(GameError::StateMalformed {
game_name: NAME,
hint: format!(
"String does not match the pattern '{}'.",
STATE_PATTERN
),
})
}
},
Err(_) => {
if piece == "X" {
Ok(())
} else {
Err(GameError::StateMalformed {
game_name: NAME,
hint: format!(
"String does not match the pattern '{}'.",
STATE_PATTERN
),
})
}
},
}
}

fn parse_pieces(state: &str) -> Vec<String> {
state
.split(['|', '-'])
.map(|piece| piece.to_owned())
.collect()
}

#[cfg(test)]
mod test {

use super::*;
use crate::game::crossteaser::*;
use crate::game::{util::verify_history_dynamic, Game};
use std::collections::HashSet;

/* STATE STRING PARSING */

#[test]
fn test_transition() {
let session: Session = Session {
Expand Down Expand Up @@ -52,6 +169,9 @@ mod test {
unsolved.push(state);
}
}
if found.len() % 100000 == 0 {
println!("total: {}", found.len());
}
}
}
}

0 comments on commit 51c7ae6

Please sign in to comment.