Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Рассмотрим ряд приемов программирования на языке ActionScript 3.0 и разработаем программу для реализации известной логической игры Ханойские башни.


Добрый день, уважаемые читатели!

Рассмотрим ряд приемов программирования на языке ActionScript 3.0 и разработаем программу для реализации известной логической игры Ханойские башни.

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

Изучим следующие приемы программирования и разработки на языке ActionScript 3.0:

  • Работа с классами и иерархия классов;
  • Перетаскивание объектов мышкой.

Условия игры

Ханойские башни - это классическая головоломка, изобретенная французским математиком Эдуардом Люка, в 1883 году. Правила игры хорошо известны и описаны, например, здесь. Классическую версию игры можно коротко описать так: Даны три стержня, на один из которых нанизаны восемь колец, причем кольца отличаются размером и лежат меньшее на большем. Задача состоит в том, чтобы перенести пирамиду из восьми колец за наименьшее число ходов. За один раз разрешается переносить только одно кольцо, причём нельзя класть большее кольцо на меньшее. В нашей реализации мы не будем ограничиваться восемью кольцами. Предоставим пользователю следующие возможности:

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

Дизайн игры

В нашем варианте игра будет содержать три неподвижных объекта: большие диски со стержнями, на которые будут нанизываться подвижные диски. Цвет каждого из подвижных дисков будет выбираться случайным образом. Естественно, все диски будут иметь разные значения диаметра. Вне рабочего поля разместим экземпляр класса NumberField, описанного в одном из предыдущих выпусков рассылки. При изменении значения числа, содержащегося в этом объекте, будет изменяться количество подвижных дисков. Одновременно все подвижные диски будут возвращаться в исходное состояние. Кроме того, вне рабочего поля разместим кнопку, при нажатии на которую будет запускаться автоматический решатель.

Иерархия классов

Напомню, что в соответствии с принципами объектно-ориентированного программирования класс представляет собой программно-информационную структуру, содержащую свойства и методы. Полагается создавать иерархическую структуру классов, определяя и описывая в классах-родителях наиболее общие методы и свойства.
При необходимости можно переопределять методы предков в классах-потомках.
Построим следующие классы:
Назначим классом - родителем графических объектов назначим rect. Опишем в нем общие свойства и методы. Вот его текст:

// Игра Ханой. Класс-предок всех прямоугольников
package
{
import flash.display.Sprite;

public class Rect extends Sprite
{
protected var __parent:hanoy;
public function Rect(pa:Object,h:uint,w:uint,c:uint,b:uint)
{
__parent = pa as hanoy;
__parent.addChild(this);
graphics.lineStyle(1,0);
graphics.beginFill(c);
graphics.drawRect(0,0,w,h);
graphics.endFill();
Bottom = b;
}

override public function set name(value:String):void{
Constants.Inds[value] = this;
super.name = value;
}
public function get Center():uint{
return x + width / 2;
}
public function set Center(newCenter:uint):void{
x = newCenter - width / 2;
}
public function get Bottom():uint{
return y + height;
}
public function set Bottom(newBottom:uint):void{
y = newBottom - height;
}
}
}

Свойство Center будет использоваться для обеспечения установки дисков с совпадающими центрами, а свойство Bottom - для установки дисков друг над другом. Обратите внимание на переопределенный в этом классе метод set name. Как видите, в нем всего две строки.
В команде: Constants.Inds[value] = this; мы заносим информацию в ассоциативный массив Inds для последующего поиска произвольного объекта по его имени. Ключи в этом массиве - имена объектов, а значения - указатели на соответствующие объекты. Например, левый неподвижный диск будет иметь имя "UD1" и для поиска его указателя достаточно в любом модуле проекта написать, примерно, следующее:
var obj:UnmovableDisk =Constants.Inds["UD1"];
Вторая команда в методе set name - это вызов соответствующего метода класса-предка:
super.name = value;
На этом примере видно использование ключевого слова super.

Прямым потомком класса Rect является класс Disk:

// Класс Disk отличается от Rect тем, что строит диски постоянной высоты: Constants.DISKHEIGHT
package
{
import flash.display.Sprite;
public class Disk extends Rect
{
public function Disk(pa:Object,w:uint,c:uint,b:uint)
{
super(pa,Constants.DISKHEIGHT,w,c,b);
}
}
}

Классы UnmovableDisk и МovableDisk описывают неподвижные и подвижные диски соответственно.

//Неподвижный диск имеет размер по горизонтали w = Constants.UNMOVABLEDISKWIDTH
package
{
public class UnmovableDisk extends Disk
{
private var _disks:Array = new Array(); // Подвижные диски
public function UnmovableDisk(pa:Object)
{
super(pa,Constants.UNMOVABLEDISKWIDTH,Constants.UNMOVABLEDISKCOLOR,Constants.LOWLEVEL);
}
// Для неподвижный дисков центр устанавливается только один раз. Используем это для построения стержня.
override public function set Center(newCenter:uint):void{ //Построение центрального стержня
x = newCenter - width / 2;
var stack:Rect = new Rect(__parent,Constants.STACKHEIGHT,Constants.STACKWIDTH,Constants.UNMOVABLEDISKCOLOR,Bottom-height);
__parent.addChild(stack);
stack.Center = Center;
}
public function deleteDisk():void{ // Удаление подвижного диска
var mDisk:MovableDisk = _disks.pop();
if(UpDisk is MovableDisk){ // Если стек был не пуст
(UpDisk as MovableDisk).setListener();
}
mDisk.PaDisk = null; // У удаляемого диска родительский диск не определен
}
public function receiveDisk(disk:MovableDisk):void{ // Добавление и регистрация подвижного диска
disk.Bottom = UpDisk.y;
disk.Center = Center;
disk.PaDisk = this;
if(UpDisk is MovableDisk){
(UpDisk as MovableDisk).deleteListener();
}
disk.setListener();
_disks.push(disk);
}
public function get UpDisk():Disk{
if(_disks.length){
var d:MovableDisk = _disks[_disks.length - 1];
return d;
}else{
return this;
}
}
public function get Disks():Array{
return _disks;
}
public function nextDisk(disk:MovableDisk):MovableDisk{
var i:uint = 0;
for each(var d:MovableDisk in _disks){
if (d == disk) break;
i++;
}
if(i < _disks.length){
return _disks[i+1];
}else{
return null;
}
}
public function deleteAll():void{ // Удаление всех подвижных дисков
while(_disks.length){
_disks.pop();
}
}
}
}

Ниже следует описание класса MovableDisk.
// Проект "Ханойские башни". Описание класса MovableDisk - подвижный диск
package
{
import flash.events.MouseEvent;
public class MovableDisk extends Disk
{
private var _paDisk:UnmovableDisk;

public function MovableDisk(pa:Object,w:uint,c:uint,b:uint)
{
super(pa,w,c,b);
}
public function setListener():void{
addEventListener(MouseEvent.MOUSE_DOWN,mDown);
addEventListener(MouseEvent.MOUSE_UP,mUp);
}
public function deleteListener():void{
removeEventListener(MouseEvent.MOUSE_DOWN,mDown);
removeEventListener(MouseEvent.MOUSE_UP,mUp);
}
public function get PaDisk():UnmovableDisk{
return _paDisk;
}
public function changeParent(newParent:UnmovableDisk,who:MyTimer,state:uint):void{
switch(state){
case(0):
_paDisk.deleteDisk();
who.step();//ждем следующего вызова (следующего тика таймера).
break;
case(1):
y = Constants.HIGH;
who.step();
break;
case(2):
Center = newParent.Center;
who.step();
break;
case(3):
newParent.receiveDisk(this);
who.ready();
break;
}
}
public function set PaDisk(disk:UnmovableDisk):void{
_paDisk = disk;
}
private function mDown(me:MouseEvent):void{ // Обработчик события "Нажатие на кнопку мыши"
startDrag();
}
private function mUp(me:MouseEvent):void{ // Обработчик события "Отпускание кнопки мыши"
stopDrag();
__parent.setDisk(this);
}

}
}

Перетаскивание объекта мышкой

Два последних метода описанного выше класса MovableDisk показывают, насколько простым является программирование перетаскивания объекта мышкой. Для начала и для завершения перетаскивания можно использовать события "Нажатие на кнопку мыши" и "Отпускание кнопки мыши" соответственно. В обработчики этих событий добавляем вызов стандартного метода startDrag() и stopDrag() соответственно. В данном случае методы вызываются "изнутри", то есть, перемещается объект того класса, в котором эти методы описаны, следовательно, не возникает проблемы идентификации объекта, который нужно перетаскивать.
Если эти методы нужно вызвать "извне", например, в нашей программе - из экземпляра класса "hanoy", то следует воспользоваться уже знакомым Вам приемом поиска объекта, вызвавшего события. В этом случае соответствующая функция имела бы примерно такой вид:
private function mDown(me:MouseEvent):void{ // Обработчик события "Нажатие на кнопку мыши"
(me.target as MovableDisk).startDrag();
}

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

Рекомендации

Можно добавить счетчики количества сделанных ходов, и количества ошибок. Для этого можно использовать специальный класс, построенный на основе TextField.
По всем вопросам прошу обращаться по почте, или через любой Интернет-браузер. Мой адрес и другие контактные данные доступны зарегистрированным пользователям сайта. Желаю успехов!


В избранное