Телеграм чат начинающих программистов. Общаемся и помогаем друг другу
Если ссылка не открывается, можно найти нас в поиске по чатам @rubyrush
или
пойти другим путем
В этом уроке мы создадим третью версию нашей замечательной игры «Виселица». Мы будем используем файлы для хранения списка загаданных слов, а также будем открывать файлы с графическими изображениями виселицы.
Мы улучшим нашу игру Виселицы так, чтобы в коде программы не было строковых констант. Научимся подгружать псевдографику из отдельных текстовых файлов в папке image
и загадаем слово, выбирая его произвольно из отдельного файла words.txt
.
Заодно вспомним, что такое поля (переменные) класса в Ruby и как ими пользоваться.
В обеих версиях нашей Виселицы (без классов и с классами) для игры нужен второй человек, который загадает игроку слово.
Мы рады, если вам удалось найти коллегу и вы проходите наши уроки вдвоём, но что делать тем, кому не так повезло? :)
Давайте сделаем так, чтобы программа загадывала одно из слов из подготовленного заранее небольшого словарика.
Мы будем улучшать код нашей старой программы, поэтому скопируйте три файла
(game.rb
, result_printer.rb
и viselitsa.rb
) из Виселицы v.2 в новую папку
для текущего урока.
Не следует хранить данные, которые использует программа (ещё их часто называют «ресурсами») в той же папке, что и код программы.
Поэтому снова создаём подпапку data
и уже в ней создаём наш словарик words.txt
. Мы придумали свои слова, вы придумайте свои!
руби
хипстер
файл
бибета
радость
программирование
фейсбук
задачка
богатство
любовь
джунгли
счастье
Внимание! Пользователи Windows: не забывайте все файлы (как данные, так и код программ) сохранять в кодировке UTF-8, это можно сделать в Sublime (Menu → Save With Encoding → UTF-8) или в Блокноте (Меню → Сохранить как + выбрать в выпадающем списке кодировку UTF-8).
Давайте придумаем, что в нашей программе файл со словами будет читать отдельный целый класс. Не будем сейчас рассуждать, насколько это целесообразно — выделять для такой простой функции целый класс, но наша задача научиться пользоваться классами и привыкнуть к процессу.
Итак, как обычно, для нового класса — новый файл! Создаём файл word_reader.rb
и пишем в нём наш новый класс
class WordReader
# тут будет описание класса
end
Наш класс будет открывать файл с помощью единственного метода read_from_file
, который на вход принимает один параметр: имя файла для чтения.
Потом он будет из этого файла читать построчно все слова и возвращать один случайный элемент из массива строк (мы считаем, что в нашем файле каждое слово находится на отдельной строке).
Напомню, что с тем, как читать из файла и возвращать произвольную строчку, мы разбирались в 13-м уроке.
require_relative 'word_reader.rb'
Теперь нам надо создать экземпляр нашего нового класса WordReader
reader = WordReader.new
Ну и теперь вместо взятия слова из консоли мы передаём управление нашему новому объекту reader
, вызывая у него метод read_from_file
и передавая ему путь к словарику.
Обратите внимание, что путь к файлику мы склеиваем из строк current_path
и /data/words.txt
:
slovo = reader.read_from_file(current_path + "/data/words.txt")
Вот и всё, мы теперь читаем файл со списком слов и загадываем одно из них. Можно проверить это, запустив программу в консоли:
cd c:\rubytut\lesson14
ruby viselitsa.rb
Обратите внимание, что теперь слово после названия программы писать не нужно.
Как мы уже неоднократно говорили, данные следует хранить отдельно от кода программы. Давайте же посмотрим, что у нас творится в методе print_viselitsa
класса ResultPrinter
(файл result_printer.rb
).
А там — армагеддон, вот отрывок из файла:
when 6
puts "
_______
|/
| ( )
| _|_
| / | \\
| |
| / \\
| / \\
|
__|________
| |
"
when 7
puts "
_______
|/ |
| (_)
| _|_
| / | \\
| |
| / \\
| / \\
|
__|________
| |
"
Это, фактически, графика нашей игры. И она у нас является частью текста нашей программы. Конечно, так лучше не делать. Умение отделять «зёрна от плевел» — важный навык программиста. Пока просто скажем, что графику вашей программы нужно выносить в отдельные файлы и папки.
Фактически функционал метода print_viselitsa
, как и всего класса не зависит о того, какую именно картинку он выводит. Что, если мы захотим поменять эту картинку, подрисовав её немного. К сожалению, для этого нам придётся править файл result_printer.rb
, а это не совсем правильно, ведь логика работы класса останется неизменной.
Чаще всего, такие вещи выделяют в отдельные файлы. Давайте и мы поступим, как положено и вынесем картинки в отдельные текстовые файлы. Такие, как этот.
_______
|/ |
| (_)
| _|_
| / | \
| |
| / \
| / \
|
__|________
| |
* * * RIP * * *
Лучше всего вам будет эти файлы не делать самостоятельно, а взять из наших дополнительных материалов, так как перенос псевдографики может быть довольно утомительным.
Файлы с картинками (0.txt
— 7.txt
) мы положим в папку image
, которую создадим в нашей рабочей папке c:\rubytut\lesson 14
(напомню, данные отдельно, код программы — отдельно).
Другими словами – мухи отдельно, котлеты отдельно! Данные, нужные для работы программы, лучше отделять от логики программы (то есть от всех инструкций, методов и классов) и хранить в разных файлах.
Теперь давайте поправим наш файл result_printer.rb
так, чтобы он выводил содержимое одного из этих файлов в зависимости от того, сколько ошибок ему передали в качестве аргумента.
Обратите внимание на название файлов, они названы цифрами не просто так — цифра в названии файла соответствует количеству ошибок, которые допустил пользователь.
То есть, в 0.txt
у нас пустая виселица, а в 7.txt
у нас повешенный человечек.
Давайте загрузим все эти файлы в поле класса ResultPrinter
при его создании. Каждый раз, когда что-то происходит при создании класса у прилежного ученика в голове должно возникать слово «конструктор», который описывается методом initialize
.
Добавим в конструктор класса ResultPrinter
:
class ResultPrinter
def initialize
@status_image = []
current_path = File.dirname(__FILE__)
counter = 0
while counter <= 7 do
file_name = current_path + "/image/#{counter}.txt"
if File.exist?(file_name)
f = File.new(file_name, "r:UTF-8")
@status_image << f.read
f.close
else
@status_image << "\n [ изображение не найдено ] \n"
end
counter += 1
end
end
end
Обратите внимание, что имена файлов мы собираем из двух частей: уже полюбившейся нам конструкции current_path
(чтобы программу можно было запускать из любого места) и строки "/images/#{counter}.txt"
, в которую мы вставляем номер картинки из переменной counter
, которая проходит путь от 0
до 7
.
А если вдруг такой файл не нашёлся, мы вставляем в массив вместо картинки строку «изображение не найдено», нет смысла заканчивать игру, если просто нет файла с виселицей, которая нужна для только красоты.
Таким образом в поле класса @status_image
у нас теперь храниться массив из 7-ми элементов, каждый из которых является строковой константой, которую можно вывести на экран, когда нам это потребуется: напомню, что мы можем использовать этот массив в любом методе нашего класса.
Ну и конечно же, не забываем закрыть файл сразу после того, как получили от него всё, что нужно.
Теперь метод print_viselitsa
станет проще и понятнее, смотрите:
def print_viselitsa(errors)
puts @status_image[errors]
end
Вот и всё! Посмотрите, нам не пришлось менять саму программу, а также файл game.rb
или другие методы класса ResultPrinter
.
В этом уроке мы улучшили нашу игру Виселица так, чтобы в коде программы не было строковых констант, научились загружать графику из отдельных текстовых файлов и загадывать слово, выбирая его произвольно из отдельного файла words.txt
.
Заодно вспомнили, что такое поля класса и как ими пользоваться.