
    6jX                       d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
mZ ddlmZ ddlmZ ddlmZ ddlmZmZ dd	lmZmZ  ed
          Z ee                                          j        d         Zedz  dz  Zd\  ZZZ Z!dZ"dZ#dZ$dZ%d"dZ& ej'        d          d#d            Z(e
 G d d                      Z) G d d          Z* G d  d!          Z+dS )$u\  狼人杀多人房间:密钥建房 → 邀请码加入 → 多真人 + AI 补位 → 大厅→对局→是否继续 → 闲置自毁。

隔离:每个 Room 持有自己的一局(引擎 + 后台线程 + 多真人控制器),房间之间互不干扰。
身份:每个真人有 token(浏览器存),据此找房+座位。退出/掉线>60s → AI 补位;持 token 回来 poll → 夺回原座位。
密钥:GENESIS_WW_ROOM_KEY(.env,不入库)校验"能否建房";邀请码校验"能否加入某房"。
生命周期:LOBBY(房主集合)→ PLAYING → ENDED(房主决定是否继续)→ 新局 / CLOSED(解散)。
    )annotationsN)	dataclassfield)Path)Optional)
get_logger)RandomProviderWerewolfEngine)MultiHumanProviderWebHumanControllerzwerewolf.rooms   worldswerewolf)lobbyplayingendedclosedg      @g     V@g      N@namestrreturnc                \   t          j        |           }|r|S t          dz  }|                                ru|                    d                                          D ]L}|                                }|                    | dz             r|                    dd          d         c S MdS )uD   读环境变量,缺失则回退项目根 .env(密钥等不入库)。z.envzutf-8)encoding=    )	osgetenvROOTexists	read_text
splitlinesstrip
startswithsplit)r   venvflns       1/root/genesis-world/src/genesis/werewolf/rooms.py_envr)   $   s    
	$A &=D{{}} +..'.22==?? 	+ 	+BB}}TCZ(( +xxQ''****+2    i   )maxsizetextvoicebytesc                (    ddl m}  || |          S )un   合成并缓存(相同文本+音色不重复调火山);Room 预合成与 server /api/tts 共用同一缓存。r   )
synthesize)r-   )genesis.voice.volc_ttsr0   )r,   r-   r0   s      r(   tts_mp3r2   2   s+     211111:d%((((r*   c                  j    e Zd ZU ded<   ded<   dZded<   dZded<   d	Zd
ed<   dZded<   dZded<   dS )Playerr   tokennicknamer   pidNzOptional[int]seatr   intavatarg        float	last_seenFbool
is_creator)	__name__
__module____qualname____annotations__r7   r8   r:   r<   r>    r*   r(   r4   r4   9   sq         JJJMMMCMMMMDFOOOOIJr*   r4   c                     e Zd ZdZdddd=dZd>dZed?d            Zd@dZd@dZ	d@dZ
dAdBdZd?dZd@dZdCdZdDd!ZdEd#Zd>d$ZdFd&ZdEd'Zd>d(Zd>d)ZdGd+ZdHd-Zd>d.ZdId0ZdJd3ZedKd4            ZdJd5ZdLd>d7ZdMd9ZdNd;Z dOd<Z!d6S )PRoomul   一个隔离房间 = 一局(可连续多局)的容器:大厅 + 引擎 + 多真人控制器 + 生命周期。llm9_swh)brainboardroom_idr   invite_coderH   rI   r   Nonec                  || _         || _        d | _        || _        || _        t
          | _        i | _        t          j	                    | _
        t          j                    | _        d | _        d | _        i | _        t#                      | _        i | _        d | _        d | _        d| _        t          j                    | _        d | _        d | _        d| _        d | _        d S )NTr   )idrK   creator_tokenrH   board_idLOBBYphaseplayers	threadingRLocklocktime	monotoniclast_activityengineprovidercontrollerssetkickedseat_avatar_threadlivevoice_onEvent_spoken_spoken_wantwinnergame_id
_ai_models)selfrJ   rK   rH   rI   s        r(   __init__zRoom.__init__G   s    &,0

*,O%%	!^--046::<55+-37$(	 ((+/%)r*   c                6    t          j                    | _        d S N)rW   rX   rY   ri   s    r(   touchz
Room.touch`   s    !^--r*   r=   c                b    | j         t          k    pt          | j        o| j        j                  S )uO  本局是否已中止(房关 / 引擎被置 aborted)。语音节拍据此立即放行,不空等。
        ★必须存在:_wait_audio 引用 self.aborted,缺它会抛 AttributeError 被引擎 try/except 吞掉,
          导致每段发言的『等播完』钩子静默失效 → 引擎抢跑、文字全冒、语音错位、乱序。)rR   CLOSEDr=   rZ   abortedrm   s    r(   rq   zRoom.abortedc   s*    
 zV#PtDK,ODK<O'P'PPr*   r9   c                    dS )N	   rC   rm   s    r(   capacityzRoom.capacityj   s    qr*   c                l    | j         5  t          | j                  cd d d            S # 1 swxY w Y   d S rl   )rV   lenrS   rm   s    r(   human_countzRoom.human_countm   s{    Y 	% 	%t|$$	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	%s   )--c                    d | j                                         D             }t          d|                                 dz             D ]
}||vr|c S dS )Nc                    h | ]	}|j         
S rC   )r:   .0ps     r(   	<setcomp>z%Room._alloc_avatar.<locals>.<setcomp>s   s    888Q888r*   r   )rS   valuesrangert   )ri   usedas      r(   _alloc_avatarzRoom._alloc_avatarr   se    88$,"5"5"7"7888q$--//A-.. 	 	A}} qr*   Fr6   creatorr4   c           
        | j         5  t          j                    j        }|pd                                d d         p|rdndt          | j                  dz    }t          ||t          j                    j        d d         |                                 |t          j
                              }|| j        |<   |r| j        s|| _        |                                  |cd d d            S # 1 swxY w Y   d S )Nr      u   房主u   玩家r      )r5   r6   r7   r:   r>   r<   )rV   uuiduuid4hexr"   rv   rS   r4   r   rW   rX   rO   rn   )ri   r6   r   r5   nickr|   s         r(   
add_playerzRoom.add_playery   sE   Y 		 		JLL$EN))++CRC0o5nXXNnWZ[_[gWhWhklWlNnNnDUTtz||7G7KTXTfTfThTh")T^5E5EG G GA"#DL +t1 +%*"JJLLL		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		 		s   CC//C36C3c                    | j         5  | j        t          t          fv o)t	          | j                  |                                 k     cd d d            S # 1 swxY w Y   d S rl   )rV   rR   rQ   ENDEDrv   rS   rt   rm   s    r(   can_joinzRoom.can_join   s    Y 	X 	X:%/WC4E4E4W	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	Xs   ?AAAc                    t          j                    | j        5  t          fd| j                                        D                       cd d d            S # 1 swxY w Y   d S )Nc              3  B   K   | ]}|j         z
  t          k     d V  dS )r   N)r<   DISCONNECT_GRACEr{   r|   nows     r(   	<genexpr>z$Room.online_count.<locals>.<genexpr>   s5      bbQC!+<MQa;a;aq;a;a;a;abbr*   )rW   rX   rV   sumrS   r~   ri   r   s    @r(   online_countzRoom.online_count   s    nY 	c 	cbbbb$,"5"5"7"7bbbbb	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	cs   2AA"Alistc                    t          j                    | j        5  fdt          | j                                        d           D             cd d d            S # 1 swxY w Y   d S )Nc           	     v    g | ]5}|j         |j        |j        |j        |j        |j        z
  t          k     d 6S ))r8   r6   r7   r:   r>   online)r8   r6   r7   r:   r>   r<   r   r   s     r(   
<listcomp>z Room._roster.<locals>.<listcomp>   s_     f f f VAEUVU]#$<C!+<MQa;ac c f f fr*   c                "    | j         p| j        pdS )Nc   )r8   r:   )xs    r(   <lambda>zRoom._roster.<locals>.<lambda>   s    !&JbTUT\Jb`b r*   )key)rW   rX   rV   sortedrS   r~   r   s    @r(   _rosterzRoom._roster   s    nY 	f 	ff f f f#DL$7$7$9$9?c?cdddf f f	f 	f 	f 	f 	f 	f 	f 	f 	f 	f 	f 	f 	f 	f 	f 	f 	f 	fs   5AA"%A"engr
   rngrandom.Randomc                      j         dk    rQddlm} ddlm}m}  j         |ddd           _        |                    | fd	 |t                    
          S t          |          S )NrF   r   )build_chat_model)MindProviderload_personasg?i  <   )temperature
max_tokensrequest_timeoutc                     j         S rl   )rh   )kwri   s    r(   r   z Room._build_ai.<locals>.<lambda>   s	    do r*   )
make_modelpersonas)
rH   genesis.llm.lc_adapterr   genesis.werewolf.brainr   r   rh   for_gameWORLDr	   )ri   r   r   r   r   r   s   `     r(   	_build_aizRoom._build_ai   s    :??????JJJJJJJJ &"2"2ssdf"g"g"g((9U9U9U9U2?-2F2F ) H H Hc"""r*   by_tokenc           	       
 | j         5  || j        k    s| j        t          t          fvr	 ddd           dS t          j                    }t          t          | j	        t          |          |          }d|_        d|_        | j        |_        | j        |_        t#          |                     ||          |j                  }|j        |_        ||_        i | _        t1          |j        j                  }|                    |           t7          t1          | j                                                  |          D ]A\  }}||_        t?          t@                    }|| j        |<   |!                    ||           Bi | _"        tG                      
| j                                        D ]7}|j        .|j$        | j"        |j        <   
%                    |j$                   8
fdtM          dd	          D             }	|j        j        D ],}|| j"        vr!|	r|	'                    d
          n|| j"        |<   -||c| _(        | _        d| _)        d| _*        | xj+        dz  c_+        tX          | _        | -                                 t]          j/        | j0        dd| j1                   | _2        | j2        3                                 th          5                    d| j1        tm          | j                  d | j                                        D             | j7                   	 ddd           dS # 1 swxY w Y   dS )ul   房主开局(LOBBY 或 ENDED→新一局):发牌 + 在房真人随机入座 + AI 补满 + 起对局线程。NF)r   g      @g      $@timeoutc                    g | ]}|v|	S rC   rC   )r{   r   r   s     r(   r   z#Room.start_game.<locals>.<listcomp>   s    ===!q}}A}}}r*   r   
   r   Tzroom-)targetdaemonr   u:   房间 %s 开局:真人 %d 人入座 %s,AI 补满,brain=%sc                    g | ]	}|j         
S rC   )r8   rz   s     r(   r   z#Room.start_game.<locals>.<listcomp>   s    +R+R+RqAF+R+R+Rr*   )8rV   rO   rR   rQ   r   randomRandomr
   r   rP   r	   pacethink_pause_wait_audio
wait_audio_prewarmprewarm_ttsr   r   roles	has_humanis_humanr[   r\   r   stateshuffleziprS   r~   r8   r   TURN_TIMEOUTattachr_   r]   r:   addr   poprZ   rf   ra   rg   PLAYINGrn   rT   Thread_runrN   r`   startloggerinforv   rH   )ri   r   r   r   provseatsr|   stcpoolr   s             @r(   
start_gamezRoom.start_game   sg   Y (	 (	4---E5>1Q1Q(	 (	 (	 (	 (	 (	 (	 (	 -//C ~c7J7JPSTTTCCH"CO!-CN"mCO%dnnS#&>&>	JJD>CLCL!D))EKKT$,"5"5"7"788%@@ # #2&|<<<'( $B""""!D55D\((** L L6%/0xD$QV,!(9K9K9K====uQ||===Dio G GT---:>+F488A;;;BD$R(),d&DKDKDILLALL DJJJLLL$+49TPaX\X_PaPabbbDLL   KKTVZV]DL))+R+RDL<O<O<Q<Q+R+R+RTXT^` ` `Q(	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	 (	s   "L5K0L55L9<L9c                `   	 | j                                          n0# t          $ r# t                              d| j                   Y nw xY w| j        5  | j         r| j         j        j        nd | _        | j	        t          k    rt          | _	        |                                  d d d            d S # 1 swxY w Y   d S # | j        5  | j         r| j         j        j        nd | _        | j	        t          k    rt          | _	        |                                  d d d            w # 1 swxY w Y   w xY w)Nu   房间 %s 对局线程崩溃)rZ   run_game	Exceptionr   	exceptionrN   rV   r   rf   rR   r   r   rn   rm   s    r(   r   z	Room._run   s   		K  """" 	F 	F 	F;TWEEEEE	F   :>+Odk/664:((!&DJ

	                   :>+Odk/664:((!&DJ

	               s^    B= *A	B= A		B= AB00B47B4=D-AD!D-!D%%D-(D%)D-contc                    | j         5  || j        k    s| j        t          k    r	 d d d            dS 	 d d d            n# 1 swxY w Y   |r|                     |          S |                     |           dS )NFT)rV   rO   rR   r   r   close)ri   r   r   s      r(   continue_gamezRoom.continue_game   s    Y 	 	4---u1D1D	 	 	 	 	 	 	 	1D	 	 	 	 	 	 	 	 	 	 	 	 	 	 	  	-??8,,,

8ts   ?AAc                r   | j         5  || j        k    r	 ddd           dS t          | _        | j        rd| j        _        | j                                        D ]}|                                 | j	        
                                 |                                  	 ddd           dS # 1 swxY w Y   dS )ud   房主关房:置 CLOSED + 中止对局 + 唤醒所有等待中的真人(对局线程随即收尾)。NFT)rV   rO   rp   rR   rZ   rq   r\   r~   releaserd   r]   rn   )ri   r   r   s      r(   r   z
Room.close   s   Y 
	 
	4---
	 
	 
	 
	 
	 
	 
	 
	  DJ{ +&*#%,,..  		LJJLLL
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	s   B,A=B,,B03B0c                   | j         5  t          | _        | j        rd| j        _        | j                                        D ]}|                                 | j        	                                 ddd           dS # 1 swxY w Y   dS )uF   管理器清理调用:中止对局线程(daemon,随后自然退出)。TN)
rV   rp   rR   rZ   rq   r\   r~   r   rd   r]   ri   r   s     r(   force_destroyzRoom.force_destroy   s    Y 	 	DJ{ +&*#%,,..  		L	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   A)A>>BBc                L   | j         s| j        t          k    rt          | _        | j        rd| j        _        | j                                        D ]}|                                 | j        	                                 t                              d| j                   dS dS dS )up   一个真人都不剩了(全退/全被踢)→ 立即结束对局 + 关房(不空跑 AI;管理器随后销毁)。Tu5   房间 %s:无真实玩家,自动结束对局并关闭N)rS   rR   rp   rZ   rq   r\   r~   r   rd   r]   r   r   rN   r   s     r(   _maybe_autoclosezRoom._maybe_autoclose   s    | 	Z
f 4 4DJ{ +&*#%,,..  		LKKOQUQXYYYYY	Z 	Z 4 4r*   r5   c                *   | j         5  | j                            |d          }|r-|j        &| j        r| j                            |j                   |                                  |                                  ddd           dS # 1 swxY w Y   dS )uv   显式退出:让出座位 → AI 补位;移除玩家(再回来是新身份,夺不回原座位,符合"显式退出")。N)rV   rS   r   r8   r[   detachr   rn   )ri   r5   r|   s      r(   leavez
Room.leave  s    Y 	 	  --A -QV'DM'$$QV,,,!!###JJLLL	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   A3BBB
target_pidc                   | j         5  || j        k    r	 ddd           dS t          fd| j                                        D             d          }|r|j        | j        k    r	 ddd           dS | j                            |j        d           | j                            |j                   |j	        &| j
        r| j
                            |j	                   |                                  |                                  t                              d| j        |j                   	 ddd           dS # 1 swxY w Y   dS )uz   房主踢人:按公开 pid 定位(不能踢自己);移出房间 + 座位转 AI;该玩家下次 poll 收到 phase:kicked。NFc              3  2   K   | ]}|j         k    |V  d S rl   )r7   )r{   r|   r   s     r(   r   zRoom.kick.<locals>.<genexpr>  s/      SSqu
?R?R1?R?R?R?RSSr*   u   房间 %s:房主踢出 %s(%s)T)rV   rO   nextrS   r~   r5   r   r^   r   r8   r[   r   r   rn   r   r   rN   r6   )ri   r   r   r   s     ` r(   kickz	Room.kick  s   Y 	 	4---	 	 	 	 	 	 	 	 SSSSdl&9&9&;&;SSSUYZZF V\T-???	 	 	 	 	 	 	 	 LV\4000KOOFL))){&4=&$$V[111!!###JJLLLKK7&/S]^^^	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   D?AD?6B<D??EEc           	        | j         t          k    s| j        sdS t          j                    }| j        5  | j                                        D ]}|j        | j        	                    |j                  rd||j
        z
  t          k    rQ| j                            |j                   t                              d| j        |j        |j        t                     	 ddd           dS # 1 swxY w Y   dS )uj   掉线>60s 的真人座位 → AI 补位(持 token 回来 poll 会自动夺回)。管理器周期调用。Nu-   房间 %s:%d号(%s)掉线>%.0fs → AI 补位)rR   r   r[   rW   rX   rV   rS   r~   r8   r   r<   r   r   r   r   rN   r6   )ri   r   r|   s      r(   check_disconnectszRoom.check_disconnects!  s@   :   FnY 	@ 	@\((** @ @F&4=+B+B16+J+J& 1;.2BBBM((000KK OQUQXZ[Z`bcbln~	@	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@s   B'C))C-0C-r|   c                   | j         t          k    r|j        | j        r| j        r| j                            |j                  s| j        j                            |j                  r| j        	                    |j                  pt          t                    }|| j        |j        <   | j                            |j        |           t                              d| j        |j        |j                   dS dS dS dS dS dS dS )u`   玩家 poll 时:若其座位正被 AI 接管且本人还活着 → 夺回(重新挂控制器)。Nr   u*   房间 %s:%d号(%s)回归 → 夺回座位)rR   r   r8   r[   rZ   r   r   is_aliver\   getr   r   r   r   r   rN   r6   )ri   r|   r   s      r(   _reclaim_if_neededzRoom._reclaim_if_needed-  s    J'!!af&8T]&8t{&8//77 '9<@K<M<V<VWXW]<^<^ '9 $$QV,,X0B<0X0X0XA'(DQV$M  +++KKDdgqvWXWabbbbb "!&8&8&8&8&8&8&8&8&8&8r*   r8   r,   c           	        | j         r| j        t          k    s| j        rdS |                     ||          }| j        5  || _        | j                                         ddd           n# 1 swxY w Y   t          dt          dt          |pd          dz                      }t          j                    |z   dz   }t          j                    |k     rM| j        t          k    s| j        rdS | j                            d          rdS t          j                    |k     M| j        5  d| _        ddd           dS # 1 swxY w Y   dS )	u   阻塞到『这一段发言』在前端真正播完(keyed,避免上一段的 spoken 误满足本段→末位被切/轮次重叠 bug6/bug7)。
        无在场真人/无人回报则超时兜底。关语音/房关则不等。Ng      D@g      @r   g      @g      (@g      ?r   )rb   rR   rp   rq   _spoken_keyrV   re   rd   clearminmaxrv   rW   rX   wait)ri   r8   r,   r   estdeadlines         r(   r   zRoom._wait_audio7  s    } 	
f 4 4 4FtT**Y 	! 	! #DL   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! $CTZR3!67788>##c)D0n))zV##t|#|   -- 	 n))
 Y 	% 	% $D	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	%s#   !A++A/2A/*D??EEc                $    |  d|pdd d          S )N|r   0   rC   r8   r,   s     r(   r   zRoom._spoken_keyJ  s#    ,,$*"crc*,,,r*   c                    | j         r|sd S ddlm} 	 t          |                                d d          ||                     d S # t
          $ r Y d S w xY w)Nr   )voice_for_seati  )rb   r1   r	  r2   r"   r   )ri   r8   r,   r	  s       r(   r   zRoom._prewarmN  s    } 	D 	F999999	DJJLL#&t(<(<===== 	 	 	DD	s   3A 
AANc                r   | j         5  | j        }|	 ddd           dS |]|                     t          |                              d                                          rt          |          n||          |k    r d| _        | j                                         ddd           dS # 1 swxY w Y   dS )u   前端报『某段发言播完』。只有匹配当前正在等的那段(_spoken_want)才唤醒引擎——
        防止前一段的播完信号误满足后一段的等待(致末位被切/轮次重叠)。无 seat(旧前端)则兜底直接唤醒。N-)	rV   re   r   r   lstripisdigitr9   rd   r]   )ri   r8   r,   wants       r(   mark_spokenzRoom.mark_spokenW  s    Y 	# 	#$D|	# 	# 	# 	# 	# 	# 	# 	# |t//SYY=M=Mc=R=R=Z=Z=\=\0fD			bfhlmmquuu$(!  """	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	#s   B,A?B,,B03B0partialc                *    ||d d         d| _         d S )Ni  r  )ra   )ri   r8   r  s      r(   sinkz	Room.sinkb  s    !74C4=99			r*   dictc                   | j         5  || j        v rddicd d d            S | j                            |          }|rAt	          j                    |_        |                     |           |                                  |r|j	        nd }| j
        r*| j        t          k    r| j
                            |          nd }d }|rt|j	        m| j        t          k    r]| j        rV| j                            |j	                  r7| j                            |j	                  }|r|                                nd }| j        | j        | j        | j        |r||j        |j        dnd |                                 t3          | j                  t7          | j                  |                                 |||rd n| j        | j        t?          | j         o| j         !                                          dcd d d            S # 1 swxY w Y   d S )NrR   r^   )r8   r6   r>   )rR   roominviterg   yourS   r_   rw   rt   frameawaitingra   rf   running)"rV   r^   rS   r   rW   rX   r<   r   rn   r8   rZ   rR   rQ   r  r   r[   r   r\   pendingrN   rK   rg   r6   r>   r   r  r_   rv   rt   ra   rf   r=   r`   r   )ri   r5   r|   r8   r  r  r   s          r(   	state_forzRoom.state_forf  sK   Y 	 	##*	 	 	 	 	 	 	 	   ''A "n..''***

(166DD04^
e@S@SDK%%d+++Z^EH 6QV'DJ',A,Adm,AX\XeXoXopqpvXwXw,A$((00*+5199;;;TW@P]a]i^_i1:Q\ZZZei<<>>#D$455"4<00dmmoo$ (7di+ H1F1F1H1HII 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   G9G
G99G= G=c                :   | j         5  | j                            |          }|r|j        	 d d d            dS | j                            |j                  }|                                  d d d            n# 1 swxY w Y   |r|                    |          ndS )NF)rV   rS   r   r8   r\   rn   submit)ri   r5   answerr|   r   s        r(   r  zRoom.submit  s    Y 	 	  ''A 	 	 	 	 	 	 	 	  $$QV,,AJJLLL	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 $%/qxx%/s   %A93A99A= A=)
rJ   r   rK   r   rH   r   rI   r   r   rL   r   rL   )r   r=   )r   r9   )F)r6   r   r   r=   r   r4   )r   r   )r   r
   r   r   )r   r   r   r=   )r   r   r   r=   r   r=   )r5   r   r   rL   )r   r   r   r   r   r=   )r|   r4   r   rL   )r8   r9   r,   r   r   rL   r   r   )NN)r8   r9   r  r   r   rL   )r5   r   r   r  )r5   r   r   r=   )"r?   r@   rA   __doc__rj   rn   propertyrq   rt   rw   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   staticmethodr   r   r  r  r  r  rC   r*   r(   rE   rE   D   s       vvGL[b      2. . . . Q Q Q XQ   % % % %
   
 
 
 
 
X X X Xc c c c
f f f f
# 
# 
# 
#* * * *X
 
 
 
         	Z 	Z 	Z 	Z      "
@ 
@ 
@ 
@c c c c% % % %& - - - \-   	# 	# 	# 	# 	#: : : :   80 0 0 0 0 0r*   rE   c                  P    e Zd ZdZdddZdddZdddZddZddZddZ	d dZ
dS )!RoomManagerub   全局房间表:建房(密钥)/加入(邀请码)/按 token 找房 + 后台清理。线程安全。rF   rH   r   r   rL   c                n    || _         i | _        i | _        i | _        t	          j                    | _        d S rl   )rH   rooms	by_inviter   rT   rU   rV   )ri   rH   s     r(   rj   zRoomManager.__init__  s1    
&(
)+(*O%%			r*   r   r   r6   Optional[dict]c                   |r|t          d          k    rd S | j        5  t          j                    j        d d         }|                                 }t          ||| j                  }|                    |d          }|| j	        |<   || j
        |<   || j        |j        <   t                              d||           |||j        dcd d d            S # 1 swxY w Y   d S )NGENESIS_WW_ROOM_KEYr   )rH   T)r   u   建房 %s 邀请码 %s)rJ   rK   r5   )r)   rV   r   r   r   	_gen_coderE   rH   r   r(  r)  r   r5   r   r   )ri   r   r6   ridcoder  r   s          r(   createzRoomManager.create  s>    	cT"788884Y 		Q 		Q*,,"2A2&C>>##DT444Dooho==G"DJsO#&DN4 +.DM'-(KK0#t<<<"4'-PP		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Q 		Qs   B,CCCr/  r  c                   | j         5  | j                            |pd                                                                          }|r| j                            |          nd }|sddicd d d            S |                                sddicd d d            S |                    |          }|| j        |j	        <   ||j	        |j
        dcd d d            S # 1 swxY w Y   d S )Nr   erroru!   房间不存在或邀请码无效u   房间已满或对局进行中)rJ   r5   rK   )rV   r)  r   r"   upperr(  r   r   r   r5   rK   )ri   r/  r6   r.  r  r|   s         r(   joinzRoomManager.join  s   Y 		W 		W.$$djb%7%7%9%9%?%?%A%ABBC*-74:>>#&&&4D F!DE			W 		W 		W 		W 		W 		W 		W 		W
 ==?? C!AB		W 		W 		W 		W 		W 		W 		W 		W ))A%(DM!'""QWTEUVV		W 		W 		W 		W 		W 		W 		W 		W 		W 		W 		W 		W 		W 		W 		W 		W 		W 		Ws   A$C9C3CC!$C!r5   Optional[Room]c                    | j         5  | j                            |          }|r| j                            |          nd cd d d            S # 1 swxY w Y   d S rl   )rV   r   r   r(  )ri   r5   r.  s      r(   room_ofzRoomManager.room_of  s    Y 	8 	8-##E**C*-74:>>#&&&4	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8s   8AAAc                ~    d	 d                     fdt          d          D                       }|| j        vr|S :)N ABCDEFGHJKLMNPQRSTUVWXYZ23456789Tr   c              3  @   K   | ]}t          j                  V  d S rl   )r   choice)r{   _alphas     r(   r   z(RoomManager._gen_code.<locals>.<genexpr>  s-      BBA6=//BBBBBBr*      )r4  r   r)  )ri   r/  r=  s     @r(   r-  zRoomManager._gen_code  sP    2	77BBBBqBBBBBD4>))	r*   c                P   t          j                    }| j        5  g }| j                                        D ]\  }}||j        z
  }|j        t          k    s|                                dk    r|	                    |           M|
                                dk    r!|t          k    r|	                    |           |t          k    r|	                    |           |                                 |D ]}|                     |           	 ddd           dS # 1 swxY w Y   dS )uv   周期清理:已关/无真人 → 销毁;真人全离线超 EMPTY_TTL → 结束对局+关房;否则查掉线补位。r   N)rW   rX   rV   r(  itemsrY   rR   rp   rw   appendr   	EMPTY_TTLIDLE_TTLr   _destroy)ri   r   deadr.  r  idles         r(   cleanupzRoomManager.cleanup  sx   nY 	# 	#D!Z--// 	- 	-	TT//:''4+;+;+=+=+B+BKK$$$$&&((A--$2B2BKK$$$$H__KK$$$$**,,,, # #c""""#	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	#s   C2DD"Dr.  c                   | j                             d           }|sd S |                                 | j                            |j        d            fdt          | j                                                  D             D ]}| j                            |d            t          	                    d           d S )Nc                &    g | ]\  }}|k    |S rC   rC   )r{   trr.  s      r(   r   z(RoomManager._destroy.<locals>.<listcomp>  s"    HHH1qCxx!xxxr*   u   房间 %s 已销毁)
r(  r   r   r)  rK   r   r   r@  r   r   )ri   r.  r  rJ  s    `  r(   rD  zRoomManager._destroy  s    z~~c4(( 	F4+T222HHHHT]%8%8%:%: ; ;HHH 	' 	'AMa&&&&)3/////r*   N)rF   )rH   r   r   rL   )r   )r   r   r6   r   r   r*  )r/  r   r6   r   r   r  )r5   r   r   r5  r!  r   )r.  r   r   rL   )r?   r@   rA   r"  rj   r0  r4  r7  r-  rG  rD  rC   r*   r(   r&  r&    s        ll& & & & &Q Q Q Q Q
W 
W 
W 
W 
W8 8 8 8
   # # # #$0 0 0 0 0 0r*   r&  )r   r   r   r   )r,   r   r-   r   r   r.   ),r"  
__future__r   	functoolsr   r   rT   rW   r   dataclassesr   r   pathlibr   typingr   genesis.obs.logging_setupr   genesis.werewolf.enginer	   r
   genesis.werewolf.humanr   r   r   __file__resolveparentsr   r   rQ   r   r   rp   rC  rB  r   r   r)   	lru_cacher2   r4   rE   r&  rC   r*   r(   <module>rX     s%    # " " " " "     				        ( ( ( ( ( ( ( (             0 0 0 0 0 0 B B B B B B B B I I I I I I I I	$	%	%tH~~'*x*$ E wv	     T""") ) ) #")        E0 E0 E0 E0 E0 E0 E0 E0P
J0 J0 J0 J0 J0 J0 J0 J0 J0 J0r*   