diff --git a/game/event.go b/game/event.go index 529d2c1..712460e 100644 --- a/game/event.go +++ b/game/event.go @@ -24,10 +24,12 @@ func (s *GameSession) EnterRoom(roomIdx int) { s.spawnMonsters() s.state.Phase = PhaseCombat s.state.CombatTurn = 0 + s.signalCombat() case dungeon.RoomBoss: s.spawnBoss() s.state.Phase = PhaseCombat s.state.CombatTurn = 0 + s.signalCombat() case dungeon.RoomShop: s.generateShopItems() s.state.Phase = PhaseShop @@ -43,25 +45,33 @@ func (s *GameSession) EnterRoom(roomIdx int) { } func (s *GameSession) spawnMonsters() { - count := 1 + rand.Intn(5) // 1~5 monsters + count := 1 + rand.Intn(5) floor := s.state.FloorNum s.state.Monsters = make([]*entity.Monster, count) - // Pick appropriate monster type for floor - var mt entity.MonsterType - switch { - case floor <= 5: - mt = entity.MonsterSlime - case floor <= 10: - mt = entity.MonsterSkeleton - case floor <= 14: - mt = entity.MonsterOrc - default: - mt = entity.MonsterDarkKnight + type floorRange struct { + mt entity.MonsterType + minFloor int + maxFloor int + } + ranges := []floorRange{ + {entity.MonsterSlime, 1, 5}, + {entity.MonsterSkeleton, 3, 10}, + {entity.MonsterOrc, 6, 14}, + {entity.MonsterDarkKnight, 12, 20}, + } + var valid []entity.MonsterType + for _, r := range ranges { + if floor >= r.minFloor && floor <= r.maxFloor { + valid = append(valid, r.mt) + } + } + if len(valid) == 0 { + valid = []entity.MonsterType{entity.MonsterSlime} } - // Solo mode: 50% HP for i := 0; i < count; i++ { + mt := valid[rand.Intn(len(valid))] m := entity.NewMonster(mt, floor) if s.state.SoloMode { m.HP = m.HP / 2 diff --git a/game/session.go b/game/session.go index ee6712c..3076da4 100644 --- a/game/session.go +++ b/game/session.go @@ -43,13 +43,28 @@ type GameState struct { GameOver bool Victory bool ShopItems []entity.Item + CombatLog []string // recent combat messages +} + +func (s *GameSession) addLog(msg string) { + s.state.CombatLog = append(s.state.CombatLog, msg) + // Keep last 5 messages + if len(s.state.CombatLog) > 5 { + s.state.CombatLog = s.state.CombatLog[len(s.state.CombatLog)-5:] + } +} + +func (s *GameSession) clearLog() { + s.state.CombatLog = nil } type GameSession struct { - mu sync.Mutex - state GameState - actions map[string]PlayerAction // playerName -> action - actionCh chan playerActionMsg + mu sync.Mutex + state GameState + started bool + actions map[string]PlayerAction // playerName -> action + actionCh chan playerActionMsg + combatSignal chan struct{} } type playerActionMsg struct { @@ -62,17 +77,56 @@ func NewGameSession() *GameSession { state: GameState{ FloorNum: 1, }, - actions: make(map[string]PlayerAction), - actionCh: make(chan playerActionMsg, 4), + actions: make(map[string]PlayerAction), + actionCh: make(chan playerActionMsg, 4), + combatSignal: make(chan struct{}, 1), } } // StartGame determines solo mode from actual player count at game start func (s *GameSession) StartGame() { s.mu.Lock() + if s.started { + s.mu.Unlock() + return + } + s.started = true s.state.SoloMode = len(s.state.Players) == 1 s.mu.Unlock() s.StartFloor() + go s.combatLoop() +} + +// combatLoop continuously runs turns while in combat phase +func (s *GameSession) combatLoop() { + for { + s.mu.Lock() + phase := s.state.Phase + gameOver := s.state.GameOver + s.mu.Unlock() + + if gameOver { + return + } + + if phase == PhaseCombat { + s.RunTurn() // blocks until all actions collected or timeout + } else { + // Not in combat, wait for an action signal to avoid busy-spinning + // We'll just sleep briefly and re-check + select { + case <-s.combatSignal: + // Room entered, combat may have started + } + } + } +} + +func (s *GameSession) signalCombat() { + select { + case s.combatSignal <- struct{}{}: + default: + } } func (s *GameSession) AddPlayer(p *entity.Player) {