fix: use fingerprint as player ID to prevent name collision

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 10:50:21 +09:00
parent cd2013a917
commit e8887cd69a
4 changed files with 23 additions and 18 deletions

View File

@@ -70,8 +70,8 @@ type GameSession struct {
} }
type playerActionMsg struct { type playerActionMsg struct {
PlayerName string PlayerID string
Action PlayerAction Action PlayerAction
} }
func NewGameSession() *GameSession { func NewGameSession() *GameSession {
@@ -209,12 +209,12 @@ func (s *GameSession) GetState() GameState {
} }
} }
func (s *GameSession) SubmitAction(playerName string, action PlayerAction) { func (s *GameSession) SubmitAction(playerID string, action PlayerAction) {
s.actionCh <- playerActionMsg{PlayerName: playerName, Action: action} s.actionCh <- playerActionMsg{PlayerID: playerID, Action: action}
} }
// BuyItem handles shop purchases // BuyItem handles shop purchases
func (s *GameSession) BuyItem(playerName string, itemIdx int) bool { func (s *GameSession) BuyItem(playerID string, itemIdx int) bool {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.state.Phase != PhaseShop || itemIdx < 0 || itemIdx >= len(s.state.ShopItems) { if s.state.Phase != PhaseShop || itemIdx < 0 || itemIdx >= len(s.state.ShopItems) {
@@ -222,7 +222,7 @@ func (s *GameSession) BuyItem(playerName string, itemIdx int) bool {
} }
item := s.state.ShopItems[itemIdx] item := s.state.ShopItems[itemIdx]
for _, p := range s.state.Players { for _, p := range s.state.Players {
if p.Name == playerName && p.Gold >= item.Price { if p.Fingerprint == playerID && p.Gold >= item.Price {
p.Gold -= item.Price p.Gold -= item.Price
p.Inventory = append(p.Inventory, item) p.Inventory = append(p.Inventory, item)
return true return true

View File

@@ -10,6 +10,7 @@ import (
func TestGetStateNoRace(t *testing.T) { func TestGetStateNoRace(t *testing.T) {
s := NewGameSession() s := NewGameSession()
p := entity.NewPlayer("Racer", entity.ClassWarrior) p := entity.NewPlayer("Racer", entity.ClassWarrior)
p.Fingerprint = "test-fp"
s.AddPlayer(p) s.AddPlayer(p)
s.StartGame() s.StartGame()
@@ -30,7 +31,7 @@ func TestGetStateNoRace(t *testing.T) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
select { select {
case s.actionCh <- playerActionMsg{PlayerName: "Racer", Action: PlayerAction{Type: ActionWait}}: case s.actionCh <- playerActionMsg{PlayerID: "test-fp", Action: PlayerAction{Type: ActionWait}}:
default: default:
} }
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
@@ -41,6 +42,7 @@ func TestGetStateNoRace(t *testing.T) {
func TestSessionTurnTimeout(t *testing.T) { func TestSessionTurnTimeout(t *testing.T) {
s := NewGameSession() s := NewGameSession()
p := entity.NewPlayer("test", entity.ClassWarrior) p := entity.NewPlayer("test", entity.ClassWarrior)
p.Fingerprint = "test-fp"
s.AddPlayer(p) s.AddPlayer(p)
s.StartFloor() s.StartFloor()

View File

@@ -36,7 +36,7 @@ func (s *GameSession) RunTurn() {
select { select {
case msg := <-s.actionCh: case msg := <-s.actionCh:
s.mu.Lock() s.mu.Lock()
s.actions[msg.PlayerName] = msg.Action s.actions[msg.PlayerID] = msg.Action
s.mu.Unlock() s.mu.Unlock()
collected++ collected++
case <-timer.C: case <-timer.C:
@@ -53,8 +53,8 @@ resolve:
// Default action for players who didn't submit: Wait // Default action for players who didn't submit: Wait
for _, p := range s.state.Players { for _, p := range s.state.Players {
if !p.IsOut() { if !p.IsOut() {
if _, ok := s.actions[p.Name]; !ok { if _, ok := s.actions[p.Fingerprint]; !ok {
s.actions[p.Name] = PlayerAction{Type: ActionWait} s.actions[p.Fingerprint] = PlayerAction{Type: ActionWait}
} }
} }
} }
@@ -79,7 +79,7 @@ func (s *GameSession) resolvePlayerActions() {
if p.IsOut() { if p.IsOut() {
continue continue
} }
action, ok := s.actions[p.Name] action, ok := s.actions[p.Fingerprint]
if !ok { if !ok {
continue continue
} }

View File

@@ -172,6 +172,9 @@ func (m Model) updateTitle(msg tea.Msg) (tea.Model, tea.Cmd) {
} else { } else {
m.playerName = "Adventurer" m.playerName = "Adventurer"
} }
if m.fingerprint == "" {
m.fingerprint = fmt.Sprintf("anon-%d", time.Now().UnixNano())
}
m.screen = screenLobby m.screen = screenLobby
m = m.withRefreshedLobby() m = m.withRefreshedLobby()
} else if isQuit(key) { } else if isQuit(key) {
@@ -349,7 +352,7 @@ func (m Model) updateGame(msg tea.Msg) (tea.Model, tea.Cmd) {
case game.PhaseCombat: case game.PhaseCombat:
isPlayerDead := false isPlayerDead := false
for _, p := range m.gameState.Players { for _, p := range m.gameState.Players {
if p.Name == m.playerName && p.IsDead() { if p.Fingerprint == m.fingerprint && p.IsDead() {
isPlayerDead = true isPlayerDead = true
break break
} }
@@ -366,15 +369,15 @@ func (m Model) updateGame(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.session != nil { if m.session != nil {
switch key.String() { switch key.String() {
case "1": case "1":
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionAttack, TargetIdx: m.targetCursor}) m.session.SubmitAction(m.fingerprint, game.PlayerAction{Type: game.ActionAttack, TargetIdx: m.targetCursor})
case "2": case "2":
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionSkill, TargetIdx: m.targetCursor}) m.session.SubmitAction(m.fingerprint, game.PlayerAction{Type: game.ActionSkill, TargetIdx: m.targetCursor})
case "3": case "3":
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionItem}) m.session.SubmitAction(m.fingerprint, game.PlayerAction{Type: game.ActionItem})
case "4": case "4":
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionFlee}) m.session.SubmitAction(m.fingerprint, game.PlayerAction{Type: game.ActionFlee})
case "5": case "5":
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionWait}) m.session.SubmitAction(m.fingerprint, game.PlayerAction{Type: game.ActionWait})
} }
// After submitting, poll for turn resolution // After submitting, poll for turn resolution
return m, m.pollState() return m, m.pollState()
@@ -401,7 +404,7 @@ func (m Model) updateShop(msg tea.Msg) (tea.Model, tea.Cmd) {
case "1", "2", "3": case "1", "2", "3":
if m.session != nil { if m.session != nil {
idx := int(key.String()[0] - '1') idx := int(key.String()[0] - '1')
m.session.BuyItem(m.playerName, idx) m.session.BuyItem(m.fingerprint, idx)
m.gameState = m.session.GetState() m.gameState = m.session.GetState()
} }
case "q": case "q":