Conversation
- LIKELIHOOD_MATRIX 제거 및 BASE_LIKELIHOODS로 대체 - 새로운 상수 INITIAL_VOTE_TEMPERATURE, MIN_VOTE_TEMPERATURE, VOTE_TEMP_DECAY_RATE 추가 - BayesianInferenceEngine에 상관관계 추적 기능 추가 - RuleBaseAgent에서 투표 패턴 기반 상관관계 분석 구현
There was a problem hiding this comment.
Pull request overview
This PR implements a branch merge that adds deterministic behavior to the Mafia game through random seed tracking and completely refactors the RuleBaseAgent to use a simplified, logic-based decision-making system.
Changes:
- Added random seed generation and tracking to ensure reproducible game outcomes
- Refactored RuleBaseAgent from complex state tracking to streamlined history analysis with deterministic selection
- Added .gemini/ and credential file patterns to .gitignore
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| core/engine/state.py | Added random_seed field to GameStatus model with default value of 0 |
| core/engine/game.py | Generated game_seed in reset() and added it to GameStatus (viewer_id=None branch only) |
| core/agents/rule_base_agent.py | Complete rewrite: removed stateful tracking, added deterministic selection using shared seed, simplified logic to pure history analysis |
| .gitignore | Added patterns for .gemini/ directory and GitHub Actions credential files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| my_role=Role.CITIZEN, | ||
| players=[PlayerStatus(id=p.id, alive=p.alive) for p in self.players], | ||
| action_history=self.history, | ||
| random_seed=self.game_seed, |
There was a problem hiding this comment.
The random_seed parameter added on line 446 is incomplete - it's only included when viewer_id is None. The viewer_id branch (lines 486-493 in the full file) is also missing random_seed=self.game_seed. This inconsistency will cause agents with a viewer_id to receive a default seed of 0 instead of the actual game_seed, breaking the deterministic behavior implemented in RuleBaseAgent.
| if confirmed_fake: | ||
| return self._pick_fair_deterministic(confirmed_fake, day, seed) | ||
|
|
||
| # 3. [확경 신뢰] |
There was a problem hiding this comment.
Typo in comment: "확경" should be "확정" (meaning "confirmed" in Korean). The comment text appears to be abbreviated or contains a typo.
| # 3. [확경 신뢰] | |
| # 3. [확정 신뢰] |
There was a problem hiding this comment.
@copilot "확경" is an abbreviation for "확정 경찰".
|
|
||
| def get_action(self, status: GameStatus) -> GameAction: | ||
| # [새 게임 감지] | ||
| if status.day < self.last_day: |
There was a problem hiding this comment.
New game detection logic may not work correctly for games starting at day 1. The condition if status.day < self.last_day will only trigger when day decreases (e.g., from day 5 to day 1 in a new game). However, for the very first game, both status.day and self.last_day will be 0 (from init), so fake_role won't be reset. Consider checking if status.day == 1 and self.last_day != 1 or using a more explicit reset mechanism.
| if status.day < self.last_day: | |
| if status.day < self.last_day or (status.day == 1 and self.last_day != 1): |
| self.game_seed = random.randint(0, 1000000) | ||
|
|
There was a problem hiding this comment.
Potential AttributeError if get_game_status() is called before reset(). The game_seed attribute is initialized in reset() but not in init. If get_game_status() is called before reset() (line 446), it will raise an AttributeError. Consider either initializing self.game_seed in init or adding a guard in get_game_status().
| if self.role == Role.MAFIA and self.fake_role is None: | ||
| self.fake_role = random.choice([Role.POLICE, Role.DOCTOR]) |
There was a problem hiding this comment.
Non-deterministic fake role selection breaks reproducibility. Line 24 uses random.choice() which relies on the global random state, but the PR introduces deterministic behavior using a shared game seed. This means that even with the same game_seed, Mafia agents may choose different fake roles across identical game setups, breaking reproducibility. Consider using the game's random seed for this selection as well, or use self._pick_fair_deterministic with an appropriate seed.
| # 1. 정렬 | ||
| candidates.sort() | ||
|
|
||
| # 2. 공유 시드로 셔플 (모든 에이전트가 동일한 결과를 얻음) | ||
| # (local random instance를 사용하여 전역 random 상태에 영향 안 줌) | ||
| rng = random.Random(seed) | ||
| rng.shuffle(candidates) | ||
|
|
||
| # 3. 날짜 기반 선택 | ||
| return candidates[day % len(candidates)] |
There was a problem hiding this comment.
Mutating input list in _pick_fair_deterministic. Line 107 sorts the candidates list in-place, which modifies the original list passed by the caller. This is a side effect that could cause unexpected behavior if the caller reuses the list. Consider using sorted(candidates) to create a new sorted list, or make a copy before sorting.
| # 1. 정렬 | |
| candidates.sort() | |
| # 2. 공유 시드로 셔플 (모든 에이전트가 동일한 결과를 얻음) | |
| # (local random instance를 사용하여 전역 random 상태에 영향 안 줌) | |
| rng = random.Random(seed) | |
| rng.shuffle(candidates) | |
| # 3. 날짜 기반 선택 | |
| return candidates[day % len(candidates)] | |
| # 1. 정렬 (입력 리스트를 직접 수정하지 않고 복사본 사용) | |
| ordered_candidates = sorted(candidates) | |
| # 2. 공유 시드로 셔플 (모든 에이전트가 동일한 결과를 얻음) | |
| # (local random instance를 사용하여 전역 random 상태에 영향 안 줌) | |
| rng = random.Random(seed) | |
| rng.shuffle(ordered_candidates) | |
| # 3. 날짜 기반 선택 | |
| return ordered_candidates[day % len(ordered_candidates)] |
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| uninvestigated_docs = [t for t in doctor_claims if t not in context["investigations"]] | ||
| if uninvestigated_docs: | ||
| return GameAction(target_id=self._pick_fair_deterministic(uninvestigated_docs, status.day, seed)) | ||
|
|
There was a problem hiding this comment.
Inconsistent parameter passed to _get_confirmed_players. In _act_vote (line 216), it's called with alive_players (includes self), but in _act_night (line 265), it's called with alive_others (excludes self). This inconsistency could lead to different confirmation logic between phases. Consider standardizing to either always include or exclude the agent, or document why the difference is intentional.
| # 1. 정렬 | ||
| candidates.sort() | ||
|
|
||
| # 2. 공유 시드로 셔플 (모든 에이전트가 동일한 결과를 얻음) | ||
| # (local random instance를 사용하여 전역 random 상태에 영향 안 줌) | ||
| rng = random.Random(seed) | ||
| rng.shuffle(candidates) | ||
|
|
||
| # 3. 날짜 기반 선택 | ||
| return candidates[day % len(candidates)] |
There was a problem hiding this comment.
The candidates list is sorted in-place, which mutates the input list. This could cause unexpected behavior if the caller reuses the list. Consider using sorted() to create a new list instead: candidates = sorted(candidates).
| # 1. 정렬 | |
| candidates.sort() | |
| # 2. 공유 시드로 셔플 (모든 에이전트가 동일한 결과를 얻음) | |
| # (local random instance를 사용하여 전역 random 상태에 영향 안 줌) | |
| rng = random.Random(seed) | |
| rng.shuffle(candidates) | |
| # 3. 날짜 기반 선택 | |
| return candidates[day % len(candidates)] | |
| # 1. 정렬 (입력 리스트를 변형하지 않도록 복사본 사용) | |
| sorted_candidates = sorted(candidates) | |
| # 2. 공유 시드로 셔플 (모든 에이전트가 동일한 결과를 얻음) | |
| # (local random instance를 사용하여 전역 random 상태에 영향 안 줌) | |
| rng = random.Random(seed) | |
| rng.shuffle(sorted_candidates) | |
| # 3. 날짜 기반 선택 | |
| return sorted_candidates[day % len(sorted_candidates)] |
| # If no valid votes exist, abstain from selecting an execution target. | ||
| if not votes: return GameAction(target_id=-1) | ||
| target_id = max(votes, key=votes.get) | ||
|
|
||
| cand_stat = self.player_stats[target_id] | ||
| survivors = [p for p in status.players if p.alive] | ||
|
|
||
| my_sus_on_target = self.individual_suspicion.get(target_id, 10) | ||
|
|
||
| if my_sus_on_target > 90: return GameAction(target_id=target_id) | ||
|
|
||
| if my_sus_on_target < 5: return GameAction(target_id=-1) | ||
| if target_id in self.known_safe: return GameAction(target_id=-1) | ||
| if cand_stat["is_proven_citizen"]: return GameAction(target_id=-1) | ||
|
|
||
| chance = 0.5 | ||
| if cand_stat["suspicion"] > 50: chance += 0.4 | ||
|
|
||
| target_role_claim = cand_stat["claimed_role"] | ||
| if target_role_claim and target_role_claim != Role.CITIZEN: | ||
| if any(self.player_stats[p.id]["claimed_role"] == target_role_claim for p in survivors if p.id != target_id): | ||
| chance += 0.5 | ||
|
|
||
| agree = (chance > 0.5) | ||
|
|
||
| if self.role == Role.MAFIA: | ||
| if target_id in self.known_mafia: agree = (cand_stat["suspicion"] > 150) | ||
| else: agree = True | ||
|
|
||
| if target_id == self.id: agree = False | ||
| target_id = max(votes, key=votes.get) |
There was a problem hiding this comment.
When target_id equals -1, this returns GameAction(target_id=self.id), which votes against execution by targeting oneself. However, this creates inconsistent behavior: when target_id == -1 (no execution candidate), the agent votes for itself, but when target_id == self.id (agent is the candidate), it also votes for itself. This seems counterintuitive. Consider returning GameAction(target_id=-1) in both cases to abstain, or document the intended voting strategy more clearly.
| accusations[event.actor_id] = event.target_id | ||
|
|
||
| # --- Execution & Feedback --- | ||
| if event.event_type == EventType.EXECUTE: |
There was a problem hiding this comment.
When an execution fails (event.value is None), this still stores the target_id in known_roles with a value of None. This could cause issues in later logic that checks known_roles, as it may interpret None differently than expected. Consider only storing the role when event.value is not None, or handle None values explicitly in downstream logic.
| if event.event_type == EventType.EXECUTE: | |
| if event.event_type == EventType.EXECUTE and event.value is not None: |
| if confirmed_fake: | ||
| return self._pick_fair_deterministic(confirmed_fake, day, seed) | ||
|
|
||
| # 3. [확경 신뢰] |
There was a problem hiding this comment.
Typo in comment: "확경" should be "확정" (confirmed).
| # 3. [확경 신뢰] | |
| # 3. [확정 신뢰] |
🔍 작업 배경 (Background)
🛠 작업 요약 (Summary)
🏷️ 작업 종류 (Type of Change)
📸 스크린샷 또는 실행 로그 (Screenshots / Logs)
✅ 체크리스트 (Self Checklist)
print등)를 정리했습니다.requirements.txt에 패키지를 추가했습니다.