Статьи

Практический CoffeeScript: создание игры в крестики-нолики

CoffeeScript — маленький крошечный язык, который компилируется в JavaScript. Во время выполнения интерпретации не существует, поскольку вы пишете CoffeeScript, компилируете его в JavaScript и используете полученные файлы JavaScript для своего приложения. Вы можете использовать любую библиотеку JavaScript (например, jQuery) из CoffeeScript, просто используя ее функции с соответствующим синтаксисом CoffeeScript. CoffeeScript может использоваться как для написания JavaScript на внешнем интерфейсе, так и на JavaScript на внутреннем.

Так почему CoffeeScript?

Меньше кода

Согласно » Маленькой книге» о CoffeeScript , синтаксис CoffeeScript уменьшает количество символов, которое необходимо набрать, чтобы заставить работать JS, примерно на 33-50%. Я представлю простую игру Tic-Tac-Toe, созданную с использованием CoffeeScript (вы, наверное, уже догадались об этом из названия), которая в своем необработанном формате CoffeeScript содержит 4963 символа, тогда как скомпилированный код JavaScript содержит 7669 символов. Это разница в 2706 символов или 36%!

Время разработки быстрее

Поскольку вы пишете короче, менее подвержены ошибкам (например, переменные имеют автоматическую область видимости, что означает, что вы не можете случайно перезаписать глобальные переменные, пропустив var ), вы сможете быстрее завершить свои проекты. Краткий синтаксис CoffeeScript также обеспечивает более читаемый код и, в конечном итоге, код, который легче поддерживать.

Начиная

В этой статье мы будем создавать простую игру в крестики-нолики с CoffeeScript и jQuery. Если вы хотите ознакомиться с синтаксисом до изучения практического примера, я предлагаю мою статью « Ускорить разработку JavaScript с использованием CoffeeScript» здесь, на SitePoint. Здесь также подробно описано, как установить CoffeeScript через npm (менеджер пакетов Node).

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

Наиболее распространенные команды CoffeeScript, которые вы будете использовать:

coffee -c fileName скомпилирует файл CoffeeScript в файл с тем же именем, но с расширением .js (файлы CoffeeScript обычно имеют расширение .coffee ).

coffee -cw fileName будет следить за изменениями в файле (при каждом сохранении файла) и компилировать его.

coffee -cw folderName/ будет следить за изменениями всех файлов .coffee в папке и компилировать их в тот же каталог, когда будут какие-либо изменения.

Наконец, удобно скомпилировать CoffeeScript из папки с файлами .coffee в папку, содержащую только файлы .js .

coffee -o js/ -cw /coffee будет следить за изменениями во всех файлах .coffee расположенных в папке coffee и помещать вывод (JavaScript) в папку js .

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

Снимок экрана Препроса

Вы можете видеть, что Prepros делает всю работу за вас — он настраивает средства наблюдения, чтобы ваши файлы .coffee были скомпилированы в JS, он позволяет вам использовать Uglify JS, который минимизирует / сжимает ваш код, он может автоматически манипулировать переменными и поддерживает Замороженный CoffeeScript . Prepros также можно использовать для препроцессоров CSS, таких как Less и Sass, и шаблонизаторов, таких как Jade.

Игра

Давайте начнем с разметки:

 <div class="wrapper"> <header class="text-center"> <h1>Tic Tac Toe</h1> </header> <div id="board"></div> <div class="alerts welcome"></div> <div class="notifications"></div> <form action="" method="POST"> ... </form> </div> <script src="jquery.min.js"></script> <script src="logic/app.js"></script> 

Интерфейс игры состоит из следующего:

  • Заголовок, который кратко описывает игру
  • Элемент div с id board которой будут расположены квадраты 3 × 3
  • Элемент div с классом alerts котором будет отображаться статус игры
  • Элемент div с классом notifications который покажет, кто играет в X и O, вместе с общей статистикой игрока.
  • Форма, которая будет отображаться только при загрузке игры и предложит игрокам ввести свои имена.

В соответствии с лучшими практиками, jQuery и скрипт, который делает тик нашего приложения, загружаются перед закрывающим тегом body.

Стиль

Используя CSS, мы можем сделать так, чтобы девять задействованных квадратов отображались в сетке 3 × 3, плавая каждый квадрат и очищая каждый четвертый.

 .square:nth-of-type(3n + 1) { clear: both; } 

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

 .square.x { color: crimson; } .square.o { color: #3997ff; } 

CoffeeScript в действии

Для справки вы можете найти основной файл CoffeeScript здесь .

Вы можете видеть, что наше приложение Tic-Tac-Toe начинается с $ -> , это эквивалентно сокращению для функции jQuery, которая выполняет код, когда DOM готов: $(function() { ... }); ,

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

Затем мы создаем объект с именем Tic который сам содержит объект с именем data . Вы можете видеть, что фигурные скобки или запятые не являются обязательными при создании объектов, если вы правильно сделаете отступ в свойствах.

 $ -> Tic = data: turns: 0 x: {} o: {} gameOver: false 

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

Свойства x и o являются объектами и будут содержать данные, относящиеся к числу X или O по трем осям, которые важны для игры: горизонтальная, вертикальная и диагональная. Они будут обновляться при каждом перемещении через метод checkEnd для представления распределения X и O на доске. Затем метод checkEnd вызовет checkWin чтобы определить, есть ли победитель.

После этого у нас есть метод внутри объекта Tic , который все запустит и запустит:

 initialize: -> @data.gameOver = false @.setPlayerNames() @.retrieveStats() @.assignRoles() @.prepareBoard() @.updateNotifications() @.addListeners() 

Обратите внимание на использование @ которое компилируется в ключевое слово JavaScript this . Как показано в первом свойстве initialize , вы можете пропустить точку после ключевого слова @ при установке или вызове свойства или метода.

Давая методам разумные имена, мы имеем четкое представление о том, что они делают:

  • setPlayerNames сохраняет значения, введенные пользователями во входных data объект data .
  • retrieveStats извлекает статистику игрока из localStorage и устанавливает ее в объекте data .
  • assignRoles определяет, кто играет на X, а кто на O.
  • prepareBoard скрывает форму, удаляет любые уведомления, очищает доску и заполняет ее девятью пустыми клетками.
  • updateNotifications обновляет пользовательский интерфейс с информацией о том, кто играет в X и кто играет в О, а также статистику игрока.
  • addListeners присоединяет слушателей событий, чтобы мы могли отвечать на addListeners игроков.

Дайвинг глубже

Давайте посмотрим на пару из этих методов более подробно.

 prepareBoard: -> ... $("<div>", {class: "square"}).appendTo("#board") for square in [0..8] 

Здесь мы повторяем девять раз и добавляем девять делений с классом square к пустой доске, чтобы заполнить ее. Это демонстрирует, как CoffeeScript позволяет писать однострочные циклы и объявлять тело цикла перед написанием самого условия.

 updateNotifications: -> $(".notifications").empty().show() @.addNotification "#{@data.player1} is playing #{@data.rolep1}" ... 

CoffeeScript допускает интерполяцию строк, что повышает удобочитаемость и уменьшает сложность и длину кода. Вы можете добавить #{} в любую строку и вставить любую переменную или возвращаемое значение из вызова функции в фигурных скобках.

 addNotification: (msg) -> $(".notifications").append($("<p>", text: msg)); 

Метод addNotification иллюстрирует, как вы определяете параметры в CoffeeScript. Вы пишете их перед стрелкой ( -> ):

Вы можете предоставить значения по умолчанию для параметров, аналогичных PHP:

 addNotification: (msg = "I am a message") -> 

Когда функция с параметром по умолчанию компилируется, она преобразуется в:

 if (msg == null) { msg = "I am a message"; } 

Наконец, давайте обратимся к методу addListeners :

 addListeners: -> $(".square").click -> if Tic.data.gameOver is no and not $(@).text().length if Tic.data.turns % 2 is 0 then $(@).html("X").addClass("x moved") else if Tic.data.turns % 2 isnt 0 then $(@).html("O").addClass("o moved") ... 

Здесь мы видим, что CoffeeScript предлагает дополнительные ключевые слова для представления истинных и ложных значений, таких как no , yes , off и on . Кроме того !== , === , && ! может быть представлен с использованием isnt , is и not соответственно.

Вы можете создать удобочитаемые однострочные условия, используя синтаксис if ... then ... else ...

Механика игры

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

 checkEnd : -> @.data.x = {} @.data.o = {} #diagonal check diagonals = [[0,4,8], [2,4,6]] for diagonal in diagonals for col in diagonal @.checkField(col, 'diagonal') @.checkWin() @.emptyStorageVar('diagonal') for row in [0..2] start = row * 3 end = (row * 3) + 2 middle = (row * 3) + 1 #vertical check @checkField(start, 'start') @checkField(middle, 'middle') @checkField(end, 'end') @checkWin() #horizontal check for column in [start..end] @checkField(column, 'horizontal') @checkWin() @emptyStorageVar('horizontal') 

Как видите, здесь используется другая удобная функция CoffeeScript — диапазоны.

 for row in [0..2] 

Это зациклится три раза, установив ряд, равный 0, 1 и 2 в этом порядке. В качестве альтернативы, [0...2] (исключительный диапазон) приведет к двум итерациям, установив строку равной 0 и 1.

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

Вот как выглядит checkField :

 checkField: (field, storageVar) -> if $(".square").eq(field).hasClass("x") if @.data.x[storageVar]? then @.data.x[storageVar]++ else @.data.x[storageVar] = 1 else if $(".square").eq(field).hasClass("o") if @.data.o[storageVar]? then @.data.o[storageVar]++ else @.data.o[storageVar] = 1 

Этот метод демонстрирует использование ? ключевое слово, которое при вставке рядом с переменной в условном выражении компилируется в:

 if (typeof someVariable !== "undefined" && someVariable !== null) { 

Что, очевидно, очень удобно.

Метод checkField добавляет единицу к соответствующей оси свойства x или o зависимости от имени класса квадрата, по которому щелкнули. Имя класса добавляется, когда пользователь нажимает на пустой квадрат доски в методе addListeners .

Это подводит нас к методу checkWin , который используется для проверки, выиграл ли кто-либо из игроков:

 checkWin: -> for key,value of @.data.x if value >= 3 localStorage.x++ @showAlert "#{@.getPlayerName("X")} wins" @data.gameOver = true @addToScore("X") for key,value of @.data.o if value >= 3 localStorage.o++ @showAlert "#{@.getPlayerName("O")} wins" @data.gameOver = true @addToScore("O") 

В CoffeeScript вы можете использовать for ... in array для циклического перебора значений массива, а for key,value of object для циклического перебора свойств объекта. checkWin использует это для проверки всех свойств внутри объектов x и o . Если кто-либо из них имеет число, большее или равное трем, у нас есть победитель, и игра должна закончиться. В таком случае мы вызываем метод addToScore который сохраняет результаты игроков через localStorage .

Слово о локальном хранилище

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

Вы можете получить доступ к API несколькими способами, например, так же, как и к свойствам обычного объекта:

 //fetch item localStorage.myProperty // set item localStorage.myProperty = 123 

Локальное хранилище всегда сохраняет строки, поэтому, если вы хотите сохранить объект или массив, вам придется использовать JSON.stringify при хранении массива / объекта и JSON.parse при его получении.

Наш метод addToScore использует этот факт:

 addToScore: (winningParty) -> ... if winningParty is "none" @.showAlert "The game was a tie" else ... localStorage[@data.player1] = JSON.stringify @data.p1stats 

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

Далее у нас есть пара полезных методов. Мы используем emptyStorageVar чтобы очистить содержимое определенной горизонтальной строки или диагонали. Это необходимо, потому что на плате две диагонали, и внутри нашего метода chekEnd мы используем одно и то же свойство данных для обеих диагоналей. Поэтому мы должны очистить свойство перед проверкой второй диагонали. То же самое касается горизонтальных рядов.

 emptyStorageVar: (storageVar) -> @.data.x[storageVar] = null @.data.o[storageVar] = null 

Получение имен игроков

Когда форма с именами игроков отправляется в начале игры, мы можем предотвратить ее действие по умолчанию и обработать отправку с помощью JavaScript. Мы проверяем, есть ли пустое имя, и совпадают ли оба имени, и отображаем дружеское предупреждение, если это так. В противном случае мы запускаем игру, вызывая Tic.initialize() .

 $("form").on "submit", (evt) -> evt.preventDefault() $inputs = $("input[type='text']") namesNotEntered = $inputs.filter(-> return @.value.trim() isnt "" ).length isnt 2 namesIndentical = $inputs[0].value is $inputs[1].value if namesNotEntered then Tic.showAlert("Player names cannot be empty") else if namesIndentical then Tic.showAlert("Player names cannot be identical") else Tic.initialize() 

В последней строке используется делегирование события, чтобы любой элемент с классом play-again реагировал на нажатие. Делегирование событий необходимо, так как этот элемент добавляется на страницу только после завершения игры. Он отсутствует при первом отображении DOM.

 $("body").on("click", ".play-again", -> Tic.initialize()) 

Собираем все вместе

Вот и все. Менее чем в 150 строках CoffeeScript у нас есть рабочая игра. Не забудьте, вы можете скачать код из этого урока с GitHub .

Вывод

Я надеюсь, что это руководство укрепило ваши знания CoffeeScript и покажет, как jQuery и CoffeeScript могут работать вместе. Есть много вещей, которые вы можете сделать, чтобы улучшить игру. Например, вы можете добавить опцию, чтобы сделать доску отличной от стандартных размеров 3 × 3. Вы могли бы реализовать некоторый простой ИИ, чтобы игроки могли играть против машины, или вы могли бы внедрять бомбы в игре, например, добавляя случайные Х или О на случайном игровом ходу, пока игроки сражаются за славу.