Проект на лабораторные работы

В рамках лабораторных работ вам предлагается реализовать законченный проект, включающий в себя:

  1. Приложение, разработанное в соответствии с заданием.

  2. Систему сборки.

  3. Unit-тесты.

  4. Настроенные автоматические сборки (CI).

Работа над проектом разбивается на короткие итерации. Процесс разработки должен быть зафиксирован в системе контроля версий (git). Проект должен быть опубликован на github.

Вам предлагается выполнить проект «Шахматы»:

Приложение принимает на вход запись партии в шахматной нотации и выводит последовательность шахматных досок с расположением фигур после каждого хода. Сложность проекта регулируется за счет реализации проверок корректности входных данных и соблюдения правил игры. Так, в простейшем случае достаточно проверять, что фигуры перемещаются в пределах доски и не покидают ее пределы. Более полная реализация должна проверять корректность ходов согласно правилам игры, например, допустимость рокировки.

В проекте возможно несколько вариантов считывания входных данных.

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

  2. Считывание данных из файла. В этом случае синтаксис запуска приложения выглядит следующим образом:

    chessviz <FILE>
    

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

  3. Гибридный вариант. Если в командной строке указан путь к файлу, то ходы считываются из него. Иначе приложение считывает данные из stdin. Аналогичное поведение можно наблюдать у многих стандартных утилит, например, cat

    Синтаксис запуска в этом случае:

    chessviz [FILE]
    

Ниже приводятся полное описание проекта. Отдельно взятая лабораторная работа предполагает реализацию небольшой части проекта.

Шахматы

Функциональность приложения

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

Формат входных данных

На вход приложение принимает список ходов, разделенных символом перевода строки. Запись хода состоит из таких компонентов:

  1. Тип фигуры, выполняющей ход (король, конь и т. д.).

  2. Поле, с которого сделан ход.

  3. Для тихого хода — дефис (-). Для взятия — буква x.

  4. Поле, на которое сделан ход.

  5. Если пешка совершила превращение, после целевого поля указывается фигура, которой она стала.

  6. Для взятия на проходе — знак e.p. (фр. en passant).

  7. Для шаха — знак плюс (+). Для мата — решётка (#).

Грамматика (pseudo-EBNF):

СписокХодов = {ЗаписьХода ПереводСтроки}
ЗаписьХода = НомерХода. Ход Ход
Ход = [ТипФигуры] Поле ТипХода Поле [ТипФигуры | 'e.p.' | '+' | '#']
    | Рокировка
ТипФигуры = 'K' | 'Q' | 'R' | 'B' | 'N'
Поле = /[a-h][1-8]/
ТипХода = '-' | 'x'
Рокировка = '0-0-0' | '0-0'

Пример:

1. e2-e4 e7-e5
2. Bf1-c4 Nb8-c6
3. Qd1-h5 Ng8-f6
4. Qh5xf7#

Проверки корректности входных данных

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

Easy

Проверки диапазонов: все поля (начало и конец хода) должны быть в пределах доски.

Normal

В дополнение к проверкам из предыдущих пунктов:

  1. Обозначения фигур должны соответствовать фактическим (нельзя взять коня с поля, на котором стоял слон).

  2. Тип хода должен соответствовать фактическому. Например, если во входных данных записано взятие, а в конечной ячейке нет фигуры соперника, то сообщить об ошибке. Взятие на проходе не рассматриваем.

Hard

В дополнение к проверкам из предыдущих пунктов:

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

  2. Проверка очередности ходов.

Nightmare

В дополнение к проверкам из предыдущих пунктов:

  1. Проверки допустимости рокировки, см. Рокировка (wiki)

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

Формат выходных данных

После запуска приложение выводит в stdout номер хода, ход и состояние доски после него. Вывод может быть в текстовом формате и/или html. В случае реализации двух форматов вывода пользователь может выбрать формат с помощью опции командной строки. Возможные варианты синтаксиса:

chessviz [--text|--html]
chessviz [--output=<text|html>]

Текстовый формат

Белые фигуры обозначаются символами в верхнем регистре, черные — символами в нижнем регистре.

Обозначение

Фигура

K

Король (king)

Q

Ферзь (queen)

R

Ладья (rook)

N

Конь (kNight)

B

Слон (bishop)

P

Пешка (pawn)

Пример:

1. e2-e4

8 r n b q k b n r
7 p p p p p p p p
6
5
4         P
3
2 P P P P   P P P
1 R N B Q K B N R
  a b c d e f g h

HTML

Возможно реализовать вывод в формате html. Доска представлена таблицей, фигуры — элементами span с классами:

<black|white> <king|queen|rook|knight|bishop|pawn>

Шаблон документа:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>chessviz</title>
  <style type="text/css">
    table.chessboard {
      border: 5px solid #333;
      border-collapse: collapse;
      height: 320px;
      margin: 20px;
      width: 320px;
    }
    table.chessboard caption {
      text-align: left;
    }
    table.chessboard td {
      background-color: #fff;
      font-size: 25px;
      height: 40px;
      text-align: center;
      vertical-align: middle;
      width: 40px;
    }
    table.chessboard tr:nth-child(odd) td:nth-child(even),
    table.chessboard tr:nth-child(even) td:nth-child(odd) {
      background-color: #999;
    }

    table.chessboard .white.king:before   { content: "\2654"; }
    table.chessboard .white.queen:before  { content: "\2655"; }
    table.chessboard .white.rook:before   { content: "\2656"; }
    table.chessboard .white.bishop:before { content: "\2657"; }
    table.chessboard .white.knight:before { content: "\2658"; }
    table.chessboard .white.pawn:before   { content: "\2659"; }

    table.chessboard .black.king:before   { content: "\265A"; }
    table.chessboard .black.queen:before  { content: "\265B"; }
    table.chessboard .black.rook:before   { content: "\265C"; }
    table.chessboard .black.bishop:before { content: "\265D"; }
    table.chessboard .black.knight:before { content: "\265E"; }
    table.chessboard .black.pawn:before   { content: "\265F"; }
  </style>
</head>

<body>
  <table class="chessboard">
    <caption>1. e2-e4</caption>
    <tr>
      <td><span class="black rook"></span></td>
      <td><span class="black knight"></span></td>
      <td><span class="black bishop"></span></td>
      <td><span class="black queen"></span></td>
      <td><span class="black king"></span></td>
      <td><span class="black bishop"></span></td>
      <td><span class="black knight"></span></td>
      <td><span class="black rook"></span></td>
    </tr>
    <tr>
      <td><span class="black pawn"></span></td>
      <td><span class="black pawn"></span></td>
      <td><span class="black pawn"></span></td>
      <td><span class="black pawn"></span></td>
      <td><span class="black pawn"></span></td>
      <td><span class="black pawn"></span></td>
      <td><span class="black pawn"></span></td>
      <td><span class="black pawn"></span></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td><span class="white pawn"></span></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><span class="white pawn"></span></td>
      <td><span class="white pawn"></span></td>
      <td><span class="white pawn"></span></td>
      <td><span class="white pawn"></span></td>
      <td></td>
      <td><span class="white pawn"></span></td>
      <td><span class="white pawn"></span></td>
      <td><span class="white pawn"></span></td>
    </tr>
    <tr>
      <td><span class="white rook"></span></td>
      <td><span class="white knight"></span></td>
      <td><span class="white bishop"></span></td>
      <td><span class="white queen"></span></td>
      <td><span class="white king"></span></td>
      <td><span class="white bishop"></span></td>
      <td><span class="white knight"></span></td>
      <td><span class="white rook"></span></td>
    </tr>
  </table>
</body>
</html>

Отображение в браузере:

chessviz
1. e2-e4

Пример работы приложения

Входные данные:

1. e2-e4 e7-e5
2. Bf1-c4 Nb8-c6
3. Qd1-h5 Ng8-f6
4. Qh5xf7#

Пример работы приложения:

$ ./chessviz scholars_mate.txt

8 r n b q k b n r
7 p p p p p p p p
6
5
4
3
2 P P P P P P P P
1 R N B Q K B N R
  a b c d e f g h


1. e2-e4

8 r n b q k b n r
7 p p p p p p p p
6
5
4         P
3
2 P P P P   P P P
1 R N B Q K B N R
  a b c d e f g h


1. e7-e5

8 r n b q k b n r
7 p p p p   p p p
6
5         p
4         P
3
2 P P P P   P P P
1 R N B Q K B N R
  a b c d e f g h


2. Bf1-c4

8 r n b q k b n r
7 p p p p   p p p
6
5         p
4     B   P
3
2 P P P P   P P P
1 R N B Q K   N R
  a b c d e f g h


2. Nb8-c6

8 r   b q k b n r
7 p p p p   p p p
6     n
5         p
4     B   P
3
2 P P P P   P P P
1 R N B Q K   N R
  a b c d e f g h


3. Qd1-h5

8 r   b q k b n r
7 p p p p   p p p
6     n
5         p     Q
4     B   P
3
2 P P P P   P P P
1 R N B   K   N R
  a b c d e f g h


3. Ng8-f6

8 r   b q k b   r
7 p p p p   p p p
6     n     n
5         p     Q
4     B   P
3
2 P P P P   P P P
1 R N B   K   N R
  a b c d e f g h


4. Qh5xf7#

8 r   b q k b   r
7 p p p p   Q p p
6     n     n
5         p
4     B   P
3
2 P P P P   P P P
1 R N B   K   N R
  a b c d e f g h

Замечания по реализации

Вывод HTML

Генерация HTML верстки без использования предназначенных для этого шаблонизаторов может быть неудобна.

#include <stdio.h>

int main()
{
    printf("  <meta charset=\"utf-8\">\n");
    printf("  <style type=\"text/css\">\n");
    printf("    .white.king:before { content: \"\\2654\"; }\n");
    printf("  </style>\n");
    return 0;
}
  1. Приходится экранировать символы, имеющие специальное значение (в данном случае обратный слеш и кавычки).

  2. Используется множество вызовов printf.

От части вызовов printf можно избавиться, используя многострочные литералы:

#include <stdio.h>

int main()
{
    printf(
        "  <meta charset=\"utf-8\">\n"
        "  <style type=\"text/css\">\n"
        "    .white.king:before { content: \"\\2654\"; }\n"
        "  </style>\n");
    return 0;
}

Современные версии gcc в режиме компиляции кода на языке C поддерживают некоторые возможности языка C++, в частности raw string literal. Это позволяет избавиться от экранирования специальных символов и от использования символа переноса строки \n. Для использования этого расширения может потребоваться опция компилятора -std=gnu99 или -std=gnu11.

#include <stdio.h>

int main()
{
    printf(
        R"( <meta charset="utf-8">
            <style type="text/css">
              .white.king:before { content: "\2654"; }
            </style>)");
    return 0;
}