linux下的corewars模拟环境pmars

想写点关于corewars的东西。
好吧,让我们来玩一下也好。


环境介绍与安装
有关背景之类的东西就不在这儿贴了,先跳一下:en.wikipedia.org/wiki/Corewars。
就像wiki介绍的那样,corewars是运行在mars机上的一个游戏,所以我们要模拟一个mars环境,如果在linux下,这个环境模拟软件就是pmars,可以在koth.org找到最新的版本。
下载下来后是pmars-x.x.x.tar.gz或者其它这个文件名样子的文件,解压后就是pmars-x.x.x的文件夹了。
这是一个比较小巧的软件,所以也没有一般的程序安装起来那么麻烦,不用./configure,也不用make install,只要一个make就可以在当前文件夹下生成一个pmars的可执行文件。可以把它加到path以方便使用。
可不能高兴的太早,因为这个文件是不会显示两个warrior战斗时的状态的,也就是没有图形。我们要给它装上图形功能。找到src文件夹下的config.h文件,在文件刚开始的部分注释了一块GRAPHX宏的定义,好了,我们把注释符号取消,再次编译就可以支持图形了。
还有几个目录要说明的,doc目录下面是documents,config下面是一些macro键定义或是option文件,warriors是一些示例warrior所在的目录。


pmars的调试环境
学一门语言大约主要分两个阶段,学习它和使用它。而对于汇编学习的无外就是各条指令的作用,死定的理所当然的东西不是靠记忆的,把这些东西证明出来就好了。
先来假设我们使用的是最常见的mars环境,它有8000个内存单元,而且首尾相连,形成一个环状。
我们先来证明这一点吧,写一个文件test.red,只写入一条语句就好了,
mov 0, 1 ; 其实这就是著名的Imp战士,跟helloworld一样伟大
我们先来看一看它的地址是不是像传说的那样循环,
pmars -e test.red
其中的-e是打开调试开关,也就是告诉pmars我们要进入debug模式,执行这条语句后程序会带领我们进入一个窗口,可以在出现的(cdb)后输入调试命令。
我们主要用到的调试命令也就有两三个:
list 列出内存的redcode代码,如果不指定内存,它指的就是当前内存。
输入list后我们可以看到类似于 00000 MOV.I $ 0, $ 1 这样的内容,而list 1的结果则是空的,也就是 00001,实际上pmars在初始化时会把所有内存初始化成dat 0, 0,也就是一些值为0的数据,而list是不会显示dat 0, 0。
step 跟gdb里的step含意相同,单步中断。
输入step后程序就会执行将要执行的指令,并显示下一条要执行的指令,在图形上表示为不全的点。在imp程序里step的显示总是mov 0, 1,这是因为mov 0, 1的含意是把当前位置的指令复制一份到下一个内存单元,执行完的结果是
mov 0, 1
mov 0, 1 <-- 这是将要执行的指令,由上一条指令执行复制而来
因此imp程序的效果就出来了,不断复制自身到下一个单元。
由于mars环境是一个环状结构,所以imp会一直快乐的执行下去,直到指令遭到破坏。
quit 即退出调试环境了。


指令集
我们知道不同的cpu会支持不同的指令集,redcode也一样,ICWS-88支持10种操作,ICWS-94支持18种。我们来看看94年修订版的14条指令吧:
数据定义 dat
数据移动 mov
运算指令 add sub mul div mod
流程控制 cmp jmp jmz jmn djn slt(skip if less than)
多线程 spl
一个很重要的概念是dat(定义数据),在redcode里数据是不可以执行的,所以程序如果执行到dat就会死掉,相当于被kill掉,所以有人也把dat叫做中止进程的指令。
数据移动和运算指令的用法就像我们猜想的一样,跟intel汇编也差不了多少,但这里要注意的是,redcode里有指令修饰符的概念,也就是修饰指令的状态。这个我们将在下面讲解。
还有两个有点儿陌生,slt和spl。我们来看一段代码吧:
        org start
        mov 0, 1
start   slt #2, #3 ; 检测2<3是否正确,如果正确,就跳过下一条指令
        dat #0, #0 ; 被跳过,因此程序不会被杀死
        mov -3, 3 ; 将-3地址的指令复制到3的地址,这里的地址都是相对地址
        spl 2 ; 生成一个新线程,地址为2,也就是上条指令复制的目的地址
        jmp start ; 跳到start
org跟start的作用很容易可以推测出来,一个是规定开始地址,一个是定义一个label,注意这里start后面没":"。
程式会在每个start循环里启动一个新的imp任务,状态就像我们的生产线一样,定期产生产品,不断被送到远处。当然这个程序是没有实际意义的,只为说明两条指令的用法。
这里要为"任务"说明一下,跟电脑线程有所区别,并不是任务越多执行就会越快。pmars虚拟机的任务管理器是一个队列结构,执行一个操作就会把这个对象的下一个操作放到队尾,因此每个目标的执行速度是相同的。还有一个要注意的就是,只要多任务中一个任务被杀死了,整个warrior也就算是被杀死。


指令修饰符
上面我们已提到指令修饰符的概念,如mov 0, 1实际上就是mov.i $0, $1的简写,其中的i就是修饰符了,我们来看看还有哪些修饰符。
修饰符又可以分两种:
移动方向控制:mov.a mov.b mov.ab mov.ba mov.f mov.x (mov.ab表示数据从a移动到b,mov.b、mov.ab、mov.ba可类比得到,mov.f中f为full,即ab均移动到目的的ab,mov.x可以念作叉,对,它的意思就是交叉,想不到corewars作者对汉语也有所了解,果然神人也:-))
    mov.x $1, $2 ; 执行完后,地址为2的指令变为dat #2, #1
    dat   #1, #2
    dat   #3, #4
孤单单的一种:mov.i (意思是不想执行数据复制,而是复制整条指令)
看吧,指令修饰符也不是很复杂。


内存寻址方式
如果你是在认真地看这篇文章,可能已经很不耐烦了,因为我也感觉有种隐瞒真相的罪恶感。有太多$#之类都没有很好地得到解释,我自己都感觉说不过去了。
内存的寻址方式大约分五种:
1、立即数寻址:#
2、直接寻址:$(可以省略)
3、间接寻址:A:* B:@
4、预减1间接寻址:A:{ B:<
5、后加1间接寻址:A:} B:>
来看两段代码吧:
mov 1, {1       mov 1, }1
dat #2, 0       dat #2, 0
{1是先拿到1处A数据的值,然后将它减1,得到的地址便是目的地址了,注意它是相对于间接地址指向的地址,在此处是相对于dat指令所在的地址。注意副本是减1后的结果。
}1是先拿到1处A数据的值,把它作为目的地址,完成移动后再把数据加1。这里的地址仍然是相对于dat指令所在的地址。


煮个栗子
前面我们已经看到了imp的例子,再来看一个drawf战士吧。
start   add #4, 3
throw   mov 2, @2
        jmp -2
bomb    dat #0, #0
程序共有四条指令,第一条指令每次增加投掷炸弹的距离,第二条指令扔出炸弹,第三条指令跳回起始处继续上次的动作,第四条指令保存炸弹距离。很简单有效的战士,不是吗?
koth.org还有很多资料可以看,欢迎光临。

你可能感兴趣的:(linux下的corewars模拟环境pmars)