Рубрики
Uncategorized

RUST RPG: вводное учебное руководство по тестированию ржавчины и единицы с рогоэлью

Использование ржавчины, построить тестируемую единицу и постоянно интегрированную игру в стиле Roguelike

Автор оригинала: Cameron Manavian.

Давайте поговорим о Ржавчина Отказ Это статически набранный и сильно напечатанный язык программирования, который можно использовать для создания программ низкоуровневых, 3D-видеоигры, 2D видеоигры, системных утилит и даже серверов веб-приложений.

Ржавчина — чрезвычайно полезный и производительный язык. Поскольку это «C-похожий» язык (или, скорее, принадлежит C-семьи в истории программирования), он будет знаком тем, кто знает C ++, C, C #, JavaScript или Java.

Многие программисты предпочитают ржавчину для своей живой быстрой скорости, гибкой строгости типа, оптимизированные справочные управлению и сетью безопасности памяти. Он также делает все переменные неизменными (только для чтения) по умолчанию, значительно заставляют вас создавать функциональное программирование и составлением многих подфункций в более крупные программы.

Мы собираемся пройти через приложение ржавчины, которое я построил, который в основном по существу является базовым roguelike в большинстве случаев.

Roguelike:

… Поджанре ролевой игры в видеоигры, характеризующуюся тем, что подземелье выползуют процедурногенерированные уровни, поворот на основе игрового процесса, графика на основе плитки и постоянную смерть персонажа игрока — через Википедию

В наше Применение ржавчины, я сделал начало roguelike: вы можете выбрать из пяти классов — каждая со своими типами атрибутов. Далее вы можете приступить к атаку врага или уклониться от атаки врага. Когда мы разрушаем программу, мы распутаем основные правила, которые я создал для этой игры.

Castle.jpg.

Сборка и мета файлов

Давайте посмотрим на код — Вот ссылка на исходный код 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 Узор, преимущества которых лучше всего подходят:

  • Ясно отделяется то, что тестируется из шагов установки и проверки.
  • Разъясняет и сосредотачивает внимание на исторически успешном и вообще необходимом наборе тестовых шагов.

Делает некоторые тесты более очевидными:

Мне нравится вставлять комментарии к созданию своего рода шаблон для последующих обновлений, чтобы следовать.

В любом случае, наш тест ожидает, что наша сила атаки составляет 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, построенный с ржавчиной, который вы можете играть в терминале.

Некоторые возможные функции, которые мы могли бы добавить (потянуть запросы!):

Kevin-Horstmann-345328-unsplash.jpg
  • Новая опция игры, которая может просто вернуть вас к выбору персонажа
  • Больше классов на выбор
  • Несколько типов врагов
  • Босс врагов каждые 5-10 раундов или около того
  • Графика, может быть, даже просто emoji или Ascii-Art

Спасибо за ваше время.

Оригинал: «https://www.codementor.io/@cameronmanavian/rust-rpg-introductory-tutorial-of-rust-and-unit-testing-with-a-roguelike-njqhpy50p»