百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

「详细教程/附源码」如何使用C语言打造人机对战版五子棋?

liebian365 2025-01-29 16:42 15 浏览 0 评论

文章较长,建议收藏后学习!!


1.项目分析


2.项目目标

  • AI的基础应用
  • 算法的设计和实现
  • C语言的图形界面程序开发

3.项目准备

  • VS/VC++(任意版本)
  • easyx图形库(直接双击即可安装)
  • 领取素材(传送门)(图片、音效)

4.创建项目

  1. 创建空项目。
  2. 把素材拷贝到项目目录下

5.画棋盘

画棋盘,播放开局提示、播放背景音乐

#include <graphics.h>   //easyx图像库的头文件
#include <windows.h>
#include <mmsystem.h>   //播放音乐的头文件
#pragma comment(lib, "winmm.lib")

void init() {
	initgraph(897, 895);
	loadimage(0, "res/棋盘.jpg");

	mciSendString("play res/start.wav", 0, 0, 0);
	mciSendString("play res/bg.mp3 repeat", 0, 0, 0);
}

int main(void) {
	init();

	system("pause");
	return 0;
}

6.画棋子

鼠标点击后,在点击位置画棋子

IMAGE chessBlackImg;
IMAGE chessWhiteImg;
const float BLOCK_SIZE = 67.4; // 格子的大小

void init() {
	......

	loadimage(&chessBlackImg, "res/black.png", BLOCK_SIZE, BLOCK_SIZE, true);
	loadimage(&chessWhiteImg, "res/white.png", BLOCK_SIZE, BLOCK_SIZE, true);
}

int main(void) {
	init();

	while (1) {
		MOUSEMSG msg = GetMouseMsg();
		if (msg.uMsg == WM_LBUTTONDOWN) {
			putimage(msg.x, msg.y, &chessBlackImg);
		}
	}

	system("pause");
	return 0;
}

效果:


黑色区域,透明背景的PNG图片显示不了。


解决方案:

  1. 导入工具库tools.h, tools.cpp
  2. 修改代码
#include "tools.h"

int main(void) {
	init();

	while (1) {
		MOUSEMSG msg = GetMouseMsg();
		if (msg.uMsg == WM_LBUTTONDOWN) {
			//putimage(msg.x, msg.y, &chessBlackImg);
			drawPNG(&chessBlackImg, msg.x, msg.y);
		}
	}

	system("pause");
	return 0;
}

效果:

修改:

drawPNG(&chessBlackImg, msg.x, msg.y);
drawPNG(&chessBlackImg, msg.x - 0.5 * BLOCK_SIZE, msg.y - 0.5 * BLOCK_SIZE);

看上去,很完美,但是有一个严重的BUG!!

当不在交叉点准确点击时,就会出现以上情况。


解决方案:

需要判断这个点击是否是合法未知的点击,并允许一定的偏差


判断有效的点击

定义数据模型

因为点击时,要判断是否在已经有棋子的位置上点击(不能在已经落子的位置点击)

所以需要定义一个数据模型,来表示当前的所有棋子数据。


【模块化开发思想】

创建ChessData.h, 并把main.cpp中的与围棋相关的全局数据,剪贴到ChessData.h中

ChessData.h

#pragma once

const float BLOCK_SIZE = 67.4; // 格子的大小
const int BOARD_GRAD_SIZE = 13; //13x13棋盘大小
const int POS_OFFSET = BLOCK_SIZE * 0.4; // 20 鼠标点击的模糊距离上限

struct  ChessData {
    // 存储当前游戏棋盘和棋子的情况,空白为0,黑子1,白子-1
    int chessMap[BOARD_GRAD_SIZE][BOARD_GRAD_SIZE];

    // 存储各个点位的评分情况,作为AI下棋依据
    int scoreMap[BOARD_GRAD_SIZE][BOARD_GRAD_SIZE];

    // 标示下棋方, true:黑棋方  false: AI 白棋方(AI方)
    bool playerFlag;
};


在main.cpp中添加围棋数据变量game

#include "ChessData.h"

ChessData game; 

初始化数据模型

ChessData.h

void initChessData(ChessData*); // 开始游戏

ChessData.cpp

void initChessData(ChessData *data)
{
    if (!data)return;
    memset(data->chessMap, 0, sizeof(data->chessMap));
    memset(data->scoreMap, 0, sizeof(data->scoreMap));
    data->playerFlag = true;
}

main.cpp

void init() {
......
// 初始化游戏模型
initChessData(&game);
}


7.判断有效点击

判断原理

先计算出绿点,然后分别计算出3个黑点位置,计算当前位置离4个点的位置。

如果小于阈值(POS_OFFSET),就认为选择了哪个点。

在main.cpp中添加变量,存储有效点击的位置

int clickPosRow, clickPosCol; // 存储点击的位置

判断是否是有效点击,如果是有效点击,返回true并把结果保存到全局变量clickPosRow、 clickPosCol;

ChessData.h

const int POS_OFFSET = BLOCK_SIZE * 0.4; // 20 鼠标点击的模糊距离上限

bool clickBoard(MOUSEMSG msg) {
	int x = msg.x;
	int y = msg.y;

	int col = (x - margin_x) / BLOCK_SIZE;
	int row = (y - margin_y) / BLOCK_SIZE;

	int leftTopPosX = margin_x + BLOCK_SIZE * col;
	int leftTopPosY = margin_y + BLOCK_SIZE * row;

	int len;
	int selectPos = false;

	do {
		len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + (y - leftTopPosY) * (y - leftTopPosY));
		if (len < POS_OFFSET) {
			clickPosRow = row;
			clickPosCol = col;
			if (game.chessMap[clickPosRow][clickPosCol] == 0) {
				selectPos = true;
			}
			break;
		}

		// 距离右上角的距离
		len = sqrt((x - leftTopPosX - BLOCK_SIZE) * (x - leftTopPosX - BLOCK_SIZE) + 
(y - leftTopPosY) * (y - leftTopPosY));
		if (len < POS_OFFSET) {
			clickPosRow = row;
			clickPosCol = col + 1;
			if (game.chessMap[clickPosRow][clickPosCol] == 0) {
				selectPos = true;
			}
			break;
		}

		// 距离左下角的距离
		len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + 
(y - leftTopPosY - BLOCK_SIZE) * (y - leftTopPosY - BLOCK_SIZE));
		if (len < POS_OFFSET) {
			clickPosRow = row + 1;
			clickPosCol = col;
			if (game.chessMap[clickPosRow][clickPosCol] == 0) {
				selectPos = true;
			}
			break;
		}

		// 距离右下角的距离
		len = sqrt((x - leftTopPosX - BLOCK_SIZE) * (x - leftTopPosX - BLOCK_SIZE) + 
(y - leftTopPosY - BLOCK_SIZE) * (y - leftTopPosY - BLOCK_SIZE));
		if (len < POS_OFFSET) {
			clickPosRow = row + 1;
			clickPosCol = col + 1;

			if (game.chessMap[clickPosRow][clickPosCol] == 0) {
				selectPos = true;
			}
			break;
		}
	} while (0);

	return selectPos;
}

实现有效点击

int main(void) {
	init();

	while (1) {
		MOUSEMSG msg = GetMouseMsg();
		if (msg.uMsg == WM_LBUTTONDOWN && clickBoard(msg)) {
			//putimage(msg.x, msg.y, &chessBlackImg);
//drawPNG(&chessBlackImg, msg.x - 0.5 * BLOCK_SIZE, msg.y - 0.5 * BLOCK_SIZE);

			int x = margin_x + clickPosCol * BLOCK_SIZE - 0.5 * BLOCK_SIZE;
			int y = margin_y + clickPosRow * BLOCK_SIZE - 0.5 * BLOCK_SIZE;
			drawPNG(&chessBlackImg, x, y);
		}
	}

	system("pause");
	return 0;
}

测试效果:


8.优化项目架构

1.封装画棋子的代码

  1. 在ChessData.h中添加棋子类型
typedef enum {
    CHESS_WHITE = -1,
    CHESS_BLACK = 1
} chess_kind_t;
  1. 在main.cpp封装“落子”代码
void chessDown(int row, int col, chess_kind_t kind) {
	mciSendString("play res/down7.WAV", 0, 0, 0);

	int x = margin_x + col * BLOCK_SIZE - 0.5 * BLOCK_SIZE;
	int y = margin_y + row * BLOCK_SIZE - 0.5 * BLOCK_SIZE;

	if (kind == CHESS_WHITE) {
		drawPNG(&chessWhiteImg, x, y);
	}
	else {
		drawPNG(&chessBlackImg, x, y);
	}
}
  1. 落子
int main(void) {
	init();

	while (1) {
		MOUSEMSG msg = GetMouseMsg();
		if (msg.uMsg == WM_LBUTTONDOWN && clickBoard(msg)) {
			chessDown(clickPosRow, clickPosCol, CHESS_BLACK);
		}
	}

	system("pause");
	return 0;
}

2.优化项目架构

bool checkOver() { // 检查游戏是否结束
	return false;
}

void AI_GO() {  //AI走棋

}

void manGo() { // 玩家走棋
chessDown(clickPosRow, clickPosCol, CHESS_BLACK);
}

int main(void) {
	init();

	while (1) {
		MOUSEMSG msg = GetMouseMsg();
		if (msg.uMsg == WM_LBUTTONDOWN) {
			manGo();
			if (checkOver()) {
				init();
				continue;
			}

			AI_GO();
			if (checkOver()) {
				init();
				continue;
			}
		}
	}

	closegraph();
	return 0;
}


9.更新游戏数据

人(黑方)落子后,还没有修改底层的游戏数据。

在ChessDatat.h添加接口:

void updateGameMap(ChessData* data, int row, int col);

在ChessData.cpp中添加实现。

void updateGameMap(ChessData* data, int row, int col)
{
	if (!data)return;
	if (data->playerFlag)
		data->chessMap[row][col] = 1;
	else
		data->chessMap[row][col] = -1;

	data->playerFlag = !data->playerFlag; // 换手
}

应用更新:

void manGo() { // 玩家走棋
	chessDown(clickPosRow, clickPosCol, CHESS_BLACK);
	updateGameMap(&game, clickPosRow, clickPosCol);
}


10.实现AI走棋

五子棋入门

连2

活3

死3

活4

死4

连5(赢)



AI走棋原理

计算每个合法的落子点的“权值”,然后再权值最大的点落子

以后,可以在这个基础之上,实现多个层次的计算.

对于每个空白点,分别计算周围的八个方向

因为在计算某个方向时,正向和反向需同时考虑,所以实际上只需计算4个方向即可:

如果黑棋走这个点

产生效果

评分

连2

10

死3

30

活3

40

死4

60

活4

200

连5

20000

如果白棋AI走这个点

产生效果

评分

连1(普通)

5

连2

10

死3

25

活3

50

死4

55

活4

300

连5

30000


计算各点的“权值”

权值的计算,放在ChessData模块中。

ChessData.h

void calculateScore(ChessData* data);

ChessData.cpp

#include <string.h> //memset函数

// 最关键的计算评分函数
void calculateScore(ChessData* data)
{
    if (!data) return;

    // 统计玩家或者电脑连成的子
    int personNum = 0; // 玩家连成子的个数
    int botNum = 0; // AI连成子的个数
    int emptyNum = 0; // 各方向空白位的个数

    // 清空评分数组
    memset(data->scoreMap, 0, sizeof(data->scoreMap));
    for (int row = 0; row < BOARD_GRAD_SIZE; row++)
        for (int col = 0; col < BOARD_GRAD_SIZE; col++) {
            // 空白点就算
            if (row >= 0 && col >= 0 && data->chessMap[row][col] == 0)
            {
                // 遍历周围4个方向,分别计算正反两个方向
                int directs[4][2] = { {1,0}, {1,1}, {0,1}, {-1,1 } };
                for (int k = 0; k < 4; k++) {
                    int x = directs[k][0];
                    int y = directs[k][1];

                    // 重置
                    personNum = 0;
                    botNum = 0;
                    emptyNum = 0;

                    // 对黑棋评分(正向)
                    for (int i = 1; i <= 4; i++) {
                        if (row + i * y >= 0 && row + i * y < BOARD_GRAD_SIZE &&
                                col + i * x >= 0 && col + i * x < BOARD_GRAD_SIZE &&
                                data->chessMap[row + i * y][col + i * x] == 1) { // 真人玩家的子
                            personNum++;
                        } else if (row + i * y >= 0 && row + i * y < BOARD_GRAD_SIZE &&
                                col + i * x >= 0 && col + i * x < BOARD_GRAD_SIZE &&
                                data->chessMap[row + i * y][col + i * x] == 0) { // 空白位
                            emptyNum++;
                            break;     // 遇到空白位置,停止该方向的搜索
                        } else            // 出边界,或者遇到白棋,就停止该方向的搜索
                            break;
                    }

                    // 对黑棋评分(反向)
                    for (int i = 1; i <= 4; i++) {
                        if (row - i * y >= 0 && row - i * y < BOARD_GRAD_SIZE &&
                                col - i * x >= 0 && col - i * x < BOARD_GRAD_SIZE &&
                                data->chessMap[row - i * y][col - i * x] == 1) { // 玩家的子
                            personNum++;
                        }
                        else if (row - i * y >= 0 && row - i * y < BOARD_GRAD_SIZE &&
                                col - i * x >= 0 && col - i * x < BOARD_GRAD_SIZE &&
                                data->chessMap[row - i * y][col - i * x] == 0) { // 空白位
                            emptyNum++;
                            break;
                        }  else            // 出边界,或者有AI自己的棋子
                            break;
                    }

                    if (personNum == 1)                      // 杀二
                        data->scoreMap[row][col] += 10;
                    else if (personNum == 2) {                // 杀三
                        if (emptyNum == 1)     // 死三
                            data->scoreMap[row][col] += 30;    
                        else if (emptyNum == 2) // 活三
                            data->scoreMap[row][col] += 40;
                    } else if (personNum == 3) {              // 杀四
                        if (emptyNum == 1)    //死四
                            data->scoreMap[row][col] += 60;
                        else if (emptyNum == 2) //活四
                            data->scoreMap[row][col] += 200;
                    }
                    else if (personNum == 4)                 // 杀五
                        data->scoreMap[row][col] += 20000;

                    // 进行一次清空
                    emptyNum = 0;

                    // 对白棋评分(正向)
                    for (int i = 1; i <= 4; i++) {
                        if (row + i * y > 0 && row + i * y < BOARD_GRAD_SIZE &&
                                col + i * x > 0 && col + i * x < BOARD_GRAD_SIZE &&
                                data->chessMap[row + i * y][col + i * x] == -1) { // 玩家的子
                            botNum++;
                        } else if (row + i * y > 0 && row + i * y < BOARD_GRAD_SIZE &&
                                col + i * x > 0 && col + i * x < BOARD_GRAD_SIZE &&
                                data->chessMap[row + i * y][col + i * x] == 0)  { // 空白位
                            emptyNum++;
                            break;
                        } else          
                            break;
                    }

                    // 对白棋评分(反向)
                    for (int i = 1; i <= 4; i++) {
                        if (row - i * y > 0 && row - i * y < BOARD_GRAD_SIZE &&
                                col - i * x > 0 && col - i * x < BOARD_GRAD_SIZE &&
                                data->chessMap[row - i * y][col - i * x] == -1) { // AI的子
                            botNum++;
                        } else if (row - i * y > 0 && row - i * y < BOARD_GRAD_SIZE &&
                                col - i * x > 0 && col - i * x < BOARD_GRAD_SIZE &&
                                data->chessMap[row - i * y][col - i * x] == 0) { // 空白位
                            emptyNum++;
                            break;
                        } else            // 出边界
                            break;
                    }

                    if (botNum == 0)                      // 普通下子
                        data->scoreMap[row][col] += 5;
                    else if (botNum == 1)                 // 活二
                        data->scoreMap[row][col] += 10;
                    else if (botNum == 2) {
                        if (emptyNum == 1)                // 死三
                            data->scoreMap[row][col] += 25;
                        else if (emptyNum == 2)
                            data->scoreMap[row][col] += 50;  // 活三
                    } else if (botNum == 3) {
                        if (emptyNum == 1)                // 死四
                            data->scoreMap[row][col] += 55;
                        else if (emptyNum == 2)
                            data->scoreMap[row][col] += 300; // 活四
                    } else if (botNum >= 4)
                        data->scoreMap[row][col] += 30000;   // 活五,应该具有最高优先级

                }
            }
        }
}

AI思考落子点

在各落子点,找到分值最大的点。如果有多个分值相同的点,直接在其中取一个随机点。

在ChesssData模块实现。

ChessData.h

typedef struct point {
    int row;
    int col;
} point_t;

point_t actionByAI(ChessData* data); // 机器执行下棋

ChessData.cpp

#include <time.h>
#include <stdlib.h>

point_t actionByAI(ChessData *data)
{
    // 计算评分
    calculateScore(data);

    // 从评分中找出最大分数的位置
    int maxScore = 0;
    //std::vector<std::pair<int, int>> maxPoints;
    point_t maxPoints[BOARD_GRAD_SIZE * BOARD_GRAD_SIZE] = { 0, };
    int k=0;

    for (int row = 0; row < BOARD_GRAD_SIZE; row++)
        for (int col = 0; col < BOARD_GRAD_SIZE; col++)
        {
            // 前提是这个坐标是空的
            if (data->chessMap[row][col] == 0)
            {
                if (data->scoreMap[row][col] > maxScore)          // 找最大的数和坐标
                {
                    //maxPoints.clear();
                    memset(maxPoints, 0, sizeof(maxPoints));
                    k = 0;
                    maxScore = data->scoreMap[row][col];
                    //maxPoints.push_back(std::make_pair(row, col));
                    maxPoints[k].row = row;
                    maxPoints[k].col = col;
                    k++;
                }
                else if (data->scoreMap[row][col] == maxScore) {   // 如果有多个最大的数,都存起来
                    //maxPoints.push_back(std::make_pair(row, col));
                    maxPoints[k].row = row;
                    maxPoints[k].col = col;
                    k++;
                }
            }
        }

    // 随机落子,如果有多个点的话
    srand((unsigned)time(0));
    int index = rand() % k;
    return maxPoints[index];
}

实现AI落子

void AI_GO() {  //AI走棋
	point_t point = actionByAI(&game);
	clickPosRow = point.row;
	clickPosCol = point.col;

	Sleep(1000); //AI计算的太快,此处以假装思考
	chessDown(clickPosRow, clickPosCol, CHESS_WHITE);
	updateGameMap(&game, clickPosRow, clickPosCol);
}

11.判断棋局是否结束

在ChessData模块定义判断输赢的接口

原理分析:

在4个方向上搜索。

以右下方向为例:(黑色棋子表示刚下的棋子)

从当前棋子开始,向右下方数5个

从当前棋子的左上角开始,向右下方数5个

从当前棋子的左上第2个开始,向右下方数5个

从当前棋子的左上第3个开始,向右下方数5个

从当前棋子的左上第4个开始,向右下方数5个

ChessData.h

bool checkWin(ChessData* game, int row, int col); //row,col表示当前落子

ChessData.cpp

bool checkWin(ChessData* game, int row, int col)
{
    // 横竖斜四种大情况,每种情况都根据当前落子往后遍历5个棋子,有一种符合就算赢
    // 水平方向
    for (int i = 0; i < 5; i++)
    {
        // 往左5个,往右匹配4个子,20种情况
        if (col - i >= 0 &&
            col - i + 4 < BOARD_GRAD_SIZE &&
            game->chessMap[row][col - i] == game->chessMap[row][col - i + 1] &&
            game->chessMap[row][col - i] == game->chessMap[row][col - i + 2] &&
            game->chessMap[row][col - i] == game->chessMap[row][col - i + 3] &&
            game->chessMap[row][col - i] == game->chessMap[row][col - i + 4])
            return true;
    }

    // 竖直方向(上下延伸4个)
    for (int i = 0; i < 5; i++)
    {
        if (row - i >= 0 &&
            row - i + 4 < BOARD_GRAD_SIZE &&
            game->chessMap[row - i][col] == game->chessMap[row - i + 1][col] &&
            game->chessMap[row - i][col] == game->chessMap[row - i + 2][col] &&
            game->chessMap[row - i][col] == game->chessMap[row - i + 3][col] &&
            game->chessMap[row - i][col] == game->chessMap[row - i + 4][col])
            return true;
    }

    // “/"方向
    for (int i = 0; i < 5; i++)
    {
        if (row + i < BOARD_GRAD_SIZE &&
            row + i - 4 >= 0 &&
            col - i >= 0 &&
            col - i + 4 < BOARD_GRAD_SIZE &&
            // 第[row+i]行,第[col-i]的棋子,与右上方连续4个棋子都相同
            game->chessMap[row + i][col - i] == game->chessMap[row + i - 1][col - i + 1] &&
            game->chessMap[row + i][col - i] == game->chessMap[row + i - 2][col - i + 2] &&
            game->chessMap[row + i][col - i] == game->chessMap[row + i - 3][col - i + 3] &&
            game->chessMap[row + i][col - i] == game->chessMap[row + i - 4][col - i + 4])
            return true;
    }

    // “\“ 方向
    for (int i = 0; i < 5; i++)
    {
        // 第[row+i]行,第[col-i]的棋子,与右下方连续4个棋子都相同
        if (row - i >= 0 &&
            row - i + 4 < BOARD_GRAD_SIZE &&
            col - i >= 0 &&
            col - i + 4 < BOARD_GRAD_SIZE &&
            game->chessMap[row - i][col - i] == game->chessMap[row - i + 1][col - i + 1] &&
            game->chessMap[row - i][col - i] == game->chessMap[row - i + 2][col - i + 2] &&
            game->chessMap[row - i][col - i] == game->chessMap[row - i + 3][col - i + 3] &&
            game->chessMap[row - i][col - i] == game->chessMap[row - i + 4][col - i + 4])
            return true;
    }

    return false;
}

调用AI接口

main.cpp

#include <stdio.h>

bool checkOver() {
	if (checkWin(&game, clickPosRow, clickPosCol)) {
		Sleep(1500);
		if (game.playerFlag == false) {  //黑棋赢(玩家赢),此时标记已经反转,轮到白棋落子
			mciSendString("play res/不错.mp3", 0, 0, 0);
			loadimage(0, "res/胜利.jpg");
		} else {
			mciSendString("play res/失败.mp3", 0, 0, 0);
			loadimage(0, "res/失败.jpg");
		}
	
		getch();
		return true;
	}
	return false;
}

显示分数

在胜利窗口,或者失败窗口中,显示分数。


main.cpp

#define INIT_SCORE  1000
int  score;                // 当前分数

void initScore() {
	// 显示分数的字体设置
	settextcolor(WHITE);
	settextstyle(50, 0, "微软雅黑");

	FILE *fp = fopen("score.data", "rb");
	if (fp == NULL) {
		score = INIT_SCORE;
	} else {
		fread(&score, sizeof(score), 1, fp);
	}
	if (fp)fclose(fp);
}

void init() {
	......

	initScore();
}

更新分数

ChessData.cpp

bool checkOver() {
	if (checkWin(&game, clickPosRow, clickPosCol)) {
		Sleep(1500);
		if (game.playerFlag == false) {  //黑棋赢(玩家赢),此时标记已经反转,轮到白棋落子
			mciSendString("play res/不错.mp3", 0, 0, 0);
			loadimage(0, "res/胜利.jpg");
			score += 100;
		}
		else {
			mciSendString("play res/失败.mp3", 0, 0, 0);
			loadimage(0, "res/失败.jpg");
			score -= 100;
		}

		// 显示分数
		char scoreText[64];
		sprintf(scoreText, "当前分数 :%d", score);
		outtextxy(310, 800, scoreText);

		// 记录分数
		FILE* fp = fopen("score.data", "wb");
		fwrite(&score, sizeof(score), 1, fp);
		fclose(fp);

		getch();
		return true;
	}
	return false;
}

项目迭代

联网对战功能


通过项目的实战积累,在实战中成长

服务器联网通信开发。

游戏大厅配对功能

服务器端业务开发。

AI迭代

使用搜索树,提高算度。

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: