@@ -70,7 +70,7 @@ impl AI {
7070 . map ( |( move_idx, score) | {
7171 let piece = & state. get_pieces ( state. current_player ) [ * move_idx as usize ] ;
7272 let track = GameState :: get_player_track ( state. current_player ) ;
73- let ( move_type, to_square) = if piece. square == - 1 {
73+ let ( move_type, to_square) = if piece. is_at_start ( ) {
7474 ( "enter" . to_string ( ) , Some ( track[ 0 ] ) )
7575 } else if let Some ( track_pos) =
7676 track. iter ( ) . position ( |& sq| sq as i8 == piece. square )
@@ -205,7 +205,7 @@ impl AI {
205205
206206 fn get_move_score ( & self , piece : & PiecePosition , state : & GameState ) -> i32 {
207207 let mut score = 0 ;
208- if piece. square == - 1 {
208+ if piece. is_at_start ( ) {
209209 score += 100 ;
210210 } else {
211211 let track = GameState :: get_player_track ( state. current_player ) ;
@@ -226,6 +226,26 @@ impl AI {
226226 }
227227 score
228228 }
229+
230+ pub fn clear_transposition_table ( & mut self ) {
231+ self . transposition_table . clear ( ) ;
232+ self . nodes_evaluated = 0 ;
233+ self . transposition_hits = 0 ;
234+ }
235+
236+ pub fn get_cache_stats ( & self ) -> ( usize , u32 , u32 ) {
237+ (
238+ self . transposition_table . len ( ) ,
239+ self . nodes_evaluated ,
240+ self . transposition_hits ,
241+ )
242+ }
243+ }
244+
245+ impl Default for AI {
246+ fn default ( ) -> Self {
247+ Self :: new ( )
248+ }
229249}
230250
231251#[ derive( Serialize ) ]
@@ -240,3 +260,139 @@ pub struct MoveEvaluation {
240260 #[ serde( rename = "toSquare" ) ]
241261 pub to_square : Option < u8 > ,
242262}
263+
264+ #[ cfg( test) ]
265+ mod tests {
266+ use super :: * ;
267+
268+ #[ test]
269+ fn test_ai_creation ( ) {
270+ let ai = AI :: new ( ) ;
271+ assert_eq ! ( ai. nodes_evaluated, 0 ) ;
272+ assert_eq ! ( ai. transposition_hits, 0 ) ;
273+ assert ! ( ai. transposition_table. is_empty( ) ) ;
274+ }
275+
276+ #[ test]
277+ fn test_ai_default ( ) {
278+ let ai1 = AI :: new ( ) ;
279+ let ai2 = AI :: default ( ) ;
280+ assert_eq ! ( ai1. nodes_evaluated, ai2. nodes_evaluated) ;
281+ assert_eq ! ( ai1. transposition_hits, ai2. transposition_hits) ;
282+ }
283+
284+ #[ test]
285+ fn test_ai_best_move_single_option ( ) {
286+ let mut state = GameState :: new ( ) ;
287+ state. dice_roll = 1 ;
288+ state. current_player = Player :: Player1 ;
289+
290+ for i in 1 ..PIECES_PER_PLAYER {
291+ state. player1_pieces [ i] . square = 20 ;
292+ }
293+
294+ let mut ai = AI :: new ( ) ;
295+ let ( best_move, evaluations) = ai. get_best_move ( & state, 2 ) ;
296+
297+ assert_eq ! ( best_move, 0 ) ;
298+ assert ! ( evaluations. len( ) >= 1 ) ;
299+ assert_eq ! ( evaluations[ 0 ] . piece_index, 0 ) ;
300+ }
301+
302+ #[ test]
303+ fn test_ai_best_move_multiple_options ( ) {
304+ let mut state = GameState :: new ( ) ;
305+ state. dice_roll = 1 ;
306+ state. current_player = Player :: Player1 ;
307+
308+ let mut ai = AI :: new ( ) ;
309+ let ( best_move, evaluations) = ai. get_best_move ( & state, 1 ) ;
310+
311+ assert ! ( best_move < PIECES_PER_PLAYER as u8 ) ;
312+ assert ! ( !evaluations. is_empty( ) ) ;
313+ assert_eq ! ( evaluations. len( ) , PIECES_PER_PLAYER ) ;
314+ }
315+
316+ #[ test]
317+ fn test_ai_no_valid_moves ( ) {
318+ let mut state = GameState :: new ( ) ;
319+ state. dice_roll = 0 ;
320+
321+ let mut ai = AI :: new ( ) ;
322+ let ( best_move, evaluations) = ai. get_best_move ( & state, 1 ) ;
323+
324+ assert_eq ! ( best_move, 0 ) ;
325+ assert ! ( evaluations. is_empty( ) ) ;
326+ }
327+
328+ #[ test]
329+ fn test_ai_clear_transposition_table ( ) {
330+ let mut ai = AI :: new ( ) ;
331+ ai. nodes_evaluated = 100 ;
332+ ai. transposition_hits = 50 ;
333+ ai. transposition_table . insert (
334+ 123 ,
335+ TranspositionEntry {
336+ evaluation : 0 ,
337+ depth : 1 ,
338+ } ,
339+ ) ;
340+
341+ ai. clear_transposition_table ( ) ;
342+
343+ assert_eq ! ( ai. nodes_evaluated, 0 ) ;
344+ assert_eq ! ( ai. transposition_hits, 0 ) ;
345+ assert ! ( ai. transposition_table. is_empty( ) ) ;
346+ }
347+
348+ #[ test]
349+ fn test_ai_get_cache_stats ( ) {
350+ let mut ai = AI :: new ( ) ;
351+ ai. nodes_evaluated = 100 ;
352+ ai. transposition_hits = 50 ;
353+ ai. transposition_table . insert (
354+ 123 ,
355+ TranspositionEntry {
356+ evaluation : 0 ,
357+ depth : 1 ,
358+ } ,
359+ ) ;
360+
361+ let ( cache_size, nodes, hits) = ai. get_cache_stats ( ) ;
362+
363+ assert_eq ! ( cache_size, 1 ) ;
364+ assert_eq ! ( nodes, 100 ) ;
365+ assert_eq ! ( hits, 50 ) ;
366+ }
367+
368+ #[ test]
369+ fn test_move_evaluation_creation ( ) {
370+ let eval = MoveEvaluation {
371+ piece_index : 2 ,
372+ score : 150.5 ,
373+ move_type : "capture" . to_string ( ) ,
374+ from_square : 5 ,
375+ to_square : Some ( 10 ) ,
376+ } ;
377+
378+ assert_eq ! ( eval. piece_index, 2 ) ;
379+ assert_eq ! ( eval. score, 150.5 ) ;
380+ assert_eq ! ( eval. move_type, "capture" ) ;
381+ assert_eq ! ( eval. from_square, 5 ) ;
382+ assert_eq ! ( eval. to_square, Some ( 10 ) ) ;
383+ }
384+
385+ #[ test]
386+ fn test_get_move_score ( ) {
387+ let ai = AI :: new ( ) ;
388+ let state = GameState :: new ( ) ;
389+
390+ let start_piece = PiecePosition :: new ( -1 , Player :: Player1 ) ;
391+ let score = ai. get_move_score ( & start_piece, & state) ;
392+ assert ! ( score > 0 ) ;
393+
394+ let board_piece = PiecePosition :: new ( 5 , Player :: Player1 ) ;
395+ let score = ai. get_move_score ( & board_piece, & state) ;
396+ assert ! ( score >= 0 ) ;
397+ }
398+ }
0 commit comments