Skip to content
12 changes: 12 additions & 0 deletions python/socha/_socha.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,18 @@ class GameState:
def __str__(self) -> str: ...
def __repr__(self) -> str: ...

def possible_moves_for(self, start: Coordinate) -> List[Move]:
"""
Berechnet alle Züge, die aus der aktuellen Spielposition für den Fisch an der Koordinate möglich sind.

Args:
start (Coordinate): Die Position des gewählten Fisch.

Returns:
List[Move]: Die Liste der Züge.
"""
...

def possible_moves(self) -> List[Move]:
"""
Berechnet alle Züge, die aus der aktuellen Spielposition für den aktuellen Spieler möglich sind.
Expand Down
43 changes: 18 additions & 25 deletions src/plugin2026/game_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ impl GameState {
pub fn __str__(&self) -> String {self.to_string()}
pub fn __repr__(&self) -> String {format!("{:?}", self)}

pub fn possible_moves_for(&self, start: &Coordinate) -> Vec<Move> {
let mut moves: Vec<Move> = Vec::new();

for d in Direction::all_directions() {
moves.push(Move { start: start.to_owned(), direction: d });
}

moves
.into_iter()
.filter(|m| RulesEngine::can_execute_move(&self.board, m).is_ok())
.collect()
}

pub fn possible_moves(&self) -> Vec<Move> {
let mut moves: Vec<Move> = Vec::new();
let mut fish: Vec<Coordinate> = Vec::new();
Expand All @@ -41,40 +54,20 @@ impl GameState {
}

for f in fish {
for d in Direction::all_directions() {
moves.push(Move { start: f.clone(), direction: d });
}
moves.extend(self.possible_moves_for(&f));
}

moves
.into_iter()
.filter(|m| RulesEngine::can_execute_move(&self.board, m).is_ok())
.collect()
}

pub fn perform_move(&self, move_: &Move) -> Result<GameState, PyErr> {

RulesEngine::can_execute_move(&self.board, move_)
.map_err(|e| {
let full_error = e.to_string();
let clean_error = full_error.strip_prefix("PiranhasError:").unwrap_or(&full_error).trim();
PiranhasError::new_err(format!("Cannot execute move: {}", clean_error))
})?;

let target = RulesEngine::target_position(&self.board, move_);
let mut new_board = self.board.clone();
new_board.map[target.y as usize][target.x as usize] = self.board.get_field(&move_.start).unwrap();
new_board.map[move_.start.y as usize][move_.start.x as usize] = FieldType::Empty;
let mut new_game_state = self.clone();
new_game_state.perform_move_mut(move_)?;

let new_state = GameState {
board: new_board,
turn: self.turn + 1,
last_move: Some(move_.clone())
};

Ok(new_state)
Ok(new_game_state)
}

pub fn perform_move_mut(&mut self, move_: &Move) -> Result<(), PyErr> {

RulesEngine::can_execute_move(&self.board, move_)
Expand Down
15 changes: 0 additions & 15 deletions src/plugin2026/rules_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,6 @@ impl RulesEngine {
let distance = Self::move_distance(board, move_);
let direction_fields = board.get_fields_in_direction(&move_.start, &move_.direction);
let path_fields: Vec<_> = direction_fields.iter().take(distance - 1).cloned().collect(); // not including start or target
/*
println!("{}", move_);
println!("{}", move_.start);
println!("{}", move_.direction);
println!("{}", distance);
println!("{}", target_pos);
println!("{}", target_field);
for d in direction_fields {
print!("{} ", d);
}
println!();
for d in &path_fields {
print!("{} ", *d);
}
println!();*/

for d in path_fields {
let mut blocked_fields = this_team.get_fish_types();
Expand Down
119 changes: 90 additions & 29 deletions src/plugin2026/test/board_test.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,104 @@
#[cfg(test)]
mod tests {
use crate::plugin2026::field_type::FieldType;
use crate::plugin2026::test::common::*;
use crate::plugin2026::utils::coordinate::Coordinate;
use crate::plugin2026::utils::direction::Direction;

use crate::plugin2026::{
field_type::FieldType, test::common::*, utils::{
coordinate::Coordinate,
direction::Direction
}
};

#[test]
pub fn test01() {
pub fn get_field_test() {
let b = create_test_board();

println!("{}", b);
assert_eq!(b.get_field(&Coordinate { x: 0, y: 4 }), Some(FieldType::OneS));
assert_eq!(b.get_field(&Coordinate { x: 7, y: 0 }), Some(FieldType::TwoL));
assert_eq!(b.get_field(&Coordinate { x: 3, y: 3 }), Some(FieldType::Empty));
assert_eq!(b.get_field(&Coordinate { x: 6, y: 2 }), Some(FieldType::Squid));
assert_eq!(b.get_field(&Coordinate { x: -2, y: 0 }), None); // out of bounds
}

#[test]
pub fn get_fields_by_type_test() {
let mut b = create_test_board();

// remove squids
b.map[2][6] = FieldType::Empty;
b.map[7][3] = FieldType::Empty;

let one_s_positions = vec![
Coordinate {x: 0, y: 2},
Coordinate {x: 9, y: 2},
Coordinate {x: 0, y: 4},
Coordinate {x: 0, y: 6},
Coordinate {x: 9, y: 7},
Coordinate {x: 0, y: 8},
Coordinate {x: 9, y: 8},
];

for variant in Direction::all_directions() {
let c = Coordinate {x: 2, y: 1};
let f = b.get_fields_in_direction(&c, &variant);

print!("[ ");
for field in &f {
print!("{} ", field);
assert_eq!(b.get_fields_by_type(FieldType::OneS), one_s_positions);
assert_eq!(b.get_fields_by_type(FieldType::Squid), vec![]);
}

#[test]
pub fn get_fields_in_direction_test() {
let b = create_test_board();
let start = Coordinate {x: 2, y: 6};

for d in Direction::all_directions() {
match d {
Direction::Up => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
FieldType::Empty, FieldType::Empty, FieldType::TwoS
]),
Direction::UpRight => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
FieldType::Squid, FieldType::Empty, FieldType::TwoM
]),
Direction::Right => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
FieldType::Empty, FieldType::Empty, FieldType::Empty,
FieldType::Empty, FieldType::Empty, FieldType::Empty, FieldType::OneM
]),
Direction::DownRight => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
FieldType::Empty, FieldType::Empty, FieldType::Empty,
FieldType::Squid, FieldType::Empty, FieldType::TwoS
]),
Direction::Down => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
FieldType::Empty, FieldType::Empty, FieldType::Empty,
FieldType::Empty, FieldType::Empty, FieldType::TwoS
]),
Direction::DownLeft => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
FieldType::Empty, FieldType::OneS
]),
Direction::Left => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
FieldType::Empty, FieldType::OneS
]),
Direction::UpLeft => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
FieldType::Empty, FieldType::OneS
]),
}
print!("] {} {}", c, variant);
println!();
}
}

let mut sum = 0;
#[test]
pub fn get_fields_on_line_test() {
let b = create_test_board();
let start = Coordinate {x: 2, y: 7};
let direction = Direction::Right;

for fvarient in FieldType::all_field_types() {
let f = b.get_fields_by_type(fvarient);

print!("[ ");
for field in &f {
print!("{} ", field);
}
print!("]");
println!();
assert_eq!(b.get_fields_on_line(&start, &direction), vec![
FieldType::OneL, FieldType::Empty, FieldType::Empty,
FieldType::Squid, FieldType::Empty, FieldType::Empty,
FieldType::Empty, FieldType::Empty, FieldType::Empty, FieldType::OneS
]);
}

sum += f.len();
}
#[test]
pub fn get_fish_on_line_test() {
let b = create_test_board();
let start = Coordinate {x: 2, y: 7};
let direction = Direction::Right;

println!("{}", sum);
assert_eq!(b.get_fish_on_line(&start, &direction), vec![
FieldType::OneL, FieldType::OneS
]);
}
}
10 changes: 6 additions & 4 deletions src/plugin2026/test/common.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::plugin2026::{
board::Board,
field_type::FieldType,
utils::constants::PluginConstants
board::Board, field_type::FieldType, game_state::GameState, utils::constants::PluginConstants
};

pub fn create_test_board() -> Board {
Expand Down Expand Up @@ -36,5 +34,9 @@ pub fn create_test_board() -> Board {
}
}

Board::new(new_map)
Board {map: new_map}
}

pub fn create_test_game_state() -> GameState {
GameState { board: create_test_board(), turn: 0, last_move: None }
}
30 changes: 0 additions & 30 deletions src/plugin2026/test/coordinate_test.rs

This file was deleted.

85 changes: 84 additions & 1 deletion src/plugin2026/test/game_state_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,87 @@
#[cfg(test)]
mod tests {

use crate::plugin2026::{
field_type::FieldType, r#move::Move, test::common::create_test_game_state, utils::{coordinate::Coordinate, direction::Direction}
};

#[test]
pub fn possible_moves_for_test() {
let state = create_test_game_state();

// 3 possible moves
let position01 = Coordinate {x: 0, y: 3};
let poss01 = state.possible_moves_for(&position01);

assert_eq!(poss01, vec![
Move {start: position01.clone(), direction: Direction::UpRight},
Move {start: position01.clone(), direction: Direction::Right},
Move {start: position01.clone(), direction: Direction::DownRight},
]);

// no possible move because out of bounds start
let position02 = Coordinate {x: 0, y: -1};
let poss02 = state.possible_moves_for(&position02);

assert_eq!(poss02, vec![]);
}

#[test]
pub fn possible_moves_test() {
// this state's board has 48 (standard number) moves for team one (turn 0)
// team two would have 6 turns less from this position due to squids
let mut state = create_test_game_state();

assert_eq!(state.possible_moves().len(), 48); // team one

state.turn = 1;
assert_eq!(state.possible_moves().len(), 42); // team two
}

#[test]
pub fn perform_move_test() {
pyo3::prepare_freethreaded_python();

let mut state = create_test_game_state();

// correct move
let new_state = state.perform_move( &Move {
start: Coordinate { x: 0, y: 3 },
direction: Direction::Right
}).unwrap();

assert_eq!(new_state.board.get_field(&Coordinate { x: 0, y: 3 }), Some(FieldType::Empty));
assert_eq!(new_state.board.get_field(&Coordinate { x: 2, y: 3 }), Some(FieldType::OneL));

// illegal moves
let result = state.perform_move(&Move {
start: Coordinate { x: -1, y: 0 },
direction: Direction::Right
});
assert!(result.is_err(), "Move FROM out of bounds should fail, but succeeded");

let result = state.perform_move(&Move {
start: Coordinate { x: 0, y: 3 },
direction: Direction::Left
});
assert!(result.is_err(), "Move TO out of bounds should fail, but succeeded");

let result = state.perform_move(&Move {
start: Coordinate { x: 3, y: 3 },
direction: Direction::Right
});
assert!(result.is_err(), "Move FROM non-fish field should fail, but succeeded");

let result = state.perform_move(&Move {
start: Coordinate { x: 6, y: 0 },
direction: Direction::Up
});
assert!(result.is_err(), "Move TO squid field should fail, but succeeded");

state.board.map[3][2] = FieldType::TwoL;
let result = state.perform_move(&Move {
start: Coordinate { x: 2, y: 0 },
direction: Direction::Up
});
assert!(result.is_err(), "Move TO own-fish field should fail, but succeeded");
}
}
6 changes: 1 addition & 5 deletions src/plugin2026/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,4 @@ mod common;
#[cfg(test)]
mod board_test;
#[cfg(test)]
mod game_state_test;
#[cfg(test)]
mod coordinate_test;
#[cfg(test)]
mod rules_engine_test;
mod game_state_test;
Loading