【VB.NET】打造一个象棋魔法学校的老师——谨以此文献给象棋爱好者——如何实现与引擎的通讯
如文章题目所写,仅给象棋爱好者,如果你想牟利,Get out!如果你十窍通九窍就想牟利,Get out your fucking!
首先,我想推荐一个网站,http://www.elephantbase.net/,这是我所知道的最好的象棋和相关编程网站,虽然可能出于某些原因,近期未公开象棋巫师的源码,但所提供的资料非常完整,为推动象棋这一传统竞技项目的发展做出了不可磨灭的贡献。其实你完全可以参考象棋巫师的源码写出一个很好的程序,但鉴于大家都很忙,开源和免费还迫于你手头的银子而不能很好的进行,我这个业余爱好者再出一份力~~
对于与引擎通讯,勿疑是一种技术,多用匿名管道解决(如果你想获得VB.NET的匿名管道代码且不用于商业用途,可以联系我),而象棋巫师的这一部分是调用用一个C代码实现的DLL,其实VB6也好,VB.NET也好,都能自己实现(也许这是我偏爱VB的论调)。在这第一篇文章里面,我想介绍给大家VB.NET实现输入输出重定向的代码,以代替繁杂的API或者DLL调用。
VB.NET的Process类提供了丰富的内容——尽管有些时候我们还习惯于调用WIN32 API来解决我们面临的问题(特别是如我一样从VB6转到.NET的人),但在我们的问题的讨论过程中,也许你应该增加以下你对.NET FRAMEWORK的熟悉程度,Process类的StartInfo属性,提供了大量可用的功能,其中对我们有用的包括:
myProcess.StartInfo.FileName
myProcess.StartInfo.CreateNoWindow
myProcess.StartInfo.WorkingDirectory
myProcess.StartInfo.UseShellExecute
myProcess.StartInfo.RedirectStandardInput
myProcess.StartInfo.RedirectStandardOutput
它们提供了丰富的功能,完全能否辅助我们实现对一个控制台程序进行输入输出重定向以及其他一些功能。好了,废话少来,贴个代码也许大家更高兴——它也许成为你的积木大厦的一块地基。我在代码中进行了详细注释,你可以一步一步看下去,相信会对这个功能的实现有更深了解(当然想从API层面更深入了解,可以索取WIN32 API实现的该功能的代码,也可以反编译.NET FRAMEWORK的这一部分功能)。
Imports System.IO
Imports System.Threading
Public Class ReIntOutStream
Private myProcess As Process '子进程
Private myThread As Thread '循环线程
Private myStreamWriter As StreamWriter '子进程写入流
Private myStreamRead As StreamReader '子进程读取流
Event ReadLine(ByVal Info As String) '读到一行数据
''' <summary>
''' 创建子进程并重定向其输入、输出流以供程序处理
''' </summary>
''' <param name="ExePath">子进程所在目录(支持转义符),应以"\"结束;当进程位于系统环境变量所指示的目录时,可将此值设置为String.Empty。</param>
''' <param name="ExeName">子程序文件名</param>
''' <param name="CreateNoWindow">是否使用无窗口模式</param>
''' <param name="WorkingDirectory">是否设置子进程工作目录为ExePath参数所指定的目录,设置此目录可解决某些程序使用工作目录下文件的问题</param>
''' <remarks>要接收子进程返回的内容应处理PipeReadLine事件</remarks>
Sub New(ByVal ExePath As String, ByVal ExeName As String, Optional ByVal CreateNoWindow As Boolean = True, Optional ByVal WorkingDirectory As Boolean = True)
'定义子进程并设置其参数
myProcess = New Process() '创建一个进程
myProcess.StartInfo.FileName = ExePath & ExeName '要启动的程序,如果文件位于系统Path内,则第一个参数可设置为String.Empty
myProcess.StartInfo.CreateNoWindow = CreateNoWindow '创建无窗口模式,这要比创建隐藏窗口的代码运行起来更稳定
If WorkingDirectory Then '对于某些程序,它直接调用自身工作目录下的文件,如果不设置该值(为String.Empty)将出现错误
myProcess.StartInfo.WorkingDirectory = ExePath
End If
'允许重定义子进程输入输出流
myProcess.StartInfo.UseShellExecute = False '是否启动外壳程序,只有当不启动时,才可以重定向输入、输出、错误流
myProcess.StartInfo.RedirectStandardInput = True '允许重定向输入流
myProcess.StartInfo.RedirectStandardOutput = True '允许重定向输出流
myProcess.Start() '启动该进程
'重定义输入输出流
myStreamWriter = myProcess.StandardInput '重定义输入流,以用我们的写入流向其该进程输入内容
myStreamRead = myProcess.StandardOutput '重定义输出流,以用我们的读取流获得该进程输出内容
'启动新线程实现读取信息
myThread = New Thread(AddressOf ReadInfo) '为方便处理子进程返回的信息,循环读取以引发事件的方式来处理
myThread.Start()
End Sub
''' <summary>运行指定的命令</summary>
''' <param name="cmd">命令内容</param>
''' <remarks>命令内容无需以回车、换行或回车换行符结束</remarks>
Public Sub SendCommand(ByVal cmd As String)
myStreamWriter.WriteLine(cmd) '向写入流写入命令(无需加入回车、换行等即可自动执行),以使子进程执行
End Sub
'用一个循环读取子进程返回的内容并引发PipeReadLine事件以将其传递给调用者
Private Sub ReadInfo()
Do Until myProcess.HasExited '循环读取直到子进程退出时终止线程
Try '由于这个流不支持Peek方法,所以只能测试读回值是不是Nothing
Dim Info As String = myStreamRead.ReadLine()
If Info IsNot Nothing Then RaiseEvent ReadLine(Info)
Catch ex As Exception
MsgBox(ex.ToString, , "发生错误: " & Err.Number)
End Try
Loop
If myThread IsNot Nothing Then
myThread.Join(1000)
myThread = Nothing
End If
End Sub
End Class
好了,到这里,这个“管道”类已经实现了,你可以尝试用cmd.exe来创建你的test程序以测试这个类的功能。
建议你在看下一节内容之前,详细阅读http://www.elephantbase.net/上面关于UCCI和UCI引擎协议的详细说明,否则你将只能引用我的类来完成你的工作,而你,永远也不知道如何修改它来满足你的需要。