☰
用遗传算法驱动电子宠物:从状态机到GA模拟的完整Python实现
2026/10/13 21:30:58 网站建设 项目流程

简介:这是一个基于JavaScript实现的Tomagotchi(电子宠物)完整项目,源自GA SEI 322课程实践。项目面向正在学习前端开发的初学者或需要完成类似作业的学生,展示了如何用原生HTML/CSS/JS搭建可交互的养成类游戏,涵盖角色展示、饥饿/能量/乏味状态监控、年龄增长等核心机制,并配有README中的线框图和用户故事。压缩包共5个文件,含2个JavaScript逻辑文件、1个HTML结构页面、1个CSS样式文件及1个Markdown文档,整体仅4KB,内容精简、结构清晰,便于快速阅读和改造。项目强调分模块开发、使用console.log与element inspector调试、编写语义化标记、配合Git进行20次以上提交,并部署到GitHub公共URL,这些实践对前端学习者很有参考价值。目前已有141人学习,可作为理解面向对象编程、DOM操作、状态维护与项目管理方法的高质量入门案例。

1. 一个叫“pachy-gotchi”的电子宠物,为什么值得动手做

pachy-gotchi 是一个顶着 Tomagotchi 名字的虚拟养成项目,项目代号里的 GA 指遗传算法,SEI 322 只是当时内部用的期数编号。整个项目的核心就一句话:让电子宠物不再用同一套“饿了就吃、困了就睡”的固定逻辑,而是用遗传算法给每只宠物生成一套性格基因,玩家养的是自己的个体,不是拷贝。它能解决的问题很实际:状态机怎么组织、属性数值怎么随时间衰减、遗传算法的基因编码和适应度函数怎么和游戏循环结合、数据怎么落地。适合想做一次完整小全栈验证的新手,也适合想看看 GA 参数对养成手感影响的熟手。我按自己从零搭这个项目的顺序来讲,尽量把参数和坑说明白。

2. 把 Tomagotchi 拆成五个模块:先定边界再写代码

做这类虚拟养成项目最容易犯的错是一上来就写 Pet 类。一个实例变量接一个实例变量,最终变成几百行的 if-else。我先按边界把它拆成五个模块:状态机、属性系统、遗传算法、时钟、持久化。这五个模块各管一段,后面加玩法不用推翻重来。

2.1 从喂食、清洁、睡觉到状态机:核心循环长什么样

虚拟宠物的核心循环并不复杂:现实时间或游戏时间推移,属性按一定速率衰减;玩家触发操作,属性得到恢复;属性落到阈值之下,宠物进入生病或濒死状态。这里说的“状态”不是心情文案,而是真正影响行为的有限状态。状态机的价值在于把“能不能做某件事、做了之后去哪”集中管理,避免用多个布尔值互相组合产生不可维护的分支。

先定义状态和转移条件。下面是我实际采用的五个状态。

状态进入条件退出条件
idle初始化、睡醒、吃完、玩完执行操作、属性命中阈值
eating玩家投喂且饥饿值低于阈值进食动作结束,属性更新
playing玩家选择玩耍且快乐值偏低玩耍结束,快乐恢复,精力扣减
sleeping玩家选择睡觉或精力过低睡眠计时结束,精力恢复
sick任一属性连续低值或健康值过低治疗操作且属性恢复,否则转死亡
dead健康值为 0 或生病超时无,终态

这省去了大量“能不能睡觉”的判断。状态机在代码层面用一个 Enum 加一张转移表就够了,不需要引第三方库。下面是状态枚举的骨架,后面所有判断都引用它。

from enum import Enum class PetState(Enum): IDLE = "idle" EATING = "eating" PLAYING = "playing" SLEEPING = "sleeping" SICK = "sick" DEAD = "dead"

Enum 的好处是状态名唯一,日志里打印的是 idle、sleeping 这种可读字符串而不是 0/1 魔法数字。后续写判断逻辑时,IDE 也能帮你检查漏掉的分支。真正执行状态转移时,我建议用一个字典把“当前状态 + 触发事件”映射到“下一个状态”,而不是在各处写 if,否则状态一多就变成黑匣子。这个映射表在初始版本里可以只有 idle、sleeping、sick、dead 四条,先跑通再扩充。

2.2 三个可选技术栈:命令行、Web、桌面,我为什么建议先做CLI

同一个核心逻辑可以套三层壳:命令行、Web 页面、桌面应用。它们各有利弊,选型会直接决定开发节奏。

方案上手成本调试速度适合验证的目标主要风险
CLI低快状态机、遗传算法、属性平衡展示效果弱,非玩家友好
Web中高中实时动画、多端访问、可视化基因变化大量时间花在样式和网络
桌面高中离线本地养成、系统托盘提醒打包分发和平台适配

我一般会先做 CLI,原因很直接:遗传算法调试需要跑大量模拟、打印每一代的适应度变化,命令行下最容易批量执行和回放。Web 版可以后面再套,但核心逻辑不要写进网页事件回调里。不少人的第一反应是上一套前端框架,结果大部分时间在调按钮样式,状态机反而没写对。先做 CLI,把核心循环和 GA 跑稳定,再用 Web 壳呈现,是最省后悔药的路线。

CLI 版本不需要做太多花活,一个输入提示符加几条命令就够:feed、play、sleep、status、quit。命令解析单独放一个函数,不要和业务逻辑混在一起。这样以后接 Web 时,HTTP 路由可以复用同一个函数。

2.3 遗传算法在养成里的定位:不是花架子,是决策层

很多仿 Tomagotchi 项目里,遗传算法是硬加进来的“高科技感”,其实它可以落在一个非常具体的位置:决策层。普通电子宠物互动公式固定,喂食一定加固定饱食度;在 pachy-gotchi 里,喂食收益、属性衰减速度、生病阈值都由宠物的基因调制。基因决定了“这只宠物适合什么样的玩家”,遗传算法则负责在每一次繁殖中把这些基因推向更能适应当前玩家风格的组合。

具体流程是这样:初始种群是一批随机基因的蛋,每个基因对应一组成长参数;用固定的模拟玩家策略跑一段养成,记录属性变化;适应度函数计算“这只宠物在玩家风格下过得是否健康、快乐”;高适应度的个体参与交叉变异生成下一代。玩家真实的喂食、玩耍操作也可以作为评估样本喂给适应度函数,这就是 GA 和游戏循环结合的接口。

这一步的常见误用是把 GA 当成了游戏循环本身,每 tick 都跑一次进化。实际上基因应该相对稳定,只有“繁殖”或“新的蛋出生”时才发生改变。养成循环用状态机跑,GA 决定性格参数,两者通过一组基因参数单向连接,逻辑才清晰。第 3 章会展开讲基因和适应度,第 4 章给完整骨架。

3. 用遗传算法驱动宠物“性格”:基因编码与适应度设计

这是整个项目里最需要想清楚的部分,也是新手觉得“玄学”的地方。基因怎么编码、适应度怎么定,决定了最后进化出来的是“有趣的宠物”还是“只会睡觉的石头”。我按编码、适应度、繁殖三步讲。

3.1 把性格编码成染色体:基因位、取值域和初始化

用实值编码而不是二进制编码。因为每个基因对应的都是连续参数,比如代谢速率、情感需求、睡眠需求,直接用浮点数,后面的高斯变异实现起来很简单。如果强行用二进制串,反而要在编码解码之间来回折腾,看不到直观收益。

我定义了 5 个基因位:代谢速率 metabolism,影响饥饿衰减;情感需求 affection,影响快乐衰减;睡眠需求 sleepiness,影响精力衰减;食物偏好 food_bias,影响喂食恢复倍率;玩耍偏好 play_bias,影响玩耍恢复倍率。取值范围统一放在 0.1 到 2.0,小于 0.1 会让属性几乎不衰减,大于 2.0 会让宠物几分钟就饿死,先按这个边界试。

初始化代码:

import random GENE_NAMES = ["metabolism", "affection", "sleepiness", "food_bias", "play_bias"] GENE_RANGE = (0.1, 2.0) def random_genome(): return {name: round(random.uniform(*GENE_RANGE), 4) for name in GENE_NAMES}

random.uniform(*GENE_RANGE) 生成闭区间内的均匀分布浮点数,round 到 4 位是为了日志可读和结果可复现。这里不用正态分布做初始化,因为初始种群要尽量覆盖整个参数空间,均匀分布更容易保持多样性。如果你的实验想复现,记得在这个函数调用前固定 random.seed。

3.2 适应度函数:让宠物“喜欢”或“抗拒”玩家操作

适应度函数是 GA 的指挥棒,它有几种明显错误写法。第一种只奖励“存活时间”,进化出来的个体会变成代谢极低的“冬眠型生物”;第二种只惩罚单一属性,比如只盯饥饿,宠物会变得不用玩耍、不睡觉也不会死;第三种用最终值而不是过程值,中途刷出的高分没有意义。

我采用的方案是观察一个评估窗口内的属性曲线,把每个采样点的饥饿、快乐、精力归一化后加权求和,再扣掉低属性惩罚。公式写成:

fitness = α × mean(hunger_norm) + β × mean(happiness_norm) + γ × mean(energy_norm) − penalty

其中 hunger_norm、happiness_norm、energy_norm 已经是 0 到 1 的归一化值,属性越高越接近 1。α、β、γ 默认都是 1,表示三项等权。penalty 是当任一属性低于 0.2 时,每个低属性样本扣 0.05。这个惩罚的作用是防止“不死但长期濒死”的个体刷分。

代码上推荐把适应度函数设计成纯函数:输入一段属性序列,输出浮点数。这样可以直接用于批量模拟,也可以后续接入玩家真实操作记录。

def compute_fitness(history, alpha=1.0, beta=1.0, gamma=1.0): if not history: return 0.0 hunger = [h / 100.0 for h in history["hunger"]] happiness = [h / 100.0 for h in history["happiness"]] energy = [e / 100.0 for e in history["energy"]] score = alpha * sum(hunger) / len(hunger) score += beta * sum(happiness) / len(happiness) score += gamma * sum(energy) / len(energy) penalty = 0.05 * sum(1 for h, hap, e in zip(hunger, happiness, energy) if h < 0.2 or hap < 0.2 or e < 0.2) return score - penalty

假设属性上限是 100,所以除以 100 得到归一化值。penalty 按低属性样本数累加,如果窗口有 100 个采样点,一个长期濒死的个体最多扣 5 分,这足以抵消它对均值的贡献。参数调整时优先动 α、β、γ,不要轻易把惩罚系数调大,否则又回到“只要不死就是高分”的极端。

3.3 繁殖与变异:下一代性格如何产生

有了适应度,就可以做选择、交叉和变异。选择我用排名截断抽样:先把种群按适应度降序排,只看前一半个体,从中均匀随机选两个作为父母。这样不会因为某个个体分数特别高就垄断下一代。交叉用均匀交叉,每个基因位独立地以 0.5 概率来自父本或母本。变异使用高斯扰动,sigma 建议 0.08 到 0.15,太小容易早熟,太大就是随机搜索。精英保留 2 个,不让最优个体在交叉变异中丢失。

def crossover(parent_a, parent_b): child = {} for name in GENE_NAMES: child[name] = parent_a[name] if random.random() < 0.5 else parent_b[name] return child def mutate(genome, rate=0.15, sigma=0.1): mutated = genome.copy() for name in GENE_NAMES: if random.random() < rate: value = mutated[name] + random.gauss(0, sigma) mutated[name] = round(min(max(value, GENE_RANGE[0]), GENE_RANGE[1]), 4) return mutated

先看 crossover:0.5 概率逐位选择,均匀交叉在实值编码里比单点交叉更不容易产生“极端组合”,因为每个基因位都有机会独立重组。再看 mutate:rate 是每位基因发生变异的概率,0.15 意味着一个 5 基因的个体平均有接近一个基因位发生扰动;min/max 把变异结果限制在边界内,避免产生负数代谢率。gauss 的 sigma=0.1 对 0.1-2.0 的取值域来说是一次约 10% 的波动,这是比较稳的起步参数。

种群大小通常取 30 到 50,代数取 20 到 30。在你没有改适应度函数之前,这些参数不需要频繁动。真正影响手感的是衰减系数和适应度权重,不是种群大小。初始种群可以理解成“一批待孵化的蛋”,每只蛋孵化前先看到基因,孵出来后按基因参数跑养成模拟。

4. 最小可跑版本:核心状态循环 + GA 模块的Python骨架

这章给一个能直接运行的最小骨架。没有花哨界面,目标是在命令行看到宠物状态变化,并让遗传算法能把一代代性格迭代出来。建议先跑通这个骨架,再往上加 Web 面板。

4.1 项目文件结构

按第 2 章的边界,我把代码分成四个文件,外加一个配置文件:

pachy-gotchi/ ├── main.py # 命令行入口:交互和模拟循环 ├── pet.py # 状态机与属性系统 ├── genome.py # 基因初始化、交叉、变异 ├── ga.py # 种群管理、适应度评估、选择 └── config.py # 属性上限、衰减系数、惩罚阈值

genome.py 只关心遗传操作,不关心宠物怎么用这些基因;pet.py 只关心状态变化,不关心进化;ga.py 把两者串起来。这个分层不是摆设,后面把 CLI 换成 Web 时,只有 main.py 需要改。

4.2 状态机与时钟循环:用Pet类承载属性

先看 config.py,它把数值集中在同一处,避免散落在各文件里。

# config.py CONFIG = { "max_attr": 100.0, "sick_threshold": 0.2, "offline_clamp_seconds": 120 }

max_attr 是各项属性上限,sick_threshold 是进入生病状态的健康阈值,offline_clamp_seconds 是离线补算的最大时间跨度。接下来是 pet.py 里的核心类。

import time from enum import Enum class PetState(Enum): IDLE = "idle" EATING = "eating" PLAYING = "playing" SLEEPING = "sleeping" SICK = "sick" DEAD = "dead" class Pet: def __init__(self, genome, config): self.genome = genome self.state = PetState.IDLE self.max_attr = config["max_attr"] self.sick_threshold = config["sick_threshold"] self.offline_clamp_seconds = config["offline_clamp_seconds"] self.hunger = self.max_attr self.happiness = self.max_attr self.energy = self.max_attr self.health = self.max_attr self.last_tick = time.time() self.history = {"hunger": [], "happiness": [], "energy": []} def update(self, now=None): if self.state == PetState.DEAD: return now = now or time.time() dt = min(now - self.last_tick, self.offline_clamp_seconds) self.last_tick = now self.hunger -= dt * 0.1 * self.genome["metabolism"] self.happiness -= dt * 0.05 * self.genome["affection"] self.energy -= dt * 0.08 * self.genome["sleepiness"] self._clamp_values() self._check_state() if int(now) % 5 == 0: self._record_history() def _clamp_values(self): self.hunger = max(0.0, min(self.max_attr, self.hunger)) self.happiness = max(0.0, min(self.max_attr, self.happiness)) self.energy = max(0.0, min(self.max_attr, self.energy)) def _check_state(self): if self.hunger <= 0 or self.happiness <= 0 or self.energy <= 0: self.health -= 10 if self.health <= 0: self.state = PetState.DEAD elif self.health < self.sick_threshold * self.max_attr: self.state = PetState.SICK else: self.state = PetState.IDLE def _record_history(self): self.history["hunger"].append(self.hunger) self.history["happiness"].append(self.happiness) self.history["energy"].append(self.energy)

这段包含一个关键设计:update 不按固定的 1 秒 tick 循环,而是用 now - last_tick 算真实时间差。dt 的单位是秒,衰减系数 0.1 表示 metabolism=1 时大约 1000 秒饿到 0。dt 用 min 限制在 120 秒内,避免睡一觉醒来宠物直接暴毙,这是离线场景最常见的问题。_clamp_values 把属性限制在 [0, 100],_check_state 根据健康阈值切到 SICK 或 DEAD。这里的健康值判断做了简化,实际项目里更稳的做法是记录“属性连续低于阈值的时长”,但不影响理解主线。

时钟循环放在 main.py 中,每秒调用一次 pet.update(),而不是让 Pet 自己起线程。这样项目容易测试和回放。

4.3 遗传算法模块:种群、评估、选择、交叉变异

ga.py 里我实现一个最简单的种群管理器。它不直接使用 Pet,而是接收一个 simulate 函数,避免 GA 和游戏逻辑耦合。

import random from genome import GENE_NAMES, random_genome, crossover, mutate def evaluate_population(population, simulate, steps=500, policy="default"): scores = [] for genome in population: history = simulate(genome, steps, policy) scores.append(compute_fitness(history)) return scores def evolve(population, scores, elite_size=2, mutate_rate=0.15, mutate_sigma=0.1): ranked = sorted(zip(population, scores), key=lambda x: x[1], reverse=True) next_gen = [genome for genome, _ in ranked[:elite_size]] pool = ranked[: len(ranked) // 2] while len(next_gen) < len(population): parent_a = random.choice(pool)[0] parent_b = random.choice(pool)[0] child = crossover(parent_a, parent_b) next_gen.append(mutate(child, mutate_rate, mutate_sigma)) return next_gen

这里的 evaluate_population 把 simulate 当作黑盒传入,方便你替换成真实玩家日志。simulate 的职责是:拿到基因,跑完指定步数,返回一段属性历史。compute_fitness 直接用第 3 章的函数,放在同一个文件里即可。elite_size 固定为 2,种群大小默认 30 时足够。pool 取排名前一半,从中均匀随机选两个父母,这就是排名截断抽样,比适应度比例抽样更不容易早熟。

4.4 命令行交互入口

main.py 用最朴素的 input 方式,让玩家能手动操作宠物,也能用 --auto 跑一次自动模拟。自动模拟用于验证 GA。

import sys from pet import Pet, PetState from config import CONFIG def handle_command(cmd, pet): if cmd == "feed": pet.hunger = min(100, pet.hunger + 20 * pet.genome["food_bias"]) return "喂食完成" if cmd == "play": pet.happiness = min(100, pet.happiness + 15 * pet.genome["play_bias"]) pet.energy = max(0, pet.energy - 5) return "玩耍完成,有点累" if cmd == "sleep": pet.state = PetState.SLEEPING return "开始睡觉" if cmd == "status": return f"饥饿={pet.hunger:.1f} 快乐={pet.happiness:.1f} 精力={pet.energy:.1f} 状态={pet.state.value}" return "未知命令" def main(): from genome import random_genome pet = Pet(random_genome(), CONFIG) while True: cmd = input("> ").strip().lower() if cmd == "quit": break print(handle_command(cmd, pet)) pet.update() if __name__ == "__main__": main()

注意 feed 的恢复量是 20 乘以 food_bias,这就是基因影响玩法的入口。food_bias=2.0 的宠物喂一次能回 40,food_bias=0.1 的宠物喂一次才回 2,前者明显更好养。handle_command 是纯业务函数,后续接 Web 时可以直接复用。sleep 操作在最小版本里没有做计时恢复,只切换状态,真正的睡眠恢复可以放在 update 里按状态分支处理,这是你自己可以扩展的第一个点。

5. 参数调优与常见问题避坑:为什么你的宠物总是饿死或暴毙

这一章是我愿意称之为“血泪经验”的部分。功能写完后,大部分时间都花在调属性衰减、适应度函数和种群参数上。下面按现象逐个说。

5.1 属性衰减用固定 tick,离线之后救不回来

现象:宠物晚上进入 sleeping,第二天打开,精力变成 0,宠物已经 sick 甚至 dead。原因是属性更新用固定 tick 数累积,睡眠期间没有离线补偿。

解决:update 里用真实时间差 dt,并把单次补偿上限 clamp 到 120 秒。这样离开一小时只会补算两分钟的衰减,宠物会“托管”在原来状态,醒来还能救。这算是整个项目里最影响体验的一个坑。别觉得 clamp 是作弊,真实电子宠物大多有托管模式,否则任何游戏都经不起玩家睡觉。如果你希望离线也有进展,可以把 120 换成 7200,但记得把能量衰减同时调低,否则还是暴毙。

5.2 适应度函数只算最终存活,遗传算法刷出“石头宠物”

现象:进化 20 代后,种群里的宠物都是 metabolism 接近 0.1、affection 接近 0.1 的个体。它们几乎不衰减,但也几乎没有互动价值。

原因:适应度函数奖励“活着”而不奖励“陪你玩”。解决:在适应度里加入行为多样性检查。具体做法是在 simulate 生成的 history 里同时记录操作类型,如果玩家策略覆盖了 feed、play、sleep 三类操作,才给满分;否则乘以 0.6。这样“只要不死”的个体分数会被压制。这也是“目的性”问题:GA 只会优化你写在适应度里的目标。想让宠物有趣,就把“有趣”量化成指标,否则它一定会找到让你无聊的出路。

5.3 选择压力过大,种群在十几代内全部收敛

现象:前面还在说多样性,跑几代之后发现所有基因几乎一样,后续进化等于白算。原因通常是适应度比例选择配合适应度差距过大,高适应度个体被复制满一代。

解决:改用排名截断抽样或锦标赛选择,精英保留只保留前两名,而不是前五名。mutate_sigma 也不要小于 0.05,否则收敛后没办法跳出局部最优。判断是否收敛,可以每代打印基因位上的标准差。当所有位点的标准差都小于 0.05,说明种群已经失去多样性。这时候再跑 100 代也是原地打转,不如重新初始化或引入移民个体。

5.4 随机种子不固定,同一套参数跑出两个结果

现象:代码一行没改,两次跑 GA 的适应度曲线差异巨大,一度怀疑是状态机写错了。原因基本是 random 模块没有固定种子,初始种群和交叉变异每次都不一样。

解决:在 main.py 里加一个 --seed 参数,默认情况下每次随机,实验时固定 seed 并把 seed 写进日志。比如 seed=42 跑出来最优适应度 0.83,换 seed=17 可能只有 0.61,这个波动本身也是信息,不能直接说算法“好坏”。固定种子的另一个好处是可以做参数回归:调了一个参数,用同一批种子对比前后差异,能确认变化是不是这个参数引起的,否则只能靠玄学调参。

5.5 数据持久化用 JSON 直接覆盖,存档损坏后只能重养

现象:宠物养成到第 30 天,程序异常退出,存档文件变成空文件或只有半个花括号。原因是直接 open("save.json", "w") 写入,写入过程中进程崩溃就截断了。

解决:写临时文件再原子替换。

import json, os, tempfile def atomic_save_json(path, data): fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path) or ".", suffix=".tmp") try: with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) os.replace(tmp, path) except BaseException: os.unlink(tmp) raise

关键在于 os.replace,它在同一文件系统上是原子操作,不会出现半个文件的情况。先写到同目录的临时文件,再替换目标文件。虽然看起来多了一步,但这是存档类项目的最后一道后悔药,值得保留。

6. 从跑通到进阶:可视化面板与遗传代数追踪

到这里,CLI 版本已经能跑,GA 也迭代起来了。最后一层是把结果从黑匣子变成能看到的东西。我常用的做法是加两个信息出口:一是每代输出适应度均值、最优值和基因标准差;二是把存档和遗传代数导出成 JSON 或 CSV,供外部工具绘图。

6.1 用三个指标判断遗传算法是否在“干活”

打印这三行就能判断进化是否正常:第 0 代适应度均值很低、最优值不明显;第 10 代最优值上升,方差收窄;如果第 20 代方差已经很小但最优值不再上升,说明收敛,可以停了。如果最优值一直没动,先检查适应度函数,再检查模拟函数是否真的使用了 genome。很多所谓的“进化不动”只是 mutation 改的基因没有被 pet.update 读到。

import statistics from genome import GENE_NAMES def log_generation(gen, scores, genomes): best = max(scores) mean = sum(scores) / len(scores) stds = [] for g in GENE_NAMES: values = [genome[g] for genome in genomes] stds.append(statistics.pstdev(values)) print(f"第{gen:3d}代 最优={best:.4f} 均值={mean:.4f} 基因标准差={max(stds):.4f}")

max(stds) 代表所有基因位里标准差最大的那个,它最敏感地反映多样性。如果它掉到 0.05 以下,就应该考虑增加变异强度或重新初始化。

6.2 把CLI扩展成Web看板的最短路径

我也不建议直接上重框架,等存档结构和 API 稳定之后,用 Python 自带的 http.server 写三个路由就够了:一个返回当前属性,一个返回基因向量,一个接收 feed、play 等操作。前端只需要一个定时刷新页面,每 5 秒请求一次状态接口。这个路径不会破坏 pet.py 和 ga.py 的独立性,main.py 里的 handle_command 可以直接被路由复用。等你发现需要更复杂的路由、模板和持久化时,再换 Web 框架也来得及,核心逻辑不需要重写。

我最早把 GA 当成神秘核心,结果发现它只是参数生成器,真正决定手感的是衰减系数和适应度函数。这个项目给我最大的教训是:先把“无聊”定义清楚,再让算法去优化它,否则你只会得到一只不需要照顾的石头。希望这些能帮你在自己的 pachy-gotchi 上少踩几个坑。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询