详解三子棋

  • 前言
  • 一.游戏规则
  • 二 .游戏流程
  • 三.代码实现
    • 3.1主体部分实现
    • 3.2 game函数的实现
      • 3.2.1创建数组
      • 3.2.2初始化棋盘
      • 3.2.3显示棋盘
      • 3.2.4玩家下棋
      • 3.2.5判断输赢
      • 3.2.6电脑下棋
      • 3.2.7判断输赢
      • 3.2.7给出结论
  • 四.一些锦上添花以及完整代码

前言

本文将介绍三子棋(井字棋)的实现方法,以游戏的流程为主线,运用模块化编程的思想,分为game.c, game.h , test.c三个文件完成代码。先看一下最后的效果吧


一.游戏规则

三子棋的游戏大家应该都不陌生,但这里还是有必要介绍一下,毕竟熟悉游戏的流程思路才会清晰。

游戏规则要求:电脑(随机)和玩家一次下一子,双方轮流交替下子,直到电脑和玩家任意一方获胜,或者棋盘满了,结束游戏。

获胜判定:只要电脑或者玩家任意一方的棋子有三子连在一起,可以为横连、纵连、斜连,则该方获胜,游戏结束。若棋盘满了,则判定为平局。
加粗样式

二 .游戏流程

整个游戏流程可分为以下这几步
1.给玩家一个菜单,让其选择进入游戏或者退出游戏

接下来是一个循环的过程


2.玩家选择进入游戏后,接下来呈现一个棋盘

3.系统提示玩家落子,玩家输入一个坐标

4.屏幕上打印棋盘,展现落子情况

5.电脑落子

6.打印棋盘,展现落子情况


7.游戏分出结果(平局,玩家胜,电脑胜)

8.回到第一步

上述流程中我们可以看到,游戏的核心部分是2到7步,因此我们将这些步骤的实现封装在一个game函数里,game函数里需要用到的函数就在game.h文件中声明,在game.c文件中定义。有了这些思考我们就可以开工了。

三.代码实现

3.1主体部分实现

首先打印一个菜单,给出两个选项,玩家的选择值用一个变量接收。

接着使用多分支switch case语句,若玩家选择开始游戏就进入game函数,若选择退出就结束程序,如果选择不符合要求就让他重新输入。

整个过程用循环结构套起来,当一套流程走完后用户又可以重新选择。这里我们选择do while循环,让这个逻辑至少走一遍。

另外这里接收用户输入的值时需要进行合法性判断。比如我期望接收一个数字,但你却输入一个字母,如果没有处理这种情况程序就会陷入死循环(至于为什么下文分析)

上代码:

void menu()
{printf("*****************************************\n");printf("*******      1. 三子棋游戏          *****\n");printf("*******      2. 退出游戏            *****\n");printf("***************************************  \n");}int main()
{int input = 0;do{menu();printf("请选择>:");while (scanf("%d", &input) != 1){printf("输入非法,请重新输入>:");while (getchar() != '\n'){;}}switch (input){case 1:printf("三子棋游戏开始\n");game1();break;case 2:printf("退出游戏\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input != 2);return 0;
}

这里的do while循环和switch case语句的配套使用基本上可以作为固定写法形成模板,以后执行类似的逻辑时直接套用它就行了。

这里讲一讲用户输入数据的合法性判断。
这里我期望用户输入一个数字,只要是数字就行,要求不高,哪怕不是1或者2都行,因为我的default可以处理。

但总有一些叛逆的玩家不这么干,给你丢一个字母放上面,按下回车,scanf函数于是开始读取数据,但是我要求接收的是%d啊,你咋给我放个字符啊。scanf函数罢工,接收失败,input也没有放入新的值,还是初值0。程序往下走,来到default这儿,提示选择错误。input != 2,再次进入do while循环。

因为上次循环时那个字母读取失败了嘛,所以还留在缓冲区里,这里scanf函数往缓冲区内一瞅,发现里面有东西,所以不会停下来等待用户输入,直接开始读取,结果又读取失败,input的值还是最初的0。所以就这样陷入死循环。

这里的解决方法就是给scanf函数套上一个while循环,当你接收成功就跳出去,如果接收不成功还要刷新缓冲区以便再次接收,关于刷新缓冲区的方法之前有讲过,感兴趣的老铁可以翻看我以前的文章

3.2 game函数的实现

void game1()
{//存放数据需要一个3*3的二维数组char board[ROW][COL] = { 0 };//初始化棋盘InitBoard(board, ROW, COL);//显示棋盘DisplayBoard(board, ROW, COL);char ret = 0;while (1){//玩家下棋PlayerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//显示棋盘//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}//电脑下棋ComputerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//显示棋盘//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}}if ('*' == ret){printf("恭喜你,你赢了\n");}else if ('#' == ret){printf("很遗憾,你输了\n");}elseprintf("平局,再接再厉\n");
}

这是game函数的实现,可以看到game函数里嵌套了许多函数,前文讲过,这些嵌套函数的定义和声明分别放在game.c和game.h文件中。接下来我将逐步介绍game函数里的每个步骤,同时讲解这些函数在game.c中是如何实现的

3.2.1创建数组

//存放数据需要一个3*3的二维数组char board[ROW][COL] = { 0 };

首先我们想到玩家和电脑下棋,那我们需要一个棋盘并记录他们落子的位置。我们创建一个3*3的二维数组来存储数据,玩家落子后就在这个位置放*,电脑落子后就放 #

这里我们使用宏定义(放在game.h中),万一我们以后想做五子棋,想要一个更大的棋盘,只需更改ROW和COL的·数值即可。

3.2.2初始化棋盘

//初始化棋盘InitBoard(board, ROW, COL);

什么是初始棋盘?也就是给数组board的每个元素都赋值为空格这个字符。

有老铁可能会有疑问,有这个必要吗?不是一开始赋初值为0了,玩家和电脑落子后再把相应的位置替换成#或者*,这不是刚刚好?

回答是有必要的,因为board这个数组不仅是记录落子信息的,后面我们要打印棋盘也要用到它。

如果一开始每个元素都是0,也就是’\0‘,空字符,打印出来是不占位置的,而 *和 #要占据一个位置,到时候棋盘打印出来岂不是会参差不齐?所以我们把每个元素都换成空格,就算这个地方没有落子,打印出来也好歹会占个位置。

//初始化期棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){board[i][j] = ' ';}}
}

3.2.3显示棋盘

//显示棋盘DisplayBoard(board, ROW, COL);


我们预想的效果就是这个样子,棋盘由横杠,竖杠,空格和数组元素组成,棋盘旁边有行列标号以便玩家更好的输入自己要下的位置。

怎么打印这个图案呢?我们首先想到的肯定是循环,不然一个一个打印岂不是要累成狗。

我们做如下分解:
首先打印列号,一个for循环搞定,格式就是”空格%-2d空格"(注意竖杠也占据一个位置),这里-2d表示域宽为2并且左对齐。为什么这里要使用域宽而不是直接用空格呢,这是为了方便以后打印更大的棋盘时,列号超过了9,出现两位数,如果不用域宽打印效果就没有那么好。当然在for循环前还要打印几个空格以便对齐。

然后我们可以将含有数据的行(简称数据行)和”—|—|—“行(简称分割行)看成一组,那么总共就有三组,其中最后一组没有分割行,那么我们又可以用一个for循环搞定,加上if语句限制分割行打印

接着我们单独看数据行。最先我们需要打印一个行号,这里我们同样使用域宽打印,原因和上面一样。然后可以把“空格%c空格”和‘|’看成一组,总共有三组其中最后一组没有‘|’,这样就可以用一个for循环配套if语句搞定。

分割行思路和数据行类似。
上代码

//显示棋盘
void DisplayBoard(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;//打印列号printf(" ");for (i = 1; i <= col; i++){printf(" %d  ", i);}printf("\n");//数据行和分割行看成一组,共有三组,最后一组没有分割行for (i = 0; i < row; i++){//打印行号printf("%d", i + 1);//打印数据行//将元素与“|”看成一组,共有三组,最后一组没有“|”for (j = 0; j < col; j++){printf(" %c ", board[i][j]);if (j < col - 1){printf("|");}}printf("\n");printf(" ");//打印分隔行//将“---”与“|”看成一组,共三组,最后一组没有“|”if (i < row - 1){for (j = 0; j < col; j++){printf("---");if (j < col - 1){printf("|");}}printf("\n");}}
}

3.2.4玩家下棋

//玩家下棋PlayerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//显示棋盘

玩家下棋,即让玩家选择坐标,也就是行号和列号,因此我们需要两个变量x和y来存储信息。

scanf函数接收时,我们同样要进行判断看它是否接收成功,方法前文提过,这里不再赘述。

数据接收成功后,我们还要对坐标进行判断,看横纵坐标是否超出范围,例如我输入一个4 4,系统就要提示坐标非法并能重新输入。

如果坐标合法我们还要进行一次判断,看该位置是否已经落子。

如果一切条件都满足后,最后才能把这个位置相对应的数组元素改成*,注意这里可不敢直接写成board[x][y]哈,横纵坐标都要减去一。

上代码

//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;printf("该你走了\n");while (1){printf("请输入坐标落子>:");while (scanf("%d %d", &x, &y) != 2){printf("输入非法,请重新输入>:");while (getchar() != '\n'){;}}//判断坐标合法性if (x >= 1 && x <= 3 && y >= 1 && y <= 3){if (' ' == board[x - 1][y - 1]){board[x - 1][y - 1] = '*';break;}else{printf("该位置已经落子,请重新选择\n");}}else{printf("坐标非法,请重新选择\n");}}

接下来是一段循环的过程,玩家下棋——电脑下棋——玩家下棋,直至分出结果,所以套上while循环

3.2.5判断输赢

//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}

玩家落子之后电脑是否要落子呢?这要看是否已经出了结果,所以我们要进行判断。

我们写一个判断输赢的函数,如果
玩家胜,返回 *,
电脑胜,返回#,
平局返回Q
未分出结果返回C
这里有个巧妙的设计,玩家和电脑胜利返回的符号,和数组里双方棋子代表的符号相同耶!为什么这么设计呢,先卖个关子。

玩家胜利和电脑胜利其实又可以归纳为一种情况,也就是分出了胜负,什么时候会分出胜负?当棋盘上三子一线时。所以我们把所有请情况都列举出来,横着三条线用for循环,竖着三条线再用for循环,斜着的两条线想不到好办法就单独用两个if语句判断。
若分出胜负,我们直接返回那条线上中的任意棋子的符号就行。例如第一行有三个#,就可以直接返回board[0][0]

这就时那个巧妙的设计,若不这样设计,我们还得判断是哪三个棋子连成一线,然后分情况返回。

若没有分出结果,则继续判断是否是平局。什么时候平局呢?当棋盘满了并且仍未分出胜负时
因为就是没有分出胜负才来判断是否平局,所以我们只需看棋盘是否已经满了。可以设计一个IsFull函数,如果棋盘满了就返回1,没满就返回0。

最后,如果上述两种情况都不满足,那就是游戏还要继续,返回C

上代码

//棋盘是否下满
int IsFull(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){if (' ' == board[i][j]){return 0;}}}return 1;
}
//判断输赢//玩家赢返回*//电脑赢返回#//平局返回Q//继续返回C
char IsWin(char board[ROW][COL], int row, int col)
{//分出胜负int i = 0;for (i = 0; i < row; i++){if (board[i][0] == board[i][1] && board[i][1] == board[i][2]&& board[i][1] != ' '){return board[i][0];}}for (i = 0; i < col; i++){if (board[0][i] == board[1][i] && board[1][i] == board[2][i]&& board[1][i] != ' '){return board[1][i];}}if (board[0][0] == board[1][1] && board[1][1] == board[2][2]&& board[1][1] != ' '){return board[1][1];}if (board[0][2] == board[1][1] && board[1][1] == board[2][0]&& board[1][1] != ' '){return board[1][1];}//平局//棋盘满了返回1,没满返回0;if (IsFull(board, row, col) == 1){return 'Q';}//继续return 'C';
}

3.2.6电脑下棋

//电脑下棋ComputerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//显示棋盘

这里电脑下棋,其实就是随机产生横坐标和纵坐标,因此我们也要两个变量x和y来存储随机值。

产生随机值要用到rand函数,它产生的是一个0~32767之间的数字,而我们需要的是1到3之间的数字,因此我们要限制范围。怎么限制呢?难道像玩家下棋一样,用if语句判断,如果不在1到3就重新生成吗?这个方法不太好,因为生成1到3的概率差不多是万分之一,虽然最后肯定能实现,但效率就大打折扣了。

所以我们用取模的方法,随机数模上3,范围是0到2,再加上1,不就是我们要的1~3吗?

然后就是和玩家下棋一样,还要判断这个地方是否已经落子。

上代码

//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;//判断坐标合法性while (1){x = rand() % row;y = rand() % col;if (' ' == board[x][y]){board[x][y] = '#';break;}}
}

这里顺便提一下,rand函数一般和srand函数搭配是使用,同时还要用到时间戳,这样才能产生“真正的随机数”,所以我们要在main函数开头加上这样一条语句。关于rand函数的具体用法有兴趣的童鞋自行去了解

srand((unsigned int)time(NULL));

3.2.7判断输赢

//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}

Iswin函数的实现上面已经写过,这里不再重复

3.2.7给出结论

if ('*' == ret){printf("恭喜你,你赢了\n");}else if ('#' == ret){printf("很遗憾,你输了\n");}elseprintf("平局,再接再厉\n");

这个地方可以自由发挥,如果玩家输了可以无情嘲讽他,“不是吧不是吧,连人工智障都下不过~”等等

不知道你有没有注意到我这儿if语句里的写法,常量放在左边,变量放在右边,包括前面的一些位置也是,其实这都是刻意为之的。
因为对与新手而言,写着写着就容易把等号写成赋值符号,那么括号里的条件真假取决于复制号右边的量,如果是0就恒为假,非0恒为真,并且这种错误较为隐蔽,编译器不会报错。
我把常量放在左边后,对常量赋值的操作是不允许的,编译器就会报错,这也是一种编程的良好习惯或者技巧。

四.一些锦上添花以及完整代码

我们其实还可以对这个程序做一些优化。例如当用户选择开始游戏后,可以让系统停顿一会并显示加载中…………等字样。

轮到电脑下棋时,玩家还没反应过来电脑就已经下完了并且重新打印了棋盘,所以我们可以让系统停顿机几秒并显示电脑思考中…………等字样增强游戏体验感。

另外整个过程屏幕上会打印很多内容显得不简洁,所以我们可以在适当的位置加上清屏命令。

其实还有很多地方可以优化,比如把双方棋子换成不同颜色打印出来,这样会更醒目一些,这些内容都可以通过查阅资料的方式进行拓展。

完整代码
test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game1.h"void menu()
{printf("*****************************************\n");printf("*******      1. 三子棋游戏          *****\n");printf("*******      2. 退出游戏            *****\n");printf("***************************************  \n");}void game1()
{//存放数据需要一个3*3的二维数组char board[ROW][COL] = { 0 };//初始化棋盘InitBoard(board, ROW, COL);//显示棋盘DisplayBoard(board, ROW, COL);char ret = 0;while (1){//玩家下棋PlayerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//显示棋盘//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}//电脑下棋ComputerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//显示棋盘//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}}if ('*' == ret){printf("恭喜你,你赢了\n");}else if ('#' == ret){printf("很遗憾,你输了\n");}elseprintf("平局,再接再厉\n");
}int main()
{srand((unsigned int)time(NULL));int input = 0;do{menu();printf("请选择>:");scanf("%d", &input);switch (input){case 1:printf("三子棋游戏开始\n");printf("玩命加载中……………………\n");Sleep(3000);system("cls");game1();break;case 2:printf("退出游戏\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input != 2);return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game1.h"//初始化期棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){board[i][j] = ' ';}}
}//显示棋盘
void DisplayBoard(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;//打印列号printf(" ");for (i = 1; i <= col; i++){printf(" %d  ", i);}printf("\n");//数据行和分割行看成一组,共有三组,最后一组没有分割行for (i = 0; i < row; i++){//打印行号printf("%d", i + 1);//打印数据行//将元素与“|”看成一组,共有三组,最后一组没有“|”for (j = 0; j < col; j++){printf(" %c ", board[i][j]);if (j < col - 1){printf("|");}}printf("\n");printf(" ");//打印分隔行//将“---”与“|”看成一组,共三组,最后一组没有“|”if (i < row - 1){for (j = 0; j < col; j++){printf("---");if (j < col - 1){printf("|");}}printf("\n");}}
}//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;printf("该你走了\n");while (1){printf("请输入坐标落子>:");while (scanf("%d %d", &x, &y) != 2){printf("输入非法,请重新输入>:");while (getchar() != '\n'){;}}//判断坐标合法性if (x >= 1 && x <= 3 && y >= 1 && y <= 3){if (' ' == board[x - 1][y - 1]){system("cls");board[x - 1][y - 1] = '*';break;}else{printf("该位置已经落子,请重新选择\n");}}else{printf("坐标非法,请重新选择\n");}}}//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;printf("该电脑走了,思考中………………\n");Sleep(2000);system("cls");//判断坐标合法性while (1){x = rand() % row;y = rand() % col;if (' ' == board[x][y]){board[x][y] = '#';break;}}
}int IsFull(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){if (' ' == board[i][j]){return 0;}}}return 1;
}//判断输赢//玩家赢返回*//电脑赢返回#//平局返回Q//继续返回C
char IsWin(char board[ROW][COL], int row, int col)
{//分出胜负int i = 0;for (i = 0; i < row; i++){if (board[i][0] == board[i][1] && board[i][1] == board[i][2]&& board[i][1] != ' '){return board[i][0];}}for (i = 0; i < col; i++){if (board[0][i] == board[1][i] && board[1][i] == board[2][i]&& board[1][i] != ' '){return board[1][i];}}if (board[0][0] == board[1][1] && board[1][1] == board[2][2]&& board[1][1] != ' '){return board[1][1];}if (board[0][2] == board[1][1] && board[1][1] == board[2][0]&& board[1][1] != ' '){return board[1][1];}//平局//棋盘满了返回1,没满返回0;if (IsFull(board, row, col) == 1){return 'Q';}//继续return 'C';
}

game.h

#pragma once
#define ROW 3
#define COL 3
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <time.h>//函数声明
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);//显示棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);//判断输赢
char IsWin(char board[ROW][COL], int row, int col);

以上就是三子棋的全部内容了,码字不易~~如果觉得这篇文章对你有帮助的话,别忘记留下你的

超详解三子棋(优化后)【万字教程包教包会】相关推荐

  1. 详解三子棋(九宫格)游戏

    三子棋是一种民间传统游戏,又叫九宫棋.圈圈叉叉棋.一条龙.井字棋等.游戏分为双方对战,双方依次在9宫格棋盘上摆放棋子,率先将自己的三个棋子走成一条线就视为胜利,而对方就算输了,但是三子棋在很多时候会出 ...

  2. 线性规划之单纯形法【超详解+图解】-转载

    线性规划之单纯形法[超详解+图解] 目录 1.作用 2.线性规划的一般形式 5.1几何意义 5.2如何判断最优 5.3如何选择新的基变量 5.4如何选择被替换的基变量 5.5终止条件 标准型: 转化为 ...

  3. C/C++实现蛇形矩阵(超详解)【沈七】

    C/C++实现蛇形矩阵(超详解) 题目链接 题目描述 输入样例 题解部分 完整代码 完结散花 悄悄告诉你: 参考文章 萌新报道! 唤我沈七就行嘿嘿. 大一软件工程在读. 菜鸡蒟蒻想在博客中记录一些算法 ...

  4. C/C++实现回文质数(超详解)【沈七】

    C/C++实现回文质数(超详解) 题目链接 题目描述 输入样例 **题解部分** **完整代码** **未完待续** 参考文章 唤我沈七就行嘿嘿. 大一软件工程在读. 菜鸡蒟蒻想在博客中记录一些算法学 ...

  5. Android vector标签 PathData 画图超详解

    此文章来源于https://www.cnblogs.com/yuhanghzsd/p/5466846.html点击打开链接 Android vector标签 PathData 画图超详解 SVG是一种 ...

  6. 【阿里云课程】详解深度学习优化:泛化与正则化,学习率与最优化

    大家好,继续更新有三AI与阿里天池联合推出的深度学习系列课程,本次更新内容为第6课中两节,介绍如下: 第1节:泛化与正则化 第1节课内容为:泛化与正则化,讲述泛化的概念与重要性,各种正则化方法,包括显 ...

  7. python控制手机模拟器_Appium+python自动化之连接模拟器并启动淘宝APP(超详解)...

    简介 上一篇讲解完模拟器的安装.配置好以后,就好比我们手机已经买好,并且系统已经做好了,就差我们用数据线和电脑连接开始实战了,这篇宏哥就带着小伙伴们和童鞋们趁热打铁,讲解和分享一下如何连接模拟器(电脑 ...

  8. js打印三角形超详解

    js打印三角形超详解 j控制星星的总行数,i控制每行星星的打印个数 打印图形如下: (1) (2) //str=""用来存储星星// 理解步骤1:在一行输出6个星星如何操作,在循环 ...

  9. 【平衡小车制作】(七)串级PID调参及平衡成果展示(超详解)

      大家好,我是小政.本篇文章我将针对PID调参进行详细的讲解,让每位小伙伴能够对比例.积分.微分三个参数如何调节有更加清晰的理解. 一.调参步骤 确立机械中值 直立环(内环)--Kp极性.Kp大小. ...

最新文章

  1. 马莉 - 人神共愤的处女座
  2. SQL2005 四个排名函数(row_number、rank、dense_rank和ntile)的比较
  3. c语言如何随机获取1kb,基于VS2010+C语言实现播放器的顺序播放、随机播放
  4. D3.js、echar.js 前端必备大数据技能
  5. Hive on Spark和Spark sql on Hive,你能分的清楚么
  6. img src请求后台值值能判断_图片src拼接后台返回ID
  7. 随记(电脑硬件对比)
  8. jquery 图像滑块_jQuery缩略图图像滑块– CSS,JavaScript
  9. 【日常】关于爬虫中iframe节点处理小结(以超星课件下载为例)
  10. 苦熬31年终于登陆科创板!WPS如何一步步熬到了今天?
  11. MongoDB实战(MongoDB开发者现身说法)
  12. 我和谷歌共同成长----Andriod(安卓)开发(持续更新)
  13. 【2020】【论文笔记】太赫兹新型探测——太赫兹特性介绍、各种太赫兹探测器
  14. 物联网技术周报第 103 期: DIY 智能音箱:基于 Raspberry Pi + Snowboy + AVS
  15. 基于PHP的精品课程教学网站
  16. 计算机毕业设计ssm客房订餐系统s2whx系统+程序+源码+lw+远程部署
  17. Vulnhub靶场题解
  18. es父子结构查询_探索ES-嵌套对象和父子对象(四)
  19. 16.数据统计之数据分组方法
  20. Dreamweaver 1 网页制作

热门文章

  1. 鸡头?凤尾?请过来人给指点下
  2. 二分查找法-floor和ceil函数实现(C++)
  3. 线索化二叉树的学习记录
  4. 基于stm32f103c8t6与手机蓝牙控制LED
  5. Hibernate从0开始,入门到放弃,一文上手
  6. P6770 [USACO05MAR]Checking an Alibi 不在场的证明(spfa)
  7. 北斗卫星同步时钟在分布式系统时钟同步的重要性
  8. 视频教程-【CVPR2018】A Closer Look at Spatiotemporal Convolu-计算机视觉
  9. Cy3花青素荧光染料,(CAS:146368-13-0 )
  10. 为什么把资金叫做头寸