第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
開始,其他的部分保持不變。
- 當在中文(或日文)界面下開始顯示全角字符的第2個字節時,refresh的範圍會從
-
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中:
對於hello4來講,執行上面(1~2行)生成hello4.org以後,不會執行下面的代碼(3~4行),而是執行:%.org : %.bim Makefile ../app_make.txt $(BIM2HRB) $*.bim $*.org $(MALLOC) %.hrb : %.org Makefile ../app_make.txt $(BIM2BIN) -osacmp in:$*.org out:$*.hrb
$(APP).hrb : $(APP).org Makefile $(COPY) $(APP).org $(APP).hrb
- 對hello5.hrb和winhelo.hrb的Makefile做同樣處理。
- 這樣,文件本身的普通生成規則會優先於一般生成規則,於是,app_make.txt中:
-
到此爲止,此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中沒有用到,因此忽略即可。
- exit是用來結束應用程序的函數,只要
-
標準函數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是編譯器附帶的,可以直接使用。
...
這種形式的參數不常用,瞭解即可。
- printf用於向屏幕上輸出信息。
-
標準函數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了。
- 原先的13號API:
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(); }
- 由於時間問題,代碼中註釋較少,閱讀起來可能比較辛苦,但代碼比較簡單。
-
遊戲規則:
- 使用
w
、s
、a
、d
控制上下左右方向。 - 空格可以暫停/開始遊戲。
- 左上角的第一個四位數字是遊戲進行的時間(單位s),第二個四位數字是你的得分。
- 如果退出遊戲,可以連續按兩次空格。或者Shift+F1,或者點擊“X”按鈕。
- 紅色的是食物,吃到後會隨機產生下一個食物,併產生音效。
- snake咬到自己的身體遊戲結束,此時可以退出或者按空格鍵重新開始遊戲。
- Have fun!
- 使用
-
遊戲截圖:
- 本遊戲刻意留了一個小bug,可以自己嘗試找一下,如果操作得當可以讓Snake遊戲崩潰哦。上述代碼中有提示哦(笑)。
8. 寫在倒數第二天
- 現在是2020.5.12 15:48
- 耗時近一天寫完了Snake遊戲。主要耗時點在於像素點設置以及用vmware調試過程,不包括忘記修改makefile導致卡住近兩個半小時。貪吃蛇遊戲雖然有點醜,但是麻雀雖小五臟俱全,還配有優美的音效(笑)。
- 在開發過程中故意留了一個小bug,如果操作得當,將會導致snake遊戲崩潰,有興趣的小夥伴歡迎交流指正。
- 今晚結束第30天,加油!