基于Agg的扫雷程序实现(下)
初始化了雷盘之后,玩家开始点击雷盘中的小格子,我们暂且称之为Cell,这时候初始化雷盘,要保证当前点击的地方不为炸弹,否则,一开始玩就被KO了。
1、初始化雷盘。初始化代码主要放在InitInstance 函数中,如下:
1
BOOL InitInstance()2


{3

if((SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO)==-1))
{4
printf("Could not initialize SDL: %s.\n", SDL_GetError());5
exit(-1);6
}7

8
// segment 19
render_engin::initSDL(rh, sdl_screen, sdl_surface, buffer, render_engin::width, render_engin::height);10

11
//segment 212
rh.init_scene(render_engin::width / 32, render_engin::height / 32);13
return TRUE;14
}15

16
//segment 117

18
bool initSDL(render_engin::render_handler& rh, SDL_Surface*& screen, SDL_Surface*& surface, unsigned char*& buff, int screen_width, int screen_height )19


{20
rh.init_buffer(render_engin::width, render_engin::height, buff);21

22
int stride = screen_width * (SDL_GetVideoInfo()->vfmt->BytesPerPixel);23
screen = SDL_SetVideoMode(screen_width, screen_height, 32, SDL_SWSURFACE|SDL_ANYFORMAT);24
surface = SDL_CreateRGBSurfaceFrom((void* )buff, screen_width, screen_height, SDL_GetVideoInfo()->vfmt->BitsPerPixel, stride, 0x00, 0x00, 0x00, 0xff<<24);25
26
SDL_WM_SetCaption("Agg_Bomb Game", NULL);27

28

29
if( SDL_BlitSurface(surface, NULL, screen, NULL) < 0)30
return false;31
SDL_UpdateRect(screen, 0, 0, 0, 0);32

33
return true;34

35
}36

37
//segment 238

39

namespace render_engin
{40

41

42
int width = 512;43
int height = 512;44
render_engin::cell* pCell[16][16];45

46

class render_handler
{47
public:48
void init_buffer(const int& w, const int& h, unsigned char*& buffer)49

{50
buffer = new unsigned char [w * h * 4];51

52
rbuf.attach(buffer, w, h, w * 4);53
pixfmt.attach(rbuf);54
renb.attach(pixfmt);55
ren.attach(renb);56

57
renb.clear(agg::rgba8(255, 255, 255, 255));58
//the same as : memset(buffer, 255, w * h * 4);59
}60
61
void render_cell(cell& cl)62

{63
ren.color(cl.m_color);64
ras.add_path(cl.m_path);65
agg::render_scanlines(ras, sl, ren);66
}67

68
void init_scene(const int w_num, const int h_num)69

{70
for(int iL1 = 0; iL1 < h_num; iL1 ++)71

{72
for(int iL2 = 0; iL2 < w_num; iL2 ++ )73

{74
pCell[iL1][iL2] = new cell(iL1, iL2);75
if(iL2 % 2 == 0 && iL1 % 2 == 1)76

{77
pCell[iL1][iL2]->Draw_Cell(agg::rgba8(200, 122, 50)); 78
}79
else if(iL2 % 2 == 1 && iL1 % 2 == 0)80

{81
pCell[iL1][iL2]->Draw_Cell(agg::rgba8(232, 0, 210)); 82
}83
else if(iL2 % 2 == 0 && iL1 % 2 == 0)84

{85
pCell[iL1][iL2]->Draw_Cell(agg::rgba8(132, 110, 210)); 86
}87
else if(iL2 % 2 == 1 && iL1 % 2 == 1)88

{89
pCell[iL1][iL2]->Draw_Cell(agg::rgba8(0, 232, 210)); 90
}91
92
render_cell(*pCell[iL1][iL2]);93
}94
} 95
}96

97
public:98
agg::rendering_buffer rbuf;99
agg::pixfmt_rgba32 pixfmt;100
agg::renderer_base<agg::pixfmt_rgba32> renb;101
agg::renderer_scanline_aa_solid<agg::renderer_base<agg::pixfmt_rgba32> > ren;102
agg::scanline_p8 sl;103
agg::rasterizer_scanline_aa<> ras;104
};105
};
在init_map函数中,我们采用srand与rand函数来产生随机数,因为rand函数实际上产生的是伪随机数,srand去得的种子如果相同,rand产生的随机数就相同,所以我们采用系统时间的秒数来初始化srand, 初始化雷盘没什么,不是有雷就是没雷,因为随机函数产生的雷太多,而且过于集中,所以我们做些混淆,撒雷的时候每次隔6列,做六次,对雷数也有限制。
init_count_map计算每个区域周围有几个雷,然后保存起来。方法就是对于每个区域,扫描周围的8个位置,看是否为雷,如果是,就将数值加一,最后的值就是count_map保存的值,也是到时候我们显示的值。
check_map是为了自动展开功能而写的,如果一个区域周围的雷都已探明,此时应该将周围的数字全部展开。方法是对于每一个像素都检查周围的8个相邻区域,递归进行,直到不满足条件。对于一次点击要判断此处是不是雷,如果是则game over,否则显示数字,并探测是否需要自动展开。
如果是右键点击,首先判断是否是雷,如果是雷,则将is_bomb位激活,下次自动检测时可以将此位归为一个雷。如果第二次点击,则将标志位都归为,重新给区域画上背景色。给人一个悔过的机会。这部分代码在鼠标事件处。
1
//初始化地图,假设炸弹去为16 * 16,那么我们的地图去设为18*182
//这样做得好处是在计算每个16*16的每个点周围有几个炸弹,3
//边界点我们可以在18 * 18的地图内设为04
void init_map(int p[][18])5


{6

7
GetLocalTime( &sys1 );8
int iCount = 0;9

10
std::srand(sys1.wSecond);11

12
for(int iL1 = 1; iL1 < 17; iL1 += 6)13

{14
for(int iL2 = 1; iL2 < 17; iL2 ++)15

{16
if(iCount < 4)17

{18
p[iL1][iL2] = rand() % 2;19
if(p[iL1][iL2] == 1)20
iCount ++;21
}else22

{23
p[iL1][iL2] = 0; 24
}25
}26
}27

28
iCount = 0;29
for(int iL1 = 2; iL1 < 17; iL1 += 6)30

{31
for(int iL2 = 1; iL2 < 17; iL2 ++)32

{33
if(iCount < 8)34

{35
p[iL1][iL2] = rand() % 2;36
if(p[iL1][iL2] == 1)37
iCount ++;38
}else39

{40
p[iL1][iL2] = 0; 41
}42
}43
}44

45
iCount = 0;46
for(int iL1 = 3; iL1 < 17; iL1 += 6)47

{48
for(int iL2 = 1; iL2 < 9; iL2 ++)49

{50
if(iCount < 20)51

{52
p[iL1][iL2] = rand() % 2;53
if(p[iL1][iL2] == 1)54
iCount ++;55
}else56

{57
p[iL1][iL2] = 0; 58
}59
}60
}61

62
63
iCount = 0;64
for(int iL1 = 4; iL1 < 17; iL1 += 6)65

{66
for(int iL2 = 1; iL2 < 7; iL2 ++)67

{68
if(iCount < 30)69

{70
p[iL1][iL2] = rand() % 2;71
if(p[iL1][iL2] == 1)72
iCount ++;73
}else74

{75
p[iL1][iL2] = 0; 76
}77
}78
}79

80
81
iCount = 0;82
for(int iL1 = 5; iL1 < 17; iL1 += 6)83

{84
for(int iL2 = 1; iL2 < 7; iL2 ++)85

{86
if(iCount < 10)87

{88
p[iL1][iL2] = rand() % 2;89
if(p[iL1][iL2] == 1)90
iCount ++;91
}else92

{93
p[iL1][iL2] = 0; 94
}95
}96
}97

98
99
iCount = 0;100
for(int iL1 = 6; iL1 < 17; iL1 += 6)101

{102
for(int iL2 = 1; iL2 < 7; iL2 ++)103

{104
if(iCount < 10)105

{106
p[iL1][iL2] = rand() % 2;107
if(p[iL1][iL2] == 1)108
iCount ++;109
}else110

{111
p[iL1][iL2] = 0; 112
}113
}114
}115

116
for(int iL1 = 0; iL1 < 18; iL1 ++)117

{118
p[0][iL1] = 0;119
p[iL1][0] = 0;120
p[17][iL1] = 0;121
p[iL1][17] = 0;122
}123
}124

125
//这个函数计算每个点周围有几个炸弹,q数组保存了结果,126
//p数组是我们上面初始化的炸弹布局图,初始化q只需计算它周围的8个区域是否为炸弹区,127
//如果是则计数加一,最后的数值就是q[i][j]的值, 0 <= i , j < 16128
void init_count_map(int p[][18], int q[][16])129


{130
for(int iL1 = 0; iL1 < 16; iL1 ++)131

{132
for(int iL2 = 0; iL2 < 16; iL2 ++)133

{134
q[iL1][iL2] = 0;135
if(p[iL1 + 1][iL2 + 1] == 0)136

{ 137
for(int iL3 = iL1; iL3 < iL1 + 3; iL3 ++)138

{139
for(int iL4 = iL2; iL4 < iL2 + 3; iL4 ++)140

{141
if(p[iL3][iL4] == 1)142

{143
q[iL1][iL2] ++;144
}145
}146
}147

148
}else 149

{150
render_engin::pCell[iL1][iL2]->m_is_bomb = true;151
}152
153
}154
}155

156
}157

158

159
//当左键按下时,需要检查当前点是否为雷区,及是否已经被点过,160
//如果是雷区,而且没检查过,那么就触雷了,161
//如果不是雷区,则要显示这点的数字,同时要查看这点周围是否全部的雷都已找到,162
//如果找到,则进行自动展开,这是个递归程序163
void check_map(int p[][16], render_engin::cell* pcell)164


{165
int x = pcell->m_x;166
int y = pcell->m_y;167
int iCount = 0;168

169
for(int iL1 = x- 1; iL1 <= x + 1; iL1 ++)170

{171
if(iL1 >= 0 && iL1 <= 15)172

{173
for(int iL2 = y - 1; iL2 <= y + 1; iL2 ++)174

{175
if(iL2 >= 0 && iL2 <= 15)176

{177
if(render_engin::pCell[iL1][iL2]->m_checked == true && 178
render_engin::pCell[iL1][iL2]->m_is_bomb == true)179

{180
iCount ++;181
}182
}183
}184
}185
186
}187

188
if(iCount == p[x][y])189

{190
for(int iL1 = x -1; iL1 <= x + 1; iL1 ++ )191

{192
if(iL1 >= 0 && iL1 <= 15)193

{194
for(int iL2 = y - 1; iL2 <= y + 1; iL2 ++)195

{196
if(iL2 >= 0 && iL2 <= 15)197

{198
agg::gsv_text t; 199
char buf[20] = "";200
t.size( 12.0);201
t.flip(true);202

203
agg::conv_stroke<agg::gsv_text> ts(t);204
ts.width(1.6);205
ts.line_cap(agg::round_cap);206
if(render_engin::pCell[iL1][iL2]->m_checked == false)207

{208
iCount_num ++;209
render_engin::pCell[iL1][iL2]->m_checked = true;210

211
sprintf_s(buf, 2, "%d", p[iL1][iL2] );212
t.start_point((iL1 + 0.3)* 32 , ( iL2 + 0.8) * 32);213
t.text(buf);214
rh.ras.add_path(ts);215
rh.ren.color(agg::rgba8(0, 0, 0)); 216
if(iL1 != x && iL2 != y)217

{218
check_map(p, render_engin::pCell[iL1][iL2]);219
}220
}221
222

223

224
225
agg::render_scanlines(rh.ras, rh.sl, rh.ren);226
if( SDL_BlitSurface(sdl_surface, NULL, sdl_screen, NULL) < 0)227
return ;228
SDL_UpdateRect(sdl_screen, 0,0,0,0); 229

230

231
}232
233
}234
}235
236
}237
}238
}239

在Main函数中,去掉了Windows的消息循环,用SDL的窗口系统代替,代码如下:
1
int APIENTRY _tWinMain(HINSTANCE hInstance,2
HINSTANCE hPrevInstance,3
LPTSTR lpCmdLine,4
int nCmdShow)5


{6
if (!InitInstance ())7

{8
return FALSE;9
}10

11

12
if( SDL_BlitSurface(sdl_surface, NULL, sdl_screen, NULL) < 0)13
return false;14
SDL_UpdateRect(sdl_screen, 0, 0, 0, 0);15

16
bool quit = false;17

18
agg::rgba8 t_color(0, 0, 0);19
render_engin::cell* pcurrent_cell;20
int x = 0; 21
int y = 0;22
int prev_x = -1;23
int prev_y = -1;24
bool m_init = false;25

26

27
while(SDL_WaitEvent(&sdl_event) && !quit)28

{29
switch(sdl_event.type)30

{31
case SDL_MOUSEBUTTONUP:32

{33
bool left_down = (sdl_event.button.button == SDL_BUTTON_LEFT);34
bool right_down = (sdl_event.button.button == SDL_BUTTON_RIGHT);35

36
if(m_init == false)37

{38
m_init = true;39
init_map(map);40
map[x + 1][y + 1] = 0;41
init_count_map(map, count_map);42
} 43

44
//字体对象,本质是一个0101这样的位图,保存于数组45
//顺便讲一下,字体有两种,一种是设备相关的,一种是设备无关的46
//另外一种分法就是位图字体与矢量字体47
agg::gsv_text t; 48
char buf[20] = "";49
t.size( 12.0);50
t.flip(true);51

52
//conv代表conversion,在agg里面 最开始的东西可能只是一个顶点信息,路径信息。53
//经过matrix平移缩放旋转之后,生成的屏幕坐标就称为conv_*,再经过裁剪 光栅化之后,54
//生成像素值还是称为conv,conv就是一个转换的意思,55
//agg里面非常喜欢一层套一层 56
//agg::rendering_buffer rbuf;57
//agg::pixfmt_rgba32 pixfmt;58
//agg::renderer_base<agg::pixfmt_rgba32> renb;59
//这样做其实是这么回事,rbuf用一个attach方法绑定一块裸的内存区。60
//这么做得实质是rbuf类对象(也就是rendering_buffer类)内有个 char* m_data;变量61
//只需要将m_data = 裸的内存区,然后rendering_buffer内封装了一些方法,属性,比如返回内存区的长度,宽度啊62
//等信息,pixfmt就是多封装了一个像素格式,可以存取每个点的颜色值 rgba分量63
//所以一层一层的套,说明白了就是针对一快内存区,不断的提供一些写好的方法,64
//每多套一层,方法越多,封装越复杂,agg给我们提供了不同层次的封装,像是一些进口车零件65
//cairo windows GDI等则相当与一台破的国产车,虽然好用,但是低效,agg内的算法则高明的多66
agg::conv_stroke<agg::gsv_text> ts(t);67
ts.width(1.6);68
ts.line_cap(agg::round_cap);//这里是设置线的拐角处为圆69

70
if(iCount_num == 256)71

{72
::MessageBoxA(NULL,"Yeah, you win!",NULL,MB_OK);;73
}74

75
if(left_down) // 单击左键76

{ 77
if(pcurrent_cell->m_checked == false) //如果这个点还没有被扫过78

{79
iCount_num ++;80
pcurrent_cell->m_checked = true;81
if(map[x + 1][y + 1] == 0) //当前点不是炸弹82

{83
sprintf_s(buf, 2, "%d", count_map[x][y] );84
t.start_point((x + 0.3)* 32 , ( y + 0.8) * 32);85
t.text(buf);86
rh.ras.add_path(ts);87
rh.ren.color(agg::rgba8(0, 0, 0));88
check_map(count_map, pcurrent_cell); //做检查,看看是否需要连环展开,并标记当前点周围的炸弹数89
}90
else //当前点为炸弹91

{92
//this place is a bomb93
pcurrent_cell->m_is_bomb = true;94
agg::ellipse ec(x * 32 + 16, (y + 0.5) * 32, 16, 16, 20);95
rh.ras.add_path(ec);96
rh.ren.color(agg::rgba8(0, 0, 0));97
SDL_WM_SetCaption("Sorry you lose your game!", NULL);98
99
//展开剩下的炸弹100
for(int iL1 = 0; iL1 < 16; iL1 ++)101

{102
for(int iL2 = 0; iL2 < 16; iL2 ++)103

{104
if(render_engin::pCell[iL1][iL2]->m_checked == false)105

{106
render_engin::pCell[iL1][iL2]->m_checked = true;107
if(map[iL1 + 1][iL2 + 1] != 0) 108

{109
pcurrent_cell->m_is_bomb = true;110
agg::ellipse ec(iL1 * 32 + 16, (iL2 + 0.5) * 32, 16, 16, 20);111
rh.ras.add_path(ec);112
rh.ren.color(agg::rgba8(0, 0, 0));113
}114
else115

{116
sprintf_s(buf, 2, "%d", count_map[iL1][iL2] );117
t.start_point((iL1 + 0.3)* 32 , ( iL2 + 0.8) * 32);118
t.text(buf);119
rh.ras.add_path(ts);120
rh.ren.color(agg::rgba8(0, 0, 0));121
}122
agg::render_scanlines(rh.ras, rh.sl, rh.ren);123
if( SDL_BlitSurface(sdl_surface, NULL, sdl_screen, NULL) < 0)124
return false;125
SDL_UpdateRect(sdl_screen, 0,0,0,0); 126
}127
}128
}129
GetLocalTime(&sys2);130
int minute = sys2.wMinute - sys1.wMinute;131
int second = sys2.wSecond - sys1.wSecond;132
::MessageBoxA(NULL,"sorry you lose your game!",NULL,MB_OK);;133
} 134
}135
136
137
}else if(right_down) //标记炸弹,138

{139
if(pcurrent_cell->m_checked == false)//没被检查,140

{141
142
pcurrent_cell->m_checked = true;143
if(map[x + 1][y + 1] != 0) //确实是炸弹144

{145
iCount_num ++;146
pcurrent_cell->m_is_bomb = true;//确实是炸弹,则将是炸弹设为真147
} 148
agg::ellipse ec(x * 32 + 16, (y + 0.5) * 32, 16, 16, 20);149
rh.ras.add_path(ec);150
rh.ren.color(agg::rgba8(255, 255, 0));151
}152
else//已经检查过,此时有回退功能,将已经检查的地方重新设为盲区153

{ //这里有点小问题,本来是应该只有我们假定是雷的区域才能回退,这里数字也退了154
//没做判断,扫雷真的比较麻烦,呵呵 155
iCount_num --;156
pcurrent_cell->m_checked = false;157

158
if(y % 2 == 0 && x % 2 == 1)159

{160
pcurrent_cell->Draw_Cell(agg::rgba8(200, 122, 50)); 161
}162
else if(y % 2 == 1 && x % 2 == 0)163

{164
pcurrent_cell->Draw_Cell(agg::rgba8(232, 0, 210)); 165
}166
else if(y % 2 == 0 && x % 2 == 0)167

{168
pcurrent_cell->Draw_Cell(agg::rgba8(132, 110, 210)); 169
}170
else if(y % 2 == 1 && x % 2 == 1)171

{172
pcurrent_cell->Draw_Cell(agg::rgba8(0, 232, 210)); 173
}174

175
rh.render_cell(*pcurrent_cell);176
}177

178
179
}180

181
agg::render_scanlines(rh.ras, rh.sl, rh.ren);182
if( SDL_BlitSurface(sdl_surface, NULL, sdl_screen, NULL) < 0)183
return false;184
SDL_UpdateRect(sdl_screen, 0,0,0,0); 185

186
break;187
}188
case SDL_MOUSEBUTTONDOWN:189

{190
191
192
break;193
}194
case SDL_MOUSEMOTION:195

{196
//这里获取x y值,本来不必要在这里获取的,只是我想做鼠标移动的特效,197
//后来把特效撤消了,所以在这里保存了x y 值198
x = sdl_event.motion.x / render_engin::cell_width;199
y = sdl_event.motion.y / render_engin::cell_height;200
pcurrent_cell = render_engin::pCell[x][y];201

202
prev_x = x;203
prev_y = y;204

205

206

207

208
break;209
}210
211
case SDL_QUIT:212
quit = true;213
break;214

215
default:216
break;217
}218
}219

220
return 0;221
}
这个程序做得并不好 ,只是简单的做了些功能进去,当时只是为了熟悉Agg随便做着玩的,好了 最后上一张游戏GameOver的图。

浙公网安备 33010602011771号