
    IiP+                        S r SSKJrJr  SSKJrJr  SSKrSSKJ	r	  SSK
Jr  \ " S S\5      5       rS%S\S\S\\   4S jjrS&S\S\4S jjrS
S.S\\   S\S\\\4   4S jjrS
S.S\\   \\\4   -  S\S\\\4   4S jjr S'S
S.S\\   \\\4   -  S\S\S-  S\S\4
S jjjrS
S.S\\   \\\4   -  S\S\S\4S jjrS	SSS
S.S\\   \\\4   -  S\S\S\S-  S\S\S\4S  jjrSS
S!.S\\   \\\4   -  S"\S\S\4S# jjr/ S$Qrg)(z$Dice utilities for dice-based games.    )IterableMapping)	dataclassfieldN)Sequence)DataClassJSONMixinc                   F   \ rS rSr% SrSr\\S'   Sr\\S'   \	" \
S9r\
\   \S'   \	" \
S9r\
\   \S	'   \	" \
S9r\
\   \S
'   S r\S\4S j5       r\S\4S j5       r\S\4S j5       r\S\4S j5       rS+S jrS,S\S\S\
\   4S jjrS\S\4S jrS\S\4S jrS\S\4S jrS\S\4S jrS\S\S-  4S jrS\S\S-  4S jrS\S\4S jrS-S\S\S\4S jjrS.S\S \S\4S! jjr S/S \S\4S" jjr!S#\S\4S$ jr"S0S%\S-  S\4S& jjr#S\$4S' jr%\&S(\$SS 4S) j5       r'S*r(g)1DiceSet   a  Set of dice with keep/lock mechanics.

Supports rolling any number of dice, keeping or locking dice across rolls,
and toggling keep status on unlocked dice.

Typical flow:
    1) roll() rolls all dice.
    2) keep()/unkeep() marks dice for preservation.
    3) roll() again locks kept dice and rerolls others.
    4) reset() clears state for the next turn.

Attributes:
    num_dice: Number of dice in the set.
    sides: Sides per die.
    values: Current die values.
    kept: Indices marked to keep.
    locked: Indices locked until reset.
   num_dice   sides)default_factoryvalueskeptlockedc                 6    U R                   (       d  / U l         gg)z"Initialize empty values if needed.N)r   selfs    5c:\Users\dbart\PlayPalace11\server\game_utils\dice.py__post_init__DiceSet.__post_init__&   s    {{DK     returnc                 F    [        U R                  5      U R                  :H  $ )zCheck if dice have been rolled.)lenr   r   r   s    r   
has_rolledDiceSet.has_rolled+   s     4;;4==00r   c                    ^  T R                   (       d  T R                  $ [        U 4S j[        T R                  5       5       5      $ )z"Count of dice that are not locked.c              3   J   >#    U  H  oTR                   ;  d  M  S v   M     g7f   Nr   .0ir   s     r   	<genexpr>)DiceSet.unlocked_count.<locals>.<genexpr>5   s     K2t{{6J112   #	#)r   r   sumranger   s   `r   unlocked_countDiceSet.unlocked_count0   s0     == KeDMM2KKKr   c                 B   ^  [        U 4S jT R                   5       5      $ )zECount of kept dice that are not locked (will be locked on next roll).c              3   J   >#    U  H  oTR                   ;  d  M  S v   M     g7fr"   r$   r%   s     r   r(   .DiceSet.kept_unlocked_count.<locals>.<genexpr>:   s     @iDKK+?11ir*   )r+   r   r   s   `r   kept_unlocked_countDiceSet.kept_unlocked_count7   s     @dii@@@r   c                 x   ^  T R                   (       d  g[        U 4S j[        T R                  5       5       5      $ )z,Check if all dice are either kept or locked.Fc              3   l   >#    U  H)  oTR                   ;   =(       d    UTR                  ;   v   M+     g 7fN)r   r   r%   s     r   r(   &DiceSet.all_decided.<locals>.<genexpr>A   s*     T?S!		>5Q$++%55?Ss   14)r   allr,   r   r   s   `r   all_decidedDiceSet.all_decided<   s)     TuT]]?STTTr   Nc                 .    / U l         / U l        / U l        g)z$Reset all dice state for a new turn.N)r   r   r   r   s    r   resetDiceSet.resetC   s    	r   	lock_kept
clear_keptc                    U R                   (       dV  [        U R                  5       Vs/ s H$  n[        R                  " SU R
                  5      PM&     snU l        U R                  $ U(       a?  U R                   H/  nX@R                  ;  d  M  U R                  R                  U5        M1     [        U R                  5       HS  nX@R                  ;  d  M  X@R                  ;  d  M%  [        R                  " SU R
                  5      U R                  U'   MU     U(       a  [        U R                  5      U l        U R                  $ s  snf )a  
Roll the dice.

If dice haven't been rolled yet, rolls all dice.
Otherwise, respects kept/locked dice and rerolls the rest.

Args:
    lock_kept: If True, kept dice become locked before rolling.
              Set False for games where you can unkeep after rolling.
    clear_kept: If True, clears kept list after rolling.
               Set False to preserve kept state.

Returns:
    List of all dice values after rolling.
r#   )r   r,   r   randomrandintr   r   r   r   appendlist)r   r>   r?   _r'   s        r   rollDiceSet.rollI   s      BGBVWBVQ6>>!TZZ8BVWDK" {{ A+**1- #
 4==)KK'AYY,>%+^^Atzz%BDKKN *  -	{{# Xs   +Eindexc                     XR                   ;   $ )z"Check if a die at index is locked.r$   r   rH   s     r   	is_lockedDiceSet.is_lockedn   s    ##r   c                     XR                   ;   $ )z Check if a die at index is kept.)r   rJ   s     r   is_keptDiceSet.is_keptr   s    		!!r   c                 x    XR                   ;   a  gXR                  ;  a  U R                  R                  U5        g)zO
Mark a die to keep.

Returns:
    True if successful, False if die is locked.
FT)r   r   rC   rJ   s     r   keepDiceSet.keepv   s0     KK		!IIU#r   c                 x    XR                   ;   a  gXR                  ;   a  U R                  R                  U5        g)zY
Unmark a die from being kept.

Returns:
    True if successful, False if die is locked.
FT)r   r   removerJ   s     r   unkeepDiceSet.unkeep   s0     KKIIIIU#r   c                     XR                   ;   a  gXR                  ;   a  U R                  R                  U5        gU R                  R                  U5        g)zc
Toggle keep status of a die.

Returns:
    True if now kept, False if now unkept, None if locked.
NFT)r   r   rT   rC   rJ   s     r   toggle_keepDiceSet.toggle_keep   sE     KKIIIIU#IIU#r   c                 v    U R                   (       a  U[        U R                  5      :  a  gU R                  U   $ )z Get the value of a specific die.N)r   r   r   rJ   s     r   	get_valueDiceSet.get_value   s+    %3t{{+;";{{5!!r   c                 D    XR                   ;   a  gXR                  ;   a  gg)z5Get status string for a die: 'locked', 'kept', or ''.r   r    )r   r   rJ   s     r   
get_statusDiceSet.get_status   s    KKiir   show_statusc                     U R                   (       d  g[        U R                  U   5      nU(       a   U R                  U5      nU(       a  U SU S3$ U$ )z Format a single die for display.-z ())r   strr   r_   )r   rH   ra   valuestatuss        r   
format_dieDiceSet.format_die   sK    DKK&'__U+F6(!,,r   	separatorc                     U R                   (       d  g[        U R                  5       Vs/ s H  o0R                  X15      PM     nnUR	                  U5      $ s  snf )zFormat all dice for display.rc   )r   r,   r   rh   join)r   ra   rj   r'   partss        r   
format_allDiceSet.format_all   sG    :?:NO:NQ0:NO~~e$$ Ps   Ac                 j    U R                   (       d  gUR                  S U R                   5       5      $ )z+Format just the dice values without status.rc   c              3   8   #    U  H  n[        U5      v   M     g 7fr6   )re   r&   vs     r   r(   -DiceSet.format_values_only.<locals>.<genexpr>   s     :kc!ffks   )r   rl   r   )r   rj   s     r   format_values_onlyDiceSet.format_values_only   s%    ~~:dkk:::r   rf   c                 f   ^ U R                   (       d  g[        U4S jU R                   5       5      $ )z*Count how many dice show a specific value.r   c              3   6   >#    U  H  oT:X  d  M
  S v   M     g7fr"    )r&   rs   rf   s     r   r(   &DiceSet.count_value.<locals>.<genexpr>   s     8k%Z11k   		)r   r+   r   )r   rf   s    `r   count_valueDiceSet.count_value   s"    8dkk888r   exclude_valuec                 p    U R                   (       d  gSnU R                   H  nUb  X1:X  a  M  X#-  nM     U$ )zb
Sum all dice values.

Args:
    exclude_value: If set, dice showing this value are counted as 0.
r   )r   r   )r   r~   totalrs   s       r   
sum_valuesDiceSet.sum_values   s=     A(Q-?JE  r   c                 v    U R                   U R                  U R                  U R                  U R                  S.$ )zSerialize to dictionary.r   r   r   r   r   r   r   s    r   to_dictDiceSet.to_dict   s1     ZZkkIIkk
 	
r   datac           
          U " UR                  SS5      UR                  SS5      UR                  S/ 5      UR                  S/ 5      UR                  S/ 5      S9$ )	zDeserialize from dictionary.r   r   r   r   r   r   r   r   )get)clsr   s     r   	from_dictDiceSet.from_dict   sW     XXj!,((7A&88Hb)&"%88Hb)
 	
r   )r   r   r   )r   N)TT)T)T, )r   r6   ))__name__
__module____qualname____firstlineno____doc__r   int__annotations__r   r   rD   r   r   r   r   propertyboolr   r-   r2   r9   r<   rF   rK   rN   rQ   rU   rX   r[   re   r_   rh   rn   ru   r|   r   dictr   classmethodr   __static_attributes__ry   r   r   r
   r
      s   & HcE3Nd3FDI3D1D$s)1d3FDI3
 1D 1 1 L L L AS A A UT U U#d #t #tCy #J$s $t $"S "T "# $ C D    "s "sTz "  
 
$ 
# 
%d %c %S %;C ;3 ;9 9 9d
 c  
 
 
T 
i 
 
r   r
   r#   r   r   r   r   c                 n    [        U 5       Vs/ s H  n[        R                  " SU5      PM     sn$ s  snf )z+Roll multiple dice and return their values.r#   )r,   rA   rB   )r   r   rE   s      r   	roll_dicer      s)    .3Ho>oFNN1e$o>>>s   !2c                 0    [         R                  " SU 5      $ )z'Roll a single die and return its value.r#   )rA   rB   r   s    r   roll_dier      s    >>!U##r   r   dicec                    U(       a   [        SUS-   5       Vs0 s H  o"S_M     snO0 nU  H-  nXC;   a  X4==   S-  ss'   M  UR                  US5      S-   X4'   M/     U$ s  snf )z$Count occurrences of each die value.r#   r   )r,   r   )r   r   r'   countsrf   s        r   
count_dicer      sg    49E!UQY/0/qd/0rF?MQM"JJua014FM	 
 M 1s   Adice_or_countsc                V   [        U [        5      (       a_  U(       a0  [        SUS-   5       Vs0 s H  o"U R                  US5      _M     snO0 nU R	                  5        H  u  pEXC;  d  M  XSU'   M     U$ [        U [
        5      (       a	  [        XS9$ [        [        U 5      US9$ s  snf )Nr#   r   r   )
isinstancer   r,   r   itemsr   r   rD   )r   r   r'   basekeyrf   s         r   _coerce_countsr     s     .'**MRU1eai5HI5H>%%a++5HIXZ(..0JC!S	 1 .(++.66d>*%88 Js   B&countrf   c                   ^ [        XS9nUb  UR                  US5      T:  $ [        U4S jUR                  5        5       5      $ )zGReturn True if there are ``count`` or more of ``value`` (or any value).r   r   c              3   ,   >#    U  H	  oT:  v   M     g 7fr6   ry   )r&   rs   r   s     r   r(   "has_n_of_a_kind.<locals>.<genexpr>!  s     3?aEz?s   )r   r   anyr   )r   r   rf   r   r   s    `   r   has_n_of_a_kindr     sB     N8Fzz%#u,,36==?333r   exactc                \   ^ [        XS9n[        U4S jUR                  5        5       5      $ )z>Count how many distinct values appear exactly ``exact`` times.r   c              3   6   >#    U  H  oT:X  d  M
  S v   M     g7fr"   ry   )r&   rs   r   s     r   r(   &count_exact_matches.<locals>.<genexpr>,  s     8/Q%Zqq/r{   )r   r+   r   )r   r   r   r   s    `  r   count_exact_matchesr   $  s&     N8F8&--/888r   F)	min_value	max_valuerequire_uniquer   lengthr   r   r   c                    [        XS9nU(       d  [        UR                  5       SS9nSn[        X#S-   5       H8  nUR	                  US5      n	U	S:  a  U(       a  U	S:X  a  US-  nXq:  a    gM6  SnM:     g)z?Return True if there is a run of ``length`` consecutive values.r   r   )defaultr#   TF)r   maxkeysr,   r   )
r   r   r   r   r   r   r   runrf   r   s
             r   has_consecutive_runr   /  s|     N8Fq1	
Cya-0

5!$19n
1HC}  C 1 r   )allow_five_kindr   r   c                   [        XS9n[        S UR                  5        5       5      n[        S UR                  5        5       5      nU(       a  U(       a  gU(       a&  [        S UR                  5        5       5      (       a  gg)zKReturn True if counts contain a 3-of-a-kind and a pair (optionally 5-kind).r   c              3   *   #    U  H	  oS :H  v   M     g7f)   Nry   rr   s     r   r(   !has_full_house.<locals>.<genexpr>P  s     4OqFO   c              3   *   #    U  H	  oS :H  v   M     g7f)   Nry   rr   s     r   r(   r   Q  s     2/Qq&/r   Tc              3   *   #    U  H	  oS :H  v   M     g7f)r   Nry   rr   s     r   r(   r   T  s     ?!Avr   F)r   r   r   )r   r   r   r   	has_threehas_twos         r   has_full_houser   H  sa     N8F4FMMO44I2&--/22GW3?v}}???r   )r
   r   r   r   r   r   r   r   )r#   r   )r   r6   )r   collections.abcr   r   dataclassesr   r   rA   typingr   mashumaro.mixins.jsonr   r
   r   rD   r   r   r   r   r   r   r   r   r   r   __all__ry   r   r   <module>r      s    * - (   4 d
  d
 d
N? ? ?DI ?
$C $ $
 56 Xc] c $sCx.  HI9SMGCH$559AD9	#s(^9" 4
 4SMGCH$5544 :4
 4 
4$ 	9SMGCH$5599 	9
 	9   SMGCH$55 	
 Tz   
8 "	SMGCH$55  	
 
"	r   