C++とアセンブラで8クイーン解いてみた
8クイーンの話題二度目。
それにしてもパズル好きだなあと思う。
でも今回は、アセンブラの練習がしたかったのが主な理由。
あと、今はじめて知ったのだが、プログラムコード記述用に「シンタックス・ハイライト」なるものがあって、自動的にプログラムを色づけして表示してくれるらしい。こんな便利なものがあったとは……
ともあれ、8クイーンの話。
まず、8クイーンを解くアルゴリズムは、以前の記事
http://d.hatena.ne.jp/g940425/20100505/1273051885
と同じく、このページ→http://www.ic-net.or.jp/home/takaken/nt/queen/index.htmlから引用しました。
まず、C++で書けばこう↓なる。
#include<iostream> using namespace std; int count=0; void search(int n,int l,int s,int r){ l|=n;l<<=1;l&=255; s|=n;s&=255; r|=n;r>>=1;r&=255; if(s==255){count++;return;} int c=255^(l|s|r); while(c){search(-c&c,l,s,r);c^=(-c&c);} } int main(){ search(0,0,0,0); cout<<count<<"\n"; return 0; }
実行すると
92
となる。
で、本題ですよ。
これと全く同じことをアセンブラでやりたいのだが、如何せんアセンブラ自体やりはじめなんで、関数への引数の受け渡しとかスタックの使い方とかが手探りだったので時間がかかってしょうがなかった。
で、できたプログラムが下のコード。
ほとんどC++版コードと同じ流れですが、次の段のクイーンの配置可能場所を生成するところと、正解かどうか判定するところが逆になってます。
bits 16 org 100h mov ch,0 mov ah,00000000b mov al,00000000b mov bh,00000000b mov bl,00000000b call search mov dl,ch call putt endquit: ;プログラム終了 mov ax,4Ch int 21h ;DLレジスタを出力 putc: push ax mov ah,02h int 21h; pop ax ret ;DLレジスタを10進数で表示 putt: push ax push bx mov ah,0 mov al,dl ;100の位 mov bl,64h div bl cmp al,0h jz decade push dx add al,30h mov dl,al sub al,30h call putc pop dx mov dl,ah ;10の位と1の位 decade: mov ah,0 mov al,dl mov bl,0Ah div bl add al,30h add ah,30h mov dl,al call putc mov dl,ah call putc pop bx pop ax ret ;DLの最下位の1をDLに取得 getmin: push ax push dx xor dl,11111111b ;11111111でマスクしたDLをALに保存 mov al,dl inc al pop dx and dl,al pop ax ret ;potitioning(AH,AL,BH,BL) ;AH:新規にクイーンを配置する位置 ;AL:クイーン左前方攻撃範囲 ;BH:クイーン前方攻撃範囲 ;BL:クイーン右前方攻撃範囲 ;の4つのデータから、 ;AL:新規クイーン配置後のクイーン左前方攻撃範囲 ;BH:新規クイーン配置後のクイーン前方攻撃範囲 ;BL:新規クイーン配置後のクイーン右前方攻撃範囲 ;DL:新規クイーン配置後のクイーン配置可能範囲 ;(1:配置可能、0:配置不可能) ;を生成する関数 potitioning: or al,ah or bh,ah or bl,ah shl al,1 shr bl,1 mov dl,al or dl,bh or dl,bl xor dl,11111111b ret ;クイーンの置ける位置を探索し、置いていく関数 ;8クイーンが完成したらCHをインクリメント ;深さ優先探索 search: call potitioning cmp bh,11111111b jnz begloop inc ch begloop: push ax push bx push dx call getmin cmp dl,00000000b jz endloop mov ah,dl call search pop dx xor dl,ah pop bx pop ax jmp begloop endloop: pop dx pop bx pop ax ret ;改行を出力 newline: push dx mov dl,0dh call putc mov dl,0ah call putc pop dx ret
実行すると
92
となる。成功。
せっかく作ったんで、次回は速度比較でもしようと思います。
アセンブラの方はスタック以外のメモリ使ってないから速そうだしね。
あ、それと小ネタ(?)を一つ
「C++でならどこまで短いコード書けるだろうか?」と思っていろいろやってみたところ、
#include<iostream> int t=0;f(int n,int l,int s,int r){s|=n;l|=n<<1;r|=n>>1;if(s==255)t++;int c=~(l|s|r)&255;while(c){f(n=-c&c,l<<1&255,s,r>>1&255);c^=n;}}main(){f(0,0,0,0);std::cout<<t<<"\n";}
の193バイトが最高でした。見づらいけど。
ではまた(・ω・)ノシ