14.3.2 设计仿真操作

728 篇文章 1 订阅
26 篇文章 0 订阅

14.3.2 设计仿真操作

 

    在本节中,我们会考虑需要实现仿真的操作。现在,我们不会实现所有困难的操作,因为,我们只想要设计应用程序的结构。我们的首要目标是用最小的努力,运行这个应用程序,然后,再回到有趣的部分,比如,描述动物和捕食者的动作的算法。

    以典型函数的方式,我们将从一些初始状态开始,并在每一步中,会基于上一个状态,创建新的状态。这意味着,我们需要一个操作,来创建初始状态,另一个操作来运行仿真的一步。两个操作在逻辑上都与仿真的状态有关,所以,在 C# 中,我们要把它们添加到 Simulation 类中;在 F# 中,使用内部类型扩展(intrinsic type extensions),把它们添加到 Simulation 类型中,第 9 章中我们讨论过。下面的代码片断显示了这些操作的类型,使用了 C# 语法:

 

class Simulation {
  public static Simulation CreateInitialState();
  public Simulation Step();
}

 

    如果你在读这本书时,写示例代码,可以用一些简单的方式,在自己的代码中来实现这些。现在,step 方法可以返回原始状态,或者,在某个方向上,把所有的动物移动一个像素,因此,我们可以说,仿真正在运行。CreateInitialState 方法应生成几个随机分布的动物及捕食者。我们最终实现运行这个仿真的机械之后,再回到这些方法。

    接下来,我们需要绘制仿真状态的能力。在 C# 中,我们把这部分放在 MainForm 类;在 F# 中,窗体是全局值,所以,可以把绘制代码实现为一个简单的函数。该操作将在当前仿真状态,遍历所有的动物和捕食者,使用 System.Drawing 类把它们绘制在窗体上。C# 方法有这样的签名:

 

bool DrawState(Simulation state);;

 

    在这里,我们不会提供的完整代码,但现在,当我们调用时,你就知道了。有关这个方法,一个值得注意的事情是,它返回布尔值,表示窗体是否仍可见。我们不久将使用此结果,停止这个仿真,每当用户关闭窗体时。

    到止,我们已经有了运行仿真所需要的一切,虽然还没有为动物和捕食者的运动实现任何有趣的算法。让我们把这一切放在一起,在开始使动物的行为更具智能化之前,可以测试它。

 

运行仿真

 

    我们会运行这个仿真,尽计算机所能够的快,所以,我们会将它实现为一个循环,运行 Step 方法、重绘窗体,然后,再次启动,直至窗体关闭为止。我们不想阻塞应用程序主线程,因为,这会使应用程序没有响应,因此,我们会把仿真作为后台进程运行。F# 和 C# 版本以不同的方式实现,所以,两个都 要看一下。清单 14.22 显示 C# 代码,显式创建一个线程。

 

Listing 14.22 Running simulation on a thread (C#)

 

private void MainForm_Load(object sender, EventArgs e) {
  var th = new Thread(() => {
    var state = Simulation.CreateInitialState();
    bool running = true;
    while(running) {
      state = state.Step();
      running = (bool)this.Invoke(new Func<bool>(() =>
        DrawState(state)));
    }
  });
  th.Start();
}

 

    这个方法只是仿真的 C# 版本的一部分,需要使用可变的状态。特别是,我们创建了一个持有仿真的当前状态的变量,另一个变量,用于确定仿真窗体是否仍然打开。我们在一个 while 循环中,在一个线程上运行仿真,然后计算新的状态。我们在每次迭代时,保存这个状态在相同的本地变量中,并更新窗体。在 C# 中,我们不能写没有变量的代码,但在 F# 中不需要。

    另一个值得注意的是,更新窗体的方式。在 Windows 窗体中,只能从图形用户界面的主线程中访问控件。我们使用 Invoke 方法,它取一个委托,并在图形用户界面线程上运行。我们前面说过,DrawState 方法返回一个标志,表示窗体是否仍然打开,所以,打这个方法调用打包到 Func<bool> 委托中。当它返回一个布尔值的结果时,更新 running 标志。

    在 F# 版本中,不要显式使用线程,因为可以使用异步工作流,启动仿真。另外,可以使用递归,来替换变量和命令式的 while 循环,如下所示:

 

Listing 14.23 Running simulation using recursion and async (F#)

 

let rec runSimulation(state:Simulation) =
  let running = form.Invoke(new Func<bool>(fun () –>
    drawState(state))) :?> bool
  if (running) then
    runSimulation(state.Step())

Async.Start(async {
    runSimulation(Simulation.CreateInitialState())
  })

 

  运行仿真的循环被实现成递归函数。我们不必担心运行超出堆栈空间,因为,这个递归调用是尾递归。在 C# 中,尾递归的缺乏是唯一阻止我们在这里使用相同的技术。

    这个函数是一个普通的函数,以阻塞的方式循环,使窗体可见,但我们仍可以使用异步工作流,在后台启动它。这与典型的异步编程不相关,我们在前一章讨论过。Async.Start 以简单方式,在单独的线程中启动函数的执行。工作流调用递归函数,它阻塞这个线程,并运行仿真循环。

    如果你已经实现了 Step 方法,并添加了绘制仿真的代码,现在,应用程序应该可以工作了。现在,我们已经有了适当的框架,可以继续对动物和捕食者行为进行智能化,但首先,我们需要几个辅助函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值