├── README.md ├── img ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg └── structure.jpg ├── 源代码 └── main.cpp ├── 环境配置文件 ├── easyx.h └── graphics.h └── 程序操作说明书 └── 操作说明.docx /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ##### 1 引言 4 | 5 | 《球球大作战》虽然在玩法上类似于大球吃小球的模式看起来很单薄。但是在游戏过程中会出现无数种意外情况,这就需要玩家运用一系列策略来达到不被吃和吃掉别人球的目的,大大增加了游戏的耐玩性。游戏彻底抛弃了枯燥无味的单机模式,采取全球玩家联网实时对战。当竞技在PC端打的火热的时候,《球球大作战》率先引进了微竞技的新模式,让玩家在休闲的同时还能体验到竞技的乐趣,这种尝试也博得了一片称赞声。 6 | 7 | 8 | 9 | 1.1 编写目的 10 | 11 | 球球大作战在在最近几年风靡全球,其版本也在不断迭代。本项目取其最核心的玩 12 | 13 | 法,给玩家一个最纯粹的休闲游戏体验。此外《球球大作战(简易版)》是基于Easyx图形库开发出来的,在用户界面显示方面表现良好,相信一定会给用户一个不错的游戏体验。 14 | 15 | 16 | 17 | 1.2 开发背景 18 | 19 | 《球球大作战》是民族自研,积极健康休闲竞技游戏的代表。轻快明亮的画风、休闲有趣的玩法、富有深度的竞技性,使其成为国产手游创新模范。作为目前最具人气的移动电竞游戏,《球球大作战》累积用户数已超过1.7亿,最高同时在线人数突破175万。月活跃用户达到6000万。年轻化也是《球球大作战》的一大特色,朝气活力的学生群体是游戏主力军,95后及00后用户占比极高。 20 | 21 | 22 | 23 | 1.3 可行性分析 24 | 25 | 1.3.1 经济可行性 26 | 27 | 本项目是基于C语言开发,开发平台我们选择Visual Studio 2017 Community,此版本为免费版,由Microsoft公司免费提供给开发者使用;主流的开发环境有Windows和Linux,我们选择使用Windows进行开发,因为Windows我们比较熟悉,方便我们快速入手项目开发,所以需要一台安装了Windows操作系统的计算机;此外还使用了Easyx图形库,这个图形库也是免费提供给开发者使用和学习的。因此,总的经济方面付出不高,故经济可行性较高。 28 | 29 | 30 | 31 | 1.3.2 技术可行性 32 | 33 | 本项目大部分使用了C语言的语法,还使用少量的C++语法,调用了Easyx图形库。C语言方面,大部分知识是可以比较容易实现的,少部分通过翻书和查阅资料也是能够实现的。C++方面的语法,我们则较为不熟悉,需要花费较多的时间去了解。 34 | 35 | 36 | 37 | 1.4 问题定义 38 | 39 | 完成一个简易版的球球大作战项目,如何实现大球吃小球的功能?如何实现小球的移动和地图的刷新?如何实现游戏的暂停?等等...... 40 | 41 | 42 | 43 | 1.5 参考文献 44 | 45 | [1] 叶安胜, 鄢涛. C语言综合项目实战[M]. 科学出版社, 2015. 46 | 47 | [2] David Griffiths. 深入浅出C语言. 东南大学出版社, 2013. 48 | 49 | [3] Stephen Prata. C Primer Plus(第6版 中文版). 人民邮电出版社, 2016. 50 | 51 | [4] [啊哈磊](http://search.dangdang.com/?key2=%B0%A1%B9%FE%C0%DA&medium=01&category_path=01.00.00.00.00.00). 啊哈C语言!逻辑的挑战(修订版). [电子工业出版社](http://search.dangdang.com/?key3=%B5%E7%D7%D3%B9%A4%D2%B5%B3%F6%B0%E6%C9%E7&medium=01&category_path=01.00.00.00.00.00), 2017. 52 | 53 | [5] [啊哈磊](http://search.dangdang.com/?key2=%B0%A1%B9%FE%C0%DA&medium=01&category_path=01.00.00.00.00.00). 啊哈!算法. 人民邮电出版社, 2014. 54 | 55 | [6] https://www.baidu.com/[百度] 56 | 57 | [7] https://easyx.cn/[Easyx] 58 | 59 | 60 | 61 | ##### 2 需求分析 62 | 63 | 2.1 需求分析图 64 | 65 | ![](img/structure.jpg) 66 | 67 | 68 | 69 | 2.2 功能需求分析 70 | 71 | 1)地图。利用easyx图形库生成一个适当大小的白色背景地图,和在右上角生成一个浅灰色的小地图用于显示玩家和AI的缩略图与位置。 72 | 73 | 2)文件存储。利用文件操作存储累计游戏次数和游戏时间。 74 | 75 | 3)玩家。初始化玩家球(包括坐标、生命值、颜色、大小、形状)。 76 | 77 | 4)AI(机器人)。初始化AI,生成指定数量的AI,每个AI随机坐标,生命值均为1,颜色随机,大小固定与玩家相同,形状为圆形。 78 | 79 | 5)生命。玩家和AI初始生命值均为1表示活,死后生命值变为0。 80 | 81 | 6)食物。随机位置生成指定数量的食物,颜色随机,大小在一定范围内随机,形状为圆形、椭圆形、圆角矩形等随机。 82 | 83 | 7)思路。玩家通过方向键控制球移动;大球吃小球,若a.r>b.r且a和b 圆心距dr <= 0) 305 | ball->life = false; 306 | if (ball->life == false) { // 判定游戏是否接束 307 | HWND hwnd = GetHWnd(); 308 | MessageBox(hwnd, _T("你被吃了"), _T("游戏结束"), MB_ICONEXCLAMATION); 309 | endtime(); 310 | 311 | } 312 | 313 | if (eaten + ai_eaten == AINUM) // 是否吃掉所有 AI 314 | { 315 | HWND hwnd = GetHWnd(); 316 | MessageBox(hwnd, _T("恭喜过关"), _T("游戏结束"), MB_OK | MB_ICONEXCLAMATION); // 结束 317 | endtime(); 318 | } 319 | ``` 320 | 321 | 4.1.5 玩家的移动 322 | 323 | 向键控制移动: 324 | 325 | ```c 326 | static int mx = 0, my = 0; // 记录偏移量 327 | 328 | if (GetAsyncKeyState(VK_UP) && (ball->y - ball->r > 0 && ball->y <= (MAPH - ball->r + 10))) { 329 | ball->y -= speed; 330 | my += speed; 331 | } 332 | if (GetAsyncKeyState(VK_DOWN) && (ball->y - ball->r >= -10 && ball->y < (MAPH - ball->r))) { 333 | ball->y += speed; 334 | my -= speed; 335 | } 336 | if (GetAsyncKeyState(VK_LEFT) && ball->x - ball->r > 0 && (ball->x <= (MAPW - ball->r + 10))) { 337 | ball->x -= speed; 338 | mx += speed; 339 | } 340 | if (GetAsyncKeyState(VK_RIGHT) && ball->x - ball->r >= -10 && (ball->x < (MAPW - ball->r))) { 341 | ball->x += speed; 342 | mx -= speed; 343 | } 344 | ``` 345 | 346 | 4.1.6 游戏暂停 347 | 348 | 按空格键游戏暂停: 349 | 350 | ```c 351 | if (GetAsyncKeyState(VK_SPACE)) { 352 | settextcolor(WHITE); 353 | settextstyle(32, 0, _T("宋体")); 354 | outtextxy(384 - mx, 350 - my, _T("游戏已暂停!")); 355 | outtextxy(20 - mx, 500 - my, _T("(ESC)退出")); 356 | outtextxy(780 - mx, 500 - my, _T("(回车键)继续")); 357 | FlushBatchDraw(); 358 | getch(); 359 | if (GetAsyncKeyState(VK_ESCAPE)) 360 | exit(0); 361 | else 362 | getch(); 363 | } 364 | 365 | ``` 366 | 367 | 4.1.7 食物的生成 368 | 369 | 食物的随机生成: 370 | 371 | ```c 372 | void Food() { 373 | for (int i = 0; i < FNUM; i++) { // 食物刷新 374 | if (food[i].eat == 0) { 375 | food[i].eat = 1; 376 | food[i].color = RGB(rand() % 256, rand() % 256, rand() % 256); 377 | food[i].x = rand() % MAPW; 378 | food[i].y = rand() % MAPH; 379 | food[i].type = rand() % 10 + 1; 380 | } 381 | } 382 | } 383 | ``` 384 | 385 | 绘制食物: 386 | 387 | ```c 388 | for (int i = 0; i < FNUM; i++) { // 画出食物 389 | if (food[i].eat == 0) continue; 390 | setfillcolor(food[i].color); 391 | switch (food[i].type) { // 形状 392 | case 1: solidellipse(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4); break; 393 | case 2: solidellipse(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2); break; 394 | case 3: solidrectangle(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2); break; 395 | case 4: solidrectangle(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4); break; 396 | case 5: solidroundrect(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4, 2, 2); break; 397 | case 6: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 2, 2); break; 398 | case 7: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 4, 2); break; 399 | case 8: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 2, 4); break; 400 | case 9: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 1, 1); break; 401 | case 10: fillcircle(food[i].x, food[i].y, 4); break; 402 | } 403 | } 404 | ``` 405 | 406 | 4.2 重难点分析 407 | 408 | 4.2.1 时间的计算:调用“time.h”头文件。 409 | 410 | 时间的计算: 411 | 412 | ```c 413 | clock_t start_t, end_t; 414 | int total_t; 415 | ... 416 | end_t = clock(); 417 | total_t = (end_t - start_t); 418 | ... 419 | total_t = readTime() + total_t; 420 | writeTime(total_t); 421 | ``` 422 | 423 | 424 | 425 | 4.2.2 游戏结束的判断:通过为生命赋值来解决,true表示存活,false表示死亡。 426 | 427 | 游戏结束的判断: 428 | 429 | ```c 430 | if (ball->r <= 0) 431 | ball->life = false; 432 | if (ball->life == false) { // 判定游戏是否接束 433 | HWND hwnd = GetHWnd(); 434 | MessageBox(hwnd, _T("你被吃了"), _T("游戏结束"), MB_ICONEXCLAMATION); 435 | endtime(); 436 | 437 | } 438 | ``` 439 | 440 | 4.2.3 小地图的绘制:调用了Easyx图形库里面的一些函数得到解决。 441 | 442 | 小地图的绘制: 443 | 444 | ```c 445 | void draw() { 446 | clearcliprgn(); 447 | IMAGE image; 448 | loadimage(&image, _T("../resourse/background.jpg"), WIDTH * 4, HEIGHT * 4); 449 | putimage(0, 0, &image); 450 | setlinestyle(PS_SOLID | PS_JOIN_BEVEL, 20); // 改变笔的颜色、状态 451 | setlinecolor(RGB(0, 100, 0)); 452 | line(-20, MAPH + 20, -20, -20); // 左竖 453 | line(-20, MAPH + 20, MAPW + 20, MAPH + 20); // 上横 454 | line(-20, -20, MAPW + 20, -20); // 下横 455 | line(MAPW + 20, -20, MAPW + 20, MAPH + 20); // 右竖 456 | setfillcolor(GREEN); 457 | 458 | if (mover.x - 0.5 * WIDTH / asp < -20) 459 | floodfill(-20 - 11, mover.y, RGB(0, 100, 0)); 460 | if (mover.x + 0.5 * WIDTH / asp > MAPW + 20) 461 | floodfill(MAPW + 20 + 11, mover.y, RGB(0, 100, 0)); 462 | if (mover.y - 0.5 * HEIGHT / asp < -20) 463 | floodfill(mover.x, -20 - 11, RGB(0, 100, 0)); 464 | if (mover.y + 0.5 * HEIGHT / asp > MAPH + 20) 465 | floodfill(mover.x, MAPH + 20 + 11, RGB(0, 100, 0)); 466 | 467 | setlinecolor(WHITE); 468 | setlinestyle(PS_NULL); 469 | 470 | for (int i = 0; i < FNUM; i++) { // 画出食物 471 | if (food[i].eat == 0) continue; 472 | setfillcolor(food[i].color); 473 | switch (food[i].type) { // 形状 474 | case 1: solidellipse(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4); break; 475 | case 2: solidellipse(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2); break; 476 | case 3: solidrectangle(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2); break; 477 | case 4: solidrectangle(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4); break; 478 | case 5: solidroundrect(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4, 2, 2); break; 479 | case 6: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 2, 2); break; 480 | case 7: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 4, 2); break; 481 | case 8: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 2, 4); break; 482 | case 9: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 1, 1); break; 483 | case 10: fillcircle(food[i].x, food[i].y, 4); break; 484 | } 485 | } 486 | 487 | for (int i = 0; i < AINUM; i++) { // 画 AI 488 | if (ai[i].life == 0) continue; 489 | setfillcolor(ai[i].color); 490 | fillcircle(ai[i].x, ai[i].y, int(ai[i].r + 0.5)); 491 | } 492 | 493 | setfillcolor(mover.color); // 画玩家 494 | fillcircle(mover.x, mover.y, int(mover.r + 0.5)); 495 | 496 | IMAGE map(150, 100); // 小地图 497 | SetWorkingImage(&map); 498 | setbkcolor(RGB(120, 165, 209)); // 浅灰色背景 499 | cleardevice(); 500 | for (int i = 0; i < AINUM; i++) // 画 AI(小地图) 501 | { 502 | if (ai[i].life == 0) continue; 503 | setfillcolor(ai[i].color); 504 | fillcircle(ai[i].x * 150 / WIDTH / 4, ai[i].y * 100 / HEIGHT / 4, int(ai[i].r / 28 + 1.5)); 505 | } 506 | 507 | setfillcolor(mover.color); // 画玩家(小地图) 508 | fillcircle(mover.x * 150 / WIDTH / 4, mover.y * 100 / HEIGHT / 4, int(mover.r / 28 + 3.5)); 509 | setlinecolor(RGB(0, 100, 0)); 510 | 511 | SetWorkingImage(); // 恢复绘图背景 512 | putimage(mover.x + int(0.5 * WIDTH) - 150, mover.y - int(0.5 * HEIGHT), 150, 100, &map, 0, 0); // 画出小地图 513 | setlinecolor(LIGHTBLUE); 514 | setlinestyle(PS_SOLID | PS_JOIN_BEVEL, 4); 515 | line(mover.x + int(0.5 * WIDTH) - 151, mover.y - int(0.5 * HEIGHT), mover.x + int(0.5 * WIDTH) - 151, mover.y - int(0.5 * HEIGHT) + 99); // 地图边框线 516 | line(mover.x + int(0.5 * WIDTH) - 151, mover.y - int(0.5 * HEIGHT) + 99, mover.x + int(0.5 * WIDTH), mover.y - int(0.5 * HEIGHT) + 99); // 地图边框线 517 | 518 | setlinestyle(PS_NULL); // 恢复笔 519 | TCHAR str[32]; 520 | swprintf_s(str, _T("质量:%.1fg 击杀:%d"), mover.r, eaten); 521 | settextcolor(WHITE); // 改字体 522 | outtextxy(mover.x - int(0.5 * WIDTH), mover.y - int(0.5 * HEIGHT), str); 523 | settextcolor(WHITE); 524 | outtextxy(mover.x - 20, mover.y, _T("user")); 525 | } 526 | ``` 527 | 528 | 4.2.4 AI的移动:相距最近的AI相互靠近。 529 | 530 | AI的移动规则: 531 | 532 | ```c 533 | double min_DISTANCE = 100000; 534 | int min = -1; 535 | for (int k = 0; k < AINUM; k++) { // AI 靠近 AI 536 | if (ai[i].r > ai[k].r&&ai[k].life != 0) { 537 | if (DISTANCE(ai[i].x, ai[i].y, ai[k].x, ai[k].y) < min_DISTANCE) { 538 | min_DISTANCE = DISTANCE(ai[i].x, ai[i].y, ai[k].x, ai[k].y); 539 | min = k; 540 | } 541 | } 542 | } 543 | if ((min != -1) && (rand() % 2 == 1)) { 544 | if (rand() % 2) { 545 | if (ai[i].x < ai[min].x) 546 | ai[i].x++; 547 | else 548 | ai[i].x--; 549 | } 550 | else { 551 | if (ai[i].y < ai[min].y) 552 | ai[i].y++; 553 | else 554 | ai[i].y--; 555 | } 556 | } 557 | ``` 558 | 559 | 560 | 561 | **5** **编码与单元测试** 562 | 563 | 5.1 编码(完整代码) 564 | 565 | ```c 566 | #include 567 | #include 568 | #include 569 | #include 570 | #include 571 | #include 572 | 573 | #define WIDTH 1024 // 屏幕宽 574 | #define HEIGHT 576 // 屏幕高 575 | #define MAPW (WIDTH*4) // 地图宽 576 | #define MAPH (HEIGHT*4) // 地图高 577 | #define AINUM 100 // AI 数量 578 | #define FNUM 2000 // FOOD 数量 579 | #define DISTANCE(x1,y1,x2,y2) (sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))) //计算距离 580 | 581 | struct FOOD { 582 | bool eat; 583 | COLORREF color; // 颜色 584 | int x, y; // 坐标 585 | char type; 586 | }; 587 | 588 | struct BALL { //小球结构体 589 | bool life; //生命 590 | COLORREF color; //颜色 591 | int x, y; //坐标 592 | float r; //半径 593 | }; 594 | 595 | FOOD food[FNUM]; //食物 596 | BALL mover = { 1,RGB(0,0,0),0,0,0 }; //玩家 597 | BALL ai[AINUM] = { 1,RGB(0,0,0),0,0,0 }; //AI 598 | 599 | void move(BALL* ball); // 玩家移动 600 | void draw(); // 绘图 601 | void start(); // 游戏开始 602 | void setall(); // 初始化 603 | void AI(); // AI 604 | void Food(); // 食物 605 | void delay(DWORD ms); // 绝对延时 606 | 607 | DWORD* pBuffer; // 显存指针 608 | int eaten = 0; // 吃 AI 的数量 609 | int ai_eaten = 0; // AI 吃 AI的数量 610 | float asp = 1; // 缩放因子 611 | float Time = 0; // 时间 612 | 613 | int main() { 614 | initgraph(WIDTH, HEIGHT); 615 | start(); 616 | setall(); 617 | BeginBatchDraw(); 618 | while (true) { 619 | move(&mover); 620 | AI(); 621 | Food(); 622 | draw(); 623 | FlushBatchDraw(); // 显示缓存的绘制内容 624 | delay(20); 625 | } 626 | } 627 | 628 | void move(BALL* ball) { 629 | if (ball->r <= 0) 630 | ball->life = false; 631 | if (ball->life == false) { // 判定游戏是否接束 632 | HWND hwnd = GetHWnd(); 633 | MessageBox(hwnd, _T("你被吃了"), _T("游戏结束"), MB_ICONEXCLAMATION); 634 | closegraph(); 635 | exit(0); //可以增加重新游戏功能 636 | } 637 | 638 | if (eaten + ai_eaten == AINUM) // 是否吃掉所有 AI 639 | { 640 | HWND hwnd = GetHWnd(); 641 | MessageBox(hwnd, _T("恭喜过关"), _T("游戏结束"), MB_OK | MB_ICONEXCLAMATION); // 结束 642 | closegraph(); 643 | exit(0); 644 | } 645 | 646 | for (int i = 0; i < AINUM; i++) { // 玩家吃 AI 判定 647 | if (ball->r >= ai[i].r) { 648 | if (ai[i].life == 0) continue; 649 | if (DISTANCE(ball->x, ball->y, ai[i].x, ai[i].y) < (4 / 5.0 * (ball->r + ai[i].r))) { 650 | ai[i].life = 0; //AI被吃 651 | ball->r = sqrt(ai[i].r*ai[i].r + ball->r*ball->r); 652 | eaten++; 653 | } 654 | } 655 | } 656 | 657 | for (int n = 0; n < FNUM; n++) { // 玩家吃食物 658 | if (food[n].eat == 0) continue; 659 | if (DISTANCE(ball->x, ball->y, food[n].x, food[n].y) < ball->r) { 660 | ball->r += 4 / ball->r; // 增加面积 661 | food[n].eat = 0; // 食物被吃 662 | } 663 | } 664 | 665 | static int mx = 0, my = 0; // 记录偏移量 666 | 667 | if (GetAsyncKeyState(VK_UP) && (ball->y - ball->r > 0 && ball->y <= (MAPH - ball->r + 10))) { 668 | ball->y -= 4; 669 | my += 4; 670 | } 671 | if (GetAsyncKeyState(VK_DOWN) && (ball->y - ball->r >= -10 && ball->y < (MAPH - ball->r))) { 672 | ball->y += 4; 673 | my -= 4; 674 | } 675 | if (GetAsyncKeyState(VK_LEFT) && ball->x - ball->r > 0 && (ball->x <= (MAPW - ball->r + 10))) { 676 | ball->x -= 4; 677 | mx += 4; 678 | } 679 | if (GetAsyncKeyState(VK_RIGHT) && ball->x - ball->r >= -10 && (ball->x < (MAPW - ball->r))) { 680 | ball->x += 4; 681 | mx -= 4; 682 | } 683 | setorigin(mx, my); //坐标修正 684 | } 685 | 686 | 687 | void AI() { 688 | for (int i = 0; i < AINUM; i++) { // AI 吃玩家 689 | if (ai[i].r > mover.r) { 690 | if (DISTANCE(mover.x, mover.y, ai[i].x, ai[i].y) < (ai[i].r + mover.r)) { 691 | ai[i].r = sqrt(ai[i].r*ai[i].r + mover.r*mover.r); 692 | mover.life = 0; 693 | mover.r = 0; 694 | } 695 | } 696 | for (int j = 0; j < AINUM; j++) { // AI 吃 AI 697 | if (ai[i].r > ai[j].r) { 698 | if (ai[j].life == 0) continue; 699 | if (DISTANCE(ai[i].x, ai[i].y, ai[j].x, ai[j].y) < (ai[i].r + ai[j].r)) { 700 | ai[i].r = sqrt(ai[i].r*ai[i].r + ai[j].r*ai[j].r); 701 | ai[j].life = 0; 702 | ai[j].r = 0; 703 | ai_eaten++; 704 | } 705 | } 706 | } 707 | 708 | double min_DISTANCE = 100000; 709 | int min = -1; 710 | for (int k = 0; k < AINUM; k++) { // AI 靠近 AI 711 | if (ai[i].r > ai[k].r&&ai[k].life != 0) { 712 | if (DISTANCE(ai[i].x, ai[i].y, ai[k].x, ai[k].y) < min_DISTANCE) { 713 | min_DISTANCE = DISTANCE(ai[i].x, ai[i].y, ai[k].x, ai[k].y); 714 | min = k; 715 | } 716 | } 717 | } 718 | if ((min != -1) && (rand() % 2 == 1)) { 719 | if (rand() % 2) { 720 | if (ai[i].x < ai[min].x) 721 | ai[i].x++; 722 | else 723 | ai[i].x--; 724 | } 725 | else { 726 | if (ai[i].y < ai[min].y) 727 | ai[i].y++; 728 | else 729 | ai[i].y--; 730 | } 731 | } 732 | for (int n = 0; n < FNUM; n++) { // AI 吃食物 733 | if (food[n].eat == 0) continue; 734 | if (DISTANCE(ai[i].x, ai[i].y, food[n].x, food[n].y) < ai[i].r) { 735 | ai[i].r += 4 / ai[i].r; 736 | food[n].eat = 0; 737 | } 738 | } 739 | } 740 | } 741 | 742 | void Food() { 743 | for (int i = 0; i < FNUM; i++) { // 食物刷新 744 | if (food[i].eat == 0) { 745 | food[i].eat = 1; 746 | food[i].color = RGB(rand() % 256, rand() % 256, rand() % 256); 747 | food[i].x = rand() % MAPW; 748 | food[i].y = rand() % MAPH; 749 | food[i].type = rand() % 10 + 1; 750 | } 751 | } 752 | } 753 | 754 | void draw() { 755 | clearcliprgn(); 756 | setlinestyle(PS_SOLID | PS_JOIN_BEVEL, 20); // 改变笔的颜色、状态 757 | setlinecolor(RGB(0, 100, 0)); 758 | line(-20, MAPH + 20, -20, -20); // 左竖 759 | line(-20, MAPH + 20, MAPW + 20, MAPH + 20); // 上横 760 | line(-20, -20, MAPW + 20, -20); // 下横 761 | line(MAPW + 20, -20, MAPW + 20, MAPH + 20); // 右竖 762 | setfillcolor(GREEN); 763 | 764 | if (mover.x - 0.5 * WIDTH / asp < -20) 765 | floodfill(-20 - 11, mover.y, RGB(0, 100, 0)); 766 | if (mover.x + 0.5 * WIDTH / asp > MAPW + 20) 767 | floodfill(MAPW + 20 + 11, mover.y, RGB(0, 100, 0)); 768 | if (mover.y - 0.5 * HEIGHT / asp < -20) 769 | floodfill(mover.x, -20 - 11, RGB(0, 100, 0)); 770 | if (mover.y + 0.5 * HEIGHT / asp > MAPH + 20) 771 | floodfill(mover.x, MAPH + 20 + 11, RGB(0, 100, 0)); 772 | 773 | setlinecolor(WHITE); 774 | setlinestyle(PS_NULL); 775 | 776 | for (int i = 0; i < FNUM; i++) { // 画出食物 777 | if (food[i].eat == 0) continue; 778 | setfillcolor(food[i].color); 779 | switch (food[i].type) { // 形状 780 | case 1: solidellipse(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4); break; 781 | case 2: solidellipse(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2); break; 782 | case 3: solidrectangle(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2); break; 783 | case 4: solidrectangle(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4); break; 784 | case 5: solidroundrect(food[i].x, food[i].y, food[i].x + 2, food[i].y + 4, 2, 2); break; 785 | case 6: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 2, 2); break; 786 | case 7: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 4, 2); break; 787 | case 8: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 2, 4); break; 788 | case 9: solidroundrect(food[i].x, food[i].y, food[i].x + 4, food[i].y + 2, 1, 1); break; 789 | case 10: fillcircle(food[i].x, food[i].y, 4); break; 790 | } 791 | } 792 | 793 | for (int i = 0; i < AINUM; i++) { // 画 AI 794 | if (ai[i].life == 0) continue; 795 | setfillcolor(ai[i].color); 796 | fillcircle(ai[i].x, ai[i].y, int(ai[i].r + 0.5)); 797 | } 798 | 799 | setfillcolor(mover.color); // 画玩家 800 | fillcircle(mover.x, mover.y, int(mover.r + 0.5)); 801 | 802 | IMAGE map(150, 100); // 小地图 803 | SetWorkingImage(&map); 804 | setbkcolor(RGB(120, 165, 209)); // 浅灰色背景 805 | cleardevice(); 806 | for (int i = 0; i < AINUM; i++) // 画 AI(小地图) 807 | { 808 | if (ai[i].life == 0) continue; 809 | setfillcolor(ai[i].color); 810 | fillcircle(ai[i].x * 150 / WIDTH / 4, ai[i].y * 100 / HEIGHT / 4, int(ai[i].r / 28 + 0.5)); 811 | } 812 | 813 | setfillcolor(mover.color); // 画玩家(小地图) 814 | fillcircle(mover.x * 150 / WIDTH / 4, mover.y * 100 / HEIGHT / 4, int(mover.r / 28 + 0.5)); 815 | setlinecolor(RGB(0, 100, 0)); 816 | 817 | SetWorkingImage(); // 恢复绘图背景 818 | putimage(mover.x + int(0.5 * WIDTH) - 150, mover.y - int(0.5 * HEIGHT), 150, 100, &map, 0, 0); // 画出小地图 819 | setlinecolor(LIGHTBLUE); 820 | setlinestyle(PS_SOLID | PS_JOIN_BEVEL, 4); 821 | line(mover.x + int(0.5 * WIDTH) - 151, mover.y - int(0.5 * HEIGHT), mover.x + int(0.5 * WIDTH) - 151, mover.y - int(0.5 * HEIGHT) + 99); // 地图边框线 822 | line(mover.x + int(0.5 * WIDTH) - 151, mover.y - int(0.5 * HEIGHT) + 99, mover.x + int(0.5 * WIDTH), mover.y - int(0.5 * HEIGHT) + 99); // 地图边框线 823 | 824 | setlinestyle(PS_NULL); // 恢复笔 825 | TCHAR str[32]; 826 | swprintf_s(str, _T("质量:%.1fg 击杀:%d"), mover.r, eaten); 827 | settextcolor(BLUE); // 改字体 828 | outtextxy(mover.x - int(0.5 * WIDTH), mover.y - int(0.5 * HEIGHT), str); 829 | settextcolor(BLUE); 830 | outtextxy(mover.x - 36, mover.y - 8, _T("user name")); 831 | } 832 | 833 | void setall() { 834 | srand((unsigned)time(NULL)); // 随机数 835 | mover.color = RGB(rand() % 256, rand() % 256, rand() % 256); // 随机颜色 836 | mover.life = 1; // 赋初值1 837 | mover.x = int(WIDTH*0.5); 838 | mover.y = int(HEIGHT*0.5); 839 | mover.r = 20; 840 | 841 | for (int i = 0; i < AINUM; i++) { // AI 的属性 842 | ai[i].life = 1; 843 | ai[i].color = RGB(rand() % 256, rand() % 256, rand() % 256); 844 | ai[i].r = float(rand() % 10 + 10); 845 | ai[i].x = rand() % (MAPW - int(ai[i].r + 0.5)) + int(ai[i].r + 0.5); 846 | ai[i].y = rand() % (MAPH - int(ai[i].r + 0.5)) + int(ai[i].r + 0.5); 847 | } 848 | 849 | for (int i = 0; i < FNUM; i++) // 食物的属性 850 | { 851 | food[i].eat = 1; 852 | food[i].color = RGB(rand() % 256, rand() % 256, rand() % 256); 853 | food[i].x = rand() % MAPW; 854 | food[i].y = rand() % MAPH; 855 | food[i].type = rand() % 10 + 1; 856 | } 857 | 858 | pBuffer = GetImageBuffer(NULL); // 获取显存指针 859 | setbkcolor(WHITE); // 白色背景 860 | cleardevice(); // 初始化背景 861 | settextcolor(LIGHTRED); // 改字体 862 | setbkmode(TRANSPARENT); 863 | settextstyle(16, 0, _T("宋体")); 864 | 865 | } 866 | 867 | void delay(DWORD ms) // 绝对延时 868 | { 869 | static DWORD oldtime = GetTickCount(); 870 | 871 | while (GetTickCount() - oldtime < ms) 872 | Sleep(1); 873 | 874 | oldtime = GetTickCount(); 875 | } 876 | 877 | void start() 878 | { 879 | setbkcolor(WHITE); // 白色背景 880 | cleardevice(); // 初始化背景 881 | settextcolor(RED); // 改字体 882 | setbkmode(TRANSPARENT); 883 | settextstyle(128, 0, _T("宋体")); 884 | outtextxy(320, 40, _T("贪婪球")); 885 | settextstyle(32, 0, _T("宋体")); 886 | outtextxy(740, 135, _T("Ver 1.0")); 887 | settextcolor(BLUE); 888 | outtextxy(170, 240, _T(" ↑ 上移  ↓ 下移  ← 左移  → 右移 ")); 889 | settextcolor(GREEN); 890 | outtextxy(112, 340, _T("躲避大球   追补小球   贪吃食物   增强实力")); 891 | settextcolor(BLACK); 892 | settextstyle(32, 0, _T("宋体")); 893 | outtextxy(384, 500, _T("按任意键开始游戏")); 894 | settextstyle(20, 0, _T("宋体")); 895 | outtextxy(810, 10, _T("2018级软件工程1班")); 896 | outtextxy(810, 30, _T("贺巍")); 897 | outtextxy(810, 50, _T("201810414113")); 898 | _getch(); 899 | } 900 | ``` 901 | 902 | 5.2 单元测试 903 | 904 | 5.2.1 测试理论 905 | 906 | 在一个系统的开发过程中,出现一些错误是在所难免的。硬件实现时的功能问题和软件实现时的语法错误,在初期实现过程就会很容易被发现。对于这些错误,大部分编译工具都会在运行时自动提示,并要求纠正。但是在设计中的一些逻辑错误就不那么容易被发现。由于这些错误的隐蔽性极强,所以在系统正式运行前,对其进行全面的测试是非常有必要的。 907 | 908 | 5.2.2 单元测试 909 | 910 | | 测试内容 | 测试方法 | 预知结果 | 测试情况 | 911 | | -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------- | 912 | | 关卡选择 | 1.根据提示信息选择合适关卡。 2.进入游戏观察是否为所选关卡。 | 不同的关卡小球移动不同,关卡数越高,移动越慢,游戏难度越高。 | 通过 | 913 | | 游戏开始 | 1.按数字键选择关卡开始游戏。 2.按回车键开始游戏,默认关卡为关卡1。 | 按不同的数字键进入相应的关卡,按回车键进入关卡1,游戏开始,进入游戏界面。 | 通过 | 914 | | 游戏暂停 | 1.游戏中按空格键可以暂停游戏,所有小球全部停止移动。 2.暂停中按回车键可以继续游戏,所有静止小球开始移动。 | 游戏中按空格键,小球全部停止移动,并提示游戏已暂停;随后按回车键,提示消失,游戏继续,小球开始移动。 | 通过 | 915 | | 游戏计时和计次 | 1.游戏开始时读取保存游戏次数的文件,并打印在游戏开始界面。 2.游戏结束时读取并写入游戏累计时间,并打印在游戏结束界面,写入新的游戏累计次数。 | 在游戏开始界面显示游戏累计运行次数,关闭程序并再一次打开,游戏累计运行次数增加1。游戏结束界面显示游戏累计运行时间,包括本次游戏累计时间和游戏运行总时间。 | 通过 | 916 | 917 | 918 | 919 | #### **总结** 920 | 921 | 本文档论述了球球大作战(简易版)的功能、实现及不足之处。该项目最大的亮点在于还原的最原始的、最休闲的、最纯粹的球球大作战模式,游戏规则只有一条,吃光其他所有球,总的来说,游戏难度并不高,比较任意获得胜利,其次单局游戏时间较短,几分钟就能完成一局。 922 | 923 | 本项目是我们第一次以团队协作的方式来完成的,无论是完成项目的时间还是修改的代码量,都超出来以前任何一次的工作量,故程序还有不少的不足之处。此外,在做项目的过程中,遇到了各种各样的错误,几乎每一种错误都花费了巨量的时间,甚至有一些错误至今还未能成功修改,只能通过修改程序功能以求尽可能完善。 924 | 925 | 除此之外,本项目还使用大量的Easyx图形库中的函数,这是我第一次接触到这个图形库,尽管其中大部分函数与EGE中的函数类似,但也花费了不少时间去熟悉这个图形库。但也多亏使用了Easyx图形库才使得程序界面多姿多彩,也算是本项目的一个主要的亮点,就因为如此,才会引起玩家的兴趣。 926 | 927 | 总体来说,这个项目花了较多时间去完成,游戏体验也比较良好,基本上达到了预期的效果。游戏开发是一个漫长的过程,但只有经历过这个过程才能得到真的的收获。 -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/img/1.jpg -------------------------------------------------------------------------------- /img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/img/2.jpg -------------------------------------------------------------------------------- /img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/img/3.jpg -------------------------------------------------------------------------------- /img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/img/4.jpg -------------------------------------------------------------------------------- /img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/img/5.jpg -------------------------------------------------------------------------------- /img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/img/6.jpg -------------------------------------------------------------------------------- /img/structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/img/structure.jpg -------------------------------------------------------------------------------- /源代码/main.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/源代码/main.cpp -------------------------------------------------------------------------------- /环境配置文件/easyx.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/环境配置文件/easyx.h -------------------------------------------------------------------------------- /环境配置文件/graphics.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/环境配置文件/graphics.h -------------------------------------------------------------------------------- /程序操作说明书/操作说明.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hw-sudo/Big_fight/5b1f9cac7b9a5481ce5c5fc72416653a6835c88f/程序操作说明书/操作说明.docx --------------------------------------------------------------------------------