您的方法几乎没有问题,代码中有一些错误。因此,我将首先强调后者,然后提出并证明另一种方法。
问题
第一期
看起来你send_to_client
返回一个 type 的值unit Lwt.t
。如果您只是通过用 终止表达式来忽略它;
,那么这意味着“不要等到消息发送并继续前进”。通常这不是你想要的。unit Lwt.t
因此,您需要通过绑定到其返回值来等待线程完成。
第 2 期
通常在 Lwt 编程中,函数接受立即类型的值(即,不包含在 中的Lwt.t
值)并返回延迟线程(即,类型的值'some Lwt.t
)。当然,这通常是没有人阻止你做一些不同的事情。但尽量坚持“立即输入,延迟输出”的模式。
第 3 期
使用工具。用于ocp-indent
缩进您的代码,这将有助于提高可读性。此外,看起来您不使用编译器并且正在顶级播放。通常这是一个坏主意,尤其是对于系统编程。用于ocamlbuild
编译和运行您的代码:
ocamlbuild game.native --
游戏
与 Python 或其他弱类型系统语言的编程相比,OCaml 编程具有不同的理念。在 OCaml 中,应该从设计类型和签名开始,然后填写实现。当然,这是理想化,在现实生活中会有一个迭代提炼的过程,但大体的做法还是一样的。从类型开始。
所以,首先,让我们定义一个player
类型。这是微不足道的,但有改进的余地。
open Lwt
type player = {
fd : Lwt_unix.file_descr
}
接下来,让我们使用类型系统来帮助我们,了解我们的游戏初始化问题。你需要让两名玩家准备好并愿意玩你的游戏。这意味着,您有三个连续的状态:
Nobody
准备好了
One player
准备好了
Both (player1, player2)
准备好了
实际上,因为一旦你到达第三个状态,你就可以开始游戏了,你不需要那个状态,所以我们最终只有两个选择:
type stage =
| Nobody
| One of player
我们可以player option
在这里使用类型,因为它与我们的选择同构。但是让我们更明确地使用我们自己的stage
类型。它将使我们的模型更加受约束和拟合。
下一步将是定义客户端和服务器之间的交互协议。我们将使用名称request
来表示从服务器到客户端的消息,以及response
反向移动的消息。
type request =
| Init
| Wait
| Unknown_command
| Bye
type response =
| Start
| Quit
这个协议是抽象的,因为它不包含任何具体的表示——基于它,您可以构建不同的表示,例如,gui 界面或支持不同语言的文本聊天。
但是让我们模拟一个使用文本命令的最简单的具体实现:
let response_of_string msg =
match String.trim (String.uppercase msg) with
| "START" -> Some Start
| "QUIT" -> Some Quit
| _ -> None
而在相反的方向(注意:最好在客户端呈现此消息,并发送类型request
和response
在线的值,这将使您的流量配置文件保持较低,更重要的是,将允许透明地附加不同的客户端)。
let string_of_request = function
| Init -> "Welcome to a game server.
Please, type
- `start' to start game;
- `quit' to finish session"
| Wait -> "Please wait for another player to join the game"
| Unknown_command -> "Don't understand this"
| Bye -> "Thank you, see you later!"
下一步是为Io
. 该模块负责客户端和服务器之间的交互。请注意我们如何通过抽象隐藏所有细节,例如使用套接字或字符串。
module Io : sig
val send : player -> request -> unit Lwt.t
val recv : player -> response option Lwt.t
end = struct
let send dst msg = return_unit
let recv dst = return None
end
现在,我们可以定义我们的Game
模块了。起初它将有两个不同的自动机:
init
初始化两个玩家之间的游戏;
play
玩一次游戏,我们得到两个受害者。
让我们在 OCaml 中明确地说:
module Game : sig
(** [play a b] play a game between player [a] and player [b] *)
val play : player -> player -> unit Lwt.t
(** [init next_player] waits until two players are ready to play.
TODO: Describe a grammar that is recognized by this automaton. *)
val init : (unit -> player Lwt.t) -> (player * player) Lwt.t
end = struct
let play a b = return_unit
let init next_player =
let rec process stage player =
Io.send player Init >>= fun () ->
Io.recv player >>= function
| None ->
Io.send player Unknown_command >>= fun () ->
process stage player
| Some Quit ->
Io.send player Bye >>= fun () ->
next_player () >>= process stage
| Some Start -> match stage with
| One a -> return (a,player)
| Nobody ->
Io.send player Wait >>= fun () ->
next_player () >>= process (One player) in
next_player () >>= process Nobody
end
现在我们可以写出 main 函数,它将所有内容粘合在一起:
let main server_sock =
let next_player () =
Lwt_unix.accept server_sock >>=
fun (fd,_) -> return {fd} in
Game.init next_player >>= fun (a,b) ->
Game.play a b
当您继续使用这种方法时,您稍后可能会注意到,游戏的不同有限状态机定义了不同的语言(即协议)。因此,您最终可能会为每个 FSM 使用特定的协议,而不是使用一个协议,例如 、init_protocol
等play_protocol
。但是您可能还会注意到,这些协议有一些交叉点。要处理此问题,您可以使用子类型和多态变体。