C语言-扫雷游戏的实现-创新互联

C语言-扫雷游戏的实现

目前创新互联已为数千家的企业提供了网站建设、域名、虚拟主机网站托管运营、企业网站设计、海阳网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。C语言-扫雷游戏的实现
  • C语言-扫雷游戏的实现
    • 游戏背景介绍
    • 游戏实现思路
      • test模块
      • menu模块
      • _game模块_
        • init_board(初始化棋盘)
        • DisplayBoard(打印棋盘)
        • SetMine(布置雷)
        • FindMine(排雷)
    • 游戏全部代码

游戏背景介绍

游戏最终需要呈现出以下的画面过程

游戏规则:
(1)游戏可以重复玩
(2)玩家每次选择排雷的位置后,显示其周围8个位置中有雷的个数
(3)玩家确定有雷的位置,可进行雷的标记
(4)玩家可以看到当前还有多少雷未确定位置
(5)当玩家排到有雷的位置或者标记雷的位置并不是真正的雷,则游戏结束,玩家失败
(6)当玩家排查到剩余的位置只有雷时或者标记出所有的雷后,则游戏结束,玩家胜利




游戏实现思路

我们计划创建两个源文件test.c和game.c,一个头文件game.h,来完成对这个游戏的实现。
其中,test.c文件主要用于整体框架的实现,game.c文件主要用来实现游戏中的各个步骤。

游戏的整体模块如下:
总

在整体框架搭建好的基础上,下面我们分别实现各模块。


test模块

同上篇博客内容一样,框架和代码都是一致的。具体不再详述。在这里插入图片描述
模块代码如下:

void test()
{srand((unsigned int)time(NULL));
    int input = 0;
    menu();
    do 
    {printf("\n请选择开始游戏(1)或退出游戏(0)\n");
        scanf("%d",&input);
        switch (input)
        {case 1:
            printf("\n开始游戏\n");
            game();
            break;
        case 0:
            printf("\n退出游戏\n");
            break;
        default:
            printf("\n输入错误,请重新输入\n");
            break;

        }

    } while (input);

}

menu模块

同上篇博客内容一样,框架和代码都是一致的。具体不再详述。
在这里插入图片描述

void menu()
{printf("**********************************************************\n");
    printf("***********************   1 play   ***********************\n");
    printf("***********************   2 exit   ***********************\n");
    printf("**********************************************************\n");
}

game模块

为了实现该模块,我们理清一下思路
我们首先希望对棋盘初始化,然后随机的在棋盘内布置好雷;当玩家在选择一个位置排雷时,需要展现给玩家每次排雷后的棋盘的情况;为此,我们需要设计两个棋盘(也即是两个二维数组),一个用来放置雷(以下称为布雷棋盘),另一个用来展示给玩家(以下称为玩家棋盘)。
需要注意如下几点:
(1)对于玩家棋盘,标记出的雷的用字符’M‘表示,未知的区域我们放置字符‘*’,以‘数字字符’来表示周围雷的数量;
对于布雷棋盘,布置雷的位置我们用字符‘M’表示,没有雷的位置用字符‘0’表示
两个数组均为字符数组;
(2)当排查雷的信息时,对于位于棋盘中间的位置,它的周围8个雷的坐标不会产生越界;
而对于边缘的位置,它的周围的坐标可能会越界,这里要判断坐标的合法性
一个解决方案为:申请11x11位的数组,对于边缘的位置,就不会有越界信息报错

基于上述的信息,设计出该模块的框架如下:
在这里插入图片描述
模块代码如下:

void game()
{char mine[ROWS][COLS];
    char show[ROWS][COLS];

    //我们希望mine数组内在开始的时候全为‘0’(还未埋雷)
    //show数组在开始的时候全为‘*’(表示全部都是未知量)

    //初始化mine和show数组
    //InitMine(mine, ROWS, COLS);
    //InitShow(show, ROWS, COLS);
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');

    //打印棋盘,因为我们增加的两行的行和列是为了方便显示其周围附近
    //的雷,因此在打印棋盘的时候,不需要打印多余的这两行两列。
    //DisplayBoard(mine,ROW,COL);
   // DisplayBoard(show,ROW,COL);

    //布置雷
    SetMine(mine, ROW, COL);
    //DisplayBoard(mine, ROW, COL);

    //排雷
    //排雷就是随意指定mine数组中的元素,
    //1 先判断它是不是雷,如果是,那就输掉了
    //如果不是,就显示在它周围的8个位置中有雷的个数,并输出到show数组中去
    //2 继续排雷,首先确认这个位置是否被排过了,若被排过了,需要重新选择,
    //然后进行步骤1
    DisplayBoard(show, ROW, COL);
    FindMine(mine,show,ROW,COL);
    

}

game模块部分是该游戏实现的核心,下面我们从函数角度来完成各步骤的实现


init_board(初始化棋盘)

这里我们需要对两个棋盘进行初始化,
对于布雷棋盘,我们全部初始化为‘0’;
对于玩家棋盘,我们全部初始化为‘*’

函数实现如下:

//初始化mine和show数组
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{for (int i = 0; i< rows; i++)
	{for (int j = 0; j< cols; j++)
		{	board[i][j] = set;
		}
	}
}

这里通过char set来改变初始化的值


DisplayBoard(打印棋盘)

这里的棋盘我们一般考虑展示玩家棋盘,布雷棋盘只用于调试过程
在这里插入图片描述

为了打印出上图所示的棋盘,我们参考上篇博客棋盘的打印过程,在其基础上进行改变
在这里插入图片描述

我们可以看到该棋盘的第一行打印的全是数字;
第二行打印下划线和竖线的组合
第三行打印的是竖线
第四行打印的是列的标号,竖线和数组的元素
第五行打印的是下划线和竖线
第六行打印的是竖线
第七行打印的是列的标号,竖线和数组的元素
… …
可以看出第2,3,4行一直在循环进行,可以用for实现,但是这样循环的话,最后一行并没有下划线封底,为此,我们考虑,将第1,2行单独拿出,第3,4,5依次进行循环操作,就可以实现上述的棋盘

模块的框架如下:
在这里插入图片描述
模块代码如下:

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{	printf("		        ");
	for (int k = 1; k<= col; k++)
	{printf(" %d   ", k);
	}
	printf("\n");//打印出第一行的数字坐标

	printf("		      _|");
	for (int m = 0; m< col; m++)
	{printf("____|");
	}
	printf("\n");//打印出划线分割,单独出来的一行


	for (int i = 1; i<=row; i++)//从1到9
	{printf("		       |");
		for (int p = 0; p< col; p++)
		{	printf("    |");
		}
		printf("\n");//打印出第一种类型的划线分割

		printf("		     %d |", i);//列的数字坐标
		for (int j = 1; j<=col; j++)
		{	printf(" %c  |", board[i][j]);
		}
		printf("\n");

		printf("		      _|");
		for (int q = 0; q< col; q++)
		{	printf("____|");
		}
		printf("\n");//打印出第二种类型的划线分割
	}
	
}

SetMine(布置雷)

布置雷的过程,我们只在布雷棋盘中进行
布置雷
实际上就是向数组中10个随机位置(可通过标识符常量自由设置雷的数量)写入符号‘M’
存在一种情况,生成的随机组合x,y如果相同,视为此次组合作废

代码如下:

void SetMine(char board[ROWS][COLS], int row, int col)
{for (int i = 0; i< COUNT; i++)
	{//生成x,y的随机坐标,x和y均为0-8的数字
		int x = rand() % row+1;
		int y = rand() % col+1;
		if (board[x][y]!='M')
		{	board[x][y] = 'M';
		}
		else
		{	i--;
		}
	}

}

FindMine(排雷)

排雷模块,也是我们最重要且核心的模块,基于游戏规则,我们需要实现以下需求:
(1)玩家选择排雷的位置是否合法
(2)玩家选择排雷的位置是否已经被排查过
(3)玩家排查的位置是否为雷
(4)显示排查的位置的周围有雷的数量
(5)记录玩家排查雷的次数
(6)询问玩家是否需要标记已确定的雷的位置
(7)判断玩家需要标记雷的位置是否为真正的雷
(8)记录玩家标记类的次数,同时显示剩余多少雷未找到
(9)当玩家标记完所有的雷或者排查雷的次数满足一定条件时,玩家胜利,show pass 返回上层函数
(10)当排查的位置是雷或者标记的位置不是雷时,玩家失败,show fail 返回上层函数

基于以上分析,该模块的框架如下:在这里插入图片描述
该模块的代码如下:

//排雷
void FindMine(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col)
{int count = 0;
	int flag = 1;
	int sign_num = 0;
	while (count!=(row*col)-COUNT)
	{printf("\n请你选择排雷的位置\n");
		int x = 0;
		int y = 0;
		scanf("%d %d", &x, &y);
		if ((x >= 1 && x<= 9) && (y >= 1 && y<= 9)) 
		{	if (board2[x][y] != '*')
			{		printf("\n该位置被排查过了,请重新选择位置\n");
				continue;
			}
			if (board1[x][y] != 'M')
			{		int sum = get_mine_count(board1, x, y);
				board2[x][y] = sum + '0';
				count++;
				DisplayBoard(board2, ROW, COL);
				int return_sing_num = sign_mine(board1, board2, ROW, COL);
				if(return_sing_num!=2)
				{sign_num = sign_num + return_sing_num;
					printf("\n剩余未排雷的个数为:%d\n", COUNT - sign_num);
					if (COUNT - sign_num == 0)
					{flag = 1;
						break;
					}
				}
				else
				{DisplayBoard(board1, ROW, COL);
					printf("\n标记雷错误,LOSE\n");
					flag = 0;
					show_fail();
					break;
				}
				
			}
			else
			{		DisplayBoard(board1, ROW, COL);
				printf("\n你中雷了,LOSE\n");
				flag = 0;
				show_fail();
				break;
			}
		}
		else
		{	printf("\n坐标非法,请重新输入\n");
			continue;
		}
	}
	if(flag==1)
	{DisplayBoard(board1, ROW, COL);
		printf("\n恭喜你,win\n");
		show_pass();
	}	
}

其中,get_mine_count函数:

//计算雷的数量
int get_mine_count(char board1[ROWS][COLS], int x, int y)
{int sum = board1[x - 1][y] + board1[x + 1][y] + board1[x][y - 1] + board1[x][y + 1]+ board1[x - 1][y - 1] + board1[x - 1][y + 1] + board1[x + 1][y - 1] +board1[x + 1][y + 1]-8*48;
	return sum = sum / 29;
}

sign_mine函数:

//标记雷的位置
//首先,玩家需不需要对该位置进行标记,如果要标记,则继续进行
//不需要标记,则跳过该过程,向下运行。
int sign_mine(char board1[ROWS][COLS],char board2[ROWS][COLS], int row, int col)
{printf("\n当前你是否需要标记雷的位置?Y/N\n");
	printf("标记错误即为失败,请谨慎操作\n");
	//当定义一个字符变量时,在键盘上输入一个scanf会吸收回车和空格字符,
	//为了进行代码的可行性需在函数结束前加函数getchar()进行吸收
	getchar();
	char c=0;
	scanf("%c", &c);
	if (c == 'Y')
	{printf("\n请输入你已经判断出是雷的坐标位置,该回合只有一次机会\n");
		int a = 0;
		int b = 0;
		scanf("%d %d", &a, &b);
		if (board1[a][b]== 'M')
		{	board2[a][b] = 'M';
			DisplayBoard(board2, ROW, COL);
			return 1;
		}
		else
		{	//如果说被标记的位置不是雷,那么也是排雷失败
			return 2;
		}
	}
	return 0;
}

show_pass函数

void show_pass()
{printf("******* ******* ******* *******\n");
	printf("*     * *     * *       *      \n");
	printf("*     * *     * *       *      \n");
	printf("*     * *     * *       *      \n");
	printf("******* ******* ******* *******\n");
	printf("*       *     *       *       *\n");
	printf("*       *     *       *       *\n");
	printf("*       *     *       *       *\n");
	printf("*       *     * ******* *******\n");

}

show_fail函数:

void show_fail()
{printf("******* ******* ******* *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("******* *******    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     * ******* *******\n");
	printf("\n");

}

至此,所有模块的功能均已实现




游戏全部代码

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"
//扫雷的游戏规则:点击后出现的数字,表示在这个方格附近含有雷的数量
//(最多有8个,正方向4个,斜方向4个)

//排查出的雷的信息用字符1表示,因为在未知的区域内我们放置*,整个数组为
//字符数组
//而布置雷的数组我们也用字符0和1
//
//思路:布置2个棋盘,一个棋盘用来放置雷,另一个棋盘用来放置雷的信息
//
//当排查雷的信息时,对于位于棋盘中间的位置,它的周围8个雷的坐标不会产生
//越界,而对于边缘的位置,它的周围的坐标可能会越界,这里要判断坐标的合法性
//一个解决方案为:申请11*11位的数组,对于边缘的位置,就不会有越界信息报错
void game()
{char mine[ROWS][COLS];
    char show[ROWS][COLS];

    //我们希望mine数组内在开始的时候全为‘0’(还未埋雷)
    //show数组在开始的时候全为‘*’(表示全部都是未知量)

    //初始化mine和show数组
    //InitMine(mine, ROWS, COLS);
    //InitShow(show, ROWS, COLS);
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');

    //打印棋盘,因为我们增加的两行的行和列是为了方便显示其周围附近
    //的雷,因此在打印棋盘的时候,不需要打印多余的这两行两列。
    //DisplayBoard(mine,ROW,COL);
   // DisplayBoard(show,ROW,COL);

    //布置雷
    SetMine(mine, ROW, COL);
    //DisplayBoard(mine, ROW, COL);

    //排雷
    //排雷就是随意指定mine数组中的元素,
    //1 先判断它是不是雷,如果是,那就输掉了
    //如果不是,就显示在它周围的8个位置中有雷的个数,并输出到show数组中去
    //2 继续排雷,首先确认这个位置是否被排过了,若被排过了,需要重新选择,
    //然后进行步骤1
    DisplayBoard(show, ROW, COL);
    FindMine(mine,show,ROW,COL);
    

}
void menu()
{printf("**********************************************************\n");
    printf("***********************   1 play   ***********************\n");
    printf("***********************   2 exit   ***********************\n");
    printf("**********************************************************\n");
}
void test()
{srand((unsigned int)time(NULL));
    int input = 0;
    menu();
    do 
    {printf("\n请选择开始游戏(1)或退出游戏(0)\n");
        scanf("%d",&input);
        switch (input)
        {case 1:
            printf("\n开始游戏\n");
            game();
            break;
        case 0:
            printf("\n退出游戏\n");
            break;
        default:
            printf("\n输入错误,请重新输入\n");
            break;

        }

    } while (input);

}
int main()
{test();
    return 0;
}

game.h

#pragma once
#include#include#include#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define COUNT 2

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);

//打印棋盘
void DisplayBoard(char board[ROWS][COLS],int row,int col);

//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);

//排雷
void FindMine(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col);

game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"



//初始化mine和show数组
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{for (int i = 0; i< rows; i++)
	{for (int j = 0; j< cols; j++)
		{	board[i][j] = set;
		}
	}
}

//打印棋盘
//前面的设计思路和三子棋的思路是一样的
//添加横纵坐标的思路:对于行,在进行全部的循环之前可以进行一个循环添加
//可以自己调试改变输出的格式,使其适应棋盘
//对于列,考虑在只在输出字符的那一类的行,在开始循环前,进行添加数字
//void DisplayBoard(char board[ROWS][COLS], int row, int col)
//{//	printf("  ");
//	for (int k = 1; k<= col; k++)
//	{//		printf(" %d  ", k);
//	}
//	printf("\n");
//	for (int i = 0; i< row; i++)
//	{//			printf("%d ", i+1);
//			for (int j = 0; j< col; j++)
//			{//				if (j< col - 1)
//				{//					printf(" %c |", board[i][j]);
//				}
//				else
//				{//					printf(" %c ", board[i][j]);
//				}
//			}
//			printf("\n");
//			printf("  ");
//			if (i< row - 1)
//			{//				for (int j = 0; j< col; j++)
//				{//					if (j< col - 1)
//					{//						printf("---|");
//					}
//					else
//					{//						printf("---");
//					}
//				}
//			}
//		printf("\n");
//	}
//}
//打印棋盘,优化
//用矩形补全棋盘
//void DisplayBoard(char board[ROWS][COLS], int row, int col)
//{//	printf("   ");
//	for (int k = 1; k<= col; k++)
//	{//		printf(" %d  ", k);
//	}
//	printf("\n");//打印出第一行的数字坐标
//
//	printf("  |");
//	for (int k = 0; k//		printf("---|");
//	}
//	printf("\n");//打印出划线分割,单独出来的一行
//
//	for (int i = 0; i< row; i++)
//	{//		printf("%d |", i + 1);//列的数字坐标
//		for (int j = 0; j< col; j++)
//		{//				printf(" %c |", board[i][j]);
//		}
//		printf("\n");
//
//
//		printf("  |");
//			for (int j = 0; j< col; j++)
//			{//					printf("---|");
//			}
//		
//		printf("\n");
//	}
//}
//继续优化以实心的下划线代替虚线,同时使棋盘移动到中间位置
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{	printf("		        ");
	for (int k = 1; k<= col; k++)
	{printf(" %d   ", k);
	}
	printf("\n");//打印出第一行的数字坐标

	printf("		      _|");
	for (int m = 0; m< col; m++)
	{printf("____|");
	}
	printf("\n");//打印出划线分割,单独出来的一行


	for (int i = 1; i<=row; i++)//从1到9
	{printf("		       |");
		for (int p = 0; p< col; p++)
		{	printf("    |");
		}
		printf("\n");//打印出第一种类型的划线分割

		printf("		     %d |", i);//列的数字坐标
		for (int j = 1; j<=col; j++)
		{	printf(" %c  |", board[i][j]);
		}
		printf("\n");

		printf("		      _|");
		for (int q = 0; q< col; q++)
		{	printf("____|");
		}
		printf("\n");//打印出第二种类型的划线分割
	}
	
}


//布置雷
//实际上就是向数组中10个随机位置写入符号‘M’
//存在一种情况,生成的随机组合x,y如果相同,视为此次组合作废
void SetMine(char board[ROWS][COLS], int row, int col)
{for (int i = 0; i< COUNT; i++)
	{//生成x,y的随机坐标,x和y均为0-8的数字
		int x = rand() % row+1;
		int y = rand() % col+1;
		if (board[x][y]!='M')
		{	board[x][y] = 'M';
		}
		else
		{	i--;
		}
	}

}


//计算雷的数量
int get_mine_count(char board1[ROWS][COLS], int x, int y)
{int sum = board1[x - 1][y] + board1[x + 1][y] + board1[x][y - 1] + board1[x][y + 1]+ board1[x - 1][y - 1] + board1[x - 1][y + 1] + board1[x + 1][y - 1] +board1[x + 1][y + 1]-8*48;
	return sum = sum / 29;
}


//标记雷的位置
//首先,玩家需不需要对该位置进行标记,如果要标记,则继续进行
//不需要标记,则跳过该过程,向下运行。
int sign_mine(char board1[ROWS][COLS],char board2[ROWS][COLS], int row, int col)
{printf("\n当前你是否需要标记雷的位置?Y/N\n");
	printf("标记错误即为失败,请谨慎操作\n");
	//当定义一个字符变量时,在键盘上输入一个scanf会吸收回车和空格字符,
	//为了进行代码的可行性需在函数结束前加函数getchar()进行吸收
	getchar();
	char c=0;
	scanf("%c", &c);
	if (c == 'Y')
	{printf("\n请输入你已经判断出是雷的坐标位置,该回合只有一次机会\n");
		int a = 0;
		int b = 0;
		scanf("%d %d", &a, &b);
		if (board1[a][b]== 'M')
		{	board2[a][b] = 'M';
			DisplayBoard(board2, ROW, COL);
			return 1;
		}
		else
		{	//如果说被标记的位置不是雷,那么也是排雷失败
			return 2;
		}
	}
	return 0;
}

void show_pass()
{printf("******* ******* ******* *******\n");
	printf("*     * *     * *       *      \n");
	printf("*     * *     * *       *      \n");
	printf("*     * *     * *       *      \n");
	printf("******* ******* ******* *******\n");
	printf("*       *     *       *       *\n");
	printf("*       *     *       *       *\n");
	printf("*       *     *       *       *\n");
	printf("*       *     * ******* *******\n");

}

void show_fail()
{printf("******* ******* ******* *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("******* *******    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     *    *    *      \n");
	printf("*       *     * ******* *******\n");
	printf("\n");

}

//排雷
void FindMine(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col)
{int count = 0;
	int flag = 1;
	int sign_num = 0;
	while (count!=(row*col)-COUNT)
	{printf("\n请你选择排雷的位置\n");
		int x = 0;
		int y = 0;
		scanf("%d %d", &x, &y);
		if ((x >= 1 && x<= 9) && (y >= 1 && y<= 9)) 
		{	if (board2[x][y] != '*')
			{		printf("\n该位置被排查过了,请重新选择位置\n");
				continue;
			}
			if (board1[x][y] != 'M')
			{		int sum = get_mine_count(board1, x, y);
				board2[x][y] = sum + '0';
				count++;
				DisplayBoard(board2, ROW, COL);
				int return_sing_num = sign_mine(board1, board2, ROW, COL);
				if(return_sing_num!=2)
				{sign_num = sign_num + return_sing_num;
					printf("\n剩余未排雷的个数为:%d\n", COUNT - sign_num);
					if (COUNT - sign_num == 0)
					{flag = 1;
						break;
					}
				}
				else
				{DisplayBoard(board1, ROW, COL);
					printf("\n标记雷错误,LOSE\n");
					flag = 0;
					show_fail();
					break;
				}
				
			}
			else
			{		DisplayBoard(board1, ROW, COL);
				printf("\n你中雷了,LOSE\n");
				flag = 0;
				show_fail();
				break;
			}
		}
		else
		{	printf("\n坐标非法,请重新输入\n");
			continue;
		}
	}
	if(flag==1)
	{DisplayBoard(board1, ROW, COL);
		printf("\n恭喜你,win\n");
		show_pass();
	}	
}

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


名称栏目:C语言-扫雷游戏的实现-创新互联
网站URL:http://bzwzjz.com/article/ddceeh.html

其他资讯

Copyright © 2007-2020 广东宝晨空调科技有限公司 All Rights Reserved 粤ICP备2022107769号
友情链接: 成都网站建设 外贸营销网站建设 阿坝网站设计 四川成都网站制作 网站制作 重庆网站建设 成都网站制作 品牌网站建设 教育网站设计方案 网站建设推广 成都网站制作 古蔺网站建设 网站制作 重庆电商网站建设 重庆外贸网站建设 成都网站设计 手机网站制作 成都网站制作 定制网站建设 温江网站设计 成都网站建设公司 网站制作