Поговорим о Луне! #0. Угадываем числа.

Totoro 2015-06-16   5 минут на чтение

На днях я рассказывал об интересном языке для OpenComputers (и не только).

MoonScript

Но одно дело - прочитать об языке где-то. А совсем другое - попробовать язык самому.

Именно этим я и предлагаю заняться.

Для разогрева, начнем с чего-нибудь несложного. Например "Угадай число".

Думаю все знают эту игру. Компьютер загадывает число, мы пытаемся угадать. На каждую нашу попытку, компьютер злорадно сообщает - "больше!", "меньше!" или "у вас закончились попытки!" и "вы проиграли!".

Немного модифицируем исходную идею, и перенесем ее на 2d поле. Просто, чтобы не было скучно.

ТЗ

Что нам потребуется?

  1. Отрисовать сетку Тут мы просто возьмем текущий размер дисплея, и разметим его на клеточки.
  2. Загадать число
  3. Слушать команды пользователя Юзер будет тыкать на клеточки. Нам надо будет слушать эвент touch.
  4. Обновлять игровое поле в ответ Собственно после тыка, будем открывать клетку. Если это не та клетка - рисовать на ней стрелочку. Если та - рисовать победный баннер. Если закончились ходы - рисовать что-нибудь обидное.

За дело

Первым делом надо подключить все, что мы будем использовать.

В Lua обычно мы при помощи команды require пишем все в локальные переменные.

В MoonScript все переменные по дефолту локальны. Поэтому использовать ключевое слово local нет необходимости.

Для подключения же библиотек, используется ключевое слово import:

import getResolution setForeground setBackground set fill from require('component').gpu
import pull from require 'event'
import ceil, random from math
import rep from string

Мы вытащили из нужных библиотек нужные функции. Ничего лишнего.

Теперь объявим переменные, которые будут использоваться в коде.

-- Размеры экрана
width, height = getResolution()
width /= 2    -- потому что по горизонтали наши клетки займут 2 символа
height -= 1   -- потому что внизу будет статус

-- Цвета
white = 0xFFFFFF
black = 0x000000
gray  = 0x222222
green = 0x00BB33
yellow = 0xFFC04C
red = 0xFF0000
pink = 0xFF0074
violet = 0xD600FF
blue = 0x4E5AFF
cyan = 0x4ED7FF
teal = 0x00CC99

-- Заготовка для сетки - один ряд клеток
grid_line = rep("▒▒  ", ceil(width / 2))

-- Наша цель
target = { x: 0, y: 0 }

-- Количество попыток
-- (150 - магический коэффициент сложности, больше - сложнее, меньше - легче)
maxAttemts = ceil(width * height / 150)
attempts = maxAttemts

Тут тоже присутствует несколько новых фич MoonScript.

Во-первых - это сдвоенные операции. Конструкции a /= b или a -= b означают тоже самое, что a = a / b и a = a - b.

Во-вторых это новый синтаксис создания таблиц. Названия полей и их значения отделены двоеточиями. (Такое обозначение будет знакомо тем, кто владеет JavaScript).

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

Тут мы столкнемся еще с несколькими новшествами, по сравнению с Луа.

Первое, в MoonScript нету ключевого слова end. Блоки кода обозначаются отступом разной величины.

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

Второе, функции объявляются конструкцией вида (a, b, c) -> .... Тут слева - набор аргументов, потом стрелочка - разделитель и блок кода, который собственно является телом функции.

-- Очищаем экран
clear = () ->
  setForeground white
  setBackground black
  fill 1, 1, width * 2, height + 1, ' '

-- Рисуем сетку
grid = ->
  setForeground gray
  setBackground black
  for y = 1, height
    set (if y % 2 == 0 then 1 else 3), y, grid_line

Пустой набор аргументов можно опустить, как в функции grid.

Кроме того, как несложно заметить, MoonScript позволяет вызывать функции, не используя скобочки.

Продолжим.

-- Открываем одну клетку
sign = (x, y) ->
  if x == target.x and y == target.y then black, white, "[]"
  -- по неведомой мне причине, стрелки вниз в новом шрифте ОС 1.6 нету =)
  elseif x == target.x and y < target.y then white, green, "▼▼"
  elseif x == target.x and y > target.y then white, violet, "↑↑"
  elseif x < target.x and y < target.y then white, teal, "↘↘"
  elseif x < target.x and y == target.y then white, cyan, "→→"
  elseif x < target.x and y > target.y then white, blue, "↗↗"
  elseif x > target.x and y < target.y then white, yellow, "↙↙"
  elseif x > target.x and y == target.y then white, red, "←←"
  elseif x > target.x and y > target.y then white, pink, "↖↖"
  
cell = (x, y) ->
  fore, back, text = sign x, y
  setForeground fore
  setBackground back 
  set x * 2 - 1, y, text

Здесь функция sign сконструирована так, чтобы отдавать три переменных разом.

Следует заметить, что в MoonScript можно не пользоваться оператором return.
Функция автоматически вернет значение последнего оператора в теле.

Кроме функций, значения умеют возвращать и условия. Поэтому в данном случае, функция возвращает значение условия, а условие возвращает три значения из той ветки, которая выполнится.

Функция cell просто берет эти значения и отрисовывает в нужном месте клетку.

Далее.

-- Рисуем статус
status = (state) ->
  setForeground white
  setBackground black
  fill 1, height + 1, width * 2, height + 1, ' '
  set 2, height + 1, "[Угадай, где клад!]"
  switch state
    when 'win'
      setForeground green
      set 24, height + 1, "Вы победили!"
    when 'lose'
      setForeground red
      set 24, height + 1, "Вы проиграли!"
    else
      set 24, height + 1, "Попыток осталось: #{attempts}"
  set width * 2 - 10, height + 1, "[R] [Q]"

Здесь тоже используются две новые конструкции.

Первая - это switch. Наверняка многие уже знакомы с ним. По сути, это просто удобный вариант длинных условий, со множеством elseif. Свитч получает значение, а потом сравнивает с ним все ветки when. Какая совпадет - та и выполнится.

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

Последние приготовления:

-- Генерируем цель
setTarget = ->
  target = { x: random(1, width), y: random(1, height) }
  
-- Инициализируем игру
newGame = ->
  attempts = maxAttemts
  setTarget!
  clear!
  grid!
  status!

Функция newGame использует специальный синтаксис для вызова функции, которой не нужны аргументы.

Вместо того, чтобы писать setTarget(), MoonScript советует использовать восклицательный знак. setTarget!.

Это довольно весело смотрится в коде. =)

Ну чтож, все готово.
Давайте соберем все написанное, и запилим немного игровой логики!

while true
  -- Ждем события
  event, _, x, y = pull!
  -- Обрабатываем его
  switch event
    when 'touch'   -- Если был клик
      -- Открываем клетку, если остались попытки
      if attempts > 0
        x = ceil(x / 2)
        cell x, y
        attempts -= 1
        -- Обновляем инфу
        if x == target.x and y == target.y
          attempts = 0
          status('win')
        elseif attempts == 0
          status('lose')
        else
          status!
 
    when 'key_down'
      switch x
        when 113   -- Q: выход из игры
          break
        when 114   -- R: перезапуск
          newGame!
 
clear!

Вуаля! Оно работает. И даже можно поиграть. И даже победить =)

Круто, правда?

Не надо делать такое выражение лица, я знаю что на самом деле вы со мной согласны. =)

А вы, да-да, вы! - на задних рядах, хватит кидаться тапками!

Полный код игрушки доступен тут:
http://pastebin.com/M0sxk1QH

Enjoy!