fix: prevent double StartGame, use overlapping monster floor ranges

This commit is contained in:
2026-03-24 00:51:00 +09:00
parent 3cc6f783b3
commit 743b5b9058
2 changed files with 83 additions and 19 deletions

View File

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

View File

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