fix: prevent double StartGame, use overlapping monster floor ranges
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user