Наш чатик

Телеграм чат начинающих программистов. Общаемся и помогаем друг другу

Если ссылка не открывается, можно найти нас в поиске по чатам @rubyrush или пойти другими путем

Третья версия «Виселицы»

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

Мы улучшим нашу игру Виселицы так, чтобы в коде программы не было строковых констант. Научимся подгружать псевдографику из отдельных текстовых файлов в папке image и загадаем слово, выбирая его произвольно из отдельного файла words.txt.

Заодно вспомним, что такое поля (переменные) класса в Ruby и как ими пользоваться.

План урока

  1. Используем файлы в «Виселице» — храним данные
  2. Почему плохо хранить строковые данные в основном тексте программы

Учим виселицу загадывать слово самостоятельно.

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

Мы рады, если вам удалось найти коллегу и вы проходите наши уроки вдвоём, но что делать тем, кому не так повезло? :)

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

Мы будем улучшать код нашей старой программы, поэтому скопируйте три файла (game.rb, result_printer.rb и viselitsa.rb) из Виселицы v.2 в новую папку для текущего урока.

Виселица, третья версия

Создаём список слов для загадывания

Не следует хранить данные, которые использует программа (ещё их часто называют «ресурсами») в той же папке, что и код программы.

Поэтому снова создаём подпапку data и уже в ней создаём наш словарик words.txt. Мы придумали свои слова, вы придумайте свои!

руби
хипстер
файл
бибета
радость
программирование
фейсбук
задачка
богатство
любовь
джунгли
счастье

Внимание! Пользователи Windows: не забывайте все файлы (как данные, так и код программ) сохранять в кодировке UTF-8, это можно сделать в Sublime (Menu → Save With Encoding → UTF-8) или в Блокноте (Меню → Сохранить как + выбрать в выпадающем списке кодировку UTF-8).

Класс WordReader

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

Итак, как обычно, для нового класса — новый файл! Создаём файл word_reader.rb и пишем в нём наш новый класс

class WordReader
  # тут будет описание класса
end

Метод read_from_file

Наш класс будет открывать файл с помощью единственного метода read_from_file, который на вход принимает один параметр: имя файла для чтения.

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

Напомню, что с тем, как читать из файла и возвращать произвольную строчку, мы разбирались в 13-м уроке.

Используем класс WordReader в программе

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 у нас повешенный человечек.

Game over!

Давайте загрузим все эти файлы в поле класса 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.

Заодно вспомнили, что такое поля класса и как ими пользоваться.