namespace LivingVillage.Desktop open LivingVillage.Kernel type MenuPage = | MainMenu | OccupationSelect | Settings | Controls | Playing | LoadError type MenuItem = | ContinueItem | NewGameItem | LoadItem | SettingsItem | ControlsItem | ExitItem type MenuSetting = | OneXSetting | TwoXSetting | FiveXSetting | PauseSetting type MenuInput = | Up | Down | Confirm | Back type MenuCommand = | NoCommand | ContinueGame | StartNewGame | StartNewGameWith of Occupation.Kind option | LoadGame | ApplySettings of SimulationControl | ExitGame type ControlRow = { Keys: string Action: string } type MenuState = { Page: MenuPage Selected: int HasCurrentWorld: bool ReturnPage: MenuPage Settings: SimulationControl Error: string option } module MenuState = let settings : MenuSetting list = [ OneXSetting; TwoXSetting; FiveXSetting; PauseSetting ] let controlRows : ControlRow list = [ { Keys = "WASD"; Action = "移动" } { Keys = "E"; Action = "互动" } { Keys = "1-6"; Action = "对话" } { Keys = "Q"; Action = "观察" } { Keys = "TAB"; Action = "需求" } { Keys = "C"; Action = "年鉴" } { Keys = "L"; Action = "关系" } { Keys = "P"; Action = "暂停" } { Keys = "F1/F2/F3"; Action = "速度" } { Keys = "F6"; Action = "保存" } { Keys = "F7"; Action = "读取" } { Keys = "ESC"; Action = "菜单或关闭" } { Keys = "UP/DOWN"; Action = "选择" } { Keys = "ENTER"; Action = "确认" } ] let create (simulationControl: SimulationControl) (hasCurrentWorld: bool) : MenuState = { Page = MainMenu Selected = 0 HasCurrentWorld = hasCurrentWorld ReturnPage = MainMenu Settings = simulationControl Error = None } /// P16 开始界面收敛为三项:开始(无存档)或继续(回到进行中的世界)/ 读档 / 退出。 /// 设置与操作说明不再占用开始界面,仍可在游戏内通过 F1/F2/F3 与 P 打开。 let items (state: MenuState) : MenuItem list = if state.HasCurrentWorld then [ ContinueItem; LoadItem; ExitItem ] else [ NewGameItem; LoadItem; ExitItem ] let settingsLabel setting : string = match setting with | OneXSetting -> "1倍速" | TwoXSetting -> "2倍速" | FiveXSetting -> "5倍速" | PauseSetting -> "暂停" let itemLabel item : string = match item with | ContinueItem -> "继续" | NewGameItem -> "开始" | LoadItem -> "读档" | SettingsItem -> "设置" | ControlsItem -> "操作说明" | ExitItem -> "退出" /// 开局四选一 + 「暂不选择」(None = 旧行为,无职业)。 let occupationOptions : Occupation.Kind option list = [ Some Occupation.Farmer Some Occupation.Fisher Some Occupation.Peddler Some Occupation.Scholar None ] let occupationLabel (occupation: Occupation.Kind option) : string = match occupation with | Some kind -> Occupation.nameOf kind | None -> "暂不选择" let selectedOccupation (state: MenuState) : Occupation.Kind option = occupationOptions |> List.item state.Selected let private optionsFor (state: MenuState) : int = match state.Page with | MainMenu -> items state |> List.length | OccupationSelect -> occupationOptions.Length | Settings -> settings.Length | _ -> 0 let private moveBy (delta: int) (state: MenuState) : MenuState = let count = optionsFor state if count = 0 then state else let next = (state.Selected + delta) % count let wrapped = if next < 0 then next + count else next { state with Selected = wrapped } let move direction state = match direction with | Up -> moveBy -1 state | Down -> moveBy 1 state | Confirm | Back -> state let selectedItem (state: MenuState) : MenuItem = items state |> List.item state.Selected let selectedSetting (state: MenuState) : MenuSetting = settings |> List.item state.Selected let private selectedControl (state: MenuState) : SimulationControl = match selectedSetting state with | OneXSetting -> { state.Settings with Paused = false; Speed = OneX } | TwoXSetting -> { state.Settings with Paused = false; Speed = TwoX } | FiveXSetting -> { state.Settings with Paused = false; Speed = FiveX } | PauseSetting -> { state.Settings with Paused = true } let enterGame (state: MenuState) : MenuState = { state with Page = Playing; Selected = 0; ReturnPage = MainMenu; Error = None } let setCurrentWorld (hasCurrentWorld: bool) (state: MenuState) : MenuState = { state with HasCurrentWorld = hasCurrentWorld } let setSettings (simulationControl: SimulationControl) (state: MenuState) : MenuState = { state with Settings = simulationControl } let openMain (state: MenuState) : MenuState = { state with Page = MainMenu; Selected = 0; ReturnPage = MainMenu; Error = None } let openSettings (state: MenuState) : MenuState = { state with Page = Settings; Selected = 0; ReturnPage = state.Page; Error = None } /// 新档确认后进入职业选择页;默认选中「暂不选择」= 不写职业的旧行为。 let openOccupationSelect (state: MenuState) : MenuState = { state with Page = OccupationSelect Selected = occupationOptions.Length - 1 ReturnPage = state.Page Error = None } let openControls (state: MenuState) : MenuState = { state with Page = Controls; Selected = 0; ReturnPage = state.Page; Error = None } /// P key: open the full key list as a pause-help page. The simulation is paused while /// the page is open; the caller keeps the previous control and restores it on exit. let enterPauseHelp (control: SimulationControl) (state: MenuState) : MenuState * SimulationControl = openControls state, { control with Paused = true } let showLoadError (message: string) (state: MenuState) : MenuState = { state with Page = LoadError; Selected = 0; ReturnPage = MainMenu; Error = Some message } let private returnFromSubpage (state: MenuState) : MenuState = match state.ReturnPage with | Playing -> { state with Page = Playing; Selected = 0; Error = None } | _ -> openMain state /// Leaving the pause-help page returns to the page it was opened from and restores /// the exact simulation control the player had before. let exitPauseHelp (original: SimulationControl) (state: MenuState) : MenuState * SimulationControl = returnFromSubpage state, original let private confirmMain (state: MenuState) : MenuState * MenuCommand = match selectedItem state with | ContinueItem -> enterGame state, ContinueGame | NewGameItem -> state, StartNewGame | LoadItem -> state, LoadGame | SettingsItem -> openSettings state, NoCommand | ControlsItem -> openControls state, NoCommand | ExitItem -> state, ExitGame let update (input: MenuInput) (state: MenuState) : MenuState * MenuCommand = match input with | Up -> move Up state, NoCommand | Down -> move Down state, NoCommand | Back -> match state.Page with | MainMenu -> state, ExitGame | OccupationSelect -> openMain state, NoCommand | Settings | Controls -> returnFromSubpage state, NoCommand | LoadError -> openMain state, NoCommand | Playing -> openMain state, NoCommand | Confirm -> match state.Page with | MainMenu -> confirmMain state | OccupationSelect -> state, StartNewGameWith (selectedOccupation state) | Settings -> let nextSettings = selectedControl state { state with Settings = nextSettings }, ApplySettings nextSettings | Controls -> state, NoCommand | LoadError -> openMain state, NoCommand | Playing -> state, NoCommand