В конструктор класса Form1 добавьте новый оператор:
groupBox1.AllowDrop = groupBox2.AllowDrop = groupBox3.AllowDrop = true;
В класс Form1 добавьте новые методы label_MouseDown и label_Move:
private void label_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
DoDragDrop(sender, DragDropEffects.Move);
}
private void label_Move(Label lb, GroupBox gb)
{
lb.Parent = gb;
lb. Top = gb.Height - 2 - gb.Controls.Count * lb. Height;
}
В методе Form1Load в конец тела цикла for добавьте следующий оператор:
lb.MouseDown += label_MouseDown;
Определите обработчики событий DragEnter и DragDrop для компонента groupBoxl, после чего свяжите созданные обработчики с событиями DragEnter и DragDrop компонентов groupBox2 и groupBox3.
private void groupBox1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void groupBox1_DragDrop(object sender, DragEventArgs e)
{
Label lb = e.Data.GetData(typeof(Label)) as Label;
GroupBox gb = sender as GroupBox;
if (gb == lb.Parent)
return;
label_Move (lb, gb);
}
Результат: любой блок (т. е. метку типа Label) можно переместить на другую башню (т. е. в другой компонент GroupBox), причем перемещенный блок всегда будет располагаться на вершине башни.
Ошибка: переместить можно не только верхний, но и любой другой блок башни.
Исправление: измените метод groupBox1_DragEnter:
private void groupBoxl_DragEnter(object sender, DragEventArgs e)
{
Label lb = e.Data.GetData(typeof(Label)) as Label;
if (lb.Parent.Controls[lb.Parent.Controls.Count - 1]!= lb)
e.Effect = DragDropEffects.None;
else
e.Effect = DragDropEffects.Move;
}
Результат: теперь переместить можно только верхний блок башни, при попытке переместить нижние блоки курсор перетаскивания становится запрещающим.
Осталось учесть дополнительное условие задачи: блок не может перемещаться на башню с верхним блоком меньшего размера. Для этого еще раз изменим метод groupBox1_DragEnter:
private void groupBox1_DragEnter(object sender, DragEventArgs e)
{
Label lb = e.Data.GetData(typeof(Label)) as Label;
int k = int.MaxValue;
GroupBox gb = sender as GroupBox;
if (gb.Controls.Count > 0)
k = gb.Controls[gb.Controls.Count - 1].Width;
if (lb.Parent.Controls[lb.Parent.Controls.Count - 1]!= lb || lb.Width > k)
|
e.Effect = DragDropEffects.None;
else
e.Effect = DragDropEffects.Move;
}
Результат: при перемещении блока учитывается дополнительное условие.
Ошибка: при изменении количества меток с помощью компонента numericUpDown1 удаляются только те метки, которые находятся в компоненте groupBox1.
Исправление: измените метод numericUpDownl_ValueChanged:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
label_Dispose(groupBox1);
label_Dispose (groupBox2);
label_Dispose (groupBox3);
Form1_Load(this, null);
}
Шаг 4. Восстановление начальной позиции и подсчет числа перемещений блоков
Разместите в форме Form1 кнопку button1, свойству Text кнопки присвойте значение Сброс и свяжите событие click кнопки button1 с существующим обработчиком numericUpDown1_ValueChanged. Кроме того, разместите в форме Form1 метку label1 (ее свойство Text изменять не требуется). Расположите добавленные компоненты в соответствии с рис. 2.
Рис. 2. Вид формы Form1 для проекта TOWERS на промежуточном этапе разработки
В класс Form1 добавьте описания двух полей:
private int count;
private int minCount;
и вспомогательный метод:
private void Info()
{
label1.Text = string.Format("Число ходов: {0} ({1})", count, minCount);
}
В метод Form1_Load добавьте три оператора:
count = 0;
minCount = (int) Math.Round (Math.Pow (2, n)) - 1;
Info();
В метод label_Move добавьте два оператора:
count++;
Info ();
Результат: для восстановления начальной позиции с тем же количеством блоков следует нажать кнопку Сброс. Если при восстановлении начальной позиции требуется изменить число блоков, то по-прежнему достаточно указать новое значение в компоненте numericUpDown1 (нажимать кнопку Сброс в этом случае не требуется). Информация о числе ходов (т. е. перемещений блоков) выводится в тексте метки iabel1. Там же в скобках указывается количество ходов, минимально необходимое для решения задачи с данным числом блоков N (это количество равно 2N-1). Обратите внимание на то, что перемещения блока в пределах одного и того же компонента GroupBox при подсчете числа ходов не учитываются.
|
Для нахождения величины 2N была использована функция Pow класса Math. Поскольку она возвращает результат типа double, полученное значение должно преобразовываться к целому типу. Так как при преобразовании (int) происходит отбрасывание дробной части, мы предварительно выполняем округление полученного числа до ближайшего целого с помощью функции Round. Заметим, что после округления необходимость в преобразовании (int) сохраняется, так как функция Round возвращает значение типа double (хотя и с нулевой дробной частью).
Шаг 5. Проверка решения задачи
Разместите в форме Form1 под имеющейся меткой label1 еще одну метку (label2), ее свойству Text присвойте значение Задача решена!, а свойству ForeColor — значение Green.
В метод Form1_Load добавьте оператор:
label2.Visible = false;
Добавьте в конец метода label_Move следующий фрагмент:
if (groupBox2.Controls.Count == numericUpDown1.Value || groupBox3.Controls.Count == numericUpDown1.Value)
label2.Visible = true;
Результат: задача считается решенной и об этом выводится сообщение «Задача решена!», если размер башни в конечной позиции (т. е. в компоненте groupBox2 или groupBox3) равен общему числу блоков.
Недочет: после решения задачи по-прежнему разрешено перемещение блоков.
Исправление: добавьте в начало метода groupBox1_DragEnter следующий фрагмент:
if (label2.Visible)
{
e.Effect = DragDropEffects.None;
return;
}
Результат: после решения задачи блоки нельзя перемещать.
|
Шаг 6. Выполнение задачи в демо-режиме
Разместите в форме Form1 еще одну кнопку (button2) и присвойте ее свойству Text значение демо-режим. Форма Form1 примет вид, приведенный на рис. 3.
Рис. 3. Окончательный вид формы Form1 для проекта TOWERS
В класс Formlдобавьте новый метод step:
private void Step(int n, GroupBox src, GroupBox dst, GroupBox tmp)
{
if (n == 0)
return;
Step(n - 1, src, tmp, dst);
if (button1. Enabled)
return;
label_Move (src.Controls [src.Controls.Count - 1 ] as Label, dst);
Application.DoEvents();
System. Threading. Thread. Sleep (1500 /((int) numericUpDown1.Value) - 1);
Step(n - 1, tmp, dst, src);
}
Метод step (n, src, dst, tmp) определяет действия, необходимые для решения задачи с n блоками; при этом параметры src, dst и tmp типа GroupBox определяют начальную позицию башни src (source), конечную позицию dst (destination) и вспомогательную область tmp (temporary), используемую при перемещении блоков. Очевидно, что для решения задачи с n блоками достаточно выполнить три действия:
1. Переместить башню, состоящую из n-1 блока, из области src в область tmp, используя область dst как вспомогательную.
2. Переместить нижний, n-й блок из области src в область dst.
3. Переместить башню, состоящую из n - 1 блока, из области tmp в область dst, используя область src как вспомогательную.
При этом действия 1 и 3 сводятся к вызову того же метода step с первым параметром, равным n - 1. Таким образом, в методе реализуется рекурсивный алгоритм. Цепочку рекурсивных вызовов метода step следует прервать, когда параметр N станет равным 0. Кроме того, в методе step предусмотрен дополнительный вариант выхода: в случае, если была повторно нажата кнопка Демо-режим (для проверки этого действия анализируется свойство Enabled компонента button1). Действие 2 реализовано с помощью метода labe1_Move. После вызова этого метода необходимо вызвать метод DoEvents класса Application, который обеспечит перерисовку перемещенной метки на новом месте, а также позволит обработать нажатие кнопки Демо-режим, если пользователь захочет досрочно выйти из демонстрационного режима. Наконец, вызывается метод sleep класса Thread, приостанавливающий выполнение программы на указанный промежуток времени (этот промежуток зависит от количества блоков).
Определите обработчик события clickдля кнопки button2:
private void button2_Click(object sender, EventArgs e)
{
numericUpDown1.Enabled = button1.Enabled =! button1.Enabled;
if (!button1.Enabled)
{
if (groupBox1.Controls.Count!= numericUpDown1.Value)
numericUpDown1_ValueChanged(null, null);
Step((int) numericUpDown1.Value, groupBox1, groupBox3, groupBox2);
numericUpDown1.Enabled = button1.Enabled = true;
}
}
В начало метода label1_MouseDown добавьте следующий фрагмент:
if (! button1.Enabled)
return;
Результат: при нажатии кнопки Демо-режим программа переходит в демонстрационный режим, в котором показывается правильное решение задачи с указанным числом блоков (блоки перемещаются автоматически). В демо-режиме блокируются компонент numericUpDownl и кнопка Сброс, а также запрещено перетаскивание меток. Выход из демо-режима происходит после завершения решения задачи, а также при повторном нажатии кнопки Демо-режим (в последнем случае после выхода из демо-режима можно продолжать решать задачу самостоятельно).
Ошибка: при попытке завершить программу, находящуюся в демо-режиме, возникает ошибка времени выполнения ArgumentOutOfRangeException, связанная с тем, что ранее вызванные методы step продолжают выполняться уже после того, как свойства-коллекции Controls компонентов GroupBox были очищены в результате завершающих действий программы.
Исправление: определите обработчик события FormClosed для формы Form1:
private void Forml_FormClosed(object sender, FormClosedEventArgs e)
{
button1.Enabled = true;
}
Результат: теперь при закрытии формы кнопка button1 делается доступной, что позволяет немедленно завершить все рекурсивные вызовы метода step без обращения к коллекциям Controls.
Приведем изображение работающей программы после решения задачи с пятью блоками (рис. 4).
Рис. 4. Вид работающего приложения TOWERS