
    Ii:                        S r SSKJr  SSKJrJr  SSKJr  SSKJ	r	J
r
Jr  SSKJr  \(       a  SS	KJr  SS
KJr  Sr\ " S S5      5       r " S S5      rSS jrSS jrg)u  Grid navigation mixin for 2D board games.

Provides a reusable foundation for games that use a 2D grid (Battleship,
Minesweeper, Chess, etc.).  The mixin manages per-player cursor state,
spatial keybind navigation, grid cell action generation, and the
``grid_enabled`` / ``grid_width`` protocol flags so that both the desktop
and web clients can render a proper 2D layout.

Cell content announcements are **not** handled here — subclasses must
override ``get_cell_label(row, col, player, locale)`` to describe what
is in each cell.
    )annotations)	dataclassfield)TYPE_CHECKING   )Action	ActionSet
Visibility   )Localization)Player)User
grid_cell_c                  6    \ rS rSr% SrSrS\S'   SrS\S'   Srg)	
GridCursor   z'Per-player cursor position on the grid.r   introwcol N)	__name__
__module____qualname____firstlineno____doc__r   __annotations__r   __static_attributes__r       ;c:\Users\dbart\PlayPalace11\server\game_utils\grid_mixin.pyr   r      s    1CLCLr   r   c                  "   \ rS rSrS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S)S jrS(S jrS)S jrS'S jrSS.     S*S jjrSS.S+S jjrS,S jrSS jrS-S jrS.S jrS/S jrS0S jrSrg)1GridGameMixin&   a  Mixin adding 2D grid support to a Game subclass.

Subclasses must set ``grid_rows`` and ``grid_cols`` before calling
``_init_grid()``.  Override ``get_cell_label`` to provide
game-specific cell descriptions.

Attributes consumed from the host Game class:
    - ``self.players``
    - ``self.status``
    - ``self.get_user(player) -> User | None``
    - ``self.get_active_players() -> list[Player]``
    - ``self.rebuild_all_menus()``
    - ``self.update_player_menu(player, selection_id=...)``

Serialised fields (add to your Game dataclass):
    grid_rows: int
    grid_cols: int
    grid_cursors: dict[str, GridCursor]   # player_id -> cursor
    grid_row_labels: list[str]
    grid_col_labels: list[str]
c                h   [        U S5      (       d  0 U l        U R                  5        H@  nUR                  U R                  ;  d  M  [	        SSS9U R                  UR                  '   MB     [        U S5      (       a  U R
                  (       d7  [        U R                  5       Vs/ s H  n[        US-   5      PM     snU l        [        U S5      (       a  U R                  (       dA  [        U R                  5       Vs/ s H  n[        [        S5      U-   5      PM     snU l	        ggs  snf s  snf )	z8Initialise the grid state.  Call once from ``on_start``.grid_cursorsr   r   r   grid_row_labelsr   grid_col_labelsAN)hasattrr$   get_active_playersidr   r&   range	grid_rowsstrr'   	grid_colschrord)selfplayeris      r   
_init_gridGridGameMixin._init_gridA   s    t^,,79D--/Fyy 1 11/9aQ/G!!&)), 0 t.//t7K7K8=dnn8M#N8M1CAJ8M#ND t.//t7K7K?DT^^?T#U?T!CC1$5?T#UD  8L $O#Us   &D*?!D/c                    [        S5      e)u>  Return the display label for one grid cell.

This is what appears as the menu item text **and** what the
screen reader speaks when the cursor lands on the cell.  Include
the coordinate plus any game-specific content, e.g.
``"A1 — empty"`` or ``"B3 — hit"``.

Must be overridden by every game that uses this mixin.
zBSubclasses must implement get_cell_label(row, col, player, locale))NotImplementedError)r2   r   r   r3   locales        r   get_cell_labelGridGameMixin.get_cell_labelQ   s     "P
 	
r   c                    g)zCalled when a player selects (Enter / tap) a grid cell.

Override to implement game-specific selection logic (fire a shot,
place a piece, reveal a cell, etc.).
Nr   r2   r3   r   r   s       r   on_grid_selectGridGameMixin.on_grid_selectc   s     	r   c                J    U R                   S:w  a  gUR                  (       a  gg)zReturn ``None`` if the cell is selectable, or a disabled-reason
localization key if not.  Default: all cells enabled during play.
playingaction-not-playingaction-spectatorNstatusis_spectatorr=   s       r   is_grid_cell_enabled"GridGameMixin.is_grid_cell_enabledk   s"     ;;)#'%r   c                b    U R                   S:w  a  [        R                  $ [        R                  $ )z@Return cell visibility.  Default: all cells visible during play.rA   )rE   r
   HIDDENVISIBLEr=   s       r   is_grid_cell_hidden!GridGameMixin.is_grid_cell_hiddenu   s&    ;;)#$$$!!!r   c                    U R                   R                  UR                  5      nUc"  [        SSS9nX R                   UR                  '   U$ )Nr   r%   )r$   getr+   r   r2   r3   cursors      r   _get_cursorGridGameMixin._get_cursor   sD    ""&&vyy1>A1-F+1fii(r   c                    [        S[        UR                  U R                  S-
  5      5      Ul        [        S[        UR                  U R
                  S-
  5      5      Ul        g Nr   r   )maxminr   r-   r   r/   r2   rQ   s     r   _clamp_cursorGridGameMixin._clamp_cursor   sF    C

DNNQ,>?@
C

DNNQ,>?@
r   c                B    [        UR                  UR                  5      $ N)grid_cell_idr   r   rX   s     r   _cursor_cell_idGridGameMixin._cursor_cell_id   s    FJJ

33r   c                T    UR                   U R                  -  UR                  -   S-   $ )z2Return 1-based flat index for the cursor position.r   )r   r/   r   rX   s     r   _grid_position_for_cursor'GridGameMixin._grid_position_for_cursor   s#    zzDNN*VZZ7!;;r   c                <   U R                  U5      nUR                  S5      nUR                  UR                  peUS:X  a&  UR                  S:  a  U=R                  S-  sl        OUS:X  a3  UR                  U R                  S-
  :  a  U=R                  S-  sl        OdUS:X  a&  UR                  S:  a  U=R                  S-  sl        O8US:X  a2  UR                  U R
                  S-
  :  a  U=R                  S-  sl        UR                  U:X  a  UR                  U:X  a  g U R                  U5      nU(       aC  UR                  nU R                  UR                  UR                  X5      n	UR                  U	SS	9  U R                  XR                  U5      S
9  g )N
grid_move_upr   r   downleftrightgame)buffer)selection_id)rR   removeprefixr   r   r-   r/   get_userr9   r:   speakupdate_player_menur^   )
r2   r3   	action_idrQ   	directionold_rowold_coluserr9   labels
             r   _action_grid_moveGridGameMixin._action_grid_move   s=   !!&)**<8	!::vzzaJJ!OJ& VZZ$..12D%DJJ!OJ& VZZ!^JJ!OJ'!fjj4>>A3E&EJJ!OJ:: VZZ7%:}}V$[[F''

FJJOEJJuVJ,5I5I&5QRr   c                r    U R                  U5      nU R                  XR                  UR                  5        g r\   )rR   r>   r   r   )r2   r3   rp   rQ   s       r   _action_grid_select!GridGameMixin._action_grid_select   s*    !!&)FJJ

;r   c                J    U R                   S:w  a  gUR                  (       a  gg NrA   rB   rC   rD   r2   r3   s     r   _is_grid_nav_enabled"GridGameMixin._is_grid_nav_enabled   s     ;;)#'%r   c                "    [         R                  $ r\   r
   rJ   r}   s     r   _is_grid_nav_hidden!GridGameMixin._is_grid_nav_hidden          r   c                    U R                   S:w  a  gUR                  (       a  gU R                  U5      nU R                  XR                  UR
                  5      $ r|   )rE   rF   rR   rG   r   r   rP   s      r   _is_grid_select_enabled%GridGameMixin._is_grid_select_enabled   sH    ;;)#'%!!&)((VZZHHr   c                "    [         R                  $ r\   r   r}   s     r   _is_grid_select_hidden$GridGameMixin._is_grid_select_hidden   r   r   c                    [        U5      nUc  gUu  pEUS:  d$  X@R                  :  d  US:  d  XPR                  :  a  gU R                  U5      nXEsUl        Ul        U R                  XU5        g)z9Handler for tapping / clicking a grid cell (web / touch).Nr   )parse_grid_cell_idr-   r/   rR   r   r   r>   )r2   r3   rp   coordsr   r   rQ   s          r   _action_grid_cellGridGameMixin._action_grid_cell   sk    #I.>7c^^+sQw#:O!!&)!$
FJF-r   N)rp   c               `    U(       d  g[        U5      nUc  gU R                  XS   US   5      $ )Nzaction-not-availabler   r   )r   rG   r2   r3   rp   r   s       r   _is_grid_cell_enabled#GridGameMixin._is_grid_cell_enabled   s7     )#I.>)((F1IFFr   c                   U(       d  [         R                  $ [        U5      nUc  [         R                  $ U R                  XS   US   5      $ rU   )r
   rJ   r   rL   r   s       r   _is_grid_cell_hidden"GridGameMixin._is_grid_cell_hidden   sG    $$$#I.>$$$''q	6!9EEr   c                    [        U5      nUc  U$ U R                  U5      nU(       a  UR                  OSnU R                  US   US   X5      $ )Nenr   r   )r   rm   r9   r:   )r2   r3   rp   r   rt   r9   s         r   _get_grid_cell_label"GridGameMixin._get_grid_cell_label   sO    #I.>}}V$ $$""6!9fQiHHr   c                J    SSK Jn  U R                  SSS/UR                  S9  g)aS  Register spatial navigation keybinds.  Call from
``setup_keybinds`` **after** ``super().setup_keybinds()``.

Arrow-key navigation is handled client-side when
``grid_enabled=True`` (desktop) or via the CSS grid (web).
These server-side keybinds are a fallback for the select action
and for any client that doesn't support native grid mode.
r   )KeybindStateenterzSelect cellgrid_select)stateN)server.core.ui.keybindsr   define_keybindACTIVE)r2   r   s     r   setup_grid_keybinds!GridGameMixin.setup_grid_keybinds   s.     	9O%%	 	 	
r   c                    / n[        U R                  5       HH  n[        U R                  5       H,  n[        X45      nUR	                  [        USSSSSSS95        M.     MJ     U$ )zBuild Action objects for every grid cell.

Returns a flat list in row-major order that the menu system can
render either linearly or as a grid (via ``grid_enabled`` /
``grid_width``).
 r   r   r   r   F)r+   ru   handler
is_enabled	is_hidden	get_labelshow_in_actions_menu)r,   r-   r/   r]   appendr   )r2   r3   actionsr   r   cell_ids         r   build_grid_actions GridGameMixin.build_grid_actions  si     !#(CT^^,&s0"  3#:"8"8-2
 - ) r   c                    / nS H&  nUR                  [        SU 3SU 3SSSSS95        M(     UR                  [        S	S
SSSSS95        U$ )z;Build hidden navigation + select actions (for keybind use).)re   rf   rg   rh   rd   zMove rv   r~   r   F)r+   ru   r   r   r   r   r   Selectry   r   r   )r   r   )r2   r   rq   s      r   build_grid_nav_actions$GridGameMixin.build_grid_nav_actions*  st     "8INN#I;/!)-/53).	 9 	 -42%*		
 r   c                D    U R                   S:w  a  0 $ SU R                  S.$ )zReturn extra kwargs for ``show_menu`` to enable grid mode.

Only returns grid params when the game is actively playing.
Lobby and end screens use normal linear menus.
rA   T)grid_enabled
grid_width)rE   r/   )r2   s    r   _build_grid_menu_kwargs%GridGameMixin._build_grid_menu_kwargsH  s)     ;;)#I ..
 	
r   c                    U[        U R                  5      :  a  U R                  U   O
[        U5      nU[        U R                  5      :  a  U R                  U   O
[        U5      nU U 3$ )z6Return the human-readable coordinate label, e.g. 'A1'.)lenr'   r.   r&   )r2   r   r   	col_label	row_labels        r   _grid_cell_coordinate#GridGameMixin._grid_cell_coordinateU  sg    14s4;O;O7P1PD((-VYZ]V^	14s4;O;O7P1PD((-VYZ]V^	YK((r   )r'   r$   r&   )returnNone)
r   r   r   r   r3   'Player'r9   r.   r   r.   )r3   r   r   r   r   r   r   r   )r3   r   r   r   r   r   r   
str | None)r3   r   r   r   r   r   r   r
   )r3   r   r   r   )rQ   r   r   r   )rQ   r   r   r.   )rQ   r   r   r   )r3   r   rp   r.   r   r   )r3   r   r   r   )r3   r   r   r
   )r3   r   rp   r   r   r   )r3   r   rp   r   r   r
   )r3   r   rp   r.   r   r.   )r3   r   r   list[Action])r   r   )r   dictr   r   r   r   r   r.   )r   r   r   r   r   r5   r:   r>   rG   rL   rR   rY   r^   ra   rv   ry   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r!   r!   &   s    4
V 
$"A4<S0<!I!
. <@GG.8G	G QU FI
,0<
)r   r!   c                    [          U  SU 3$ )z+Encode a (row, col) pair into an action ID._)GRID_CELL_PREFIXr%   s     r   r]   r]   a  s    uAcU++r   c                    U R                  [        5      (       d  gU R                  [        5      nUR                  SS5      n[	        U5      S:w  a  g [        US   5      [        US   5      4$ ! [         a     gf = f)z;Decode an action ID back to (row, col), or None if invalid.Nr   r   r   r   )
startswithr   rl   splitr   r   
ValueError)rp   restpartss      r   r   r   f  sx     011!!"23DJJsAE
5zQ58}c%(m++ s   A0 0
A=<A=Nr   )rp   r.   r   ztuple[int, int] | None)r   
__future__r   dataclassesr   r   typingr   r   r   r	   r
   messages.localizationr   
games.baser   server.core.users.baser   r   r   r!   r]   r   r   r   r   <module>r      s_    # (   2 2 0#+      s) s)v	,
r   