我最近在Android电子市场发表了一个人机对弈游戏:五福。这是我小时候常玩的一个游戏。
我采用了Minimax算法。这个算法实现起来相当直接。难点在于怎样快速地找出最佳走法,也就是怎样才能尽可能先评估最佳走法,以及如何优化评估函数(evaluation function)。
计算一步棋可能需要很长的时间,所以我们不能主线程做计算。我采用了AsyncTask类。AsyncTask类的通常用法是在doInBackground()函数中进行耗时的运算,然后在onPostExecute()中,把结果通知相应的活动(activity)。问题在于当onPostExecute()被调用时,那个活动有可能已经被杀掉了。例如Android会在屏幕改变方向时杀掉并重建正在显示的活动。如果你在此时调用活动的函数,程序就会死机。
为此,我采用了如下方法。写一个名为WufuApplication的类并继承Application类。当活动的onResume()函数被调用时,把该活动注册到WufuApplication。当onPause()被调用时,把给活动从WufuApplication中注销。在计算完成,即AsyncTask类的onPostExecute()函数被调用时,调用WufuApplication的一个函数。如果当时有注册的活动,该函数直接把计算结果传给该活动。否则,先把结果记录下来。该活动的onResume()函数会在注册完后检查WufuApplication中是否存有计算结果。如果有,就取得该结果并做出相应的动作。这样我们就不用担心在AsyncTask类进行计算时,活动被重建的情况了。
另一个问题是如何模拟一个一个的提子。如果同时提掉所有应提的子,玩家就可能不知道哪些子被提了。我写了一个实现了Runnable接口的名为MoveAnimator的类,并利用android.os.Handler类。MoveAnimator维护一个简单的状态机。该状态机有4个状态:等待、运行、暂停和完成。初始状态为等待。当MoveAnimator的play()函数被调用时,把当前状态改为运行,并调用如下函数:
m_handler.removeCallbacks(this);
m_handler.postDelayed(this,INTERVAL); // Add MoveAnimator to the message queue.
其中,m_handler为android.os.Handler的一个实例。
在MoveAnimator的run()方法中,如果当前状态为运行,并且还有未完成的步,我们就模拟该步。如果还有未完成的步,就继续调用postDelayed()在消息队列中放入一个新的请求。
当相关的活动的onPause()被调用时,调用MoveAnimator的onPause()方法,以把当前状态设置为暂停。当相关的活动的onResume()被调用时,调用MoveAnimator的onResume ()方法,以把当前状态恢复为运行。这样在屏幕切换方向后,还能继续提子。