2009年10月30日 星期五

運動

 
拳拳服膺,動靜自然,舒適而有勁者,謂之拳。

好久沒動「身」了 >_<;

XOR

注意,本文禁止轉載!

今天畫油畫時,心血來潮,將衣帶上的花紋線水平地作 XOR 運算,
一條接一條的畫著,柔柔軟軟,效果不錯。
順便想到在玩電腦的時候,也幾度見到 Xor 的身影。

xor 是 Exclusive OR 的縮寫,
像 MUTEX 的 X 也是 Exclusive (mutual exclusive)
它是雙運算子函數,很多語言都有提供,如:

     X xor Y                 --> by VB
     X ^ Y == (X&Y)^(~X&~Y)  --> by C and C++ and C# and Java
     xor X,Y                 --> by Assembly
     ::operator^(X,Y)        --> by C++

(0). SSI(Small Scale Integration)中,
      常用 XOR+AND或OR gate 做出 universal gate..
   
(1). 由 True table 可看出它是個奇函數, 
     所以它可以驗證是否 binary 變數有奇數個1,比如:

      #define  hasOdd_1(n)  ((n)^0) 

(2). 可用來交換變數,像是:

      #define  swap(a,b)    (a)^=(b); (b)^=(a); (a)^=(b);
      
     或:
      #define  swap(a,b)    (a)^=(b)^=(a)^=(b);

(3). 將變數清成0: 

      #define  cls(n)       (_asm xor (n),(n))


在密碼學中,XOR 出現的次數可說是多到有濫用的嫌疑,隨便想就有:

(4). Vigenere 的 autokey cryptSystem, 
      有 plaintext 與 key 具相同字元頻率分布的問題,
      AT&T 的 Gilbert Vernam 在 1918 年說:
      "令 Ci = Pi^Ki 就行了!"
      Ci = CryptText 的第 i 個 bit
      Pi = PlainText 的第 i 個 bit
      Ki = key  的第 i 個 bit
      (註: 後來大兵 Joseph Mauborgane 將之改良成著名的 one-time pad
       架構, 我私下把它叫做 discard pad: 用完即可丟架構)

(5). 將 Block cipher 轉成 Stream cipher:
      1945 年,偉大的 Claude Shannon 提出 product cipher 
      (乘積加密法) 的兩大建構方式, 
      一個是 confusion (混淆,相con-,融合fusion)
      一個是 diffusion (擴散,非diff-,融合fusion)
      diffusion 就是讓一個明文位元可影響到多個密文位元,
      最簡單的方式為:

           bool C[64], P[64], IV;    //P[i]=第i個明文位元 
           cin >> IV;                //C[i]=第i個密文位元
           while (cin >> C[i]);      //IV=Initial Vector
           C[0] = P[0] ^ IV;
           for( i=1; i<n; i++)                      
                C[i] = C[i-1] ^ P[i];      
      
      ECB(Electronic Codebook Mode) 為應付大流量資料
      而提出的 CBC(Cipher Block Chaining Mode) 就是此類的應用。
      更進一步地, CFB(Cipher FeedBack Mode),
      可將 Block cipher 轉成 Stream cipher, 
      使得每加密完一個字元後,便可將它立即送出,
      這要如何辦到!? 就像這般:

           char C[64], P[64];        //P[i]=第i個明文字元
           int  IV; 
           cin >> IV;                //C[i]=第i個密文字元
           while (cin >> C[i]);      //IV=Initial Vector
           C[0] = P[0] ^ ((IV & 0xf000)>>24);
           send( C[0] );             //將 C[0] 送出
           IV <<= 8; 
           IV |= C[0]; 
           for( i=1; i< n; i++)
           {                      
                C[i] = P[i] ^ ((IV & 0xf000)>>24);
                send( C[i] );
                IV <<= 8; 
                IV |= C[i];                
           }
     解密時, 反著作就行了.

(6). 對於 DES 等的 Differential Attack (差異破解法):
      令DES明文區 binary block = Mi, 1<i<18
      Ki = key  的第 i 個 bit
      則 Mi+1 = Mi-1 ^ f(Mi,Ki)     ,i=1~16, f=加密函數,為已知
      再取得另一明文訊息 M',
      令 XOR difference (XOR差距) Xi = Mi ^ M'i
      則 X(i+1) = M(i-1) ^ M'(i+1)
       = [M(i-1) ^ f(Mi,Ki)] ^ f(M'i,Ki)
      
      假設 key 不變,輸入M的XOR difference是 X_in,
      其輸出的XOR difference是 X_out,
      而 X_in 導致 X_out 的機率很高,
      也就是 X(i-1) 與 X(i) 出現機率很高的話,
      則 X(i+1) 出現機率也會很高,
      咱便可比對多組輸入輸出的 XOR difference,求出 key。

Microcosmos

Microcosmos 剛出版時所寫的, 以前很會想東想西哪 :-)

在 <微觀宇宙> (Microcosmos) 一書中,Lynn Marguils 認為,
多細胞有機體的形成,乃是原始細菌 symbiosis(共生) 的結果。
故事是說,在固液氣三態的高能量邊界中,蛋白質自催化成 RNA,
RNA 衍生出 DNA,DNA 複製能力強,具放大效應,
可將微小化學反應的效果放大,製造出變種。
到了氧氣大浩劫時期,藍綠藻細胞跑到其他細胞中覓食時,
在宿主細胞核周圍形成細胞膜,產生有核細胞,
然後好氧的桿狀細菌,也跑進來找食物,產生 symbiosis 狀況,
2者相安並存,桿狀細菌演化成粒線體 (具相異DNA)。
接著,好動的螺旋菌也跑了進來,symbiosis 後,定下來,
演化成鞭毛或纖毛,之後,更神,細胞中的微小管,
演化成 axon (軸突) 或 dendrite (樹突),
導致了腦的發展...(聽聽就好)

這是生物小系統形成的一個例子。
搬到電腦上來講,也會發現相似的故事,
記憶體,如電容與放大器合作,產生變種,
(Not/And/Or/minority)gate,imp,adder,multiplexer,
demux,encoder,decoder。相異個體合作,
形成 CU,ALU,CPU,形成了演算核心,電線演化出 bus,
板鈕(console)演化成鍵盤,燈泡陣列(monitor)進化成螢幕,
方程式亦概念化為 program (程式)...

簡單構成複雜,繁密的功能只是概念的放大,
目前常見的電腦系統,如此運作。

Coding 行文風格

 
多年前寫的:

明清流行彈詞,如陳端生的<再生緣>:

少年早挂紫羅衣,美貌佳人作眾妻。畫戟橫挑胡虜懼,繡旗遠布姓名奇。
人間富貴榮華盡,膝下芝蘭玉樹齊。美滿良緣留妙跡,過百年,又歸正果上清虛。
雖然說,風流一世無惆悵,尚有余情未盡題。鄭氏如昭商客女,于歸謝府作偏妻。
德性溫柔無妒忌,仁心慷慨少嫌疑。
敬公姑,晨昏不缺飢寒禮﹔和姊妹,閨閣無爭大小儀。
如此為人真可羨,正應該,同膠似漆作夫妻。
偏懷身孕臨盆晚,謝玉輝,暗信讒言致見疑。
便令賢人懷抱恨,冤情雖白怨猶遺。若非生子如親父,一旦清明化作虛。
長齋一世修真性,得作仙宮執拂姬。雖則上天成正果,前生景況尚依依。
更兼美婦陳芳素,也得修行上太虛。玉皇封作焚香女,一點痴心未肯離。

又如陶貞懷的 <天雨花>:

公子便對家將道,那曉村中出歹人。我想你等人二十,個個精通武藝能。
何不此地停兩日,察訪捉拿作惡人。若得與他來除去,也與村中除禍根。
眾多家將聽此語,開言便叫大爺身。知他拐子何人物,對面相逢認不明。
搬柩回去多要緊,如何耽擱在鄉村。算來這等閑事件,大爺不必管他身。
公子聽了無言語,少時來了眾家人。安排飯食多停當,一齊擺在案中存。
服侍大爺來用罷,眾人俱各吃完成。便請大爺來上路,公子開言說事因。

讀來順暢無比,這是因其以 7~9 字,斷句為文,前後互對聲韻之故。
人腦的短期記憶體,其 cache memary buffer size,適合接收這樣的資料量。
所以我想,在提高程式碼可讀性這方面,也可將這種'短句'的行文風格納入考量。

-----------------------------
今之註:   簡鍊而能暢其意,妙筆天成也。   再歷千錘百鍊,終臻無懈可擊。  

Power-On Self Test

 
這是我 N 年前對 bootsect.S 的解析。
以前的文筆充滿年輕活力啊 XD
好似宋朝晏殊《蝶戀花》之「獨上高樓,望盡天涯路」。

感覺現在程式能力減退很多,
以前一天可以 trace 完上千行 Assembly,
外加轉譯 C++ 虛擬碼,再打上一堆註解,
現在看幾行組語就要想個半天 @@...
畢竟有好幾年時間不怎麼碰程式了.....

注意,本文禁止轉載!

// 注意! 文章中 [] 用於指標取值, 例如 [xx]=9 即 C 語言的 *xx=9
// 此相當於 MASM 的慣例寫法.
// 至於一般位址, 是用16進位表示, 如 FFF0 或 0xFFF0 或 FFF0h
// 或是合成形式: (segment:offset)
// 有時可能會和常數值搞混 (雖然藉由上下文仍可辨認出2者),
// 這是因為我找不到表示位址值的好方法, 譬如: #FFF0, *FFF0,
// (FFF0), <FFF0>, |FFF0|, $FFF0... 看起來都怪怪的..

//--------------------------------------------------------------------
// 揳子: <POST: Power-On Self Test>
//
// 開機後, power supply (電源供應器) 會先測試電流和電壓是否正常,
// 就像 <星界戰旗> 中 <拉菲爾> 的突擊艦巴斯羅伊魯號要啟動時,
// 會先做壓力平衡測試一般, 若沒問題, 就送出 power good 信號給
// CPU 中的 8284 計時晶片(clock generator), 使它不會繼續對 CPU
// 發出 reset 信號, 接著, 位於主機板上 BIOS ROM 中的 BIOS 程式
// 會被映射到記憶體的 FEOOO~FFFFF, 開機時, CS 的內容全為 1,
// 而 IP 的內容全為 0, 所以 CPU 會從位址 FFFF:0000 開始執行,
// 雖然由 FFFF0 到 FFFFF 只有區區 16 Byte (在 Real mode 下),
// 但已足夠我們放入一個 jump 指令 (一般是: jmp FE05B),
// 讓她跳到實際的 ROM BIOS 起始位址去工作.
//
// 由於各家 BIOS 廠商的 ROM BIOS 程式, 其功能, 大小與起始位址,
// 都不太一樣, 所以我們聰明的祖先, 便設計了一個由統一的進入點
// FFFF0, 跳到不同位址去執行的方式, 讓各家 BIOS 都能相容並存.
//
// BIOS 程式會依序檢測並初始化主機板上的: CPU, ROM BIOS, CMOS RAM,
// 8237(DMA), 8255(鍵盤控制器), 基本的64K RAM, 8259(中斷控制器),
// 8253(中斷計時器), Cache 控制器, 然後再依序檢測週邊設備:
// CMOS RAM, 6845 (CRT控制器), 64KB以上的RAM (若是暖開機則不測試它)
// reset 鍵盤, cassette bus, diskette(軟碟機), 硬碟控制器.
//
// 檢測週邊的方式是到 UMB (Upper Memory Block, A0000h~FFFFFh)
// 中, 找找看是否存在週邊設備的 ROM 起始檢測程式, 若有則執行之.
// 例如, 它會到到 C0000~C7800 去尋找顯示卡的 ROM 起始檢測程式,
// 執行它, 對顯示卡做初始化等等. 至於, 如何測出裝置或晶片功能
// 是否正常呢? 很簡單, 咱們可以先將一段資料寫進去, 然後再讀出來,
// 看看是否相同即可.
//
// 檢查完畢後, 它會嗶一下, 告訴你電腦硬體很健康, 一切平安.
// 就像盲腸手術後一定要'那個'一樣.
// 接著將位址 00472 填入 1234h, 表示已完成冷開機程序.
// BIOS 程式的最後一步是執行 INT 19, INT 19 俗稱:
//    1. bios-startup routine (BIOS啟動常式),
//    2. 或是叫 bootstrap routine (靴帶式/鞘式啟動常式),
//    3. 或是叫 reboot interrupt (重開機中斷, 供 warm boot 叫用)
//
// 其作用是將軟碟上的 "第0軌/第0磁頭/第1磁區" 的內容 (也就是
// 底下的 bootsect.s) 讀進 RAM 的 7C00h, 然後再跳到 7C00 去執行.
//
// 軟碟上的 <第0軌/第0磁頭/第1磁區>, 就是我們常常聽到的 boot
// sector (開機磁區), 相當於硬碟上由 fdisk 造出的 MBR (Master
// Boot Record, 主開機紀錄), 不過兩者的內容不太一樣,
// MBR 啟動程式所處理的雜事會多一些 (廢話..), 但兩者都以 55AA
// 作為結束標記. (當然地, 也有不是使用 55AA 的系統)
//
//--------------------------------------------------------------------
// <bootsect.s> 內容簡介:
//
//   1. 本程式一開始被 bios-startup 常式載入到位址 7C00h 去執行,
//      執行後, 它會將自己複製一份到位址 90000, 然後跳到那兒去執行
//
//   2. 將中斷向量 1Eh 所指涉的磁碟參數表複製一份到 9000h:4000h-12
//      將其內紀錄的每軌的磁區數增加到 36, 再將向量 1Eh 指向此新表,
//      並呼叫 ah=0,int 13h, 重置磁碟, 使設定能發揮功能.
//
//   3. 載入位於啟動磁區之後的 setup-sectors (啟動程式)
//      呼叫 ah=0,int 13h, 將其載入到 bootsect 之後 (90200h)
//
//   4. 讀取磁碟裝置參數, 如 sectors 與 track 的數目,
//      然後將系統核心載入到 0x10000
//
//   5. 選擇一個 root device (這是啥?)
//--------------------------------------------------------------------
// 虛擬碼如下:

    // bootsect.s 將被 bios-startup 常式載入到位址 0x7C00 ,
    // 之後他會將自己複製一份到位址 0x90000, 然後跳到那兒去執行.
    // 為何不直接從 7C00 開始執行就好呢? 我猜測, 因為有可能咱們
    // 選用硬碟開機, 此時 MBR 中紀錄著用來開機的磁區, 位於哪個分
    // 割區(partition)中. 於是, 我們將 MBR 的資料放到 7C00 上,
    // 然後 MBR 再將自己移到 90000 去執行, 它的動作是將設定用來
    // 開機的那個磁區, 放到 7C00 上去執行.
    // 如此, 我們可載入多個磁區資料, 到特定的執行點(7C00)上去執行,
    // 最後執行到的那個, 便是最正確恰當的開機磁區(boot sector)!
    // (因此, 只要載入不同的 loader, 便可實現多系統開機)

    INITSEG = 0x9000
_main:
    DS = 0x07C0
    ES = 0x9000
    for( SI=DI=0,CX=256; CX--;)
      [ES:DI++] = [DS:SI++]     //將自己拷貝到 0x9000
    jmpi go,INITSEG             //跳到 9000h, 位移為 go 的地方開始執行
go:                             //亦即 CS= 9000h, IP= OFFSET go
    DI = 0x4000-12              //4000 是我們隨便找的一個大於 bootsect
    DS = 0x9000                 //容量的位址值, 用來暫存磁碟參數表
    SS = 0x9000                 //將堆疊指向 0x9000:0x4000-12
    SP = 0x4000-12

    // 做完 POST 後, 中斷向量 1Eh 會指向磁碟參數表,
    // 由於大多數 BIOS 預設的磁碟參數表, 每軌的磁區數平均是 7 個
    // 這將造成編號大於 7 的磁區無法被讀取, 是故, 咱們將磁碟參數
    // 表 (共 11 Byte) 先複製一份到 9000h:4000h-12, 然後將參數
    // 表第 4 Byte 的內容 (紀錄每軌的磁區數) 調到 36,
    // 再將中斷向量 1Eh 指向這個新的磁碟參數表 (9000h:4000h-12)

    FS = DS = 0                 //將 FS:BX 指向磁碟參數表
    BX = 0x78                   //78h = 中斷向量 1Eh 的起始位址
    DS:SI = *BX                 //令 DS:SI = 磁碟參數表的 seg:ofs
    for( CL=6; CL--;)           //從 [0:78h] 拷貝資料到 9000h:4000h-12
       [ES:DI++] = [DS:SI++]
     DI = 0x4000-12
    DS = 0x9000
    [DI+4] = 36                 //設定磁區數
    [1Eh] = ES:DI               //將 1Eh 號中斷指向 9000:4000-12

    // 接著將位於 bootsect 之後的 setup-sectors (啟動程式)
    // 載到位址 0x90200

load_setup:
    DL = AH = 0                 //重設一下磁碟機
    int 13h                     //讓剛剛的設定能發揮作用
    DX = 0                      //磁碟機號碼=0, 磁頭號碼=0
    CL = 2                      //起始磁區號碼=2, 磁軌號碼=0
    BX = 200h                   //將資料讀到 ES:BX (=0x90200) 中
    AH = 2
    AL = 4                      //欲讀取的磁區數
    int 13h                     //讀取磁區資料!

    // 接著, 取得磁碟裝置參數 (如 sectors 與 track 的數目)
    // 由於沒有任何 BIOS 中斷能取得磁區數目, 所以底下就用 36, 18,
    // 15 這 3 個猜測值一一去測試是否能讀取到磁區, 若都失敗就用 9.

ok_load_setup:
    Byte disksizes= {36,18,15,9} //欲測試的磁區數大小
    SI = &disksizes
    for(;AX = [DS:SI++], SI<&disksizes[4];)
    {
      CX = AX
      DX = 0                     //drive 0, head 0
      ES = 9000h
      BX = 0A00h
      AX = 0201h                 //service 2, 1 sector
      int 13h                    //嘗試讀取磁區資料
    }
    print "\nLoading"

    // 接著將系統核心載入到 0x10000 (以 ES:BX 指向載入時
    // 的緩衝區), 並確保載入的資料不會跨越 64kB 的邊界

read_it:                         //載入系統核心
    AX = ES = 0x1000
    sread = 5
    BX = 0
    U2B sread = 0                //目前磁軌上可讀取的磁區數
    U2B head  = 0                //目前的磁頭編號
    U2B track = 0                //目前的磁軌編號

    while(1){
      #ifdef __BIG_KERNEL__
      #define CALL_HIGHLOAD_KLUDGE .word 0x1eff,0x220
        CALL_HIGHLOAD_KLUDGE     //this is within setup.S
      #else                      //但是 setup.S 在哪咧?
         AX = ES - 1000h
      #endif
      if( AX>8000h ) break;
      CX = sectors - sread       //= 尚未讀取的磁區數
      CX = CX*512 + BX           //= 目前已讀到的段內偏移位置 ES:BX
      if( CX >= 0x10000)
        AX = (-BX)>>9            //????
      call read_track            //傳入 es:bx=緩衝區, AL=欲讀取的磁區數
      CX = AX                    //CX = 剛剛讀入的磁區數
      if(AX==sectors)            //若已讀完目前磁軌上的磁區
        if(head==0) head=1;      //若目前位於第 0 磁頭, 則換到第1個
        else track++;            //已讀完目前磁軌上的磁區, 換下一軌
      sread += CX
      BX += CX<<9                //調整緩衝區偏移指標
      if( BX<65536) continue     //若 BX < 64K 則繼續重複讀取
      ES += 10                   //否則就位移到下一個 64K 的開頭
    }                            //並繼續重複讀取    
    goto kill_motor

read_track:
    print '.'
    DX = track                   //DX = 目前的磁軌編號
    CX = sread+1                 //CX = 欲讀取的起始磁區編號(從1開始算)
    CH = CL                      //CH = 磁軌編號
    DH = head                    //DH = 目前的磁頭編號
    DL = 0                       //DL = 0 (磁碟機編號 A)
    AH = 2
    int 13h
    ret

    // 然後關閉磁碟馬達
kill_motor:
    DX = 0x3f2
    AL = 0
    OUTB

    // 最後, 決定要使用哪個 root-device. 若已有指定好的, 就用它.
    // 否則就依據我們前面所猜測的磁區數目, 從 /dev/fd0H2880 (2,32)
    // 或 /dev/PS0 (2,28) 或 /dev/at0 (2,8) 中選出一個.

    if(root_dev==0)                    //若並未指定 root-device
      switch(sectors){
        case 15: root_dev = 208h; break;
        csae 18: root_dev = 1ch;  break;
        case 36: root_dev = 20h;  break;
        default: root_dev = 0;
      }

    // 至此, 一切都準備好了, 便跳到位於 bootblock 後的
    // setup-routine 去執行!

    jmpi 0,SETUPSEG


//------------------------------------------------------------------------
// This is the assembly source for the boot sector used by the
// Linux free operating system. If it looks unfamiliar for intel
// assembly, that's because it gets run through the standard C
// preprocessor (CPP) and has comments starting with '!' stripped off..

! bootsect.s 將被 bios-startup 常式載入到位址 0x7c00 處,
! 之後他會將自己複製一份到位址 0x90000 處, 然後跳到 0x90200
! 去執行它的起始(setup)程式. 並呼叫中斷將系統載到 0x10000 去.
!
! 由於目前系統的大小約 (508KB = 8*65536-4096) Bytes. 這樣的大小,
! 即使是在往後的日子裡, 也已經是夠用的了! 且一個 OS 應該盡量維持
! 簡明易懂才是. 508 kB 絕對夠用, 而且這個 Liunx 核心不像 minix
! 那樣還包含了 buffer cache(快取緩衝區), 再說..她還是被壓縮過的呢.
!
! 這個 loader 已被盡可能的簡化了, 所以啦, 持續的讀取錯誤很容易就
! 會讓它死當, 然後必須煩勞你手動重開機. 本程式會盡可能一次讀取多
! 個磁軌, 以加速磁區資料的載入.

;#include <linux/config.h>     //for CONFIG_ROOT_RDONLY
;#include <asm/boot.h>

.text

SIZEBYTES = SIZEDISKB+511    ! round disk.b size for division
SETUPSECS = SIZEBYTES/512    ! 啟動磁區(setup sectors)數 (一般= 4)
BOOTSEG   = 0x07C0           ! 原始的 boot-sector 位址
INITSEG   = 0x9000           ! 我們會將啟動程式碼備份到此處
SETUPSEG  = INITSEG+0x20     ! 啟動程式從這開始執行
SYSSEG    = 0x1000           ! 系統會載入到 0x10000 (65536).
SYSSIZE   = 0x8000           ! 系統大小: 以 16 Byte 為單位做載入
ASK_VGA   = 0xfffd

! ROOT_DEV & SWAP_DEV are now written by "build".
ROOT_DEV = 0
SWAP_DEV = 0
#ifndef SVGA_MODE
# define SVGA_MODE ASK_VGA
#endif
#ifndef RAMDISK
# define RAMDISK 0
#endif
#ifndef CONFIG_ROOT_RDONLY
# define CONFIG_ROOT_RDONLY 1
#endif

! 本程序執行時, 如同一般程式, 需要一個程式進入點標示:
.globl    _main
_main:
    mov    ax,#BOOTSEG
    mov    ds,ax             ! DS= 0x07C0
    mov    ax,#INITSEG
    mov    es,ax             ! ES= 0x9000
    mov    cx,#256           ! 總共要移動 512 Bytes
    sub    si,si
    sub    di,di
    cld
    rep
    movsw                     ! 將自己拷貝到 0x9000
    jmpi    go,INITSEG        ! 跳到 0x9000

   ! 目前 ax 和 es 的內容是 INITSEG
go:
        mov   di,#0x4000-12       ! 0x4000 是一個大於 bootsect + length
                                  ! of setup + room for stack 大小的任
                                  ! 意值, 12 則是磁碟參數表的大小 
                                  ! (事實上是11)
    mov    ds,ax             ! DS = 0x9000
    mov    ss,ax             ! put stack at INITSEG:0x4000-12.
    mov    sp,di

/* 大多數 BIOS 的磁碟參數表 (disk parameter tables) 預設的每軌磁
 * 區數太少 (一般平均是 7 個), 以致於程式無法讀取到超過預設最大值
 * 之後的磁區, 而且我們的 loader 不可能一次只讀取 1 個磁區, 那樣
 * 太慢了! 使用者會因載入時間太久, 而開始幹譙.
 * 所以我們的 loader 程式, 必須另外建一個磁碟參數表, 並將每軌磁區
 * 數提升到 36 (此為 ED 2.88 上所能遇到的最大值).
 * 此處, 值太大並不是壞事, 太低才是!
 */
 ! 目前 ds=es=ss=cs= INITSEG, fs = 0, gs 未使用, cx = 0

;;;    mov    fs,cx             ! 用 ;;; 所註解掉的是舊版的碼
    mov    bx,#0x78          ! 將 fs:bx(指標) 指向 parameter table
    push    ds
;;;    seg fs
    push    cx                ! contains 0 from  rep movsw above
    pop    ds                !
    lds    si,(bx)           ! 將 ds:si(變數值) 填入磁碟參數表的所在位址

    mov    cl,#6             ! copy 12 bytes
    cld                       !
    push    di                ! DI= 0x4000-12
    rep                       !
    movsw                     ! 從 [0:78h] 拷貝 12 Byte 到 9000h:4000h-12
    pop    di                ! DI= 0x4000-12
    pop    ds                ! DS= 0x9000
    movb    4(di),*36         ! 在磁碟參數表中的相關欄位, 設定磁區數為 36

;;;    seg fs
    push    ds                ! 此處將 1Eh 號中斷指向 9000:4000-12
    push    #0
    pop    ds
    mov    (bx),di           ! [78h] = 0x4000-12
;;;    seg fs
    mov    2(bx),es          ! [7Ah] = 0x9000
    pop    ds

! 由 bootblock 載入 setup-sectors. ES=0x9020, CX=0.

load_setup:
    xor    ah,ah             ! 做 I/O 前先重設一下磁碟機
    xor    dl,dl             ! 磁碟機號碼= 0
    int     0x13              ! 讓剛剛的設定能發揮作用
                                  ! 讀取磁區資料:
    xor    dx, dx            ! 磁碟機號碼(drive)=0, 磁頭號碼(head)=0
    mov    cl,#0x02          ! 起始磁區號碼(sector)=2, 磁軌號碼(track)=0
    mov    bx,#0x0200        ! 將資料讀到 ES:BX (=0x90200) 中
    mov    ah,#0x02          !
    mov    al,setup_sects    ! nr of sectors(欲讀取的磁區數, 一般=4)
    int    0x13              ! 讀取磁區資料!
    jnc    ok_load_setup     ! ok - continue

    push    ax                ! dump error code
    call    print_nl
    mov    bp, sp
    call    print_hex
    pop    ax
    jmp    load_setup        ! 再試一次

ok_load_setup:

   ! 取得磁碟裝置參數 (如 sectors 與 track 的數目)
#if 0
    xor    dl,dl
    mov    ah,#0x08          ! AH=8 is get drive parameters
    int    0x13
    xor    ch,ch
#else
   ! 因為好像沒有任何 BIOS 中斷能取得磁區數目, 所以底下就用 36, 18,
   ! 15 這 3 個猜測值一一去測試是否能讀取到磁區, 若都失敗就用 9.

    mov    si,#disksizes     ! 欲測試的磁區數大小, 其內容={36,18,15,9}
probe_loop:
    lodsb                     ! AX = [DS:SI] = [9000h:#disksizes]
    cbw                       ! extend to word
    mov    sectors, ax
    cmp    si,#disksizes+4   ! disksizes+4 的內容是 9
    jae    got_sectors       ! if all else fails, try 9
    xchg    ax, cx            ! cx = track and sector
    xor    dx, dx            ! drive 0, head 0
    xor    bl, bl            !
    mov    bh,setup_sects    ! BH = 4
    inc    bh                ! ES:BX = 9000:0A00
    shl    bh,#1             ! address after setup (es = cs)
    mov    ax,#0x0201        ! service 2, 1 sector
    int    0x13              ! 讀取磁區資料
    jc    probe_loop           ! CF=1 表失敗, 則回頭嘗試其他值
#endif

got_sectors:

   ! Restore es
;;;    mov    ax,#INITSEG
;;;    mov    es,ax
   ! 秀出一些令人安心的訊息

    mov    ah,#0x03          ! read cursor pos
    xor    bh,bh
    int    0x10

    mov    cx,#9
    mov    bx,#0x0007        ! page 0, attribute 7 (normal)
    mov    bp,#msg1          !
    mov    ax,#0x1301        ! write string, move cursor
    int    0x10              ! 印出 "\nLoading"

   ! 接著將系統載入到 0x10000

    mov    ax,#SYSSEG
    mov    es,ax             ! segment of 0x10000
        call    read_it           ! 載入系統核心
    call    kill_motor        ! 關閉磁碟馬達
    call    print_nl          ! 換行

   ! 底下決定要使用哪個 root-device. 若已有指定好的, 就用它.
   ! 否則就從 /dev/fd0H2880 (2,32) 或 /dev/PS0 (2,28) 或 /dev/at0 (2,8),
   ! 中選出一個, 挑選的方式是依據我們前面所猜測的磁區數目, 選出最適用的.

    seg cs
    mov    ax,root_dev
    or    ax,ax
    jne    root_defined      ! 若 root_dev!=0 就跳到 root_defiend
    seg cs
    mov    bx,sectors
    mov    ax,#0x0208        ! /dev/ps0 - 1.2Mb
    cmp    bx,#15
    je    root_defined
    mov    al,#0x1c          ! /dev/PS0 - 1.44Mb
    cmp    bx,#18
    je    root_defined
    mov    al,#0x20          ! /dev/fd0H2880 - 2.88Mb
    cmp    bx,#36
    je    root_defined
    mov    al,#0             ! /dev/fd0 - autodetect
root_defined:
    seg cs
    mov    root_dev,ax

   ! 到此處, 一切都準備的差不多了, 我們便跳到位於 bootblock
   ! 之後的 setup-routine 去執行

    jmpi    0,SETUPSEG        ! 跳到 90200h 去執行

   ! 下面的程式碼會將系統載到位址 10000h, 並且確保載入的資料不會
   ! 跨越 64kB 的邊界, 並盡可能一次讀取多個磁軌, 以加速載入動作.
   !
   ! 輸入參數: es = 起始區段位址 (正常值為 0x1000)

sread:   .word 0                  ! 目前磁軌上可讀取的磁區數
head:    .word 0                  ! 目前的磁頭編號
track:   .word 0                  ! 目前的磁軌編號

read_it:
    mov   al,setup_sects      ! AL = 4
    inc   al                  ! AL = 5
    mov   sread,al            !
    mov   ax,es               ! AX = 1000h
    test  ax,#0x0fff          ! (1000h AND 0fffh) -> ZF=1
die:                              !
        jne die                   ! ES 必須位於 64K 邊界, 否則不給通過
    xor bx,bx                 ! bx is starting address within segment
rp_read:
#ifdef __BIG_KERNEL__
#define \                      ! 叫用far* bootsect_kludge(as86無法組譯這段碼)
        CALL_HIGHLOAD_KLUDGE .word 0x1eff,0x220
    CALL_HIGHLOAD_KLUDGE      ! this is within setup.S
#else
    mov ax,es
    sub ax,#SYSSEG            ! AX = ES - 1000h
#endif
    cmp ax,syssize            ! have we loaded all yet?
    jbe ok1_read              ! 若 AX<=8000h 則繼續讀取
    ret
ok1_read:
    mov ax,sectors            ! AX = 磁軌上的磁區數
    sub ax,sread              !
    mov cx,ax                 ! CX = 尚未讀取的磁區數
    shl cx,#9                 ! CX *= 512
    add cx,bx                 ! CX += 目前已讀到的段內偏移位置 ES:BX
    jnc ok2_read              !
    je  ok2_read              ! 若 CX<=64K 則繼續讀取
    xor ax,ax 
    sub ax,bx
    shr ax,#9
ok2_read:
    call read_track           ! 傳入 es:bx=緩衝區, AL=欲讀取的磁區數
    mov cx,ax                 ! CX = 已讀入的磁區數
    add ax,sread              ! AX = 目前磁軌上的磁區數
    cmp ax,sectors            !
    jne ok3_read              ! 若尚未讀完目前磁軌上的磁區, 則跳到 ok3_read
    mov ax,#1
    sub ax,head
    jne ok4_read              ! 若目前位於第 0 磁頭, 則跳到 ok4_read
    inc track                 ! 已讀完目前磁軌上的磁區, 換下一個
ok4_read:
    mov head,ax               ! 切換磁頭
    xor ax,ax                 ! AX = 0 (因為尚未開始讀取)
ok3_read:
    mov sread,ax              ! sread = 目前磁軌上的尚未讀取的磁區數
    shl cx,#9                 ! 算出之前讀入的資料量
    add bx,cx                 ! 調整緩衝區偏移指標
    jnc rp_read               ! 若 BX < 64K 則繼續重複讀取
    mov ax,es                 !
    add ah,#0x10              ! 否則將 ES +=10,
    mov es,ax                 ! 也就是位移到下一個 64K 的開頭
    xor bx,bx                 !
    jmp rp_read               ! 並繼續重複讀取

read_track:
    pusha
    pusha
    mov    ax, #0xe2e        ! 在螢幕上印出 '.'
    mov    bx, #7
     int    0x10
    popa

    mov    dx,track          ! DX = 目前的磁軌編號
    mov    cx,sread          !
    inc    cx                ! CX = 欲讀取的起始磁區編號 (從1開始算)
    mov    ch,dl             ! CH = 磁軌編號
    mov    dx,head           !
    mov    dh,dl             ! DH = 目前的磁頭編號
    and    dx,#0x0100        ! DL = 0 (磁碟機編號 A)
    mov    ah,#2

    push    dx                ! save for error dump
    push    cx
    push    bx
    push    ax

    int    0x13              ! 載入磁區
    jc    bad_rt            ! 成功了嗎?
    add    sp, #8            ! 取消上面推入的 DX,CX,BX,AX
    popa
    ret

bad_rt:
   push    ax                        ! save error code
    call print_all            ! ah = error, al = read
    xor ah,ah
    xor dl,dl
    int 0x13
    add sp, #10
    popa
    jmp read_track

/* print_all 用於除錯, 他可被副程式叫用, 印出所有的暫存器內容,
 * 當叫用她時, 堆疊內應具有如下的結構:
 *   dx
 *   cx
 *   bx
 *   ax
 *   error
 *   ret <- sp
*/

print_all:
    mov    cx, #5            ! error code + 4 registers
    mov    bp, sp
print_loop:
    push    cx                ! 保存剩餘的計數值
    call    print_nl          ! 換行顯示, 以加強可讀性
    cmp    cl, #5
    jae    no_reg            ! see if register name is needed

    mov    ax, #0xe05+'A-1   ! 依序印 D,C,B,A
    sub    al, cl
    int    0x10
    mov    al, #'X
    int    0x10
    mov    al, #':
    int    0x10
no_reg:
    add    bp, #2            ! next register
    call    print_hex         ! print it
    pop    cx
    loop    print_loop
    ret

print_nl:
    mov    ax, #0xe0d        ! CR
    int    0x10
    mov    al, #0xa          ! LF
    int     0x10
    ret

// print_hex 用於除錯, 會以 16 進位數值印出 [ss:bp]

print_hex:
    mov    cx, #4            ! 4 位數的 hex
    mov    dx, (bp)          ! load word into dx
print_digit:
    rol    dx, #4            ! rotate so that lowest 4 bits are used
    mov    ax, #0xe0f        ! ah = request, al = mask for nybble
    and    al, dl
    add    al, #0x90         ! convert al to ASCII hex (four instructions)
    daa                       ! 轉成 10 進制
    adc    al, #0x40
    daa
    int    0x10
    loop    print_digit
    ret

// 這段程式可關閉軟碟機的馬達, 使得我們進入核心後,
// 其狀態是已知的. (若不關的話會怎樣呢?)

kill_motor:
    push dx
    mov dx,#0x3f2
    xor al, al
    outb
    pop dx
    ret

// 底下是變數群:

sectors:
    .word 0

disksizes:
    .byte 36,18,15,9

msg1:
    .byte 13,10
    .ascii "Loading"
    .byte 0

.org 497         //底下變數由本程式的 497 Bytes 偏移處開始存放
setup_sects:
    .byte SETUPSECS
root_flags:
    .word CONFIG_ROOT_RDONLY
syssize:
    .word SYSSIZE
swap_dev:
    .word SWAP_DEV
ram_size:
    .word RAMDISK
vid_mode:
    .word SVGA_MODE
root_dev:
    .word ROOT_DEV
boot_flag:
    .word 0xAA55

輸出重導

 
freopen 同時通用於 C 與 C++,進行 I/O stream 重導。
例如:
freopen ("in.txt", "r", stdin); freopen ("out.txt", "w", stdout); freopen ("err.txt", "w", stderr); ..... // reset: freopen ("CON", "r", stdin); //DOS freopen ("/dev/console", "r", stdin); //Linux
將 stdio 導入檔案,再導回螢幕,可製作出如 gotoxy() 般,
游標上下左右移的效果。

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;

struct Redirect
{
    FILE* f;
   ~Redirect() {fclose (stdout);}
    Redirect (char* path) {
        f = freopen (path, "w", stdout);
        if (!f) {
            puts ("redirect fail!"); 
            exit(~0);
        }
    }
} Redirect ("out.txt");

int main ()
{
    cout << "Hello, World!";
    puts ("\n redirection OK!");
    return system ("pause");
}
輸出至 out.txt 的內容:
freopen ("in.txt", "r", stdin); 請按任意鍵繼續 . . . Hello, World! redirection OK!

system (..) 會亂序,因為呼叫它會喚起 cmd 程序,
造成 main 與 cmd 兩個 thread 各自獨立輸出,
輸出結果則和 OS scheduling 有關。

坦克大戰

 


本來想在 512 行內寫完,但天不從人願,花了近 900 行。
編寫時沒遇上「許功蓋」,但在 VC9 上遭遇了三合字 (trigraph),
虛耗半天才 debug 出來,順便規避掉在 g++ 可能碰著的 digraph。

在設計遊戲時,首先要讓它以穩定的速度進行,
也就是讓 FPS 盡量維持在一定範圍,
在這段時間內,進行畫面更新和玩家輸入處理。
最簡單的計時器架構是:
     bool  bGameOver;                     //是否結束遊戲
     DWORD curr;                          //目前的時間點
     int   frequence = 50;                //更新率
  
     curr = timeGetTime();
     while (!bGameOver)   
         if (timeGetTime() - curr > 1000/frequence)
         {
             update();                    //畫面更新
             key_control();               //玩家輸入處理
             curr = timeGetTime();
         }
接著是秀圖,由於各平台的繪圖 API 不盡相同,
我們會把秀圖功能統一包裝在一個函式中,
然後在其中呼叫系統相關的繪圖函式,如 2D 方面有:
xlib、Glib、libXt、BGI、OpenGL、DirectX、GDI、
GDI+、Motif、LessTif、Qt/KDE、GTK+/Gnome.. 等。

圖會先畫在一塊 buffer 上,再根據玩家螢幕解析度進行縮放。
程式中使用 StretchDIBits 達成此目的。

處理輸入有許多方法,皆為 platform dependent,
如 Win32 上有 kbhit + getch,GetAsyncKeyState、或 DirectInput。
因為 Battle City 一次要分析的按鍵不多,
這裡便使用 GetAsyncKeyState 來讀取按鍵狀態。

好啦!閒話不多說,快把遊戲編出來玩吧 :-)

若是用 Dev C++ 編譯,要在 [專案選項> 參數> 連結器] 內
設定 -lwinmm、-lGDI32,並將 vsprintf_s 改成 vsprintf。
遊戲說明: 20 關,每回 25 隻敵人,不含音效資料,不含雪地地形, 吃越多星星能力越高,最後能在河面行走。 可雙人操作,按 Esc 退出遊戲。 P1 按 S、W、D、A 控制方向,H 發射子彈, P2 按方向鍵控制方向,Num0 發射子彈。

//坦克大戰雛形,daviddr, 2009, 7 天寫完。

#pragma comment (lib,"WINMM.LIB")
#undef UNICODE

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <time.h>

#define TankWar(o) m##o##n(){int daviddr(907);}\
        using namespace ImageSet;struct

extern"C" WINBASEAPI HWND WINAPI GetConsoleWindow();

enum {UP=0, RIGHT, DOWN, LEFT}; 
namespace Bonus {
    enum {LIFE, CLOCK, SHOVEL, BOMB, STAR, HELMET};
};
    
struct Image                                        //影像物件
{ 
    int  w, h, *data; 
    int& operator[] (int i)    {return data[i];}
    void create (int W, int H) {w=W; h=H; data = new int[w*h];}
    void free ()               {delete[] data; data=0;}

    void draw (const Image& in, int sx, int sy, int dir=UP)
    {        
        #define DRAW \
            if (0x8F8F8E^in.data[j]) data[i] = in.data[j];

        int i, j=0, x, y; 
        int W = in.w, H=in.h, sz = W*H, 
        int dw = w-W;
        int beg = sy*w+sx

        if (0==dir) for (i=beg; j<sz; i+=dw)
            for (x=0; x<W; ++x, ++i, ++j) {DRAW} else
        if (1==dir) for (i=beg, x=0; x<W; i+=dw, ++x)
            for (y=H-1; y>=0; --y, ++i) {j=y*W+x; DRAW} else
        if (2==dir) for (i=beg, y=H-1; y>=0; i+=dw, --y)
            for (x=0; x<W; ++x, ++i) {j=y*W+x; DRAW} 
        else for (i=beg, x=0; x<W; i+=dw, ++x)
            for (y=0; y<H; ++y, ++i) {j=y*W+x; DRAW} 
    }
    void draw2 (const Image& in, int sx, int sy, int c)
    {
        int i, j=0, x, sz = in.h*in.w, dw=w-in.w;
        for (i=sy*w+sx; j<sz; i+=dw)
            for (x=0; x<in.w; ++x, ++i) 
                if (in.data[j++]^0xC0C0C0) data[i] = c;
    }
};

namespace ImageSet
{
    enum {NUL=-1, BRICK, CONCRETE, TREE, RIVER, ROAD, HAWK, STONE, 
          STAR, SHIELD=STAR+4, SHIELD2, PLAYER, ENEMY=PLAYER+8, 
          EXPLODE=ENEMY+6, EXPLODE2, BONUS, BULLET=BONUS+6, PLAYER2,
          RENEMY=PLAYER2+8, YENEMY=RENEMY+6, GENEMY=YENEMY+2, HOLE=54};
    const int N_OBJ = 36+8+6+4+1;                       //物件數目
    int   palette[N_OBJ];
    Image obj[N_OBJ]; 
       
    char obj_pal[][4] = {                               //物件色盤索引
        {1,2,3},{3,4,5},{6,7,8,9},{10,11},{10,11},{1,0,3},      //5
        {2,0,3},{9,12},{9,12},{9,12},{9,12},{9,12},{9,12},      //12
        {9,13,14,15},{0},{0},{0},{0},{0},{0},{0},{9,16,4,17},   //21
        {0},{0},{0},{0},{0},{9,18,19,20},{21,9,22,12},{9,16,4,12},
        {0},{0},{0},{0},{0},{9,4},{9,6,23,24},{9,25,21,17}
    };
    char* hex_pal[] = { "202020",                       //色盤總表
        "990000","CC6600","868686","C0C0C0","E3E3E3","006600",  //6
        "008000","CCFF00","8F8F8E","0066FF","00FFFF","F1F1F1",  //12
        "A06000","FFA040","FFC080","003366","DDDDDD","800000",  //18
        "FF0000","FFFFFF","FF3300","800080","00AB47","B8F8D8","990099"
    };
    char* hex_img[] = {
    "3P2CP0ACP0AWP6P0EP0EW","GF0AS1AS1AS1AS1A5@6","`2@0P`1@P0P0P4R@1Q@0P1"
    "P@R@0@0TT@Q`Q@R`","4@17@60@1@22@0@170@51@2@0","6@0@51@42@1@06@2@31@0"
    "@2@6","OQKQ@QBRCQ@SBP0PAS@RBQBR@U@Q@UAP0W0PA@R0U0R@AS0Q0SAA[ABR@Q@RB"
    "FQFESECWCCQ@Q@QCO","OOC0JB1@PHB0@RGA0@TF@1@XB@0@ZA1@ZA0AWAQ@0@UAPAQ@"
    "0BREPA0DPEPA0N0N0N","***6@76@76@75B62H35B66@76@76@7****","*6@76@76@7"
    "6@75C54E40L14E45C56@76@76@76@7**","*6@76@76@75B65B63F40L13F45B65B66@"
    "76@76@7**","6@76@76@75B65B64D52I2O2I24D55B65B66@76@76@76@7","5C54@3@"
    "43@5@33@5@31A7A10@;@0@=@@=@@=@0@;@01A7A13@5@33@5@34@3@44@3@45C5","1A"
    "7A10@1@5@1@0@3A1A3@@5A5@0@;@01@9@11@9@12@7@22@7@21@9@11@9@10@;@0@5A5"
    "@@3A1A3@0@1@5@1@01A7A1","5`65`6`A2`3`A`P`2`3@QA`0`P`A1B`PbSBQAaPaSB`"
    "PaP`P@R@QAaP`P@RB`PbPAR@QA`@aRD`P`0D1@QPA7BPA7B","5`65`6`Q2`3`QA`2`3"
    "B`P`0`P`A1@QAbSD`PaPaS@QAaP`P@RB`PaP`P@R@QAbPARB`P`@aRBQA`0D1B`Q7@Q`"
    "Q7@Q","5`65`65`6`Q1@`@2`QAb@`F`QaP`PCQAPaREAP`P`P@PD`Q`P`P@PBQAP`P`P"
    "@PD`Q`QAPBQAPaRE`P`HQB7B","5`65`65`6AP1@`@2`A`Qa@`DQAPaP`PE`QaRCQ`Q`"
    "P`P@PBQAP`P`P@PD`Q`P`P@PBQAP`QAPD`QaRCQA`J`P@7@Q","4`Q55`6PA0`@`A1`A"
    "`Q@`@`EP@PaTD`P`QaSAP@P`Q`P@RB`P`Q`P@RAP@P`Q`P@RB`P`Q`P@RAP@PaQAPD`Q"
    "aRDP@QJ`Q7AP","4`Q55`6`A0`@`A1`@PR@`@`F`PaTCPQ`QaSB`P`Q`P@RAPQ`Q`P@R"
    "B`P`Q`P@RAPQ`Q`P@RB`PaQAPCPRaRE`QIPR7B","PA2`P2`A`PbP`P@R@P@QaP`P@Q@"
    "P@`QaP`P@Q@Q@QcRAP@`Q`UAQ@Q`PbQAP@`Q`P`Q@PAQ@Q`P`Q@PAP@`Q`QBPAQ@Q`UA"
    "P@`QFP@Q@PHPA`Q7@Q","PA2`P2`@P@PbP`P@RA`QaP`P@Q@Q@QaP`P@Q@P@`QcRAQ@Q"
    "`UAP@`Q`PbQAQ@Q`P`Q@PAP@`Q`P`Q@PAQ@Q`QBPAP@`Q`UAQ@QFP@P@`PHP@P@Q7@P@"
    ,"5`65`6`Q1P`@2`QAP0`P`A1PA`QaP`CRAPaRBPA`Q`Q@QARAP`PA`PAPA`Q`P@aPARA"
    "P`TAPA`QaRBRAP0`QA1PA`Q1B2R=","5`65`6AP1P`@2`A`Q0`P`A1RAPaP`CPA`QaRB"
    "RAP`Q@QAPA`Q`PA`PARAP`P@aPAPA`Q`TARAPaRBPA`Q0`QA1RAP1B2PA=","6`50A0a"
    "@`@Q0@00P@b@`@S00APa@`BP@02Q`P`PAP12P`T@P10AP`Q@Q@P@00P@P`PA`P@Q00AP"
    "`P@aP@P@02P`T@P10ARbAP@00P@PFQ00A0BPB0@00A0BPB0@0","6`50P@0a@`@Q0P00"
    "Ab@`@R@00APa@`BP@02Q`P`PAP12P`T@P10P@P`Q@Q@Q00AP`PA`P@P@00AP`P@aP@P@"
    "02P`T@P10P@RbAQ00APFP@00A0BPB0@00A0BPB0@0","AQ@aP@QA0`T`@T0AP`Q`@QB0"
    "`Q`Q`@`P@Q0APaP`@`C0`QeAQ0AP`Q@QC0`Q`PA`PAQ0AP`P@aPC0`Q`TAQ0AP`TC0`R"
    "DP@Q0APFPA0`Q2P2@Q0","`R@aP@S0AS`@RA0`Q`Q`@Q@Q0AP`Q`@`PB0`QaP`@`AQ0A"
    "PeC0`Q`Q@QAQ0AP`PA`PC0`Q`P@aPAQ0AP`TC0`Q`TAQ0AQDPB0`QFR0AP2P2B0","1@"
    "1@2`30`0@0`@0`@2`A`@0@`0`@0a@1@`Ab@`A02`Pb@Pa0@0@0@`P0PaA1@0@bR0@baA"
    "`0Q0aA01@a@R`A0@2`P`@0@`P`1C`PbP@a02`@`Aa0A`2A`@0@`2@1`1`0@0A2","`ON"
    "P`B`LQaF`@@P`BPD`R@PcQC`PAAQCa@bP@PbA`QA`PBBQA`Pb0b@a@`P@`QAPBBPAaQ0"
    "PiP@`PEE`PcPeQa@`P@aBCQhQcPbQaPACPc@`QcPeQaR@DaAd0c0cPa@`Q@Bb@b0b1a1"
    "`0`Pc@`P@APf2`0`2`0cPa@`P`A`Pa@b0@1`P1`0dP`@`RBP`@Pc0a0@0`0b0`P`@`Q`"
    "@BQ@Pc3`P2`0ePB@`@b@P`4b@`0eQaA@i1P@a3cPbQ@b0Pc0`0a0Pa0@`2eP@cPb0a3@"
    "1`0f@aP`PaP`0c1`1`3dPa@aPSaQ`1Q`2a1aQc@`@@Q@i0e0bPa@`P`DfPePh@`PCa@a"
    "@f@Q@fP`P`CbAQbPbAbP@aAPaCeQb@cPaQA`Q`@CPcP@c@bRaT`AA`AP`QARa@R@`0aQ"
    "`CB`AQEbBbQC`AAP@PD`PBfQ`AP@PA@`APF`BaQ`RCP@`@`I`DR@PHP","0lP`<``0K`"
    "`0D`Q0B``0@cR@0A``0@3R@0A``0B`QAP@0@``0A`TA0@``0@QcR@0``0@P`P5P0``0@"
    "0`0`0`0`@P0``0A0V0@``0B6A`PlP","0lP`<``0C`P`P0B``0CP2`P0@``0BS3@``0A"
    "PcP0B``0@Pa@bP0A``0@Pa@bP0A``0@Pb@aP0A``0@0PcP0B``0A0S0C``0B3D``0K`P"
    "lP","0lP`<``0G`B``0G`PA``0GR@``0F`2@``0B`A`0C``0AaP`0D``0@aP@P0D``0@"
    "`P@R0C``0@T0D``0@S0E``0@3F`PlP","0lP`<``0BbQ0B``0B`P@1P0A``0A`R@1P0@"
    "``0@`P@`P@P0P0@``0@R1P1P0@``0@`P@`P@P0P0@``0@R1P1P0@``0@`P@`P@P1A``0"
    "AP@1P0C``0B`Q0D``0B2E`PlP","0lP`<``0D`0D``0CaP0C``0CaP0C``0dQc0``0@R"
    "`P`R1``0APcQ1@``0AaQaP0A``0@P`Q0Q`P0@``0@`Q2Q`0@``0@P2A1P0@``0@1E1@`"
    "PlP","0lP`<``0K``0K``0BbQ0B``0AaT0A``0A`U0A``0AV0A``0@W0A``0@4S0@``0"
    "E4@``0K``0K`PlP","0@1B0B0B0"
    };
    char* lev_data[] = {
    "uuuk'g'g'g'g'g'i'g'g'g'g'g'i'g'g'g'g'g'i'g'g'g'g'g'i'g'g'7'g'g'i'g'g"
    "'7'g'g'i'g'g'g'g'g'i'g'o'g'i'g'o'g'q'g'ui'g'o'g)o)g'7g)o)g7o'g'ui+q'"
    "g'g+g'g'i'g'g'g'g'g'i'g'g'g'g'g'i'g'g'g'g'g'i'g'o'g'i'g'o'g'i'g'i'i'"
    "g'r&g&uk&g&p","k7k7u7k7q'g7k'g'g'i'g7k'g'g'i'm)g'7'i'm)g'7'm'o7q'o7k"
    "Gi'i7i'G'7Gi'i7i'G'7Ik'i7gGiIk'i7gGk+K7iG'i+K7iG'm7G'g'g'g'm7G'g'g'g"
    "'g7'g7g'g'k'g7'g7g'g'k'i'g'g+g'7'i'g'g+g'7'i'g'g+q'g'g+q's'g'i's'g'i"
    "'g'iwi+i'g'iwi+g","m'k'u'k'oK'ugK'o;'Kug'KuiKk'g,hKk'g,fM-g'h&gM+i'h"
    "&gMi'n&gMi'n&iGm;iGiGm;iGuiMg'g'oM(g)g,M(g)g&kMo'kMo'g)M'i6l)Kg'i6pK"
    "g)g6pKg)g6pKg7)kwi'k7)kwi'k","gIuGiIuGgIk'qKi/kIj2i7Gj4i7i5&n5&m(k+g"
    "&m&o)g&m&g6h6h(q&g6h6h(lWg'o(h[g'g)i(hYi5o5n5'm5'l5)o1o)g-g)gGg+g)g+"
    "gIg)m)gKuiI7GmwkI9GmwkI7","m)uk)uk'k;i7g'g'o7i7g'k'q7g'k'q'g+g)gYgW'"
    "g+g)gYgW'k'mWumWsYg[o'gYg[k)iW'g(j-iW'g(j)mWo7qWo7i[gWg7g'g6j[gWg7g'"
    "g6un6f)k)o6f)m/u'k)k+q)i)u'i'owumwq","p&g&fIuf&g&fIm&g6g&m&G&g&Gg&g6"
    "g&m&G&g&Gg&g6g&h'h&G&g&Gg&g6g&h'h&G&g&Gg'i'g7g'Gg'Gg'i'g7g'Gg'Gg'h&7"
    "g'g'6hIg'h&i'i6hIg'kG'Gi)i'kG'Gi)p&K&ug&K&n7k'K'f*9mKh*=kGk;m'gGg'o'"
    "i'k'o'i'k'o(j'g'j(Gg(u(Gi'sKuiKqwmIi'kwk'I","s9uuk=m7m7s7m7kGg;m7kGi"
    "9k7kG7k7k7kG7k7qG9k9oG9m7i7gG;g7o7gG;g7p6g9k9n6g9k9k6l7g;j6g6l7g;j6j"
    "8k9Gi7j8k9Gi7i7m7Gi9i7m7Gi9i;iGi7q7iGi7um7i7uk9qwq9mwq","i'i'k'o'i'g"
    "'g'kG+g'k(jG+g'g7g(jKi'g'g'h'fKm'l'fGeYgWGeYgWg'um'm)s'j07k'j*g'm'g'"
    "h*G'i'g'g'h*G'9'k7kMo7g7gMiYg_gag_g]Ih&ugIh&i)oI'g&k&k'gI'g&k&g7)gGg"
    "'g&n'g'gG7'g&r'swm'swi'k","k'qGo'o7Gi'qGf9h)o7Gf9h'mGf9h7Go7Gf9jGn9h"
    "7Gt9jGu7GumGukGgGgGgGqG7GgG7Gk7'h9i9h'9'h9i9h'7kG7GgG7GqGgGgGgGuum7k"
    "7m'j9i9j)j9i9j)iG7GgG7Gi)iGgGgGgGi'us's'm)iwi)m)iwi)i","uuul*q*i&g'q"
    "'g&g(i'gIg'j&f&k'gIg'j(k'M'j(k'M'j(k'G9G(h)k'G9G(h'ma)i+a)i+9'9+i+9'"
    "9+k)7g'g7*l)7g'g7*l5(l5(h'G+9-G)Gk9mG)UK)UK'iKkMkKkMm&jwk&p&jwk&j",
    "o7g'g)s7g'g)l.g'r.g'u&h'g)gKk&h'g)gKh&o7gMh&o7gMh&g+7)I'7h&g+7)Ig7g+"
    "7i'gIq7i'gIj,g7Oj,g7Oo7iO'm7iO'g7'gM7K'g7'gM7K'h(Om(g(Om(h'Im7+i'Io+"
    "kIo'h&kIo'h&kIiws'Iiwq","s+ui+m+o'm-g'i's'g'm)m'q)g_g(j'7g_g(j'qWg'g"
    "8f'k;Wg'g8f'i-[gW)i-[gW)o7WkW7q7WkWk[gY)gWk[gY)gWu'9g[q'k[g+ui+um'g9"
    "k)h&i'q)h(u'i)u'i'qwumwq","uug'k'o-k-i-k-i'm'm7i'm'm7i7g)k)g)g7g'o'g"
    ")g'g&fGg7gGf&g7'g'g&fG;Gf&g7'g'iOi7oOm'kOm'7iOi'g'7g&fG;Gf&g'g'7g&fG"
    "g7gGf&g'g)g'o'g7g)g)k)g7g'7m'm'g'7m'm'g/k+9/k+9)i'k'i'g)ug'g)mwq)mwq"
    ,"uuuiIk+kMi/iKj1jIj1jGk)G'G)q)G'G)q'I'I'q'I'I'kGi3iIi3iKi'G'G'iMi'G'"
    "G'iI[g/gag/g[n&f&f&f&f&uf&f&f&f&f&u&f&f&f&f&uf&f&f&f&f&o6f6f6s6f6f6g"
    "6f6f6s6f6f6f&f&f&u&f&f'f&f&u&f&f&6f6f6g6iwi6g6f6f7f6f6g6iwi6g6f6f6",
    "m)i'u)i'oI)k'oI)k'mU)kU)kG7'G+M'7Gg'G+M'7I'K7I'6f'gI'KgI'6f'iI'gM'g'"
    "iI'7M'g'i+iI*fIg+iI*fIf8)k)kGf6g)k'mGg'g'k'I(fGg'g'g7'gI(fGg'j*I'iGg"
    "'j(gI'iGg*g(IgG'Ig*g&gI'G'Ii'o'G'Gk'o'G'Gk'kwiKswiKg","uuum7G7ui7G7u"
    "kGgGuiGgG7sGmGsGmG'qIiGgGqIiGgG7oGgGgGiGoGgGgGiG'mGiGkImGiGkI7mGmMmG"
    "mM'mGiGgMmGiGgMg'oGiK7'oGiK7)oGgM)oGgM7)oGgK7)oGgK9)iwgGiI9)iwgGiI",
    "uug'o'k'g)iK)i'g)iK)i'i'g7Oi'i'g7OgK6f'i'MgK6f'i'MgQ)f'lQ)f'q6M'f'h7"
    "l6M'f'l-S1S)k)M6r)M6n+gK)g'i+gK)g'gK'Gm'g'gK'Gm'g'gO7g7k'gOo)g'Mm'k'"
    "Mm'k)6lwi'g'g)6lwi'g'g","u;Gug;Gi'q7i7i'q7i7g'G'k-g7g'G'k-g7i'G'i'gG"
    "'9i'G'i'gG'9k'gG7'Gg'o'gG7'Gg's7g'7)s7g'7)o)7'g7s)7'g7s'gG'7Gs'gG'7G"
    "o;Gg'i)k;Gg'i)k7g-i'9i7g-i'9i7i7o7)g7i7o7)gG;q'9G;q'9qwm9qwm9","g'g'"
    "g'g'g'g'i'g'g'g'g'g'i'g'g'g'g'g'i'g'g'g'g'g'i7g7g7g7g7g7uui'k'm'g'g'"
    "k'g'g)g+g'g+g)g'g'g'g'g'g'i7g7g7g7g7q7k7mIi'gGg'iMi'gGg'iQ)G)U'gGg'U"
    "UUQm'K'm'g'g'K'g'g'g'g'iGi'g'i'g'iGi'g'i'g'o'g'i'g'o'g'swumwq","kWg'"
    "i'g'oWg'i'g'ui'g7ui'g7oWi7g'g'oWg'7g'g'i7g'Wg7i'g'm'Wg7g'i'm'Wk's'Wk"
    "'q)Yg]g'i)Yg]g'uGgWg9k'kGgWk)f(g7KWk)f(g7KWg+h&i'KWg'l&i'KWg'q'gGgWg"
    "Gi7k'gGgWgGi'k+iKg'g7qKg'g'oWKg'g'oWKqwiWgGswiWgGg"
    };
    void set_image (char pal[4], char* code, Image& img, int w, int h)
    {
        img.create (w,h);
        int i,j,n; char c, *end = code + strlen (code);
        for (i=0; i<w*h && code < end; ++code) {
            c = *code; c = (c=='*'?'?':c=='+'?'\\':c)-'0';
            j = pal [c>>4];                     //顏色索引值 (0~3)
            n = (c&15) + 1;                     //重複次數 (1~16)
            while (n--) img[i++] = palette [j]; 
        }
    }
    void init_obj ()
    {
        int i, n = sizeof hex_pal/ sizeof*hex_pal;
        for (i=0; i<n; ++i)                     //設定調色盤
            sscanf (hex_pal[i], "%x", palette+i); i=0;
        #define SET_IMG(p,i,o,w,h) \
            set_image (obj_pal[p], hex_img[i], obj[o], w, h);
        for (;i <= ROAD;    ++i) SET_IMG (i,i,i,  8,  8);
        for (;i <= SHIELD2; ++i) SET_IMG (i,i,i, 16, 16);
        for (;i <= EXPLODE-1;++i)SET_IMG (i<21?PLAYER:ENEMY,i,i, 14,14);
        for (;i <= EXPLODE; ++i) SET_IMG (i,i,i, 14, 14);
        for (;i <= EXPLODE2;++i) SET_IMG (i,i,i, 32, 32);
        for (;i <= BULLET-1;++i) SET_IMG (BONUS,i,i, 15, 14);
        SET_IMG (i,i,i, 4, 4); 
        for (i=0; i<8; ++i) SET_IMG (36, i+PLAYER,i+PLAYER2,14, 14);
        for (i=0; i<6; ++i) SET_IMG (37, i+ENEMY, i+RENEMY, 14, 14);
        for (i=0; i<2; ++i) SET_IMG (13, i+ENEMY+4, i+YENEMY, 14, 14);
        for (i=0; i<2; ++i) SET_IMG (36, i+ENEMY+4, i+GENEMY, 14, 14);  
        obj[HOLE].create (4, 4);
        memset (obj[HOLE].data, 0x22, 4*4*4);
    }
    void release_obj () {for (int i=N_OBJ; i--;) obj[i].free();}
};

int TankWar(ai) Sprite                          //單元物件
{
    int   id;                                   //影像類型,可索引物件邊界
    int   x, y, dir, step;                      //位置, 移動方向, 步距
    int   type, life;                           //元素類型, 生命值
    int   maxMoveDelay, nMoveDelay;             //移動延遲, 移動計數
    bool  bActive;                              //是否在活動中

    bool move (int dir_)                        //傳回「是否移動了」
    {                                           //時間到才移動
        if (nMoveDelay-- > 0) return false;        
        nMoveDelay = maxMoveDelay;        
        dir = dir_;
        if (dir == UP   ) y -= step; else
        if (dir == DOWN ) y += step; else
        if (dir == LEFT ) x -= step; else
        if (dir == RIGHT) x += step;
        return true;
    }
    bool bCollide (const Sprite& o)             //碰撞判定
    {
        int x2 = x + obj[id].w;
        int y2 = y + obj[id].h;
        int X2 = o.x + obj[o.id].w;
        int Y2 = o.y + obj[o.id].h;
        return !(x2 < o.x || x > X2 || y2 < o.y || y > Y2);
    }
    void set (int ID, int X, int Y, int DIR, int STP, 
              int LF, int MD=0, int TYPE=0, bool ACT=true) {
        id=ID; x=X; y=Y; dir=DIR; step=STP; life=LF; type=TYPE;
        nMoveDelay = maxMoveDelay = MD; bActive = ACT;
    }
    Sprite() :bActive (false) {} 
};


struct Map
{
    enum   {W=13, H=13, W4=W*4, H4=H*4};
    enum   {MAX_EXPLODE = 64};
    Sprite explode [MAX_EXPLODE];               //爆破物件
    int    nMaxExplode;
    int    cid;                                 //撞到什麼東西    
    char   space[W4][H4];                       //記錄子區塊的元素類型
    int    mapX, mapY;                          //地圖在canvas中的起始座標 
    Image  canvas;                              //back buffer 
    Image  bg;                                  //背景畫面
    bool   bStone;                              //堡壘是否被破壞
    bool   bRiver1;                             //河流動畫
    bool   bProtected;                          //基地是否被水泥保護  
    int    riverChangeTime;
    int    nRiverChangeTime;
    int    protectTime;                         //基地被水泥保護的剩餘秒數
    
    Map(): riverChangeTime(10), protectTime(0),
           nRiverChangeTime(0), nMaxExplode(0)
    {mapX = 16; mapY = 18;} 

    void set_space_2x2 (int x, int y, int o) 
    {
        space [y][x  ] = space [y+1][x  ] = 
        space [y][x+1] = space [y+1][x+1] = o;
    }
    void draw_hawk (bool bStone_=false)
    {
        bStone = bStone_;
        set_space_2x2 (7*4-2, 13*4-2, HAWK);    //建主堡
        bg.draw (obj[bStone?STONE:HAWK], 6*16, 12*16);        
    }
    void setProtect (bool bProtect)             //設定基地是否被水泥保護
    {
        static struct {int x,y;} 
        p[] = {11,23,12,23,13,23,14,23,11,24,11,25,14,24,14,25};
        for (int o = bProtect? CONCRETE: BRICK, i=0; i<8; i++) {
            bg.draw (obj[o], p[i].x*8, p[i].y*8);
            set_space_2x2 (p[i].x*2, p[i].y*2, o);
        }
        if (bProtected = bProtect) protectTime = 360;        
    }

    bool bPass (Sprite& o)                      //測試物件是否發生碰撞
    {
        cid = ROAD;
        int x = o.x, W = obj[o.id].w; 
        int y = o.y, H = obj[o.id].h; 
        if (x<0 || y<0 || x>=bg.w-W || y>=bg.h-H) {
            cid = HOLE; return false;
        }
        int i,j,c, w = x+W-1, h = y+H-1;
        x/=4; y/=4; w/=4; h/=4;                 
        for (i=y; i<=h; ++i)
            for (j=x; j<=w; ++j) {
                c = space[i][j]; 
                if (c!= TREE && c!= ROAD) {
                    if (o.type > 15 && c==RIVER) continue;
                    cid = c;
                    return false;
                }
            }
        return true;
    }

    void draw_canvas (const Sprite& s)
    {
        canvas.draw (obj[s.id], mapX+s.x, mapY+s.y, s.dir);
    }
    void draw_river()
    {
        for (int o,x,y=0; y<H4; y+=2)
            for (x=0; x<W4; x+=2)
                if((o = space[y][x])==RIVER)
                    canvas.draw (obj[o+bRiver1], mapX+x*4, mapY+y*4);
    }
    void update()
    {
        if (protectTime > 0) {
            if (protectTime-- == 1) setProtect (false);
        }
        if (nRiverChangeTime++ > riverChangeTime) {
            nRiverChangeTime = 0;               //更新河流狀態
            bRiver1 = !bRiver1;
        }
        for (int o,x,y=0; y<H4; y+=2)
            for (x=0; x<W4; x+=2)               //重繪樹
                if ((o = space[y][x]) == TREE)
                    canvas.draw (obj[o], mapX+x*4, mapY+y*4);
        draw_explodes();                        //畫出爆破物
    }

    void addExplode (int id, int x, int y)
    {
        int i, j;
        for (i=0; i<MAX_EXPLODE; ++i) 
            if (!explode[i].bActive) {          //根據爆炸模式設定時間
                explode[i].set (id, x, y,0,0, id==EXPLODE? 4:12);    
                if (i > nMaxExplode) nMaxExplode = i;
                break;
            }
        for (j=nMaxExplode; !explode[j].bActive && j>i; --j); 
        nMaxExplode = j;                        //調整欲顯示的最大編號
    }
    void draw_explodes ()
    {
        Sprite *e;
        for (int i=0; i<= nMaxExplode; ++i) 
            if (explode[i].bActive) {
                e = explode + i;                //是否播放完畢
                if (--e->life == 0) 
                    e->bActive = false;                     
                else if (EXPLODE == e->id ||    //延遲大爆炸的播放
                    (EXPLODE2 == e->id && e->life < 8))                    
                    draw_canvas (explode[i]);
            }
    }
};
const int MAX_LEVEL = 20;                       //內定關卡數 
const int MAX_TANK = 27;                        //坦克集合的大小          
struct Tank; Tank *tanks = 0;                   //指向坦克集合 
Map*   map = 0;                                 //指向遊戲地圖

struct Depot                                    //彈藥庫 
{
    int MAX_BULLET;                             //最大彈藥數
    int nLimit;                                 //限制可存取的總彈藥量
    Sprite *b, *bullet;                         //庫藏彈藥 
    
    int getBullet ()                            //從庫中取出 1 顆子彈
    {                                           //若失敗, 傳回 -1
        for (int i=0; i<MAX_BULLET; ++i)        //若成功, 傳回子彈id
            if (bullet[i].bActive == false) 
                {bullet[i].bActive = true; return i;}
        return -1;
    }
    void processBullets ()                      //顯示所有作用中的子彈
    {
        for (int i=0; i<nLimit; i++)
            if ((b=bullet+i)->bActive) {   
                map->draw_canvas (*b);          //畫出子彈
                hitObject (*b);                 //是否撞到其他物件
            }
    }    
    bool hitObject (Sprite& b)
    {
        bool hit = false;
        int x,y, dx,dy, c, sx,sy, ex,ey; 
        int bx= b.x-6, by= b.y-6;               //(bx,by)= 邊界盒左上角
        int bW = map->bg.w, bH = map->bg.h; 

        b.move (b.dir);
        switch (b.dir) {
            case UP:  case DOWN:  sx=0, ex=16, sy=4, ey=12; break;
            case LEFT:case RIGHT: sx=4, ex=12, sy=0, ey=16; break;
        }
        for (dy=sy; dy<ey; dy+=4)               //檢測子彈邊界盒 4 邊角
            for (dx=sx; dx<ex; dx+=4) { 
                x = bx + dx;                    
                y = by + dy;                    //是否出界? 
                if (x<0 || y<0 || x>=bW-2 || y>=bH-2) {  
                    hit = true; 
                    b.life = 1; 
                    continue;
                }                
                x/=4; y/=4;                     //對應到space單位去索引
                c = map->space[y][x];             
                
                if (b.type==c || c==BRICK && b.type==CONCRETE) {
                    map->space[y][x] = ROAD;                    
                    map->bg.draw (obj[HOLE], x*4, y*4);
                }
                if (c==BRICK || c==CONCRETE) hit = true;
                else if (c==HAWK) {             //若打到堡壘
                    map->draw_hawk (true);      //便將它化成廢墟
                    map->addExplode (EXPLODE2, 11*8, 23*8);
                    hit = true;
                }                
            }                                   //若打中東西
        if (hit) checkExplode (b);              //便損耗子彈壽命
        return hit;
    }
    void checkExplode (Sprite& b)
    {
        if (--b.life <= 0) {                    
            b.bActive = false;   
            map->addExplode (EXPLODE, b.x-6, b.y-6);
        } 
    }    
    void setup (int nBullet, int nLimit_) 
    {
        nLimit = nLimit_;
        bullet = new Sprite [MAX_BULLET = nBullet];
        for (int i=0; i<MAX_BULLET; ++i) 
            bullet[i].bActive = false;
    } 
   ~Depot () {delete[] bullet;} 
};


struct Tank: Sprite
{
    bool  bMov2;                                //捲輪子
    int   cid;                                  //撞到什麼東西
    int   frame;
    int   bonus;                                //攜帶道具
    int   nAtkDelay, maxAtkDelay;               //攻擊時間間隔
    float nBegin, nFlick, nShield;              //各種特效的剩餘播放時間
    Depot *depot;                               //彈藥庫指標
   
    void setBonus()
    {
        int i = rand()%100; flick();
        bonus = BONUS + (i<10?0: i<30?1: i<50?2: i<62?3: i<85?4: 5);
    }
    void fire()
    {
        if (nAtkDelay > 0) return;              //時間到才做運算          
        nAtkDelay = maxAtkDelay;                //重新啟動射擊延遲
        int i = depot->getBullet();             //申請子彈
        if (i == -1) return;                    //彈藥已無庫存 
         
        int cw = obj[id].w, bX = x, bW = 4;     //子彈圖像的寬度
        int ch = obj[id].h, bY = y, bH = 4;     //子彈圖像的高度
        switch (dir) {                          //將子彈放在砲口方向
            case UP:    bX += (cw-bW)/2; bY -= bH; break;
            case LEFT:  bX -= bW; bY += (ch-bH)/2; break;
            case DOWN:  bX += (cw-bW)/2; bY += ch; break;
            case RIGHT: bX += cw; bY += (ch-bH)/2; break;
        }            
        int speed = 1, b_life = 1;
        int b_type = type>2? CONCRETE: BRICK;
        switch (type) {                         //在坦克物件中的
            case 0: speed = 3; break;           //type 意指子彈類型
            case 1: speed = 4; break;
            case 2: speed = 5; break;
            case 3: speed = 5; break;
            case 4: speed = 5; b_life = 2; break;
            case 5: case 6: speed = 5; b_life = 3; break;
            case 7: case 8: speed = 5; b_life = 4; break;
            case 9: case 10:speed = 6; b_life = 5; break;
            case 11:case 12:speed = 7; b_life = 5; break;
            case 13:case 14:speed = 7; b_life = 6; break;
            case 15:case 16:speed = 7; b_life = 7; break;
            case 17:case 18:speed = 7; b_life = 8; break;
        }                                       
        depot->bullet[i].set                    //設定子彈資訊
            (BULLET, bX, bY, dir, speed, b_life, 0, b_type, true); 
    }

    void move (int dir_)
    {
        cid = ROAD;
        if (nBegin > 0) return;
        int ox=x, oy=y, odir = dir;             //保存移動前的位置
        ++frame %= 4;
        if (0 == frame) bMov2 = !bMov2;
        Sprite::move (dir_);                       
        
        if (!map->bPass (*this)) {              //若和地圖元素發生碰撞
            if (dir != odir) {                  //若方向有變
                int dx = x%16, dy = y%16;       //調整位置使之與元素相貼    
                x = (x/16)*16 + (dx<6? 1: dx<13? 9: 17);
                y = (y/16)*16 + (dy<6? 1: dy<13? 9: 17);
                if (!map->bPass (*this)) 
                    x = ox, y = oy, cid = map->cid;
            }else x = ox, y = oy, cid = map->cid;
        }           
        Sprite s; s.id=ENEMY;                   //重疊移動用的暫時物件
        for (int i=0; i<MAX_TANK; ++i)          //是否和其他坦克發生碰撞
            if (this != &tanks[i]   && 
                tanks[i].bActive    &&
                tanks[i].nBegin <=0 &&
                bCollide (tanks[i])) {
                    s.x = ox; s.y = oy;         //檢驗移動前是否已碰撞
                    if (!tanks[i].bCollide(s))  //若非一開始便位置重疊
                        x = ox, y = oy,         //便進行碰撞處理 
                        cid = i>1? ENEMY: PLAYER;       
                }
    }
    void autoMove()
    {
        move (rand()%180==0? rand()%4: dir);
        if (cid != ROAD) {
            if ((cid == BRICK || cid == PLAYER) &&
                 rand()%3 > 0) fire(); 
            else dir = rand()%4;
        }
        else if (rand()%15 == 0) fire();
    }
    void draw ()                                //根據狀態描繪外貌
    {
        if (nAtkDelay > 0) nAtkDelay--;
        int    mx = x + map->mapX;
        int    my = y + map->mapY;
        Image* c  = &map->canvas; 

        if (nBegin > 0) 
            c->draw (obj[STAR + int(nBegin-=.7f) %4], mx, my);
        else {
            if (nFlick > 0 && bMov2) {          //閃紅光
                nFlick--;
                int i = id>ENEMY+6? 4: id-ENEMY;
                c->draw (obj[RENEMY + i + bMov2], mx, my, dir);
            }
            else c->draw (obj[id + bMov2], mx, my, dir);
            if (nShield > 0)
                c->draw (obj[SHIELD+(int(--nShield)%4<2)],mx,my);            
        }
    }    
    void setup (Depot& depot_, int atkDelay_) 
    {
        depot = &depot_;
        maxAtkDelay = atkDelay_;
        begin();
    }
    void checkExplode()
    {
        life--;
        if (id == GENEMY) id = YENEMY; else
        if (id == YENEMY) id = ENEMY+4; 
        if (life <= 0) {
            bActive = false;
            map->addExplode (EXPLODE2, x-8, y-8);
        }
    }
    void levelup()
    {
        if (type < 18) type++;
        if (type == 2) {
            int i = depot->nLimit++;
            depot->bullet[i].bActive = false;
        }
        int a = id-PLAYER, b = id-PLAYER2;
        if (0<=a && a<6 || 0<=b && b<6) id += 2;    //改變外型
    }                                   
    void begin()  {nBegin  = 28;}
    void flick()  {nFlick  = 360;}
    void shield() {nShield = 320;}
};

struct Game: Map
{    
    enum   {txtW=80, txtH=14};
    HBITMAP hBmp;
    HDC     hdc, hdcMem;  
    HFONT   font;
    Image   text;
    bool    bOver;                              //是否結束遊戲
    int     level;                              //目前關卡
    int     life[2];                            //玩家生命
    int     nEnemy, nActEnemy;                  //剩餘& 活動中 的坦克數
    int     enemyHoldTime;
    int     enemyCreateTime;  
    int     maxEnemyCreateTime;
    Tank    tank [MAX_TANK];                    //坦克集合: [0,1] 為我方
    Depot   depot[3];                           //我方2人與敵方彈藥庫
    Sprite  bonus;
    
    void setup_player (int i, bool bReset)
    {
        int type = bReset? 0: tank[i].type;
        int id   = bReset? (i? PLAYER2: PLAYER): tank[i].id;
        if (bReset) depot[i].nLimit = 1;
        tank[i].set (id, (i?7:4)*16+8+1, 12*16+1, UP, 2, 1, 0, type);
        tank[i].setup (depot[i], 5); tank[i].shield();
    }
    void create_enemy()
    {
        static struct {int x, y;} 
        p[] = {0,0, 16*6,0, 16*12,0, 16*12,16*5, 0,16*5};

        if (--enemyCreateTime > 0  ||
             nEnemy < 1 || nActEnemy >= level/4+4) return;

        enemyCreateTime = maxEnemyCreateTime;
        nActEnemy++;

        int i  = --nEnemy + 2;                      //t決定坦克類型
        int t  = rand()%(MAX_LEVEL+7)<=level?2: rand()%2;       
        int d  = t==0? 2:t==1? 3+(level>13): 1+level/6; 
        int l  = nEnemy% (level<15? 3: 5);          //出現位置
        int id = t<2? ENEMY+ t*2: GENEMY;

        tank[i].set (id, p[l].x+1, p[l].y+1, 
                     rand()%4, d, (t==2?3:1), 1);   //d = 移動速度

        d = (6-t) * (level>15? 4: level>10? 5: 6);  //攻擊時間間隔
        tank[i].setup (depot[2], d);   

        if (rand()%100 > 66) 
            tank[i].setBonus();                     //34%機率攜帶道具        
    }

    void init (int w=W*4, int h=H*2)
    {
        srand ((UINT)time(0));
        hdc    = GetDC (GetConsoleWindow());
        hdcMem = CreateCompatibleDC (hdc);
        hBmp   = CreateCompatibleBitmap (hdc, txtW, txtH);
        font   = CreateFont 
                 (10,0,0,0,FW_BOLD,0,0,0,0,0,0,0,0,"Courier");

        SelectObject (hdcMem, GetStockObject (LTGRAY_BRUSH));
        SetBkColor   (hdcMem, RGB(0xC0,0xC0,0xC0));
        SetTextColor (hdcMem, RGB(0,0,64));
        SelectObject (hdcMem, font);                //取入字型
        SelectObject (hdcMem, hBmp);
        SetBkMode    (hdc,    TRANSPARENT); 
        
        SMALL_RECT size = {0, 0, 79, 40};        
        HANDLE     hOut = GetStdHandle (STD_OUTPUT_HANDLE);
        CONSOLE_CURSOR_INFO cur = {100, 0};
        SetConsoleCursorInfo (hOut, &cur);          //關閉字標
        SetConsoleWindowInfo (hOut, TRUE, &size);   //變更視窗大小
        bg.create (w*4, h*8);                       //背景佔視窗的 1/4
        canvas.create (w*4+mapX*2, h*8+mapY*2+12);  //畫布要比背景大 
        memset (bg.data, 0x22, 4*bg.w*bg.h);        //用以容納邊界爆破
        init_obj();                                 //配置影像元素
        text.create (txtW, txtH);                   //配置文字面版
        map = this;                                 //設定全域性代理者
        tanks = tank;                               //指涉地圖與坦克集合
        depot[0].setup (16, 1);                     //配置我方彈藥庫
        depot[1].setup (16, 1);                     //配置我方彈藥庫
        depot[2].setup (64,64);                     //配置敵方彈藥庫
        life[0] = life[1] = 3;                      //初始生命值
        setup_player (0, true);                     //初始屬性        
        setup_player (1, true);  
        level = 0;
        new_level();                                //建立地圖
    }
    void release ()                                 //釋放資源
    {
        release_obj();
        DeleteObject (hBmp);
        DeleteDC (hdcMem);  
        DeleteDC (hdc);
        text.free();
        bg.free();
        canvas.free();
    }
    void new_level ()
    {
        char c, *code = lev_data [level];         //元素索引值 j=(0~4)
        int  x,y,n,i,j, k=0;
        int  len = strlen (code);

        for (i=0; i<len; ++i) {
            c = ((c=code[i])=='$'?'?':c=='%'?'\\':c)-'&';
            for (j = c>>4, n = (c&15)+1 ;n--; ++k) { 
                x = k%(W*2)*2;
                y = k/(H*2)*2;
                set_space_2x2 (x, y, j);
                if (j<2) bg.draw (obj[j], x*4, y*4);                
            }
        }                                       
        draw_hawk ();
        setProtect (false);
        level++;
        nActEnemy = 0;
        maxEnemyCreateTime = 132;
        nEnemy = MAX_TANK - 2;
        setup_player (0, false);        
        setup_player (1, false);        
    }
    void check_over () 
    {
        if (bStone || life[0]==0 && life[1]==0) 
            drawText (5*16, 7*16, "Game Over", 9);
        else if (nActEnemy + nEnemy == 0 &&
                 level == MAX_LEVEL) 
            drawText (5*16, 7*16, "You  Win!", 9);
    }
    void check_levelup()
    {
        static int c = 0; if(++c>255) c=0;
        if (nActEnemy + nEnemy == 0 && level < MAX_LEVEL) {     
            SetTextColor (hdc,  RGB(255,(c+55)%256,c));
            TextOut (hdc, 5*16*3+6, 7*16*3, 
                 "請按空白鍵進入下一關", 20);
            if (GetAsyncKeyState (VK_SPACE) >= 0) return;
            memset (bg.data, 0x22, 4*bg.w*bg.h);
            new_level();
        }
    }
    void create_bonus (int id)
    {
        bonus.id   = id;
        bonus.x    = rand()%(W-1) *16;
        bonus.y    = rand()%(H-1) *16;
        bonus.life = 280;
    }
    void draw_bonus()
    {
        if (bonus.life > 0) {
            bonus.life--; 
            draw_canvas (bonus);
        }
    }
    void colisionTest()
    {
        Depot *d, *d2; Tank* t; 
        int i,j,k,l;
        if (enemyHoldTime > 0) 
            enemyHoldTime--;
        for (i=0; i<MAX_TANK; ++i)                  //畫出坦克
            if ((t = tank+i)->bActive) {
                if (i>1 && t->nBegin<=0 && enemyHoldTime <= 0) 
                    t->autoMove();                  //移動敵方坦克
                t->draw();
                if (t->nBegin > 0) continue;        //閃爍時不做碰撞測試
                for (j=0; j<3; ++j)                 //處理子彈和坦克的碰撞
                    if (t->depot != (d = &depot[j])) 
                        for (k=0; k<d->nLimit; ++k) 
                            if (d->bullet[k].bActive &&
                                t->bCollide (d->bullet[k])) {
                                if (t->nShield > 0){//護盾狀態時吸收子彈
                                    if (--d->bullet[k].life <= 0) 
                                        d->bullet[k].bActive= false;
                                    continue;
                                }
                                d->checkExplode (d->bullet[k]);
                                t->checkExplode ();
                                if (t->life == 0 && i>1) {
                                    nActEnemy--;
                                    if (t->nFlick > 0) 
                                        create_bonus(t->bonus);
                                }
                            }
            }
        Sprite *b, *b2;
        for (j=0; j<3; ++j) {                       //子彈碰撞處理
            d = depot + j;
            d->processBullets ();                   //和景物的碰撞
            for (i=0; i<d->nLimit; ++i)             //和其他子彈的碰撞
                for (b= d->bullet+i, k=0; k<3; ++k)
                    if (k != j && b->bActive) 
                        for (d2= depot+k, l=0; l<d2->nLimit; ++l) {
                            b2 = d2->bullet + l;
                            if (b2->bActive && b->bCollide (*b2)) {
                                if (--b->life <= 0) b->bActive = false;
                                if (--b2->life<= 0) b2->bActive= false;
                            }
                        }
        }
    }
    void check_player()
    {
        for (int j,i=0; i<2; ++i) 
            if (life[i] == 0) continue;
            else if (!tank[i].bActive) {            //還有命嗎?
                if (--life[i] > 0) 
                    setup_player (i, true);
            } 
            else if (bonus.life > 0 &&
                     tank[i].nBegin <= 0 && 
                     tank[i].bCollide (bonus)) {
                bonus.life = 0;
                switch (bonus.id - BONUS) {         //若吃到特殊道具
                case Bonus::LIFE:   life[i]++;           break;
                case Bonus::CLOCK:  enemyHoldTime = 240; break;
                case Bonus::SHOVEL: setProtect (true);   break;
                case Bonus::HELMET: tank[i].shield();    break;
                case Bonus::STAR:   tank[i].levelup();   break;
                case Bonus::BOMB:
                     for (j=2; j<MAX_TANK; ++j)
                        if (tank[j].bActive && tank[i].nBegin < 1) 
                            tank[j].life = 1,
                            tank[j].checkExplode(),
                            nActEnemy--;                        
                }
            }
    }
    void _cdecl drawText (int x, int y, char* fmt...)
    {
        static char s[64];
        static BITMAPINFO f = {{40,txtW,-txtH,1,32,0,0,0,0,0,0},{{0}}};
        Rectangle (hdcMem, -1, -1, txtW+1, txtH+1);
        vsprintf_s (s, fmt, (char*)(&fmt+1));
        TextOut (hdcMem, 0, 0, s, strlen(s));
        GetDIBits (hdcMem, hBmp, 0,txtH, text.data, &f, DIB_RGB_COLORS);
        canvas.draw2 (text, x, y, RGB(180,80,80));
    }
    void update ()                       
    {      
        memset (canvas.data, 0xC0, 4*canvas.w*canvas.h); 
        drawText (16*6-8,   2,   "LEVEL%2d", level);
        drawText (16*11,    2,   "  [%2d] ", nEnemy);
        drawText (16*1, 16*14+2, "P1:%2d  ", life[0]);
        drawText (16*9, 16*14+2, "  P2:%2d", life[1]);
        canvas.draw (bg, mapX, mapY);               //繪出背景
        draw_river();                               //畫出河流
        create_enemy();
        colisionTest();                             //移動物件並處理碰撞
        check_player();                             //吃東西與重生
        Map::update();                              //繪出前景
        draw_bonus();                               //繪出道具
        check_over();
        show (canvas, 0,0);
        check_levelup();                            //是否進到下一關
    }
    void show (Image& in, int x, int y)  
    {
        BITMAPINFO info = {{40, in.w, -in.h, 1,32,0,0,0,0,0,0},{{0}}};
        StretchDIBits (hdc, x, y, int(in.w*2.7), int(in.h*2.7),
            0, 0, in.w, in.h, in.data, &info, DIB_RGB_COLORS, SRCCOPY);
    }
    void key_control()                              //按鍵控制
    {                     
        #define PRESS(key) if (GetAsyncKeyState(key)& 0x8000)        
        PRESS (VK_ESCAPE) bOver = true;
        if (bStone) return;
        if (tank[0].bActive) {                      //操控玩家1
            PRESS ('S') tank[0].move (DOWN ); else
            PRESS ('A') tank[0].move (LEFT ); else
            PRESS ('D') tank[0].move (RIGHT); else
            PRESS ('W') tank[0].move (UP   );   
            PRESS ('H') tank[0].fire ();
        }
        if (tank[1].bActive) {                      //操控玩家2
            PRESS (VK_DOWN   ) tank[1].move (DOWN ); else
            PRESS (VK_LEFT   ) tank[1].move (LEFT ); else
            PRESS (VK_RIGHT  ) tank[1].move (RIGHT); else
            PRESS (VK_UP     ) tank[1].move (UP   );   
            PRESS (VK_NUMPAD0) tank[1].fire ();
        }
    }
    Game()
    {
        init();
        for (DWORD curr = timeGetTime(); !bOver;)    
            if (timeGetTime() - curr > 20) {
                update();
                key_control();
                curr = timeGetTime();
            }
        release ();
    }
}_;