Skip to content

Fix/branch-merge#177

Merged
chang1025 merged 16 commits intomainfrom
fix/branch-merge
Jan 27, 2026
Merged

Fix/branch-merge#177
chang1025 merged 16 commits intomainfrom
fix/branch-merge

Conversation

@xoxown31
Copy link
Collaborator

🔍 작업 배경 (Background)

  • 브랜치 머지

🛠 작업 요약 (Summary)

  • 브랜치 머지

🏷️ 작업 종류 (Type of Change)

  • ✨ 기능 추가 (New Feature)
  • 🐛 버그 수정 (Bug Fix)
  • ♻️ 리팩토링 (Refactoring)
  • 📝 문서 작업 (Documentation)
  • 🔧 환경 설정 및 기타 (Config & Other)

📸 스크린샷 또는 실행 로그 (Screenshots / Logs)

✅ 체크리스트 (Self Checklist)

  • 코드가 정상적으로 실행되며 에러가 발생하지 않습니다.
  • API 명세(입출력 규격)를 준수했습니다. (Gymnasium Env 등)
  • 불필요한 주석이나 디버깅 코드(print 등)를 정리했습니다.
  • (필요한 경우) requirements.txt에 패키지를 추가했습니다.

- LIKELIHOOD_MATRIX 제거 및 BASE_LIKELIHOODS로 대체
- 새로운 상수 INITIAL_VOTE_TEMPERATURE, MIN_VOTE_TEMPERATURE, VOTE_TEMP_DECAY_RATE 추가
- BayesianInferenceEngine에 상관관계 추적 기능 추가
- RuleBaseAgent에서 투표 패턴 기반 상관관계 분석 구현
Copilot AI review requested due to automatic review settings January 26, 2026 10:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
if confirmed_fake:
return self._pick_fair_deterministic(confirmed_fake, day, seed)

# 3. [확경 신뢰]
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "확경" should be "확정" (meaning "confirmed" in Korean). The comment text appears to be abbreviated or contains a typo.

Suggested change
# 3. [확경 신뢰]
# 3. [확정 신뢰]

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot "확경" is an abbreviation for "확정 경찰".


def get_action(self, status: GameStatus) -> GameAction:
# [새 게임 감지]
if status.day < self.last_day:
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if status.day < self.last_day:
if status.day < self.last_day or (status.day == 1 and self.last_day != 1):

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
self.game_seed = random.randint(0, 1000000)

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +24
if self.role == Role.MAFIA and self.fake_role is None:
self.fake_role = random.choice([Role.POLICE, Role.DOCTOR])
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +115
# 1. 정렬
candidates.sort()

# 2. 공유 시드로 셔플 (모든 에이전트가 동일한 결과를 얻음)
# (local random instance를 사용하여 전역 random 상태에 영향 안 줌)
rng = random.Random(seed)
rng.shuffle(candidates)

# 3. 날짜 기반 선택
return candidates[day % len(candidates)]
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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)]

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings January 26, 2026 11:08
Copy link

Copilot AI commented Jan 26, 2026

@xoxown31 I've opened a new pull request, #178, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +115
# 1. 정렬
candidates.sort()

# 2. 공유 시드로 셔플 (모든 에이전트가 동일한 결과를 얻음)
# (local random instance를 사용하여 전역 random 상태에 영향 안 줌)
rng = random.Random(seed)
rng.shuffle(candidates)

# 3. 날짜 기반 선택
return candidates[day % len(candidates)]
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
# 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)]

Copilot uses AI. Check for mistakes.
Comment on lines +233 to +236
# 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)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
accusations[event.actor_id] = event.target_id

# --- Execution & Feedback ---
if event.event_type == EventType.EXECUTE:
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if event.event_type == EventType.EXECUTE:
if event.event_type == EventType.EXECUTE and event.value is not None:

Copilot uses AI. Check for mistakes.
if confirmed_fake:
return self._pick_fair_deterministic(confirmed_fake, day, seed)

# 3. [확경 신뢰]
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "확경" should be "확정" (confirmed).

Suggested change
# 3. [확경 신뢰]
# 3. [확정 신뢰]

Copilot uses AI. Check for mistakes.
@chang1025 chang1025 merged commit 4c27d5f into main Jan 27, 2026
@xoxown31 xoxown31 deleted the fix/branch-merge branch January 27, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants