Статьи

Создайте свой собственный OOP Connect 4 Game

ООП может ускорить разработку, и ваши приложения будут работать быстрее. В этом уроке я покажу, как создать игру ActionScript Connect 4, используя организованный подход ООП.

Давайте начнем!




В исходные файлы я включил файл connect4_start.fla. Начните с этого файла; он содержит все видеоклипы, необходимые для завершения урока. Он содержит 4 мувиклипа. Мувиклип с красными фишками, мувиклип с желтыми фишками, мувиклип на доске и видеофильм с диалогом победителя.

Создайте новый файл ActionScript и сохраните его с именем Connect4. Затем добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package
{
    import flash.display.Sprite
    import flash.events.MouseEvent;
    import flash.events.Event;
    import flash.geom.Point;
    import caurina.transitions.Tweener;
     
    public class Connect4 extends Sprite
    {
        private var columns:uint;
        private var rows:uint;
        private var board:Array = new Array();
        private var columnWidth:uint;
        private var rowHeight:uint;
        private var currentPlayer:uint = 1;
        private var currentChip;
         
        public function Connect4(columns:uint,rows:uint):void
        {
            this.columns = columns
            this.rows = rows
             
            columnWidth = new BoardPiece().width
            rowHeight = new BoardPiece().height
        }
    }
}

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

Наконец, мы инициируем переменные columnWidth и rowHeight. Один столбец имеет ширину, равную ширине одного элемента BoardPiece (тот же принцип для rowHeight).

Эта функция будет рисовать игровую площадку игры в зависимости от того, сколько столбцов и строк вы хотите. Добавьте эту функцию в конструктор класса Connect4:

01
02
03
04
05
06
07
08
09
10
11
12
13
private function drawboard():void
{
    for(var i:uint = 0; i < rows; i++)
    {
        for(var j:uint = 0; j < columns; j++)
        {
            var boardpiece:BoardPiece = new BoardPiece();
            boardpiece.x = j * boardpiece.width;
            boardpiece.y = i * boardpiece.height;
            this.addChild(boardpiece);
        }
    }
}

Здесь мы создаем 2-мерный массив, который будет содержать все местоположения доски.

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

01
02
03
04
05
06
07
08
09
10
11
private function createboardArray():void
{
    for(var i:uint = 0; i < rows; i++)
    {
        board[i] = []
        for(var j:uint=0; j < columns; j++)
        {
            board[i][j] = 0;
        }
    }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
private function putChipReady():void
{
    if(currentPlayer == 1)
    {
        currentChip = new RedChip();
    }
    else
    {
        currentChip = new YellowChip();
    }
    currentChip.y = -50;
    this.addChildAt(currentChip,0);
}

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

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

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

1
2
3
4
5
6
private function enterFrameHandler(e:Event):void
{
    var currentcolumn:uint = calculateColumn(this.mouseX);
    var xPosChip:uint = currentcolumn * columnWidth + columnWidth/2
    Tweener.addTween(currentChip, {x:xPosChip, time:0.3, transition:»lineair»});
}

Добавьте входящий фрейм Event Listener в конструктор.

1
this.addEventListener(Event.ENTER_FRAME, enterFrameHandler);

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function calculateColumn(mouseXPos):uint
{
    if(mouseXPos < 0)
    {
        return 0;
    }
    else if(mouseXPos > this.width)
    {
        return columns — 1;
    }
    else
    {
        return mouseXPos/columnWidth;
    }
}

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

Команда for проходит по всем строкам, и как только доска [row] [columnclicked] == 0, мы знаем, что чип должен быть размещен в этом месте. Помните 0 в массиве BOARD означает, что место пусто.

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

Наконец, мы переключаем плеер (объяснил 2 шага далее) и ставим еще один готовый чип. Возвращение гарантирует, что мы выйдем из цикла for .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private function boardClick(e:MouseEvent):void
{
    var columnclicked:uint = calculateColumn(e.currentTarget.mouseX);
     
    for(var row:int=rows-1; row>=0; row—)
    {
         
        if(board[row][columnclicked]==0)
        {
            board[row][columnclicked] = currentPlayer;
            placeChip(new Point(row,columnclicked))
            togglePlayer();
            putChipReady();
            return
        }
    }
}

Добавьте прослушиватель событий click в конструктор.

1
this.addEventListener(MouseEvent.CLICK, boardClick)

Эта функция получает строку (position.x) и столбец (position.y), куда должен войти чип, а затем вычисляет расстояние y и x.

distanceY: вы умножаете строку, которую вы щелкнули, на высоту одной строки. Поскольку точка регистрации фишек центрирована, мы должны добавить высоту строки, деленную на 2.

DistanceX использует тот же принцип.

Затем мы используем tweener для анимации текущего чипа в нужной позиции.

1
2
3
4
5
6
private function placeChip(position:Point):void
{
    var distanceY:int = position.x * rowHeight + rowHeight/2;
    var distanceX:int = position.y * columnWidth + columnWidth/2;
    Tweener.addTween(currentChip, {x: distanceX, y:distanceY, time:0.7, transition:»easeOutBounce»});
}

Эта функция довольно проста. Если текущим игроком является 1, переключитесь на игрока 2, иначе переключитесь обратно на игрока 1.

01
02
03
04
05
06
07
08
09
10
11
private function togglePlayer():void
{
    if(currentPlayer == 1)
    {
        currentPlayer = 2
    }
    else
    {
        currentPlayer = 1
    }
}

На данный момент вы уже можете разместить фишки, но в настоящее время нет проверки, удалось ли игроку подключить 4 фишки. Следующим шагом является код этой проверки.

Эта функция имеет 1 аргумент, позицию последней размещенной фишки и возвращает true или false. Функция использует 4 подфункции, каждая из которых проверяет победителя.

Мы проходим через позицию для каждой подфункции. Если один из 4 возвращает true, у нас есть победитель.

1
2
3
4
5
6
7
8
private function checkForWinner(position:Point):Boolean
{
    if(verticalCheck(position))return true;
    if(horizontalCheck(position))return true;
    if(leftUpDiagonalCheck(position))return true;
    if(rightUpDiagonalCheck(position))return true;
    return false;
}

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

Сначала мы проверяем, есть ли 3 места под текущим чипом. Если нет, вы не сможете подключиться к 4, и мы вернем false.

Если есть 3 или более местоположений ниже, мы запускаем цикл, который проходит через 3 ряда ниже. Если одна из фишек ниже от другого игрока, мы не подключили 4 фишки (верните false).
Если цикл может быть завершен, мы знаем, что есть 4 связанных (верните true).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
function verticalCheck(position:Point):Boolean
{
    var row:uint = position.x;
    var column:uint = position.y;
    var player:uint = board[row][column];
 
    if (row >= rows — 3)
    {
        return false;
    }
     
    for (var i:uint = row+1; i <= row + 3; i++)
    {
        if (board[i][column] != player)
        {
            return false;
        }
    }
    return true;
}

Здесь мы сначала проверяем микросхемы слева от текущей микросхемы, а затем микросхемы справа.

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

Мы делаем то же самое для правой стороны. Поэтому, если наш счетчик равен 4 или более, мы подключили 4 чипа (верните true).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function horizontalCheck(position:Point):Boolean
{
    var row:uint = position.x;
    var column:uint = position.y;
    var player:uint = board[row][column];
    var counter:uint = 1;
             
    for(var i:uint = column-1; i>=0; i—)
    {
        if(board[row][i] != player)
        {
            break;
        }
        counter++;
    }
             
    for(var j:uint = column+1; j<columns; j++)
    {
        if(board[row][j] != player)
        {
            break;
        }
        counter++;
    }
             
    if(counter >=4)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Эта функция похожа на горизонтальную проверку. Разница лишь в том, что мы теперь проверяем по диагонали.

Сначала мы идем влево вверх: мы подсчитываем фишки текущего игрока, и если мы сталкиваемся с фишкой другого игрока, мы нарушаем цикл. Чтобы убедиться, что мы не выходим за пределы массива board , цикл while должен остановиться, когда наша строка или наш столбец меньше 0.

Мы используем тот же принцип, чтобы проверить позиции вниз.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function leftUpDiagonalCheck(position:Point):Boolean
{
    var player:uint = board[position.x][position.y];
         
    var row:Number = position.x — 1;
    var column:Number = position.y — 1;
         
    var counter:uint = 1;
 
    while (row >= 0 && column >= 0)
    {
        if (board[row][column] == player)
        {
            counter++;
            row—;
            column—;
        }
        else
        {
            break;
        }
    }
         
    row = position.x + 1;
    column = position.y + 1;
         
    while (row < rows && column < columns)
    {
        if (board[row][column] == player)
        {
            counter++;
            row++;
            column++;
        }
        else
        {
             break;
        }
    }
    if(counter >=4)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Эта функция практически идентична предыдущей функции. Единственная разница — направление диагонали, которую мы проверяем.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private function rightUpDiagonalCheck(position:Point):Boolean
{
    var player:uint = board[position.x][position.y];
     
    var row:Number = position.x + 1;
    var column:Number = position.y — 1;
         
    var counter:uint = 1;
         
    while (row < rows && column >= 0)
    {
        if (board[row][column] == player)
        {
            counter++;
            row++;
            column—;
        }
        else
        {
            break;
        }
    }
         
    row = position.x — 1;
    column = position.y + 1;
 
    while (row >= 0 && column < columns)
    {
        if (board[row][column] == player)
        {
            counter++;
            row—;
            column++;
        }
        else
        {
             break;
        }
    }
    if(counter >=4)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Теперь мы должны реализовать код, который мы только что написали. После размещения чипа мы проверяем победителя. Если у нас есть победитель, мы удаляем наши EventListeners, чтобы вы не могли разместить новый чип, и мы показываем, кто выиграл (объяснение на следующем шаге). Если у нас нет победителя, мы переключаемPlayer и готовим новый чип.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private function boardClick(e:MouseEvent):void
{
    var columnclicked:uint = calculateColumn(e.currentTarget.mouseX);
             
    for(var row:int=rows-1; row>=0; row—)
    {
                 
        if(board[row][columnclicked]==0)
        {
            board[row][columnclicked] = currentPlayer;
            placeChip(new Point(row,columnclicked))
            if(checkForWinner(new Point(row,columnclicked)))
            {
                this.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
                this.removeEventListener(MouseEvent.CLICK, boardClick);
                showWinnerDialog();
            }
            else
            {
                togglePlayer();
                putChipReady();
            }
            return
        }
    }
}

Эта функция просто добавляет новый экземпляр WinnerDialog, который вы можете найти в библиотеке. Если у текущего игрока 1 красный выигрывает, в противном случае желтый выигрывает.

1
2
3
4
5
6
7
8
9
private function showWinnerDialog():void
{
    var dialog:WinnerDialog = new WinnerDialog();
    var winner:String = (currentPlayer == 1)?
    dialog.txtWinner.text = winner + » wins!!!»;
    dialog.x = (this.width — dialog.width)/2;
    dialog.y = 100;
    this.addChild(dialog);
}

Создайте новый файл ActionScript и сохраните его с именем «Приложение».

В этом классе мы добавляем экземпляр класса Connect4, который мы записали, в список отображения. Не забывайте аргументы конструктора.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package
{
    import flash.display.MovieClip;
    import flash.events.Event;
     
    public class Application extends MovieClip
    {
        public function Application():void
        {
            var connect4:Connect4 = new Connect4(7,6);
            connect4.x = (stage.stageWidth — connect4.width)/2;
            connect4.y = (stage.stageHeight — connect4.height)/2 + 50;
            this.addChild(connect4);
        }
    }
}

Наконец, нажмите на сцену и установите Класс документа на «Приложение».

Вы только что узнали, как создать игру «Connect 4» и как несколько измерений могут сделать жизнь намного проще! Я надеюсь, что вам понравился урок, и спасибо за чтение.