基于Agg的扫雷程序实现(下)
初始化了雷盘之后,玩家开始点击雷盘中的小格子,我们暂且称之为Cell,这时候初始化雷盘,要保证当前点击的地方不为炸弹,否则,一开始玩就被KO了。
1、初始化雷盘。初始化代码主要放在InitInstance 函数中,如下:
1BOOL 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 1
9 render_engin::initSDL(rh, sdl_screen, sdl_surface, buffer, render_engin::width, render_engin::height);
10
11 //segment 2
12 rh.init_scene(render_engin::width / 32, render_engin::height / 32);
13 return TRUE;
14}
15
16//segment 1
17
18bool 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 2
38
39namespace render_engin{
40
41
42int width = 512;
43int height = 512;
44render_engin::cell* pCell[16][16];
45
46class render_handler{
47public:
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
97public:
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*18
2//这样做得好处是在计算每个16*16的每个点周围有几个炸弹,
3//边界点我们可以在18 * 18的地图内设为0
4void 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 }else
22 {
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 }else
39 {
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 }else
56 {
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 }else
74 {
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 }else
92 {
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 }else
110 {
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 < 16
128void 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//如果找到,则进行自动展开,这是个递归程序
163void 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的窗口系统代替,代码如下:
1int 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 bomb
93 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 else
115 {
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的图。