在伊莉論壇的C/C++討論區裡看到daviddr大大寫坦克大戰的小遊戲程式
而且也有source code,讓大家學習學習
原文網址:http://www.eyny.com/thread-3505536-1-1.html
每回 25 隻敵人,不含音效資料,不含雪地地形
吃越多星星能力越高,最後能在河面行走
可雙人操控,按ESC退出遊戲
P1:W、S、A、D 控制方向,H發射子彈
P2:方向鍵控制方向,Num 0 發射子彈
/*******************/
//坦克大戰雛形,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, W=in.w, H=in.h;
int beg = sy*w+sx, sz = W*H, dw=w-W;
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 draw16 (const Image& in, int sx, int sy, int c)
{
int i=sy*w+sx, j=0, x, sz = in.h*in.w, dw=w-in.w;
for (short* p=(short*)in.data; j<sz; i+=dw)
for (x=0; x<in.w; ++x, ++i)
if (p[j++]^0x6318) data[i] = c;
}
void draw32 (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, //6
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 || nBegin>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 = (6-t) * (level>15? 4: level>10? 5: 6); //攻擊時間間隔
tank[i].setup (depot[2], d);
if (rand()%100 > 61) tank[i].setBonus(); //39%機率攜帶道具
}
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)
for (int x,y,n,j,k=0, len = strlen (code), i=0; i<len; ++i) {
c = ((c=code[ i ])=='$' ? '?' : c=='%'?'\\':c)-'&';
for (j = c>>4, n = (c&15)+1 ;n--; ++k) {
set_space_2x2 (x= k%(W*2)*2, y= k/(H*2)*2, 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;
if (life[0]) setup_player (0, false);
if (life[1]) 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])) continue;
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 int bits = GetDeviceCaps (hdc, BITSPIXEL) *
GetDeviceCaps (hdc, PLANES);
static BITMAPINFO f = {{40,txtW,-txtH,1,bits,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);
if (bits == 16)
canvas.draw16 (text, x, y, RGB(180,80,80));
else canvas.draw32 (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 ();
}
}_;
/*******************/
code 用 VC++ 或 Dev C++ 來編譯,編譯時以 console mode 編譯。
Dev C++ 要在 [專案選項>參數>連結器]
內設定 -lwinmm、-lGDI32,
並將 vsprintf_s 改成 vsprintf。
Q&A:
@一開始的構築要怎規劃呢?
首先要讓遊戲以穩定的速度進行
也就是讓 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 來讀取按鍵狀態。
順便附上Neil用Visual C++ 2008編譯出來的執行檔
沒有編譯程式的就點下面連結下載吧~
點我
有錯誤
回覆刪除沒有declare {vsprintf_s}