Meet Cozy Café
Meet Cozy Café
Part 2: Meet Cozy Café (and Give Your Bot a Backbone) 第二部分:认识“舒适咖啡馆”(并为你的机器人构建骨架)
Part 2 of a beginner-friendly series on building a real Discord bot with JavaScript, discord.js, and Prisma. Part 1 got a bot online and replying to /ping. Today we reveal what we’re actually building — and lay the foundation for it.
这是面向初学者的系列教程的第二部分,旨在教你如何使用 JavaScript、discord.js 和 Prisma 构建一个真正的 Discord 机器人。第一部分我们已经让机器人上线并能回复 /ping 指令了。今天,我们将揭晓我们要构建的实际项目,并为其打下基础。
Welcome back! In Part 1 you did something genuinely cool: you built a program that logs into Discord and answers a /ping command with Pong!. That’s the entire skeleton of every Discord bot ever made — listen for an event, react to it. But /ping is a bit lonely. So in this part, two things happen. First, the big reveal: I’ll show you the actual project we’re building across this series. Second, we’ll give your bot a backbone — a tidy structure that lets it grow from one command to dozens without turning into an unreadable mess — and write the first two real commands of our game.
欢迎回来!在第一部分中,你完成了一件非常酷的事情:你编写了一个程序,可以登录 Discord 并用“Pong!”来回复 /ping 指令。这就是所有 Discord 机器人的核心骨架——监听事件,然后做出反应。但 /ping 有点孤单。因此,在这一部分中,我们将做两件事。首先是重头戏:我将展示我们在本系列中要构建的实际项目。其次,我们将为你的机器人构建一个“骨架”——一种整洁的结构,让它能够从一个指令扩展到几十个,而不会变成一团乱麻——并编写我们游戏的前两个真实指令。
Let’s meet the café. The reveal: we’re building Cozy Café ☕ 让我们来认识一下这家咖啡馆。揭晓答案:我们要构建的是“舒适咖啡馆”(Cozy Café)☕。
Here’s the project: Cozy Café — an idle café game you run entirely from inside Discord. You open a little café with a slash command. From then on, your café quietly serves customers and earns coins in real time — even while you’re offline. You drop back in whenever you feel like it to collect your earnings, spend them on upgrades and new recipes, and slowly master your menu. Over time a humble coffee cart grows into a cozy corner the whole server knows. That’s it. No twitch reflexes, no grinding, no pressure. Just a warm little thing that’s yours and is always gently ticking along in the background. 项目介绍:Cozy Café 是一款完全在 Discord 内部运行的放置类咖啡馆游戏。你可以通过斜杠指令开设一家小咖啡馆。从那时起,你的咖啡馆就会在后台静静地接待顾客并实时赚取金币——即使你离线时也是如此。你可以随时回来领取收益,将其用于升级和解锁新食谱,并慢慢完善你的菜单。随着时间的推移,一个小小的咖啡车会成长为整个服务器都知晓的温馨角落。就是这样。没有紧张的操作,没有枯燥的刷怪,也没有压力。它只是一个属于你的、温暖的小东西,始终在后台静静地运行。
What a moment of play feels like: You open Discord in the morning, type /collect, and your bot replies: ☕ While you were away (9h 12m) your café served 74 customers and earned 184 coins. You also discovered a new recipe: Cinnamon Roll — add it to your menu with /menu. You spend thirty seconds deciding what to do — upgrade your espresso machine so you earn faster, or save up for that Matcha Latte you’ve been eyeing — then close Discord and get on with your day. The whole visit takes a minute, and it’s completely optional.
游戏体验是这样的:你早上打开 Discord,输入 /collect,机器人回复:☕ 在你离开的这段时间(9小时12分钟)里,你的咖啡馆接待了74位顾客,赚取了184枚金币。你还发现了一个新食谱:肉桂卷——使用 /menu 将其添加到你的菜单中。你花三十秒决定要做什么——是升级浓缩咖啡机以加快赚钱速度,还是攒钱买你一直心仪的抹茶拿铁——然后关闭 Discord 继续你的一天。整个过程只需一分钟,而且完全是自愿的。
Why this game? (the design philosophy): This is the part I really want you to understand, because it’s a deliberate design choice, not an accident. You might know the feeling of a game or app that punishes you for not showing up — a streak you’ll lose, a pet that starves, daily quests that pile up into guilt. That’s the opposite of cozy. Cozy Café is built on one rule: Reward presence, never punish absence. Everything follows from that sentence: Your café earns while you’re away, so checking in once a day feels rewarding — there’s always something waiting to collect. But earnings cap out at about a day’s worth, so you never need to check more than once a day. No compulsive refreshing. And nothing ever decays. Skip three days, skip a week — your coins, recipes, and progress are all exactly where you left them. You just collect a full batch when you’re back. The thing that keeps pulling you back isn’t fear of losing progress — it’s the quiet satisfaction of collecting and mastering: filling out a recipe book, leveling up your favourite drinks, watching your café grow. A carrot, never a stick. That’s the game. Now let’s build the foundation it needs. 为什么选择这款游戏?(设计理念):这是我最希望你理解的部分,因为这是一个深思熟虑的设计选择,而非偶然。你可能体会过那种因为没上线而惩罚你的游戏或应用——连胜中断、宠物饿死、堆积如山的每日任务带来的负罪感。这与“舒适”背道而驰。Cozy Café 建立在一个原则之上:奖励参与,绝不惩罚缺席。一切都源于这句话:你的咖啡馆在你离开时也会盈利,所以每天查看一次会让你感到满足——总有东西等着你去领取。但收益上限约为一天的量,所以你无需每天查看多次。没有强迫性的刷新,也没有任何东西会衰减。跳过三天、跳过一周——你的金币、食谱和进度都完好无损。当你回来时,只需领取积攒的收益即可。让你不断回来的动力不是害怕失去进度,而是收集和精通带来的宁静满足感:填满食谱、升级你最喜欢的饮品、看着你的咖啡馆成长。只有胡萝卜,没有大棒。这就是游戏。现在,让我们来构建它所需的基础。
Why we can’t just keep piling code into one file: In Part 1, everything lived in a single index.js. That was perfect for one command. But look at where we’re heading: /open, /cafe, /collect, /daily, /shop, /recipes, /menu… If we cram all of those into index.js, we’ll end up with a 600-line monster where everything is tangled together and finding anything is a nightmare. So before we add features, we’re going to reorganize. This is a habit real developers live by, and the “why” behind it has a name: separation of concerns. The idea is simple — each piece of your program should have one clear job and live in one obvious place. When every command is its own little file, you always know exactly where to look, and adding a new command never risks breaking an old one.
为什么我们不能把代码一直堆在一个文件里:在第一部分中,所有代码都放在一个 index.js 文件中。这对于一个指令来说很完美。但看看我们要去向何方:/open、/cafe、/collect、/daily、/shop、/recipes、/menu……如果我们把所有这些都塞进 index.js,最终会得到一个 600 行的庞然大物,所有东西都纠缠在一起,查找任何东西都是一场噩梦。因此,在添加功能之前,我们要进行重组。这是真正开发者遵循的习惯,其背后的原因被称为:关注点分离(Separation of Concerns)。这个想法很简单——程序的每一部分都应该有一个明确的工作,并放在一个显而易见的地方。当每个指令都有自己的小文件时,你总是知道去哪里查找,而且添加新指令永远不会有破坏旧指令的风险。
Here’s the structure we’re building toward: 这是我们即将构建的结构:
cozy-cafe-bot/
├─ commands/
│ ├─ open.js ← one file per command
│ └─ cafe.js
├─ data/
│ └─ store.js ← where café data lives (for now)
├─ .env ← your secrets (from Part 1)
├─ deploy-commands.js
└─ index.js ← the "brain" that ties it together
(That top folder is simply your project from Part 1. I’ve named it cozy-cafe-bot/ here, but the name honestly doesn’t matter — keep my-discord-bot from Part 1 if you like. Only the files and folders inside it matter.)
(最顶层的文件夹就是你第一部分的项目。我在这里将其命名为 cozy-cafe-bot/,但名称其实并不重要——如果你喜欢,保留第一部分的 my-discord-bot 也可以。只有内部的文件和文件夹才重要。)
The trick that makes this work is a command handler: a small bit of code in index.js that automatically finds every file in the commands/ folder and loads it. Add a new command file, and the bot just picks it up — you never have to manually wire each one in. Let’s build it.
实现这一点的诀窍是“指令处理器”(command handler):这是 index.js 中的一小段代码,它会自动查找 commands/ 文件夹中的每个文件并加载它们。添加一个新的指令文件,机器人就会自动识别——你无需手动为每个指令进行配置。让我们开始构建吧。
Step 1 — Give every command a consistent shape: The handler can only auto-load our commands if they all look the same on the outside. So we’ll agree on a simple contract: every command file exports an object with exactly two things — data — the command’s name and description (built with SlashCommandBuilder, same as Part 1). execute — the function that runs when someone uses the command. That’s the whole contract. { data, execute }. Once every command follows it, the handler can treat them all identically, without caring what any individual command actually does. That consistency is what makes the magic possible.
第一步——赋予每个指令一致的形态:只有当所有指令在外部看起来都一样时,处理器才能自动加载它们。因此,我们约定一个简单的契约:每个指令文件导出一个包含两项内容的对象——data(指令名称和描述,使用 SlashCommandBuilder 构建,与第一部分相同)和 execute(当有人使用该指令时运行的函数)。这就是全部契约:{ data, execute }。一旦每个指令都遵循它,处理器就可以一视同仁地处理它们,而无需关心每个指令具体做了什么。这种一致性正是魔法实现的基础。
Step 2 — A place to keep café data (just for today): Our commands need somewhere to store each player’s café. For this part, we’ll use the simplest storage that exists: a Map held in the bot’s memory. A Map is a built-in JavaScript container that stores key → value pairs. We’ll use each player’s Discord user ID as the key, and their café as the value — so we can look up “the café belonging to this person.” Create a folder called data, and inside it a file store.js:
第二步——存放咖啡馆数据的地方(仅限今天):我们的指令需要一个地方来存储每个玩家的咖啡馆。对于这一部分,我们将使用最简单的存储方式:保存在机器人内存中的 Map。Map 是 JavaScript 内置的容器,用于存储键值对。我们将使用每个玩家的 Discord 用户 ID 作为键,将他们的咖啡馆作为值——这样我们就可以查找“属于这个人的咖啡馆”。创建一个名为 data 的文件夹,并在其中创建一个 store.js 文件:
// data/store.js
// A simple in-memory store: Discord user ID -> that user's café.
// ⚠️ Heads up: this lives in the bot's memory
// data/store.js
// 一个简单的内存存储:Discord 用户 ID -> 该用户的咖啡馆。
// ⚠️ 注意:这保存在机器人的内存中