From 0602fc9a38dcc3ba46e775b82ca91d7c231e7a7f Mon Sep 17 00:00:00 2001 From: smilelsb Date: Wed, 21 Oct 2026 21:02:08 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(i18n):=20add=20Simplified=20Chinese=20?= =?UTF-8?q?Stage=200=20localization=20/=20=E6=B7=BB=E5=8A=A0=E9=98=B6?= =?UTF-8?q?=E6=AE=B5=200=20=E7=AE=80=E4=BD=93=E4=B8=AD=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add zh-CN UI strings, navigation, glossary entries, and translated Learning Course pages through Stage 0. 添加 zh-CN 界面文案、导航、术语表,以及截至阶段 0 的学习课程译文。 --- src/config/locales.ts | 25 +- src/config/sidebarConfig.ts | 209 ++++++++-- src/content/docs/zh-cn/index.mdx | 43 ++ .../getting-started/forking-and-cloning.mdx | 25 ++ .../getting-started/required-tools.mdx | 47 +++ .../getting-started/vscode-overview.mdx | 145 +++++++ .../getting-started/website-feature-guide.mdx | 87 +++++ .../docs/zh-cn/learning-course/index.mdx | 109 ++++++ .../stage0/additional-resources.mdx | 32 ++ .../zh-cn/learning-course/stage0/arrays.mdx | 178 +++++++++ .../stage0/classes-methods.mdx | 215 ++++++++++ .../stage0/classes-objects.mdx | 201 ++++++++++ .../learning-course/stage0/conditionals.mdx | 139 +++++++ .../learning-course/stage0/debugging.mdx | 167 ++++++++ .../docs/zh-cn/learning-course/stage0/git.mdx | 57 +++ .../stage0/interfaces-lists.mdx | 245 ++++++++++++ .../stage0/java-fundamentals.mdx | 278 +++++++++++++ .../zh-cn/learning-course/stage0/loops.mdx | 169 ++++++++ .../learning-course/stage0/operators.mdx | 366 ++++++++++++++++++ .../learning-course/stage0/stage-overview.mdx | 23 ++ .../learning-course/stage0/stage-wrap-up.mdx | 17 + src/content/docs/zh-cn/resources/glossary.mdx | 7 + src/content/i18n/zh-CN.json | 44 +++ src/data/glossary.zh-cn.yaml | 75 ++++ 24 files changed, 2872 insertions(+), 31 deletions(-) create mode 100644 src/content/docs/zh-cn/index.mdx create mode 100644 src/content/docs/zh-cn/learning-course/getting-started/forking-and-cloning.mdx create mode 100644 src/content/docs/zh-cn/learning-course/getting-started/required-tools.mdx create mode 100644 src/content/docs/zh-cn/learning-course/getting-started/vscode-overview.mdx create mode 100644 src/content/docs/zh-cn/learning-course/getting-started/website-feature-guide.mdx create mode 100644 src/content/docs/zh-cn/learning-course/index.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/additional-resources.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/arrays.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/classes-methods.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/classes-objects.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/conditionals.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/debugging.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/git.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/interfaces-lists.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/java-fundamentals.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/loops.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/operators.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/stage-overview.mdx create mode 100644 src/content/docs/zh-cn/learning-course/stage0/stage-wrap-up.mdx create mode 100644 src/content/docs/zh-cn/resources/glossary.mdx create mode 100644 src/content/i18n/zh-CN.json create mode 100644 src/data/glossary.zh-cn.yaml diff --git a/src/config/locales.ts b/src/config/locales.ts index 189471de..31f32e32 100644 --- a/src/config/locales.ts +++ b/src/config/locales.ts @@ -4,9 +4,12 @@ export const defaultLang = 'en'; // adding a new locale requires adding a directory under src/content/docs/ and -// adding an entry here. The directory name must match `lang`. +// adding an entry here. The directory name must match the locale key; `lang` +// is the BCP-47 tag written to the page. Starlight lowercases route IDs, so +// region-tagged locales should use a lowercase key such as `zh-cn`. const configuredLocales: Record = { root: { label: 'English', lang: defaultLang }, + 'zh-cn': { label: '简体中文', lang: 'zh-CN' }, }; export const langByLocaleDir: Record = Object.fromEntries( @@ -61,14 +64,20 @@ function isPageUrl(url: string): boolean { * Prefixes a root-relative page URL with a locale directory. Anything already * in the locale, external, or pointing at public/ is returned unchanged. * - * `localeDir` doubles as the BCP-47 tag, so `Astro.currentLocale` can be passed - * straight in. + * Accepts either a locale directory (such as `zh-cn`) or its BCP-47 language + * tag (such as `zh-CN`), so `Astro.currentLocale` can be passed straight in. */ -export function localizeHref( - href: string, - localeDir: string | undefined, -): string { - if (!localeDir || !(localeDir in langByLocaleDir)) return href; +export function localizeHref(href: string, locale: string | undefined): string { + if (!locale) return href; + + const localeDir = + locale in langByLocaleDir + ? locale + : Object.entries(langByLocaleDir).find( + ([, lang]) => lang === locale, + )?.[0]; + + if (!localeDir) return href; if (!isPageUrl(href)) return href; const [path = ''] = href.split(/[?#]/); diff --git a/src/config/sidebarConfig.ts b/src/config/sidebarConfig.ts index 08fdee28..5b42bba2 100644 --- a/src/config/sidebarConfig.ts +++ b/src/config/sidebarConfig.ts @@ -31,190 +31,259 @@ export type SidebarItem = { const learningCourse: SidebarTopic = { label: 'Learning Course', + translations: { 'zh-CN': '学习课程' }, id: 'learning-course', link: '/learning-course/', icon: 'notes', items: [ - { label: 'Overview', slug: 'learning-course' }, + { + label: 'Overview', + translations: { 'zh-CN': '概览' }, + slug: 'learning-course', + }, { label: 'Website Feature Guide', + translations: { 'zh-CN': '网站功能指南' }, slug: 'learning-course/getting-started/website-feature-guide', }, { label: 'Course Setup', + translations: { 'zh-CN': '课程准备' }, collapsed: true, items: [ { label: 'Required Tools', + translations: { 'zh-CN': '开发必备工具' }, slug: 'learning-course/getting-started/required-tools', }, { label: 'VS Code Overview', + translations: { 'zh-CN': 'VS Code 概述' }, slug: 'learning-course/getting-started/vscode-overview', }, { label: 'Forking and Cloning', + translations: { 'zh-CN': '派生仓库与克隆仓库' }, slug: 'learning-course/getting-started/forking-and-cloning', }, ], }, { label: 'Stage 0', + translations: { 'zh-CN': '阶段 0' }, collapsed: true, items: [ { label: 'Stage 0 Overview', + translations: { 'zh-CN': '阶段 0 概览' }, slug: 'learning-course/stage0/stage-overview', }, { label: 'Java Fundamentals', + translations: { 'zh-CN': 'Java基础' }, slug: 'learning-course/stage0/java-fundamentals', }, { label: 'Debugging', + translations: { 'zh-CN': '调试' }, slug: 'learning-course/stage0/debugging', }, { label: 'Git - Uploading code', + translations: { 'zh-CN': 'Git - 上传代码' }, slug: 'learning-course/stage0/git', }, { label: 'Operators', + translations: { 'zh-CN': '运算符' }, slug: 'learning-course/stage0/operators', }, { label: 'Conditionals', + translations: { 'zh-CN': '条件语句' }, slug: 'learning-course/stage0/conditionals', }, - { label: 'Loops', slug: 'learning-course/stage0/loops' }, + { + label: 'Loops', + translations: { 'zh-CN': '循环' }, + slug: 'learning-course/stage0/loops', + }, { label: 'Classes and Objects', + translations: { 'zh-CN': '类与对象' }, slug: 'learning-course/stage0/classes-objects', }, { label: 'Methods and Mutable State', + translations: { + 'zh-CN': '方法与可变状态', + }, slug: 'learning-course/stage0/classes-methods', }, { label: 'Arrays and For-Each Loops', + translations: { + 'zh-CN': '数组与 for-each 循环', + }, slug: 'learning-course/stage0/arrays', }, { label: 'Interfaces, Generics, and Lists', + translations: { + 'zh-CN': '接口、泛型与列表', + }, slug: 'learning-course/stage0/interfaces-lists', }, { label: 'Additional Resources', + translations: { 'zh-CN': '补充资源' }, slug: 'learning-course/stage0/additional-resources', }, { label: 'Stage 0 Wrap-Up', + translations: { 'zh-CN': '阶段 0 总结' }, slug: 'learning-course/stage0/stage-wrap-up', }, ], }, { label: 'Stage 1', + translations: { 'zh-CN': '阶段 1' }, collapsed: true, items: [ { label: 'Stage 1 Overview', + translations: { 'zh-CN': '阶段 1 概览' }, slug: 'learning-course/stage1/stage-overview', }, { label: 'Stage 1A: Kitbot Intro', + translations: { 'zh-CN': '阶段 1A:Kitbot 入门' }, collapsed: true, items: [ { label: 'Stage 1A Overview', + translations: { 'zh-CN': '阶段 1A 概览' }, slug: 'learning-course/stage1/stage1a/stage-overview', }, { label: 'Getting Started', + translations: { 'zh-CN': '入门' }, slug: 'learning-course/stage1/stage1a/getting-started', }, { label: 'Kitbot Drivetrain', + translations: { 'zh-CN': 'Kitbot 传动系统' }, slug: 'learning-course/stage1/stage1a/kitbot-drivetrain', }, { label: 'Drivetrain Simulation', + translations: { 'zh-CN': '传动系统仿真' }, slug: 'learning-course/stage1/stage1a/drivetrain-sim', }, { label: 'Simple Auto', + translations: { 'zh-CN': '简单自动程序' }, slug: 'learning-course/stage1/stage1a/simple-auto', }, { label: 'Additional Motors', + translations: { 'zh-CN': '额外电机' }, slug: 'learning-course/stage1/stage1a/kitbot-additional-motors', }, { label: 'Stage 1A Wrap-Up', + translations: { 'zh-CN': '阶段 1A 总结' }, slug: 'learning-course/stage1/stage1a/stage-wrap-up', }, ], }, { label: 'Stage 1B: Commands', + translations: { 'zh-CN': '阶段 1B:命令' }, collapsed: true, items: [ { label: 'Stage 1B Overview', + translations: { 'zh-CN': '阶段 1B 概览' }, slug: 'learning-course/stage1/stage1b/stage-overview', }, { label: 'The Concepts', + translations: { 'zh-CN': '核心概念' }, slug: 'learning-course/stage1/stage1b/command-based-overview', }, { label: 'The Body of a Command', + translations: { 'zh-CN': '命令的主体' }, slug: 'learning-course/stage1/stage1b/the-command-body', }, { label: 'Commands & Mechanisms, Pt. 1', + translations: { 'zh-CN': '命令与机构(第一部分)' }, slug: 'learning-course/stage1/stage1b/commands-and-mechanisms', }, { label: 'Triggers and Scheduling', + translations: { 'zh-CN': '触发器与调度' }, slug: 'learning-course/stage1/stage1b/triggers', }, { label: 'Commands & Mechanisms, Pt. 2', + translations: { 'zh-CN': '命令与机构(第二部分)' }, slug: 'learning-course/stage1/stage1b/commands-and-mechanisms-pt2', }, { label: 'Bonus: Spot the Error', + translations: { + 'zh-CN': '拓展练习:找出代码错误(第一部分)', + }, slug: 'learning-course/stage1/stage1b/spot-the-error', }, { label: 'Exercise - Kitbot Rewrite, Pt. 1', + translations: { + 'zh-CN': '练习:Kitbot 代码重构(第一部分)', + }, slug: 'learning-course/stage1/stage1b/command-based-kitbot', }, { label: 'Suppliers in Command-Based', + translations: { + 'zh-CN': + '基于命令的编程中的 Supplier(供给器)', + }, slug: 'learning-course/stage1/stage1b/suppliers-in-command-based', }, { label: 'Exercise - Kitbot Rewrite, Pt. 2', + translations: { + 'zh-CN': '练习:Kitbot 代码重构(第二部分)', + }, slug: 'learning-course/stage1/stage1b/command-based-kitbot-pt2', }, { label: 'Bonus: Spot the Error, Pt 2', + translations: { + 'zh-CN': '附加:找出错误(第二部分)', + }, slug: 'learning-course/stage1/stage1b/spot-the-error-pt2', }, { label: 'Stage 1B Wrap-Up', + translations: { 'zh-CN': '阶段 1B 总结' }, slug: 'learning-course/stage1/stage1b/stage-wrap-up', }, ], }, { label: 'Stage 1C: Control and Telemetry', + translations: { 'zh-CN': '阶段 1C:控制与遥测' }, collapsed: true, items: [ { label: 'Stage 1C Introduction', + translations: { 'zh-CN': '阶段 1C 介绍' }, slug: 'learning-course/stage1/stage1c/stage-overview', }, ], @@ -223,10 +292,12 @@ const learningCourse: SidebarTopic = { }, { label: 'Stage 2', + translations: { 'zh-CN': '阶段 2' }, collapsed: true, items: [ { label: 'Stage 2 Overview', + translations: { 'zh-CN': '阶段 2 概览' }, slug: 'learning-course/stage2/stage-overview', }, ], @@ -236,76 +307,168 @@ const learningCourse: SidebarTopic = { const educatorsGuide: SidebarTopic = { label: "Educator's Guide", + translations: { 'zh-CN': '教师指南' }, id: 'educators-guide', link: '/educators-guide/introduction/', icon: 'open-book', items: [ - { label: 'Introduction', slug: 'educators-guide/introduction' }, + { + label: 'Introduction', + translations: { 'zh-CN': '介绍' }, + slug: 'educators-guide/introduction', + }, { label: 'The Stages', + translations: { 'zh-CN': '各个阶段' }, slug: 'educators-guide/introduction/the-stages', }, { label: 'Preparing Yourself', + translations: { 'zh-CN': '做好准备' }, slug: 'educators-guide/introduction/preparation', }, - { label: 'Stage 0', slug: 'educators-guide/stage0' }, + { + label: 'Stage 0', + translations: { 'zh-CN': '阶段 0' }, + slug: 'educators-guide/stage0', + }, { label: 'Stage 1', + translations: { 'zh-CN': '阶段 1' }, collapsed: true, items: [ - { label: 'Overview', slug: 'educators-guide/stage1' }, - { label: 'Stage 1A', slug: 'educators-guide/stage1/stage1a' }, - { label: 'Stage 1B', slug: 'educators-guide/stage1/stage1b' }, - { label: 'Stage 1C', slug: 'educators-guide/stage1/stage1c' }, + { + label: 'Overview', + translations: { 'zh-CN': '概览' }, + slug: 'educators-guide/stage1', + }, + { + label: 'Stage 1A', + translations: { 'zh-CN': '阶段 1A' }, + slug: 'educators-guide/stage1/stage1a', + }, + { + label: 'Stage 1B', + translations: { 'zh-CN': '阶段 1B' }, + slug: 'educators-guide/stage1/stage1b', + }, + { + label: 'Stage 1C', + translations: { 'zh-CN': '阶段 1C' }, + slug: 'educators-guide/stage1/stage1c', + }, ], }, - { label: 'Stage 2', slug: 'educators-guide/stage2' }, + { + label: 'Stage 2', + translations: { 'zh-CN': '阶段 2' }, + slug: 'educators-guide/stage2', + }, ], }; const bestPractices: SidebarTopic = { label: 'Best Practices', + translations: { 'zh-CN': '最佳实践' }, id: 'best-practices', link: '/best-practices/overview/', icon: 'approve-check-circle', items: [ - { label: 'Overview', slug: 'best-practices/overview' }, - { label: 'Git Usage', slug: 'best-practices/git-usage' }, - { label: 'GitHub Usage', slug: 'best-practices/github-usage' }, - { label: 'Code Formatter', slug: 'best-practices/code-formatter' }, - { label: 'CI Checks', slug: 'best-practices/ci-checks' }, + { + label: 'Overview', + translations: { 'zh-CN': '概览' }, + slug: 'best-practices/overview', + }, + { + label: 'Git Usage', + translations: { 'zh-CN': 'Git 使用' }, + slug: 'best-practices/git-usage', + }, + { + label: 'GitHub Usage', + translations: { 'zh-CN': 'GitHub 使用' }, + slug: 'best-practices/github-usage', + }, + { + label: 'Code Formatter', + translations: { 'zh-CN': '代码格式化' }, + slug: 'best-practices/code-formatter', + }, + { + label: 'CI Checks', + translations: { 'zh-CN': 'CI 检查' }, + slug: 'best-practices/ci-checks', + }, ], }; const resources: SidebarTopic = { label: 'Resources', + translations: { 'zh-CN': '资源' }, id: 'resources', link: '/resources/overview/', icon: 'document', items: [ - { label: 'Overview', slug: 'resources/overview' }, - { label: 'Examples', slug: 'resources/examples' }, - { label: 'Documentation', slug: 'resources/docs' }, - { label: 'Hardware', slug: 'resources/hardware-intro' }, - { label: 'Glossary', slug: 'resources/glossary' }, + { + label: 'Overview', + translations: { 'zh-CN': '概览' }, + slug: 'resources/overview', + }, + { + label: 'Examples', + translations: { 'zh-CN': '示例' }, + slug: 'resources/examples', + }, + { + label: 'Documentation', + translations: { 'zh-CN': '文档' }, + slug: 'resources/docs', + }, + { + label: 'Hardware', + translations: { 'zh-CN': '硬件' }, + slug: 'resources/hardware-intro', + }, + { + label: 'Glossary', + translations: { 'zh-CN': '术语表' }, + slug: 'resources/glossary', + }, ], }; const contribution: SidebarTopic = { label: 'Contribution', + translations: { 'zh-CN': '贡献' }, id: 'contribution', link: '/contribution/', icon: 'code-branch', items: [ - { label: 'Overview', slug: 'contribution' }, + { + label: 'Overview', + translations: { 'zh-CN': '概览' }, + slug: 'contribution', + }, { label: 'Methods of Contributing', + translations: { 'zh-CN': '贡献方式' }, slug: 'contribution/methodsofcontributing', }, - { label: 'Style Guide', slug: 'contribution/styleguide' }, - { label: 'Contributors', slug: 'contribution/contributors' }, - { label: 'Roadmap', slug: 'contribution/roadmap' }, + { + label: 'Style Guide', + translations: { 'zh-CN': '风格指南' }, + slug: 'contribution/styleguide', + }, + { + label: 'Contributors', + translations: { 'zh-CN': '贡献者' }, + slug: 'contribution/contributors', + }, + { + label: 'Roadmap', + translations: { 'zh-CN': '路线图' }, + slug: 'contribution/roadmap', + }, ], }; diff --git a/src/content/docs/zh-cn/index.mdx b/src/content/docs/zh-cn/index.mdx new file mode 100644 index 00000000..3a359997 --- /dev/null +++ b/src/content/docs/zh-cn/index.mdx @@ -0,0 +1,43 @@ +--- +title: 欢迎来到 FRCSoftware.org +description: FRC 编程综合学习指南 +template: splash +hero: + tagline: FRC 编程综合学习指南 + image: + html: 'FRCSoftware.org 标志' + actions: + - text: 开始学习 + link: /learning-course/ +--- + +import HomeCard from '../../../components/HomeCard.astro'; + +
+ + + + + + +
diff --git a/src/content/docs/zh-cn/learning-course/getting-started/forking-and-cloning.mdx b/src/content/docs/zh-cn/learning-course/getting-started/forking-and-cloning.mdx new file mode 100644 index 00000000..d6fe690c --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/getting-started/forking-and-cloning.mdx @@ -0,0 +1,25 @@ +--- +title: 派生仓库与克隆仓库 +description: 介绍如何派生并克隆用于个人工作的 GitHub 仓库 +--- + +import ContentFigure from '@components/ContentFigure.astro'; + +## Forking(派生仓库) + +开始之前,需要为相关的 repository(仓库)创建自己的 fork(派生仓库)。 +在此之前,需要先按照“必备工具”部分的说明完成准备工作。 +要创建 fork,请访问需要复制的仓库(例如 https://github.com/frcsoftware/Stage0-Template),然后点击右上角的“Fork”按钮。 +这会在自己的 GitHub 账户下创建该仓库的副本,以便在不影响原仓库的情况下进行修改。 + + + +## Cloning(克隆) + +要 clone(克隆)刚刚创建的 repository(仓库),请打开 VS Code。 +然后复制刚刚派生的仓库 URL。 +在 VS Code 中打开左上角的 Source Control(源代码管理)侧栏,然后选择“Clone Repository”(克隆仓库)。 +粘贴派生仓库的 URL,并选择用于存放该仓库的本地目录。 +这会在计算机上创建仓库的本地副本,以便处理其中的代码。 + + diff --git a/src/content/docs/zh-cn/learning-course/getting-started/required-tools.mdx b/src/content/docs/zh-cn/learning-course/getting-started/required-tools.mdx new file mode 100644 index 00000000..cace84cb --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/getting-started/required-tools.mdx @@ -0,0 +1,47 @@ +--- +title: 开发必备工具 +description: 安装完成本课程和参与 FRC 编程所需的全部软件 +--- + +要为 FRC 机器人编程并跟随本网站学习,需要安装 WPILib 工具和 Git,并注册 GitHub 账户。 + +## WPILib 工具 + +首先需要安装 WPILib 工具包。 +WPILib 工具包包含特定版本的 VS Code,队伍可以使用它编写代码,并将代码 deploy(部署)到机器人。 + +本网站要求使用 2027 赛季 WPILib 的 alpha 版本。 +下载链接请参阅[此处](https://github.com/wpilibsuite/allwpilib/releases)。 + + + +下载 2027 WPILib 工具后,可以按照[此处](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html)的说明完成安装。 + +WPILib 工具包还包含适用于 data logging(数据日志记录)、Simulation(仿真)、仪表盘等用途的程序。 +后续阶段将使用其中的一部分工具。 + +### Driver Station(操控站) + +如果需要控制实体机器人,可能还需要安装 FRC Game Tools(FRC 比赛工具包)。 +该工具包会安装 Driver Station,这是一款连接并控制机器人的程序。 +安装说明请参阅[此处](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/frc-game-tools.html),但完成本课程并不要求安装它。 + +## Git + +接下来需要安装 Git。 +Git 是 FRC 中常用的版本控制系统,可以帮助队伍在机器人制作和比赛赛季期间跟踪代码变更。 +队伍还可以使用 GitHub 提供的 Pull Requests(合并请求)、Issues(议题)和项目看板等功能管理机器人代码。 +Git 的安装说明请参阅[此处](https://git-scm.com/install/)。 + +## 创建 GitHub 账户 + +GitHub 是托管 Git repositories(仓库)并支持代码协作的网站。 +GitHub 不仅免费且易于使用,还是行业标准,因此在 FRC 中得到广泛应用。 + +本网站的许多课程都需要使用 GitHub 账户。 +可以在[此处](https://github.com/signup)注册账户。 +还建议申请 [GitHub Student Developer Pack(GitHub 学生开发者包)](https://education.github.com/pack),以获取各种资源。 diff --git a/src/content/docs/zh-cn/learning-course/getting-started/vscode-overview.mdx b/src/content/docs/zh-cn/learning-course/getting-started/vscode-overview.mdx new file mode 100644 index 00000000..199ebba6 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/getting-started/vscode-overview.mdx @@ -0,0 +1,145 @@ +--- +title: VS Code 概述 +description: 介绍使用 WPILib 时所需的 VS Code 程序及其界面 +--- + +## 什么是 VS Code + +Visual Studio Code(VS Code)是 Microsoft 开发的一款免费开源代码编辑器。 +它支持 Windows、macOS 和 Linux,并可通过 Extension Marketplace(扩展市场)中的扩展支持几乎所有编程语言。 +它还集成了 Git,可以直接在编辑器的 Source Control(源代码管理)面板中进行暂存、commit(提交)、push(推送)、pull(拉取)和查看 diff(差异)等操作。 + +在之前的页面中,你安装了包含 WPILib VS Code 的 WPILib 工具。 +在 FRC 中,应当使用这个版本的 VS Code。 +WPILib VS Code 会随其他 WPILib 工具一起安装,并作为单独的应用程序运行。 +如果之前安装过普通版 VS Code,请确保使用 WPILib 专用版本为机器人编程。 + +WPILib VS Code 与普通版 VS Code 使用不同的图标。 +如下图所示,WPILib VS Code 使用 WPILib 标志,而普通版 VS Code 使用蓝色标志。 + + + + + +## 欢迎界面 + +首次打开 WPILib VS Code 时,会显示包含多个选项的欢迎界面。 +在这里选择 `File`,即可打开文件菜单。 +菜单中有许多选项,其中最常用的是: + +1. `New file` +2. `Open file` +3. `Open folder` + + + + + + + +## 浏览界面 + +VS Code 的布局可以划分为几个主要区域: + +1. Activity Bar(活动栏) + 位于最左侧,由一列垂直图标组成,用于切换主要视图,包括 + Explorer(资源管理器)、Search(搜索)、Source Control(源代码管理)、Run & + Debug(运行和调试)以及 Extensions(扩展)。 +2. Side Bar(侧栏) + 位于活动栏旁边,用于显示当前所选视图的详细内容,例如 Explorer 中的文件树。 +3. Panel(面板)位于屏幕底部。 它包含 + Terminal(终端)、Problems(问题)、Output(输出)和 Debug + Console(调试控制台)四个主要视图。 +4. Editor(编辑器)位于中央区域。 + 编辑器是主要工作区域,可以在其中查看和编辑文件。 +5. Command Palette(命令面板)可通过 `Ctrl+Shift+P` / + `Cmd+Shift+P` 打开,也可通过顶部工具栏中的 WPILib 按钮打开,用于搜索和运行 VS + Code 中的任何命令。 + + + +### WPILib Command Palette(WPILib 命令面板) + +WPILib VS Code 的一项重要功能是提供 FRC 专用命令,例如:Build(构建),用于编译机器人代码并检查设备上的错误;Deploy(部署),用于构建代码并通过与机器人的网络连接将代码发送到 Systemcore;以及 Simulate(仿真),用于通过内置仿真工具在计算机上测试代码。 +这些命令都可以通过 Command Palette(命令面板)运行。 + + + +## 保存和打开文件 + +### 打开文件 + +- 打开单个文件:`File > Open File...` 或 `Ctrl+O` / `Cmd+O` +- 打开文件夹或项目:`File > Open Folder...` 或 `Ctrl+K Ctrl+O` / `Cmd+K Cmd+O` +- 快速打开文件:按 `Ctrl+P` / `Cmd+P`,然后开始输入文件名 +- 重新打开最近关闭的文件:`Ctrl+Shift+T` / `Cmd+Shift+T` + +### 保存文件 + +- 保存当前文件:`Ctrl+S` / `Cmd+S` +- 另存为新的名称或位置:`Ctrl+Shift+S` / `Cmd+Shift+S` +- 保存所有已打开的文件:`Ctrl+K S` / `Cmd+K S` + + + +文件标签页上的圆点表示存在**未保存的更改**。 +保存文件后,圆点会消失并变为“X”关闭图标。 + + + +## 键盘快捷键 + +VS Code 提供了许多实用的键盘快捷键。 + +- [Windows 键盘快捷键](https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf) +- [MacOS 键盘快捷键](https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf) + +其中最常用的快捷键包括: + +- `Ctrl+P` / `Cmd+P`:打开 Quick Open(快速打开),可以按名称打开任意文件 +- `Ctrl+Shift+P` / `Cmd+Shift+P`:打开 Command Palette(命令面板) +- `` Ctrl+` ``:快速打开或关闭集成终端 +- `Ctrl+B` / `Cmd+B`:切换侧栏的显示状态 +- `Ctrl+Tab`:在已打开的编辑器之间切换 + + diff --git a/src/content/docs/zh-cn/learning-course/getting-started/website-feature-guide.mdx b/src/content/docs/zh-cn/learning-course/getting-started/website-feature-guide.mdx new file mode 100644 index 00000000..bdbf6207 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/getting-started/website-feature-guide.mdx @@ -0,0 +1,87 @@ +--- +title: 网站功能指南 +description: 了解 FRCSoftware 的各项功能,以便高效浏览内容 +--- + +import { REV_CTRE_CHOOSER_KEY } from '@data/tabsSyncKeys.ts'; +import { TabItem, Tabs } from '@astrojs/starlight/components'; + +在开始学习之前,需要先了解网站中的一些功能,否则可能难以高效地完成课程。 +下面通过示例介绍这些功能,帮助你在浏览网站前快速掌握它们。 + +## 全站功能 + +- [链接](/learning-course/)以紫色文字显示。 +- 点击图片可将其放大,并在适用时显示说明文字。 + 按 `Esc` 键可再次缩小图片。 +- 网站提供与课程内容相关的[术语表](/resources/glossary/)。 + 术语表中的词语在网站中出现时会带有下划线。 + 将鼠标悬停在这些带下划线的词语上,即可快速查看定义(例如 PDH、SPARK MAX)。 +- 部分信息隐藏在下拉框中。 + 请尽量查看其中的大部分内容! + + + + + +FRCSoftware 主要介绍针对不同电机编写代码的两种方式。 +在后续阶段中,将使用标签页来区分这些差异。 +要查看基于 REV 电机的代码,请点击 REV 标签页。 +要查看基于 CTRE 电机的代码,请点击 CTRE 标签页。 + + + 这里将显示 CTRE 代码 + 这里将显示 REV 代码 + + +### 提示框 + + + + + + + + + + + +## 学习课程 + +### 幻灯片 + + +![幻灯片 1](/learning-course/getting-started/website-feature-guide/slide1.webp) 点击两侧的箭头浏览幻灯片。 + +![幻灯片 2](/learning-course/getting-started/website-feature-guide/slide2.webp) 说明文字下方的圆点表示当前所在的幻灯片。 +也可以点击这些圆点切换幻灯片。 + + + + + + diff --git a/src/content/docs/zh-cn/learning-course/index.mdx b/src/content/docs/zh-cn/learning-course/index.mdx new file mode 100644 index 00000000..d01975c1 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/index.mdx @@ -0,0 +1,109 @@ +--- +title: FRCSoftware 学习课程 +description: 学习 FRC 编程的引导式课程 +--- + +import CourseSection from '@components/CourseSection.astro'; + +欢迎参加 FRCSoftware 学习课程! +本课程旨在帮助不同技能水平的学生学习如何为 FRC 机器人编程。 +课程从学习编写 Java 开始,逐步进阶到为复杂度不断提高的机器人编程。 +我们会先以 FRC Kitbot 为例学习机器人的单个机构,再学习更复杂的机器人。 + +FRC 软件一直在不断变化! +如果你有反馈或建议,欢迎通过 [GitHub](https://github.com/frcsoftware/frcsoftware.org) +或 [Discord](https://discord.com/invite/uUugPrPZFs) 与我们交流。 + + + + +

+ 课程准备介绍如何下载完成本学习课程所需的工具,以及如何浏览和使用这些工具。如果你刚开始学习 FRC 编程,可以从这里入手。 + + - 必备工具:下载本学习课程所需的工具。 + - VS Code 概述:进一步了解本课程使用的代码编辑器 VS Code。 + - 派生仓库与克隆仓库:学习如何使用 Git 进行 fork(派生仓库)和 clone(克隆)。这些操作对于获取课程中使用的模板十分重要。 + +

+ +
+ + +

+ 阶段 0 介绍为简单 FRC 机器人 Kitbot 编程所需的 Java + 基础知识,其中包括: +

    +
  • Java基础
  • +
  • Operators(运算符)
  • +
  • Conditionals(条件语句)
  • +
  • Loops(循环)
  • +
  • Classes and Objects(类与对象)
  • +
  • Methods and Mutable State(方法与可变状态)
  • +
  • Arrays and For-Each Loops(数组与 for-each 循环)
  • +
  • Interfaces, Generics, and Lists(接口、泛型与列表)
  • +
+

+ +
+ + +

+ 阶段 1 将开始为 Kitbot 编程。Kitbot 是一种常用于学习 FRC 基础技能的入门机器人。 + +

+ +
    +
  • + 阶段 1A:使用 `Robot` 类为 Kitbot 编程,学习 FRC 编程的基础技能。 +
  • +
  • + 阶段 1B:在 1A 编写的代码中加入 Command-Based Programming(基于命令的编程)文件结构,学习如何为 FRC 机器人编程。 +
  • +
  • + {' '} + + +
  • +
+

+ +
+ + +

+

+

+ +
diff --git a/src/content/docs/zh-cn/learning-course/stage0/additional-resources.mdx b/src/content/docs/zh-cn/learning-course/stage0/additional-resources.mdx new file mode 100644 index 00000000..d20c956a --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/additional-resources.mdx @@ -0,0 +1,32 @@ +--- +title: Additional Resources(补充资源) +description: 用于继续学习的可选资源 +--- + +你已经完成阶段 0。 +现在已经掌握了为第一台 FRC 机器人编程所需的 Java。 + +正如[阶段 0 概览](/learning-course/stage0/stage-overview/)中所说,阶段 0 只涵盖基础知识,所以还有更多 Java 可以学习。 +本页列出用于继续学习 Java 的**可选**资源。 +这一页可以先跳过,以后再回来查看。 + +## Java 课程 + +- [Codecademy](https://www.codecademy.com/learn/learn-java):在浏览器里交互式学习 Java,并完成配套练习。 +- FIRST 的 Java 培训:FIRST 官方的 Java 编程培训。 + - [第 1 部分](https://training.firstinspires.org/courses/java-programming-part-1) + - [第 2 部分](https://training.firstinspires.org/courses/java-programming-part-2) +- [Mooc fi 的 Java 课程](https://java-programming.mooc.fi/):赫尔辛基大学的免费 Java 课程,包含大量编程练习。 +- [Java 的 Learn Java](https://dev.java/learn/):dev.java 上的 Java 入门学习路径。 +- [Oracle 的 Java 教程](https://docs.oracle.com/javase/tutorial/java/):Oracle 官方的 Java 语言和标准库教程。 +- [7237 Unibots,Java Basics](https://www.youtube.com/playlist?list=PLbyD6UONF3m7Tvp6mTluAtD8vSv2MeO8o):7237 队制作的 Java 基础视频系列。 + +## Java 参考资料 + +- [W3Schools](https://www.w3schools.com/java/):按主题查阅 Java 语法和短示例。 +- [GeeksForGeeks](https://www.geeksforgeeks.org/java/java/):Java 概念、语法和示例文章。 + +## 练习 + +- [Coding Bat](https://codingbat.com/java):针对基础逻辑的小型 Java 练习题。 +- [CodeWars](https://www.codewars.com/):按难度分级的编程挑战。 diff --git a/src/content/docs/zh-cn/learning-course/stage0/arrays.mdx b/src/content/docs/zh-cn/learning-course/stage0/arrays.mdx new file mode 100644 index 00000000..2482e2f3 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/arrays.mdx @@ -0,0 +1,178 @@ +--- +title: Arrays and For-Each Loops(数组与 for-each 循环) +description: 介绍 Java 中的数组和 for-each 循环 +codeRegionSources: + default: stage0/snippets/src/Arrays.java +--- + +在[上一课](/learning-course/stage0/classes-methods/)中,我们定义了 `Point` 类,用来表示机器人在赛场上的位置。 +本节课我们将学习 arrays(数组)。数组可以把多个相同类型的值存放在一起。 +同时学习 for-each loop(for-each 循环)。它用来遍历这些值,相比你已经学过的 `for` 循环,代码通常会更加简洁。 + +## 为什么使用数组? + +假设 drivetrain(底盘驱动系统)有四台电机,你想记录每台电机设定的速度。 +如果没有数组,就需要为每台电机单独创建一个变量: + +{/* rli:ignore */} + +```java +double motor1Speed = 0.5; +double motor2Speed = 0.5; +double motor3Speed = 0.5; +double motor4Speed = 0.5; +``` + +只有四台电机时,这样写可以完成任务,但无法继续扩展。 +如果机器人有八台电机,就需要八个变量,而且任何要对“所有电机速度”进行操作的代码,都必须为每个变量重复写一次。 +**数组**把多个相同类型的值放在同一个变量里,从而解决这个问题。 + +## 声明和创建数组 + +数组类型的写法是元素类型后面加上方括号,例如 `double[]` 代表存放 `double` 类型数据的数组。 +创建数组最简单的方式是使用 array literal(数组字面量),也就是放在花括号中,用逗号分隔的一组元素: + +```java #motorSpeedsLiteral + +``` + +这行代码创建了包含四个 `double` 数值的数组,并把它保存在变量 `motorSpeeds` 中。 +数组一旦创建,长度就固定不变,所以只要 `motorSpeeds` 存在,它就始终正好保存着四个元素。 +不过,数组中的元素的值可以随时更改。 + + + +## 索引与长度 + +数组中的每个值称为 element(元素)。访问特定元素时使用它的 index(索引),也就是该元素在数组中的位置。 +在 Java 中,数组索引从 `0` 开始,而不是从 `1` 开始。 +因此,在 `motorSpeeds` 中,第一台电机的速度位于索引 `0`,最后一台电机的速度位于索引 `3`。 + +在数组变量名后面使用方括号来读取和写入元素。 +下面更新第一台电机的速度,并把它打印输出: + +```java #setSpeed + +``` + +数组还有一个属性 `length`,它表示数组包含的元素数量。 +和 `String.length()` 方法不同,数组的 `length` 是一个成员变量(field),所以访问时不加圆括号: + +```java #speedsLength + +``` + + + +## 对象数组 + +数组的元素类型不限于 `double` 这样的基本类型,它可以是任何类型,包括你自己定义的类,例如 `Point`。 +把一条固定的自动路径定义成 waypoints(路径点)数组,数组中的元素顺序就是机器人应当依次经过它们的顺序: + +```java #pathArray + +``` + +和 `motorSpeeds` 一样,`path` 的大小是固定的。这里是三个,因为创建时放入了三个路径点。 +这很适合自动路径:路线事先已经规划好,所以我们确切知道它有多少个路径点。 + + + + + +## 遍历数组 + +数组的用处在于可以遍历其中的元素,而不必逐个手写。 +前面已经学习过基于索引的 `for` 循环。下面用它把 `motorSpeeds` 中每个电机的速度加起来: + +```java #indexLoopSpeeds + +``` + +循环条件 `i < motorSpeeds.length` 让这段代码可以适配任意长度的数组: +无论 `motorSpeeds` 有4个元素还是40个元素,它都会在最后一个有效索引之后停止。 + +## for-each 循环 + +通常,这种循环只需要“对每个元素执行一段操作”,并不需要使用索引。 +Java 的 for-each 循环可以完成这个操作,不需要再单独管理索引变量: + +{/* rli:ignore */} + +```java +for (ElementType element : array) { + // code that uses element +} +``` + +冒号左边整体是「类型 + 局部变量名」。类型必须与数组的元素类型一致;类型后面的名称是一个新的局部变量,每次循环保存一个元素。冒号右边是待遍历的数组或集合。 +我们使用 for-each 循环来打印 `path` 中的每个路径点: + +```java #forEachPath + +``` + +这比对应的基于索引的 for 循环更易于阅读和理解,因为不需要一直写 `path[i]`。 +`waypoint` 会按顺序依次变成 `path` 中的每个 `Point` 对象,每个元素对应一次循环。 + +但有些场景必须用带索引的普通 for 循环,因为需要的不只是“当前元素”。 +例如,要计算路径的总距离,需要每个路径点以及它上一个路径点,而for-each 循环无法表达这一点: + +```java #pathLength + +``` + +这里,`path[i - 1]` 是上一个路径点,`path[i]` 是当前路径点。`i` 从 `1` 开始,因为索引 `0` 没有前一个路径点可以比较。 +for-each 循环无法访问前一个元素,所以这里更适合使用基于索引的 `for` 循环。 + +## 数组与 for-each 循环练习 + + diff --git a/src/content/docs/zh-cn/learning-course/stage0/classes-methods.mdx b/src/content/docs/zh-cn/learning-course/stage0/classes-methods.mdx new file mode 100644 index 00000000..bad616b1 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/classes-methods.mdx @@ -0,0 +1,215 @@ +--- +title: Methods and Mutable State(方法与可变状态) +description: 编写定义类行为的方法,以及状态会随时间变化的类 +codeRegionSources: + point: stage0/snippets/src/classes-methods/Point.java + tracker: stage0/snippets/src/classes-methods/RobotTracker.java + usage: stage0/snippets/src/classes-methods/Usage.java +--- + +在[上一课](/learning-course/stage0/classes-objects/)中,我们编写了带有成员变量(field)、constructor(构造函数)和 getter(获取器)的 `Point` 类。 +它可以保存一对坐标,但不能对坐标进行任何计算。 +本节课我们将编写 methods(方法),用来定义类的行为,然后再实现一个状态会随时间变化的类。 + +## 编写方法 + +methods(方法)是定义在类内部的一段代码,用来定义这个类的行为。 +我们已经见过两个方法示例:`String` 类中的 `length()`,以及我们在 `Point` 上编写的 getter 方法。 +方法可以有参数,也就是调用方法时传入的值。 + +方法执行结束后,可以向调用者返回值,这称为返回值。 +想要返回值,需要在方法体需要使用 `return` 关键字,后面跟上要返回的数据。 +一旦 `return` 语句被执行,方法会立刻停止运行,并把该值返回给调用者。 +例如,`String` 类中的 `length()` 方法会返回字符串的长度,并且它没有参数。 + +方法的一般语法格式为: + +{/* rli:ignore */} + +```java +public ReturnType methodName(ParameterType1 param1, ParameterType2 param2, ...) { + // method body +} +``` + +一个方法可以拥有任意数量的参数,也可以没有参数,参数也可以是任何类型。 +`ReturnType` 代表方法返回值的类型,例如 `length()` 的返回类型是 `int`。 +方法不是必须返回数据;如果不需要返回任何内容, +就使用返回类型 `void`。`void` 是一个关键字,表示方法不返回任何值。 +`void` 类型的方法也可以单独写 `return` 后面不带返回值,用来提前终止方法。 +不过这种写法并不常用,大多数情况下直接省略即可 —— 方法执行到末尾就会自动结束。 + +接下来我们给 `Point` 类添加一些行为,先从 `plus` 方法开始。 +`plus` 方法接收另一个 `Point` 对象作为参数, +并返回一个全新的 `Point` 对象,这个对象是两个 `Point` 对象之和。 + +```java {point}#plus + +``` + +这里的 `other` 只是我们给这个方法参数取的名字,代表我们要相加的另一个 `Point` 对象。 +`this` 仍然指调用 `plus` 方法的那个 `Point` 对象,与构造函数中相同。 +因此,`this.x` 和 `this.y` 是调用 `plus` 的那个 `Point` 的坐标, +`other.x` 和 `other.y` 是传入的 `Point` 的坐标。 + +在这个方法中,我们使用 `new` 关键字创建一个新的 `Point` 对象。 +因为前面定义的构造函数接收两个参数, +所以把 `this.x + other.x` 和 `this.y + other.y` 作为实参传给构造函数。 +因此,新的 `Point` 对象会拥有两个 `Point` 对象坐标之和。 + +`minus` 方法的逻辑与之类似,但它是减去坐标,而不是相加: + +```java {point}#minus + +``` + +`minus` 方法得到的结果并不是场地上的一个位置。 +它是一个 vector(向量),也就是带有方向和长度的箭头,例如“向右 3 米、向上 4 米”。 +用这个 `Point` 减去 `other`,得到的是从 `other` 指向这个 `Point` 的向量。 +`Point` 既可以保存位置,也可以保存向量,因为两者都只用 `x` 和 `y` 两个数值描述。 + +最后定义 `norm` 方法,它返回这个 `Point` 到原点的距离。 +想象一个直角三角形,一个角的顶点在原点,对角的顶点就是当前的这个 `Point`。 +两条直角边的长度分别是 `x` 和 `y`,所以根据勾股定理,斜边长度是 `x² + y²` 的平方根。 +对于点 (3, 4),结果就是 9 + 16 的平方根,也就是 5。 +向量的长度称为它的 norm(范数),这个方法也因此得名。 + +```java {point}#norm + +``` + +这个方法定义了一个 local variable(局部变量) `sumOfSquares`,用于储存 `x` 和 `y` 这两个成员变量(field)的平方和。 +局部变量只在当前的方法内部访问, +一般用来保存中间计算结果。 +这个方法还使用 `Math.sqrt()` 方法。 +它是 `Math` 类中的 static(静态)方法, +作用是返回传入参数的平方根。 + + + +`Point` 现在可以完成这些数学运算了。 +下面是完整的类: + +```java {point}#pointFull + +``` + +下面是使用它的示例: + +```java {usage}#createAndUse + +``` + +`a.plus(b)` 的含义是“在对象 `a` 上调用 `plus` 方法,并把 `b` 作为实参传入”。 +在 `plus` 方法内部,`a` 就对应关键字 `this`,`b` 对应参数 `other`。 + +## Mutable State(可变状态) + +我们定义的 `Point` 类是 immutable(不可变) 的。这意味着 `Point` 对象一旦被创建, +它的 `x` 和 `y` 值就永远不能改变,因为这些成员变量(field)用了 `final` 修饰。 +不可变类型有很多好处,因为它让类更容易理解:不需要担心成员变量(field)的值会在意外的时候被改变。 + +不过,有时我们需要一个状态会随时间变化的类。接下来我们定义一个 `RobotTracker` 类,用来跟踪机器人在场地上的当前位置。 +位置从某个地方开始,并随着机器人移动而更新,因此它不能是 `final`: + +```java {tracker}#mutableField + +``` + +注意,成员变量(field)的类型不一定非得是 primitive type(基本类型),例如 `double`。 +就像 `x` 和 `y` 是 `double` 类型的成员变量(field)一样,`position` 也是一个成员变量(field),它的类型是我们自己写的 `Point` 类。 +任何类型,无论是 Java 内置的,还是我们自己定义的,都可以用作成员变量(field)的类型。 + +我们仍然使用 `private`,防止其他类直接修改这个成员变量(field), +因此改变位置的唯一方式是通过我们定义的方法。 +这很重要,因为 `move`、`distanceTo` 和 `reset` 可以依赖 `position` 始终有效, +而不用担心另一个类把它设成意外的值。 +如果其他代码可以直接修改 `position` 的值,`RobotTracker` 就无法保证自己的行为。 + +构造函数的工作方式与之前相同,把 `position` 初始化为给定的起始位置值: + +```java {tracker}#robotTrackerConstructor + +``` + +只要每个构造函数接收的参数组合不同,一个类就可以有多个构造函数。 +当存在合理的默认值时,这个特性非常好用:在这里不带参数的 `RobotTracker` 会默认从原点开始。 +我们不用重复写 `this.position = Point.ORIGIN`,而是使用 `this(...)` 的方法来调用另一个构造函数: + +```java {tracker}#constructorChaining + +``` + +调用 `move` 方法时,传入一个 `delta`,也就是位置的变化量,这个方法会把它加到当前位置上。 +这就是 mutation(可变修改),方法对 `this.position` 重新赋值,改变了这个对象的状态。 + +```java {tracker}#stateMutation + +``` + +注意,`move` 方法的返回类型是 `void`。 +这是因为该方法实际上不需要返回任何内容。 +它改变的是自己的内部状态。 + +`distanceTo` 方法用来计算从当前位置到目标的直线距离。 +它使用一个局部变量 `diff` 保存两点之间的向量,然后再取这个向量的长度: + +```java {tracker}#localVariableChain + +``` + +因为 `position` 是私有(private)的,所以我们还需要一个 getter 方法来读取机器人的当前位置: + +```java {tracker}#getPosition + +``` + +`reset` 方法把位置设回原点。 +我们不用写 `new Point(0, 0)`,而是复用 `Point.ORIGIN` 常量。 +两种写法都可以,因为 `new Point(0, 0)` 和 `Point.ORIGIN` 的坐标相同。 +但复用 `ORIGIN` 可以避免创建一个多余的对象,也让代码的意图更清楚: +我们明确指的是原点本身,而不是一个碰巧位于 (0, 0) 的点: + +```java {tracker}#useStaticConstant + +``` + +下面是完整的 `RobotTracker` 类: + +```java {tracker}#robotTrackerFull + +``` + +下面是使用它的示例: + +```java {usage}#trackerUsage + +``` + + + +## 方法与可变状态练习 + + diff --git a/src/content/docs/zh-cn/learning-course/stage0/classes-objects.mdx b/src/content/docs/zh-cn/learning-course/stage0/classes-objects.mdx new file mode 100644 index 00000000..0a7bc8fe --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/classes-objects.mdx @@ -0,0 +1,201 @@ +--- +title: Classes and Objects(类与对象) +description: 介绍 Java 的类和对象,以及如何为它们添加字段和构造函数 +codeRegionSources: + point: stage0/snippets/src/classes-methods/Point.java + usage: stage0/snippets/src/classes-methods/Usage.java +--- + +在[前面的课程](/learning-course/stage0/java-fundamentals/)中,我们讨论了 Java 的不同数据类型, +例如 `int`、`double` 和 `String`。 +本节课我们将学习**类**。类可以用来创建我们自己的数据类型,以及如何用类创建**对象**。 +我们还会定义类的两个组成部分:fields(字段)和 constructors(构造函数)。 + +## 什么是对象 + +在开始编写代码之前,需要先弄清楚对象是什么。 +到目前为止,我们编写的代码都是从运行程序的计算机这一方来描述的。 +例如,`System.out.println("hello");` 读起来像一条指令:“让计算机输出 hello。” + +类让我们从另一个角度编写代码,这种方式有时称为 object-oriented programming(面向对象编程)。 +程序不再由一个执行者完成所有事情,而是由许多对象组成。每个对象都有自己的**状态**和**行为**, +类似于戏剧中的角色。 +角色有服装、名字等属性,也有唱歌、说台词等能力。 +在代码中,对象的属性保存在**字段**中,对象的能力由**方法**定义。 + +角色的有些属性在整场戏剧中保持不变,例如名字; +另一些则会随场景变化,例如服装,或者他们站在舞台上的位置。 +字段也是如此:有些设定一次后就不再改变,我们称之为 immutable(不可变); +另一些则会随时间变化,我们称之为 mutable(可变)。 +字段应该可变还是不可变,是设计类时需要做出的选择。 +在本节课和下一课中,我们都会看到这两类例子。 + +在 FRC 代码中,对象通常比戏剧角色更抽象。 +`Point` 表示一个位置,`Motor` 表示一台实体电机,等等。 +对象也可以只是把相关数据和行为组织在一起的一种方式, +所以它并不一定对应现实中的某个物体。 + +## 类型的种类 + +Java 有两类类型:primitive types(基本类型)和 reference types(引用类型): + +- **基本类型**是语言内置的最基础类型,例如 `int`、`double` 和 `boolean`。 +- **引用类型**(也称为对象类型)是由程序员定义的更复杂类型。 + 许多引用类型内置在 Java Development Kit(Java 开发工具包,JDK)中,例如 `String`。 + 另一些来自外部库,但你也可以定义自己的对象类型。 + +类是定义对象类型的一种方式。 +**类**是创建对象的模板, +用字段和方法定义对象的状态和行为。 +**对象**是类的实例。 +例如,`"a"` 和 `"b"` 都是 `String` 类的对象。 + + + +## 定义自己的类 + +现在我们已经知道类是什么,接下来定义自己的类。 +第一个示例是一个简单的 `Point` 类,表示二维空间中的一个点。 +它可以帮助我们编写代码,让机器人知道自己在场地上的位置。 + +在 Java 中,每个类都定义在一个 `.java` 文件中,并以 `public class` 关键字开头。 +`public` 关键字表示这个类可以在程序的任何地方访问,包括其他文件。 +在本节课后面,我们会再次看到 `public`。那时它用于字段和构造函数,而不是整个类。 +`class` 关键字告诉 Java 我们正在定义一个新类,后面跟着我们给它的名称 `Point`。 + +创建一个名为 `Point.java` 的新文件,并这样开始: + +{/* rli:ignore */} + +```java +public class Point { + +} +``` + +接下来添加三个字段。 +**字段**是属于类的变量,用于保存对象状态的一部分。 +每个 `Point` 对象都有自己的一份字段副本,因此两个不同的 `Point` 对象可以有不同的坐标。 +首先添加两个 `double` 字段 `x` 和 `y`,表示点的坐标。 +每个 `Point` 对象的 `x` 和 `y` 都可以不同。 + +```java {point}#finalFields + +``` + +注意,我们使用 `final` 关键字使这些字段**不可变**。 +这意味着它们一旦创建,值就不能再改变。 +通常情况下,除非有明确的理由,否则应该把所有字段都设为 `final`。 +在下一课中,我们会看到这样的理由。 +我们还使用 `private` 关键字使这些字段成为**私有**字段。 +这意味着只有同一个类中的方法才能访问它们。 + +到目前为止,`x` 和 `y` 字段都属于某一个具体的 `Point` 对象。 +不过,有时我们想要一个由该类的所有对象共享的值,而不是每个对象各有一份。 +接下来添加一个静态常量字段 `ORIGIN`,表示原点 (0, 0)。 + +```java {point}#staticConstant + +``` + +`static` 关键字用于创建**静态**字段。 +静态字段由类的所有实例共享。 +在这里,`ORIGIN` 字段由所有 `Point` 对象共享,表示原点 (0, 0)。 +静态字段和方法通过类名访问,而不是通过类的实例访问, +因此 `ORIGIN` 字段通过 `Point.ORIGIN` 访问。 +`ORIGIN` 字段也是 `public` 的,所以程序的任何地方都可以访问它。 +因为它既是 `static` 又是 `final`,所以 `ORIGIN` 是一个 constant(常量)。 +这也是它的名字使用大写下划线命名法的原因,[Java基础](/learning-course/stage0/java-fundamentals/#名称)中介绍过这种写法。 + + + +## Constructors(构造函数) + +目前还没有任何代码给 `x` 和 `y` 赋上值。 +这是**构造函数**要完成的工作:使用 `new` 关键字创建对象时执行的一段特殊代码。 +它通常用来给对象的字段设置初始值。 + +构造函数是一种**方法**。方法是属于类的、有名字的代码块,用来定义类行为的一部分。 +我们将在[下一课](/learning-course/stage0/classes-methods/)中自己编写方法。 +目前只需要构造函数,以及用来读取字段值的简单方法。 + +定义构造函数时,先写 `public` 关键字, +然后写类名,再在圆括号中写参数列表。 +**参数**是调用构造函数时传入的值。 + +```java {point}#constructor + +``` + +这样就可以用 `new` 关键字创建新的 `Point` 对象。 +例如,点 (1, 1) 可以用 `new Point(1, 1)` 创建。 +`new Point (1, 1)` 是 `Point` 类的一个对象。 + +这里需要注意 `this` 关键字。 +`this` 用来表示当前对象。 +它用于访问对象的字段和方法。 +在这个示例中,我们用 `this` 访问当前对象的 `x` 和 `y` 字段, +并把它们设为参数 `x` 和 `y` 的值。 +这里必须使用 `this`,因为参数名和字段名相同。 +所以 `this.x` 指字段 `x`,而 `x` 指参数 `x`。 +`this` 并不总是必需的:如果方法参数的名称与它们要设置的字段不同, +Java 已经能够区分它们,就可以省略 `this`。 + +## Getter(获取器) + +接下来我们要定义一些 getter 方法。getter 方法的作用很简单:获取并返回一个成员变量(field)的值。它的返回值类型和对应的成员变量一致,并且不带参数。 +由于 `x` 和 `y` 这两个成员变量是私有(private)的,我们必须编写 getter 方法,才能让其他类访问到它们: + +```java {point}#getters + +``` + +getter 方法是最简单的一种方法。 +方法名前面的 `double` 是它的**返回类型**,也就是方法返回给调用者的值的类型。 +`return` 关键字负责把这个值返回回去。 +可以看到,getter 方法按照惯例以 `get` 开头,后面跟着成员变量(field)的名称。 + +## 创建和使用对象 + +有了构造函数和 getter 方法之后,`Point` 就是一个可以实际使用的类型: + +```java {usage}#createPoint + +``` + +我们使用 `new` 关键字和构造函数创建新的 `Point` 对象。 +这会执行刚才编写的构造函数,并返回一个新的对象。 +然后使用点运算符(`.`)调用对象的方法: +`a.getX()` 的意思是“在对象 `a` 上调用 `getX` 方法”。 +在这次调用中,`a` 就是 `this` 所指的对象。 +任何方法调用都使用同样的形式:`object.method(arguments)`。 + +也可以直接在类上访问 `ORIGIN` 常量,而不创建实例: + +```java {usage}#accessStaticConstant + +``` + +## 类与对象练习 + + diff --git a/src/content/docs/zh-cn/learning-course/stage0/conditionals.mdx b/src/content/docs/zh-cn/learning-course/stage0/conditionals.mdx new file mode 100644 index 00000000..fa1807bd --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/conditionals.mdx @@ -0,0 +1,139 @@ +--- +title: Conditionals(条件语句) +description: 介绍 Java 中的 if、else 和 else if 语句 +codeRegionSources: + default: stage0/snippets/src/Conditionals.java +--- + +在[上一课](/learning-course/stage0/operators/)中,我们用比较运算符和逻辑运算符得到 `true` 或 `false`。 +本节课我们将利用这些值,通过**条件语句**实现程序判断:只有当条件成立(`true`)时,才执行对应的代码块。 + +## 为什么需要条件语句? + +日常生活中我们一直在做决定,例如“如果饿了,就去吃点零食”。 +机器人也会做决定,例如“如果机器人离墙很远,就向前行驶”,或者“如果比赛计时到 0,就停止行驶”。 +到目前为止,我们写的每一行代码每次都会运行。 +条件语句让我们可以根据传感器读数或比赛剩余时间这样的值,选择运行哪一段代码。 + +我们会介绍三种条件语句:`if`、`else` 和 `else if`。 + + + +## `if` 语句 + +**`if` 语句**只在条件为 true 时运行一段代码: + +```java #ifSyntax + +``` + +`if` 语句有三个部分: + +- `if` 关键字 +- 圆括号里的条件,结果为 true 或 false,例如 `6 > 2` +- **代码块**,也就是花括号 `{` 和 `}` 之间的代码 + +如果条件为 true,代码块就会运行。 +如果为 false,代码块会被跳过,程序继续执行它后面的代码。 + +下面的示例只有当机器人离墙壁超过 10 厘米时,才让机器人向前行驶: + +```java #ifExample + +``` + +`distance` 是 60,所以条件 `distance > 10` 为 true。 +代码块会运行,打印 `Driving at full speed`,并把传动系统设置为全速。 +如果 `distance` 是 5,条件就为 false,不会打印任何内容。 + + + +## 加上 `else` + +单独的 `if` 语句在条件为 false 时不会执行任何操作。 +但很多时候,我们希望在这种情况下执行其他动作,比如机器人靠近墙壁时停下。 +**`else`** 代码块只在 `if` 语句的条件为 false 时运行: + +```java #ifElseSyntax + +``` + +`else` 本身不带判断条件。 +它负责处理所有 `if` 没有匹配到的情况。 +这两段代码块**只会执行其中一个,不会同时运行**。 + +现在机器人就可以实现:离墙较远时前进,靠近墙壁时停下。 + +```java #elseExample + +``` + +不运行代码,判断它会输出什么? + + + +## 加上 `else if` + +`if` 和 `else` 只能在两种情况里做选择,但有时我们需要更多分支。 +**`else if`** 可以增加新的判断条件,只有它前面所有条件都 false 时,才会检查这个条件: + +```java #elseIfSyntax + +``` + +Java 会**从上往下依次判断条件**,执行第一个结果为 true 所对应的代码块。 +只要某一段代码块执行了,后面所有条件都会直接跳过,哪怕后面的条件同样成立。 +如果所有条件都不成立,就不会执行任何代码块。 + +我们用 `else if` 实现让机器人在靠近墙壁时减速: + +```java #elseIfExample + +``` + +`distance` 为 30,所以第一个条件 `distance > 50` 为 false,Java 继续判断 `else if`。 +`distance > 10` 为 true,所以机器人以半速行驶。 + +条件的判断顺序在这里非常关键。 +`distance` 为 60 时,它同时满足大于 50 和大于 10,但只会执行第一个匹配到的代码块,所以机器人全速行驶。 +如果我们先检查 `distance > 10`,那么即便机器人离墙很远,也只会以半速行驶。 + +## 综合运用 + +刚才的例子里,一旦机器人距离墙壁小于等于 10 厘米,程序就不会执行任何分支,电机会保持在上一次设定的转速继续运转。 +我们可以在末尾加上一个 `else` 来解决这个问题,它用来处理剩下所有的情况: + +```java #conditionalExample + +``` + +不运行代码,判断它会输出什么? +注意,这一题有点容易踩坑。 + + + +你可以在 `if` 和末尾的 `else` 之间按需串联任意多个 `else if`,并且最后的这个 `else` 也可以省略。 + +## 条件语句练习 + + diff --git a/src/content/docs/zh-cn/learning-course/stage0/debugging.mdx b/src/content/docs/zh-cn/learning-course/stage0/debugging.mdx new file mode 100644 index 00000000..8f79e38d --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/debugging.mdx @@ -0,0 +1,167 @@ +--- +title: Debugging(调试) +description: 介绍语法错误、运行时错误和逻辑错误,以及如何找到并修复它们 +codeRegionSources: + default: stage0/snippets/src/Debugging.java +--- + +在[上一课](/learning-course/stage0/java-fundamentals/)中,你写出了第一段 Java 代码,过程中可能也遇到了一些错误。 +如果第一次就全部成功,那要么是你非常幸运,要么是还没有充分测试。 +找出并修复这些错误称为 debugging(调试),以后你会经常做这件事。 +这一课介绍你会遇到的三种错误,以及如何分别追查它们。 + + + +## 错误类型 + +错误主要有三种,每种出现的时间不同: + + + + + + + + + + + + + + + + + + + + + + + + + + +
名称发生节点现象
Syntax error(语法错误)编译时编译器会显示错误信息,代码也无法完成构建
Runtime error(运行时错误)运行时程序会崩溃并显示错误信息。在机器人上,代码随后会重新启动
Logic error(逻辑错误)运行时代码能够运行,但不会按你的意图工作
+ +## Syntax Errors(语法错误) + +代码运行之前,一个称为 compiler(编译器)的程序会把它翻译成计算机能够理解的指令。 +就像英语有语法规则一样,Java 也有 syntax(语法)规则,用来确保编译器能理解你写的代码。 +如果代码违反了其中一条规则,编译器不会构建代码,而是给出一个 syntax error(语法错误)。 +这发生在编译器翻译代码的时候,代码实际上还没有运行,所以我们说它发生在 compile time(编译时)。 + +VS Code 会在它认为写错的代码下方显示红线,就像 Google Docs 标出拼写错误一样。 +例如,把 `System` 拼错就是语法错误: + + + +漏掉语句的一部分也是语法错误,例如变量声明里缺少等号: + + + +把鼠标悬停在红线上,VS Code 会告诉你更多关于这个错误的信息。 +例如,如果变量名拼错了,悬停时会显示 `numer cannot be resolved to a variable`。 +这表示编译器正在寻找名为 `numer` 的变量,但找不到。 +严格来说,这不是语法错误,因为代码符合 Java 的语法规则,但编译器仍会在编译时发现它,并用同样的方式显示出来。 + + + + + +构建代码时,同样的错误信息也会出现在终端中。 +真正的错误不一定就在编译器指出的那一行。 +某一行缺少分号或花括号,可能会让后面几行才报错,所以在动手修改之前,也要看一看错误附近的几行。 + +## Runtime Errors(运行时错误) + +编译器抓不住所有错误。 +有些代码符合 Java 的全部规则,问题只有在代码运行时才会出现。 +这些称为 runtime errors(运行时错误)。 +一种常见的运行时错误是除以零: + +{/* rli:ignore */} + +```java +int answer = 10 / 0; +System.out.println(answer); +``` + +这段代码能够通过编译,但运行时会崩溃,并出现类似这样的错误: + +{/* rli:ignore */} + +``` +Exception in thread "main" java.lang.ArithmeticException: / by zero + at Main.main(Main.java:5) +``` + +这条信息称为 stack trace(堆栈跟踪),因为它会把错误追溯到程序中发生问题的位置。 +第一行说明出了什么问题。 +`ArithmeticException` 是数学错误,`/ by zero` 说明具体是哪一种。 +第二行说明发生在哪里,`(Main.java:5)` 表示 `Main.java` 的第 5 行。 +要修复它,就修改代码,让它永远不会除以零。 + +## Logic Errors(逻辑错误) + +有时代码能够通过编译,运行时也不会崩溃,但没有做出你想要的结果。 +这是 logic error(逻辑错误)。 +计算机不知道你的本意,它只按代码所说的去做,即使那是错的。 + +下面这段代码本应计算一个宽为 2、高为 5 的矩形面积。 +不运行它的话,你能找出错误吗? + +```java #logicError1 + +``` + + + +逻辑错误没有错误信息,所以必须自己找。 +通读代码,检查它实际写的内容是否和你的本意一致。 +在程序的不同位置用 `System.out.println()` 打印数值也会有帮助。 +第一个打印出你没预料到的值的位置,大致就是出错的地方。 +真实的逻辑错误通常比这个例子更难发现。 + +## 避免和修复错误的建议 + +你不可能写出完全没有错误的代码,但可以让错误更少出现,也更容易找到: + +- 使用能说明变量保存内容的变量名。 +- 把复杂的代码拆成更小、更简单的部分。 +- 让代码易于阅读。 +- 每写完一小段代码就测试一次。 + 不要等全部写完再一次测试。 + +遇到错误时,要仔细阅读错误信息。 +如果卡住了,就在网上搜索这条错误信息。 +很可能已经有人遇到过同样的问题。 + +VS Code 也有一些无障碍功能,可以让代码更容易阅读和调试。 +例如,有些人觉得换一种字体(比如 Comic Sans)更容易发现错误。 diff --git a/src/content/docs/zh-cn/learning-course/stage0/git.mdx b/src/content/docs/zh-cn/learning-course/stage0/git.mdx new file mode 100644 index 00000000..6398d23e --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/git.mdx @@ -0,0 +1,57 @@ +--- +title: Git - Uploading code(上传代码) +description: Git 入门 +--- + +在前面的课程中,你已经编写了第一段 Java 代码,学习了一些调试方法,并完成了练习。 +现在需要把这些修改 push(推送)到 GitHub。 + +Git 是一种版本控制系统。它通过命令记录代码的修改。 +本课程会用到两个命令:commit(提交)和 push(推送)。 + + + +## Commit(提交) + +commit(提交)是仓库的一份已保存状态。 +每完成一项任务,例如做完一个练习,都应该做一次 commit。 +每次 commit 都要写一段说明,这段说明称为 message(提交说明)。 +提交说明应当能说清这次改了什么,并且不要太长。 +例如,完成 Print.java 练习后,提交说明可以写成 "Completed Print.java"。 + +## Push(推送)与 Pull(拉取) + +push(推送)会把你电脑上的 commit 上传到远程仓库,也就是你 GitHub 账号上的仓库。 +上传到 GitHub 时,要先 commit,再 push。 + +pull(拉取)会把远程仓库中的修改下载到本地仓库,也就是你电脑上的仓库。 + +VS Code 里的 Git 不直接使用 push 这个名称,而是使用 "Sync Changes"(同步更改)。它会在 push 你的修改的同时,pull 远程的新修改。 + +## 上传代码 + +完成练习后,先保存代码,然后打开 VS Code 左侧的 Source Control(源代码管理)。 + + + +这时会打开一个新的侧栏。 +在这里可以看到哪些文件被修改了,以及具体改了什么。 +也可以在这里把修改 commit 并 push 到 GitHub。 + +在本阶段,每完成一个练习,建议按图中的方式把修改 commit 并 push 到你的仓库。 + + + +在本课程的其他阶段,建议在完成一项任务后,或准备停下来时,再 push 代码。 +这样可以避免进度丢失。 diff --git a/src/content/docs/zh-cn/learning-course/stage0/interfaces-lists.mdx b/src/content/docs/zh-cn/learning-course/stage0/interfaces-lists.mdx new file mode 100644 index 00000000..efeaf072 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/interfaces-lists.mdx @@ -0,0 +1,245 @@ +--- +title: Interfaces, Generics, and Lists(接口、泛型与列表) +description: 介绍 Java 中的接口、泛型方法,以及 List 接口 +codeRegionSources: + sensor: stage0/snippets/src/interfaces-lists/DistanceSensor.java + ultrasonic: stage0/snippets/src/interfaces-lists/UltrasonicSensor.java + lidar: stage0/snippets/src/interfaces-lists/LidarSensor.java + tracker: stage0/snippets/src/interfaces-lists/RobotHistoryTracker.java + usage: stage0/snippets/src/interfaces-lists/InterfacesListsUsage.java + pair: stage0/snippets/src/interfaces-lists/Pair.java +--- + +import { FileTree } from '@astrojs/starlight/components'; + +在前面的课程中,我们编写了 `Point` 和 `RobotTracker` 这样的类,用来表示程序中的事物。 +本节课我们将学习 interfaces(接口)。接口用来描述一个类能做什么,而不规定具体实现方式。 +还要学习 generics(泛型),让同一段代码可以用于不止一种类型。 +以及 List 接口,它比数组更灵活。 + +## 为什么使用接口? + +假设机器人需要在 autonomous(自动模式)期间测量自己与墙壁的距离。 +某一年,队伍可能使用超声波传感器;下一年,可能换成激光测距传感器。 +两者都测量距离,但它们是不同的硬件,由不同的代码控制。 + +如果机器人的其余代码只能配合某一个具体的传感器类工作,更换硬件就意味着重写所有使用它的代码。 +接口可以描述传感器能做什么,而不指明它具体是哪一种传感器,这样就解决了这个问题: + +```java {sensor}#distanceSensorInterface + +``` + +接口看起来和类很像,但它的方法没有方法体,只有一个以分号结尾的 signature(方法签名)。 +它相当于是一份约定:任何 implements(实现) `DistanceSensor` 的类都必须提供 `getDistanceMeters()` 方法。 + +下面是两个各自以不同方式实现这份约定的传感器类: + +```java {ultrasonic}#ultrasonicSensorClass + +``` + +```java {lidar}#lidarSensorClass + +``` + +这里使用 `implements` 关键字告诉 Java:`UltrasonicSensor` 和 `LidarSensor` 实现了 `DistanceSensor`。 +如果我们写了 `implements DistanceSensor`,却没有添加 `getDistanceMeters()` 方法,编译器就会报错。 +我们还使用 `@Override` 注解告诉编译器,我们正在 overriding(重写) `getDistanceMeters()` 方法。 +重写方法是改变父类或接口中已有方法行为的一种方式。 + + + +因为这两个类都实现了 `DistanceSensor`,只依赖 `DistanceSensor` 的代码就可以使用其中任意一个传感器, +例如这个用来判断我们是否离物体太近的方法: + +```java {usage}#isTooClose + +``` + +这里,`isTooClose` 的形参类型是 `DistanceSensor`,所以传入 `UltrasonicSensor` 还是 `LidarSensor` 都没有关系: + +```java {usage}#distanceSensorCall + +``` + +如果队伍在下个赛季更换传感器,这个方法完全不需要修改,因为它只依赖 `DistanceSensor` 接口。 + +## 泛型方法 + +接口让一段代码可以用于几种相关的类型。 +泛型更进一步,让一个方法可以用于任何类型。 +下面这个方法返回数组的最后一个元素,无论数组里是什么类型: + +```java {usage}#genericLast + +``` + +返回类型前面的 `` 引入一个 type parameter(类型参数),它是一个占位符,直到方法被调用时才确定具体类型。 +在方法内部,`T` 像真实类型一样使用:参数的类型是 `T[]`,返回类型是 `T`。 + +我们可以用完全不同的数组类型调用 `last`,两种情况都可以正常执行: + +```java {usage}#genericLastCall + +``` + +调用 `last(path)` 时,Java 把 `T` 填成 `Point`;调用 `last(sensors)` 时,Java 把 `T` 填成 `DistanceSensor`。 +我们不需要为每种类型单独写一个方法。 + +类或接口也可以是泛型的。 +例如,如果想从一个方法返回两个值,可以编写一个 `Pair` 类: + +```java {pair}#pairClass + +``` + +这样我们就可以创建存放任意两种类型的 `Pair` 对象: + +```java {usage}#pairCall + +``` + +## 包与导入 + +到目前为止,我们编写的每个类都没有制定包,所以各个类之间可以直接使用,完全不需要 `import` 语句。 +不过,大多数真实项目会把许多类组织到不同目录中, +因此我们需要一种方式,告诉 Java 去哪里找到这些类,以及它们依赖的类。 +Java Development Kit(Java 开发工具包,JDK)内置的类、WPILib 等第三方库中的类, +以及你自己的机器人项目中的类,都被组织成 packages(包):包就是一组相关类的命名集合。 + +包名与类所在的目录对应,用 `.` 代替斜杠。 +`ArrayList` 位于 JDK 的 `java/util/` 目录中,所以它的包是 `java.util`,完整名称是 `java.util.ArrayList`。 +机器人代码遵循同样的规则,通常位于 `first.robot` 包中。开始本课程的阶段 1 时就会看到: + +{/* prettier-ignore */} + +- src/main/java + - first + - robot + - Robot.java + - subsystems + - Drivetrain.java + - Main.java + + +包中的每个文件都在第一行声明自己属于哪个包。 +`Drivetrain.java` 位于 `first/robot/subsystems/`,所以它这样开头: + +{/* rli: ignore */} + +```java +package first.robot.subsystems; +``` + +同一个包中的类可以直接互相使用。 +要使用另一个包中的类,需要在 package 声明下面写一条 `import` 语句,并写明要使用的具体类: + +```java {usage}#importList + +``` + +这会从 `java.util` 包导入 `ArrayList` 和 `List`,接下来就会用到它们。 +课程后面,你也会用同样的方式从 WPILib 包导入类,例如 `org.wpilib.math.geometry.Translation2d`。 + + + +## `List` 接口与 `ArrayList` + +在[上一课](/learning-course/stage0/arrays/)中,我们使用数组把多个值存放在一起。 +数组一旦创建,大小就固定了。当你确切知道需要多少个元素时,例如一条固定的自动路径,这种方式很合适。 +但有些场景下,你事先不知道需要多少元素。例如,你想在整场比赛期间,每当机器人移动就记录一次它的位置。 + +`List` 是一个接口,和 `DistanceSensor` 一样。它描述一个可以增大和缩小的 collection(集合)。 +`ArrayList` 是实现 `List` 的类: + +```java {usage}#historyList + +``` + +就像 `DistanceSensor sensor = new UltrasonicSensor();` 一样,声明的类型(`List`)是接口,而我们创建的对象(`new ArrayList<>()`)是它的一种具体实现。 +`` 告诉 Java,这个 `List` 保存的是 `Point`。`List` 本身是泛型的,和 `Pair` 一样。 + +`List` 没有固定长度。你可以随时向它添加元素,它会自动扩容来容纳新数据: + +```java {usage}#historyAdd + +``` + + + +如果一开始就知道每一个元素,例如上一课里那条固定的自动路径,`List.of` 可以用一行代码创建包含这些元素的 `List`: + +```java {usage}#listOf + +``` + +`List.of` 返回一个 `List`,但不会告诉你是哪个类实现了它。 +它返回的对象并不是 `ArrayList`。 +这里它是 `ImmutableCollections$ListN`。这是 JDK 内部的一个类,类名不需要自己输入。 +这样是可以的,因为我们只通过 `List` 接口使用它,就像 `isTooClose` 只使用 `DistanceSensor` 接口一样。 + +### `List` 的可变性 + +在[前面的课程](/learning-course/stage0/classes-methods/)中,我们看到有些类是 mutable(可变)的,也就是创建之后状态还可以改变,有些类则是 immutable(不可变)的。 +列表也是如此。 +因为 `List` 是接口,某个列表能不能改变,取决于实现它的是哪个类: + +- `ArrayList` 是 mutable(可变)的,所以可以随时添加、删除和替换元素。 +- `List.of` 创建的列表是 immutable(不可变)的。 + 它的元素在创建时就固定了,对它调用 `add` 或 `remove` 会抛出 `UnsupportedOperationException`。 + +编译器无法区分这两者,因为它们都被声明为 `List`。 +只有程序运行、`add` 抛出异常时,才会出现这个错误。 + +不能把 immutable(不可变)列表变成 mutable(可变)列表,但可以基于它创建一份可变副本。 +把已有的列表传给 `new ArrayList<>(...)`,就能生成一个初始元素和原列表完全一致的新 `ArrayList`: + +```java {usage}#copyList + +``` + +`editablePath` 是和 `autoPath` 两个相互独立的对象,往 `editablePath` 里新增路径点,不会改变 `autoPath`。 + +## 给 `RobotTracker` 增加历史记录功能 + +[上一课](/learning-course/stage0/classes-methods/)中的 `RobotTracker` 会跟踪机器人的当前位置,但无法记录走过的轨迹。 +。现在我们借助 `List`,让它记录机器人到访过的每一个坐标,而不只是当前位置: + +```java {tracker}#robotHistoryTrackerClass + +``` + + + +`getHistory()` 按顺序返回 `move` 曾经移动到的每个位置组成的 `List`。 +我们可以用 for-each 循环遍历它,方式和上一课遍历数组相同: + +```java {usage}#forEachHistory + +``` + +即使每次调用 `move` 时 `history` 的元素都会增加,for-each 循环也不需要事先知道将要访问多少个位置。它会依次访问其中的每一个位置。 + +## 接口、泛型与列表练习 + + diff --git a/src/content/docs/zh-cn/learning-course/stage0/java-fundamentals.mdx b/src/content/docs/zh-cn/learning-course/stage0/java-fundamentals.mdx new file mode 100644 index 00000000..e40518d4 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/java-fundamentals.mdx @@ -0,0 +1,278 @@ +--- +title: Java基础 +description: 介绍 Java 语法、变量、打印语句和注释 +codeRegionSources: + default: stage0/snippets/src/JavaFundamentals.java +--- + +欢迎来到阶段 0 的第一课! +我们会先学习编写 Java 代码的规则,然后学习如何用变量保存信息、用打印语句显示信息,以及用注释说明代码。 + +## Syntax(语法) + +Syntax(语法)是一门编程语言中所有代码都必须遵守的规则。 +可以把它理解成语法规则,就像英文句子以大写字母开头、以句号结尾一样,Java +代码也有类似的规则,并且 Java 对这些规则要求严格,违反规则的代码完全无法运行。 +这类错误称为 syntax errors(语法错误),也是你最常遇到的错误。 +每当本课程介绍一种新语法时,都要仔细留意。 + +## Variables(变量) + +Variable(变量)是程序中有名字的容器,用来保存一条信息。 +变量可以保存温度、电机速度,或储球仓里的球数。 + +**声明**一个变量,也就是创建它,需要五个部分: + +1. 数据类型 +2. 变量名 +3. 等号 +4. 变量值 +5. 分号 + +下面逐一说明。 + +### Data Types(数据类型) + +变量的 data type(数据类型)告诉 Java 这个变量保存哪种值,以及你可以对它做什么。 +与 Python 等某些编程语言不同,Java 要求每个变量都必须有类型。 +编译器会检查这些类型,所以如果你试图把 `"fast"` 存进一个 `int` 变量,代码还没运行就会报错。 + +FRC 编程中常用的一些数据类型包括: + +- `int`:整数,可以是正数或负数,没有小数部分。 + 示例:`12` +- `double`:可以带小数的正数或负数。 + 示例:`34.1` +- `boolean`:值为 `true` 或 `false`。 +- `String`:一串字符,用双引号(`"`)括起来。 + 示例:`"Hello World"` + + + +### 名称 + +变量名可以包含字母、数字和下划线(`_`),但不能以数字开头。 +除此之外,变量的命名由你决定,只要它能说明这个变量保存的是什么。 +有描述性的名字会让你自己和帮助你的人更容易看懂代码。 + +Java 代码常用两种命名风格: + +- Camel case(驼峰命名法) + :除第一个单词外,其余每个单词的首字母大写,例如 + `frontLeftDrive`、`currentTemperature` 和 `targetPosition`。 + 大多数变量使用驼峰命名法。 +- Upper snake case(大写下划线命名法) + :每个字母都大写,单词之间用下划线分隔,例如 `BACK_LEFT_DRIVE` 和 + `GEAR_RATIO`。 这种写法用于 constants(常量) + ,也就是整个程序共用的固定值,例如齿轮比。 + + + +给变量起名字是编程里有趣的部分之一,Java 也完全允许你把变量命名为 `bingo`。 +不过阅读你代码的队友就不会那么高兴了,因为 `bingo` 看不出这个变量保存的是什么。 +以拾取机构的电机为例,下面列出一些不合适和合适的变量名: + + + + + + + + + + + + + + + + + + + + + + + + + + +
不合适的变量名合适的变量名
+ `f` 避免使用过短或只有一个字母的变量名。 + 这样的名称没有描述性,无法清楚说明变量的用途。 + `frontLeftMotor` 清楚说明了变量的用途,并且易于阅读。
`bingo` 避免使用随意的单词。这样很难知道变量的用途。`backLeftMotor` 变量名易于阅读,并且具有描述性。
`motor` 机器人上有许多电机。这样无法说明这台电机的用途。 + `intakeMotor` 加入机构名称后,就能清楚表明这台电机属于拾取机构。 +
+ `intakeMotorForTheTopIntakeRoller` + 描述过多,名称也太长,因此难以阅读。 + `intakeTopMotor` 变量名更短,但仍能清楚说明用途。
+ +### 分号 + +在 Java 中,分号(`;`)用来标记一条 statement(语句)的结束。语句就是一条指令,就像句号用来结束一个句子。 +Java 不关心 whitespace(空白),例如空格、换行和缩进;空白多一点或少一点,并不会改变代码的作用。 +Java 用分号来区分各条语句,所以漏写分号通常会导致错误。 + +### 合在一起 + +下面是四个变量,每个都使用不同的数据类型: + +```java #variables + +``` + +## Print Statements(打印语句) + + + +**打印语句**会在终端中显示信息。 +它可以把信息显示给正在运行程序的人,也可以用来检查代码在做什么,例如电机被要求以多快的速度旋转。 +Java 中的打印语句如下所示: + +```java #printLiteral + +``` + +这会把文本 `hello!` 打印到终端。 +`"hello!"` 两侧的引号告诉 Java 把它当作文本,按原样处理。 +如果没有引号,Java 会把 `hello!` 当作代码,并且报错,因为它不是一个有效的变量名或值。 + + + +要打印变量的值,去掉引号,使用变量名即可: + +```java #printVariable + +``` + +### 组合文字和值 + +当程序打印很多信息时,给每个值加上标签会更清楚。 +可以用 `+` 运算符把一个 `String` 和一个值组合起来,这称为 **字符串拼接**: + +```java #stringConcatenation1 + +``` + +这会打印 `Left Motor Speed 1.0`。 +因为 `speed` 是 `double`,即使我们写成 `1`,打印出来也会带小数点。 +注意 `"Left Motor Speed "` 末尾的空格。 +如果没有这个空格,打印结果会是 `Left Motor Speed1.0`。 + +也可以在一条打印语句里组合多个值。 +不运行代码的话,下面这行会打印出什么? + +```java #stringConcatenation2 + +``` + + + +## Comments(注释) + +Comment(注释) +是写在代码里的笔记,是写给人看的,不是写给计算机的。 +注释用来说明为什么要写这段代码,既给你的队友看,也给几周后的你自己看。 +编译器会忽略注释,因此也可以把代码变成注释,让它不再运行。 + +Java 中的注释有两种:单行注释和多行注释。 + +### 单行注释 + +**单行注释**以 `//` 开头,这一行里它后面的所有内容都属于注释: + +```java #singleLineComment + +``` + +注释也可以写在一行代码的末尾: + +```java #inlineComment + +``` + +两个示例做的是同一件事。 +把注释放在代码前面,还是写在同一行,由你决定。 + +### 多行注释 + +**多行注释**以 `/*` 开始,以 `*/` 结束。两者之间的所有内容都属于注释,即使跨了好几行也一样。 +多行注释适合写较长的说明,或者把一大段代码变成注释: + +```java #multiLineComment + +``` + +### 编写有用的注释 + +好的注释解释代码存在的原因,而不是重复代码本身在做什么。 +这条注释是有用的,因为它说明了代码的目的: + +```java #goodComment + +``` + +这条注释没有用,因为它只是重复了代码已经表达的内容: + +```java #badComment + +``` + +不必为每一行代码都写注释。 +但如果某些内容可能会让别人,或者以后的你感到困惑,就留下一条注释! + +## 运行代码 + +在这个阶段,你会根据每个练习中的说明编写代码。 +写完之后,需要运行代码,确认它能正常工作。 +要运行代码,点击 VS Code 右上角的三角形箭头按钮。 + +显示出来的信息会出现在终端中,终端位于 Panel(面板)里。 + + + + + +## Java 基础练习 + + + + diff --git a/src/content/docs/zh-cn/learning-course/stage0/loops.mdx b/src/content/docs/zh-cn/learning-course/stage0/loops.mdx new file mode 100644 index 00000000..cc4a5cb4 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/loops.mdx @@ -0,0 +1,169 @@ +--- +title: Loops(循环) +description: 介绍 Java 中的 while 循环和 for 循环 +codeRegionSources: + default: stage0/snippets/src/Loops.java +--- + +在[上一课](/learning-course/stage0/conditionals/)中,我们用条件语句决定一段代码是否执行。 +本节课我们将学习**循环**。循环可以让同一段代码块执行不止一次。 + +## 为什么需要循环? + +程序经常需要把同一件事连续执行多次。 +日常生活中,如果知道成绩即将公布,你可能会反复查看,直到成绩出来。 +机器人也是这样:它可能需要一直向前行驶,直到离墙壁足够近;也可能需要对料斗里的每一个球都执行一次发射流程。 +如果每重复一次就再写一行相同的代码,既麻烦,也无法应对事先不知道要重复多少次的情况。 + +条件语句和循环都可以用来控制哪段代码应该执行。 +条件语句的代码块**最多执行一次**,循环的代码块可以执行很多次。 +本节介绍两种主要的循环:`while` 循环和 `for` 循环。 + + + +## `while` 循环 + +**`while` 循环**会反复执行它的代码块,只要条件成立就会继续: + +```java #whileSyntax + +``` + +`while` 循环和[上一课](/learning-course/stage0/conditionals/)的 `if` 语句很像。 +它同样包含 `while` 关键字、圆括号里结果为 true 或 false 的条件,以及花括号里的代码块。 + +它们的区别在于代码块执行完之后: +`if` 语句会继续执行后面的代码,而 `while` 循环会再次进行条件的判断。 +如果条件仍然成立,代码块会再执行一次,如此重复,直到条件不成立为止。 +如果第一次判断时条件就是 false,代码块将一次都不会执行。 +与 `if` 语句不同,`while` 循环不能带有 `else` 语句。 + +下面这个循环会依次输出从 0 到 5 的数字: + +```java #whileExample + +``` + +`i` 从 0 开始。 +每执行一次循环,代码都会输出 `i` 的值,然后用 `i++` 给它加 1,然后再次判断 `i < 6`是否成立。 +`i++` 就是 [Operators(运算符)](/learning-course/stage0/operators/)那一节内容里的自增运算符。 +一旦 `i` 的值达到 6,条件就不成立,循环结束,所以最后输出的数字是 5。 + +每执行一次循环的代码块,称为一次 iteration(迭代)。这个循环会执行六次迭代。 + +### 循环和条件语句一起用 + +循环的代码块里可以包含任何代码,包括 `if` 语句。 +下面这个循环会让机器人先向前行驶几次,然后停下来: + +```java #whileExample2 + +``` + +不运行代码,判断它会输出什么? + + + +## `for` 循环 + +很多循环都遵循同一种结构:先创建计数变量,然后在条件里判断它,并在每次迭代结束时更新它。 +写成 `while` 循环就是: + +```java #ForExample1 + +``` + +这个循环依赖三部分:`int i = 0`、`i < 6` 和 `i++`。 +**`for` 循环**把这三部分放在一起,写在循环开头: + +```java #ForExample2 + +``` + +`for` 后面的圆括号里有三个部分,用分号隔开: + +- Initialization(初始化)(`int i = + 0`)在循环开始前执行一次,通常用来创建计数变量。 +- Condition(条件)(`i < 6`)在每次迭代前判断,条件成立时循环继续。 +- Update(更新)(`i++`)在每次迭代结束时执行。 + +这两个循环完成的是同一件事,但 `for` 循环更容易阅读和理解。 +计数从哪里开始、循环何时停止、计数如何变化,都写在同一行里,而不是散落在循环各处。 + + + +不运行代码,判断这个循环会依次输出什么? + +```java #forExample + +``` + + + +## 小心无限循环 + +只有条件变成 false,循环才会停止。 +如果循环内部没有任何代码改变这个条件,条件就会一直成立,循环将永远不会停止。 +这称为 infinite loop(无限循环)。 + +在机器人的控制系统中,无限循环很危险。 +传动系统可能会一直行驶,机械臂也可能一直运动,直到撞上其他物体。 +下面这个循环的本意是让机器人连续向前行驶 7 次: + +{/* rli:ignore */} + +```java +int loopCount = 0; +while (loopCount < 7) { + drivetrain.setThrottle(1); // sets drive motors to full speed +} +``` + +但是循环内部没有修改 `loopCount` 的值。 +所以它的值一直为 0,`loopCount < 7` 一直成立,机器人永远不会停止行驶。 + +要修正这个问题,就需要在循环内部更新 `loopCount` 的值,让条件最终不成立: + +{/* rli:ignore */} + +```java +int loopCount = 0; +while (loopCount < 7) { + drivetrain.setThrottle(1); // sets drive motors to full speed + loopCount++; +} +``` + +现在 `loopCount` 的值每次迭代结束时都会被加 1,循环会在 7 次迭代后停止。 + + + +## 循环练习 + + diff --git a/src/content/docs/zh-cn/learning-course/stage0/operators.mdx b/src/content/docs/zh-cn/learning-course/stage0/operators.mdx new file mode 100644 index 00000000..edee224b --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/operators.mdx @@ -0,0 +1,366 @@ +--- +title: Operators(运算符) +description: 介绍 Java 中的算术运算符、赋值运算符、比较运算符和逻辑运算符 +codeRegionSources: + default: stage0/snippets/src/Operators.java +--- + +在 [Java基础](/learning-course/stage0/java-fundamentals/) 这一课中,我们把值存在变量里。 +这一课学习 **运算符**,也就是像 `+` 和 `>` 这样的符号,用来根据已有的值计算出新的值。 +机器人代码里到处都是运算符。 +数储球仓里的球要用 `+`,检查射球机构是否达到速度要用 `>`。 + +我们会介绍四种运算符: + +- Arithmetic operators(算术运算符),用来做数学运算 +- Assignment operators(赋值运算符),用来更新变量的值 +- Comparison operators(比较运算符),用来比较两个值 +- Logical operators(逻辑运算符),用来组合 `true` 和 `false` + +## Arithmetic Operators(算术运算符) + +**算术运算符**对数字做基本数学运算: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
运算符说明示例结果
`+`加法:将两个值相加`5 + 2``7`
`-`减法:用一个值减去另一个值`5 - 2``3`
`*`乘法:将两个值相乘`5 * 2``10`
`/`除法:用一个值除以另一个值`10 / 2``5`
`%`余数:返回两个数相除后的余数`5 % 2``1`
+ +任何可以使用数字的地方都可以使用运算符,例如给变量赋值时。 +不运行代码的话,下面这些变量分别保存什么值? + +```java #variables + +``` + + + + + +## Assignment Operators(赋值运算符) + +**赋值运算符**给变量一个新的值。 +你已经用过最简单的一个,`=`,用来设置变量的值。 +其他赋值运算符是根据变量当前值来更新它的简写: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
运算符说明示例
`=`把一个值赋给变量`a = 2`
`+=` + 加法:取变量的当前值,加上指定的量,再把结果赋给这个变量。它与 + `x = x + y` 相同 + `a += 3`
`-=` + 减法:取变量的当前值,减去指定的量,再把结果赋给这个变量。它与 + `x = x - y` 相同 + `a -= 4`
`*=` + 乘法:取变量的当前值,乘以指定的量,再把结果赋给这个变量。它与 + `x = x * y` 相同 + `a *= 5`
`/=` + 除法:取变量的当前值,除以指定的量,再把结果赋给这个变量。它与 + `x = x / y` 相同 + `a /= 6`
+ +`score += 2` 和 `score = score + 2` 做的是同一件事,但变量名只需要写一次。 +代码更短,少一个容易出错的地方。 + +这里,`score` 从 10 开始并增加 2,而 `ballsCarried` 从 5 开始并减少 1: + +```java #arithmetic + +``` + +加 1 或减 1 非常常见,所以 Java 专门提供了运算符。 +`++` 通过加 1 来 increment(自增)一个变量,`--` 通过减 1 来 decrement(自减)一个变量。 +在本阶段后面的[循环](/learning-course/stage0/loops/)里,你会经常看到 `++`。 + + + + + + + + + + + + + + + + + + + + + +
运算符说明示例
`++`自增:给变量加 1`a++`
`--`自减:给变量减 1`a--`
+ +机器人每射出一个球,就可以记下这一次,并从储球仓里去掉这个球: + +```java #increments + +``` + +## Comparison Operators(比较运算符) + +**比较运算符**比较两个值,并返回一个 `boolean`,也就是 `true` 或 `false`。 +程序靠它们做判断,[条件语句](/learning-course/stage0/conditionals/)那一课会用到。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
运算符说明示例结果
`==`等于:检查两个值是否相同`5 == 2``false`
`!=`不等于:检查两个值是否**不**相同`5 != 2``true`
`>`大于:检查一个值是否大于另一个值`5 > 2``true`
`<`小于:检查一个值是否小于另一个值`5 < 5``false`
`>=`大于或等于:检查一个值是否大于或等于另一个值`6 >= 7``false`
`<=`小于或等于:检查一个值是否小于或等于另一个值`6 <= 6``true`
+ +它们的用法和数学课里的比较一样。 +这里,机器人已经进了 2 个球,但需要 4 个,所以 `ballsScored > ballsNeeded` 是 `false`: + +```java #comparison + +``` + + + +### `=` 与 `==` + +要检查两个值是否相等,使用双等号 `==`。 +单个等号 `=` 是赋值运算符。 +它设置变量的值,不做比较。 + +把它们弄混是常见错误。 +下面这行代码无法通过编译。 +Java 会把它理解成试图把数字 6 改成 8,这在java中是做不到的。 +错误信息会说,赋值的左边必须是一个变量: + +{/* rli:ignore */} + +```java +System.out.println(6 = 8); +``` + +换成 `==` 后,Java 比较的是两个值,并且会打印 `false`,因为 6 不等于 8: + +{/* rli:ignore */} + +```java +System.out.println(6 == 8); +``` + +## Logical Operators(逻辑运算符) + +**逻辑运算符**用来组合 `boolean` 值,这样可以同时根据多个条件做判断,例如“机器人有球,并且已经对准目标”。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
运算符说明示例结果
`&&`与:两个语句都为 true 时结果为 true`true && true``true`
`||`或:至少一个语句为 true 时结果为 true`true || false``true`
`!`非:取反。true 变成 false,false 变成 true`!true``false`
+ +这里,机器人有球,但还没有对准目标。 +不运行代码的话,每一行会打印什么? + +```java #logical + +``` + + + +## 改变变量与使用它的值 + +运算符本身会算出一个新的值,但不会改变任何变量。 +只有用 `=` 或其他赋值运算符把结果赋回去,变量才会改变。 + +这里,`totalPoints` 被设为 `autoPoints + 10` 的结果,所以它的值变成 12: + +```java #math1 + +``` + +在这个示例里,`gamePieces * 2` 是在打印语句内部计算的: + +```java #math2 + +``` + +这会打印 12,但之后 `gamePieces` 仍然是 6。 +`gamePieces * 2` 的结果只用于打印,从未赋回给 `gamePieces`。 +可以在下一行再打印一次 `gamePieces` 来确认。 + +## 运算符练习 + + diff --git a/src/content/docs/zh-cn/learning-course/stage0/stage-overview.mdx b/src/content/docs/zh-cn/learning-course/stage0/stage-overview.mdx new file mode 100644 index 00000000..1066c07f --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/stage-overview.mdx @@ -0,0 +1,23 @@ +--- +title: 阶段 0 概览 +description: 介绍阶段 0 涵盖的主题 +--- + +学习 Java 是学习 FRC 编程最重要的一步。 +使用 Java,可以编写代码来运行电机、调节电机的速度,以及完成更多操作。 +关于 Java 要学的内容很多,但阶段 0 只涵盖编写机器人代码所需的基础知识。 + +阶段 0 的每一课都会介绍一个不同的核心主题,并且会在前面课程的基础上继续展开。 +如果你是编程新手,请**按顺序学习这些课程**。 + +本阶段涵盖以下主题: + +- [Java基础](/learning-course/stage0/java-fundamentals/) +- [Debugging(调试)](/learning-course/stage0/debugging/) +- [Operators(运算符)](/learning-course/stage0/operators/) +- [Conditionals(条件语句)](/learning-course/stage0/conditionals/) +- [Loops(循环)](/learning-course/stage0/loops/) +- [Classes and Objects(类与对象)](/learning-course/stage0/classes-objects/) +- [Methods and Mutable State(方法与可变状态)](/learning-course/stage0/classes-methods/) +- [Arrays and For-Each Loops(数组与 for-each 循环)](/learning-course/stage0/arrays/) +- [Interfaces, Generics, and Lists(接口、泛型与列表)](/learning-course/stage0/interfaces-lists/) diff --git a/src/content/docs/zh-cn/learning-course/stage0/stage-wrap-up.mdx b/src/content/docs/zh-cn/learning-course/stage0/stage-wrap-up.mdx new file mode 100644 index 00000000..42093438 --- /dev/null +++ b/src/content/docs/zh-cn/learning-course/stage0/stage-wrap-up.mdx @@ -0,0 +1,17 @@ +--- +title: 阶段 0 总结 +description: 总结阶段 0 +--- + +恭喜你完成阶段 0! +能够动手编写代码,是实现机器人各项动作的基础。 +完成本阶段学习后,你已经掌握了后续编写机器人程序所需要的 Java 基础能力。 +如果你想要复习自己写过的练习,阶段 0 全部练习的答案在 [Stage 0 Solution 仓库](https://github.com/frcsoftware/Stage0-Solution)中。 + +在阶段 0 中,你已经学习了: + +- Java 基础语法,以及如何找到并修复错误 +- 进行数学运算和比较数值 +- 用条件语句和循环控制代码执行逻辑 +- 类、对象和接口,以及把代码拆分到不同文件 +- 用数组和列表存储大量数据 diff --git a/src/content/docs/zh-cn/resources/glossary.mdx b/src/content/docs/zh-cn/resources/glossary.mdx new file mode 100644 index 00000000..85ddfc81 --- /dev/null +++ b/src/content/docs/zh-cn/resources/glossary.mdx @@ -0,0 +1,7 @@ +--- +title: 术语表 +--- + +import GlossaryList from '@components/GlossaryList.astro'; + + diff --git a/src/content/i18n/zh-CN.json b/src/content/i18n/zh-CN.json new file mode 100644 index 00000000..ece160f1 --- /dev/null +++ b/src/content/i18n/zh-CN.json @@ -0,0 +1,44 @@ +{ + "skipLink.label": "跳到正文", + "search.label": "搜索", + "search.ctrlKey": "Ctrl", + "search.cancelLabel": "取消", + "search.devWarning": "搜索仅在生产构建中可用。\n请先构建并预览网站,再在本地测试搜索。", + "themeSelect.accessibleLabel": "选择主题", + "themeSelect.dark": "深色", + "themeSelect.light": "浅色", + "themeSelect.auto": "自动", + "languageSelect.accessibleLabel": "选择语言", + "menuButton.accessibleLabel": "菜单", + "sidebarNav.accessibleLabel": "主导航", + "tableOfContents.onThisPage": "本页内容", + "tableOfContents.overview": "概述", + "i18n.untranslatedContent": "此内容尚无简体中文译本。", + "page.previousLink": "上一页", + "page.nextLink": "下一页", + "404.text": "页面未找到。请检查网址,或使用搜索栏查找。", + "aside.note": "Note(备注)", + "aside.tip": "Tip(提示)", + "aside.caution": "Warning(警告)", + "aside.danger": "Danger(危险)", + "aside.answer": "Answer(答案)", + "aside.example": "Example(示例)", + "aside.exercise": "Exercise(练习)", + "aside.hint": "Hint(提示)", + "aside.video": "Video(视频)", + "aside.wip": "Work in Progress(正在完善)", + "header.menuToggle": "切换导航菜单", + "banner.development": "本网站仍在开发中。如果发现任何错误,请{link}报告。", + "banner.developmentLinkText": "在这里", + "courseSection.underConstruction": "(正在建设中)", + "glossary.definitionNotFound": "未找到定义", + "footer.github": "在 GitHub 上打开 FRCSoftware.org", + "footer.discord": "加入 FRCSoftware.org Discord", + "card.copyLink": "复制链接到剪贴板", + "slides.previous": "上一张幻灯片", + "slides.next": "下一张幻灯片", + "slides.goTo": "转到第 {number} 张幻灯片", + "slides.closeLightbox": "关闭灯箱", + "video.unsupported": "你的浏览器不支持 video 标签。", + "video.youTubeTitle": "YouTube 视频" +} diff --git a/src/data/glossary.zh-cn.yaml b/src/data/glossary.zh-cn.yaml new file mode 100644 index 00000000..bf623574 --- /dev/null +++ b/src/data/glossary.zh-cn.yaml @@ -0,0 +1,75 @@ +# 简体中文术语和缩写表 +# +# 键必须与 glossary.yaml 中的英文规范术语一致。 +# `term` 是中文正文中匹配和显示的术语;省略时沿用英文术语。 +# `definition` 是中文定义。未填写的条目会回退到英文。 + +# 电子器件 +PDH: + definition: 配电中心(Power Distribution Hub) + +SPARK MAX: + definition: 用于控制 REV 电机的电机控制器 + +Talon FX: + definition: 用于控制 CTRE 电机的电机控制器 + +Systemcore: + definition: 运行机器人代码的主处理器,提供多种 I/O 接口 + +CAN: + definition: >- + 控制器局域网(Controller Area Network):通常使用黄绿线连接电机控制器和传感器; + 可以采用多种拓扑结构进行布线,而不必将每根线缆都单独接回 Systemcore + +PWM: + definition: >- + 脉宽调制(Pulse Width Modulation):一种用于与电机控制器和传感器通信的规范, + 设备需要连接回 Systemcore + +Main Breaker: + term: 主断路器 + definition: 机器人的电源开关 + +Limit Switch: + term: 限位开关 + definition: >- + 一种在受到物理接触或磁力触发时动作的传感器,可用于在信号上升沿或下降沿触发操作, + 也可用于检查机构的状态 + +Throughbore Encoder: + term: 通孔编码器 + definition: 一种允许轴穿过其中心,并记录角位置的编码器 + +Magnetic Encoder: + term: 磁编码器 + definition: 一种使用接收器和磁体测量位置与运动状态的编码器 + +# 软件 +Repository: + term: 仓库 + definition: >- + 用于存放代码及相关资源的位置,常用于 Git 等版本控制系统。 + 仓库本质上是包含文件和子文件夹的文件夹,也可托管在 GitHub 等平台上, + 以便开展协作和管理版本 + +CI: + definition: >- + 持续集成(Continuous Integration):一种软件开发实践。 + 开发者频繁地将代码变更合并到共享仓库中,并由此触发自动构建和测试, + 以确保代码库保持稳定 + +WPILib: + definition: >- + WPILib 是为队伍提供的标准软件库和工具集,用于编写、测试和调试 + FIRST® Robotics Competition 与 FIRST® Tech Challenge 机器人的代码 + +AprilTag: + definition: >- + 一种类似二维码的基准标记。WPILib 和厂商提供的视觉工具可以检测 AprilTag, + 从而确定相机相对于标记的位置和朝向 + +GitHub: + definition: >- + GitHub 类似于用于代码的 Google Drive。 + 它可以托管 Git 仓库,并通过拉取请求和议题等功能改善项目协作和版本跟踪 From fc6ecab365e0776ee33fd75fa8e30136c00e5ffa Mon Sep 17 00:00:00 2001 From: smilelsb Date: Thu, 22 Oct 2026 10:06:27 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(i18n):=20keep=20the=20Stage=200=20templ?= =?UTF-8?q?ate=20link=20intact=20/=20=E4=BF=AE=E6=AD=A3=E9=98=B6=E6=AE=B5?= =?UTF-8?q?=200=20=E6=A8=A1=E6=9D=BF=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fullwidth punctuation after the bare URL was parsed as part of the address, so the link check received a 404. --- .../learning-course/getting-started/forking-and-cloning.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/zh-cn/learning-course/getting-started/forking-and-cloning.mdx b/src/content/docs/zh-cn/learning-course/getting-started/forking-and-cloning.mdx index d6fe690c..359de16b 100644 --- a/src/content/docs/zh-cn/learning-course/getting-started/forking-and-cloning.mdx +++ b/src/content/docs/zh-cn/learning-course/getting-started/forking-and-cloning.mdx @@ -9,7 +9,7 @@ import ContentFigure from '@components/ContentFigure.astro'; 开始之前,需要为相关的 repository(仓库)创建自己的 fork(派生仓库)。 在此之前,需要先按照“必备工具”部分的说明完成准备工作。 -要创建 fork,请访问需要复制的仓库(例如 https://github.com/frcsoftware/Stage0-Template),然后点击右上角的“Fork”按钮。 +要创建 fork,请访问需要复制的仓库(例如 [Stage 0 Template](https://github.com/frcsoftware/Stage0-Template)),然后点击右上角的“Fork”按钮。 这会在自己的 GitHub 账户下创建该仓库的副本,以便在不影响原仓库的情况下进行修改。