Issues fixed:
- GameSession.handleInput() was returning plain text, causing WebSocket frontend
to lose state information after each input
- GameWebSocketServer.handleGameInput() was calling unnecessary getStateInfo()
- Duplicate state saves in runCurrentState() and handleInput()
Changes:
1. GameSession.php:
- handleInput() now returns structured array with output + state info
- runCurrentState() no longer saves state (already done in handleInput)
- Consistent return format: { output, state, stateName, playerInfo }
2. GameWebSocketServer.php:
- handleGameInput() simplified to use handleInput() return value
- Direct merge of result into WebSocket message
3. web/server.php:
- handleGameInput() simplified to just return handleInput() result
- No duplicate getStateInfo() call
Results:
- Web frontend now receives complete state info after each input
- State transitions in submenus now work correctly
- No more state desynchronization between client and server
Testing:
- Input in level 3+ menus now executes correctly
- State name updates properly in header
- Player info (HP, etc) stays synchronized
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
199 lines
5.3 KiB
PHP
199 lines
5.3 KiB
PHP
<?php
|
||
namespace Game\Core;
|
||
|
||
/**
|
||
* Web 游戏会话 - 使用完整的 Game 逻辑
|
||
*/
|
||
class GameSession
|
||
{
|
||
private Game $game;
|
||
private WebOutput $output;
|
||
private string $userId;
|
||
|
||
public function __construct(string $userId)
|
||
{
|
||
$this->userId = $userId;
|
||
$this->output = new WebOutput();
|
||
|
||
// 设置 Web 输入模式
|
||
$webInput = WebInput::getInstance();
|
||
$webInput->setWebMode(true);
|
||
|
||
// 创建 Game 实例,使用 WebOutput 和用户 ID
|
||
$this->game = new Game(null, $this->output, $userId);
|
||
}
|
||
|
||
/**
|
||
* 渲染当前界面(不需要输入)
|
||
*/
|
||
public function render(): string
|
||
{
|
||
$this->output->clear();
|
||
|
||
try {
|
||
$this->runCurrentState();
|
||
} catch (NeedInputException $e) {
|
||
// 正常情况,界面渲染完成,等待输入
|
||
}
|
||
|
||
return $this->output->getOutput();
|
||
}
|
||
|
||
/**
|
||
* 处理用户输入并返回新界面(支持时间戳数据)
|
||
*/
|
||
public function handleInput(string $input): string|array
|
||
{
|
||
$this->output->clear();
|
||
|
||
// 将输入推入队列
|
||
$webInput = WebInput::getInstance();
|
||
$webInput->clear();
|
||
$webInput->push($input);
|
||
|
||
try {
|
||
$this->runCurrentState();
|
||
} catch (NeedInputException $e) {
|
||
// 需要更多输入,返回当前输出
|
||
}
|
||
|
||
// 保存状态
|
||
$this->game->saveState();
|
||
|
||
// 获取当前状态信息
|
||
$stateInfo = $this->getStateInfo();
|
||
$output = $this->output->getOutput();
|
||
|
||
// 返回包含状态信息的结构化数据
|
||
return [
|
||
'output' => $output,
|
||
'state' => $stateInfo['state'],
|
||
'stateName' => $stateInfo['stateName'],
|
||
'playerInfo' => $stateInfo['playerInfo'],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* SSE 流式战斗模式
|
||
* 当用户进入战斗时,使用SSE直接推送日志
|
||
*/
|
||
public function streamBattle(string $input): void
|
||
{
|
||
// 使用SSEOutput替换普通输出
|
||
$sseOutput = new \Game\Core\SSEOutput();
|
||
|
||
// 替换game的output对象
|
||
$this->game->output = $sseOutput;
|
||
|
||
// 将输入推入队列
|
||
$webInput = WebInput::getInstance();
|
||
$webInput->clear();
|
||
$webInput->push($input);
|
||
|
||
try {
|
||
$this->runCurrentState();
|
||
} catch (NeedInputException $e) {
|
||
// 战斗中可能需要更多输入
|
||
}
|
||
|
||
// 保存状态
|
||
$this->game->saveState();
|
||
|
||
// 发送完成信号
|
||
$sseOutput->complete();
|
||
}
|
||
|
||
/**
|
||
* 执行当前状态的逻辑
|
||
*/
|
||
private function runCurrentState(): void
|
||
{
|
||
if ($this->game->state == 0){
|
||
$this->game->state = 1;
|
||
}
|
||
switch ($this->game->state) {
|
||
case Game::MENU:
|
||
(new \Game\Modules\Menu($this->game))->show();
|
||
break;
|
||
case Game::DUNGEON_SELECT:
|
||
(new \Game\Modules\DungeonSelectPanel($this->game))->show();
|
||
break;
|
||
case Game::BATTLE:
|
||
(new \Game\Modules\Battle($this->game))->start();
|
||
break;
|
||
case Game::STATS:
|
||
(new \Game\Modules\StatsPanel($this->game))->show();
|
||
break;
|
||
case Game::INVENTORY:
|
||
(new \Game\Modules\InventoryPanel($this->game))->show();
|
||
break;
|
||
case Game::EQUIPMENT_ENHANCE:
|
||
(new \Game\Modules\EquipmentEnhancePanel($this->game))->show();
|
||
break;
|
||
case Game::NPC:
|
||
(new \Game\Modules\NpcPanel($this->game))->show();
|
||
break;
|
||
case Game::TALENT:
|
||
(new \Game\Modules\TalentPanel($this->game))->show();
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取当前游戏状态
|
||
*/
|
||
public function getState(): int
|
||
{
|
||
return $this->game->state;
|
||
}
|
||
|
||
/**
|
||
* 获取玩家信息
|
||
*/
|
||
public function getPlayerInfo(): array
|
||
{
|
||
$stats = $this->game->player->getStats();
|
||
return [
|
||
'level' => $this->game->player->level,
|
||
'hp' => $this->game->player->hp,
|
||
'maxHp' => $stats['maxHp'],
|
||
'mana' => $this->game->player->mana,
|
||
'maxMana' => $stats['maxMana'],
|
||
'spiritStones' => $this->game->player->spiritStones,
|
||
'exp' => $this->game->player->exp,
|
||
'maxExp' => $this->game->player->maxExp,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 获取当前游戏状态信息
|
||
*/
|
||
public function getStateInfo(): array
|
||
{
|
||
return [
|
||
'state' => $this->game->state,
|
||
'stateName' => $this->getStateName(),
|
||
'playerInfo' => $this->getPlayerInfo(),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 将状态常量转换为名称
|
||
*/
|
||
private function getStateName(): string
|
||
{
|
||
return match($this->game->state) {
|
||
Game::MENU => 'MENU',
|
||
Game::DUNGEON_SELECT => 'DUNGEON_SELECT',
|
||
Game::BATTLE => 'BATTLE',
|
||
Game::STATS => 'STATS',
|
||
Game::INVENTORY => 'INVENTORY',
|
||
Game::EQUIPMENT_ENHANCE => 'EQUIPMENT_ENHANCE',
|
||
Game::NPC => 'NPC',
|
||
Game::TALENT => 'TALENT',
|
||
Game::EXIT => 'EXIT',
|
||
default => 'UNKNOWN'
|
||
};
|
||
}
|
||
}
|