zoukankan      html  css  js  c++  java
  • 一步一步实现扫雷游戏(C语言实现)(四)

    此项目相关博文链接

    一步一步实现扫雷游戏(C语言实现)(一)

    一步一步实现扫雷游戏(C语言实现)(二)

    一步一步实现扫雷游戏(C语言实现)(三)

    一步一步实现扫雷游戏(C语言实现)(四)

      唉!说来就惭愧啊!几星期前我就开始使用win32写着个程序,但是到了中途碰到了许多钉子。以前我写了几篇博客记录以前写的代码,但是那是不成功的代码。今天我把扫雷游戏的基本过程实现了,下面一一介绍:

     

    思维导图:



      1.首先说说初始状态时后台随机载入地雷分布的部分,我把这部分用类封装了:

    class RandMap
    {
    private:
    int map[MAX_X][MAX_Y];
    int m, n;
    int num_mines;
    public:
    //函数声明
    RandMap();
    void set_data(int in_m, int in_n, int in_num_mines);//使用函数参数改变类内部数据的接口
    void get_map(int out_map[MAX_X][MAX_Y]);//获取随机地雷分布图
    void set_mines(void);//分布地雷位置
    int round_num_mines(int i,int j);//计算周围地雷个数
    void rand_map(void);//生成随机分布地雷图
    };

      基本过程是:由主函数调用RandMap类中函数,获取地雷分布图map[i][j]。

      主函数实现着过程的代码:

    ///////////////////////////////////////
    //生成随机地雷分布图map[i][j]
    RandMap RMap;
    RMap.set_data(m, n, num_mines);
    RMap.rand_map();
    RMap.get_map(map);
    ///////////////////////////////////////

      这样就获得了随机分布地雷的数组map[i][j]。

      类的实现(不做介绍,请看注释):

    View Code
    //randmap.cpp
    #include "randmap.h"

    //构造函数
    RandMap::RandMap()
    {
    memset(map,
    0, sizeof(map));
    }

    /*********************************************************************
    初始化地雷分布位置和个数
    函数功能:根据设置的地雷个数和分布地图(map,数组)给出分布好了地雷的数组
    函数原型:void set_mines(void)
    ********************************************************************
    */
    void RandMap::set_mines(void)
    {
    int num =1;
    int i,j;
    srand((unsigned)time(
    0));
    while (num <= num_mines)
    {
    //rand()%n 取(0,n-1)的随机数
    i = rand() % m +1;
    j
    = rand() % n +1;
    //如果出现相同的情况呢?,没事,再循环几次,直到有了足够的地雷为止
    if (i<1|| i>m || j<1|| j>n || map[i][j] == MINE)
    {
    continue;
    }
    map[i][j]
    = MINE;
    num
    ++;//判断地雷个数
    }
    }

    /****************************************************************************
    返回周围地雷个数的函数
    函数原型: int round_num_mines(int i,int j);
    参 数: int i, int j为当前的坐标
    返回值类型: int 返回该坐标处周围的地雷数
    返回值情况:(1)返回1-8代表周围有1-8个地雷;
    (2)返回0代表周围没有地雷;
    (3)返回MINE代表此坐标时地雷;
    *****************************************************************************
    */
    int RandMap::round_num_mines(int i,int j)
    {
    int dir[8][2] = {{-1,-1},{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1}};
    int k =0, mines =0; // round_num_mines 为周围地雷个数

    if (map[i][j] == MINE)
    {
    return MINE;
    }

    for (k =0; k <8; k++)
    {
    if (map[i+dir[k][0]][j+dir[k][1]] == MINE)
    {
    mines
    ++;
    }
    }
    return mines;
    }

    /******************************************************************************
    随机生成地图
    函数原型: void rand_map(void)
    *****************************************************************************
    */
    void RandMap::rand_map(void)
    {
    int i, j;
    set_mines();
    for (i =1; i <= m; i++)
    {
    for (j =1; j <= n; j++)
    {
    map[i][j]
    = round_num_mines(i, j);
    }
    }
    }
    /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    设置m,n,num_mines
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    */
    void RandMap::set_data(int in_m, int in_n, int in_num_mines)
    {
    m
    = in_m;
    n
    = in_n;
    num_mines
    = in_num_mines;
    }

    /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    获取地图
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    */
    void RandMap::get_map(int out_map[MAX_X][MAX_Y])
    {
    int i, j;
    for (i =1; i <= m; i++)
    {
    for (j =1; j <= n; j++)
    {
    out_map[i][j]
    = map[i][j];
    }
    }
    }

      2.画地图前的准备,确定窗口位置和地图中重要位置的像素点的坐标,先看确定窗口位置和大小的函数:

    这个函数使用的是用指针做参数来改变变量的值,在C++中也可以使用指针的引用的。

    //////////////////////////////////////////////
    //设置窗口位置和大小
    //////////////////////////////////////////////
    void set_position_size(int* p_x_position, int* p_y_position, int* p_x_size, int* p_y_size)
    {
    int width = GetSystemMetrics(SM_CXFULLSCREEN);//读取屏幕大小
    int heigh = GetSystemMetrics(SM_CYFULLSCREEN);
    * p_x_position = (int)width/3;
    * p_y_position =(int)heigh/5;
    * p_x_size = (int)width/3;
    * p_y_size = (int)width/3;
    }

    int * p_x_position, int * p_y_position;确定窗口位置(左上角相对于屏幕的x,y坐标)

    int * p_x_size, int * p_y_size;窗口大小

    再来看看怎么将地图中每个格子的像素点的位置存储到数组 int pixel_x[MAX_X]和int pixel_y[MAX_Y]中:

    View Code
    ///////////////////////////////////////////////////////
    //获取画格子的像素点并存储到pixel_x[i],pixel_y[j]中
    ///////////////////////////////////////////////////////
    void get_pixel(HWND hwnd)
    {
    int x1, y1, x2, y2;//(x1,y1),(x2,y2)为窗口位置坐标
    int x_position, y_position, x_size, y_size;
    set_position_size(
    &x_position, &y_position, &x_size, &y_size);
    int frame_width = GetSystemMetrics(SM_CXSIZEFRAME); //边框宽度
    int caption_width = GetSystemMetrics(SM_CYCAPTION); //标题栏宽度
    int menu_high = GetSystemMetrics(SM_CYMENU); //菜单高度
    x1 = caption_width;
    y1
    = caption_width;
    x2
    = x_size - caption_width - frame_width;
    y2
    = y_size -2*caption_width - frame_width;
    int i, j;
    int tmp_x = x1;
    int tmp_y = y1;

    //将画竖向格子线的像素点位置存储到pixel_x[i]中
    for (i =0; i <= m; i++)
    {
    pixel_x[i]
    = tmp_x;
    tmp_x
    += (x2-x1)/m;
    }
    //将画横向格子线的像素点位置存储到pixel_y[j]中
    for (j =0; j <= n; j++)
    {
    pixel_y[j]
    = tmp_y;
    tmp_y
    += (y2-y1)/n;
    }
    }

    代码中有注释,不在作解说。

    有了地图中每个格子的像素点坐标,下面作图也就方便了

      3.现在来看看消息的响应

    LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    int x=LOWORD(lParam);//x,y为鼠标当前的位置坐标
    int y=HIWORD(lParam);
    switch (message)
    {
    case WM_PAINT: //画图消息
    paint_map(hwnd);
    return0;

    case WM_LBUTTONDOWN: //单击鼠标左键消息
    left_key(hwnd, x, y);
    return0;

    case WM_RBUTTONDOWN: //单击右键
    // right_key(hwnd, x, y);
    return0;

    case WM_DESTROY:
    PostQuitMessage (
    0);
    return0 ;
    }
    return DefWindowProc (hwnd, message, wParam, lParam) ;
    }

    WM_PAINT消息是用来载入初始状态下的格子图的(如下图)

    paint_map(hwnd)这个函数是实现初始化载入地图(方格子)的函数,函数使用了

    ////////////////////////////////////////////////////////////
    //画格子图
    ////////////////////////////////////////////////////////////
    void paint_map(HWND hwnd)
    {
    HDC hdc;
    PAINTSTRUCT ps;

    hdc
    = BeginPaint (hwnd, &ps);
    DrawGrid(hdc);
    //使用直线画格子的
    EndPaint (hwnd, &ps);
    }

      初始态载入图我使用的是BeginPaint ()和BeginPaint ()

    DrawGrid函数的实现使用的是API函数MoveToEx()和LineTo()。实现代码如下:

    View Code
    ///////////////////////////////
    //
    //画格子
    //
    //////////////////////////////
    void DrawGrid(HDC hdc)
    {
    int i, j;
    POINT ptLeftTop;

    //画横线
    for (i =0; i <= m; i++)
    {
    ptLeftTop.x
    = pixel_x[i];
    ptLeftTop.y
    = pixel_y[0];
    MoveToEx(hdc,ptLeftTop.x,ptLeftTop.y,NULL);
    ptLeftTop.x
    = pixel_x[i];
    ptLeftTop.y
    = pixel_y[n];
    LineTo(hdc,ptLeftTop.x,ptLeftTop.y);
    }
    //画竖线
    for (j =0; j <= n; j++)
    {
    ptLeftTop.x
    = pixel_x[0];
    ptLeftTop.y
    = pixel_y[j];
    MoveToEx(hdc,ptLeftTop.x,ptLeftTop.y,NULL);
    ptLeftTop.x
    = pixel_x[m];
    ptLeftTop.y
    = pixel_y[j];
    LineTo(hdc,ptLeftTop.x,ptLeftTop.y);
    }
    }

    WM_LBUTTONDOWN 是单击鼠标左键消息,单击左键说明点开此位置。

      4.下面介绍响应左键事件部分(右键标记地雷部分还没实现,但是实现比左键简单,等下次实现了再发上来)

    我定义看一个数组int user_map[MAX_X][MAX_Y] = {0};来存放某位置是否点开,user_map[i][j]等于1就说明点开。left_key()函数中画图的实现是使用了API函数GetDC ()和ReleaseDC ()

    View Code
    ///////////////////////////////////////
    //
    //响应左键事件
    //
    //////////////////////////////////////
    void left_key(HWND hwnd, int x, int y)//x,y为鼠标当前的位置坐标
    {
    int tmp_x = x, tmp_y = y, i, j;
    POINT lpPoint;
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rect;

    for (i =0; i <= m; i++)
    {
    if (tmp_x > pixel_x[i] && tmp_x < pixel_x[i+1]) break;
    }
    for (j =0; j <= n; j++)
    {
    if (tmp_y > pixel_y[j] && tmp_y < pixel_y[j+1]) break;
    }

    hdc
    = GetDC (hwnd);

    i
    =i+1;
    j
    =j+1;

    if (map[i][j] == MINE)
    {
    GetClientRect(hwnd,
    &rect);
    DrawText (hdc, TEXT (
    "GAME OVER!"), -1, &rect,
    DT_SINGLELINE
    | DT_CENTER | DT_VCENTER);
    }
    elseif (map[i][j] ==0&& user_map[i][j] == UNOPEN)
    {
    search0(i, j);
    }
    else
    {
    user_map[i][j]
    = OPEN;
    }
    print_user_map(hdc);
    ReleaseDC (hwnd, hdc);
    }

     

     

    left_key()函数中出先了下面两个函数。

     

    void print_user_map(HDC hdc);//打印用户点击之后的地图

     

    void search0(int i, int j);//如果用户点开的位置上地雷的个数为0,则程序自动点开一片区域

    函数实现分别如下:

    View Code
    /////////////////////////////////////////////////
    //打印用户点击之后的地图
    /////////////////////////////////////////////////
    void print_user_map(HDC hdc)
    {
    RECT rect;
    int i, j;

    for (i =1; i <= m; i++)
    {
    for (j =1; j <= n; j++)
    {

    /***************************************************
    left : 指定矩形框左上角的x坐标
    top: 指定矩形框左上角的y坐标
    right: 指定矩形框右下角的x坐标
      bottom:指定矩形框右下角的y坐标
    ***************************************************
    */
    rect.left
    = pixel_x[i-1];
    rect.top
    = pixel_y[j-1];
    rect.right
    = pixel_x[i];
    rect.bottom
    = pixel_y[j];
    if (user_map[i][j] == OPEN)
    {
    TCHAR str_tmp[
    10];
    sprintf(str_tmp,
    "%d",map[i][j]);
    DrawText (hdc, TEXT (str_tmp),
    -1, &rect,
    DT_SINGLELINE
    | DT_CENTER | DT_VCENTER);
    }
    }
    }
    }
    View Code
    //////////////////////////////////////////////////////////////////////////
    //如果用户点开的位置上地雷的个数为0,则程序自动点开一片区域
    //////////////////////////////////////////////////////////////////////////
    void search0(int i, int j)
    {
    user_map[i][j]
    = OPEN; //点开(i,j)位置
    int di, dj, k;
    for (k =0; k <8; k++)
    {
    di
    = i + dir[k][0];
    dj
    = j + dir[k][1];
    if (di <1|| di > m || dj <1|| dj > n)
    {
    continue;
    }
    if (map[di][dj] !=0&& map[i][j] != MINE)
    {
    user_map[di][dj]
    = OPEN;
    }
    }
    for (k =0; k <8; k++)
    {
    di
    = i + dir[k][0];
    dj
    = j + dir[k][1];
    if (di <1|| di > m || dj <1|| dj > n)
    {
    continue;
    }
    if (map[di][dj] ==0&& user_map[di][dj] == UNOPEN)
    {
    search0(di, dj);
    }
    }
    }

    print_user_map()函数是根据user_map的值来判断是否点开某位置。

    search0()函数使用了搜索算法实现点开的位置是0就会点开一片区域。

      最后,基本说完了,说这个是C/C++混合编程的原因是,我封装了载入随机地雷分布图部分,而没有封装画图那部分,不知道合理不。下面是某些量的预定义

    //DEF.H
    /*
    ++++++++++++++++++++++++++++++++++++++
    预定义
    +++++++++++++++++++++++++++++++++++++++
    */


    #ifndef _DEF_
    #define _DEF_

    #define DEF_M //默认行坐标
    #define DEF_N //默认列坐标
    #define MINE -1 //表示地雷
    #define MAX_X 22 //最大x方向的格子数
    #define MAX_Y 22 //最大y方向的格子数
    #define UNOPEN 0 //没有点开
    #define OPEN 1 //点开地雷
    #define MARK 2 //标记地雷

    #endif /* _DEF_ */

    作者:涵曦www.hanxi.cc
    出处:hanxi.cnblogs.com
    GitHub:github.com/hanxi
    Email:im.hanxi@gmail.com
    文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

    《 Skynet 游戏服务器开发实战》

  • 相关阅读:
    Spring 详解第三天
    Spring 详解第二天
    springmvc的运行流程分析
    Spring 详解第一天
    【Java面试题】40 你所知道的集合类都有哪些?主要方法?
    【Java面试题】39 Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
    【Java面试题】38 Collection 和 Collections的区别
    【Java面试题】37 说出ArrayList,Vector, LinkedList的存储性能和特性
    【Java面试题】36 List、Map、Set三个接口,存取元素时,各有什么特点?
    【Java面试题】35 List, Set, Map是否继承自Collection接口?
  • 原文地址:https://www.cnblogs.com/hanxi/p/demining.html
Copyright © 2011-2022 走看看