Автор оригинала: Cameron Manavian.
Давайте поговорим о Ржавчина Отказ Это статически набранный и сильно напечатанный язык программирования, который можно использовать для создания программ низкоуровневых, 3D-видеоигры, 2D видеоигры, системных утилит и даже серверов веб-приложений.
Ржавчина — чрезвычайно полезный и производительный язык. Поскольку это «C-похожий» язык (или, скорее, принадлежит C-семьи в истории программирования), он будет знаком тем, кто знает C ++, C, C #, JavaScript или Java.
Многие программисты предпочитают ржавчину для своей живой быстрой скорости, гибкой строгости типа, оптимизированные справочные управлению и сетью безопасности памяти. Он также делает все переменные неизменными (только для чтения) по умолчанию, значительно заставляют вас создавать функциональное программирование и составлением многих подфункций в более крупные программы.
Мы собираемся пройти через приложение ржавчины, которое я построил, который в основном по существу является базовым roguelike в большинстве случаев.
Roguelike:
… Поджанре ролевой игры в видеоигры, характеризующуюся тем, что подземелье выползуют процедурногенерированные уровни, поворот на основе игрового процесса, графика на основе плитки и постоянную смерть персонажа игрока — через Википедию
В наше Применение ржавчины, я сделал начало roguelike: вы можете выбрать из пяти классов — каждая со своими типами атрибутов. Далее вы можете приступить к атаку врага или уклониться от атаки врага. Когда мы разрушаем программу, мы распутаем основные правила, которые я создал для этой игры.
Сборка и мета файлов
Давайте посмотрим на код — Вот ссылка на исходный код REPO Отказ
В исходном коде у нас есть некоторые интересные предметы примечания:
# rust_roguelike/ 🗀 .circle/ 🗀 src/ 🗎 .editorconfig 🗎 Cargo.toml
Cargo.toml.
Cargo.toml
Держит конфигурацию груза, которая говорит о ржании и грузам все о нашей заявке.
[package] name = "rust_roguelike" description = "Rust RPG Roguelike" version = "0.1.0" authors = ["Cameron Manavian"] [dependencies] rand = "0.3.14" text_io = "0.1.7"
.editorconfig.
Далее я хотел бы указать файл, который каждый должен использовать — a Remerconsfig , который является универсальным файлом, чтобы рассказать только о любом редакторе, как мы хотим отформатировать и записать наш код ржавчины, что позволяет для согласованных стилей кодирования между различными редакторами и идентификаторами. Вы также увидите конфигурации для файлов YAML и TOML:
root = true [*] indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.toml] indent_style = space indent_size = 4 [{*.json, *.svg}] indent_style = space indent_size = 4 [{*.yml,*.scss,*.css,*rc}] indent_style = space indent_size = 2 [*.rs] indent_style = space indent_size = 4
Конечно, ржавчина уже имеет Линтер и форматированный встроенный и легко доступен, так .editorconfig.
просто там как резервная копия.
.circleci/config.yml.
Наконец, у нас есть конфигурация папки для Circleci . Circleci — мой Непрерывная интеграция Обслуживание выбора.
Внутри этой папки основным требованием является config.yml
, но вы также можете хранить сценарии Shell здесь, чтобы сделать более сложные шаги.
В настоящее время у меня есть базовый конфиг:
version: 2 jobs: build: docker: - image: rust:1.29.0-slim environment: TZ: "/usr/share/zoneinfo/Etc/UTC" steps: - checkout - run: cargo build - run: cargo test
Чтобы начать с, я говорю Circleci использовать Docker Image, который установлен ржавчиной ( Rust: 1.29.0-Slim
), а затем у меня есть некоторые команды для запуска для моих интеграционных тестов.
Сначала мы Оформить заказ
Git с использованием коммита, который вызвал эту сборку. Далее мы бежим грузовые сборки
Чтобы создать все зависимости, загружая и установку на основе настроек в Cargo.toml
Отказ Наконец, мы бежим Грузовый тест
Что будет найдет все тесты подразделения внутри проекта и запустить их. Позже мы могли бы добавить код покрытия кода здесь, как окончательная проверка, чтобы убедиться, что Все кода проверяется.
Исходный код
Теперь давайте посмотрим на архитектуру нашего приложения, и еще раз Вот ссылка на репозиторий исходного кода на Github Отказ
У нас есть три файла ржавчины ( * .rs
) которые
# rust_roguelike/src/ 🗎 character.rs 🗎 computer.rs 🗎 main.rs
Согласно конвенции ржавчины, main.rs
Наша точка входа в приложение и имеет Функция
внутри также названа Главная
Отказ Мы вернемся к этому файлу позже.
character.rs
Персонаж модуль
Является ли центральное место для персонажей пользователя, здоровья и другой статистики.
У нас есть наш Персонаж структура
(структура), состоящая из некоторые Строка
Поля для имени и RPG класса и горстки I32
(целые числа) для здоровья и статистики. Я пошел с классикой C struck стиль:
pub struct Character { pub name: String, pub class: String, pub health: i32, attack: i32, dodge: i32, luck: i32, xp: i32, }
В ржавее, подумайте о структура
как способ иметь стандартизированные данные. Но как мы работаем с этими данными? Мы используем Черта
Действительно
Вы также увидите Игрок Черта
В файле, который по существу эквивалентен интерфейсам.
pub trait Player { fn new( name: String, class_name: String, health: i32, attack: i32, dodge: i32, luck: i32, ) -> Character; fn select(&self, player_name: String, player_luck: i32) -> Self; fn damage(&mut self, damage_amount: i32); fn heal(&mut self, heal_amount: i32); fn attack(&self) -> i32; fn dodge(&self) -> i32; fn info(&self) -> String; fn stats(&self) -> String; }
Я люблю черты, потому что они позволяют программистам иметь абстракции нулевой стоимости — черты — это огромный особенность в ржавее.
Все сказанные, система черты — это секретный соус, который дает ржавчину эргономичное, выразительное ощущение языков высокого уровня при сохранении низкого уровня контроля над выполнением кодов и представления данных. — через блог языка программирования ржавчины
Далее у нас есть наша реализация, что способ определить Методы
на объекте, таком как структура
и может даже реализовать в соответствии с спецификациями A Черта
Отказ
Вы увидите в декларации, что я обозначаю, что я «реализую» Игрок Черта
для Персонаж структура
Отказ При этом я сейчас требуется реализовать все функции, необходимые для Игрок Черта
Отказ Посмотрите на полную реализацию:
impl Player for Character { fn new( name: String, class_name: String, health: i32, attack: i32, dodge: i32, luck: i32, ) -> Character { Character { name: name.to_string(), class: class_name.to_string(), health: health, attack: attack, dodge: dodge, luck: luck, xp: 0, } } fn select(&self, player_name: String, player_luck: i32) -> Self { Self::new( player_name, self.class.to_string(), self.health, self.attack, self.dodge, self.luck + player_luck, ) } fn damage(&mut self, damage_amount: i32) { self.health -= damage_amount; self.xp += 2; } fn heal(&mut self, heal_amount: i32) { self.health += heal_amount; self.xp += 1; } fn attack(&self) -> i32 { self.xp + self.attack + self.luck / 2 } fn dodge(&self) -> i32 { self.xp + self.dodge + self.luck / 2 } fn info(&self) -> String { format!( "{} \thp: {} attack: {} dodge: {} luck: {}", self.class, self.health, self.attack, self.dodge, self.luck ) } fn stats(&self) -> String { format!( "{} - hp: {} attack: {} dodge: {} luck: {} experience: {}", self.class, self.health, self.attack, self.dodge, self.luck, self.xp ) } }
Новый
Метод по существу является конструктором. Большим интересам является Выберите
Метод, который позволяет нам клонировать экземпляр A Характер и вернуть его назад, а также позволяя Имя
и удача
настройка.
Методы лечить
и Ущерб
почти идентичны, где можно уменьшить Персонаж Здоровье, а другое увеличивает его, и оба увеличивают общий опыт. Обратите внимание, что мы должны использовать & mut self
требовать Meatable Версия экземпляра, которую необходимо использовать для того, чтобы правильно сделать настройки здоровья и XP.
Точно так же вы увидите, что атаковать
и Додж
Методы возвращают I32
Значение на основе Персонаж Статистика и использовать нормальный и я
Ссылка экземпляра, так как они не мутируют Персонаж Отказ
Наконец, у нас есть пара Строка
Форматирование помощников, Информация
и статистика
. Метод Информация
более полезно при списке базы Персонаж Информация о классе, которую мы увидим в использовании внутри main.rs
Отказ Статистика
Метод так же, как способ получить соответствующую статистику для пользователя, чтобы он посмотреть в основном, когда они начинают игру и когда они умрут.
Наконец, в нижней части файла модуля мои модульные тесты. Мы обязаны обернуть модульные тесты в закрытии модуля для правильного синтаксиса:
#[cfg(test)] mod tests { use super::*; // unit tests here }
Давайте посмотрим на один из тестов, спецификация для атаковать
:
#[test] fn test_attack() { // arrange const EXPECTED_ATTACK: i32 = 6; let player = Character::new("".to_string(), "Rogue".to_string(), 1, 4, 1, 4); // act let result = player.attack(); // assert assert_eq!(result, EXPECTED_ATTACK); }
Когда я пишу модульные тесты, я следую в стиле Организовать/Акт/Assert Узор, преимущества которых лучше всего подходят:
- Ясно отделяется то, что тестируется из шагов установки и проверки.
- Разъясняет и сосредотачивает внимание на исторически успешном и вообще необходимом наборе тестовых шагов.
Делает некоторые тесты более очевидными:
- Утверждения смешиваются с кодом «ACT».
- Методы испытаний, которые пытаются проверить слишком много разных вещей одновременно. Организовать действие акта утверждать
Мне нравится вставлять комментарии к созданию своего рода шаблон для последующих обновлений, чтобы следовать.
В любом случае, наш тест ожидает, что наша сила атаки составляет 6 ( edited_attack
), что мы можем понять, познакомившись к исходному коду и применяя значения, передаваемые конструктору, в уравнение из способа, как так:
// we constructed a player with 4 attack and 4 luck: let player = Character::new("".to_string(), "Rogue".to_string(), 1, 4, 1, 4); // our method code fn attack(&self) -> i32 { self.xp + self.attack + self.luck / 2 } // simplify -> plug in values -> PEMDAs FTW xp + attack + luck / 2 = 0 + 4 + 4 / 2 = 4 + 2 = 6
После Организовать/Акт/Assert Узор, мы можем легко настроить дорожную карту, а затем утверждать, что результат метода соответствует нашему ожидаемому результату:
// assert assert_eq!(result, EXPECTED_ATTACK);
computer.rs
Наше Компьютер Модуль построен похоже на Персонаж Модуль, использующий структура
, а Черта
и Вводной
Отказ
pub struct Computer { level: i32, difficulty: i32, } pub trait Enemy { fn new(level: i32, difficulty: i32) -> Self; fn action(&self) -> (i32, i32); fn level_up(&mut self); fn stats(&self) -> String; } impl Enemy for Computer { fn new(level: i32, difficulty: i32) -> Computer { Computer { level: level, difficulty: difficulty, } } fn action(&self) -> (i32, i32) { (self.level, self.difficulty) } fn level_up(&mut self) { self.level += 1; self.difficulty += 3; } fn stats(&self) -> String { format!("level: {} difficulty: {}", self.level, self.difficulty) } }
Код довольно просто, если вы понимаете Персонаж модуль
Как и у него похожие функции.
Метод Действие
внутри Компьютер Вводной
Это уникально, хотя, как мы используем Кортеж
из двух целых чисел ( (I32, I32)
) в качестве нашего типа возврата:
fn action(&self) -> (i32, i32) { (self.level, self.difficulty) }
Используя Кортеж
Здесь позволяет нам вернуть два значения и включить main.rs
Файл Чтобы создать диапазон мощности действия компьютера, который позволяет компьютеру расти с игроком, когда они продвинулись на протяжении всей игры. Нижняя граница диапазона является уровнем компьютера, а верхняя граница является уровнем сложности. Вы заметите, что Сложность
Перепрыгивает на 3 очка каждого раунда, делая игру потенциально намного сложнее, чем игра (как это верхняя граница).
main.rs
Теперь давайте круиз повернуться к основным файлам — наша точка входа в приложение. Он имеет две функции и кучу отказа от импорта в верхней части.
Мы загружаем внешние ящики из наших зависимостей, загружайте модули нашего проекта и укажите, какие типы мы собираемся использовать.
FN Main.
Главная
Функция начинается с маленького баннера, который читает из нашего Toml
Файл и показывает нашу версию пакета и описание.
println!( "=== Welcome to RRL {} the {}! ====\n", player.name, player.class );
Мы создали группу персонажей для игры: священнослужитель, воин, охотник, волшебник и вор, используя 25 общих точек атрибута, распределенные на основе Archetype (не стесняйтесь комментировать или критиковать мое распределение). Мы также генерируем немного случайной удачи для сеанса пользователя:
let characters: [character::Character; 5] = [ character::Character::new("".to_string(), "Cleric".to_string(), 7, 5, 6, 7), character::Character::new("".to_string(), "Warrior".to_string(), 10, 5, 5, 5), character::Character::new("".to_string(), "Hunter".to_string(), 5, 7, 7, 6), character::Character::new("".to_string(), "Wizard".to_string(), 3, 10, 5, 7), character::Character::new("".to_string(), "Thief".to_string(), 4, 5, 6, 10), ]; let _luck_amount = rand::thread_rng().gen_range(2, 6);
Далее мы просим пользователя их имя и для них выбрать один из наших классов персонажей. Как только пользователь выбрал класс, мы используем наши Персонаж Выберите
Метод помощника и передайте имя игрока и их сгенерированную сумму удачи в качестве параметров:
let mut player = characters[character_index - 1].select(_character_name.to_string(), _luck_amount);
Этот клонирует выбранный архетип RPG в новую мулюдующую версию, оставляя экземпляры образцов Intact. Я сделал это, потому что мы могли бы вернуться позже к этому проекту и улучшить игру с функцией «новой игры», поэтому мы не хотим коррумпировать оригинал Персонаж экземпляры.
FN PLAY.
Функция PLAY запускает нашу игровую петлю — как правило, когда вы делаете видеоигру, цикл используется для ожидания какой-либо «игрой над» индикатора, часто, когда здоровье игрока равна или меньше нуля.
Перед циклом мы также создаем новый Компьютер враг:
let mut enemy = computer::Computer::new(1, 16);
Внутри петли мы извлекаем компьютер Кортеж
Диапазон действий и использовать его с импортным Рэнд
Ящик:
let _action = enemy.action(); let _cpu_action = rand::thread_rng().gen_range(_action.0, _action.1);
Помните, что случайный диапазон включает в себя нижнюю границу, но исключительно на верхней границе, поэтому компьютер 1 1 компьютер с 16 сложностью будет запрашивать число от 1 до 15.
Затем мы просим игрока о том, что они хотят сделать: они могут либо атаковать, либо уклониться, и в зависимости от их выбора мы извлекаем их атаку или уклонение от их Персонаж и сравнить его с Компьютер сила действия.
С этим у нас достаточно, чтобы определить победу или потерю для раунда, что вызывает заживление или повреждение соответственно.
Вот и все. Это вся игра Отказ
Последние мысли
То, что у нас есть, это тестированная игра в Rust.
Мы не тестировали основной класс, так как я все еще ищу способы тестирования кода на основе стандарта в/Standard, но у нас есть функционал Albeit Limited Roguelike, построенный с ржавчиной, который вы можете играть в терминале.
Некоторые возможные функции, которые мы могли бы добавить (потянуть запросы!):
- Новая опция игры, которая может просто вернуть вас к выбору персонажа
- Больше классов на выбор
- Несколько типов врагов
- Босс врагов каждые 5-10 раундов или около того
- Графика, может быть, даже просто emoji или Ascii-Art
Спасибо за ваше время.
Оригинал: «https://www.codementor.io/@cameronmanavian/rust-rpg-introductory-tutorial-of-rust-and-unit-testing-with-a-roguelike-njqhpy50p»