From d9681c570844b79fc8f0dd4a94588c97207df2e3 Mon Sep 17 00:00:00 2001 From: YoEnte Date: Sun, 27 Apr 2025 21:31:37 +0200 Subject: [PATCH 01/10] rename: display for fields --- src/plugin/field.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/plugin/field.rs b/src/plugin/field.rs index 99e5411..1435589 100644 --- a/src/plugin/field.rs +++ b/src/plugin/field.rs @@ -28,15 +28,15 @@ pub enum Field { impl std::fmt::Display for Field { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Field::Position1 => write!(f, "1"), - Field::Position2 => write!(f, "2"), - Field::Hedgehog => write!(f, "I"), - Field::Salad => write!(f, "S"), - Field::Carrots => write!(f, "C"), - Field::Hare => write!(f, "H"), - Field::Market => write!(f, "M"), - Field::Goal => write!(f, "G"), - Field::Start => write!(f, "S"), + Field::Position1 => write!(f, "Pos1"), + Field::Position2 => write!(f, "Pos2"), + Field::Hedgehog => write!(f, "Hedgehog"), + Field::Salad => write!(f, "Salad"), + Field::Carrots => write!(f, "Carrot"), + Field::Hare => write!(f, "Hare"), + Field::Market => write!(f, "Market"), + Field::Goal => write!(f, "Goal"), + Field::Start => write!(f, "Start"), } } } From 72d0574a8a243730a262fc259c8c60500070f32f Mon Sep 17 00:00:00 2001 From: YoEnte Date: Sun, 27 Apr 2025 21:33:27 +0200 Subject: [PATCH 02/10] add: move compare --- python/socha/_socha.pyi | 2 ++ src/plugin/move.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/python/socha/_socha.pyi b/python/socha/_socha.pyi index 828df5a..6398686 100644 --- a/python/socha/_socha.pyi +++ b/python/socha/_socha.pyi @@ -374,6 +374,8 @@ class Move: ... def __repr__(self) -> str: ... + def __eq__(self) -> bool: ... + class GameState: """ Repräsentiert den aktuellen Zustand des Spiels. diff --git a/src/plugin/move.rs b/src/plugin/move.rs index 4960bbd..a3a758d 100644 --- a/src/plugin/move.rs +++ b/src/plugin/move.rs @@ -31,6 +31,10 @@ impl Move { fn __repr__(&self) -> PyResult { Ok(format!("Move(action={:?})", self.action)) } + + fn __eq__(&self, other: &Move) -> PyResult { + Ok(self == other) + } } impl std::fmt::Display for Move { From a6d56a38d40cbd7bd52cf0801809c3fbceaff707 Mon Sep 17 00:00:00 2001 From: YoEnte Date: Sun, 27 Apr 2025 21:34:10 +0200 Subject: [PATCH 03/10] add: slow possible moves logic as "old" --- python/socha/_socha.pyi | 9 +++ src/plugin/game_state.rs | 124 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/python/socha/_socha.pyi b/python/socha/_socha.pyi index 6398686..953efea 100644 --- a/python/socha/_socha.pyi +++ b/python/socha/_socha.pyi @@ -443,6 +443,15 @@ class GameState: """ ... + def possible_moves_old(self) -> List[Move]: + """ + Gibt eine Liste aller möglichen Züge zurück. + + Returns: + List[Move]: Eine Liste aller möglichen Züge. + """ + ... + def possible_moves(self) -> List[Move]: """ Gibt eine Liste aller möglichen Züge zurück. diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index 8b63043..ee6fb93 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -5,6 +5,7 @@ use super::action::advance::Advance; use super::action::eat_salad::EatSalad; use super::action::exchange_carrots::ExchangeCarrots; use super::action::fall_back::FallBack; +use super::action::card::Card; use super::action::Action; use super::board::Board; use super::constants::PluginConstants; @@ -105,6 +106,17 @@ impl GameState { player_one_in_goal || player_two_in_goal && both_had_last_chance || rounds_exceeded } + pub fn possible_moves_old(&self) -> Vec { + let mut moves = Vec::new(); + + moves.append(&mut self.possible_advance_moves_old()); + moves.append(&mut self.possible_eat_salad_moves()); + moves.append(&mut self.possible_exchange_carrots_moves()); + moves.append(&mut self.possible_fall_back_moves()); + + moves + } + pub fn possible_moves(&self) -> Vec { let mut moves = Vec::new(); @@ -146,7 +158,9 @@ impl GameState { .collect() } - fn possible_advance_moves(&self) -> Vec { + fn possible_advance_moves_old(&self) -> Vec { + println!("begin old"); + let current_player = self.clone_current_player(); let max_distance = (((-1.0 + (1 + 8 * current_player.carrots) as f64).sqrt()) / 2.0) as usize; @@ -182,6 +196,114 @@ impl GameState { moves.push(Move::new(Action::Advance(Advance::new(distance, vec![])))); } + /*for m in &moves { + println!("{}", m); + }*/ + + println!("{}", moves.len()); + + println!("end old\n"); + + moves + .into_iter() + .unique() + .filter(|m| m.perform(&mut self.clone()).is_ok()) + .collect() + } + + fn possible_advance_moves(&self) -> Vec { + println!("begin performance"); + + let current_player = self.clone_current_player(); + let max_distance = + (((-1.0 + (1 + 8 * current_player.carrots) as f64).sqrt()) / 2.0) as usize; + + + let mut card_permutations = Vec::new(); + + for k in 0..=current_player.cards.len() { + for permutation in current_player.cards.iter().permutations(k).unique() { + + // change permutation cards to owned + let owned_permutation: Vec = permutation.iter().map(|&card| card.clone()).collect(); + + /*println!("{}", owned_permutation.len()); + for per in &owned_permutation { + println!("perm: {}", per); + }*/ + + // if minimum one card in permutation, save permutation and add all market cards to it + if !owned_permutation.is_empty() { + card_permutations.push(owned_permutation.clone()); + + for card in PluginConstants::MARKET_SELECTION { + let mut extended_permutation = owned_permutation.clone(); + extended_permutation.push(card); // card is already owned + card_permutations.push(extended_permutation); + } + } + } + } + + + /* + TODO: + if hare in 1 to max_distance or if match_field = hare -> calc and save permutations + + or + + not using permutations anymore, do it like java + */ + + let mut moves: Vec = Vec::new(); + + for distance in 1..=max_distance { + // destination of advance + let target_pos: usize = current_player.position + distance; + + // out of range, skip + if target_pos > self.board.track.len() - 1 { + continue; + } + + // destination field of advance + let target_field: Field = self.board.track[target_pos]; + + //println!("{}", target_field); + + // add card / no card advances for each field type + match target_field { + Field::Hare => { + for permutation in &card_permutations { + moves.push(Move::new(Action::Advance(Advance::new( + distance, + permutation.iter().map(|&c| c).collect(), + )))); + } + }, + Field::Market => { + for card in PluginConstants::MARKET_SELECTION { + moves.push(Move::new(Action::Advance(Advance::new( + distance, + vec![card], + )))); + } + }, + _ => { + moves.push(Move::new(Action::Advance(Advance::new(distance, vec![])))); + } + } + } + + for m in &moves { + println!("{}", m); + } + + println!("{}", moves.len()); + + + println!("end performance\n"); + moves .into_iter() .unique() From 9a5e87c2d40b1d4b5374c5a6d379c1038b27b4ec Mon Sep 17 00:00:00 2001 From: YoEnte Date: Sun, 27 Apr 2025 21:39:22 +0200 Subject: [PATCH 04/10] chore: remove prints and old todos --- src/plugin/game_state.rs | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index ee6fb93..38a93bb 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -159,7 +159,6 @@ impl GameState { } fn possible_advance_moves_old(&self) -> Vec { - println!("begin old"); let current_player = self.clone_current_player(); let max_distance = @@ -196,14 +195,6 @@ impl GameState { moves.push(Move::new(Action::Advance(Advance::new(distance, vec![])))); } - /*for m in &moves { - println!("{}", m); - }*/ - - println!("{}", moves.len()); - - println!("end old\n"); - moves .into_iter() .unique() @@ -212,7 +203,6 @@ impl GameState { } fn possible_advance_moves(&self) -> Vec { - println!("begin performance"); let current_player = self.clone_current_player(); let max_distance = @@ -227,11 +217,6 @@ impl GameState { // change permutation cards to owned let owned_permutation: Vec = permutation.iter().map(|&card| card.clone()).collect(); - /*println!("{}", owned_permutation.len()); - for per in &owned_permutation { - println!("perm: {}", per); - }*/ - // if minimum one card in permutation, save permutation and add all market cards to it if !owned_permutation.is_empty() { card_permutations.push(owned_permutation.clone()); @@ -245,16 +230,6 @@ impl GameState { } } - - /* - TODO: - if hare in 1 to max_distance or if match_field = hare -> calc and save permutations - - or - - not using permutations anymore, do it like java - */ - let mut moves: Vec = Vec::new(); for distance in 1..=max_distance { @@ -269,8 +244,6 @@ impl GameState { // destination field of advance let target_field: Field = self.board.track[target_pos]; - //println!("{}", target_field); - // add card / no card advances for each field type match target_field { Field::Hare => { @@ -295,15 +268,6 @@ impl GameState { } } - for m in &moves { - println!("{}", m); - } - - println!("{}", moves.len()); - - - println!("end performance\n"); - moves .into_iter() .unique() From 1ef83eca9babba6cef7264e19d365d378be4a4b2 Mon Sep 17 00:00:00 2001 From: YoEnte Date: Thu, 29 May 2025 23:56:17 +0200 Subject: [PATCH 05/10] fix: hurry ahead has wrong goal moves --- src/plugin/action/card.rs | 6 ++++++ src/plugin/test/card_test.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/plugin/action/card.rs b/src/plugin/action/card.rs index 3cb8fe2..49e8d54 100644 --- a/src/plugin/action/card.rs +++ b/src/plugin/action/card.rs @@ -35,6 +35,12 @@ impl Card { cards: Vec, ) -> Result<(), PyErr> { let distance = target_position as isize - player.position as isize; + + if target_position == 64 && (player.carrots > 10 || player.salads > 0) { + println!("error"); + return Err(HUIError::new_err("Too many carrots or salads to jump to goal")); + } + RulesEngine::can_move_to( &state.board, distance, diff --git a/src/plugin/test/card_test.rs b/src/plugin/test/card_test.rs index d332416..aee758e 100644 --- a/src/plugin/test/card_test.rs +++ b/src/plugin/test/card_test.rs @@ -62,6 +62,39 @@ mod tests { assert!(hurry_ahead_card.perform(&mut state, vec![], 0).is_ok()); let current_player = state.clone_current_player(); assert_eq!(current_player.position, 8); + + // test hurry ahead in goal with too many carrots (salads are ok but that was never the problem) + let mut state = create_test_game_state(); + state.turn = 1; + + let mut current_player = state.clone_current_player(); + current_player.salads = 0; + state.update_player(current_player); + let mut other_player = state.clone_other_player(); + other_player.position = 10; + state.update_player(other_player); + + let hurry_ahead_card: Card = Card::HurryAhead; + assert!(hurry_ahead_card.perform(&mut state, vec![], 0).is_err()); + let current_player = state.clone_current_player(); + assert_eq!(current_player.position, 3); + + // test hurry ahead in goal with low enough many carrots + let mut state = create_test_game_state(); + state.turn = 1; + + let mut current_player = state.clone_current_player(); + current_player.carrots = 3; + current_player.salads = 0; + state.update_player(current_player); + let mut other_player = state.clone_other_player(); + other_player.position = 10; + state.update_player(other_player); + + let hurry_ahead_card: Card = Card::HurryAhead; + assert!(hurry_ahead_card.perform(&mut state, vec![], 0).is_ok()); + let current_player = state.clone_current_player(); + assert_eq!(current_player.position, 11); } #[test] From 0d8f90d42a4a868005a489f3d862e9108a81c61a Mon Sep 17 00:00:00 2001 From: YoEnte Date: Fri, 30 May 2025 00:07:41 +0200 Subject: [PATCH 06/10] chore: fix github compiler suggestion and remove print --- src/plugin/action/card.rs | 1 - src/plugin/game_state.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugin/action/card.rs b/src/plugin/action/card.rs index 49e8d54..899b21a 100644 --- a/src/plugin/action/card.rs +++ b/src/plugin/action/card.rs @@ -37,7 +37,6 @@ impl Card { let distance = target_position as isize - player.position as isize; if target_position == 64 && (player.carrots > 10 || player.salads > 0) { - println!("error"); return Err(HUIError::new_err("Too many carrots or salads to jump to goal")); } diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index 38a93bb..af8c670 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -215,7 +215,7 @@ impl GameState { for permutation in current_player.cards.iter().permutations(k).unique() { // change permutation cards to owned - let owned_permutation: Vec = permutation.iter().map(|&card| card.clone()).collect(); + let owned_permutation: Vec = permutation.iter().map(|&card| *card).collect(); // if minimum one card in permutation, save permutation and add all market cards to it if !owned_permutation.is_empty() { @@ -250,7 +250,7 @@ impl GameState { for permutation in &card_permutations { moves.push(Move::new(Action::Advance(Advance::new( distance, - permutation.iter().map(|&c| c).collect(), + permutation.iter().copied().collect(), )))); } }, From fd94cfe4df452aca15e2e08ee234787ba4692775 Mon Sep 17 00:00:00 2001 From: YoEnte Date: Fri, 30 May 2025 00:10:43 +0200 Subject: [PATCH 07/10] chore: another suggestion from github compiler --- logic_ahead.py | 89 +++++++++++++++++++++ logic_card_spam.py | 162 +++++++++++++++++++++++++++++++++++++++ logic_current_end.py | 39 ++++++++++ logic_test.py | 70 +++++++++++++++++ run.bat | 5 ++ src/plugin/game_state.rs | 2 +- 6 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 logic_ahead.py create mode 100644 logic_card_spam.py create mode 100644 logic_current_end.py create mode 100644 logic_test.py create mode 100644 run.bat diff --git a/logic_ahead.py b/logic_ahead.py new file mode 100644 index 0000000..ce4038e --- /dev/null +++ b/logic_ahead.py @@ -0,0 +1,89 @@ +# not all imports are currently used, but they might be in the future and it shows all available functionalities +import math +import random +import time +from typing import Optional, Tuple +from socha import * +from socha.api.networking.game_client import IClientHandler +from socha.starter import Starter + + + +class Logic(IClientHandler): + game_state: GameState + + # this method is called every time the server is requesting a new move + # this method should always be implemented otherwise the client will be disqualified + def calculate_move(self) -> Move: + + scores = [] + + i = 0 + for p in self.poss: + scores.append(0) + + ''' + if isinstance(p.action, Advance): + scores[-1] += i*2 + dest = p.action.distance + self.game_state.clone_current_player().position + + if self.game_state.board.get_field(dest) == Field.Market: + if Card.HurryAhead in p.action.cards and len(self.game_state.clone_current_player().cards) < 2: + scores[-1] += 1000 + + + ''' + if isinstance(p.action, Advance): + scores[-1] += i*2 + + dest = p.action.distance + self.game_state.clone_current_player().position + if dest == 63: + scores[-1] += 1000 + + if isinstance(p.action, FallBack): + if self.game_state.clone_current_player().carrots <= 10 and self.game_state.clone_current_player().position != 63: + scores[-1] += 10000 + + if isinstance(p.action, ExchangeCarrots): + if self.game_state.clone_current_player().position == 63: + scores[-1] += (1000 * 999) + + i += 1 + + index = 0 + highest = -1 + + i = 0 + for s in scores: + if s >= highest: + highest = s + index = i + + i += 1 + + print(scores, highest, index) + return self.poss[index] + + # this method is called every time the server has sent a new game state update + # this method should be implemented to keep the game state up to date + def on_update(self, state: GameState) -> None: + self.game_state = state + + #self.poss_old = self.game_state.possible_moves_old() + self.poss = self.game_state.possible_moves() + + print("") + print("turn:", self.game_state.turn) + print("current:", self.game_state.clone_current_player().team) + print("moves old:") + #for p in self.poss_old: + #print(p) + print("moves:") + for p in self.poss: + print(p) + + +if __name__ == "__main__": + Starter(logic=Logic()) + # if u wanna have more insights, u can set the logging level to debug: + # Starter(logic=Logic(), log_level=logging.DEBUG) diff --git a/logic_card_spam.py b/logic_card_spam.py new file mode 100644 index 0000000..166f7d1 --- /dev/null +++ b/logic_card_spam.py @@ -0,0 +1,162 @@ +# not all imports are currently used, but they might be in the future and it shows all available functionalities +import random +from socha import ( + Field, + GameState, + Move, +) +from socha.api.networking.game_client import IClientHandler +from socha.starter import Starter +import random +import time + + +class Logic(IClientHandler): + game_state: GameState + + totalTimesOldSum = 0 + totalTimesPerformanceSum = 0 + count = 0 + countSame = 0 + countSameItems = 0 + + def compare_move_lists(self, one, two) -> bool: + + if len(one) != len(two): + return False + + if len(one) == 0 and len(two) == 0: + return True + + found = 0 + + for o in one: + if o in two: + found += 1 + + if found == len(two): + return True + else: + return False + + + def on_game_over(self, roomMessage): + print("avg old:", self.totalTimesOldSum / self.count) + print("avg perf:", self.totalTimesPerformanceSum / self.count) + print("avg multi:", (self.totalTimesOldSum / self.count) / (self.totalTimesPerformanceSum / self.count)) + print("avg same:", self.countSame / self.count) + print("avg same items:", self.countSameItems / self.count) + + # this method is called every time the server is requesting a new move + # this method should always be implemented otherwise the client will be disqualified + def calculate_move(self) -> Move: + start_time = time.time() + our_player = self.game_state.clone_current_player() + + start_3 = time.time() + + possible_moves = self.game_state.possible_moves_old() + + totalTime3 = time.time() - start_3 + print("3", totalTime3, len(possible_moves)) + self.totalTimesOldSum += totalTime3 + + start_4 = time.time() + + possible_moves_performance = self.game_state.possible_moves() + + totalTime4 = (time.time() - start_4) + 0.000001 + print("4", totalTime4, len(possible_moves_performance)) + self.totalTimesPerformanceSum += totalTime4 + + print("time multi:", totalTime3 / totalTime4) + + start_time_2 = time.time() + board = [] + shop_moves = [] + hedgehog_moves = [] + + print("poss", len(possible_moves)) + print("performance", len(possible_moves_performance)) + print("same:", possible_moves == possible_moves_performance) + print("same items:", self.compare_move_lists(possible_moves, possible_moves_performance)) + + print("moves:") + for p in possible_moves_performance: + print(p) + + self.count += 1 + if possible_moves == possible_moves_performance: + self.countSame += 1 + if self.compare_move_lists(possible_moves, possible_moves_performance): + self.countSameItems += 1 + + # Einmal alle Felder in als Liste board, damit ich leichter drüber iterieren kann. + def fields_on_board(board): + for i in range (0, 64): + board.append(self.game_state.board.get_field(i)) + + + fields_on_board(board) + + + + #Simuliert einmal alle mögliche Züge, und zwischenspeichert diese in new_state, um dann zu prüfen, ob die neue Position auf einem Shopfeld ist. Wenn ja, wird der rohe Move in die Shop_moves Liste hinzugefügt. + def move_for_nearest_important_fields(possible_moves, board, shop_moves, hedgehog_moves, our_player): + + evantual_hedgehog_move = possible_moves[-1] + + possible_moves = possible_moves [:7] + # print (possible_moves) + possible_moves.append(evantual_hedgehog_move) + + try: + for i in range (0, len(possible_moves)): + new_state = self.game_state.perform_move(possible_moves[i]) + our_player_new_position = new_state.clone_other_player().position + if board[our_player_new_position] == Field.Market: # Hier other_player, sind aber noch wir. Der andere Hase ist ja nach der Zugsimulation innerhalb der Simulation dran. + if our_player.position + 7 > new_state.clone_other_player().position: # wenn wir sehr viele Karotten haben, werden die Shopfelder sehr weit vorne NICHT hinzugefügt. + shop_moves.append(possible_moves[i]) + + if board[our_player_new_position] == Field.Hedgehog: + hedgehog_moves.append(possible_moves[i]) + except: + pass + + move_for_nearest_important_fields(possible_moves, board, shop_moves, hedgehog_moves, our_player) + + + duration = time.time() - start_time + duration2 = time.time() - start_time_2 + + print ("Time needed before decision", duration) + print ("2", duration2) + + if shop_moves != []: + print ("Time needed for shop move", duration) + return (random.choice(shop_moves)) + if hedgehog_moves != []: + print ("Time needed for hedgehog move", duration) + return (hedgehog_moves[0]) + if shop_moves == [] and hedgehog_moves == []: + print ("Time needed for random move", duration) + return (random.choice(possible_moves)) + + print ("Time needed for random move, after the other 3 checks", duration) + + return (random.choice(possible_moves)) # Sicherheit, falls oben irgendwas nicht klappt + + + + # this method is called every time the server has sent a new game state update + # this method should be implemented to keep the game state up to date + def on_update(self, state: GameState) -> None: + self.game_state = state + + + + +if __name__ == "__main__": + Starter(logic=Logic()) + # if u wanna have more insights, u can set the logging level to debug: + # Starter(logic=Logic(), log_level=logging.DEBUG) \ No newline at end of file diff --git a/logic_current_end.py b/logic_current_end.py new file mode 100644 index 0000000..3bcba1e --- /dev/null +++ b/logic_current_end.py @@ -0,0 +1,39 @@ +# not all imports are currently used, but they might be in the future and it shows all available functionalities +import math +import random +import time +from typing import Optional, Tuple +from socha import * +from socha.api.networking.game_client import IClientHandler +from socha.starter import Starter + + + +class Logic(IClientHandler): + game_state: GameState + + # this method is called every time the server is requesting a new move + # this method should always be implemented otherwise the client will be disqualified + def calculate_move(self) -> Move: + + return random.choice(self.poss) + + # this method is called every time the server has sent a new game state update + # this method should be implemented to keep the game state up to date + def on_update(self, state: GameState) -> None: + self.game_state = state + + self.poss = self.game_state.possible_moves() + + print("turn:", self.game_state.turn, "ONE" if self.game_state.turn % 2 == 0 else "TWO") + print("current:", self.game_state.clone_current_player().team) + print("same:", str(self.game_state.clone_current_player().team).upper()[-3:] == ("ONE" if self.game_state.turn % 2 == 0 else "TWO")) + print("moves:") + for p in self.poss: + print(p) + + +if __name__ == "__main__": + Starter(logic=Logic()) + # if u wanna have more insights, u can set the logging level to debug: + # Starter(logic=Logic(), log_level=logging.DEBUG) diff --git a/logic_test.py b/logic_test.py new file mode 100644 index 0000000..c831536 --- /dev/null +++ b/logic_test.py @@ -0,0 +1,70 @@ +# not all imports are currently used, but they might be in the future and it shows all available functionalities +import math +import random +import time +from typing import Optional, Tuple +from socha import * +from socha.api.networking.game_client import IClientHandler +from socha.starter import Starter + + + +class Logic(IClientHandler): + game_state: GameState + + # this method is called every time the server is requesting a new move + # this method should always be implemented otherwise the client will be disqualified + def calculate_move(self) -> Move: + + poss = self.game_state.possible_moves_old() + + poss_performance = self.game_state.possible_moves() + + print("poss", len(poss)) + print("performance", len(poss_performance)) + print("same:", poss == poss_performance) + print("same items:", self.compare_move_lists(poss, poss_performance)) + + card_moves = [] + for p in poss: + print(p) + if isinstance(p.action, Advance): + if len(p.action.cards) > 0: + card_moves.append(p) + + if len(card_moves) > 0: + return random.choice(card_moves) + + return random.choice(poss) + + # this method is called every time the server has sent a new game state update + # this method should be implemented to keep the game state up to date + def on_update(self, state: GameState) -> None: + self.game_state = state + + + def compare_move_lists(self, one, two) -> bool: + + if len(one) != len(two): + return False + + if len(one) == 0 and len(two) == 0: + return True + + found = 0 + + for o in one: + if o in two: + found += 1 + + if found == len(two): + return True + else: + return False + + + +if __name__ == "__main__": + Starter(logic=Logic()) + # if u wanna have more insights, u can set the logging level to debug: + # Starter(logic=Logic(), log_level=logging.DEBUG) diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..40e23d8 --- /dev/null +++ b/run.bat @@ -0,0 +1,5 @@ +cls + +maturin develop + +python logic_ahead.py \ No newline at end of file diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index af8c670..d69fd41 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -250,7 +250,7 @@ impl GameState { for permutation in &card_permutations { moves.push(Move::new(Action::Advance(Advance::new( distance, - permutation.iter().copied().collect(), + permutation.to_vec(), )))); } }, From 21709c6a5098406f0696b90dd34ff21eee63415b Mon Sep 17 00:00:00 2001 From: YoEnte Date: Fri, 30 May 2025 00:14:15 +0200 Subject: [PATCH 08/10] Revert "chore: another suggestion from github compiler" This reverts commit fd94cfe4df452aca15e2e08ee234787ba4692775. --- logic_ahead.py | 89 --------------------- logic_card_spam.py | 162 --------------------------------------- logic_current_end.py | 39 ---------- logic_test.py | 70 ----------------- run.bat | 5 -- src/plugin/game_state.rs | 2 +- 6 files changed, 1 insertion(+), 366 deletions(-) delete mode 100644 logic_ahead.py delete mode 100644 logic_card_spam.py delete mode 100644 logic_current_end.py delete mode 100644 logic_test.py delete mode 100644 run.bat diff --git a/logic_ahead.py b/logic_ahead.py deleted file mode 100644 index ce4038e..0000000 --- a/logic_ahead.py +++ /dev/null @@ -1,89 +0,0 @@ -# not all imports are currently used, but they might be in the future and it shows all available functionalities -import math -import random -import time -from typing import Optional, Tuple -from socha import * -from socha.api.networking.game_client import IClientHandler -from socha.starter import Starter - - - -class Logic(IClientHandler): - game_state: GameState - - # this method is called every time the server is requesting a new move - # this method should always be implemented otherwise the client will be disqualified - def calculate_move(self) -> Move: - - scores = [] - - i = 0 - for p in self.poss: - scores.append(0) - - ''' - if isinstance(p.action, Advance): - scores[-1] += i*2 - dest = p.action.distance + self.game_state.clone_current_player().position - - if self.game_state.board.get_field(dest) == Field.Market: - if Card.HurryAhead in p.action.cards and len(self.game_state.clone_current_player().cards) < 2: - scores[-1] += 1000 - - - ''' - if isinstance(p.action, Advance): - scores[-1] += i*2 - - dest = p.action.distance + self.game_state.clone_current_player().position - if dest == 63: - scores[-1] += 1000 - - if isinstance(p.action, FallBack): - if self.game_state.clone_current_player().carrots <= 10 and self.game_state.clone_current_player().position != 63: - scores[-1] += 10000 - - if isinstance(p.action, ExchangeCarrots): - if self.game_state.clone_current_player().position == 63: - scores[-1] += (1000 * 999) - - i += 1 - - index = 0 - highest = -1 - - i = 0 - for s in scores: - if s >= highest: - highest = s - index = i - - i += 1 - - print(scores, highest, index) - return self.poss[index] - - # this method is called every time the server has sent a new game state update - # this method should be implemented to keep the game state up to date - def on_update(self, state: GameState) -> None: - self.game_state = state - - #self.poss_old = self.game_state.possible_moves_old() - self.poss = self.game_state.possible_moves() - - print("") - print("turn:", self.game_state.turn) - print("current:", self.game_state.clone_current_player().team) - print("moves old:") - #for p in self.poss_old: - #print(p) - print("moves:") - for p in self.poss: - print(p) - - -if __name__ == "__main__": - Starter(logic=Logic()) - # if u wanna have more insights, u can set the logging level to debug: - # Starter(logic=Logic(), log_level=logging.DEBUG) diff --git a/logic_card_spam.py b/logic_card_spam.py deleted file mode 100644 index 166f7d1..0000000 --- a/logic_card_spam.py +++ /dev/null @@ -1,162 +0,0 @@ -# not all imports are currently used, but they might be in the future and it shows all available functionalities -import random -from socha import ( - Field, - GameState, - Move, -) -from socha.api.networking.game_client import IClientHandler -from socha.starter import Starter -import random -import time - - -class Logic(IClientHandler): - game_state: GameState - - totalTimesOldSum = 0 - totalTimesPerformanceSum = 0 - count = 0 - countSame = 0 - countSameItems = 0 - - def compare_move_lists(self, one, two) -> bool: - - if len(one) != len(two): - return False - - if len(one) == 0 and len(two) == 0: - return True - - found = 0 - - for o in one: - if o in two: - found += 1 - - if found == len(two): - return True - else: - return False - - - def on_game_over(self, roomMessage): - print("avg old:", self.totalTimesOldSum / self.count) - print("avg perf:", self.totalTimesPerformanceSum / self.count) - print("avg multi:", (self.totalTimesOldSum / self.count) / (self.totalTimesPerformanceSum / self.count)) - print("avg same:", self.countSame / self.count) - print("avg same items:", self.countSameItems / self.count) - - # this method is called every time the server is requesting a new move - # this method should always be implemented otherwise the client will be disqualified - def calculate_move(self) -> Move: - start_time = time.time() - our_player = self.game_state.clone_current_player() - - start_3 = time.time() - - possible_moves = self.game_state.possible_moves_old() - - totalTime3 = time.time() - start_3 - print("3", totalTime3, len(possible_moves)) - self.totalTimesOldSum += totalTime3 - - start_4 = time.time() - - possible_moves_performance = self.game_state.possible_moves() - - totalTime4 = (time.time() - start_4) + 0.000001 - print("4", totalTime4, len(possible_moves_performance)) - self.totalTimesPerformanceSum += totalTime4 - - print("time multi:", totalTime3 / totalTime4) - - start_time_2 = time.time() - board = [] - shop_moves = [] - hedgehog_moves = [] - - print("poss", len(possible_moves)) - print("performance", len(possible_moves_performance)) - print("same:", possible_moves == possible_moves_performance) - print("same items:", self.compare_move_lists(possible_moves, possible_moves_performance)) - - print("moves:") - for p in possible_moves_performance: - print(p) - - self.count += 1 - if possible_moves == possible_moves_performance: - self.countSame += 1 - if self.compare_move_lists(possible_moves, possible_moves_performance): - self.countSameItems += 1 - - # Einmal alle Felder in als Liste board, damit ich leichter drüber iterieren kann. - def fields_on_board(board): - for i in range (0, 64): - board.append(self.game_state.board.get_field(i)) - - - fields_on_board(board) - - - - #Simuliert einmal alle mögliche Züge, und zwischenspeichert diese in new_state, um dann zu prüfen, ob die neue Position auf einem Shopfeld ist. Wenn ja, wird der rohe Move in die Shop_moves Liste hinzugefügt. - def move_for_nearest_important_fields(possible_moves, board, shop_moves, hedgehog_moves, our_player): - - evantual_hedgehog_move = possible_moves[-1] - - possible_moves = possible_moves [:7] - # print (possible_moves) - possible_moves.append(evantual_hedgehog_move) - - try: - for i in range (0, len(possible_moves)): - new_state = self.game_state.perform_move(possible_moves[i]) - our_player_new_position = new_state.clone_other_player().position - if board[our_player_new_position] == Field.Market: # Hier other_player, sind aber noch wir. Der andere Hase ist ja nach der Zugsimulation innerhalb der Simulation dran. - if our_player.position + 7 > new_state.clone_other_player().position: # wenn wir sehr viele Karotten haben, werden die Shopfelder sehr weit vorne NICHT hinzugefügt. - shop_moves.append(possible_moves[i]) - - if board[our_player_new_position] == Field.Hedgehog: - hedgehog_moves.append(possible_moves[i]) - except: - pass - - move_for_nearest_important_fields(possible_moves, board, shop_moves, hedgehog_moves, our_player) - - - duration = time.time() - start_time - duration2 = time.time() - start_time_2 - - print ("Time needed before decision", duration) - print ("2", duration2) - - if shop_moves != []: - print ("Time needed for shop move", duration) - return (random.choice(shop_moves)) - if hedgehog_moves != []: - print ("Time needed for hedgehog move", duration) - return (hedgehog_moves[0]) - if shop_moves == [] and hedgehog_moves == []: - print ("Time needed for random move", duration) - return (random.choice(possible_moves)) - - print ("Time needed for random move, after the other 3 checks", duration) - - return (random.choice(possible_moves)) # Sicherheit, falls oben irgendwas nicht klappt - - - - # this method is called every time the server has sent a new game state update - # this method should be implemented to keep the game state up to date - def on_update(self, state: GameState) -> None: - self.game_state = state - - - - -if __name__ == "__main__": - Starter(logic=Logic()) - # if u wanna have more insights, u can set the logging level to debug: - # Starter(logic=Logic(), log_level=logging.DEBUG) \ No newline at end of file diff --git a/logic_current_end.py b/logic_current_end.py deleted file mode 100644 index 3bcba1e..0000000 --- a/logic_current_end.py +++ /dev/null @@ -1,39 +0,0 @@ -# not all imports are currently used, but they might be in the future and it shows all available functionalities -import math -import random -import time -from typing import Optional, Tuple -from socha import * -from socha.api.networking.game_client import IClientHandler -from socha.starter import Starter - - - -class Logic(IClientHandler): - game_state: GameState - - # this method is called every time the server is requesting a new move - # this method should always be implemented otherwise the client will be disqualified - def calculate_move(self) -> Move: - - return random.choice(self.poss) - - # this method is called every time the server has sent a new game state update - # this method should be implemented to keep the game state up to date - def on_update(self, state: GameState) -> None: - self.game_state = state - - self.poss = self.game_state.possible_moves() - - print("turn:", self.game_state.turn, "ONE" if self.game_state.turn % 2 == 0 else "TWO") - print("current:", self.game_state.clone_current_player().team) - print("same:", str(self.game_state.clone_current_player().team).upper()[-3:] == ("ONE" if self.game_state.turn % 2 == 0 else "TWO")) - print("moves:") - for p in self.poss: - print(p) - - -if __name__ == "__main__": - Starter(logic=Logic()) - # if u wanna have more insights, u can set the logging level to debug: - # Starter(logic=Logic(), log_level=logging.DEBUG) diff --git a/logic_test.py b/logic_test.py deleted file mode 100644 index c831536..0000000 --- a/logic_test.py +++ /dev/null @@ -1,70 +0,0 @@ -# not all imports are currently used, but they might be in the future and it shows all available functionalities -import math -import random -import time -from typing import Optional, Tuple -from socha import * -from socha.api.networking.game_client import IClientHandler -from socha.starter import Starter - - - -class Logic(IClientHandler): - game_state: GameState - - # this method is called every time the server is requesting a new move - # this method should always be implemented otherwise the client will be disqualified - def calculate_move(self) -> Move: - - poss = self.game_state.possible_moves_old() - - poss_performance = self.game_state.possible_moves() - - print("poss", len(poss)) - print("performance", len(poss_performance)) - print("same:", poss == poss_performance) - print("same items:", self.compare_move_lists(poss, poss_performance)) - - card_moves = [] - for p in poss: - print(p) - if isinstance(p.action, Advance): - if len(p.action.cards) > 0: - card_moves.append(p) - - if len(card_moves) > 0: - return random.choice(card_moves) - - return random.choice(poss) - - # this method is called every time the server has sent a new game state update - # this method should be implemented to keep the game state up to date - def on_update(self, state: GameState) -> None: - self.game_state = state - - - def compare_move_lists(self, one, two) -> bool: - - if len(one) != len(two): - return False - - if len(one) == 0 and len(two) == 0: - return True - - found = 0 - - for o in one: - if o in two: - found += 1 - - if found == len(two): - return True - else: - return False - - - -if __name__ == "__main__": - Starter(logic=Logic()) - # if u wanna have more insights, u can set the logging level to debug: - # Starter(logic=Logic(), log_level=logging.DEBUG) diff --git a/run.bat b/run.bat deleted file mode 100644 index 40e23d8..0000000 --- a/run.bat +++ /dev/null @@ -1,5 +0,0 @@ -cls - -maturin develop - -python logic_ahead.py \ No newline at end of file diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index d69fd41..af8c670 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -250,7 +250,7 @@ impl GameState { for permutation in &card_permutations { moves.push(Move::new(Action::Advance(Advance::new( distance, - permutation.to_vec(), + permutation.iter().copied().collect(), )))); } }, From 769df2df8b0d7addf70950b61d3feb599301f0fa Mon Sep 17 00:00:00 2001 From: YoEnte Date: Fri, 30 May 2025 00:14:53 +0200 Subject: [PATCH 09/10] chore: another github compiler suggestion --- src/plugin/game_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index af8c670..d69fd41 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -250,7 +250,7 @@ impl GameState { for permutation in &card_permutations { moves.push(Move::new(Action::Advance(Advance::new( distance, - permutation.iter().copied().collect(), + permutation.to_vec(), )))); } }, From d63e14285de045d59342f7d518a1b12f9f8da814 Mon Sep 17 00:00:00 2001 From: YoEnte Date: Fri, 30 May 2025 20:27:57 +0200 Subject: [PATCH 10/10] fix: use constant for goal position (like in hare.rs:72) --- src/plugin/action/card.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/action/card.rs b/src/plugin/action/card.rs index 899b21a..32fad01 100644 --- a/src/plugin/action/card.rs +++ b/src/plugin/action/card.rs @@ -36,7 +36,7 @@ impl Card { ) -> Result<(), PyErr> { let distance = target_position as isize - player.position as isize; - if target_position == 64 && (player.carrots > 10 || player.salads > 0) { + if target_position == PluginConstants::NUM_FIELDS - 1 && (player.carrots > 10 || player.salads > 0) { return Err(HUIError::new_err("Too many carrots or salads to jump to goal")); }