Донецкий техникум промышленной автоматики

Гра «Життя» Джона Конвея на CoffeeScript і canvas

  1. Часто використовувані скорочення
  2. Гра «Життя» Джона Конвея
  3. Малюнок 1. Приклад реалізації гри «Життя» Джона Конвея
  4. Розмітка HTML5 і CSS
  5. Лістинг 1. Розмітка і CSS в файлі index.html
  6. Реалізація гри «Життя» за допомогою CoffeeScript
  7. компіляція CoffeeScript
  8. ініціалізація гри
  9. Лістинг 2. Клас GameOfLife і його атрибути
  10. Лістинг 3. Конструктор класу GameOfLife
  11. Лістинг 4. Підготовка елемента canvas
  12. Створення початкової конфігурації
  13. Лістинг 5. Початкова конфігурація
  14. Ігровий цикл
  15. Лістинг 6. Метод tick ігрового циклу
  16. Лістинг 7. Малювання поля
  17. Лістинг 8. Перетворення поточного покоління клітин
  18. Лістинг 9. Перетворення однієї клітини
  19. Лістинг 10. Підрахунок числа живих сусідів клітини
  20. висновок
  21. Ресурси для скачування

Створення невеликої гри за допомогою CoffeeScript і HTML5-елемента canvas

Невеликі гри відмінно підходять для вивчення нових технологій. Ця стаття проведе вас через процес створення власної версії гри «Життя» Джона Конвея за допомогою CoffeeScript і елемента HTML5 canvas. Хоча гра «Життя» по суті не є грою, вона являє собою чудову зручну для вирішення невелику задачу.

Часто використовувані скорочення
  • CSS: каскадні таблиці стилів
  • DOM: об'єктна модель документа
  • HTML: мова гіпертекстової розмітки

Що стосується самої технології, то вам знадобляться:

  • відповідний Web-браузер, що підтримує роботу з елементом HTML5 canvas;
  • CoffeeScript - міні-мову програмування, що компілює вихідний текст в JavaScript. Рекомендується витратити 10 хвилин на вивчення можливостей CoffeeScript на його сайті або прочитати безкоштовну онлайнову книгу «Маленька книга про CoffeeScript» (див. Розділ ресурси ).
  • компілятор CoffeeScript, що дозволяє створювати програми CoffeeScript. Рекомендується встановити Node.js і потім використовувати Node Package Manager (NPM) для установки компілятора (див. Розділ ресурси ). Приклад, наведений у цій статті, використовує компілятор CoffeeScript версії 1.3.3;
  • ваш улюблений текстовий редактор.

Вихідний код з прикладами для цієї статті можна скачати тут .

Гра «Життя» Джона Конвея

По суті, гра «Життя» є модель, що працює на плоскій сітці, що складається з квадратних клітин. Кожна клітина може перебувати в одному з двох станів: живому або мертвому. Гра складається з послідовності періодичних оновлень, які називаються також тактами. З кожним тактом поточне покоління клітин еволюціонує в наступне покоління. У кожному такті до кожної клітини застосовуються такі правила:

  • жива клітина вмирає від самотності, якщо у неї менше двох живих сусідів;
  • жива клітина з двома або трьома живими сусідами продовжує жити;
  • жива клітина з числом живих сусідів більше трьох вмирає через перенаселеності;
  • в мертвій клітці з трьома живими сусідами зароджується життя в результаті розмноження.

Перше покоління клітин створюється випадковим чином. Потім модель починає працювати, поки всі клітини не помруть або поки конфігурація не почне повторюватися. Детальна інформація про різних конфігураціях і історія створення цієї гри наведені в розділі ресурси .

на малюнку 1 показаний кінцевий результат вправи, описаного в цій статті. Також можна знайти ці результати в інтернеті і спробувати зіграти самому (посилання на авторську версію гри приведена в розділі ресурси ).

Малюнок 1. Приклад реалізації гри «Життя» Джона Конвея
Створення невеликої гри за допомогою CoffeeScript і HTML5-елемента canvas   Невеликі гри відмінно підходять для вивчення нових технологій

Реалізація

Наведена в цій статті реалізація гри «Життя» Джона Конвея складається з двох частин: перша частина складається з розмітки HTML5 і CSS і є фундаментом для другої частини; друга частина власне і є реалізацією гри на CoffeeScript .

Розмітка HTML5 і CSS

Першим кроком є ​​створення директорії з ім'ям game-of-life (гра «Життя»), де ви будете зберігати всі файли цього прикладу. Розмітка і CSS вимагають певного місця розташування, тому створіть в новий каталог game-of-life новий файл index.html. В лістингу 1 показані розмітка HTML5 і CSS, необхідні для реалізації гри «Життя».

Лістинг 1. Розмітка і CSS в файлі index.html

<Html> <head> <title> Game of Life </ title> <script type = "text / javascript" src = "javascripts / game_of_life.js"> </ script> <style type = "text / css"> body {background-color: rgb (38, 38, 38); } Canvas {border: 1px solid rgba (242, 198, 65, 0.1); margin: 50px auto 0 auto; display: block; } </ Style> </ head> <body> <script type = "text / javascript"> new GameOfLife (); </ Script> </ body> </ html>

код в лістингу 1 створює заголовок гри і підключає файл JavaScript з ім'ям game_of_life.js з директорії javascripts. Не турбуйтеся, програмувати ми будемо на CoffeeScript. Увімкніть цей рядок в файл index.html (нехай вас не бентежить те, що файл game_of_life.js ще не існує).

Тепер давайте трохи прикрасимо гру, щоб вона виглядала симпатично. Елемент body набуває темний фон, елемент canvas (полотно) обзаводиться рамкою. За допомогою атрибутів CSS margin і display ми добиваємося зручного розташування полотна в центрі екрану.

Для запуску гри «Життя» код додає невеликий блок сценарію в тіло розмітки і створює новий екземпляр GameOfLife.

Реалізація гри «Життя» за допомогою CoffeeScript

Ймовірно, вас здивувало, що в лістингу 1 підключається файл JavaScript game_of_life.js, а не файл CoffeeScript. Багато браузери все ще не розуміють CoffeeScript, тому потрібно скомпілювати код CoffeeScript в JavaScript, щоб браузер міг його інтерпретувати. Саме з цієї причини вам знадобляться ще дві директорії всередині директорії game-of-life. У першій (з ім'ям coffeescripts) буде зберігатися код CoffeeScript, а в другій (javascripts) - скомпільований код JavaScript.

Наш приклад реалізації містить всього один клас, тому вам доведеться створити всього один відповідний файл. Файл game_of_life.coffee, який буде містити весь код CoffeeScript, розташований в директорії coffeescripts.

компіляція CoffeeScript

Перш ніж приступати до реалізації алгоритму гри, потрібно знайти спосіб компіляції коду CoffeeScript в JavaScript. На щастя, у компілятора CoffeeScript є для цього відповідні опції. Команда автоматичної компіляції коду CoffeeScript в JavaScript виглядає так:

coffee --output javascripts / --watch --compile coffeescripts /

Щоб подати цю команду, відкрийте свій улюблений інструмент командного рядка і перейдіть в директорію game-of-life. Введіть команду coffee, щоб скомпілювати код CoffeeScript в JavaScript. На першому місці в нашому прикладі вказана вихідна папка з прапором --output. Потім використовуються прапори --watch і --compile, що вказують на директорію coffeescripts.

Що все це означає? Кожен раз, коли файл в директорії coffeescripts змінюється, команда coffee бере його, компілює і зберігає скомпільований файл JavaScript в директорію javascripts. Тепер ви знаєте, чому ми не створюємо файл game_of_life.js, який підключається в лістингу 1 . Коли ви заносите код CoffeeScript в файл game_of_life.coffee, він компілюється, і результуючий код JavaScript автоматично зберігається в файлі game_of_life.js.

ініціалізація гри

Тепер, коли ми розібралися з компілятором, можна приступити до програмування нашої версії гри «Життя». Відкрийте файл game_of_life.coffee в текстовому редакторі. Як показано в лістингу 2 , Тут присутній єдиний клас GameOfLife з декількома атрибутами.

Лістинг 2. Клас GameOfLife і його атрибути

class GameOfLife currentCellGeneration: null cellSize: 7 numberOfRows: 50 numberOfColumns: 50 seedProbability: 0.5 tickLength: 100 canvas: null drawingContext: null

Ті, що говорять імена змінних і методів дозволяють легко розібратися в цьому вихідному коді. Наведемо деякі пояснення до лістингу 2 :

  • оскільки гра «Життя» складається з двовимірного набору клітин, наш приклад повинен мати подібну ж структуру. Атрибут currentCellGeneration буде містити всі клітини двовимірного масиву;
  • cellSize вказує ширину і висоту окремої клітини - в нашому випадку це сім пікселів;
  • атрибути numberOfRows і numberOfColumns визначають розмір поля;
  • грі «Життя» потрібна початкова ( «приманки») конфігурація клітин. Атрибут seedProbability використовується при створенні затравочной конфігурації для визначення того, мертва клітина або жива;
  • атрибут tickLength визначає інтервал поновлення гри. У нашому прикладі гра оновлюється кожні сто мілісекунд;
  • атрибут canvas буде містити елемент canvas (полотно), який вам належить створити;
  • щоб намалювати малюнок на полотні, вам знадобиться графічний контекст, який зберігається в атрибуті drawingContext.

За настройку гри відповідає конструктор класу GameOfLife. Як показано в лістингу 3 , Спочатку потрібно створити полотно, а вже потім розтягнути його до потрібних розмірів. Потім можна використовувати новостворений полотно для створення графічного контексту. Після цього ви будете готові до створення початкової структури і запуску циклу гри шляхом запуску першого такту. Але спочатку давайте створимо полотно.

Лістинг 3. Конструктор класу GameOfLife

constructor: -> @createCanvas () @resizeCanvas () @createDrawingContext () @seed () @tick ()

Оскільки сучасні браузери пропонують чудовий API для управління об'єктною моделлю документа (DOM) і оскільки в нашому прикладі немає нічого хитромудрого, ви можете використовувати зовнішні системи, такі як jQuery. Використовуйте метод document.createElement для створення нового елемента canvas, який потім зберігається в однойменному атрибуті. Додайте новостворений елемент до тіла сторінки. Все це відбувається в методі createCanvas, як показано в лістингу 4 .

Лістинг 4. Підготовка елемента canvas

createCanvas: -> @canvas = document.createElement 'canvas' document.body.appendChild @canvas resizeCanvas: -> @ canvas.height = @cellSize * @numberOfRows @ canvas.width = @cellSize * @numberOfColumns createDrawingContext: -> @drawingContext = @ canvas.getContext '2d'

Метод resizeCanvas використовує атрибути cellSize, numberOfRows і numberOfColumns для розрахунку ширини і висоти елемента canvas. Третій метод в лістингу 4 , CreateDrawingContext, отримує 2-й контекст з canvas і зберігає його для подальшого застосування.

Крім цих трьох методів, конструктор в лістингу 4 використовує два додаткові методи: seed і tick. Вони містять велику частину коду і будуть обговорюватися в наступних розділах.

Створення початкової конфігурації

Грі «Життя» потрібна початкова конфігурація. Виходячи з цієї конфігурації, клітини на поле починають еволюціонувати, з кожним тактом переходячи в нове покоління. Для початкової конфігурації (затравки) потрібно прийняти для кожної клітини випадкове рішення - чи буде вона жива чи мертва. Як показано в лістингу 5 , Для цього використовується метод seed. Два вкладених циклу дозволяють відвідати кожну клітину поля.

Зовнішній цикл перебирає всі рядки, що виконується за допомогою функції CoffeeScript, званої ranges. Три крапки (.) В вираженні for row in [0 ... @ numberOfRows] вказують на те, що діапазон не включає крайнє значення. Так, якщо numberOfRows має значення 3, змінна циклу (в даному випадку row) буде приймати значення в діапазоні від 0 до 2. Це дозволяє створити двовимірний масив currentCellGeneration.

Внутрішній цикл перебирає всі стовпці і створює для кожної клітини нового значення seedCell. Він викликає метод createSeedCell з поточним значенням рядка і стовпця. Після створення всіх клітин початкової конфігурації він зберігає їх у відповідному місці в currentCellGeneration.

Лістинг 5. Початкова конфігурація

seed: -> @currentCellGeneration = [] for row in [0 ... @ numberOfRows] @currentCellGeneration [row] = [] for column in [0 ... @ numberOfColumns] seedCell = @createSeedCell row, column @currentCellGeneration [row ] [column] = seedCell createSeedCell: (row, column) -> isAlive: Math.random () <@seedProbability row: row column: column

Створення нової клітини виконується дуже просто. Клітина є простим об'єктом і складається з трьох атрибутів. Метод createSeedCell в лістингу 5 просто передає аргументи row (рядок) і column (стовпець) об'єкту cell (клітка). Атрибут isAlive визначає, жива клітина або мертва. За допомогою методу Math.random і атрибута seedProbability ви випадковим чином створюєте живі і мертві клітини. Можливо, ви звернули увагу на відсутність ключового слова return. Це пов'язано з тим, що методи CoffeeScript автоматично повертають своє кінцеве значення.

Ігровий цикл

Тепер, коли ви створили початкову структуру, прийшов час запустити гру. Потрібно намалювати на полотні поточне покоління клітин і перетворити його в наступне покоління. Як показано в лістингу 6 , Це перетворення починається з виклику методу tick. Метод tick в лістингу 6 , Робить три речі:

  • малює поточне покоління клітин за допомогою методу drawGrid.
  • перетворює поточне покоління клітин в наступне;
  • витримує паузу (таймаут), визначальну швидкість роботи гри.

Метод setTimeout вимагає використання двох аргументів. Перший аргумент є метод, який потрібно викликати - в даному випадку це сам метод tick. Другий аргумент визначає час в мілісекундах, яке повинно пройти перед викликом методу. За допомогою атрибута tickLength можна регулювати швидкість гри.

Ймовірно, ви помітили різницю між методом tick і всіма іншими методами. Метод tick використовує функцію CoffeeScript, позначену жирної стрілкою (=>). Жирна стрілка прив'язує метод до поточного контексту, який завжди буде коректним (без цього таймаут працювати не буде).

Лістинг 6. Метод tick ігрового циклу

tick: => @drawGrid () @evolveCellGeneration () setTimeout @tick, @tickLength

Малювання поля виконується просто. Метод drawGrid в лістингу 7 використовує два вкладених циклу для відвідування кожної клітини поля і передає клітку в метод drawCell. Метод drawCell розраховує координати x і y за допомогою cellSize, а також атрибути рядка і стовпця клітини. Залежно від атрибута isAlive ви встановлюєте стиль заповнення клітини. Перед використанням методів strokeRect і fillRect для малювання клітини встановіть атрибути полотна strokeStyle і fillStyle.

Лістинг 7. Малювання поля

drawGrid: -> for row in [0 ... @ numberOfRows] for column in [0 ... @ numberOfColumns] @drawCell @currentCellGeneration [row] [column] drawCell: (cell) -> x = cell.column * @ cellSize y = cell.row * @cellSize if cell.isAlive fillStyle = 'rgb (242, 198, 65)' else fillStyle = 'rgb (38, 38, 38)' @ drawingContext.strokeStyle = 'rgba (242, 198, 65, 0.1) '@ drawingContext.strokeRect x, y, @cellSize, @cellSize @ drawingContext.fillStyle = fillStyle @ drawingContext.fillRect x, y, @cellSize, @cellSize

Для перетворення поточного покоління клітин використовуються три методи. Метод evolveCellGeneration показаний в лістингу 8 . Подібно до методу seed, він використовує два вкладених циклу для створення двовимірного масиву з ім'ям newCellGeneration, в якому буде зберігатися перетворене покоління клітин. Внутрішній цикл передає клітку методу evolveCell, який повертає перетворену клітку. Потім перетворена клітина зберігається в потрібній позиції в масиві newCellGeneration. Після перетворення всіх клітин поточного покоління можна оновити атрибут currentCellGeneration.

Лістинг 8. Перетворення поточного покоління клітин

evolveCellGeneration: -> newCellGeneration = [] for row in [0 ... @ numberOfRows] newCellGeneration [row] = [] for column in [0 ... @ numberOfColumns] evolvedCell = @evolveCell @currentCellGeneration [row] [column] newCellGeneration [row] [column] = evolvedCell @currentCellGeneration = newCellGeneration

Метод evolveCell в лістингу 9 починається зі створення змінної evolvedCell з тими ж атрибутами, що і у переданій клітини. Щоб вирішити, помре клітина, народиться або залишиться живий, потрібно знати, скільки у неї живих сусідів. Щоб дізнатися число живих сусідів, викличте метод countAliveNeighbors для даної клітини. Цей метод підраховує і повертає число живих сусідів.

Дізнавшись число живих сусідів, ви зможете відповідно до правил гри оновити атрибут isAlive перетворюється клітини. Після поновлення цього атрибута просто поверніть об'єкт evolvedCell.

Лістинг 9. Перетворення однієї клітини

evolveCell: (cell) -> evolvedCell = row: cell.row column: cell.column isAlive: cell.isAlive numberOfAliveNeighbors = @countAliveNeighbors cell if cell.isAlive or numberOfAliveNeighbors is 3 evolvedCell.isAlive = 1 <numberOfAliveNeighbors <4 evolvedCell

Метод countAliveNeighbors в лістингу 10 отримує в якості аргументу одну клітку і повертає число живих сусідів. Зазвичай клітина має вісім сусідів, але якщо вона розташована біля краю поля, число сусідів буде менше. Підрахунок живих сусідів не так вже й простий.

Для отримання елегантного і добре читається вирішення цього завдання потрібно розрахувати область, в якій ви шукаєте живих сусідів. Для клітини в середині поля кордону пошуку розраховуються просто. Клітка, що знаходиться в рядку 4 і колонці 5, має сусідів в рядках 3, 4, 5 і шпальтах 4, 5, 6.

Зовсім інакше справа йде при розташуванні клітини в рядку 0 і стовпці 0. Її сусіди знаходяться в діапазоні рядків від 0 до 1 і в діапазоні стовпців від 0 до 1. Нижня межа рядка визначається номером рядка мінус один, але вона не може бути менше нуля. Цей алгоритм можна реалізувати за допомогою методу Math.max, як показано в лістингу 10 . Розрахунок нижньої межі стовпця виконується аналогічно.

Верхня межа розраховується методом Math.min. Переконайтеся, що число, яке дорівнює номеру рядка клітини плюс один, не перевищує номера останнього рядка. Отримавши верхню і нижню межу для рядків і стовпців, можна перебрати всі клітини в цій області за допомогою двох вкладених циклів. В даному випадку ми використовуємо оператор неявного діапазону CoffeeScript, щоб гарантувати облік значень upperRowBound і upperColumnBound.

Саму клітку підраховувати не потрібно, тому слід вставити у внутрішній цикл вираз continue, яке викликається, коли значення рядка і стовпця збігаються з атрибутами клітини. Після цього збільште лічильник numberOfAliveNeighbors на одиницю, якщо поточна перевіряється клітина жива. В кінці циклу потрібно просто повернути значення цього лічильника.

Лістинг 10. Підрахунок числа живих сусідів клітини

countAliveNeighbors: (cell) -> lowerRowBound = Math.max cell.row - 1, 0 upperRowBound = Math.min cell.row + 1, @numberOfRows - 1 lowerColumnBound = Math.max cell.column - 1, 0 upperColumnBound = Math. min cell.column + 1, @numberOfColumns - 1 numberOfAliveNeighbors = 0 for row in [lowerRowBound..upperRowBound] for column in [lowerColumnBound..upperColumnBound] continue if row is cell.row and column is cell.column if @currentCellGeneration [row ] [column] .isAlive numberOfAliveNeighbors ++ numberOfAliveNeighbors

Оскільки CoffeeScript упаковує кожен файл у власну оболонку, потрібно експортувати клас GameOfLife, щоб його можна було використовувати за межами власного файлу. Додайте атрибут GameOfLife до об'єкту window наступним чином: window.GameOfLife = GameOfLife.

От і все! Ви завершили приклад реалізації гри «Життя» Джона Конвея. Відкривши файл index.html в браузері, ви можете побачити власну версію цієї гри, як показано на малюнку 1 . Якщо щось піде не так, ви можете порівняти свою версію з повним вихідним кодом самого автора (див. Розділ ресурси ).

висновок

Хоча гра «Життя» Джона Конвея (John Conway) дуже мала і має дуже прості правила, її програмування пов'язане з деякими каверзними проблемами, які доводиться долати. Гра є чудовим прикладом для вивчення нової мови програмування або для підвищення рівня майстерності.

Невеликі розміри методів і говорять імена змінних роблять код добре читаним. Крім того, це дозволяє добре структурувати вихідний код. Мова CoffeeScript відмінно підходить для вирішення даного завдання, оскільки дозволяє уникнути багатьох синтаксичних складнощів, властивих JavaScript. CoffeeScript також пропонує зручні функції, здатні підвищити вашу продуктивність.

Ресурси для скачування

Схожі тими

  • Оригінал статті: Conway's Game of Life in CoffeeScript and canvas .
  • CoffeeScript : Дізнайтеся більше про це міні-мові, який компілюється в JavaScript.
  • Маленька книга про CoffeeScript (Вид-во O'Reilly): дізнайтеся, як почати роботу з CoffeeScript.
  • Гра «Життя» Джона Конвея : Познайомтеся з історією гри і різними конфігураціями в Вікіпедії.
  • Авторська версія гри «Життя» .
  • Шпаргалка по HTML5 Canvas : Корисна інформація про цей елемент.
  • " Створення відмінною графіки з допомогою HTML5 canvas "(DeveloperWorks, лютий 2011 року): дізнайтеся, як поліпшити свої Web-сторінки за допомогою Canvas - простого, але дуже ефективного елемента HTML5.
  • " Основи HTML5: частина 4. Завершальний штрих - Canvas "(DeveloperWorks, липень 2011 року): прочитайте цю вступну статтю про елемент HTML5 Canvas, яка містить кілька прикладів для демонстрації його функцій.
  • WHATWG : Дізнайтеся про співтоваристві розробників, що використовує W3C для тонкої настройки HTML5.
  • Навчальний посібник по Canvas : Опис елемента canvas від розробників Mozilla.
  • Довідник по HTML5 Canvas : Познайомтеся з декількома корисними вправами, які допоможуть вам поглибити знання canvas.
  • Реалізація гри «Життя» Джона Конвея від автора статті : Скачайте приклад, використаний в цій статті.
  • Node.js : Завантажити та встановити цей необхідний для CoffeeScript компонент.
  • Node Package Manager (NPM) : Використовуйте його з файлом Node.js для установки CoffeeScript.
  • developerWorks в Твіттері : Приєднуйтесь і стежте за повідомленнями порталу developerWorks.

Підпішіть мене на ПОВІДОМЛЕННЯ до коментарів

Що все це означає?