
Игра 2048 в Wolfram Mathematica
- Перевод
Перевод поста 2048, Wolfram Style, написанного для официального блога компании Wolfram Research Дэном Фортунато, младшим программистом Wolfram|Alpha Parser Content.
Архив с файлом Wolfram Mathematica, в котором содержится код, вы можете скачать здесь.

Если в течение последних нескольких недель вы выходили в интернет, то вы вряд ли могли не встретиться с игрой под названием 2048, разработанной Габриэлем Чирулли. Будучи основанной на похожих играх, 1024! от Veewo Studio и THREES от Ашера Воллмера, эта игра имеет простую механику, которая затянет вас надолго — перемещайте по полю фишки, на которых написаны степени числа 2 и соединяйте их попарно, чтобы получить ещё более высокие степени. Главная цель игры — получить фишку 2048. Достаточно сложно объяснить, насколько в действительности интересна и увлекательна эта игра, поэтому я рекомендую вам самим сыграть в нее.
Чтобы отдать должное этой простой игре (и в честь всех математических игр!), я решил продемонстрировать всю мощь Языка Wolfram, используя его, чтобы разработать нашу собственную версию 2048. Начнём!
Основной структурой для игрового поля послужит матрица 4х4, заполненная пустыми элементами (строками нулевой длины):
In[1]:=

Out[2]=

После начала новой игры я случайным образом расположу на поле две фишки со значениями 2 или 4. Больший приоритет я отдам фишке 2, таким образом, она будет появляться на поле чаще.
In[3]:=

In[4]:=

Out[4]=

Настало время сделать наше игровое поле немного красивее. Я могу полностью повторить стиль оригинальной игры, импортировав её CSS-стиль (CSS — Cascading Style Sheets, каскадные таблицы стилей). Как вы можете видеть, в CSS я смог найти цвета для фона и текста всех фишек.
In[5]:=

Out[8]=

Теперь у меня есть список, содержащий каждый номер фишки и соответствующие ему цвета! Далее я могу обработать его, создав функцию для поиска цвета и перевода её из HEX (шестнадцатиричной записи цвета, используемой в CSS) в RGB (цветовую модель, основанную на сочетании красного, зелёного и синего цветов). Также я определяю цвета для остального игрового поля и, на всякий случай, для некоторых цветов по-умолчанию.
In[9]:=

In[10]:=

In[13]:=

Теперь я использую цветовую информацию для написания функции, отрисовывающей фишки. Необходимо проявить осмотрительность и сделать размер шрифта меньше, когда количество цифр в числе растёт.
In[15]:=

In[16]:=

In[17]:=

Я определил функцию drawTile таким образом, чтобы впоследствии её можно было легко изменить…
In[18]:=

Out[18]=

Чтобы применить этот стиль к оригинальному игровому полю, я просто рассматриваю каждый элемент игрового поля в матрице и располагаю соответствующую ему фишку на нужном месте.
In[19]:=

In[20]:=

Out[20]=

Выглядит красиво! Теперь нам нужно научиться управлять игрой.
Когда происходит нажатие одной из клавиш, я хочу, чтобы все фишки сдвигались по доске в указанном направлении настолько далеко, насколько это возможно, при этом одинаковые фишки должны объединяться. Я могу использовать опцию NotebookEventActions, чтобы регистрировать нажатия клавиш и соответствующе реагировать. Я сделаю так, чтобы управление осуществлялось клавишами wasd, вы можете выбрать любые другие:
In[21]:=

In[22]:=

Теперь давайте подумаем о том, что же происходит, когда фишки сдвигаются, скажем, влево. В первую очередь мне необходимо реализовать объединение одинаковых фишек. Каждая строка должна рассматриваться отдельно, поскольку при горизонтальном смещении вертикальные совпадения не объединяются. Я хочу найти последовательности из двух одинаковых чисел, возможно, с пустыми клетками между ними, и заменить их суммой. Здесь на помощь приходит мощь Языка Wolfram, и я могу использовать сопоставление с заданным шаблоным выражением, чтобы легко это осуществить.
In[23]:=

После сложения одинаковых фишек, всё, что я должен сделать — это добавить несколько пустых элементов справа, чтобы заполнить строку. Процедура повторяется для всех строк игрового поля.
In[24]:=

Сдвиг вправо осуществляется так же, с одним незначительным изменением — я хочу, чтобы сочетания одинаковых фишек справа объединялись до сочетаний, находящихся левее. Рассмотрим в качестве примера строку {$empty, 2, 2, 2}. Используя функцию combineLeft и нажав стрелку влево, я получу строку {$empty, $empty, 4, 2}, но на самом деле я хочу, чтобы сначала объединилась правая пара двоек. Переворот строки, сдвиг ячеек влево и обратный переворот легко решают проблему.
In[25]:=

In[26]:=

Теперь, когда у нас есть две этих функции, реализовать сдвиг вверх и вниз очень просто! Сдвиг вверх — это то же самое, что сдвиг влево на транспонированном игровом поле, за которым следует обратное транспонирование. Таким же отношением связаны и операции сдвига вправо и вниз.
In[27]:=

Если клавиша нажата, новые фишки на поле должны добавляться только в том случае, если состояние поля изменилось, то есть если некоторые фишки переместились или объединились. Для этого необходимо отслеживать предыдущее состояние игрового поля.
Теперь научимся вести подсчёт очков в течение игры. Каждый раз, когда объединяются две фишки, я собираю их сумму функцией Sow, а потом использую на всех суммах функцию Reap, когда все возможные объединения совершены. Также я вывожу значение наибольшей полученной на данный момент фишки.
In[29]:=

Наконец, давайте добавим проверку на выигрыш и проигрыш. Игра считается выигранной, если наибольшая фишка на поле имеет значение 2048 или выше. Я проиграл, если доска заполнена и у меня больше нет возможных ходов. В данном случае я снова могу использовать язык шаблонных выражений, чтобы определить, остались ли на доске возможные сочетания фишек.
In[30]:=

In[31]:=

Используя функцию Dynamic, я могу поддерживать отображаемое игровое поле в полном соответствии с внесёнными изменениями. Кроме того, я могу окружить всё это функцией DynamicModule и использовать Initialization, чтобы настроить обработку нажатий клавиш и игровое поле. Поместив DynamicModule внутрь CreateDialog мы можем открывать игру в отдельном окне.
Наконец, игра полностью готова.
In[32]:=


Сейчас всё выглядит вполне симпатично, но у нас в компании Wolfram Research мы любим что-нибудь более… заостренное. Такое, как Spikey — логотипы различных версий системы Mathematica и Wolfram|Alpha. Давайте немного изменим цвета фишек и их форму.
In[33]:=

In[34]:=

Out[34]=

In[35]:=

In[36]:=

In[37]:=

In[39]:=

Теперь мы можем переключаться между двумя стилями при помощи кнопок.
In[40]:=

Out[40]=

In[41]:=


Удачной вам игры!
Архив с файлом Wolfram Mathematica, в котором содержится код, вы можете скачать здесь.

Если в течение последних нескольких недель вы выходили в интернет, то вы вряд ли могли не встретиться с игрой под названием 2048, разработанной Габриэлем Чирулли. Будучи основанной на похожих играх, 1024! от Veewo Studio и THREES от Ашера Воллмера, эта игра имеет простую механику, которая затянет вас надолго — перемещайте по полю фишки, на которых написаны степени числа 2 и соединяйте их попарно, чтобы получить ещё более высокие степени. Главная цель игры — получить фишку 2048. Достаточно сложно объяснить, насколько в действительности интересна и увлекательна эта игра, поэтому я рекомендую вам самим сыграть в нее.
Чтобы отдать должное этой простой игре (и в честь всех математических игр!), я решил продемонстрировать всю мощь Языка Wolfram, используя его, чтобы разработать нашу собственную версию 2048. Начнём!
Основной структурой для игрового поля послужит матрица 4х4, заполненная пустыми элементами (строками нулевой длины):
In[1]:=

Out[2]=

После начала новой игры я случайным образом расположу на поле две фишки со значениями 2 или 4. Больший приоритет я отдам фишке 2, таким образом, она будет появляться на поле чаще.
In[3]:=

In[4]:=

Out[4]=

Настало время сделать наше игровое поле немного красивее. Я могу полностью повторить стиль оригинальной игры, импортировав её CSS-стиль (CSS — Cascading Style Sheets, каскадные таблицы стилей). Как вы можете видеть, в CSS я смог найти цвета для фона и текста всех фишек.
In[5]:=

Out[8]=

Теперь у меня есть список, содержащий каждый номер фишки и соответствующие ему цвета! Далее я могу обработать его, создав функцию для поиска цвета и перевода её из HEX (шестнадцатиричной записи цвета, используемой в CSS) в RGB (цветовую модель, основанную на сочетании красного, зелёного и синего цветов). Также я определяю цвета для остального игрового поля и, на всякий случай, для некоторых цветов по-умолчанию.
In[9]:=

In[10]:=

In[13]:=

Теперь я использую цветовую информацию для написания функции, отрисовывающей фишки. Необходимо проявить осмотрительность и сделать размер шрифта меньше, когда количество цифр в числе растёт.
In[15]:=

In[16]:=

In[17]:=

Я определил функцию drawTile таким образом, чтобы впоследствии её можно было легко изменить…
In[18]:=

Out[18]=

Чтобы применить этот стиль к оригинальному игровому полю, я просто рассматриваю каждый элемент игрового поля в матрице и располагаю соответствующую ему фишку на нужном месте.
In[19]:=

In[20]:=

Out[20]=

Выглядит красиво! Теперь нам нужно научиться управлять игрой.
Когда происходит нажатие одной из клавиш, я хочу, чтобы все фишки сдвигались по доске в указанном направлении настолько далеко, насколько это возможно, при этом одинаковые фишки должны объединяться. Я могу использовать опцию NotebookEventActions, чтобы регистрировать нажатия клавиш и соответствующе реагировать. Я сделаю так, чтобы управление осуществлялось клавишами wasd, вы можете выбрать любые другие:
In[21]:=

In[22]:=

Теперь давайте подумаем о том, что же происходит, когда фишки сдвигаются, скажем, влево. В первую очередь мне необходимо реализовать объединение одинаковых фишек. Каждая строка должна рассматриваться отдельно, поскольку при горизонтальном смещении вертикальные совпадения не объединяются. Я хочу найти последовательности из двух одинаковых чисел, возможно, с пустыми клетками между ними, и заменить их суммой. Здесь на помощь приходит мощь Языка Wolfram, и я могу использовать сопоставление с заданным шаблоным выражением, чтобы легко это осуществить.
In[23]:=

После сложения одинаковых фишек, всё, что я должен сделать — это добавить несколько пустых элементов справа, чтобы заполнить строку. Процедура повторяется для всех строк игрового поля.
In[24]:=

Сдвиг вправо осуществляется так же, с одним незначительным изменением — я хочу, чтобы сочетания одинаковых фишек справа объединялись до сочетаний, находящихся левее. Рассмотрим в качестве примера строку {$empty, 2, 2, 2}. Используя функцию combineLeft и нажав стрелку влево, я получу строку {$empty, $empty, 4, 2}, но на самом деле я хочу, чтобы сначала объединилась правая пара двоек. Переворот строки, сдвиг ячеек влево и обратный переворот легко решают проблему.
In[25]:=

In[26]:=

Теперь, когда у нас есть две этих функции, реализовать сдвиг вверх и вниз очень просто! Сдвиг вверх — это то же самое, что сдвиг влево на транспонированном игровом поле, за которым следует обратное транспонирование. Таким же отношением связаны и операции сдвига вправо и вниз.
In[27]:=

Если клавиша нажата, новые фишки на поле должны добавляться только в том случае, если состояние поля изменилось, то есть если некоторые фишки переместились или объединились. Для этого необходимо отслеживать предыдущее состояние игрового поля.
Теперь научимся вести подсчёт очков в течение игры. Каждый раз, когда объединяются две фишки, я собираю их сумму функцией Sow, а потом использую на всех суммах функцию Reap, когда все возможные объединения совершены. Также я вывожу значение наибольшей полученной на данный момент фишки.
In[29]:=

Наконец, давайте добавим проверку на выигрыш и проигрыш. Игра считается выигранной, если наибольшая фишка на поле имеет значение 2048 или выше. Я проиграл, если доска заполнена и у меня больше нет возможных ходов. В данном случае я снова могу использовать язык шаблонных выражений, чтобы определить, остались ли на доске возможные сочетания фишек.
In[30]:=

In[31]:=

Используя функцию Dynamic, я могу поддерживать отображаемое игровое поле в полном соответствии с внесёнными изменениями. Кроме того, я могу окружить всё это функцией DynamicModule и использовать Initialization, чтобы настроить обработку нажатий клавиш и игровое поле. Поместив DynamicModule внутрь CreateDialog мы можем открывать игру в отдельном окне.
Наконец, игра полностью готова.
In[32]:=


Сейчас всё выглядит вполне симпатично, но у нас в компании Wolfram Research мы любим что-нибудь более… заостренное. Такое, как Spikey — логотипы различных версий системы Mathematica и Wolfram|Alpha. Давайте немного изменим цвета фишек и их форму.
In[33]:=

In[34]:=

Out[34]=

In[35]:=

In[36]:=

In[37]:=

In[39]:=

Теперь мы можем переключаться между двумя стилями при помощи кнопок.
In[40]:=

Out[40]=

In[41]:=


Удачной вам игры!
Комментарии 6
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.