# -*- coding: utf-8 -*-
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN

DARK  = RGBColor(0x0B, 0x1F, 0x3A)
TEAL  = RGBColor(0x14, 0xB8, 0xA6)
LIGHT = RGBColor(0xF5, 0xF7, 0xFA)
GRAY  = RGBColor(0x5A, 0x6B, 0x7B)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
DIM   = RGBColor(0xB9, 0xC6, 0xD4)

W, H = Inches(13.333), Inches(7.5)
prs = Presentation()
prs.slide_width, prs.slide_height = W, H
blank = prs.slide_layouts[6]

def add_slide(bg=DARK):
    s = prs.slides.add_slide(blank)
    r = s.shapes.add_shape(1, 0, 0, W, H)
    r.fill.solid(); r.fill.fore_color.rgb = bg
    r.line.fill.background(); r.shadow.inherit = False
    return s

def tb(slide, x, y, w, h, text, size, color, bold=False, align=PP_ALIGN.LEFT):
    box = slide.shapes.add_textbox(x, y, w, h)
    tf = box.text_frame; tf.word_wrap = True
    p = tf.paragraphs[0]; p.alignment = align
    run = p.add_run(); run.text = text
    f = run.font; f.size = Pt(size); f.color.rgb = color; f.bold = bold
    return box

def bullets(slide, x, y, w, h, items, size=18, color=DARK, gap=8, accent=TEAL):
    box = slide.shapes.add_textbox(x, y, w, h)
    tf = box.text_frame; tf.word_wrap = True
    for i, item in enumerate(items):
        p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
        p.space_after = Pt(gap)
        if isinstance(item, tuple):
            head, rest = item
            r1 = p.add_run(); r1.text = "• " + head
            r1.font.size = Pt(size); r1.font.bold = True; r1.font.color.rgb = accent
            r2 = p.add_run(); r2.text = "  " + rest
            r2.font.size = Pt(size); r2.font.color.rgb = color
        else:
            r = p.add_run(); r.text = "• " + item
            r.font.size = Pt(size); r.font.color.rgb = color
    return box

def accent_bar(slide, x, y, w=Inches(1.6), h=Inches(0.08)):
    bar = slide.shapes.add_shape(1, x, y, w, h)
    bar.fill.solid(); bar.fill.fore_color.rgb = TEAL
    bar.line.fill.background(); bar.shadow.inherit = False

def content_header(slide, num, title):
    tb(slide, Inches(0.7), Inches(0.4), Inches(11.5), Inches(0.8), title, 28, DARK, bold=True)
    tb(slide, Inches(11.6), Inches(0.45), Inches(1.1), Inches(0.6), num, 20, DIM, align=PP_ALIGN.RIGHT)
    accent_bar(slide, Inches(0.72), Inches(1.2))

def card(slide, x, y, w, h, title, body, title_size=18, body_size=14):
    c = slide.shapes.add_shape(5, x, y, w, h)
    c.fill.solid(); c.fill.fore_color.rgb = WHITE
    c.line.color.rgb = RGBColor(0xD8, 0xE0, 0xE8); c.line.width = Pt(0.75)
    c.shadow.inherit = False
    tf = c.text_frame; tf.word_wrap = True
    tf.margin_left = Inches(0.22); tf.margin_right = Inches(0.22)
    tf.margin_top = Inches(0.18); tf.margin_bottom = Inches(0.15)
    p = tf.paragraphs[0]
    r = p.add_run(); r.text = title
    r.font.size = Pt(title_size); r.font.bold = True; r.font.color.rgb = DARK
    for line in body.split("\n"):
        p2 = tf.add_paragraph(); p2.space_before = Pt(4)
        r2 = p2.add_run(); r2.text = line
        r2.font.size = Pt(body_size); r2.font.color.rgb = GRAY

def table(slide, x, y, headers, rows, widths, row_h=0.62):
    xs = x
    for w, htxt in zip(widths, headers):
        c = slide.shapes.add_shape(1, xs, y, w, Inches(0.55))
        c.fill.solid(); c.fill.fore_color.rgb = DARK; c.line.fill.background(); c.shadow.inherit = False
        tf = c.text_frame; tf.margin_left = Inches(0.15)
        r = tf.paragraphs[0].add_run(); r.text = htxt
        r.font.size = Pt(15); r.font.bold = True; r.font.color.rgb = WHITE
        xs += w
    for ri, row in enumerate(rows):
        y2 = y + Inches(0.62 + ri * row_h)
        xs = x
        for w, cell in zip(widths, row):
            c = slide.shapes.add_shape(1, xs, y2, w, Inches(row_h))
            c.fill.solid(); c.fill.fore_color.rgb = WHITE if ri % 2 == 0 else LIGHT
            c.line.color.rgb = RGBColor(0xD8, 0xE0, 0xE8); c.shadow.inherit = False
            tf = c.text_frame; tf.word_wrap = True; tf.margin_left = Inches(0.15)
            r = tf.paragraphs[0].add_run(); r.text = cell
            r.font.size = Pt(13); r.font.color.rgb = DARK
            xs += w

# ---- 1 封面 ----
s = add_slide()
tb(s, Inches(0.9), Inches(2.0), Inches(11.5), Inches(1.8),
   "基于鱼群行为的群体运动仿生模拟系统", 38, WHITE, bold=True)
tb(s, Inches(0.9), Inches(3.15), Inches(11.5), Inches(0.7),
   "—— fish-swarm-sandbox", 26, TEAL, bold=True)
accent_bar(s, Inches(0.95), Inches(4.05), w=Inches(2.2), h=Inches(0.1))
tb(s, Inches(0.9), Inches(4.35), Inches(11), Inches(1.0),
   "仿生学课程研究报告\n汇报人：XXX　|　学号：XXXXXX　|　指导教师：XXX　|　日期：2025-XX-XX",
   17, DIM)

# ---- 2 目录 ----
s = add_slide(bg=LIGHT)
content_header(s, "02", "汇报目录")
items = ["选题背景与仿生学意义", "生物原型观察与机理分析", "仿生映射：从生物到模型",
         "系统设计与技术实现", "演示与结果分析", "讨论：仿生近似与局限",
         "总结与收获", "参考文献"]
for i, t in enumerate(items):
    col, row = i % 2, i // 2
    card(s, Inches(0.7 + col * 6.1), Inches(1.6 + row * 1.3), Inches(5.7), Inches(1.05), f"0{i+1}", t, 20, 16)

# ---- 3 选题背景 ----
s = add_slide(bg=LIGHT)
content_header(s, "03", "选题背景与仿生学意义")
card(s, Inches(0.7), Inches(1.6), Inches(5.9), Inches(2.4), "生物现象观察",
     "• 自然界中鱼群（沙丁鱼、鲭鱼）可达数万个体\n"
     "  却能整体有序运动、快速变换队形\n"
     "• 没有中央指挥：每条鱼只依据局部邻居信息行动", 18, 15)
card(s, Inches(0.7), Inches(4.2), Inches(5.9), Inches(2.4), "仿生学视角",
     "• 典型的群体智能（Swarm Intelligence）仿生案例\n"
     "• 「简单个体 + 局部规则 → 复杂群体行为」的涌现现象\n"
     "• 对应仿生学「结构/行为仿生 → 原理仿生」的层次", 18, 15)
card(s, Inches(6.9), Inches(1.6), Inches(5.7), Inches(5.0), "应用牵引",
     "【占位：结合课程方向选择 2-3 个展开】\n\n"
     "• 无人机集群编队与协同搜索\n"
     "• 多机器人系统（仓储 AGV、水下机器人群）\n"
     "• 交通流与人群疏散仿真\n"
     "• 计算机图形学中的群体动画\n\n"
     "研究意义：以低成本沙盒验证群体仿生原理，\n为集群算法提供可视化实验平台。", 18, 15)

# ---- 4 生物原型 ----
s = add_slide(bg=LIGHT)
content_header(s, "04", "生物原型观察与机理分析")
card(s, Inches(0.7), Inches(1.6), Inches(5.9), Inches(4.9), "生物原型：鱼类集群（Schooling）",
     "【占位：可插入鱼群照片/视频截图】\n\n"
     "• 观察对象：沙丁鱼群、鲭鱼群等\n"
     "• 群体规模：成百上千至数万个体\n"
     "• 典型行为：同步转向、间距维持、\n  遇警快速逃逸后重新聚合\n"
     "• 队形：球状（防御）、梭形（巡游）、\n  涡形（应对捕食者）", 18, 14)
card(s, Inches(6.9), Inches(1.6), Inches(5.7), Inches(2.3), "行为规律抽象",
     "• 邻近保持：与邻居保持一定距离，不碰撞\n"
     "• 同向运动：倾向与可见范围内的邻居同向\n"
     "• 向心倾向：向邻居群的中心位置靠拢", 17, 14)
card(s, Inches(6.9), Inches(4.2), Inches(5.7), Inches(2.3), "生理基础",
     "• 视觉：感知一定范围内的邻居位置与朝向\n"
     "• 侧线系统（Lateral Line）：感知周围水流\n"
     "  变化，实现近距离避碰与同步\n"
     "• 结论：个体决策仅依赖【局部感知】，无需全局信息", 16, 14)

# ---- 5 仿生映射 ----
s = add_slide(bg=LIGHT)
content_header(s, "05", "仿生映射：从生物到模型")
table(s, Inches(0.7), Inches(1.55),
      ["生物行为（原型）", "仿生模型规则（映射）"],
      [("个体间距保持 / 侧线避碰", "分离规则 Separation：远离过近邻居"),
       ("同向游动（视觉同步）", "对齐规则 Alignment：匹配邻居平均方向"),
       ("向群体中心靠拢（向心性）", "聚合规则 Cohesion：驶向邻居质心"),
       ("感知范围（视觉 + 侧线）", "感知半径 R：只与半径内邻居交互"),
       ("遇捕食者逃逸", "逃逸规则 Flee（扩展项）")],
      [Inches(5.7), Inches(6.3)])
card(s, Inches(0.7), Inches(5.35), Inches(12.0), Inches(1.35),
     "模型来源",
     "• 上述映射即 Reynolds (1987) 提出的 Boids 模型 —— 经典的群体行为仿生模型\n"
     "• 核心思想：每帧对每个个体施加三个规则的加权合力，仅用局部规则即可涌现出整体鱼群运动", 16, 14)

# ---- 6 系统设计 ----
s = add_slide(bg=LIGHT)
content_header(s, "06", "系统设计与技术实现")
card(s, Inches(0.7), Inches(1.6), Inches(3.9), Inches(4.9), "系统结构",
     "【占位：可补架构图】\n\n"
     "• 个体层：位置、速度、状态更新\n"
     "• 规则层：三规则合力计算\n"
     "• 沙盒层：参数面板（权重/半径/\n  个体数量实时调节）\n"
     "• 渲染层：Canvas 2D 绘制鱼形个体", 18, 14)
card(s, Inches(4.85), Inches(1.6), Inches(3.9), Inches(4.9), "关键实现",
     "【占位】\n\n"
     "• 邻居查找：暴力遍历 → 空间网格\n  （网格划分降低复杂度 O(n²)→O(n)）\n"
     "• 帧循环 + delta-time 保证速度\n  与帧率无关\n"
     "• 参数实时调节，行为即时变化", 16, 14)
card(s, Inches(9.0), Inches(1.6), Inches(3.6), Inches(4.9), "技术栈",
     "【占位】\n\n"
     "• 语言：JavaScript (ES6+)\n"
     "• 渲染：Canvas 2D\n"
     "• 运行入口：index.html\n"
     "• 无外部依赖，浏览器直接运行", 18, 14)

# ---- 7 演示与结果 ----
s = add_slide(bg=LIGHT)
content_header(s, "07", "演示与结果分析")
card(s, Inches(0.7), Inches(1.6), Inches(7.7), Inches(4.9), "运行演示（占位）",
     "【此处插入运行截图 / GIF】\n\n"
     "• 演示入口：index.html（浏览器打开）\n"
     "• 演示脚本：\n"
     "  1) 默认参数下的群体涌现行为\n"
     "  2) 调大「聚合」权重 → 群体收拢成球\n"
     "  3) 调大「分离」权重 → 群体分散\n"
     "  4) 缩小感知半径 → 群体分裂为小群", 18, 15)
card(s, Inches(8.7), Inches(1.6), Inches(3.9), Inches(4.9), "结果分析",
     "【占位】\n\n"
     "• 参数变化 → 群体形态对照\n"
     "  （分散 / 聚簇 / 成球 / 条带）\n"
     "• 对照真实鱼群行为：相似点与差异\n"
     "• 性能：个体规模 vs 帧率\n"
     "  （如 1000 个体 @ 60 fps）", 16, 14)

# ---- 8 讨论 ----
s = add_slide(bg=LIGHT)
content_header(s, "08", "讨论：仿生近似与局限")
card(s, Inches(0.7), Inches(1.6), Inches(5.9), Inches(2.4), "Boids 模型的简化",
     "• 个体无差异：真实鱼群有领导者、大小差异\n"
     "• 无学习与记忆：真实鱼有经验积累\n"
     "• 无能耗与生理约束：转弯半径、疲劳未建模", 17, 14)
card(s, Inches(0.7), Inches(4.2), Inches(5.9), Inches(2.4), "与真实生物的差距",
     "• 未建模噪声与扰动（水流、个体感知误差）\n"
     "• 三规则为线性加权，真实决策可能非线性\n"
     "• 缺少信息传递机制（如逃逸信号扩散）", 17, 14)
card(s, Inches(6.9), Inches(1.6), Inches(5.7), Inches(5.0), "可扩展方向",
     "【占位：选择与课程方向契合的展开】\n\n"
     "• 加入捕食者 Agent，模拟逃逸队形（涡形、闪光效应）\n"
     "• 异质个体：领导鱼 / 跟随鱼混合策略\n"
     "• 感知噪声：对邻居位置加入随机误差，检验鲁棒性\n"
     "• 进化参数：用简单优化自动搜索三规则权重\n"
     "• 迁移应用：无人机集群 / 人群疏散仿真接口", 17, 14)

# ---- 9 总结 ----
s = add_slide(bg=LIGHT)
content_header(s, "09", "总结与收获")
bullets(s, Inches(0.8), Inches(1.7), Inches(11.8), Inches(3.4), [
    ("完成了什么：", "搭建了鱼群仿生沙盒，实现 Boids 三规则 + 沙盒参数交互（占位：补充实际完成项）"),
    ("验证了什么：", "验证了「局部简单规则 → 全局有序群体行为」的仿生原理，形态与真实鱼群定性一致"),
    ("学到了什么：", "仿生学「观察—抽象—映射—实现—验证」的完整研究路径；涌现思想与分布式行为设计"),
    ("不足与展望：", "【占位：结合第 8 页局限性写 1-2 点改进计划】"),
], size=19, gap=14)
card(s, Inches(0.7), Inches(5.4), Inches(12.0), Inches(1.3), "课程支撑（占位，按课程要求调整）",
     "• 工作量：约 XXX 小时 / XXX 行代码　• 分工：独立完成（或写明分工）", 16, 14)

# ---- 10 参考文献 ----
s = add_slide(bg=LIGHT)
content_header(s, "10", "参考文献")
refs = [
    "[1] Reynolds, C. W. Flocks, Herds and Schools: A Distributed Behavioral Model. "
    "Computer Graphics (SIGGRAPH '87), 21(4): 25-34, 1987.",
    "[2] Couzin, I. D., et al. Effective leadership and decision-making in animal groups "
    "on the move. Nature, 433: 513-516, 2005.",
    "[3] 【占位】仿生学教材，如《仿生学导论》，作者，出版社，年份.",
    "[4] 【占位】群体智能 / 集群机器人综述文献.",
]
bullets(s, Inches(0.8), Inches(1.7), Inches(11.8), Inches(4.5),
        [("", r) for r in refs], size=16, gap=14, accent=DARK)

# ---- 11 Q&A ----
s = add_slide()
tb(s, Inches(0.9), Inches(2.9), Inches(11.5), Inches(1.2), "谢谢观看 / Q&A", 40, WHITE, bold=True, align=PP_ALIGN.CENTER)
tb(s, Inches(0.9), Inches(4.1), Inches(11.5), Inches(0.6),
   "fish-swarm-sandbox ｜ 仿生学课程研究汇报 ｜ XXX", 16, DIM, align=PP_ALIGN.CENTER)

prs.save("fish-swarm-sandbox-仿生学汇报模板.pptx")
print("saved slides:", len(prs.slides._sldIdLst))
