在上篇文章 VSCode 中搭建 OpenCode AI 编程助手完整指南 中,我们介绍了 OpenCode 的基础使用方法。在实际使用中,除了 OpenCode Zen 官方套餐外,很多开发者希望使用其他 AI 模型来降低成本或获得更好的中文编程支持。阿里云 Qwen(通义千问)作为国产优秀的大语言模型,在中文理解和代码生成方面表现出色,而且接入成本较低。本文将详细介绍如何在 OpenCode 中接入 Qwen 模型。

阅读全文 »

前言

五子棋之后,本文介绍光影拼图游戏的开发。相比其他游戏,拼图的核心难点在于图片处理与碎片管理:如何将一张图片切割成碎片,实现拖拽吸附,并保证在移动端和桌面端都有良好的交互体验。

拼图游戏是一个看似简单但技术实现复杂的益智游戏,实际开发中需要解决:

  1. 图片处理:如何动态加载、切割图片,并处理加载失败的情况
  2. 碎片管理:如何生成、定位、旋转和吸附碎片
  3. 响应式设计:如何在不同屏幕尺寸下保持游戏体验
  4. 移动端适配:如何统一处理触摸和鼠标事件
  5. 性能优化:如何管理图片资源和动画帧

本文重点讲解三个核心问题:

  1. 图片切割与碎片生成:Canvas 图片处理技术
  2. 拖拽吸附算法:碎片位置匹配与自动吸附
  3. 跨平台事件处理:统一鼠标与触摸交互

项目结构

1
2
3
4
5
6
7
8
9
10
11
source/ai-games/puzzle/
├── index.md # 游戏页面入口(HTML + CSS)
└── modules/
├── puzzle-config.js # 配置模块(难度、颜色、动画参数)
├── puzzle-core.js # 核心逻辑(碎片生成、吸附算法)
├── puzzle-render.js # 渲染模块(Canvas 绘制、动画)
├── puzzle-ai.js # AI 提示系统
└── puzzle-game.js # 游戏入口(整合模块、事件处理)

source/ai-games/gamejs/
└── game-manager.js # 游戏生命周期管理器(复用)
阅读全文 »

前言

继扫雷、俄罗斯方块之后,本文介绍五子棋 AI 对战游戏的开发。相比其他游戏,五子棋的核心难点在于AI 算法的实现:如何在有限时间内找到最优落子位置,同时平衡进攻与防守。

五子棋是一个信息完全公开的零和博弈游戏,理论上可以通过搜索算法找到最优解。但实际应用中,我们需要考虑:

  1. 搜索空间巨大:15×15 棋盘有 225 个位置,搜索深度每增加一层,复杂度指数级增长
  2. 时间限制:玩家期望 AI 在几秒内做出决策,不能无限搜索
  3. 用户体验:AI 需要”合理”地犯错,给玩家获胜的机会

本文重点讲解两个核心问题:

  1. PJAX 兼容性处理:如何在静态博客中实现流畅的页面切换
  2. AI 算法设计:如何实现具有不同难度的棋局评估与搜索

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
source/ai-games/gomoku/
├── index.md # 游戏页面入口(HTML + CSS)
└── modules/
├── gomoku-config.js # 配置模块(难度、分数常量)
├── gomoku-utils.js # 工具函数(深拷贝、存储)
├── gomoku-core.js # 棋盘核心(状态管理、胜负判定)
├── gomoku-ai.js # AI 引擎(搜索算法、评估函数)
├── gomoku-ui.js # UI 渲染(画布绘制、事件处理)
└── gomoku-main.js # 游戏入口(整合模块、流程控制)

source/ai-games/gamejs/
└── game-manager.js # 游戏生命周期管理器
阅读全文 »

前言

作为一名钓鱼爱好者,我在抖音分享了许多钓鱼视频。为了让博客访问者能够直观地浏览我的抖音作品,决定在Hexo博客中集成抖音视频相册功能。

本文将详细介绍整个实现过程,从数据配置到页面展示,再到进阶优化,帮助你快速在博客中实现类似功能。

阅读全文 »

前言

继俄罗斯方块之后,本文讲解扫雷游戏的开发。扫雷是 Windows 系统经典的益智小游戏,核心玩法是揭开格子找出所有安全区域,同时避免触雷。

相比俄罗斯方块,扫雷更侧重于逻辑推理和策略标记,需要实现数字计算、递归扩散、旗标标记等功能。

项目结构

游戏放置在 source/ai-games/minesweeper/ 目录下,文件结构如下:

1
2
3
source/ai-games/minesweeper/
├── index.md # 游戏页面(HTML + 样式)
└── minesweeper-game.js # 游戏核心逻辑(约570行)

游戏页面实现

顶部状态栏

扫雷游戏采用经典的三段式状态栏设计:

1
2
3
4
5
6
7
8
9
<div class="game-header">
<div class="game-display">
<span id="score">10</span> <!-- 剩余地雷数 -->
</div>
<button id="face-btn">😀</button> <!-- 笑脸按钮 -->
<div class="game-display">
<span id="timer">000</span> <!-- 计时器 -->
</div>
</div>
  • 剩余地雷数:初始为地雷总数,每插一面旗减1
  • 笑脸按钮:点击重新开始游戏,不同状态显示不同表情
  • 计时器:从第一次点击开始计时,秒数递增

难度选择

1
2
3
4
5
const DIFFICULTY = {
easy: { cols: 9, rows: 9, mines: 10 }, // 简单
normal: { cols: 16, rows: 16, mines: 40 }, // 普通
hard: { cols: 30, rows: 16, mines: 99 } // 困难
};

三种难度满足不同水平玩家的需求。

核心逻辑实现

游戏状态管理

1
2
3
4
5
6
7
8
9
let board = [];           // 存储地雷和数字
let revealed = []; // 记录格子是否已揭开
let flagged = []; // 记录格子是否已插旗
let minePos = []; // 存储所有地雷位置
let firstClick = true; // 是否第一次点击
let gameOver = false; // 游戏是否结束
let win = false; // 是否胜利
let mineCount = MINES; // 剩余地雷数
let revealCount = 0; // 已揭开格子数

board 中:-1 表示地雷,0-8 表示周围地雷数量。

初始化棋盘

1
2
3
4
5
6
7
8
9
10
11
12
function initBoard() {
for (let y = 0; y < ROWS; y++) {
board[y] = [];
revealed[y] = [];
flagged[y] = [];
for (let x = 0; x < COLS; x++) {
board[y][x] = 0;
revealed[y][x] = false;
flagged[y][x] = false;
}
}
}
阅读全文 »

前言

继上一篇文章介绍贪吃蛇小游戏的实现方案后,本文继续讲解俄罗斯方块游戏的开发。相比贪吃蛇,俄罗斯方块的实现更加复杂,涉及方块旋转、多行消除、速度递增等机制。

通过这个案例,可以学习到如何用原生 JavaScript 实现经典的方块下落消除游戏。

项目结构

游戏放置在 source/ai-games/tetris/ 目录下,文件结构如下:

1
2
3
source/ai-games/tetris/
├── index.md # 游戏页面(HTML + 样式)
└── tetris-game.js # 游戏核心逻辑(约394行)

与贪吃蛇相同的独立目录结构,便于管理和扩展更多游戏。

游戏页面实现

Canvas 画布

1
<canvas id="game-canvas" width="240" height="400"></canvas>

俄罗斯方块采用 12×20 网格,每个格子 20px,宽度较窄以适应竖屏布局。

样式设计

1
2
3
4
5
6
7
canvas {
border: 2px solid #2196F3;
border-radius: 8px;
background: #111;
box-shadow: 0 0 20px rgba(33, 150, 243, 0.3);
touch-action: none;
}

使用蓝色主题(#2196F3)与贪吃蛇的绿色区分开。

核心逻辑实现

方块形状定义

游戏包含 7 种经典方块形状,用矩阵和颜色表示:

1
2
3
4
5
6
7
8
9
10
const SHAPES = {
I: { matrix: [[1,1,1,1]], color: '#00f5ff' }, // 青色长条
O: { matrix: [[1,1],[1,1]], color: '#ffeb3b' }, // 黄色方块
T: { matrix: [[0,1,0],[1,1,1]], color: '#bf5af2' }, // 紫色T形
S: { matrix: [[0,1,1],[1,1,0]], color: '#30d158' }, // 绿色S形
Z: { matrix: [[1,1,0],[0,1,1]], color: '#ff375f' }, // 红色Z形
J: { matrix: [[1,0,0],[1,1,1]], color: '#0a84ff' }, // 蓝色J形
L: { matrix: [[0,0,1],[1,1,1]], color: '#ff9f0a' } // 橙色L形
};
const SHAPE_KEYS = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];

每种方块用 0/1 矩阵表示形状,1 表示方块存在的位置。

游戏状态管理

1
2
3
4
5
6
let board = [];           // 12×20 游戏板,存储已落方块颜色
let currentPiece = null; // 当前下落的方块
let currentX = 0; // 当前方块X坐标
let currentY = 0; // 当前方块Y坐标
let score = 0; // 得分
let dropInterval = 800; // 下落间隔(毫秒)

board 是二维数组,存储已固定在底部的方块颜色,空位为 0。

随机生成方块

1
2
3
4
5
6
7
8
function randomPiece() {
const key = SHAPE_KEYS[Math.floor(Math.random() * SHAPE_KEYS.length)];
const shape = SHAPES[key];
return {
matrix: shape.matrix.map(row => [...row]), // 深拷贝矩阵
color: shape.color
};
}

使用 map(row => [...row]) 实现矩阵的深拷贝,避免修改原始形状定义。

碰撞检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function checkCollision(matrix, x, y) {
for (let r = 0; r < matrix.length; r++) {
for (let c = 0; c < matrix[r].length; c++) {
if (matrix[r][c]) {
const newX = x + c;
const newY = y + r;
// 边界检测
if (newX < 0 || newX >= COLS || newY >= ROWS) return true;
// 与已落方块碰撞检测
if (newY >= 0 && board[newY][newX]) return true;
}
}
}
return false;
}

遍历方块的每个单元格,检查是否越界或与已落方块重叠。

阅读全文 »