///|
/// Portable move coordinates, independent of internal move numbering.
pub(all) struct Step {
  from : Cell
  to : Cell
} derive(Debug, Eq)

///|
/// Convert only valid directed board jumps. Occupancy is checked during replay.
pub fn Board::encode_steps(
  self : Board,
  moves : Array[Int],
) -> Array[Step] raise PegError {
  if moves.length() > 63 {
    raise InvalidTranscript(63)
  }
  let out : Array[Step] = []
  for i = 0; i < moves.length(); i = i + 1 {
    let id = moves[i]
    if id < 0 || id >= self.jumps.length() {
      raise InvalidTranscript(i)
    }
    let jump = self.jumps[id]
    out.push({ from: self.cells[jump.from], to: self.cells[jump.to] })
  }
  out
}

///|
pub fn Board::decode_steps(
  self : Board,
  steps : Array[Step],
) -> Array[Int] raise PegError {
  if steps.length() > 63 {
    raise InvalidTranscript(63)
  }
  let out : Array[Int] = []
  for i = 0; i < steps.length(); i = i + 1 {
    let step = steps[i]
    let mut found : Int? = None
    for id = 0; id < self.jumps.length(); id = id + 1 {
      let jump = self.jumps[id]
      if self.cells[jump.from] == step.from && self.cells[jump.to] == step.to {
        found = Some(id)
      }
    }
    match found {
      Some(id) => out.push(id)
      None => raise InvalidTranscript(i)
    }
  }
  out
}

///|
pub fn Board::verify_steps(
  self : Board,
  start : Position,
  goal : Goal,
  steps : Array[Step],
) -> Bool raise PegError {
  self.verify_solution(start, self.decode_steps(steps), goal)
}