namespace LivingVillage.Desktop open System open LivingVillage.Kernel.Sim module VillagePresentation = (** 中文江南水乡阶段一样板的纯展示快照。 TilePosition 使用整数瓦片坐标;一个坐标单位代表一块场景瓦片,原点和 屏幕像素换算留给后续 MonoGame 组合层。SimulationTick 使用 int64,表示 Kernel 同一时间轴上的一个模拟 tick。PrototypeScene 以及其中的 record 和 list 都是不可变快照,调用方只能读取,不能把它们当作可变模拟状态; 本模块不保存可变全局状态,也不依赖 MonoGame。 **) [] type TilePosition = { X: int Y: int } [] type PixelPosition = { X: float32 Y: float32 } [] type SimulationTick = | SimulationTick of int64 [] type HomeId = | HomeId of int type VillageProp = | WhiteWallDarkTileHome | HomeDoor | RiverChannel | RiverBank | StoneBridge | StonePaving | Bamboo | VegetableGarden type VillageElement = { Prop: VillageProp Position: TilePosition } type Home = { Id: HomeId Door: TilePosition } type PrototypeScene = { Elements: VillageElement list Homes: Home list InitialPlayerPosition: TilePosition } type Direction = | North | South | West | East type DirectionInput = | UpInput | DownInput | LeftInput | RightInput | NoInput type CharacterAnimationFrame = | FrameA | FrameB type CharacterStyle = | StrawHatFarmer | BlueApronVillager | GreenScarfVillager type HomeMode = | Outside | Inside of HomeId type HomeInputAction = | EnterHomeAction of HomeId | ExitHomeAction of HomeId | DialogueAction type HomeTransitionError = | NotNearHomeDoor | AlreadyInsideHome of HomeId | NotInsideHome | HomeNotFound of HomeId type StageOneCopy = { Interact: string Needs: string Trade: string EnterHome: string } let private tile x y : TilePosition = { X = x; Y = y } let private element prop position : VillageElement = { Prop = prop Position = position } let createScene () : PrototypeScene = let home = { Id = HomeId 1 Door = tile 6 5 } { Elements = [ element WhiteWallDarkTileHome (tile 6 4) element HomeDoor home.Door element StonePaving (tile 4 5) element StonePaving (tile 5 5) element StonePaving (tile 6 5) element StonePaving (tile 7 5) element RiverBank (tile 10 2) element RiverBank (tile 10 3) element RiverChannel (tile 10 4) element RiverChannel (tile 10 5) element StoneBridge (tile 10 4) element Bamboo (tile 3 2) element Bamboo (tile 3 3) element VegetableGarden (tile 8 7) element VegetableGarden (tile 9 7) ] Homes = [ home ] InitialPlayerPosition = tile 5 5 } let scene : PrototypeScene = createScene () let sceneOrigin : TilePosition = tile 25 18 let sceneTileFromPixel (pixel: PixelPosition) : TilePosition = { X = int (Math.Floor(float pixel.X / float tilePixels)) - sceneOrigin.X Y = int (Math.Floor(float pixel.Y / float tilePixels)) - sceneOrigin.Y } let worldTileForSceneTile (sceneTile: TilePosition) : TilePosition = { X = sceneOrigin.X + sceneTile.X Y = sceneOrigin.Y + sceneTile.Y } let private manhattanDistance (first: TilePosition) (second: TilePosition) : int = abs (first.X - second.X) + abs (first.Y - second.Y) let isNearHomeDoor (position: TilePosition) (home: Home) : bool = manhattanDistance position home.Door <= 1 let enterHome (position: TilePosition) (mode: HomeMode) : Result = match mode with | Inside homeId -> Error(AlreadyInsideHome homeId) | Outside -> match scene.Homes |> List.tryFind (isNearHomeDoor position) with | Some home -> Ok(Inside home.Id) | None -> Error NotNearHomeDoor let exitHome (mode: HomeMode) : Result = match mode with | Outside -> Error NotInsideHome | Inside homeId -> if scene.Homes |> List.exists (fun home -> home.Id = homeId) then Ok Outside else Error(HomeNotFound homeId) let homeInputAction (pixel: PixelPosition) (mode: HomeMode) : HomeInputAction = let scenePosition = sceneTileFromPixel pixel match mode with | Outside -> match enterHome scenePosition Outside with | Ok(Inside homeId) -> EnterHomeAction homeId | _ -> DialogueAction | Inside homeId -> match scene.Homes |> List.tryFind (fun home -> home.Id = homeId) with | Some home when isNearHomeDoor scenePosition home -> match exitHome mode with | Ok Outside -> ExitHomeAction homeId | _ -> DialogueAction | _ -> DialogueAction let resetHomeMode () : HomeMode = Outside let directionForInput (input: DirectionInput) : Direction option = match input with | UpInput -> Some North | DownInput -> Some South | LeftInput -> Some West | RightInput -> Some East | NoInput -> None let private positiveModulo divisor value = let remainder = value % divisor if remainder < 0L then remainder + divisor else remainder let animationFrame (SimulationTick tick) : CharacterAnimationFrame = if positiveModulo 8L tick < 4L then FrameA else FrameB let characterStyle (LivingVillage.Kernel.Sim.NpcId npcId) : CharacterStyle = match ((npcId % 3) + 3) % 3 with | 0 -> StrawHatFarmer | 1 -> BlueApronVillager | _ -> GreenScarfVillager let stageOneCopy : StageOneCopy = { Interact = "互动" Needs = "需求" Trade = "交易" EnterHome = "进入民居" }