Данная статья представляет из-себя эксперимент с Rust’ом с последующей его компиляцией в WASM. Было интересно пощупать данные технологии на чем-то сложнее, чем вычисление факториала, поэтому выбор пал на всем известную игру 2048.

Оригинальная игра представляет из себя клеточное поле 4 на 4. Каждая клетка может быть либо пустой, либо занята плиткой
с числом, являющегося степенью двойки. Стартовое состояние поля имеет 2 заполненные клетки.

Игрок может совершать ход в одном из 4 направлений: влево, вправо, вверх и вниз, что сдвигает все плитки до упора в выбранном
направлении. После каждого хода появляется новая плитка. Новая плитка будет содержать двойку с вероятностью 90% или четверку
с вероятностью 10%.

Походим из стартового состояния, показанного выше, вправо:

Плитка на втором ряду уперлась в правую границу, также появилась двойка на первом ряду, третьем столбце.

Если плитка упирается в другую плитку, содержащее такое же число, они соединяются в одну плитку следующей степени двойки.

Походим из предыдущего состояния вверх:

Двойки последнего столбца первой и второй строк объединилсь в одну, а кроме этого на третьем ряду появилась четверка.

Цель игры: дойти до плитки с числом 2048.

Поскольку одной из целей этого эксперимента для меня было поиграться с Rust, задача выбора языка не стоит.

На данной странице представлен список фронтовых фреймворков для Rust.
Самый популярный вариант — Yew — выглядит интересно, особенно при наличии опыта с React’ом, документация и примеры присутствуют,
так что выбор пал на него.

Инструкцию по созданию проекта можно найти здесь и выполняется она тривиально:

cargo new --lib rust-2048 && cd rust-2048

Дальше, в Cargo.toml прописываем зависимости:

[dependencies]
yew = "0.17"
wasm-bindgen = "0.2.67"

В примере предоставлен код для создания простого счетчика, который нужно поместить в src/lib.rs:

use wasm_bindgen::prelude::*;
use yew::prelude::*;
struct Model {
    link: ComponentLink<Self>,
    value: i64,
}
enum Msg {
    AddOne,
}
impl Component for Model {
    type Message = Msg;
    type Properties = ();
    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        Self {
            link,
            value: 0,
        }
    }
    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::AddOne => self.value += 1
        }
        true
    }
    fn change(&mut self, _props: Self::Properties) -> ShouldRender {
        // Should only return "true" if new properties are different to
        // previously received properties.
        // This component has no properties so we will always return "false".
        false
    }
    fn view(&self) -> Html {
        html! {
            <div>
                <button >

Создаем директорию для статичных файлов:

mkdir static

Внутри которой создаем index.html со следующим содержимым:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Yew Sample App</title>
    <script type="module">
      import init from "./wasm.js";
      init();
    </script>
  </head>
  <body></body>
</html>

Собираем проект при помощи wasm-pack (устанавливается через cargo install wasm-pack):

wasm-pack build --target web --out-name wasm --out-dir ./static

У меня при сборке появилась следующая ошибка:

Error: crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your Cargo.toml file:
[lib]
crate-type = ["cdylib", "rlib"]

Окей, так и сделаем. Открываем Cargo.toml и прописываем в конце:

[lib]
crate-type = ["cdylib", "rlib"]

Теперь можем запустить любой сервер статики и проверить результат (в примере предлагают установить miniserve, я же воспользуюсь стандартным питоновским модулем):

cd static
python -m SimpleHTTPServer

Открываем http://127.0.0.1:8000 и видим рабочий счетчик. Отлично, теперь можно приступать к самой игре.

Коммит: https://github.com/dev-family/wasm-2048/commit/6bc015fbc88c1633f4605944fd920b2780e261c1

Здесь я описал основные сущности игры и реализовал перемещение плиток без объединений. Также были добавлены
вспомогательные структуры и методы для удобства.

Рассмотрим логику движения плиток на примере данного поля:

2, 4, 0, 0
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

Предположим, что был совершен ход вправо. Ожидается следующий результат:

0, 0, 2, 4
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

Такого поведения можно добиться, если идти от конца выбранного направления и сдвигать все плитки по выбранному направлению.
Проитерирую вручную для лучшего понимания:

  1. Выбрано направление вправо => идем справа влево.
         /-------- пустая клетка, пропускаем
2, 4, 0, 0
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

2.

      /-------- пустая клетка, пропускаем
2, 4, 0, 0
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

3.

    /-------- начинаем сдвигать вправо
2, 4, 0, 0
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

4.

       /-------- продолжаем
2, 0, 4, 0
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

5.

         /-------- дошли до конца, возвращаемся на следующую позицию от которой начали передвижение
2, 0, 0, 4
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

6.

 /-------- двигаем вправо
2, 0, 0, 4
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

7.

    /-------- продолжаем
0, 2, 0, 4
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

8.

       /-------- остонавливаемся и переходим к следующей строке
0, 0, 2, 4
0, 0, 0, 0
0, 0, 0, 0
0, 0, 0, 0

Также стоит заметить, что в случае горизонтальных ходов строки двигаются независимо друг от друга, а в случае вертикальных — столбцы.

В коде данный алгоритм реализован методами Direction#build_traversal (построение пути обхода поля),
Grid#traverse_from (продвижение конкретной клетки по направлению) и Grid#move_in — публичный метод, использующий предыдущие два.

Коммит: https://github.com/dev-family/wasm-2048/commit/7e08b5af6008e6f9d716d7c9a41b0810e869df9e

У объединения плиток есть одна особенность: плитки не объединяются больше одного раза,
т.е. в случае наезжающих друг на друга 4 одинаковых плит, результатом будет являться 2 плитки
следующей степени. Из этого следует, что у плиток должно появиться некое состояние, которое нужно
сбрасывать перед каждым ходом.

Плюс, пришлось изменить структуру TestCase, т.к. правильное поведение можно протестировать только за несколько ходов.

Коммит: https://github.com/dev-family/wasm-2048/commit/6082409412f6c19943c453c5d706d57bbcef538b

Для рандома используется пакет rand, который работает и в WASM среде
путем добавления wasm-bindgen feature.

Для того, чтобы не ломать предыдущие тесты, которые не рассчитаны на добавление новых случайных
плиток в структуру Grid было добавлено новое флаговое поле enable_new_tiles.

Поскольку новые плитки добавлять нужно только в случае валидного хода (т.е. за ход произошло хоть одно изменение поля),
сигнатура traverse_from меняется. Теперь метод возращает булево значение, которое показывает, было ли движение.

Коммит: https://github.com/dev-family/wasm-2048/commit/356e0889a84d7fc2582662e76238f94fc69bfed7

UI часть реализуется довольно просто, особенно для знакомых с React и/или JSX. Прочитать про html! макрос из Yew можно здесь,
а про компоненты в общем здесь. Мне очень напомнило React в пре-хуковых времен.

Документации по работе с клавиатурой не нашлось, да и в принципе сервисы никак не документированы, нужно читать исходники, смотреть примеры.

Коммит: https://github.com/dev-family/wasm-2048/commit/e258748ab114ec5f930dbeb77d674bdbc5e63b1a.

Чтобы интерфейс смотрелся более живым, необходимо добавить анимации.

Они реализованы поверх обычных CSS transitions. Мы учим плитки запоминать их предыдущую позицию и при рендере
отображаем плитку сразу в старой позиции, а на следующем тике — в новой.

Let’s block ads! (Why?)

Read More

Recent Posts

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

2 дня ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

2 дня ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

2 дня ago

Нейросети будут писать тексты объявления за продавцов на Авито

У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…

2 дня ago

Объявлены победители международной премии Workspace Digital Awards-2024

24 апреля 2024 года в Москве состоялась церемония вручения наград международного конкурса Workspace Digital Awards. В этом году участниками стали…

3 дня ago

Яндекс проведет гик-фестиваль Young Con

27 июня Яндекс проведет гик-фестиваль Young Con для студентов и молодых специалистов, которые интересуются технологиями и хотят работать в IT.…

3 дня ago