第29天 壓縮與簡單的應用程序

第29天 壓縮與簡單的應用程序

2020.5.10

1. 修復bug(harib26a)

  • 先修復harib25g中的bug。

    • 通過觀察,發現harib25g中,只有全角字符的顯示有問題,半角字符是正常的,而且移動窗口之後可以恢復正常,這說明圖層緩衝區中是有數據的。因此,問題極有可能出現在刷新上面。
  • 修改putfont8_asc_sht(window.c):

    void putfonts8_asc_sht(struct SHEET *sht, int x, int y, int c, int b, char *s, int l)
    {
        struct TASK *task = task_now();
        boxfill8(sht->buf, sht->bxsize, b, x, y, x + l * 8 - 1, y + 15);
        if (task->langmode != 0 && task->langbyte1 != 0) {
            putfonts8_asc(sht->buf, sht->bxsize, x, y, c, s);
            sheet_refresh(sht, x - 8, y, x + l * 8, y + 16);
        } else {
            putfonts8_asc(sht->buf, sht->bxsize, x, y, c, s);
            sheet_refresh(sht, x, y, x + l * 8, y + 16);
        }
        return;
    }
    
    • 當在中文(或日文)界面下開始顯示全角字符的第2個字節時,refresh的範圍會從x-8開始,其他的部分保持不變。
  • make後用VMware運行:

    • 這裏沒有在harib25h的基礎上修改,而是在harib25g的基礎上修改的,因此是日文模式。

2. 文件壓縮(harib26b)

  • 添加支持文件壓縮的功能。

    • 這裏的文件壓縮,指的是文件的解壓縮,而不是壓縮。
    • 實現該功能後,壓縮過的文件可以在OS內部自動解壓,不需要使用壓縮軟件來進行解壓縮。對於壓縮過的文件,可以像未經壓縮的文件一樣直接使用。
    • 之所以要增加這樣的功能,是爲了儘量讓日文字庫文件變小。nihongo.fnt的大小有142KB,但OSASK的jpn16v00.fnt只有56.7KB,這都是壓縮的功能。因此,實現該功能以後,能夠節省大約85KB的磁盤空間,這樣可以編寫更多的應用程序,以及不用使用ipl30.nas了(笑)。
  • 選擇何種壓縮格式

    • 世界上有很多種壓縮格式(.zip、.cab等),有的格式壓縮率很好,但是解壓縮的時間長,有的格式壓縮率不理想,但是解壓縮速度卻很快。
    • 解壓縮程序的大小也需要考慮。即便壓縮率極好,可以將142KB的nihongo.fnt壓縮到1KB,但是加壓縮程序卻需要150KB,這是得不償失的。
    • 這裏,採用書上提供的格式.tek,.tek格式在壓縮率和解壓縮時間的平衡性不錯。
  • 顯然,講解tek的解壓縮程序代碼就跑題了,而且編寫tek的程序代碼不知道要寫到什麼時候,因此,這裏直接將tek的解壓縮程序整合到此OS中。

  • edimg.exe的源代碼是公開的,且tek相關的部分也在edimg.exe的源代碼中。tek的相關代碼位於autodec_.c這個文件中。直接將這個文件複製到harib26b目錄的tek/uautodec_c.c就可以了。

  • 刪除uautodec_c.c中沒有用處的autodecomp函數,並增加必要的韓式tek_getsize和tek_decomp,同時將加工過的程序保存爲tek.c(在tek/和haribote/中同時放入tek.c)。

  • tek.c中tek_getsize和tek_decomp函數:

    int tek_getsize(unsigned char *p)
    {
        static char header[15] = {
            0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x4f, 0x53, 0x41, 0x53, 0x4b, 0x43, 0x4d, 0x50
        };
        int size = -1;
        if (memcmp(p + 1, header, 15) == 0 && (*p == 0x83 || *p == 0x85 || *p == 0x89)) {
            p += 16;
            size = tek_getnum_s7s(&p);
        }
        return size;
    }	  /* 注:memcmp和strcmp差不多,這個函數忽略字符串中的0並一直比較到指定的15個字符爲止 */
    
    int tek_decomp(unsigned char *p, char *q, int size)
    {
        int err = -1;
        if (*p == 0x83) {
            err = tek_decode1(size, p, q);
        } else if (*p == 0x85) {
            err = tek_decode2(size, p, q);
        } else if (*p == 0x89) {
            err = tek_decode5(size, p, q);
        }
        if (err != 0) {
            return -1;	/* 失敗 */
        }
        return 0;	/* 成功 */
    }
    
    • 上述代碼看起來是有難度的,因爲tek_getnum_s7s函數和tek_decode[123]函數具體的執行流程是未知的。
    • 根據書上提示,tek_getsize是用來判斷文件是否符合tek格式,如果是合法的tek格式,那麼將該文件解壓後的大小返回,如果不合法,返回-1。tek_decomp用來完成解壓縮操作。
    • 還有,就是將malloc和free分別改成了memman_alloc_4k和memman_free_4k。
  • 利用tek_getsize和tek_decomp函數,編寫file_loadfile2函數(file.c中):

    char *file_loadfile2(int clustno, int *psize, int *fat)
    {
        int size = *psize, size2;
        struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
        char *buf, *buf2;
        buf = (char *) memman_alloc_4k(memman, size);
        file_loadfile(clustno, size, buf, fat, (char *) (ADR_DISKIMG + 0x003e00));
        if (size >= 17) {
            size2 = tek_getsize(buf);
            if (size2 > 0) {	/* 使用tek格式壓縮的文件 */
                buf2 = (char *) memman_alloc_4k(memman, size2);
                tek_decomp(buf, buf2, size2);
                memman_free_4k(memman, (int) buf, size);
                buf = buf2;
                *psize = size2;
            }
        }
        return buf;
    }
    
    • tek_getsize和tek_decomp要在bootpack.h中聲明。
    • 首先,用memman_alloc_4k申請必要的內存空間(也就是從內存中直接讀出來的文件的大小),然後用file_loadfile函數將文件的內容載入內存。如果文件的大小超過17字節則表示這個文件極有可能是tek格式的文件(tek文件必須有一個用於識別格式的頭文件,這個頭文件的大小至少是17字節)。然後調用tek_getsize判斷該文件是否是tek格式,如果是,則爲解壓後的文件分配內存空間並執行解壓縮操作,然後釋放解壓縮前的文件的內存空間。函數返回值是解壓縮後的文件的內存地址。
  • 修改nihonggo.fnt載入部分:

    void HariMain(void)
    {
        ……
        /* nihongo.fnt的載入 */
        fat = (int *) memman_alloc_4k(memman, 4 * 2880);
        file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));
    
        finfo = file_search("nihongo.fnt", (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
        if (finfo != 0) {
            i = finfo->size;
            nihongo = file_loadfile2(finfo->clustno, &i, fat);
        } else {
            nihongo = (unsigned char *) memman_alloc_4k(memman, 16 * 256 + 32 * 94 * 47);
            for (i = 0; i < 16 * 256; i++) {
                nihongo[i] = hankaku[i]; 
            }
            for (i = 16 * 256; i < 16 * 256 + 32 * 94 * 47; i++) {
                nihongo[i] = 0xff;
            }
        }
        *((int *) 0x0fe8) = (int) nihongo;
        memman_free_4k(memman, (int) fat, 4 * 2880);  
    }
    
    • 這裏只需要注意一下變量i的作用是爲了獲取解壓縮後文件的大小並且不改變原先finfo->size。
    • 其實,從代碼上看,如果nihongo.fnt不被壓縮,程序照樣正常運行。
  • 對nihongo.fnt的壓縮過程需要在Windows下進行:

    • 首先,將bim2bin.exe和t5lzma.exe(這兩個文件都在z_tools文件夾下)賦值到目錄nihongo下,當前目錄是harib26b/nihongo。
    • 然後將143KB的nihongo.fnt重命名成nihongo.org。
    • 然後在當前目錄下打開命令行,輸入命令bim2bin -osacmp in:nihongo.org out:nihongo.fnt
    • 然後就生成了經過壓縮後的nihongo.fnt,大約59KB,不知道爲啥和書上說將56.6KB有點差別,貌似對於日文顯示沒啥問題,所以忽略吧。
    • 如果輸入命令bim2bin -osacmp in:nihongo.org out:nihongo.fnt -tek2可以將其壓縮成爲tek2格式,不指定選項或者指定選項爲tek5,那麼壓縮成爲tk5格式。
  • make full後用VMware運行:

    • 成功!
  • 繼續修改,讓應用程序經過tek壓縮後也可以直接運行:

    int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
    {
        ……
        int i, segsiz, datsiz, esp, dathrb, appsiz;
        struct SHTCTL *shtctl;
        ……
        if (finfo != 0) {
            appsiz = finfo->size;
            p = file_loadfile2(finfo->clustno, &appsiz, fat);
            if (appsiz >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
                ……
            } else {
                cons_putstr0(cons, ".hrb file format error.\n");
            }
            memman_free_4k(memman, (int) p, appsiz);
            cons_newline(cons);
            return 1;
        }
        /* 僼傽僀儖偑尒偮偐傜側偐偭偨応崌 */
        return 0;
    }
    
    • 修改的部分只是將程序的大小從fifo->size改成appsize,並使用file_loadfile2函數來載入。
  • 繼續修改hrb_api函數中文件API:

    int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
    {
        ……
        } else if (edx == 21) {
            ……
            if (i < 8) {
                finfo = file_search((char *) ebx + ds_base,
                        (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
                if (finfo != 0) {
                    reg[7] = (int) fh;
                    fh->size = finfo->size;
                    fh->pos = 0;
                    fh->buf = file_loadfile2(finfo->clustno, &fh->size, task->fat);
                }
            }
        } else if (edx == 22) {
        ……
    }
    
    • 將file_loadfile改成file_loadfile2.
  • 修改應用程序的app_make.txt,以實現壓縮:

    %.org : %.bim Makefile ../app_make.txt
        $(BIM2HRB) $*.bim $*.org $(MALLOC)
    
    %.hrb : %.org Makefile ../app_make.txt
        $(BIM2BIN) -osacmp in:$*.org out:$*.hrb
    
  • make full後統計一下應用程序壓縮後大小和壓縮前大小對比:

    • 大部分的應用程序經過壓縮以後變小了。但是有個別應用程序經過壓縮後變大了。
    • 壓縮,說白了就是對數據的一種轉換,絕大多數情況下這種轉換會讓文件變小,但是還是可能出現經過壓縮文件變大的情況。這不是tek壓縮特有的,這是所有壓縮方式共有的
  • 對所有經過壓縮後變大的應用程序,修改它們的Makefile。比如修改hello4.hrb的Makefile:

    APP      = hello4
    STACK    = 1k
    MALLOC   = 0k
    
    include ../app_make.txt
    
    $(APP).hrb : $(APP).org Makefile
        $(COPY) $(APP).org $(APP).hrb
    
    • 這樣,文件本身的普通生成規則會優先於一般生成規則,於是,app_make.txt中:
      %.org : %.bim Makefile ../app_make.txt
      $(BIM2HRB) $*.bim $*.org $(MALLOC)
      
      %.hrb : %.org Makefile ../app_make.txt
          $(BIM2BIN) -osacmp in:$*.org out:$*.hrb
      
      對於hello4來講,執行上面(1~2行)生成hello4.org以後,不會執行下面的代碼(3~4行),而是執行:
      $(APP).hrb : $(APP).org Makefile
          $(COPY) $(APP).org $(APP).hrb
      
    • 對hello5.hrb和winhelo.hrb的Makefile做同樣處理。
  • 到此爲止,此OS總算正式完工了。

    • typeipl.hrb(顯示ipl10.nas)已經沒啥用了,刪了吧。

3. 標準函數

  • 只做瞭解,下列程序代碼並未真正使用

  • 在C語言中,有一些函數非常常用,它們被稱作“標準函數”。

    • 其中,具有代表性的標準函數有:printf,putchar,strcmp以及malloc等。如果一個程序只調用了標準函數,那麼無論是在liunx還是windows中都可以運行。
    • 如果此OS中也有這些標準函數,那麼相同的C語言程序也可以在此OS中運行。
    • 關於C語言的標準函數,可以查閱相關數據或者網頁
  • 標準函數putchar:

    • 這個函數的功能是在屏幕上顯示一個指定的字符,只要#include<stdio.h>就可以使用。用api_putchar可以很容易實現,putchar.c:
      #include "apilib.h"  
      
      int putchar(int c){
          api_putchar(c);  
          return c;
      }
      
      • putchar參考手冊上面規定,需要return c。書上的putchar參考手冊的url都已經不能訪問
  • 標準函數strcmp:

    • 這個函數已經由編譯器附帶了,因此不需要特地編寫。
  • 標準函數exit:

    • exit是用來結束應用程序的函數,只要#include<stdlib.h>便可以使用。編寫exit.c:
      #include "apilib.h"  
      
      void exit(int status){
          api_end();
      }
      
      • status參數是用來向OS報告應用程序結束狀態的。此OS中沒有用到,因此忽略即可。
  • 標準函數printf:

    • printf用於向屏幕上輸出信息。#include<stdio.h>即可使用。編寫printf.c:
      #include <stdio.h>  
      #include <stdarg.h> 
      #include "apilib.h"  
      
      int printf(char *format, ...){
          va_list ap;
          char s[1000];  
          int i;
      
          va_start(ap, format);
          i = vsprintf(s, format, ap);
          api_putstr0(s);
          va_end(ap);
          return i;
      }
      
      • ...是C語言的語法,代表可變參數。用...傳遞的參數可以使用va_list來獲取,只要#include<stdarg.h>就可以使用了。使用時先用過va_start初始化,然後使用va_end結束。
      • 同時,有一個版本的sprintf是可以接收va_list作爲參數的,名稱是vsprintf。vsprintf是編譯器附帶的,可以直接使用。
      • ...這種形式的參數不常用,瞭解即可
  • 標準函數malloc和free:

    • 用於分配內存空間和釋放內存空間。需要#include<stdlib.h>
    • 編寫malloc.c:
      void *malloc(int size){
          char *p = api_malloc(size + 16);
          if (p != 0){
              *((int *) p) = size;
              p += 16;
          }
          return p;
      }
      
    • 編寫free.c:
      void free(void *p){
          char *q = p;
          int size;
          if (q != 0){
              q -= 16;
              size = *((int *) q);
              api_free(q, size + 16);
          }
          return;
      }
      
      • 由於標準的free函數不需要指定size,因此需要在malloc時,找個指定的地方將size放起來。
      • 在api_malloc時多分配出16字節。size佔用4字節。爲其分配16字節是因爲,當內存地址是16字節的倍數時,CPU處理速度有時可以更快。
      • 在9號API中:
        else if (edx == 9) {
            ecx = (ecx + 0x0f) & 0xfffffff0; /* 16的整數倍 */
            reg[7] = memman_alloc((struct MEMMAN *) (ebx + ds_base), ecx);
        } else if (edx == 10) {
        
        • 因此,從api_malloc返回的內存地址一定是16字節的倍數。

4. 非矩形窗口(harib26c)

  • 實現非矩形窗口的方式:通過使用透明色。

  • 編寫應用程序netrec.c:

    #include "apilib.h"
    
    void HariMain(void)
    {
        int win;
        char buf[150 * 70];
        win = api_openwin(buf, 150, 70, 255, "notrec"); /*指定透明色是255號*/
        api_boxfilwin(win,   0, 50,  34, 69, 255); /*透明色255*/
        api_boxfilwin(win, 115, 50, 149, 69, 255); /*透明色255*/
        api_boxfilwin(win,  50, 30,  99, 49, 255); /*透明色255*/
        for (;;) {
            if (api_getkey(1) == 0x0a) {
                break; 
            }
        }
        api_end();
    }
    
    • 先創建一個矩形窗口,將透明色指定爲255號,然後再用透明色在窗口中繪製3個矩形,這樣便可以顯示非矩形窗口了。
  • make full後用VMware運行:

    • 剛顯示時,可能不是上圖的效果,這是因爲refresh的問題,只要移動一下窗口就可以了。由於非矩形窗口沒有實現真正意義上的支持,因此沒有修改代碼。

5. bball(harib26d)

  • bball四beautiful ball的縮寫。

  • 編寫應用程序bball.c:

    #include "apilib.h"
    
    void HariMain(void)
    {
        int win, i, j, dis;
        char buf[216 * 237];
        struct POINT {
            int x, y;
        };
        static struct POINT table[16] = {
            { 204, 129 }, { 195,  90 }, { 172,  58 }, { 137,  38 }, {  98,  34 },
            {  61,  46 }, {  31,  73 }, {  15, 110 }, {  15, 148 }, {  31, 185 },
            {  61, 212 }, {  98, 224 }, { 137, 220 }, { 172, 200 }, { 195, 168 },
            { 204, 129 }
        };
    
        win = api_openwin(buf, 216, 237, -1, "bball");
        api_boxfilwin(win, 8, 29, 207, 228, 0);
        for (i = 0; i <= 14; i++) {
            for (j = i + 1; j <= 15; j++) {
                dis = j - i; /* 顏色 */
                if (dis >= 8) {
                    dis = 15 - dis; /* 不能超過8種顏色 */
                }
                if (dis != 0) {
                    api_linewin(win, table[i].x, table[i].y, table[j].x, table[j].y, 8 - dis);
                }
            }
        }
    
        for (;;) {
            if (api_getkey(1) == 0x0a) {
                break; 
            }
        }
        api_end();
    }
    
  • make full後用VMware運行:

    • 這裏使用的是VESA,因此沒有出現bug。
  • 不適用VESA會產生bug,還是refresh的問題。修改hrb_api中13號API:

    int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
    {
        ……
        } else if (edx == 13) {
            sht = (struct SHEET *) (ebx & 0xfffffffe);
            hrb_api_linewin(sht, eax, ecx, esi, edi, ebp);
            if ((ebx & 1) == 0) {
                if (eax > esi) {
                    i = eax;
                    eax = esi;
                    esi = i;
                }
                if (ecx > edi) {
                    i = ecx;
                    ecx = edi;
                    edi = i;
                }
                sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
            }
        } else if (edx == 14) { 
        ……
    }
    
    • 原先的13號API:
      int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
      {
          ……
          } else if (edx == 13) {
              sht = (struct SHEET *) (ebx & 0xfffffffe);
              hrb_api_linewin(sht, eax, ecx, esi, edi, ebp);
              if ((ebx & 1) == 0) {
                  sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
              }
          } else if (edx == 14) { 
          ……
      }
      
    • 爲了refresh的範圍指定正確的左上角和右下角座標,將變量比較後進行替換。這樣就OK了。

6. 外形人遊戲(harib26e)

  • 關於遊戲的背景介紹這裏不再贅述。

  • 遊戲規則:

    • 使用"4"和"6"進行左右移動(方向鍵<-和->),按空格發射炮彈。炮彈不能連續發射。
    • 消滅外星人會有獎勵(得分),並且命中率越高得分就越高。
    • 隨着遊戲的進行,外星人的移動速度會加快。
    • 當外星人到達最底下一行時,遊戲結束。按下Enter可以重新開始。
    • 關閉請使用Shift+F1或者“X”按鈕。
  • 編寫invader.c:

    #include <stdio.h>		/* sprintf */
    #include <string.h>		/* strlen */
    #include "apilib.h"
    
    void putstr(int win, char *winbuf, int x, int y, int col, unsigned char *s);
    void wait(int i, int timer, char *keyflag);
    
    static unsigned char charset[16 * 8] = {
    
        /* invader(0) */
        0x00, 0x00, 0x00, 0x43, 0x5f, 0x5f, 0x5f, 0x7f,
        0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x20, 0x3f, 0x00,
    
        /* invader(1) */
        0x00, 0x0f, 0x7f, 0xff, 0xcf, 0xcf, 0xcf, 0xff,
        0xff, 0xe0, 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0x00,
    
        /* invader(2) */
        0x00, 0xf0, 0xfe, 0xff, 0xf3, 0xf3, 0xf3, 0xff,
        0xff, 0x07, 0xff, 0xff, 0x03, 0x03, 0x03, 0x00,
    
        /* invader(3) */
        0x00, 0x00, 0x00, 0xc2, 0xfa, 0xfa, 0xfa, 0xfe,
        0xf8, 0xf8, 0xf8, 0xf8, 0x00, 0x04, 0xfc, 0x00,
    
        /* fighter(0) */
        0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
        0x01, 0x43, 0x47, 0x4f, 0x5f, 0x7f, 0x7f, 0x00,
    
        /* fighter(1) */
        0x18, 0x7e, 0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0xff,
        0xff, 0xff, 0xe7, 0xe7, 0xe7, 0xe7, 0xff, 0x00,
    
        /* fighter(2) */
        0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
        0x80, 0xc2, 0xe2, 0xf2, 0xfa, 0xfe, 0xfe, 0x00,
    
        /* laser */
        0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00
    };
    /* invader:"abcd", fighter:"efg", laser:"h" */
    
    void HariMain(void)
    {
        int win, timer, i, j, fx, laserwait, lx = 0, ly;
        int ix, iy, movewait0, movewait, idir;
        int invline, score, high, point;
        char winbuf[336 * 261], invstr[32 * 6], s[12], keyflag[4], *p;
        static char invstr0[32] = " abcd abcd abcd abcd abcd ";
    
        win = api_openwin(winbuf, 336, 261, -1, "invader");
        api_boxfilwin(win, 6, 27, 329, 254, 0);
        timer = api_alloctimer();
        api_inittimer(timer, 128);
    
        high = 0;
        putstr(win, winbuf, 22, 0, 7, "HIGH:00000000");
    
    restart:
        score = 0;
        point = 1;
        putstr(win, winbuf,  4, 0, 7, "SCORE:00000000");
        movewait0 = 20;
        fx = 18;
        putstr(win, winbuf, fx, 13, 6, "efg");
        wait(100, timer, keyflag);
    
    next_group:
        wait(100, timer, keyflag);
        ix = 7;
        iy = 1;
        invline = 6;
        for (i = 0; i < 6; i++) {
            for (j = 0; j < 27; j++) {
                invstr[i * 32 + j] = invstr0[j];
            }
            putstr(win, winbuf, ix, iy + i, 2, invstr + i * 32);
        }
        keyflag[0] = 0;
        keyflag[1] = 0;
        keyflag[2] = 0;
    
        ly = 0; /* 不顯示 */
        laserwait = 0;
        movewait = movewait0;
        idir = +1;
        wait(100, timer, keyflag);
    
        for (;;) {
            if (laserwait != 0) {
                laserwait--;
                keyflag[2 /* space */] = 0;
            }
    
            wait(4, timer, keyflag);
    
            /* 自機的處理 */
            if (keyflag[0 /* left */]  != 0 && fx > 0) {
                fx--;
                putstr(win, winbuf, fx, 13, 6, "efg ");
                keyflag[0 /* left */]  = 0;
            }
            if (keyflag[1 /* right */] != 0 && fx < 37) {
                putstr(win, winbuf, fx, 13, 6, " efg");
                fx++;
                keyflag[1 /* right */] = 0;
            }
            if (keyflag[2 /* space */] != 0 && laserwait == 0) {
                laserwait = 15;
                lx = fx + 1;
                ly = 13;
            }
    
            /* 外星人移動 */
            if (movewait != 0) {
                movewait--;
            } else {
                movewait = movewait0;
                if (ix + idir > 14 || ix + idir < 0) {
                    if (iy + invline == 13) {
                        break; /* GAME OVER */
                    }
                    idir = - idir;
                    putstr(win, winbuf, ix + 1, iy, 0, "                         ");
                    iy++;
                } else {
                    ix += idir;
                }
                for (i = 0; i < invline; i++) {
                    putstr(win, winbuf, ix, iy + i, 2, invstr + i * 32);
                }
            }
    
            /* 炮彈的處理 */
            if (ly > 0) {
                if (ly < 13) {
                    if (ix < lx && lx < ix + 25 && iy <= ly && ly < iy + invline) {
                        putstr(win, winbuf, ix, ly, 2, invstr + (ly - iy) * 32);
                    } else {
                        putstr(win, winbuf, lx, ly, 0, " ");
                    }
                }
                ly--;
                if (ly > 0) {
                    putstr(win, winbuf, lx, ly, 3, "h");
                } else {
                    point -= 10;
                    if (point <= 0) {
                        point = 1;
                    }
                }
                if (ix < lx && lx < ix + 25 && iy <= ly && ly < iy + invline) {
                    p = invstr + (ly - iy) * 32 + (lx - ix);
                    if (*p != ' ') {
                        /* hit ! */
                        score += point;
                        point++;
                        sprintf(s, "%08d", score);
                        putstr(win, winbuf, 10, 0, 7, s);
                        if (high < score) {
                            high = score;
                            putstr(win, winbuf, 27, 0, 7, s);
                        }
                        for (p--; *p != ' '; p--) { }
                        for (i = 1; i < 5; i++) {
                            p[i] = ' ';
                        }
                        putstr(win, winbuf, ix, ly, 2, invstr + (ly - iy) * 32);
                        for (; invline > 0; invline--) {
                            for (p = invstr + (invline - 1) * 32; *p != 0; p++) {
                                if (*p != ' ') {
                                    goto alive;
                                }
                            }
                        }
                        /* 全部消滅 */
                        movewait0 -= movewait0 / 3;
                        goto next_group;
        alive:
                        ly = 0;
                    }
                }
            }
        }
    
        /* GAME OVER */
        putstr(win, winbuf, 15, 6, 1, "GAME OVER");
        wait(0, timer, keyflag);
        for (i = 1; i < 14; i++) {
            putstr(win, winbuf, 0, i, 0, "                                        ");
        }
        goto restart;
    }
    
    void putstr(int win, char *winbuf, int x, int y, int col, unsigned char *s)
    {
        int c, x0, i;
        char *p, *q, t[2];
        x = x * 8 + 8;
        y = y * 16 + 29;
        x0 = x;
        i = strlen(s);	/* 計算s的字符數 */
        api_boxfilwin(win + 1, x, y, x + i * 8 - 1, y + 15, 0);
        q = winbuf + y * 336;
        t[1] = 0;
        for (;;) {
            c = *s;
            if (c == 0) {
                break;
            }
            if (c != ' ') {
                if ('a' <= c && c <= 'h') {
                    p = charset + 16 * (c - 'a');
                    q += x;
                    for (i = 0; i < 16; i++) {
                        if ((p[i] & 0x80) != 0) { q[0] = col; }
                        if ((p[i] & 0x40) != 0) { q[1] = col; }
                        if ((p[i] & 0x20) != 0) { q[2] = col; }
                        if ((p[i] & 0x10) != 0) { q[3] = col; }
                        if ((p[i] & 0x08) != 0) { q[4] = col; }
                        if ((p[i] & 0x04) != 0) { q[5] = col; }
                        if ((p[i] & 0x02) != 0) { q[6] = col; }
                        if ((p[i] & 0x01) != 0) { q[7] = col; }
                        q += 336;
                    }
                    q -= 336 * 16 + x;
                } else {
                    t[0] = *s;
                    api_putstrwin(win + 1, x, y, col, 1, t);
                }
            }
            s++;
            x += 8;
        }
        api_refreshwin(win, x0, y, x, y + 16);
        return;
    }
    
    void wait(int i, int timer, char *keyflag)
    {
        int j;
        if (i > 0) {
            /* 等待一段時間 */
            api_settimer(timer, i);
            i = 128;
        } else {
            i = 0x0a; /* Enter */
        }
        for (;;) {
            j = api_getkey(1);
            if (i == j) {
                break;
            }
            if (j == '4') {
                keyflag[0 /* left */]  = 1;
            }
            if (j == '6') {
                keyflag[1 /* right */] = 1;
            }
            if (j == ' ') {
                keyflag[2 /* space */] = 1;
            }
        }
        return;
    }
    
    • 遊戲代碼的講解如下,書上原話,這裏不做詳細解釋:
  • make full後用VMware運行:

    • invader.hrb的大小僅2335字節。

7. Snake遊戲(harib26f)

  • Snake遊戲,即貪吃蛇遊戲。

  • 話不多說,直接上代碼(snake.c):

    #include "apilib.h"   
    
    #define NULL 0
    
    struct Pos {
        int x;
        int y;
        struct Pos* next;
    };
    
    struct Snake {
        struct Pos head;
        struct Pos* tail;
        int len;
        int dir; //1:up 2:down 3:left 4:right
    };
    
    void move(struct Snake* s, int dir_now, int win) {
        struct Pos* p = s->head.next;
        int prex = s->head.x, prey = s->head.y;
        api_boxfilwin(win, s->head.x, s->head.y, s->head.x + 4, s->head.y + 4, 0);
        while (p != NULL) {
            api_boxfilwin(win, p->x, p->y, p->x + 4, p->y + 4, 0);
            int tx = p->x;
            int ty = p->y;
            p->x = prex;
            p->y = prey;  
            prex = tx;
            prey = ty;
            p = p->next;
        }
    
        if (dir_now == 1) { //up
            s->head.y -= 5;
        }
        if (dir_now == 2) { //down
            s->head.y += 5;
        }
        if (dir_now == 3) { //left
            s->head.x -= 5;
        }
        if (dir_now == 4) { //right
            s->head.x += 5;
        }
    
        if (s->head.x < 5) {
            s->head.x = 324;
        }
        if (s->head.x > 324) {
            s->head.x = 5;
        }
        if (s->head.y < 25) {
            s->head.y = 249;
        }
        if (s->head.y > 249) {
            s->head.y = 25;
        }
    
        p = &s->head;
        while (p != NULL) {
            api_boxfilwin(win, p->x, p->y, p->x + 4, p->y + 4, 3);
            p = p->next;
        }
    
        return;
    }
    
    int judge_rec_join(struct Pos p1, struct Pos p2) {
        int dx = p1.x - p2.x;
        int dy = p1.y - p2.y;
        if ((-5 < dx) && (dx < 5) && (-5 < dy) && (dy < 5)) {
            return 1;
        }
        return 0;
    }
    
    void strong(struct Snake* s) {	
        struct Pos* newp = (struct Pos*)api_malloc(sizeof(struct Pos));
        newp->next = NULL;  
        if (s->dir == 1) {
            newp->x = s->tail->x;
            newp->y = s->tail->y + 5;
        }
        if (s->dir == 2) {
            newp->x = s->tail->x;
            newp->y = s->tail->y - 5;
        }
        if (s->dir == 3) {
            newp->x = s->tail->x + 5;
            newp->y = s->tail->y;
        }
        if (s->dir == 4) {
            newp->x = s->tail->x - 5;
            newp->y = s->tail->y;
        }
    
        s->tail->next = newp;
        s->len++;
        s->tail = newp;
        
        return;
    }
    
    int judge_fail(struct Snake* s) {
        struct Pos* p = s->head.next;
        p = p->next;
        while (p != NULL) {
            if (judge_rec_join(s->head, *p)) {
                return 1;
            }
            p = p->next;
        }
        return 0;
    }
    
    void HariMain(void) {
        int win, timer, timer_move, timer_sleep;
        char str[12];
    
        char winbuf[335 * 260];
        win = api_openwin(winbuf, 335, 260, -1, "Snake");  
    
        timer = api_alloctimer();
        api_inittimer(timer, 128);
        api_inittimer(timer_move, 129);
        api_inittimer(timer_sleep, 130);
        api_initmalloc();
    
        struct Pos pnext;
    
        int pre_dir, sec, key_data, flag, i;
        struct Snake s;
    
        //struct Pos p1, p2, p3, p4, p5, p6, p7, p8;
    
    restart:
        api_boxfilwin(win, 5, 25, 329, 254, 0);
        sec = 0;
    
        s.head.x = 25;
        s.head.y = 25;
        s.head.next = NULL;
        s.tail = &s.head;
        s.len = 1;
        s.dir = 1;
        pre_dir = 1;
    
        api_boxfilwin(win, s.head.x, s.head.y, s.head.x + 4, s.head.y + 4, 3);
    
        api_settimer(timer, 100);
        api_settimer(timer_move, 20);
    
        flag = 1;
    
        for (;;) {
            key_data = api_getkey(1);
    
            if (key_data == 0x0a)
                break;
    
            if (key_data == 0x20) {
                api_settimer(timer_sleep, 50); /*至少1s,否則連續按空格會出bug*/
                for (;;) {
                    if (api_getkey(1) == 130) {
                        break;
                    }
                }
                for (;;) {
                    if (api_getkey(1) == 0x20) {
                        api_settimer(timer_move, 20);
                        api_settimer(timer, 100);
                        break;
                    }
                }
            }
                
            if (key_data == 128) {
                sec ++;
                sprintf(str, "%04d %04d", sec, s.len-1);
                api_boxfilwin(win, 5, 25, 76, 40, 0);
                api_putstrwin(win, 5, 25, 15, 9, str);
                api_settimer(timer, 100);
            }
            
            if (key_data == 129) { 
                move(&s, s.dir, win);
                pre_dir = s.dir;
    
                if (judge_fail(&s)) {
                    api_boxfilwin(win, 125, 125, 196, 140, 0);
                    api_putstrwin(win, 125, 125, 4, 9, "GAME OVER!");  
                    struct Pos* pt = s.head.next;
                    struct Pos* prept;
                    while (pt != NULL) {
                        prept = pt->next;
                        api_free(pt, sizeof(struct Pos));
                        pt = prept;
                    }
                    for (;;) {
                        if (api_getkey(1) == 0x20) {
                            goto restart;
                        }
                    }
                }
    
                api_settimer(timer_move, 20);
            }
    
            if (key_data == 'w') { /*不能立刻相應改變方向*/
                if (pre_dir != 2) {
                    s.dir = 1;
                }
            }
            if (key_data == 's') {
                if (pre_dir != 1) {
                    s.dir = 2;
                }
            }
            if (key_data == 'a') {
                if (pre_dir != 4) {
                    s.dir = 3;
                }
            }
            if (key_data == 'd') {
                if (pre_dir != 3) {
                    s.dir = 4;
                }
            }	
            
            if (flag) {
                pnext.x = (rand() + sec) % 320 / 5 * 5 + 5;
                pnext.y = (rand() + sec) % 225 / 5 * 5 + 25;  
                api_boxfilwin(win, pnext.x, pnext.y, pnext.x + 4, pnext.y + 4, 1);  
                flag = 0;  
            }
    
            if (judge_rec_join(s.head, pnext)) {
                api_boxfilwin(win, pnext.x, pnext.y, pnext.x + 4, pnext.y + 4, 0);
                for (i = 200000; i < 500000; i += i / 10) {
                    api_beep(i);
                }
                api_beep(0);
                strong(&s);
                flag = 1;
            }
            
        }
    
        for (;;) {
            if (api_getkey(1) == 0x0a) {
                break; 
            }
        }
        api_end();
    }
    
    • 由於時間問題,代碼中註釋較少,閱讀起來可能比較辛苦,但代碼比較簡單。
  • 遊戲規則:

    • 使用wsad控制上下左右方向。
    • 空格可以暫停/開始遊戲。
    • 左上角的第一個四位數字是遊戲進行的時間(單位s),第二個四位數字是你的得分。
    • 如果退出遊戲,可以連續按兩次空格。或者Shift+F1,或者點擊“X”按鈕。
    • 紅色的是食物,吃到後會隨機產生下一個食物,併產生音效。
    • snake咬到自己的身體遊戲結束,此時可以退出或者按空格鍵重新開始遊戲。
    • Have fun!
  • 遊戲截圖:

    • 本遊戲刻意留了一個小bug,可以自己嘗試找一下,如果操作得當可以讓Snake遊戲崩潰哦。上述代碼中有提示哦(笑)

8. 寫在倒數第二天

  • 現在是2020.5.12 15:48
  • 耗時近一天寫完了Snake遊戲。主要耗時點在於像素點設置以及用vmware調試過程,不包括忘記修改makefile導致卡住近兩個半小時。貪吃蛇遊戲雖然有點醜,但是麻雀雖小五臟俱全,還配有優美的音效(笑)。
  • 在開發過程中故意留了一個小bug,如果操作得當,將會導致snake遊戲崩潰,有興趣的小夥伴歡迎交流指正。
  • 今晚結束第30天,加油!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章