#![warn(clippy::pedantic)]
#![allow(clippy::too_many_lines, clippy::missing_errors_doc)]
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
pub enum Season {
Iei2,
Xo1,
Kat2,
Iat1,
}
pub mod message;
pub mod probabilistic;
impl Season {
#[must_use]
pub fn next(self) -> Option<Self> {
match self {
Season::Iei2 => Some(Season::Xo1),
Season::Xo1 => Some(Season::Kat2),
Season::Kat2 => Some(Season::Iat1),
Season::Iat1 => None,
}
}
#[must_use]
pub fn to_index(self) -> usize {
match self {
Season::Iei2 => 0,
Season::Xo1 => 1,
Season::Kat2 => 2,
Season::Iat1 => 3,
}
}
}
use cetkaik_core::absolute;
pub mod state;
impl state::C {
#[must_use]
pub fn piece_at_flying_piece_src(&self) -> absolute::Piece {
*self
.c
.f
.board
.get(&self.c.flying_piece_src)
.expect("Invalid `state::C`: at `flying_piece_src` there is no piece")
}
#[must_use]
pub fn piece_at_flying_piece_step(&self) -> absolute::Piece {
*self
.c
.f
.board
.get(&self.c.flying_piece_step)
.expect("Invalid `state::C`: at `flying_piece_step` there is no piece")
}
}
#[derive(Clone, Copy, Debug)]
pub enum Rate {
X1,
X2,
X4,
X8,
X16,
X32,
X64,
}
use probabilistic::Probabilistic;
impl Rate {
#[must_use]
pub fn next(self) -> Self {
match self {
Rate::X1 => Rate::X2,
Rate::X2 => Rate::X4,
Rate::X4 => Rate::X8,
Rate::X8 => Rate::X16,
Rate::X16 => Rate::X32,
Rate::X32 | Rate::X64 => Rate::X64,
}
}
#[must_use]
pub fn num(self) -> i32 {
match self {
Rate::X1 => 1,
Rate::X2 => 2,
Rate::X4 => 4,
Rate::X8 => 8,
Rate::X16 => 16,
Rate::X32 => 32,
Rate::X64 => 64,
}
}
}
fn apply_tam_move(
old_state: &state::A,
src: absolute::Coord,
first_dest: absolute::Coord,
second_dest: absolute::Coord,
step: Option<absolute::Coord>,
config: Config,
) -> Result<Probabilistic<state::HandNotResolved>, &'static str> {
let (penalty1, is_a_hand1) = if old_state.tam_has_moved_previously {
match config.moving_tam_immediately_after_tam_has_moved {
Consequence::Allowed => (0, false),
Consequence::Penalized{penalty, is_a_hand} => (penalty, is_a_hand),
Consequence::Forbidden => return Err(
"By config, it is prohibited for tam2 to move immediately after the previous player has moved the tam2."
)
}
} else {
(0, false)
};
let (penalty2, is_a_hand2) =
if src == second_dest {
match config.tam_mun_mok {
Consequence::Forbidden => return Err(
"By config, it is prohibited for tam2 to start and end at the same position.",
),
Consequence::Allowed => (0, false),
Consequence::Penalized { penalty, is_a_hand } => (penalty, is_a_hand),
}
} else {
(0, false)
};
let mut new_field = old_state.f.clone();
let expect_tam = new_field
.board
.remove(&src)
.ok_or("expected tam2 but found an empty square")?;
if !expect_tam.is_tam2() {
return Err("expected tam2 but found a non-tam2 piece");
}
if new_field.board.contains_key(&first_dest) {
return Err("the first destination is already occupied");
}
if let Some(st) = step {
if !new_field.board.contains_key(&st) {
return Err("the stepping square is empty");
}
}
if new_field.board.contains_key(&second_dest) {
return Err("the second destination is already occupied");
}
new_field.board.insert(second_dest, absolute::Piece::Tam2);
Ok(Probabilistic::Pure(state::HandNotResolved {
previous_a_side_hop1zuo1: old_state.f.a_side_hop1zuo1.clone(),
previous_ia_side_hop1zuo1: old_state.f.ia_side_hop1zuo1.clone(),
kut2tam2_happened: false,
tam2tysak2_raw_penalty: penalty1 + penalty2,
tam2tysak2_will_trigger_taxottymok: is_a_hand1 || is_a_hand2,
rate: old_state.rate,
i_have_moved_tam_in_this_turn: true,
season: old_state.season,
scores: old_state.scores,
whose_turn: old_state.whose_turn,
f: new_field,
}))
}
fn apply_nontam_move(
old_state: &state::A,
src: absolute::Coord,
dest: absolute::Coord,
step: Option<absolute::Coord>,
config: Config,
) -> Result<Probabilistic<state::HandNotResolved>, &'static str> {
let nothing_happened = state::HandNotResolved {
previous_a_side_hop1zuo1: old_state.f.a_side_hop1zuo1.clone(),
previous_ia_side_hop1zuo1: old_state.f.ia_side_hop1zuo1.clone(),
kut2tam2_happened: !config.failure_to_complete_the_move_means_exempt_from_kut2_tam2
&& match step {
None => false,
Some(step) => matches!(old_state.f.board.get(&step), Some(absolute::Piece::Tam2)),
},
rate: old_state.rate,
i_have_moved_tam_in_this_turn: false,
season: old_state.season,
scores: old_state.scores,
whose_turn: old_state.whose_turn,
f: old_state.f.clone(),
tam2tysak2_will_trigger_taxottymok: false,
tam2tysak2_raw_penalty: 0,
};
if let Some(st) = step {
if !old_state.f.board.contains_key(&st) {
return Err("expected a stepping square but found an empty square");
}
}
let src_piece = old_state
.f
.board
.get(&src)
.ok_or("src does not contain a piece")?;
let (new_board, maybe_captured_piece) =
move_nontam_piece_from_src_to_dest_while_taking_opponent_piece_if_needed(
&old_state.f.board,
src,
dest,
old_state.whose_turn,
)?;
let mut new_field = absolute::Field {
board: new_board,
a_side_hop1zuo1: old_state.f.a_side_hop1zuo1.clone(),
ia_side_hop1zuo1: old_state.f.ia_side_hop1zuo1.clone(),
};
if let Some(absolute::NonTam2Piece { color, prof }) = maybe_captured_piece {
new_field.insert_nontam_piece_into_hop1zuo1(color, prof, old_state.whose_turn);
}
let success = state::HandNotResolved {
previous_a_side_hop1zuo1: old_state.f.a_side_hop1zuo1.clone(),
previous_ia_side_hop1zuo1: old_state.f.ia_side_hop1zuo1.clone(),
kut2tam2_happened: match step {
None => false,
Some(step) => matches!(old_state.f.board.get(&step), Some(absolute::Piece::Tam2)),
},
rate: old_state.rate,
i_have_moved_tam_in_this_turn: false,
season: old_state.season,
scores: old_state.scores,
whose_turn: old_state.whose_turn,
f: new_field,
tam2tysak2_will_trigger_taxottymok: false,
tam2tysak2_raw_penalty: 0,
};
if !absolute::is_water(src)
&& !src_piece.has_prof(cetkaik_core::Profession::Nuak1)
&& absolute::is_water(dest)
{
return Ok(Probabilistic::Water {
failure: nothing_happened,
success,
});
}
Ok(Probabilistic::Pure(success))
}
pub fn apply_normal_move(
old_state: &state::A,
msg: message::NormalMove,
config: Config,
) -> Result<Probabilistic<state::HandNotResolved>, &'static str> {
use cetkaik_yhuap_move_candidates::{
from_hop1zuo1_candidates, not_from_hop1zuo1_candidates_, to_relative_field, PureGameState,
};
let perspective = match old_state.whose_turn {
absolute::Side::IASide => cetkaik_core::perspective::Perspective::IaIsUpAndPointsDownward,
absolute::Side::ASide => cetkaik_core::perspective::Perspective::IaIsDownAndPointsUpward,
};
let hop1zuo1_candidates = from_hop1zuo1_candidates(&PureGameState {
perspective,
opponent_has_just_moved_tam: old_state.tam_has_moved_previously,
tam_itself_is_tam_hue: config.tam_itself_is_tam_hue,
f: to_relative_field(old_state.f.clone(), perspective),
});
let candidates = not_from_hop1zuo1_candidates_(
&cetkaik_yhuap_move_candidates::Config {
allow_kut2tam2: true,
},
&PureGameState {
perspective,
opponent_has_just_moved_tam: old_state.tam_has_moved_previously,
tam_itself_is_tam_hue: config.tam_itself_is_tam_hue,
f: to_relative_field(old_state.f.clone(), perspective),
},
);
match msg {
message::NormalMove::NonTamMoveFromHand { color, prof, dest } => {
let mut new_field = old_state
.f
.find_and_remove_piece_from_hop1zuo1(color, prof, old_state.whose_turn)
.ok_or("Cannot find the specified piece in the hop1zuo1")?;
if new_field.board.contains_key(&dest) {
return Err("The destination is already occupied and hence cannot place a piece from hop1 zuo1");
}
new_field.board.insert(
dest,
absolute::Piece::NonTam2Piece {
color,
prof,
side: old_state.whose_turn,
},
);
{
if !hop1zuo1_candidates.contains(
&cetkaik_yhuap_move_candidates::PureMove::NonTamMoveFromHopZuo {
color,
prof,
dest,
},
) {
unreachable!("inconsistencies found between cetkaik_yhuap_move_candidates::PureMove::NonTamMoveFromHand and cetkaik_full_state_transition::apply_nontam_move")
}
}
Ok(Probabilistic::Pure(state::HandNotResolved {
previous_a_side_hop1zuo1: old_state.f.a_side_hop1zuo1.clone(),
previous_ia_side_hop1zuo1: old_state.f.ia_side_hop1zuo1.clone(),
kut2tam2_happened: false,
rate: old_state.rate,
i_have_moved_tam_in_this_turn: false,
season: old_state.season,
scores: old_state.scores,
whose_turn: old_state.whose_turn,
f: new_field,
tam2tysak2_will_trigger_taxottymok: false,
tam2tysak2_raw_penalty: 0,
}))
}
message::NormalMove::TamMoveNoStep {
src,
first_dest,
second_dest,
} => {
if candidates.contains(&cetkaik_yhuap_move_candidates::PureMove::TamMoveNoStep {
src,
first_dest,
second_dest,
}) {
apply_tam_move(old_state, src, first_dest, second_dest, None, config)
} else {
Err("The provided TamMoveNoStep was rejected by the crate `cetkaik_yhuap_move_candidates`.")
}
}
message::NormalMove::TamMoveStepsDuringFormer {
src,
first_dest,
second_dest,
step,
} => {
if candidates.contains(
&cetkaik_yhuap_move_candidates::PureMove::TamMoveStepsDuringFormer {
src,
first_dest,
second_dest,
step,
},
) {
apply_tam_move(old_state, src, first_dest, second_dest, Some(step), config)
} else {
Err("The provided TamMoveStepsDuringFormer was rejected by the crate `cetkaik_yhuap_move_candidates`.")
}
}
message::NormalMove::TamMoveStepsDuringLatter {
src,
first_dest,
second_dest,
step,
} => {
if candidates.contains(
&cetkaik_yhuap_move_candidates::PureMove::TamMoveStepsDuringLatter {
src,
first_dest,
second_dest,
step,
},
) {
apply_tam_move(old_state, src, first_dest, second_dest, Some(step), config)
} else {
Err("The provided TamMoveStepsDuringLatter was rejected by the crate `cetkaik_yhuap_move_candidates`.")
}
}
message::NormalMove::NonTamMoveSrcDst { src, dest } => {
if candidates.contains(&cetkaik_yhuap_move_candidates::PureMove::NonTamMoveSrcDst {
src,
dest,
is_water_entry_ciurl: true,
}) || candidates.contains(
&cetkaik_yhuap_move_candidates::PureMove::NonTamMoveSrcDst {
src,
dest,
is_water_entry_ciurl: false,
},
) {
apply_nontam_move(old_state, src, dest, None, config)
} else {
Err("The provided NonTamMoveSrcDst was rejected by the crate `cetkaik_yhuap_move_candidates`.")
}
}
message::NormalMove::NonTamMoveSrcStepDstFinite { src, step, dest } => {
if candidates.contains(
&cetkaik_yhuap_move_candidates::PureMove::NonTamMoveSrcStepDstFinite {
src,
step,
dest,
is_water_entry_ciurl: true,
},
) || candidates.contains(
&cetkaik_yhuap_move_candidates::PureMove::NonTamMoveSrcStepDstFinite {
src,
step,
dest,
is_water_entry_ciurl: false,
},
) {
apply_nontam_move(old_state, src, dest, Some(step), config)
} else {
Err("The provided NonTamMoveSrcStepDstFinite was rejected by the crate `cetkaik_yhuap_move_candidates`.")
}
}
}
}
pub fn apply_inf_after_step(
old_state: &state::A,
msg: message::InfAfterStep,
config: Config,
) -> Result<Probabilistic<state::C>, &'static str> {
if !old_state.f.board.contains_key(&msg.src) {
return Err("In InfAfterStep, `src` is not occupied; illegal");
}
if !old_state.f.board.contains_key(&msg.step) {
return Err("In InfAfterStep, `step` is not occupied; illegal");
}
let perspective = match old_state.whose_turn {
absolute::Side::IASide => cetkaik_core::perspective::Perspective::IaIsUpAndPointsDownward,
absolute::Side::ASide => cetkaik_core::perspective::Perspective::IaIsDownAndPointsUpward,
};
let candidates = cetkaik_yhuap_move_candidates::not_from_hop1zuo1_candidates_(
&cetkaik_yhuap_move_candidates::Config {
allow_kut2tam2: true,
},
&cetkaik_yhuap_move_candidates::PureGameState {
perspective,
opponent_has_just_moved_tam: old_state.tam_has_moved_previously,
tam_itself_is_tam_hue: config.tam_itself_is_tam_hue,
f: cetkaik_yhuap_move_candidates::to_relative_field(old_state.f.clone(), perspective),
},
);
if !candidates
.into_iter()
.filter(|cand| match cand {
cetkaik_yhuap_move_candidates::PureMove::InfAfterStep {
src,
step,
planned_direction: _,
} => *src == msg.src && *step == msg.step,
_ => false,
})
.count()
> 0
{
return Err(
"The provided InfAfterStep was rejected by the crate `cetkaik_yhuap_move_candidates`.",
);
}
let c = state::CWithoutCiurl {
f: old_state.f.clone(),
whose_turn: old_state.whose_turn,
flying_piece_src: msg.src,
flying_piece_step: msg.step,
season: old_state.season,
scores: old_state.scores,
rate: old_state.rate,
};
Ok(Probabilistic::Sticks {
s0: state::C {
c: c.clone(),
ciurl: 0,
},
s1: state::C {
c: c.clone(),
ciurl: 1,
},
s2: state::C {
c: c.clone(),
ciurl: 2,
},
s3: state::C {
c: c.clone(),
ciurl: 3,
},
s4: state::C {
c: c.clone(),
ciurl: 4,
},
s5: state::C { c, ciurl: 5 },
})
}
mod score;
pub use score::Scores;
fn move_nontam_piece_from_src_to_dest_while_taking_opponent_piece_if_needed(
board: &absolute::Board,
src: absolute::Coord,
dest: absolute::Coord,
whose_turn: absolute::Side,
) -> Result<(absolute::Board, Option<absolute::NonTam2Piece>), &'static str> {
let mut new_board = board.clone();
let src_piece = new_board
.remove(&src)
.ok_or("src does not contain a piece")?;
if src_piece.is_tam2() {
return Err("Expected a NonTam2Piece to be present at the src, but found a Tam2");
}
if !src_piece.has_side(whose_turn) {
return Err("Found the opponent piece at the src");
}
let maybe_captured_piece = new_board.remove(&dest);
new_board.insert(dest, src_piece);
if let Some(captured_piece) = maybe_captured_piece {
match captured_piece {
absolute::Piece::Tam2 => return Err("Tried to capture a Tam2"),
absolute::Piece::NonTam2Piece {
color: captured_piece_color,
prof: captured_piece_prof,
side: captured_piece_side,
} => {
if captured_piece_side == whose_turn {
return Err("Tried to capture an ally");
}
return Ok((
new_board,
Some(absolute::NonTam2Piece {
color: captured_piece_color,
prof: captured_piece_prof,
}),
));
}
}
}
Ok((new_board, None))
}
pub fn apply_after_half_acceptance(
old_state: &state::C,
msg: message::AfterHalfAcceptance,
config: Config,
) -> Result<Probabilistic<state::HandNotResolved>, &'static str> {
let nothing_happened = state::HandNotResolved {
previous_a_side_hop1zuo1: old_state.c.f.a_side_hop1zuo1.clone(),
previous_ia_side_hop1zuo1: old_state.c.f.ia_side_hop1zuo1.clone(),
kut2tam2_happened: !config.failure_to_complete_the_move_means_exempt_from_kut2_tam2
&& old_state.piece_at_flying_piece_step().is_tam2(),
rate: old_state.c.rate,
i_have_moved_tam_in_this_turn: false,
season: old_state.c.season,
scores: old_state.c.scores,
whose_turn: old_state.c.whose_turn,
f: old_state.c.f.clone(),
tam2tysak2_will_trigger_taxottymok: false,
tam2tysak2_raw_penalty: 0,
};
let state::C {
c: state::CWithoutCiurl {
flying_piece_src, ..
},
ciurl,
} = *old_state;
if let Some(dest) = msg.dest {
{
let perspective = match old_state.c.whose_turn {
absolute::Side::IASide => {
cetkaik_core::perspective::Perspective::IaIsUpAndPointsDownward
}
absolute::Side::ASide => {
cetkaik_core::perspective::Perspective::IaIsDownAndPointsUpward
}
};
let candidates = cetkaik_yhuap_move_candidates::not_from_hop1zuo1_candidates_(
&cetkaik_yhuap_move_candidates::Config {
allow_kut2tam2: true,
},
&cetkaik_yhuap_move_candidates::PureGameState {
perspective,
opponent_has_just_moved_tam: false,
tam_itself_is_tam_hue: config.tam_itself_is_tam_hue,
f: cetkaik_yhuap_move_candidates::to_relative_field(
old_state.c.f.clone(),
perspective,
),
},
);
if !candidates
.into_iter()
.filter(|cand| match cand {
cetkaik_yhuap_move_candidates::PureMove::InfAfterStep {
src,
step,
planned_direction,
} => {
*src == old_state.c.flying_piece_src
&& *step == old_state.c.flying_piece_step
&& *planned_direction == dest
}
_ => false,
})
.count()
> 0
{
return Err(
"The provided InfAfterStep was rejected by the crate `cetkaik_yhuap_move_candidates`.",
);
}
if ciurl < cetkaik_core::absolute::distance(old_state.c.flying_piece_step, dest) {
return Err(
"The provided InfAfterStep was rejected because the ciurl limit was exceeded.",
);
}
}
let piece = old_state.piece_at_flying_piece_src();
let (new_board, maybe_captured_piece) =
move_nontam_piece_from_src_to_dest_while_taking_opponent_piece_if_needed(
&old_state.c.f.board,
flying_piece_src,
dest,
old_state.c.whose_turn,
)?;
let mut new_field = absolute::Field {
board: new_board,
ia_side_hop1zuo1: old_state.c.f.ia_side_hop1zuo1.clone(),
a_side_hop1zuo1: old_state.c.f.a_side_hop1zuo1.clone(),
};
if let Some(absolute::NonTam2Piece { color, prof }) = maybe_captured_piece {
new_field.insert_nontam_piece_into_hop1zuo1(color, prof, old_state.c.whose_turn);
};
let success = state::HandNotResolved {
previous_a_side_hop1zuo1: old_state.c.f.a_side_hop1zuo1.clone(),
previous_ia_side_hop1zuo1: old_state.c.f.ia_side_hop1zuo1.clone(),
kut2tam2_happened: old_state.piece_at_flying_piece_step().is_tam2(),
rate: old_state.c.rate,
i_have_moved_tam_in_this_turn: false,
season: old_state.c.season,
scores: old_state.c.scores,
whose_turn: old_state.c.whose_turn,
f: new_field,
tam2tysak2_will_trigger_taxottymok: false,
tam2tysak2_raw_penalty: 0,
};
if !absolute::is_water(flying_piece_src)
&& !piece.has_prof(cetkaik_core::Profession::Nuak1)
&& absolute::is_water(dest)
{
Ok(Probabilistic::Water {
success,
failure: nothing_happened,
})
} else {
Ok(Probabilistic::Pure(success))
}
} else {
Ok(Probabilistic::Pure(nothing_happened))
}
}
pub use score::Victor;
pub enum IfTaxot {
NextSeason(Probabilistic<state::A>),
VictoriousSide(Victor),
}
#[readonly::make]
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
pub struct Config {
pub step_tam_is_a_hand: bool,
pub tam_itself_is_tam_hue: bool,
pub moving_tam_immediately_after_tam_has_moved: Consequence,
pub tam_mun_mok: Consequence,
pub failure_to_complete_the_move_means_exempt_from_kut2_tam2: bool,
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
pub enum Consequence {
Allowed,
Penalized { penalty: i32, is_a_hand: bool },
Forbidden,
}
impl Config {
#[must_use]
pub fn cerke_online_alpha() -> Config {
Config {
step_tam_is_a_hand: false,
tam_itself_is_tam_hue: true,
moving_tam_immediately_after_tam_has_moved: Consequence::Forbidden,
tam_mun_mok: Consequence::Allowed,
failure_to_complete_the_move_means_exempt_from_kut2_tam2: false,
}
}
#[must_use]
pub fn strict_y1_huap1() -> Config {
Config {
step_tam_is_a_hand: true,
tam_itself_is_tam_hue: false,
moving_tam_immediately_after_tam_has_moved: Consequence::Penalized {
penalty: -3,
is_a_hand: true,
},
tam_mun_mok: Consequence::Penalized {
penalty: -3,
is_a_hand: true,
},
failure_to_complete_the_move_means_exempt_from_kut2_tam2: false,
}
}
}
#[must_use]
pub fn resolve(state: &state::HandNotResolved, config: Config) -> state::HandResolved {
use cetkaik_calculate_hand::{calculate_hands_and_score_from_pieces, ScoreAndHands};
let tymoxtaxot_because_of_kut2tam2 = state.kut2tam2_happened && config.step_tam_is_a_hand;
let tymoxtaxot_because_of_newly_acquired: Option<i32> = match state.whose_turn {
absolute::Side::ASide => {
if state.previous_a_side_hop1zuo1 == state.f.a_side_hop1zuo1 {
None
} else {
let ScoreAndHands {
score: _,
hands: old_hands,
} = calculate_hands_and_score_from_pieces(&state.previous_a_side_hop1zuo1).unwrap();
let ScoreAndHands {
score: new_score,
hands: new_hands,
} = calculate_hands_and_score_from_pieces(&state.f.a_side_hop1zuo1).unwrap();
if new_hands.difference(&old_hands).count() > 0 {
Some(new_score)
} else {
None
}
}
}
absolute::Side::IASide => {
if state.previous_ia_side_hop1zuo1 == state.f.ia_side_hop1zuo1 {
None
} else {
let ScoreAndHands {
score: _,
hands: old_hands,
} = calculate_hands_and_score_from_pieces(&state.previous_ia_side_hop1zuo1)
.unwrap();
let ScoreAndHands {
score: new_score,
hands: new_hands,
} = calculate_hands_and_score_from_pieces(&state.f.ia_side_hop1zuo1).unwrap();
if new_hands.difference(&old_hands).count() > 0 {
Some(new_score)
} else {
None
}
}
}
};
if !tymoxtaxot_because_of_kut2tam2
&& tymoxtaxot_because_of_newly_acquired.is_none()
&& !state.tam2tysak2_will_trigger_taxottymok
{
match state.scores.edit(
-5 + state.tam2tysak2_raw_penalty,
state.whose_turn,
state.rate,
) {
Ok(new_scores) => {
return state::HandResolved::NeitherTymokNorTaxot(state::A {
f: state.f.clone(),
whose_turn: !state.whose_turn,
season: state.season,
scores: new_scores,
rate: state.rate,
tam_has_moved_previously: state.i_have_moved_tam_in_this_turn,
});
}
Err(victor) => return state::HandResolved::GameEndsWithoutTymokTaxot(victor),
}
}
let raw_score = state.tam2tysak2_raw_penalty
+ if tymoxtaxot_because_of_kut2tam2 {
-5
} else {
0
}
+ match tymoxtaxot_because_of_newly_acquired {
None => 0,
Some(score) => score,
};
let if_taxot = match state.scores.edit(raw_score, state.whose_turn, state.rate) {
Err(victor) => IfTaxot::VictoriousSide(victor),
Ok(new_scores) => {
state.season.next().map_or(
IfTaxot::VictoriousSide(new_scores.which_side_is_winning()),
|next_season| IfTaxot::NextSeason(beginning_of_season(next_season, new_scores)),
)
}
};
state::HandResolved::HandExists {
if_tymok: state::A {
f: state.f.clone(),
whose_turn: !state.whose_turn,
season: state.season,
scores: state.scores,
rate: state.rate.next(),
tam_has_moved_previously: state.i_have_moved_tam_in_this_turn,
},
if_taxot,
}
}
#[must_use]
pub fn initial_state() -> Probabilistic<state::A> {
beginning_of_season(Season::Iei2, Scores::new())
}
fn beginning_of_season(season: Season, scores: Scores) -> Probabilistic<state::A> {
let ia_first = state::A {
whose_turn: absolute::Side::IASide,
scores,
rate: Rate::X1,
season,
tam_has_moved_previously: false,
f: absolute::Field {
a_side_hop1zuo1: vec![],
ia_side_hop1zuo1: vec![],
board: cetkaik_core::absolute::yhuap_initial_board(),
},
};
let mut a_first = ia_first.clone();
a_first.whose_turn = absolute::Side::ASide;
Probabilistic::WhoGoesFirst { ia_first, a_first }
}