news 2026/9/3 1:58:37

Python实现黑白棋游戏:核心算法与AI对战全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python实现黑白棋游戏:核心算法与AI对战全解析

简介:一份基于Qt/C++的黑白棋完整项目代码,适合正在学习Qt界面开发、C++游戏逻辑或网络编程的开发者参考。项目中棋盘界面由Qt Designer设计,包含控件布局与交互响应;核心规则模块负责合法落子判断、翻转规则、计分和胜负检测,并带有双人网络对战思路,可作为课程设计或毕业设计的起点。资源共15个文件,压缩包仅1.12MB,以cpp/h源码为主,另有ui界面文件、pro工程配置、规则c文件以及jpg/png图片素材,工程结构紧凑,便于直接编译阅读和二次开发。已有348人学习/下载。通过该工程能够了解界面代码的自动生成与调用方式,掌握事件处理流程,并初步认识QTcpSocket/QUdpSocket在双人实时对战中的应用,同时涉及消息同步、心跳包、超时重传等网络编程细节,对提升C++工程组织能力和算法设计能力有明显帮助。

1. 项目概述与核心思路

黑白棋,也叫奥赛罗棋(Othello),是一个特别适合拿来做编程练手的棋类游戏。它规则不复杂,但算法里牵扯到的逻辑判断、边界处理、策略搜索,覆盖面相当广。拿它来写代码,你能把二维数组操作、方向遍历、状态判定、甚至是简单AI算法全部串起来,练完这一套,再去写别的棋牌类项目、寻路算法、甚至是状态机逻辑,都会顺很多。

先说清楚这篇博文要做什么:我会从零开始,用Python实现一个完整的黑白棋游戏,包含标准8x8棋盘、双方轮流落子、合法落子判定、翻转棋子、跳过回合、终局胜负统计。代码部分可以直接跑起来,同时我会把每个关键函数的判断逻辑拆开讲清楚——尤其是“怎么判断一个落子点是否合法”和“怎么把该翻转的棋子全部翻过来”这两个核心问题,很多人第一次写黑白棋,坑都踩在这两个地方。

适合谁来读?如果你刚学完Python基础语法,想找一个不靠抄、能自己写出来的小项目;或者你已经在写棋类程序,想看看别人的判断逻辑是怎么组织的;又或者你想在黑白棋基础上做一个人机对战AI,那这篇博文都适合你。我会把每一步怎么想、为什么这么写都交代明白,而不是直接甩一份代码让你自己琢磨。

1.1 黑白棋规则里最容易忽略的三个细节

在动笔写代码之前,规则必须吃透。黑白棋的基本规则:棋盘开局时正中间四颗子交叉摆放,黑棋先手,双方轮流在棋盘上落子,落子的位置必须能“夹住”至少一个对方棋子,落子后所有被夹住的对方棋子翻转为己方棋子。当双方都无法落子时游戏结束,棋子多的一方获胜。

听起来简单,但有三个细节是新手特别容易忽略的。

第一个细节:“夹住”是可以在八个方向上同时发生的。也就是说你落一颗子,可能同时夹住横向、纵向、斜向的多条线,每条线上被夹住的棋子都要翻。代码里要同时检查八个方向,而不是只检查一个。

第二个细节:被夹住的棋子必须在同一条直线上,中间不能有空格或者己方棋子隔断。换句话说,从你落子的位置出发,沿着一个方向走到对方棋子之后,必须再遇到一颗己方棋子才能构成合法翻转。如果走出去只碰到边界或者空格,这一条方向就不合法。

第三个细节:如果一个玩家没有合法落子位置,必须自动跳过回合,由对方继续落子。这个规则在代码里对应“当前玩家无棋可下”的状态判定,很多人会漏掉,结果程序直接卡死。

规则搞清楚了,下面进入正题。

1.2 为什么拿Python实现黑白棋

选Python写黑白棋,不是因为Python性能有多强,而是因为它处理二维逻辑和快速原型非常顺手。你不需要关心内存布局,不需要手动管理棋盘数组的边界,用双层列表天然就能表示8x8棋盘,配合列表推导式、枚举函数这些东西,代码写出来非常直观。

而且后续如果你想扩展AI功能,Python生态里现成的算法模板、可视化库也很多,比如你想把人机对战做成图形界面,用pygame或者tkinter都行;想加AI决策,minimax、alpha-beta剪枝这类经典算法,Python写起来也最顺手。

说实话,黑白棋这种项目用Python做,逻辑量和代码量都刚好卡在学习者的舒适区——不会简单到无话可说,也不会复杂到劝退。我自己的经验是:完整写过一次黑白棋之后,你对“状态”这个词的理解会完全不一样。

2. 核心数据结构与算法拆解

2.1 棋盘建模:用两层列表还是数字矩阵

棋盘建模是一切的起点。黑白棋棋盘是8x8的二维网格,最自然的表示方式就是嵌套列表,也就是一个大列表里装着8个小列表,每个小列表代表一行,每个元素代表一个格子。

我建议这样定义棋子状态:

  • 0:空格
  • 1:黑棋
  • 2:白棋

为什么不直接用字符串或者布尔值?因为后续做AI估值的时候,你往往需要把棋盘状态编码成数字输入给算法,用数字是最省事的。而且数字的打印和调试也方便,一眼就能看出棋盘上每一处的状态。

初始化棋盘的时候,中间四颗子的位置是固定的。标准的开局是:第3行第3列黑棋,第3行第4列白棋,第4行第3列白棋,第4行第4列黑棋。这里说的行和列都是0-based索引,也就是从0数到7,中间四个点分别是(3,3)、(3,4)、(4,3)、(4,4)。

还有一个小细节:有些实现会把棋盘定义成10x10或者带边框的矩阵,目的是简化边界判断——反正越界的位置都是无效位置,直接跳过就行。但我觉得8x8内加边界判断更干净,因为边界判断的代码只在一个集中函数里出现,不会到处散落。

2.2 方向向量的使用:八个方向的统一处理

写棋盘类程序有一个很实用的技巧:把方向统一成“向量”。在8x8棋盘上,一个位置可以沿八个方向移动,对应八个坐标变化量:

  • 水平左右:(0, 1)(0, -1)
  • 垂直上下:(1, 0)(-1, 0)
  • 两条对角线:(1, 1)(1, -1)(-1, 1)(-1, -1)

把方向定义成这样的元组列表之后,写合法性判断的时候,就可以用一个循环把八个方向逐个检查,而不是写八个重复代码块。这个思想在后续很多棋盘类游戏里都用得到,比如五子棋的胜利判断、国际象棋的走法生成,本质都是同一套路。

# 八个方向 DIRS = [ (0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1) ]

2.3 落子合法性判断:从落子点朝八个方向“吃”棋子

这是整个黑白棋最核心的算法,没有之一。判断逻辑可以分成两层:

首先是“有没有路”。从当前落子位置(x, y)出发,沿着某个方向(dx, dy)一步步往外走,每走一步要满足两个条件:不出界、而且落在对方棋子上。如果第一步走出去是空格,那这个方向直接废掉;如果第一步走出去就是己方棋子,那这个方向也是废的,因为中间根本没夹住任何对方棋子。

其次是“有没有终点”。沿着某个方向走的过程中,如果遇到了己方棋子,那么说明从落子点到这颗己方棋子之间的所有对方棋子都可以被翻转,这就是“夹住”的意思。如果一直走到棋盘边界都是对方棋子或者空格,没有遇到己方棋子,这个方向同样不合法。

所以合法性判断的本质是:存在至少一个方向,从落子点出发,第一步是对方棋子,继续走能遇到己方棋子。

我贴一下具体的判断函数,代码里有详细的注释:

def is_valid_move(board, row, col, player): """ 判断 (row, col) 是否是 player 的合法落子点 player: 1 黑棋 / 2 白棋 """ # 落子位置必须是空格 if board[row][col] != 0: return False opponent = 3 - player # 对方棋子 for dr, dc in DIRS: r, c = row + dr, col + dc # 第一步必须落在棋盘内,而且是对方棋子 if not (0 <= r < 8 and 0 <= c < 8) or board[r][c] != opponent: continue # 沿着这个方向继续走 r += dr c += dc while 0 <= r < 8 and 0 <= c < 8: if board[r][c] == player: return True if board[r][c] == 0: break r += dr c += dc return False

初学者最容易搞错的一个地方:直接从落子点往外找己方棋子,而忽略了“第一步必须是对方棋子”。不加这一步判断的话,会出现一个离谱的局面——落子点的斜对角就是己方棋子,中间空一格,也被判定成合法落子,这显然是错的。

2.4 翻转棋子的实现:返回而非修改是更好的设计

判断合法之后,下一步就是翻转。这里有一个设计上的小讲究:我见过很多初版代码,一上来就写一个flip(board, row, col, player)函数,直接原地修改棋盘。这个做法在单次落子场景下没问题,但当你后面做人机对战时,AI需要反复模拟落子、撤销落子来评估局面。如果翻转函数直接改原棋盘,你还要额外写一个复制棋盘的逻辑,否则模拟完一次局面就乱了。

我的建议是:翻转函数只负责返回“需要翻转的棋子位置列表”,对外层棋盘的操作由业务逻辑去做。这样做的好处是,函数是纯函数,输入棋盘和落子位置,输出翻转列表,不产生副作用,测试起来也方便。

def get_flips(board, row, col, player): """ 返回 (row, col) 落子后,所有需要翻转的棋子位置列表 """ flips = [] opponent = 3 - player for dr, dc in DIRS: path = [] r, c = row + dr, col + dc while 0 <= r < 8 and 0 <= c < 8 and board[r][c] == opponent: path.append((r, c)) r += dr c += dc # 走出循环后,如果当前位置是己方棋子,说明 path 里所有棋子可翻转 if 0 <= r < 8 and 0 <= c < 8 and board[r][c] == player: flips.extend(path) return flips

这里有个容易踩的坑:get_flipsis_valid_move的判断逻辑非常相似,能不能复用?从思路上说,可以——is_valid_move本质上就是len(get_flips(board, row, col, player)) > 0。我在实际代码里通常会把两个函数分开,原因是is_valid_move在局面的合法性筛选时被高频调用,性能上更轻量;而get_flips只在确认落子时才调用。两者语义轻重不同,分着写反而更清晰。

3. 实操过程:完整代码实现

3.1 棋盘初始化和显示

这部分最简单,但我还是要提一个工作习惯:把棋盘的初始化、当前的玩家、双方棋子数量全部封装成全局状态,方便后续扩展。就算你现在只做双人对战,也要养成“状态集中管理”的意识,后面加AI或者加入悔棋功能时,你会发现这个决策能帮你省掉大把时间。

def init_board(): """初始化8x8棋盘,中间四子按标准放置""" board = [[0] * 8 for _ in range(8)] board[3][3] = 1 board[4][4] = 1 board[3][4] = 2 board[4][3] = 2 return board def print_board(board): """打印棋盘""" print(" " + " ".join(str(i) for i in range(8))) for i in range(8): row = [str(i)] for j in range(8): if board[i][j] == 0: row.append(".") elif board[i][j] == 1: row.append("●") else: row.append("○") print(" ".join(row))

print_board里我用.表示空格,用实心圆和空心圆表示黑白棋。你也可以用BW,但打印出来视觉效果差一些。这里有一个小技巧:给玩家编号12是黑棋和白棋,分别对应不同的字符,后续所有逻辑都围绕数字展开,展示层只做转换,这样职责分离。

3.2 获取所有合法落子点

在实际对局中,玩家需要的不是一个一个试哪个位置能下,而是希望程序把所有可落子位置标出来。这个逻辑特别简单,就是在整个棋盘上遍历所有空格,逐个用is_valid_move判断。

def get_valid_moves(board, player): """返回 player 当前所有合法落子位置""" moves = [] for r in range(8): for c in range(8): if board[r][c] == 0 and is_valid_move(board, r, c, player): moves.append((r, c)) return moves

注意,这个函数会在每一回合都调用一次。如果返回的列表为空,说明当前玩家无棋可下,必须跳过。这一块的逻辑必须在主循环里处理,否则很容易出现玩家输错坐标或者程序报错退出的情况。

3.3 落子与主循环

落子逻辑串起来就是三件事:验证位置合法、翻转棋子、把棋子放到落子点上。顺序上要注意,翻转操作是基于当前棋盘状态的,所以要先把翻转位置拿到,再统一更新棋盘。如果把棋子先放上去再取翻转列表,翻转逻辑就会出错——因为己方棋子已经占据了这个位置,方向上判断会出问题。

def make_move(board, row, col, player): """执行落子,返回是否成功""" if not is_valid_move(board, row, col, player): return False flips = get_flips(board, row, col, player) board[row][col] = player for r, c in flips: board[r][c] = player return True

主循环的完整结构如下。这里我额外处理了两个点:一是跳过回合时保留原玩家继续下;二是终局时游戏结束标志的触发条件。

def count_pieces(board): """统计黑白双方棋子数量""" black = sum(row.count(1) for row in board) white = sum(row.count(2) for row in board) return black, white def main(): board = init_board() current_player = 1 # 黑棋先手 while True: print_board(board) black, white = count_pieces(board) print(f"黑棋: {black} 白棋: {white}") valid_moves = get_valid_moves(board, current_player) # 当前玩家无棋可下 if not valid_moves: other_player = 3 - current_player other_moves = get_valid_moves(board, other_player) if not other_moves: # 双方都不能下,游戏结束 break print(f"玩家 {current_player} 无合法落子,跳过回合") current_player = other_player continue print(f"当前玩家: {'黑棋' if current_player == 1 else '白棋'}") print(f"合法落子位置: {valid_moves}") try: row = int(input("请输入行号(0-7): ")) col = int(input("请输入列号(0-7): ")) except ValueError: print("输入无效,请输入数字") continue if (row, col) not in valid_moves: print("该位置不是合法落子点,请重新输入") continue make_move(board, row, col, current_player) current_player = 3 - current_player print_board(board) black, white = count_pieces(board) if black > white: print(f"黑棋获胜!比分 {black}:{white}") elif white > black: print(f"白棋获胜!比分 {black}:{white}") else: print(f"平局!比分 {black}:{white}") if __name__ == "__main__": main()

这里3 - current_player是一个很巧妙的交换写法:当current_player为1时,3-1=2;当current_player为2时,3-2=1。两行代码就能实现玩家切换,不用写if判断。

3.4 我实际调试中踩过的三个坑

第一个坑:valid_moves判断没有提前缓存。刚开始写的时候,我在玩家输入坐标之后直接调is_valid_move(board, row, col, player)判断,但玩家在输入之前,界面里显示的是遍历出来的合法点,输入之后又重新算一遍。这个逻辑上没问题,但调试时很容易出现“用户看到的合法点”和“程序实际判断的合法点”不一致的情况。所以我把合法点存在一个变量里,显示和判断都用同一个列表,彻底杜绝了这个隐患。

第二个坑:没有处理无合法落子时的循环。有一次测试时,程序走到终局前,某一方已经没有合法落子了,但因为我没有做跳过处理,主循环直接死循环了。后来加了跳过逻辑,但最初我只跳了一次,没想到双方都可能连续无棋可下,所以在判断当前玩家无棋可下之后,必须再判断对方有没有棋可下,才能决定是否结束。

第三个坑:边界条件没有测试完整。棋盘四个角落的落子、边线上的落子,判断逻辑最容易出错。我的建议是写几个已知棋谱局面来验证代码正确性,比如黑白棋里著名的“翼型开局”局面(黑棋第一步下到(2, 3)(3, 2)),逐步验证每一步的合法落子是否符合标准棋谱。

4. 常见问题与排查技巧实录

写黑白棋代码的过程中,有几个问题出现的频率特别高,我把它们整理成一张速查表,遇到问题直接对号入座。

4.1 常见错误速查表

错误现象可能原因排查方法
落子后棋子没有翻转get_flips中第一步没有判断对方棋子打印落子点每个方向的path变量
合法落子点判定太多方向判断没有要求“最终遇到己方棋子”检查is_valid_movewhile循环跳出条件
玩家输入坐标后程序崩溃输入了非数字字符输入代码外层加try-except
某一方无棋可下但程序没有跳过主循环里没有检测valid_moves为空的情况在每回合开始时检查valid_moves空列表
终局比分错误翻转逻辑重复翻转,或者落子前统计了棋子数make_move前后分别打印棋盘对比状态
棋盘打印偏移列标题用join时字符宽度不一致统一使用单个数字和符号,注意中文字符宽度

4.2 调试技巧:单步打印在黑白棋里特别好用

黑白棋这种棋盘类程序的调试,最有效的方式不是断点,而是在关键判断函数里临时加打印语句。我调试is_valid_move的时候,会在每个方向判断处打印当前的行列值和棋盘状态,这样一眼就能看出来是哪个方向判断出错。

有一个我自己总结的小方法:在调试时,把棋盘打印成数字模式,用0、1、2标识状态,避免字符显示带来的干扰。因为在终端里显示的宽度和数字不一样,视觉上容易看歪。

def debug_board(board): """打印数字版棋盘,方便调试""" for row in board: print(" ".join(str(x) for x in row))

当你确认逻辑正确之后,再切换回字符样式。这个习惯能让你更快定位是显示问题还是逻辑问题。

4.3 性能问题:黑白棋需要做性能优化吗

很多人写完第一版之后会问:这个程序能跑多快?说实话,纯双人对战的场景,性能完全不是问题——8x8的棋盘总共64个格子,每回合最多遍历64个位置,每个位置检查8个方向,单次计算量撑死几百次操作,人类玩家下棋,程序性能连1%都用不到。

真正的性能瓶颈出现在后面:如果你要写AI,AI需要考虑很多步之后的局面,每一步都要模拟落子、翻转、评估、回溯。这时候同样的判断函数会被调用成千上万次,性能就变得重要了。到时候可以做的优化有:把棋盘编码成位棋盘(bitboard)用位运算处理,这个方案在黑白棋里尤其有效,因为64个格子正好对应64位整数的每一位;或者用对称性剪枝减少搜索空间。但这些是进阶内容,等把基础版跑通再做也不迟。

5. 扩展方向:从双人对战到AI助手

5.1 一个最简单的AI策略:贪心算法

写完了双人对战版本,下一步最自然的扩展就是加入AI。最简单的AI策略是贪心:每次选择能让己方棋子数量增加最多的落子点。听起来很幼稚,但实测下来,贪心AI对完全不会玩的新手还是有一定威胁的,尤其是面临终局的时候,棋子数量最大化往往能赢下一些局。

实现起来很简单,只需要在AI回合遍历所有合法落子点,用get_flips统计每个点能翻转多少个棋子,选翻转数量最多的那个即可。

def ai_move_greedy(board, player): """贪心AI:选择翻转棋子数最多的落子点""" best_move = None best_count = -1 for row, col in get_valid_moves(board, player): flips = get_flips(board, row, col, player) if len(flips) > best_count: best_count = len(flips) best_move = (row, col) return best_move

不过贪心AI有一个致命弱点:它只顾眼前的翻转数量,不考虑后续局面的好坏。黑白棋是一个“少即是多”的游戏——角上的棋子是永久性的,因为角不会被翻转;而边上的棋子相比之下还算稳定。贪婪拿中间位置虽然一时棋子多,但可能把角的控制权送给了对手。如果你想让AI更聪明,可以引入“位置权重表”,给角、边、中心位置不同的分数,让AI在每个位置计算一个“落子后局面评分”,这比纯贪心要强很多。

5.2 加入AI之后的主循环改动

加了AI之后,主循环的改动其实很小。只需要判断当前玩家是人类还是AI,是AI就调用AI函数自动落子,是人工就继续走输入流程。这也验证了前面设计的好处:因为状态都集中管理,AI回合和人类回合的调用接口一样,都是“给定棋盘和玩家,算出落子位置”,至于这个位置是用户输入的还是算法算出来的,主循环根本不需要关心。

is_ai = {1: False, 2: True} # 黑棋人工,白棋AI if is_ai[current_player]: row, col = ai_move_greedy(board, current_player) print(f"AI ({'白棋' if current_player == 2 else '黑棋'}) 落子: ({row}, {col})") else: # 人类输入逻辑不变 ...

这里有一个小细节值得注意:AI落子之前最好加一个小延迟(比如time.sleep(1)),否则AI秒落子,你根本看不清它的走法,对局的沉浸感会大打折扣。

5.3 想做可视化界面?pygame的基本思路

如果不想在命令行里玩,可以用pygame做一个简单的图形界面。核心思路:把8x8棋盘渲染成网格,每格根据状态绘制黑色圆或白色圆,玩家点击棋盘时,把鼠标坐标换算成行列坐标,再走相同的合法性判断逻辑。

import pygame # 假设棋盘渲染相关常量 CELL_SIZE = 60 BOARD_SIZE = 8 def mouse_to_board(pos): """把鼠标坐标换算成棋盘行列坐标""" x, y = pos col = x // CELL_SIZE row = y // CELL_SIZE if 0 <= row < BOARD_SIZE and 0 <= col < BOARD_SIZE: return row, col return None, None

这种UI渲染代码本身不难,难点还是在游戏逻辑。所以我的建议是:先把命令行版本跑通、跑对,再考虑加UI。逻辑和显示分离的结构,确实让我在后续加pygame界面的时候省了很多事,基本没改逻辑代码,只加了渲染层。

最后再分享一点个人经验

黑白棋代码这个项目,我前前后后写过三遍。第一遍在大学选课时,用C语言写了一个非常粗糙的版本,函数满天飞,判断逻辑一团乱麻,跑起来经常出bug。第二遍是求职前用Python重写,精简了结构,把核心算法提炼成短小精悍的函数,面试时正好被问到,当场写了一遍合法性判断,逻辑清晰,面试官印象还挺好。第三遍就是这篇博文呈现的版本,加入了AI和可视化扩展。

如果你正在学编程,我强烈建议找个机会把黑白棋完整写一遍。不是因为黑白棋本身多有用,而是它几乎覆盖了所有基础编程的核心能力:二维数组操作、状态管理、复杂条件判断、模块化设计、算法优化思路、甚至人机交互逻辑。这些能力放到任何项目里都是通用的。

最后送你一个查bug的小技巧:遇到棋盘类程序出错,先别急着看代码,手工在草稿纸上画出当前棋盘状态和你想测试的落子点,沿着八个方向一步步模拟一遍棋子的翻转过程,再对照你的代码逻辑走一遍,绝大多数的非法判定和漏翻转问题都能当场发现。这个习惯我到现在还在用,真的很管用。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 1:58:23

WinCE设备串口不足?VSPD Mobile 4.2虚拟串口实战指南

简介&#xff1a;VSPD Mobile 4.2 是运行于 WinCE 平台的虚拟串口软件&#xff0c;主要用于嵌入式开发与 WinCE 应用调试场景。通过创建成对的虚拟串口&#xff0c;开发者可在不连接真实硬件的情况下模拟串口收发&#xff0c;便于进行串口通信调试、上下位机联调和多串口应用验…

作者头像 李华
网站建设 2026/9/3 1:57:21

B 端买家采购心理:读懂 AI 时代海外采购商如何判断供应商可信度

外贸 B 端采购决策是理性且谨慎的行为&#xff0c;海外采购商选择供应商&#xff0c;不会仅凭单一信息就下定合作决心。生成式 AI 介入采购流程之后&#xff0c;采购商的决策路径发生变化&#xff0c;但是底层判断供应商可信度的心理逻辑并未改变&#xff0c;只是获取信息的渠道…

作者头像 李华
网站建设 2026/9/3 1:56:45

STM32F401调PID用VOFA+看波形:从工程实现到调参避坑实战

简介&#xff1a;面向STM32F401的PID控制调试工程&#xff0c;配套VOFA串口数据可视化工具&#xff0c;适合需要实际调参的嵌入式学习者和开发者。工程基于HAL库编写&#xff0c;包含PID算法实现、串口协议封装、控制主循环、GPIO定时器配置等完整代码&#xff0c;重点演示如何…

作者头像 李华
网站建设 2026/9/3 1:56:08

高保低音:从电子音乐现场到回放设备的低频还原解析

看到「高保低音」这四个字&#xff0c;搭配 acloudyskye 在 Rabbit Jump 的现场 set&#xff0c;很多人的第一反应是&#xff1a;这段现场的音频素材在低频部分应该保留得比较完整&#xff0c;适合用来听低音&#xff0c;也适合拿来判断耳机、音箱和回放环境的能力。但如果你真…

作者头像 李华
网站建设 2026/9/3 1:55:38

51单片机16x16点阵显示系统设计:从动态扫描原理到滚动显示实现

简介&#xff1a;本资源是一套完整的基于51单片机的1616 LED点阵显示控制系统设计资料&#xff0c;适用于高校电子类课程设计、单片机实训及嵌入式入门项目开发。系统支持汉字/字符/图形动态显示&#xff0c;具备独立按键切换显示模式&#xff08;静止、闪烁、左右平移&#xf…

作者头像 李华
网站建设 2026/9/3 1:55:23

App Studio调整AI费用后,Web3+AI开发如何稳健落地?

8月24号的web3国内外新闻简报里&#xff0c;有一个值得开发者和产品负责人注意的动向&#xff1a;App Studio调整了AI应用创建与编辑的相关费用。这条消息在行情类资讯里往往排不到前面&#xff0c;但对正在做AI类web3工具、打算把生成式AI能力接到链上应用的人来说&#xff0c…

作者头像 李华