Главная » Уроки по ООП » Урок 12. Работа с таблицами |
ДОПОЛНИТЕЛЬНЫЕ ЗАДАНИЯ |
Урок 12. Работа с таблицами
Дополнительные задания:
Игра «Жизнь». Идея игры состоит в том, чтобы, начав с какого-нибудь простого расположения фишек (организмов), расставленных по различным клеткам доски, проследить за эволюцией исходной позиции под действием «генетических законов» Конуэя, которые управляют рождением, гибелью и выживанием фишек.
Генетические законы игры сводятся к следующему.
Выживание. Каждая фишка, у которой имеются две или три соседние фишки, выживает и переходит в следующее поколение.
Гибель. Каждая фишка, у которой оказывается больше трех соседей, погибает, т. е. снимается с доски, из-за перенаселенности. Каждая фишка, вокруг которой свободны все соседние клетки или же занята только одна клетка, погибает от одиночества.
○ Рождение. Если число фишек, с которыми граничит какая-нибудь пустая клетка, в точности равно трем (не больше и не меньше), то на этой клетке происходит рождение нового «организма», т. е. следующим ходом на нее ставится одна фишка.
Таким образом, гибель и рождение всех «организмов» происходят одновременно. Вместе взятые, они образуют одно поколение или один «ход» в эволюции начальной конфигурации. Ходы Конуэй рекомендует делать следующим образом:
1) начать с конфигурации, целиком состоящей из черных фишек;
определить, какие фишки должны погибнуть, и положить на каждую из обреченных фишек по одной черной фишке;
найти все свободные клетки, на которых должны произойти акты рождения, и на каждую из них поставить по одной фишке белого цвета;
выполнив все эти указания, еще раз внимательно проверить, не сделано ли каких-либо ошибок, затем снять с доски все погибшие фишки (т. е. столбики из двух фишек), а всех новорожденных (белые фишки) заменить черными фишками.
Пусть в нашей игре фишки n-го поколения будут умирать не сразу, а лишь после появления (п+2)-го поколения. Фишки разных поколений будут отличаться друг от друга цветом.
Решение
Положите на форму компонент ToolBar (панель инструментов) со страницы Win32. Измените его свойство следующим образом:
EdgeBorders | [ebTop.ebBottom]
Сейчас панель инструментов будет находиться в самом верху формы, а ее верхняя и нижняя стороны будут выделены вдавленной линией.
Разместите на панели инструментов 5 кнопок. Для этого щелкните правой кнопкой мыши на панели инструментов и в появившемся контекстном меню выберите пункт NewButton. На панели инструментов появится кнопка типа TToolButton. С помощью этой кнопки мы будем задавать произвольную первоначальную конфигурацию расположения фишек, поэтому назовите ее RandomTBt.
Добавьте еще одну кнопку и назовите ее NewTBt. Эта кнопка понадобится для очистки игрового поля.
Поместите разделитель на панель инструментов. Для этого опять вызовите контекстное меню панели инструментов и выберите в нем пункт NewSeparator.
Поместите еще одну кнопку на ToolBarl (ее свойство Name сделайте равным FadeTBt), разделитель и еще две кнопки типа TToolButton (с именами соответственно RunTBt и StopTBt). С помощью этих кнопок мы будем управлять ходом игры. Кнопка FadeTBt будет регулировать отображение трех или одного поколения фишек. Кнопки RunTBt и StopTBt предназначены для начала и остановки игры.
Для того чтобы поместить изображения на кнопки, нам понадобится компонент ImageList со страницы Win32. Этот компонент может содержать серию изображений.
Создадим изображение для кнопки RandomTBt. Воспользуемся программой ImageEditor, входящей в состав Delphi.
D Запустите ImageEditor, выполнив команду Пуск/Програм-мы/Borland Delphi 5/Image Editor.
Для создания пиктограммы выберите в пункте меню File команду New Bitmap File (.bmp). В диалоговом окне Bitmap Properties задайте размеры изображения (Height и Width) равными 20.
С помощью инструментов программы Image Editor создайте изображение для кнопки RandomTBt.
Сохраните в папке Life под названием Bitmapl.bmp.
Задание для самостоятельного выполнения. Создайте изображение для кнопки NewTBt и сохраните его под именем Bitmap2.bmp.
Для остальных кнопок воспользуемся стандартным набором изображений Delphi.
Поместите изображения, предназначенные для кнопок приложения, в компонент ImageListl.
Щелкните правой кнопкой мыши на этом компоненте и в контекстном меню выберите пункт ImageList Editor...
В диалоговом окне редактора изображений LifeFrm. Image I List ImageList щелкните на кнопке Add.
В диалоговом окне открытия файла выберите файл Bit mapl.bmp с изображением для кнопки RandomTBt. В редакторе изображений появится выбранное изображение под индексом 0. Аналогичным образом добавьте изобра жение для кнопки NewTBt (оно должно иметь индекс 1).
Изображения для трех остальных кнопок выберите в папке С:/Program Files/Common Files/Borland Shared/Images/Buttons.
В диалоговом окне редактора изображений LifeFrm. Image I List ImageList щелкните на кнопке Add, выберите файл С:/ Program Files/ Common Files/ Borland Shared/ Images/ Buttons/ Day.bmp. После щелчка на кнопке Open появится окно (рис. 6.2.2) с сообщением о том, что размер изображения в day.bmp больше, чем размер, определенный в ImageList. Нужно разделить на 2 обособленных изображения? Щелкните на кнопке No.
Установите Options диалогового окна редактора изображе ний в значение Crop — срезать.
Добавьте изображения для кнопок FadeTBt (индекс равен двум), RunTBt (индекс равен трем) и StopTBt (индекс равен четырем).
Чтобы показать изображения на кнопках, установите свойство Images компонента ToolBarl в ImagelListl, а у каждой кнопки свойству Imagelndex присвойте индекс соответствующего изображения в редакторе изображений (для кнопки RandomTBt — 0, для NewTBt — 1 и т. д.).
Эксперимент. Сохраните проект. Запустите, на панели инструментов должны появиться кнопки с созданными вами изображениями . ♦
Для изображения жизни организмов нам понадобится компонент StringGrid. Установите его свойства следующим образом:
Align |
alClient |
ColCount |
50 |
DefaultColWidth |
10 |
DefaultRowHeight |
10 |
FixedCols |
0 |
FixedRows |
0 |
Name |
LifeSGd |
RowCount |
25 |
Измените размеры формы так, чтобы у компонента LifeSGd не было полос прокрутки.
Смену поколений организмов будем отображать через каждую секунду. Воспользуемся компонентом Timer (страница System).
Класс TTimer поддерживает единственный обработчик события OnTimer. Это событие появляется через интервал времени, заданный свойством Interval. Таким образом, если свойство Interval установлено в 1000 (миллисекунд), то обработчик события OnTimer будет вызываться каждую секунду.
В классе TTimer определено свойство Enabled, значение False которого приводит к игнорированию событий OnTimer, значение True позволяет обрабатывать события OnTimer.
Компонент Timer — это невизуальный компонент Delphi, т. е. отображается он только на этапе проектирования приложения. Положите компонент Timer на форму, установите значения свойств следующим образом:
Итак, визуальное проектирование приложения завершено. Сохраните проект.
2-й этап. Написание программного кода
Опишем глобальные переменные.
В разделе Const раздела Interface опишите две глобальные константы
MX = 50; {количество столбцов в LifeSGd}
MY = 25; {количество строк в LifeSGd}
В разделе Private класса LifeSGd опишите глобальную переменную
Fade: Boolean;
Эта переменная будет отвечать за количество поколений, отображаемых приложением: если значение Fade равно True — будут отображаться три поколения организмов (фишек), иначе — только живые организмы (фишки), т. е. одно поколение.
Воспользуемся свойством Cells компонента LifeSGd для хранения состояния организмов колонии. Введем следующие обозначения:
— фишка мертва;
— фишка жива;
— фишка мертва одно поколение (на предыдущем ходе она была жива);
— фишка мертва два поколения.
Как уже говорилось, генерация каждого нового поколения будет происходить по таймеру, поэтому при создании формы необходимо таймер выключить. Кроме того, пусть в самом начале переменная Fade будет равна False, а все организмы будут помечены мертвыми. В соответствии с этими утверждениями обработчик события создания формы будет выглядеть так:
procedure TLifeFrm.FormCreate(Sender: TObject);
var i,j:Integer;
begin
Timerl.Enabled := False;
Fade := False;
for i :=0 to MX-1
do for j :=0 to МУ-1 do LifeSGd.Cells [i,j] := '0';
end;
Рассмотрим процедуру задания произвольной расстановки фишек — событие OnClick кнопки RandomTBt.
procedure TLifeFrm.RandomTBtClick(Sender: TObject);
var x,у: Integer; begin
Randomize; {включаем генератор случайных чисел}
for у :=0 to MY-1 do for x :=0 to MX-1 do LifeSGd.Cells[x,y]:=IntToStr(Random(2));
{если в ячейку записывается единица, то фишка жива, если в ячейку записывается нуль, то фишка считается мертвой} end;
Кнопка NewTBt используется для очистки содержимого ячеек компонента LifeSGd. Поэтому в обработчике события OnClick данной кнопки нужно просто значению каждой ячейки присвоить нуль. Кроме того, надо выключить таймер. Обработчик события будет выглядеть так:
procedure TLifeFrm.NewTBtClick (Sender: TObject);
var y,x: Integer; begin
for у := 0 to MY-1 do for x := 0 to MX-1 do LifeSGd. Cells [x, y] := '0';
Timerl.Enabled := False; end;
При нажатии кнопки FadeTBt будем изменять значение переменной Fade, которая показывает, изображать или нет несколько поколений. Если Fade равно True, то кнопка будет вдавленной. Создайте следующий обработчик события:
procedure TLifeFrm.FadeTBtClick(Sender: TObject);
begin
Fade := not(Fade);
FadeTBt.Down := Fade; end;
Для запуска игры служит кнопка RunTBt. При ее нажатии таймер должен начинать работать, кнопка RunTBt — становиться вдавленной, а кнопка StopTBt — принимать обычный вид. Все эти действия выполняются в данном обработчике:
procedure TLifeFrm.RunTBtClick(Sender: TObject); begin
Timerl.Enabled := True;
RunTBt.Down := True;
StopTBt.Down := False; end;
Кнопка StopTBt используется для остановки работы программы. Обработчик события OnClick этой кнопки должен выглядеть так:
procedure TLifeFrm.StopTBtClick (Sender: TObject); begin
Timerl.Enabled := False; StopTBt.Down := True; RunTBt.Down := Falser-end;
Эксперимент. Сохраните проект. Убедитесь в правильности функционирования написанного кода. ♦
Задавать расположение фишек можно и вручную. Для этого достаточно щелкнуть левой кнопкой мыши по нужной вам ячейке. Запрограммируем такой вариант расстановки фишек. Удобнее всего помечать фишки как живые в событии OnSelectCell компонента LifeSGd:
procedure TLifeFrm.LifeSGdSelectCell(Sender: TObject;
ACol, ARow: Integer; var CanSelect: Boolean);
begin
if LifeSGd.Cells[Acol, Arow]='l' then LifeSGd.Cells[Acol, Arow]:= '0'
else LifeSGd.Cells [Acol, Arow]:= 4'; end;
Остановимся подробнее на том, как изменяется цвет каждой ячейки. Известно, что прорисовка ячеек происходит в событии OnDrawCell. Это событие происходит столько раз, сколько ячеек содержит компонент StringGrid (в задаче — LifeSGd). Кроме того, событие вызывается каждый раз при изменении значения свойства Cells. Именно поэтому не приходилось вручную перерисовывать LifeSGd.
Схема изменения цвета ячейки достаточно проста: обработчик данного события проверяет значение свойства Cells ячейки (ACol, ARow) и в зависимости от него присваивает нужный цвет кисти свойства Canvas компонента LifeSGd. Живые фишки изображаются синим цветом; предыдущее поколение (фишки, мертвые в течение одного хода) — светло-голубым цветом; фишки, мертвые в течение двух ходов, — светло-фиолетовым цветом; мертвые фишки — белым цветом. Однако если значение переменной Fade равно False, то отображаются только живые и мертвые клетки (без промежуточных состояний). После того как цвет установлен, остается только заполнить этим цветом нужную ячейку с помощью метода FillRect свойства Canvas. В результате процедура будет выглядеть следующим образом:
procedure TLifeFrm.LifeSGdDrawCell{Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
begin
LifeSGd.Canvas.Brush.Color := clWhite;
case StrToInt(LifeSGD.Cells [ACol, ARow])of 1: LifeSGd.Canvas.Brush.Color := clBlue;
2: if Fade then LifeSGd.Canvas.Brush.Color := clBlue - 13000; 3: if Fade
then LifeSGd.Canvas.Brush.Color := clBlue-17000; end;
LifeSGd.Canvas.FillRect(Rect);
end;
Эксперимент. Запустите проект, обратите внимание, что после щелчков мышью по полю таблицы клетки перекрашиваются. ♦
Итак, осталось написать последнюю процедуру, в которой будем определять, какие фишки выживут, а какие умрут. Именно здесь нам и понадобится таймер. На каждый «тик» таймера будет вызываться процедура OneStep, в которой оценивается текущее состояние фишек, и в зависимости от него строится новое расположение фишек. Добавьте название процедуры OneStep в раздел public описания класса формы TLifeFrm.
…
Public
procedure OneStep;
…
Принцип выбора живых фишек следующий: просматриваем поочередно все ячейки; для каждой ячейки считаем количество окружающих ее живых фишек; в зависимости от количества этих фишек заполняем элементы двумерного массива А либо нулем, либо единицей; далее просматриваем элементы этого массива Айв зависимости от их значений изменяем свойство Cells компонента LifeSGd.
Для реализации этого принципа нам понадобится двумерный массив А, элементы которого соответствуют ячейкам компонента LifeSGd. Размерность массива А будет аналогична размерности LifeSGd. Кроме того, нам будут нужны два массива— константы DX и DY, содержащие приращение по вертикали и горизонтали соответственно. Эти массивы понадобятся для просмотра соседних с клеткой ячеек.
procedure TLifeFrm.OneStep; const
DX : array [1..8] of Integer = (-1, 0, 1, 1, 1, 0,-1,-1); DY : array [1..8] of Integer = (-1,-1,-1, 0, 1, 1, 1, 0) ; var
i, j, k, Count: Integer; A: array [0..MX, 0..MY] of Byte; begin
Fill Char {A, SizeOf (A) , 0) ; {обнуляем значения массива А}
for i:=l to MX-1 do {просматриваем все ячейки}
for j:=1 to MY-1 do begin Count:=0;
{переменная Count используется для подсчета количества живых фишек}
for k:=l to 8 do
{просматриваем все ячейки, которые являются соседними для ячейки (i, j)}
if LifeSGd.Cells[i+DX[k],j+DY[k]]='1' then Inc(Count); case Count of
{анализируем полученное значение переменной Count и в зависимости от него заполняем массив А}
0..1,4..8: A[i,j]:=0; 2: if LifeSGd.Cells[i,j]=•1' then A[i,j]:=1
else A[i,j]:=0; 3: A[i,j]:=1;
end; end;
for i:=0 to MX-1 do
{меняем значения ячеек компонента LifeSGd}
for j:=0 to MY-1 do
if A[i,j]=l then LifeSGd.Cells[i,j] : = 'l•
{помечаем фишку как живую}
else if (LifeSGd.Cells[i,j] = ' 1' ) then LifeSGd.Cells [i,j] : = '2r
{помечаем фишку как мертвую в течение одного поколения}
else if (LifeSGd.Cells[i,j]='2l) then LifeSGd.Cells[i,j]:='3'
{помечаем фишку как мертвую в течение двух поколений}
else LifeSGd.Cells[i,j]:='0';
{помечаем фишку как окончательно мертвую}
end;
Эксперимент. Мы завершили создание приложения. Сохраните проект.
Запустите приложение. Проследите за изменением популяции клеток.
Популяция непрестанно претерпевает необычные, нередко очень красивые и всегда неожиданные изменения. Иногда первоначальная колония организмов постепенно вымирает, т. е. все фишки исчезают, однако произойти это может не сразу, а лишь после того, как сменится очень много поколений. Однако в большинстве своем исходные конфигурации либо переходят в устойчивые (последние Конуэй называет «любителями спокойной жизни») и перестают изменяться, либо навсегда переходят в колебательный режим.
Придумайте колебательные и устойчивые конфигурации популяции. ♦
Задания для самостоятельного выполнения
6.1. Приложение «Секундомер» (рис. 6.2.3). При нажатии на кнопку «Пуск» запускается секундомер (минуты : секунды : десятые доли секунды), а название кнопки изменяется на «Пауза». При нажатии на кнопку «Пауза» отсчет времени прекращается, а название кнопки изменяется на «Пуск». При нажатии на кнопку «Стоп» секундомер останавливается и происходит сброс времени.
6.2. Напишите программу, которая с помощью компонентов Gauge (страница Samples) отображает количество часов, минут, секунд, прошедших с начала суток.
Примечание. Воспользуйтесь функциями Time (возвращает текущее время) и TimeToStr(flaTa) (преобразует полученную дату в строковый формат).
«Часы шахматиста». Принцип работы: в начале шахматной партии часы установлены в нулевое положение. В начале игры часы первого шахматиста стартуют, по окончании хода он нажимает на кнопку, в результате чего его часы останавливаются и стартуют часы второго, после хода второго игрока его часы останавливаются, часы первого игрока продолжают отсчет от установленного ранее времени и т. д.
Приложение «Крестики-нолики». Дано: доска (поле) размерам 3x3. Напоминаем суть игры. Партнеры по очереди ставят на поля квадрата (доски) крестики и нолики, выигрывает тот, кто первым выстроит три своих знака в ряд. Игра длится не более девяти ходов. Если никому из игроков не удается добиться цели, партия заканчивается вничью. Напишите приложение, в которой партнерами высту пают компьютер и человек.
Примечание. Для изображения крестиков и ноликов воспользуйтесь компонентом ImageList. Поместите в этот компонент изображения. Обработчик события OnDrawCell компонента StringGrid будет содержать следующий код:
Number := StrToInt(StringGridl[ACol, ARow]);
{В ячейках StringGrid помещены символы 0 (клетка свободна), 1 (крестик), 2 (нолик)}
ImageListl.Draw{StringGridl.Canvas,Rect.Left-2,Rect.Top-2, Number) ;
{в ячейке StringGridl отображается изображение под номером Number}
6.5. Создайте приложение «Пятнадцать». Приложение произвольным образом расставляет фишки (1... 14) в коробке, оставляя свободной клетку в правом нижнем углу. В строке состояния должно отображаться количество сделанных ходов.
После завершения игры должно выводиться сообщение, поздравляющее с выигрышем.
Создайте форму, запрашивающую имя победителя. Сохраните имя победителя и его результат в файле. Предусмотрите просмотр победителей игры.
На форме должно отображаться количество минут и секунд, прошедших с начала игры.
Приложение «Перевертыши». Дано поле размером 4x4 (6x6, 8x8). В каждой клетке расположены фишки — синие и белые. Начальное расположение фишек генерируется случайным образом. За один ход можно перевернуть фишку в какой-либо произвольно выбранной ячейке, одновременно с ней переворачиваются фишки в соответствующих ячейках по вертикали и горизонтали. Цель игры: получить во всех ячейках фишки одного и того же цвета.
Приложение «Волки и овцы». В данную игру можно играть с кем-нибудь вдвоем или с самим собой. Один играет за овец, другой — за волков. У вас есть квадратная «поляна» размером 4x4 (рис. 6.2.4). В ее углах стоят 2 овцы («О») и 2 волка («В»).
Ходы осуществляются по очереди: волк, овца, волк и т. д. При этом каждый может передвигаться только на соседнюю клетку вперед, назад, влево и вправо (рис. 6.2.5). Но если овца окажется на какой-нибудь соседней клетке по диагонали, то волк очередным ходом может съесть ее. В этом случае он становится на клетку, где была овца, а та уходит с поляны. Если зазевается волк, овца делает то же самое. Выигрывает тот, кто останется на поляне.
Приложение «Ку-ну» (корейская игра). Играют два человека. На поле размером 5x5, клетки которого являются камушками, стоят два отряда воинов по 7 человек в каждой (рис. 6.2.7). Одни воины одеты в синее кимоно, другие — в зеленое. После того как брошен жребий, какому отряду начать игру, любой из воинов прыгает на соседний камушек, но только по диагонали. Затем точно так же прыгает воин из другого отряда. Оставаться всему отряду на месте никак нельзя, так как на скользких камнях трудно держать равновесие, поэтому если камушки впереди уже заняты, тогда придется прыгать назад — опять же по диагонали — на свободное место. Побеждает тот, кто быстрее успеет перейти на другой берег.
6.9. Приложение «Вечный Календарь». Напишите приложение, визуальный интерфейс которого показан на рис. 6.2.8.
Примечание. Воспользуйтесь компонентом Calendar (страница Samples).
При изменении выбранной даты отобразите соответствующие данные в заголовок формы.
Примечание. Для получения информации о компоненте воспользуйтесь файлом c:\Program files\ Borland\ Delphi5\ Source\ Samples\ Calendar.pas
6.10. Модифицируйте приложение, описанное в задании 6.9. а) Известно, что астрологи делят год на 12 периодов и каждому из них ставят в соответствие один из знаков Зодиака:
20.01-18.02 Водолей 21.05-21.06 Близнецы 24.09-22.10 Весы
19.02-20.03 Рыбы 22.06-22.07 Рак 23.10-22.11 Скорпион
21.03-19.04 Овен 23.07-22.08 Лев 23.11-21.12 Стрелец
20.04-20.05 Телец 23.08-23.09 Дева 22.12-19.01 Козерог
Модифицируйте программу так, чтобы свойство Hint компонента Calendar отображало знак Зодиака для выбранной даты.
Ь) В старояпонском календаре был принят 60-летний цикл, состоявший из пяти 12-летних подциклов. Подциклы обозначались названиями цвета: зеленый, красный, желтый, белый и черный. Внутри каждого подцикла годы носили названия животных: крысы, коровы, тигра, зайца, дракона, змеи, лошади, овцы, обезьяны, курицы, собаки и свиньи (1984 год — год зеленой крысы — был началом очередного цикла). Модифицируйте программу так, чтобы свойство Hint формы отображало название выбранного года по старояпонскому календарю.
Приложение «Блоки». На поле размером 6x6 находятся картинки (по две одинаковые), невидимые для игрока. Необходимо открыть все картинки. Картинки открываются попарно, при этом, если открыты одинаковые картинки, они исчезают. Во время игры открытыми могут быть только две картинки: при открытии третьей картинки предыдущие две закрываются. Цель игры: открыть все картинки за наименьшее число попыток.
«Биологические ритмы». Биологические ритмы вычисляют, основываясь на гипотезе, что существует три цикла: физический (его период равен 23 дням), эмоциональный (период — 28 дней) и интеллектуальный (период — 33 дня). Кривые биологических ритмов могут быть представлены в виде синусоид. Начало всех трех кривых — день рождения. В первой половине каждого периода значения синусоиды положительны — это дни рабочего, приподнятого настроения, в дни второй части периода (когда значения синусоиды отрицательны) человек находится в пассивном, плохом настроении. В самом начале (после дня рождения) все биологические ритмы попадают в отрицательную часть периода. Составьте программу, которая запрашивает день рождения; число, на которое следует определить значения синусоид биологического ритма, и по этим данным строит синусоиды на 1 месяц.
6.5. Приложение «Японский кроссворд». В клетках японского кроссворда скрываются не слова, а картинки. Задача — нарисовать картинку по числам, которые проставлены слева от строк и над колонками. Числа разделены на группы, количество которых показывает, сколько групп закрашенных клеток находится в соответсвующей линии а сами числа показывают, сколько слитных закрашенных клеток содержит каждая группа. Например, числа 2, 5 и 4 означают, что в этом ряду есть 3 группы, состоящие: первая — из 2, вторая — из 5, третья — из 4 закрашенных клеток. Группы разделены как минимум одной пустой клеткой. Пустые клетки могут быть и по краям рядов. Самое трудное — определить, сколько же пустых клеток находится между закрашенными группами.
Решение японского кроссворда разберем на простом примере. Если возле ряда стоит одно число, которое больше, чем половина длины ряда, то несколько клеток в середине ряда будут закрашены. Поэтому их можно смело помечать. В примере можно закрасить некоторые клетки в 1-й, 4-й и 5-й строках (рис. 6.2.9). Шестая строка содержит одну группу из 10 клеток, поэтому всю эту строку можно сразу закрасить.
Теперь посмотрим на колонки. В первой всего одна клетка, ее положение уже известно, так что остальные клетки этой колонки явно пустые — отметим их серым цветом и не будем обращать на них внимание. Со второй колонкой поступим аналогично. Можно еще закрасить несколько клеток в четвертой колонке, так как группа содержит 7 клеток, а высота всей колонки 8, поэтому можем закрасить средние 6 к леток (рис. 6.2.10).
Продвигаясь шаг за шагом, получим вот такой автомобиль (рис. 6.2.11).
Напишите приложение, которое помогает разгадывать японский кроссворд, т. е. получает на входе файл с заданием японского кроссворда, а затем при выборе (отмене выбора) той или иной клетки отображает это в клетках справа и снизу.
Составитель: Салий Н.А.