Wordless: 一个周末打造的小爆游戏
这个项目是什么
Wordless就是个类似 Wordle 的猜单词游戏,用 Next.js 搭建的。玩家有 6 次机会猜出单词,支持 3 到 8 个字母的单词。说实话,开始只是想做点跟 wordle 不一样东西,没想到做着做着就越来越有意思了。
点击这里可以体验:https://wordless.online/
我用了一个周末把代码撸完以后,直接发布上线,也没怎么关注,没想到几个月过去了,这个小游戏的流量一直很稳定,有 50%的自然搜索,40%的直接访问流量,这可是个非常漂亮的流量数据呀。
用了什么技术
主要框架
React 18.3.1 - 没什么好说的,前端必备
Next.js 14.2.4 - 选它主要是因为 SSR 和 API 路由很方便
TypeScript - 虽然写起来麻烦点,但能避免很多低级错误
Tailwind CSS - 写样式贼快,不用想类名
UI 相关
Radix UI - 弹窗、通知这些组件用的,无障碍做得不错
Lucide React - 图标库,简洁好看
Canvas Confetti - 猜对了撒彩带的特效,挺有意思的
其他工具
SWR - 数据获取用的,缓存机制不错
Zod - 数据验证,比手写 if-else 强多了
nspell - 检查单词拼写是否正确
文件结构
wordlessgame/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # API 路由
│ │ │ ├── ai-completion/ # AI 辅助功能
│ │ │ ├── validate-word/ # 单词验证
│ │ │ └── words/ # 单词生成
│ │ ├── layout.tsx # 根布局
│ │ └── page.tsx # 主页面
│ ├── components/ # React 组件
│ │ ├── ui/ # 基础 UI 组件
│ │ ├── game-grid.tsx # 游戏网格
│ │ ├── key-board.tsx # 虚拟键盘
│ │ └── result-modal.tsx # 结果弹窗
│ ├── data/ # 静态数据
│ │ └── word-lists.ts # 单词词库
│ ├── lib/ # 工具函数
│ │ ├── api.ts # API 调用
│ │ └── utils.ts # 工具函数
│ ├── ai/ # AI 相关功能
│ └── styles/ # 样式文件
└── public/ # 静态资源
核心功能实现
1. 游戏状态管理
游戏使用 React Hooks 管理复杂的状态:
// 主要状态
const [columns, setColumns] = useState(3); // 单词长度
const [gridContent, setGridContent] = useState<string[]>([]); // 网格内容
const [currentCell, setCurrentCell] = useState(-1); // 当前输入位置
const [word, setWord] = useState(''); // 目标单词
const [matchResults, setMatchResults] = useState<string[]>([]); // 匹配结果
const [cellMatchClasses, setCellMatchClasses] = useState<string[]>([]); // 样式类
2. 单词生成系统
采用优化的单词生成器,避免重复选择同一单词:
class WordGenerator {
private static instance: WordGenerator;
private readonly cache: Map<number, string[]> = new Map();
private readonly usedIndices: Map<number, Set<number>> = new Map();
private readonly shuffledIndices: Map<number, number[]> = new Map();
public getRandomWord(length: number): string {
// Fisher-Yates 洗牌算法确保随机性
// 避免重复选择相同单词
}
}
3. 游戏逻辑算法
const matchWord = (guessedWord: string, targetWord: string) => {
const result = new Array(guessedWord.length).fill('X'); // X=不匹配
const targetCounts = new Map<string, number>();
// 第一遍:标记完全匹配的字母
for (let i = 0; i < guessedWord.length; i++) {
if (guessedWord[i] === targetWord[i]) {
result[i] = 'C'; // C=正确位置
} else {
targetCounts.set(targetWord[i]!, (targetCounts.get(targetWord[i]!) || 0) + 1);
}
}
// 第二遍:标记位置错误的字母
for (let i = 0; i < guessedWord.length; i++) {
if (result[i] !== 'C') {
const char = guessedWord[i]!;
if (targetCounts.get(char)! > 0) {
result[i] = 'P'; // P=位置错误
targetCounts.set(char, targetCounts.get(char)! - 1);
}
}
}
return result;
};
4. 游戏网格组件
export function GameGrid({
gridContent,
columns,
currentCell,
cellMatchClasses,
flippingRows
}: GameGridProps) {
return (
<div className={`grid ${gridCol} gap-2 mb-8`}>
{gridContent.map((content, index) => {
const matchClass = cellMatchClasses[index];
const isFlipping = flippingRows.has(Math.floor(index / columns));
return (
<div
className={cn(
'w-14 h-14 flex items-center justify-center text-2xl font-bold rounded-md',
matchClass === 'C' ? 'bg-green-500 text-white' :
matchClass === 'P' ? 'bg-yellow-500 text-white' :
matchClass === 'X' ? 'bg-zinc-400 text-white' : 'bg-white',
isFlipping ? 'animate-flip' : ''
)}
style={{
animationDelay: isFlipping ? `${(index % columns) * 100}ms` : '0ms'
}}
>
{content}
</div>
);
})}
</div>
);
}
5. 虚拟键盘组件
使用 memo 优化渲染性能:
const KeyButton = memo(({
letter,
isMatched,
noMatched,
onClick
}: KeyButtonProps) => {
return (
<button
onClick={() => onClick(letter)}
className={cn(
'w-14 h-14 rounded-md font-bold transition-colors',
isMatched ? 'bg-green-500 text-white' :
noMatched ? 'bg-zinc-400 text-white' :
'bg-white hover:bg-violet-50'
)}
>
{letter}
</button>
);
});
数据结构
单词词库
词库按长度分类存储,每个长度包含 500+ 个单词:
export const WORD_LISTS: Record<number, string[]> = {
3: ['ace', 'age', 'air', ...], // 500+ 三字母单词
4: ['able', 'acid', 'aged', ...], // 500+ 四字母单词
5: ['about', 'above', 'abuse', ...], // 500+ 五字母单词
6: ['abroad', 'accept', 'access', ...], // 500+ 六字母单词
7: ['abandon', 'ability', 'absence', ...], // 500+ 七字母单词
8: ['absolute', 'academic', 'accepted', ...] // 500+ 八字母单词
};
API 设计
Edge Runtime API
使用 Next.js Edge Runtime 提供快速的 API 响应:
export const runtime = 'edge';
export async function GET(request: NextRequest) {
// API 逻辑
return NextResponse.json({ data });
}
用户体验优化
1. 动画效果
卡片翻转动画:使用 CSS
animate-flip
类按键反馈:按键按下时的视觉反馈
胜利庆祝:使用 canvas-confetti 库
2. 响应式设计
移动端优化的键盘布局
自适应网格大小
触摸友好的交互
3. 性能优化
使用 React.memo 减少不必要的重渲染
useCallback 和 useMemo 优化函数和计算
单词生成器的缓存机制
部署和 SEO
SEO 优化
export const metadata: Metadata = {
title: "Unlimited Wordless Online: Guess the Word in 6 Tries!",
description: "Wordless Online: Endless Word Challenges...",
keywords: "wordless,wordly, wordle, game, puzzle, word, words, letters, play, online, guess,unlimited",
};
分析工具
Google Analytics 用户行为追踪
自定义事件追踪
开发工具
代码质量
ESLint - 代码规范检查
Prettier - 代码格式化
TypeScript - 类型安全
包管理
- PNPM - 快速、节省磁盘空间的包管理器
游戏流程
初始化:选择单词长度(3-8个字母)
生成目标单词:从词库中随机选择
用户输入:通过虚拟键盘或物理键盘输入
验证输入:检查是否为有效单词
匹配算法:计算字母匹配情况
视觉反馈:显示颜色提示(绿色=正确位置,黄色=错误位置,灰色=不存在)
游戏结束:6次尝试后或猜中单词后结束
结果展示:显示结果弹窗和庆祝动画
特色功能
1. 可变单词长度
支持 3-8 个字母的单词,增加游戏难度选择
2. 智能单词生成
避免重复,确保每次游戏都有新鲜感
3. 实时反馈
即时的视觉和交互反馈,提升用户体验
4. 无限游戏
没有次数限制,可以连续游戏
5. 响应式设计
适配各种设备屏幕尺寸
技术亮点
现代化技术栈:使用最新的 React、Next.js 和 TypeScript
性能优化:多层次的性能优化策略
可维护性:清晰的代码结构和组件分离
用户体验:流畅的动画和交互效果
可扩展性:模块化设计便于功能扩展
总结
Wordless 是一款用现代 Web 技术打造的游戏,干净利落,跑得飞快,代码还容易维护。没堆花里胡哨的东西,但该有的全都有——好玩、流畅、写得明白。
欢迎来体验: https://wordless.online/
Wordless: 一个周末打造的小爆游戏的更多相关文章
- C++一个吃豆人小游戏
C++一个吃豆人小游戏 代码如下 #include <cstdio>#include <iostream>#include <ctime>#include < ...
- 纯JS实现俄罗斯方块,打造属于你的游戏帝国
纯JS俄罗斯方块,打造属于你的游戏帝国. 本文原始作者博客 http://www.cnblogs.com/toutou 俄罗斯方块(Tetris, 俄文:Тетрис)是一款电视游戏机和掌上游戏机游戏 ...
- fir.im Weekly - 如何打造 Github 「爆款」开源项目
最近 Android 转用 Swift 的传闻甚嚣尘上,Swift 的 Github 主页上已经有了一次 merge>>「Port to Android」,让我们对 Swift 的想象又多 ...
- EasySwoole+ElasticSearch打造 高性能 小视频服务系统
EasySwoole+ElasticSearch打造高性能小视频服务 第1章 课程概述 第2章 EasySwoole框架快速上手 第3章 性能测试 第4章 玩转高性能消息队列服务 第5章 小视频服务平 ...
- Django+小程序技术打造微信小程序助手 ✌✌
Django+小程序技术打造微信小程序助手 (一个人学习或许会很枯燥,但是寻找更多志同道合的朋友一起,学习将会变得更加有意义✌✌) 从零到一的完整项目开发实战过程,项目开发聚焦重要知识点,先原理后实战 ...
- 利用python实现微信小程序游戏跳一跳详细教程
利用python实现微信小程序游戏跳一跳详细教程 1 先安装python 然后再安装pip <a href="http://newmiracle.cn/wp-content/uploa ...
- Django2.0+小程序技术打造微信小程序助手✍✍✍
Django2.0+小程序技术打造微信小程序助手 整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题 ...
- Django+小程序技术打造微信小程序助手
Django+小程序技术打造微信小程序助手 整个课程都看完了,当前这个课程的分享可以往下看,下面有某盘的链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,同时也分享下自己的总结 ...
- Python 工匠:一个关于模块的小故事
前言 模块(Module)是我们用来组织 Python 代码的基本单位.很多功能强大的复杂站点,都由成百上千个独立模块共同组成. 虽然模块有着不可替代的用处,但它有时也会给我们带来麻烦.比如,当你接手 ...
- 看我是如何用C#编写一个小于8KB的贪吃蛇游戏的
译者注:这是Michal Strehovský大佬的一篇文章,他目前在微软.NET Runtime团队工作,主要是负责.NET NativeAOT功能的开发.我在前几天看到这篇文章,非常喜欢,虽然它的 ...
随机推荐
- IDEA问题之“调整IDEA字体大小”
调整IDEA字体大小 1.正常版 2. 远程版
- uni-app小程序登录后…
前情 最近新接了一个全新项目,是类似商城的小程序项目,我负责从0开始搭建小程序,我选用的技术栈是uni-app技术栈,其中就有一个用户登录功能,小程序部分页面是需要登录才可以查看的,对于未登录的用户需 ...
- 告别安装烦恼!Linux下MySQL一站式部署宝典
前言 本次安装部署主要针对Linux环境进行安装部署操作,系统位数64 getconf LONG_BIT 64 MySQL版本: v5.7.38 一.下载MySQL MySQL下载地址:https:/ ...
- acwing 智商药
题目链接:5046. 智商药 - AcWing题库 首先考虑dfs 不用想肯定超时 过了10/17个测试点 代码 1 #include<bits/stdc++.h> 2 3 using n ...
- A* 合集
板子那篇烂尾了,等 \(25\) 年 \(csp\) 时再继续写吧 CF1620E Replace the Numbers 点击查看代码 #include<bits/stdc++.h> u ...
- Ocelot和Consul 实现网关API 服务注册 负载均衡
Ocelot是一个用.NET Core实现并且开源的API网关,它功能强大,包括了:路由.请求聚合.服务发现.认证.鉴权.限流熔断.并内置了负载均衡器与Service Fabric.Butterfly ...
- MyBatis常见面试题:通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
MyBatis常见面试题:通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗? Dao接口即Mapper接 ...
- Spring注解之@Autowired:Setter 方法上使用@Autowired注解
可以在 JavaBean中的 setter 方法中使用 @Autowired 注解.当 Spring遇到一个在 setter 方法中使用的 @Autowired 注解时,它会在方法中按照类型自动装配参 ...
- Mysql基线核查
查看版本信息 select @@version 查看默认创建的测试库和测试用户 show databases like "test%"; select * from mysql.u ...
- hot100之二叉树上
二叉树的中序队列(094) 先看代码 class Solution { public List<Integer> inorderTraversal(TreeNode root) { Lis ...