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.spawnMonsters()
|
||||||
s.state.Phase = PhaseCombat
|
s.state.Phase = PhaseCombat
|
||||||
s.state.CombatTurn = 0
|
s.state.CombatTurn = 0
|
||||||
|
s.signalCombat()
|
||||||
case dungeon.RoomBoss:
|
case dungeon.RoomBoss:
|
||||||
s.spawnBoss()
|
s.spawnBoss()
|
||||||
s.state.Phase = PhaseCombat
|
s.state.Phase = PhaseCombat
|
||||||
s.state.CombatTurn = 0
|
s.state.CombatTurn = 0
|
||||||
|
s.signalCombat()
|
||||||
case dungeon.RoomShop:
|
case dungeon.RoomShop:
|
||||||
s.generateShopItems()
|
s.generateShopItems()
|
||||||
s.state.Phase = PhaseShop
|
s.state.Phase = PhaseShop
|
||||||
@@ -43,25 +45,33 @@ func (s *GameSession) EnterRoom(roomIdx int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *GameSession) spawnMonsters() {
|
func (s *GameSession) spawnMonsters() {
|
||||||
count := 1 + rand.Intn(5) // 1~5 monsters
|
count := 1 + rand.Intn(5)
|
||||||
floor := s.state.FloorNum
|
floor := s.state.FloorNum
|
||||||
s.state.Monsters = make([]*entity.Monster, count)
|
s.state.Monsters = make([]*entity.Monster, count)
|
||||||
|
|
||||||
// Pick appropriate monster type for floor
|
type floorRange struct {
|
||||||
var mt entity.MonsterType
|
mt entity.MonsterType
|
||||||
switch {
|
minFloor int
|
||||||
case floor <= 5:
|
maxFloor int
|
||||||
mt = entity.MonsterSlime
|
}
|
||||||
case floor <= 10:
|
ranges := []floorRange{
|
||||||
mt = entity.MonsterSkeleton
|
{entity.MonsterSlime, 1, 5},
|
||||||
case floor <= 14:
|
{entity.MonsterSkeleton, 3, 10},
|
||||||
mt = entity.MonsterOrc
|
{entity.MonsterOrc, 6, 14},
|
||||||
default:
|
{entity.MonsterDarkKnight, 12, 20},
|
||||||
mt = entity.MonsterDarkKnight
|
}
|
||||||
|
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++ {
|
for i := 0; i < count; i++ {
|
||||||
|
mt := valid[rand.Intn(len(valid))]
|
||||||
m := entity.NewMonster(mt, floor)
|
m := entity.NewMonster(mt, floor)
|
||||||
if s.state.SoloMode {
|
if s.state.SoloMode {
|
||||||
m.HP = m.HP / 2
|
m.HP = m.HP / 2
|
||||||
|
|||||||
@@ -43,13 +43,28 @@ type GameState struct {
|
|||||||
GameOver bool
|
GameOver bool
|
||||||
Victory bool
|
Victory bool
|
||||||
ShopItems []entity.Item
|
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 {
|
type GameSession struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
state GameState
|
state GameState
|
||||||
actions map[string]PlayerAction // playerName -> action
|
started bool
|
||||||
actionCh chan playerActionMsg
|
actions map[string]PlayerAction // playerName -> action
|
||||||
|
actionCh chan playerActionMsg
|
||||||
|
combatSignal chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type playerActionMsg struct {
|
type playerActionMsg struct {
|
||||||
@@ -62,17 +77,56 @@ func NewGameSession() *GameSession {
|
|||||||
state: GameState{
|
state: GameState{
|
||||||
FloorNum: 1,
|
FloorNum: 1,
|
||||||
},
|
},
|
||||||
actions: make(map[string]PlayerAction),
|
actions: make(map[string]PlayerAction),
|
||||||
actionCh: make(chan playerActionMsg, 4),
|
actionCh: make(chan playerActionMsg, 4),
|
||||||
|
combatSignal: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartGame determines solo mode from actual player count at game start
|
// StartGame determines solo mode from actual player count at game start
|
||||||
func (s *GameSession) StartGame() {
|
func (s *GameSession) StartGame() {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
if s.started {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.started = true
|
||||||
s.state.SoloMode = len(s.state.Players) == 1
|
s.state.SoloMode = len(s.state.Players) == 1
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
s.StartFloor()
|
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) {
|
func (s *GameSession) AddPlayer(p *entity.Player) {
|
||||||
|
|||||||
Reference in New Issue
Block a user