
    Ii|/                    l   S r SSKJr  SSKrSSKrSSKJrJr  SSKJ	r	  \R                  " \5      rSSKJrJrJr  SSKJrJrJr  SS	KJrJrJrJrJrJrJr  \	(       a  SS
KJrJr  \" SSS9r SS jr!\"" 5       r#SS jr$SS jr%SS jr&SS jr'SS jr(SS jr)SS jr*SS jr+S S jr,SS jr-S!S jr.SS jr/g)"u   Bot AI for Backgammon — GNUBG engine with random/simple fallback.

GNUBG queries run in a background thread to avoid blocking the server.
bot_think() returns None while waiting for a result, and the BotHelper
will retry on the next tick.
    )annotationsN)FutureThreadPoolExecutor)TYPE_CHECKING   )GnubgProcessis_gnubg_availableresolve_next_action)BackgammonMovegenerate_legal_moveshas_any_legal_move)	bar_count
color_sign	off_countopponent_colorpoint_countpoint_ownerremaining_dice_unique)BackgammonGameBackgammonPlayergnubg)max_workersthread_name_prefixc                \   U R                   nUR                  n[        U 5      nUb  U$ [        U 5      (       a  gUR                  S:X  a9  U R
                  R                  5         [        X5      nU[        L a  gU(       a  U$ gUR                  S:X  a  [        X5      $ UR                  S:X  a  [        X#5      (       d  U R                  5         gU R
                  (       aB  [        U R
                  X#U R                  S9nU(       a  U$ U R
                  R                  5         [        X5      $ g)zDecide the bot's next action.Npre_rollpoint_0doublingmoving)forced_dice)
game_statecolor_check_pending_is_pending
turn_phase
_bot_goalsclear_maybe_offer_double_WAITING_decide_take_or_dropr   _end_moving_phaser
   _forced_dice
_pick_move)gameplayergsr!   pendingcube_actionactions          :c:\Users\dbart\PlayPalace11\server\games\backgammon\bot.py	bot_thinkr4   $   s    	BLLE T"G4	}}
")$7("	}}
"#D11	}} !",,""$??("QUQbQbcFOO!!#$''    c                <    [         R                  " U/UQ76 U l        g)z(Submit a GNUBG query to the thread pool.N)_gnubg_poolsubmit_gnubg_future)r-   fnargss      r3   _submit_asyncr<   Q   s    $++B66Dr5   c                \    [        U SS5      nUSL=(       a    UR                  5       (       + $ )z*Check if there's an in-flight GNUBG query.r9   N)getattrdone)r-   futures     r3   r#   r#   V   s)    T?D1F3fkkm"33r5   c                   [        U SS5      nUb  UR                  5       (       d  gSU l         UR                  SS9n[        U[        5      (       a  X l        gUb  U$ [        R                  SU R                  R                  5        [        U 5        U R                  nUR                  nUR                  S:X  a  [        X5      $ UR                  S:X  a  gUR                  S	:X  a  g
g! [         a    Sn Nf = f)zECheck if a pending GNUBG query has completed. Returns action or None.r9   Nr   )timeoutz%GNUBG future returned None (phase=%s)r   r   r   r   accept_double)r>   r?   r9   result	Exception
isinstancelistr%   logwarningr    r$   _notify_fallbackcurrent_color_pick_simple_move)r-   r@   rD   r/   r!   s        r3   r"   r"   \   s    #D/4@F~V[[]]Dq)
 &$   KK79S9STT	BE	}}  --	}}
"	}}
"/  s   C* *C98C9c                  ^ ^^ SSK Jn  T R                  R                  nUS;   a  gUR	                  U5      nUc  gUS:X  a  gT R                  T5      (       d  g[        T U5      mTc  gU UU4S jn[        T U5        [        $ )z|Check if a GNUBG-backed bot should offer a double before rolling.

Returns an action string, None (no double), or _WAITING.
r   DIFFICULTY_PLYrandomsimpleNwhackgammonc                 n   > TR                  TR                  TR                  5      n U S;   a  U Tl        gg)N)zdouble-takedouble-passoffer_doubler   )get_cube_decisionr    r!   _gnubg_cube_decision)decisionr-   
gnubg_procr.   s    r3   _query#_maybe_offer_double.<locals>._query   s4    //N55(0D%!r5   )	r-   rO   optionsbot_difficultyget_can_double_get_gnubg_processr<   r(   )r-   r.   rO   
difficultyplyr[   rZ   s   ``    @r3   r'   r'      s    
 %,,J))


Z
(C
{]"F###D#.J $Or5   c                    SSK Jn  U R                  R                  nUS;   a  gUR	                  U5      nUc  gUS:X  a  g[        U SS5      nUS:X  a  SU l        g	SU l        g)
u  Decide whether to take or drop a double offer.

Uses the cube decision stored by _maybe_offer_double when the opponent
doubled. GNUBG's analysis is always from the doubler's perspective —
"double-pass" means the receiver should drop, "double-take" means take.
r   rN   rP   rC   NrS   rX   rU   drop_double)r-   rO   r]   r^   r_   r>   rX   )r-   r.   rO   rb   rc   storeds         r3   r)   r)      st     %,,J))


Z
(C
{]" T148F$(! $Dr5   c                x  ^^^^	 SSK Jn  U R                  mUR                  mU R                  R
                  nUS:X  a  [        U T5      $ US:X  a  [        U T5      $ UR                  U5      nUc  [        U T5      $ US:H  m	[        X5      mTc  [        U 5        [        U T5      $ UUUU	4S jn[        X5        g)z/Pick a move based on the configured difficulty.r   rN   rQ   rR   NrS   c                 p   > T(       a  TR                  TT5      n OTR                  TT5      n U (       a  U $ g )N)get_worst_moveget_best_move)goalsr!   rZ   r/   is_whacks    r3   r[   _pick_move.<locals>._query   s5    --b%8E,,R7ELr5   )r-   rO   r    r!   r]   r^   _pick_random_moverL   r_   ra   rJ   r<   )
r-   r.   rO   rb   rc   r[   r!   rZ   r/   rl   s
         @@@@r3   r,   r,      s    $	BLLE,,JX u--X u-- 

Z
(C
{ u--]*H#D.J u--  $r5   c                    U R                   n[        U5       HJ  n[        X!U5      nU(       d  M  [        R                  " U5      nSUR
                   SUR                   3s  $    g)z7Pick a random legal move, trying all unused die values.point__N)r    r   r   rQ   choicesourcedestination)r-   r!   r/   die_valmovesmoves         r3   rn   rn      s\    	B(,$R85=='DDKK=$*:*:);<<	 -
 r5   c                    U R                   nSnSn[        U5       H-  n[        X!U5       H  n[        X&U5      nXt:  d  M  UnUnM     M/     Uc  gSUR                   SUR
                   3$ )a  Pick a move using simple heuristics.

Priority scoring:
- Bearing off is great
- Hitting an opponent blot is good
- Making a new point (landing where we have exactly 1) is good
- Escaping from opponent's home board is decent
- Leaving a blot in a dangerous area is bad
Nirp   rq   )r    r   r   _score_movers   rt   )r-   r!   r/   	best_move
best_scoreru   rw   scores           r3   rL   rL      s     
B'+IJ(,(G<D%0E!"
 		 = - I$$%Qy'<'<&=>>r5   c                   Sn[        U5      n[        U5      nUR                  (       a  US-  nUR                  (       a<  US-  nUS:X  a  UR                  S::  a  US-  nOUS:X  a  UR                  S:  a  US-  nUR                  (       d  UR                  S:  aw  UR                  S	::  ag  U R
                  R                  UR                     nXd-  S
:X  a<  US-  nUS:X  a  UR                  S::  a  US-  nOUS:X  a  UR                  S:  a  US-  nUR                  S:  an  [        U R
                  R                  UR                     5      nUS:X  a<  US-  nUS:X  a  UR                  S:  a  US-  nOUS:X  a  UR                  S::  a  US-  nUR                  (       d  UR                  S:  a  UR                  S	::  ax  U R
                  R                  UR                     nX-  S:X  aM  UR                  (       d<  US-  nUS:X  a  UR                  S:  a  US-  nOUS:X  a  UR                  S::  a  US-  nUR                  S:  a7  US:X  a  UR                  S:  a  US-  nOUS:X  a  UR                  S::  a  US-  nUR                  S:X  a  US-  nUR                  S:  a.  US:X  a  X1R                  S-  -  nU$ US	UR                  -
  S-  -  nU$ )z6Score a move with simple heuristics. Higher is better.r   d   (   red      white      r   #         
         )	r   r   is_bear_offis_hitrt   boardpointsrs   abs)	r/   rw   r!   r|   signoppcurrent	src_countdest_vals	            r3   ry   ry     s   EeD

C  {{E>d..!3RKEg$"2"2b"8RKE  0 0A 5$:J:Jb:P((//$"2"23>QRKE~$"2"2a"7'!d&6&6"&< {{a45	>RKE~$++"3'!dkkQ&6  0 0A 5$:J:Jb:P88??4#3#34?aRKE~$"2"2b"8'!d&6&6!&; {{aE>dkkR/QJEg$++"2QJE {{b
 {{aE>[[A%%E L b4;;&1,,ELr5   c                Z    [        U SS5      (       a  gSU l        U R                  S5        g)zHNotify players once that GNUBG is unavailable and bot is using fallback._gnubg_fallback_notifiedFNTzbackgammon-gnubg-fallback)r>   r   broadcast_l)r-   s    r3   rJ   rJ   Z  s,    t/77$(D!01r5   c                    [        U SS5      nUb  U$ [        5       (       d  g[        US9nUR                  5       (       a  X l        U$ g)z.Get or create the GNUBG process for this game._gnubg_procN)rc   )r>   r	   r   startr   )r-   rc   procs      r3   ra   ra   b  sJ    4-DC Dzz||r5   c                p    S H0  n[        XS5      nUc  M  UR                  5         [        XS5        M2     g)z(Stop GNUBG processes when the game ends.)r   
_hint_procN)r>   stopsetattr)r-   attrr   s      r3   cleanup_gnubgr   s  s1    -t4(IIKD%	 .r5   )r-   r   r.   r   return
str | None)r-   r   r   None)r-   r   r   bool)r-   r   r   r   )r-   r   r.   r   )r-   r   r!   strr   r   )rw   r   r!   r   r   int)r-   r   rc   r   r   zGnubgProcess | None)0__doc__
__future__r   loggingrQ   concurrent.futuresr   r   typingr   	getLogger__name__rH   r   r   r	   r
   rv   r   r   r   stater   r   r   r   r   r   r   r-   r   r   r7   r4   objectr(   r<   r#   r"   r'   r)   r,   rn   rL   ry   rJ   ra   r    r5   r3   <module>r      s    #   9  ! H H K K   6 !Q7K&T 87
4 F#L<#L?4IX2"&r5   