Skip to content

Commit ef619e0

Browse files
authored
Improve piranhas 2026 (#97)
* update starter logic.py add new socha imports update comments to German * feature: perform_move() add GameState.perform_move() and mutating variant * fix: clarify describtion * chore: remove old 2025 plugin from project * feat (possible_moves()): split up for each fish add function GameState.possible_moves_for(self, Coordinate) like in java API, which calculates every possible move for a position. possible_moves() now uses this function for every fish * merge: from master, fix conflict * unit_tests: add board method tests * chore: redundant code removed and use mut method in non-mut * unit_tests: add game_state method tests * chore: remove commented debug prints * chore: remove unneeded testing files
1 parent 94b793c commit ef619e0

File tree

9 files changed

+211
-141
lines changed

9 files changed

+211
-141
lines changed

python/socha/_socha.pyi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,18 @@ class GameState:
429429
def __str__(self) -> str: ...
430430
def __repr__(self) -> str: ...
431431

432+
def possible_moves_for(self, start: Coordinate) -> List[Move]:
433+
"""
434+
Berechnet alle Züge, die aus der aktuellen Spielposition für den Fisch an der Koordinate möglich sind.
435+
436+
Args:
437+
start (Coordinate): Die Position des gewählten Fisch.
438+
439+
Returns:
440+
List[Move]: Die Liste der Züge.
441+
"""
442+
...
443+
432444
def possible_moves(self) -> List[Move]:
433445
"""
434446
Berechnet alle Züge, die aus der aktuellen Spielposition für den aktuellen Spieler möglich sind.

src/plugin2026/game_state.rs

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ impl GameState {
3232
pub fn __str__(&self) -> String {self.to_string()}
3333
pub fn __repr__(&self) -> String {format!("{:?}", self)}
3434

35+
pub fn possible_moves_for(&self, start: &Coordinate) -> Vec<Move> {
36+
let mut moves: Vec<Move> = Vec::new();
37+
38+
for d in Direction::all_directions() {
39+
moves.push(Move { start: start.to_owned(), direction: d });
40+
}
41+
42+
moves
43+
.into_iter()
44+
.filter(|m| RulesEngine::can_execute_move(&self.board, m).is_ok())
45+
.collect()
46+
}
47+
3548
pub fn possible_moves(&self) -> Vec<Move> {
3649
let mut moves: Vec<Move> = Vec::new();
3750
let mut fish: Vec<Coordinate> = Vec::new();
@@ -41,40 +54,20 @@ impl GameState {
4154
}
4255

4356
for f in fish {
44-
for d in Direction::all_directions() {
45-
moves.push(Move { start: f.clone(), direction: d });
46-
}
57+
moves.extend(self.possible_moves_for(&f));
4758
}
4859

4960
moves
50-
.into_iter()
51-
.filter(|m| RulesEngine::can_execute_move(&self.board, m).is_ok())
52-
.collect()
5361
}
5462

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

57-
RulesEngine::can_execute_move(&self.board, move_)
58-
.map_err(|e| {
59-
let full_error = e.to_string();
60-
let clean_error = full_error.strip_prefix("PiranhasError:").unwrap_or(&full_error).trim();
61-
PiranhasError::new_err(format!("Cannot execute move: {}", clean_error))
62-
})?;
63-
64-
let target = RulesEngine::target_position(&self.board, move_);
65-
let mut new_board = self.board.clone();
66-
new_board.map[target.y as usize][target.x as usize] = self.board.get_field(&move_.start).unwrap();
67-
new_board.map[move_.start.y as usize][move_.start.x as usize] = FieldType::Empty;
65+
let mut new_game_state = self.clone();
66+
new_game_state.perform_move_mut(move_)?;
6867

69-
let new_state = GameState {
70-
board: new_board,
71-
turn: self.turn + 1,
72-
last_move: Some(move_.clone())
73-
};
74-
75-
Ok(new_state)
68+
Ok(new_game_state)
7669
}
77-
70+
7871
pub fn perform_move_mut(&mut self, move_: &Move) -> Result<(), PyErr> {
7972

8073
RulesEngine::can_execute_move(&self.board, move_)

src/plugin2026/rules_engine.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,6 @@ impl RulesEngine {
6161
let distance = Self::move_distance(board, move_);
6262
let direction_fields = board.get_fields_in_direction(&move_.start, &move_.direction);
6363
let path_fields: Vec<_> = direction_fields.iter().take(distance - 1).cloned().collect(); // not including start or target
64-
/*
65-
println!("{}", move_);
66-
println!("{}", move_.start);
67-
println!("{}", move_.direction);
68-
println!("{}", distance);
69-
println!("{}", target_pos);
70-
println!("{}", target_field);
71-
for d in direction_fields {
72-
print!("{} ", d);
73-
}
74-
println!();
75-
for d in &path_fields {
76-
print!("{} ", *d);
77-
}
78-
println!();*/
7964

8065
for d in path_fields {
8166
let mut blocked_fields = this_team.get_fish_types();

src/plugin2026/test/board_test.rs

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,104 @@
11
#[cfg(test)]
22
mod tests {
3-
use crate::plugin2026::field_type::FieldType;
4-
use crate::plugin2026::test::common::*;
5-
use crate::plugin2026::utils::coordinate::Coordinate;
6-
use crate::plugin2026::utils::direction::Direction;
7-
3+
use crate::plugin2026::{
4+
field_type::FieldType, test::common::*, utils::{
5+
coordinate::Coordinate,
6+
direction::Direction
7+
}
8+
};
9+
810
#[test]
9-
pub fn test01() {
11+
pub fn get_field_test() {
1012
let b = create_test_board();
1113

12-
println!("{}", b);
14+
assert_eq!(b.get_field(&Coordinate { x: 0, y: 4 }), Some(FieldType::OneS));
15+
assert_eq!(b.get_field(&Coordinate { x: 7, y: 0 }), Some(FieldType::TwoL));
16+
assert_eq!(b.get_field(&Coordinate { x: 3, y: 3 }), Some(FieldType::Empty));
17+
assert_eq!(b.get_field(&Coordinate { x: 6, y: 2 }), Some(FieldType::Squid));
18+
assert_eq!(b.get_field(&Coordinate { x: -2, y: 0 }), None); // out of bounds
19+
}
20+
21+
#[test]
22+
pub fn get_fields_by_type_test() {
23+
let mut b = create_test_board();
24+
25+
// remove squids
26+
b.map[2][6] = FieldType::Empty;
27+
b.map[7][3] = FieldType::Empty;
28+
29+
let one_s_positions = vec![
30+
Coordinate {x: 0, y: 2},
31+
Coordinate {x: 9, y: 2},
32+
Coordinate {x: 0, y: 4},
33+
Coordinate {x: 0, y: 6},
34+
Coordinate {x: 9, y: 7},
35+
Coordinate {x: 0, y: 8},
36+
Coordinate {x: 9, y: 8},
37+
];
1338

14-
for variant in Direction::all_directions() {
15-
let c = Coordinate {x: 2, y: 1};
16-
let f = b.get_fields_in_direction(&c, &variant);
17-
18-
print!("[ ");
19-
for field in &f {
20-
print!("{} ", field);
39+
assert_eq!(b.get_fields_by_type(FieldType::OneS), one_s_positions);
40+
assert_eq!(b.get_fields_by_type(FieldType::Squid), vec![]);
41+
}
42+
43+
#[test]
44+
pub fn get_fields_in_direction_test() {
45+
let b = create_test_board();
46+
let start = Coordinate {x: 2, y: 6};
47+
48+
for d in Direction::all_directions() {
49+
match d {
50+
Direction::Up => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
51+
FieldType::Empty, FieldType::Empty, FieldType::TwoS
52+
]),
53+
Direction::UpRight => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
54+
FieldType::Squid, FieldType::Empty, FieldType::TwoM
55+
]),
56+
Direction::Right => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
57+
FieldType::Empty, FieldType::Empty, FieldType::Empty,
58+
FieldType::Empty, FieldType::Empty, FieldType::Empty, FieldType::OneM
59+
]),
60+
Direction::DownRight => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
61+
FieldType::Empty, FieldType::Empty, FieldType::Empty,
62+
FieldType::Squid, FieldType::Empty, FieldType::TwoS
63+
]),
64+
Direction::Down => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
65+
FieldType::Empty, FieldType::Empty, FieldType::Empty,
66+
FieldType::Empty, FieldType::Empty, FieldType::TwoS
67+
]),
68+
Direction::DownLeft => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
69+
FieldType::Empty, FieldType::OneS
70+
]),
71+
Direction::Left => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
72+
FieldType::Empty, FieldType::OneS
73+
]),
74+
Direction::UpLeft => assert_eq!(b.get_fields_in_direction(&start, &d), vec![
75+
FieldType::Empty, FieldType::OneS
76+
]),
2177
}
22-
print!("] {} {}", c, variant);
23-
println!();
2478
}
79+
}
2580

26-
let mut sum = 0;
81+
#[test]
82+
pub fn get_fields_on_line_test() {
83+
let b = create_test_board();
84+
let start = Coordinate {x: 2, y: 7};
85+
let direction = Direction::Right;
2786

28-
for fvarient in FieldType::all_field_types() {
29-
let f = b.get_fields_by_type(fvarient);
30-
31-
print!("[ ");
32-
for field in &f {
33-
print!("{} ", field);
34-
}
35-
print!("]");
36-
println!();
87+
assert_eq!(b.get_fields_on_line(&start, &direction), vec![
88+
FieldType::OneL, FieldType::Empty, FieldType::Empty,
89+
FieldType::Squid, FieldType::Empty, FieldType::Empty,
90+
FieldType::Empty, FieldType::Empty, FieldType::Empty, FieldType::OneS
91+
]);
92+
}
3793

38-
sum += f.len();
39-
}
94+
#[test]
95+
pub fn get_fish_on_line_test() {
96+
let b = create_test_board();
97+
let start = Coordinate {x: 2, y: 7};
98+
let direction = Direction::Right;
4099

41-
println!("{}", sum);
100+
assert_eq!(b.get_fish_on_line(&start, &direction), vec![
101+
FieldType::OneL, FieldType::OneS
102+
]);
42103
}
43104
}

src/plugin2026/test/common.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use crate::plugin2026::{
2-
board::Board,
3-
field_type::FieldType,
4-
utils::constants::PluginConstants
2+
board::Board, field_type::FieldType, game_state::GameState, utils::constants::PluginConstants
53
};
64

75
pub fn create_test_board() -> Board {
@@ -36,5 +34,9 @@ pub fn create_test_board() -> Board {
3634
}
3735
}
3836

39-
Board::new(new_map)
37+
Board {map: new_map}
38+
}
39+
40+
pub fn create_test_game_state() -> GameState {
41+
GameState { board: create_test_board(), turn: 0, last_move: None }
4042
}

src/plugin2026/test/coordinate_test.rs

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,87 @@
11
#[cfg(test)]
22
mod tests {
3-
3+
use crate::plugin2026::{
4+
field_type::FieldType, r#move::Move, test::common::create_test_game_state, utils::{coordinate::Coordinate, direction::Direction}
5+
};
6+
7+
#[test]
8+
pub fn possible_moves_for_test() {
9+
let state = create_test_game_state();
10+
11+
// 3 possible moves
12+
let position01 = Coordinate {x: 0, y: 3};
13+
let poss01 = state.possible_moves_for(&position01);
14+
15+
assert_eq!(poss01, vec![
16+
Move {start: position01.clone(), direction: Direction::UpRight},
17+
Move {start: position01.clone(), direction: Direction::Right},
18+
Move {start: position01.clone(), direction: Direction::DownRight},
19+
]);
20+
21+
// no possible move because out of bounds start
22+
let position02 = Coordinate {x: 0, y: -1};
23+
let poss02 = state.possible_moves_for(&position02);
24+
25+
assert_eq!(poss02, vec![]);
26+
}
27+
28+
#[test]
29+
pub fn possible_moves_test() {
30+
// this state's board has 48 (standard number) moves for team one (turn 0)
31+
// team two would have 6 turns less from this position due to squids
32+
let mut state = create_test_game_state();
33+
34+
assert_eq!(state.possible_moves().len(), 48); // team one
35+
36+
state.turn = 1;
37+
assert_eq!(state.possible_moves().len(), 42); // team two
38+
}
39+
40+
#[test]
41+
pub fn perform_move_test() {
42+
pyo3::prepare_freethreaded_python();
43+
44+
let mut state = create_test_game_state();
45+
46+
// correct move
47+
let new_state = state.perform_move( &Move {
48+
start: Coordinate { x: 0, y: 3 },
49+
direction: Direction::Right
50+
}).unwrap();
51+
52+
assert_eq!(new_state.board.get_field(&Coordinate { x: 0, y: 3 }), Some(FieldType::Empty));
53+
assert_eq!(new_state.board.get_field(&Coordinate { x: 2, y: 3 }), Some(FieldType::OneL));
54+
55+
// illegal moves
56+
let result = state.perform_move(&Move {
57+
start: Coordinate { x: -1, y: 0 },
58+
direction: Direction::Right
59+
});
60+
assert!(result.is_err(), "Move FROM out of bounds should fail, but succeeded");
61+
62+
let result = state.perform_move(&Move {
63+
start: Coordinate { x: 0, y: 3 },
64+
direction: Direction::Left
65+
});
66+
assert!(result.is_err(), "Move TO out of bounds should fail, but succeeded");
67+
68+
let result = state.perform_move(&Move {
69+
start: Coordinate { x: 3, y: 3 },
70+
direction: Direction::Right
71+
});
72+
assert!(result.is_err(), "Move FROM non-fish field should fail, but succeeded");
73+
74+
let result = state.perform_move(&Move {
75+
start: Coordinate { x: 6, y: 0 },
76+
direction: Direction::Up
77+
});
78+
assert!(result.is_err(), "Move TO squid field should fail, but succeeded");
79+
80+
state.board.map[3][2] = FieldType::TwoL;
81+
let result = state.perform_move(&Move {
82+
start: Coordinate { x: 2, y: 0 },
83+
direction: Direction::Up
84+
});
85+
assert!(result.is_err(), "Move TO own-fish field should fail, but succeeded");
86+
}
487
}

src/plugin2026/test/mod.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,4 @@ mod common;
33
#[cfg(test)]
44
mod board_test;
55
#[cfg(test)]
6-
mod game_state_test;
7-
#[cfg(test)]
8-
mod coordinate_test;
9-
#[cfg(test)]
10-
mod rules_engine_test;
6+
mod game_state_test;

0 commit comments

Comments
 (0)