Статьи

Создайте интеллектуальную игру в крестики-нолики с AS3

«Крестики-нолики, это скучно!» ты можешь подумать. В этом уроке я покажу вам, что создание игры в крестики-нолики совсем не скучно. Это идеальная игра для создания, когда вы хотите изучить ActionScript 3.0. Создавая его, вы узнаете много нового о функциях и слушателях событий, увидите, как легко настроить графику игры, и даже научитесь программировать искусственный интеллект (AI)!

Давайте посмотрим на конечный результат, к которому мы будем стремиться:


Мы начнем с создания нового файла Flash (« Файл»> «Создать»> «Файл Flash (ActionScript 3.0) )». Установите размеры сцены на 300×300 и используйте черный фон. В свойствах (« Окно»> «Свойства» ) заполните «TicTacToe» как определение класса. Теперь сохраните файл в папке вашего проекта (например, Desktop \ MyFirstGame \) как «TicTacToe.fla».

Далее мы настроим файлы AS3, которые будем использовать. Мы начнем с трех файлов .as , которые мы также сохраняем в нашей папке проекта. Напишите следующий код и сохраните файлы под тем же именем, что и имя класса. Таким образом, класс TicTacToe вы сохраняете как «TicTacToe.as» и т. Д.

01
02
03
04
05
06
07
08
09
10
11
12
package
{
    import flash.display.MovieClip;
     
    public class TicTacToe extends MovieClip
    {
        public function TicTacToe():void
        {
            trace(«TicTacToe»);
        }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
package
{
    import flash.display.MovieClip;
 
    public class Game extends MovieClip
    {
        public function Game(num:uint):void
        {
            trace(«Game»);
        }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
package
{
    import flash.display.MovieClip;
     
    public class Tile extends MovieClip
    {
        public function Tile():void
        {
            trace(«Tile»);
        }
    }
}

Первый класс, TicTacToe.as, будет нашим классом документов. Не уверен, что это значит? Прочтите это краткое введение в классы документов.


Теперь у нас есть четыре файла в нашей папке проекта: один файл .fla и три файла .as. При тестировании фильма Flash (« Управление»> «Тестировать ролик» ) вы увидите сцену с черным фоном и надписью «TicTacToe» на экране «Вывод» (« Окно»> «Вывод» ). Отлично, но что мы будем с этим делать?

Файл TicTacToe.as будет обрабатывать функциональность нашего меню, которое мы напишем в конце этого урока (веселее получить рабочую игру как можно быстрее, верно?). В меню будет только две кнопки, один игрок или два игрока.

Файл Game.as будет обрабатывать нашу игру. Это также, почему мы просим аргумент «num»; это определит количество игроков. мы начнем с 2, но позже мы также хотим написать функциональность, когда есть только один игрок. В этом случае компьютер должен играть как второй игрок.

Последний файл, Tile.as, мы будем использовать для создания (как вы уже догадались) плитки. Под плиткой я подразумеваю одно из девяти мест, где вы можете поместить О или Х (или изображение цветка, вашего питомца, логотипа, вы называете его).


Мы начнем с создания линий. Вы можете быть настолько креативными, насколько захотите, но убедитесь, что линии делят сцену на 9 равных блоков. Вот что я сделал: я сделал новый мувиклип (« Вставка»> «Новый символ» ) и нарисовал 2 горизонтальные линии и 2 вертикальные линии длиной 300 пикселей. Горизонтальные линии я установил на x = 0 и ( y = 100 || y = 200 ). Вертикальные линии, которые я установил на ( x = 100 || x = 200 ) и y = 0 .

В вашей библиотеке (« Окно»> «Библиотека» ) щелкните правой кнопкой мыши новый MovieClip, перейдите в «Свойства» и установите флажок «Экспорт для ActionScript». Заполните «Сетка» как определение класса.


Далее мы сделаем плитки O и X. Вы можете использовать любое изображение, которое захотите, хотя вам следует помнить одну вещь: наша сетка делит нашу сцену на 9 квадратных плиток размером 100×100 пикселей, поэтому каждая плитка должна быть не больше 100×100. Вот что я сделал: я создал новый MovieClip и сделал форму с помощью Rectangle Tool размером 100×100. Эту форму я выровнял так, чтобы левый верх был точкой регистрации мувиклипа. Цвет заливки может быть любым, но установите его на альфа 0%. Таким образом, ваша плитка MovieClip всегда имеет размер 100×100, и вы можете нарисовать любое изображение поверх фигуры 100×100.

Вы можете удалить фон 100×100 после того, как нарисовали фигуры X и O, но это не должно иметь большого значения. Это крошечная игра, поэтому нет необходимости удалять фигуры с учетом производительности. Мне легко их оставить, чтобы потом я мог менять графику, не теряя ориентации, где я могу все разместить. Вам просто нужно выбрать обе графики (форму 100×100 и собственную графику) и выровнять их по центру (убедитесь, что функция «Выровнять по сцене» отключена), и все готово.

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

Экспортируйте оба мувиклипа для ActionScript так же, как вы делали с помощью Grid Убедитесь, что вы указали имена определений классов TileO и TileX . Теперь у нас есть три элемента в нашей библиотеке: Grid , TileO и TileX . Нам нужно только еще два, прежде чем мы сможем сделать некоторые сценарии!


Как я уже сказал, мы почти готовы к написанию сценариев, но нам нужно еще две графики. Когда кто-то выигрывает игру, мы не хотим сразу начинать новую игру, а сначала видим выигрышную линию. Для этого нам понадобится форма, которая будет располагаться сверху плитки, когда плитка является частью выигрышной линии. Я просто создал белую форму 100×100 с альфа-каналом 50%, но я знаю, что вы можете добиться большего. Вы можете создать красивый зеленый знак V, счастливую улыбку, назовите его. Экспортируйте MovieClip для ActionScript с определением класса TileE (E для «END»; у меня есть кое-что о коротких именах, и в такой простой игре, как эта, вы вспомните, что она обозначает).

Последнее изображение, которое вам нужно создать, это кнопка «button: tile, к которой мы применяем все прослушиватели событий в плитке. Опять же, создайте форму 100×100 с альфа-каналом, равным 0%. Убедитесь, что, как и вся графика плитки, которую мы создали, что точка регистрации установлена ​​в верхнем левом углу. Экспортируйте мувиклип для ActionScript с определением класса TileB (B для «КНОПКА»).

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


Мы начнем медленно. Перейдите в свой класс документов (TicTacToe.as) и примените следующие изменения:

01
02
03
04
05
06
07
08
09
10
11
12
13
package
{
    import flash.display.MovieClip;
     
    public class TicTacToe extends MovieClip
    {
        public function TicTacToe():void
        {
            var game:Game = new Game(2);
            addChild(game);
        }
    }
}

Это все. Когда мы сейчас протестируем Flash Movie, в окне Output появится Game . Мы предоставляем 2 в качестве аргумента для конструктора Game (), потому что мы хотим начать с создания игры для двух игроков. Мы также добавляем игру в список отображения, но ничего не увидим, потому что в данный момент класс игры содержит только оператор trace.


Было бы весело, если бы это был шаг 9, не так ли?

Тем не мение! Мы собираемся добавить плитки в игру. Я добавлю много кода сразу, и в результате мы не увидим ничего, кроме 9 следов со словом «Плитка», но оно того стоит. Я объясню весь код, но постараюсь сначала прочитать и понять его.

1
package { import flash.display.MovieClip;

Как видите, сначала я создаю несколько переменных. Переменная numPlayers отслеживает, сколько игроков играет. Это понадобится нам позже, когда мы добавим ИИ. То же самое касается переменной поворота , которая отслеживает чей это ход. Мы также создаем _grid , экземпляр символа Grid, который мы хотим отобразить. Наконец, массив _tiles будет отслеживать все плитки.

Функции — ваш друг, поэтому я написал новую функцию для добавления плиток addVisuals () (вы можете написать весь код в конструкторе, но это более аккуратно). В этой функции мы сначала создаем девять новых плиток. Позиции x и y плиток установлены таким образом, что плитка будет позиционироваться как клавиатура мобильного телефона:

1
2
3
4
5
            x = 0 x = 100 x = 200
 
y = 0 tile1 tile2 tile3
y = 100 tile4 tile5 tile6
y = 200 tile7 tile8 tile9

Мы добавляем каждую плитку в список отображения (который мы не увидим, поскольку плитки еще не имеют визуальных элементов ), а также помещаем ее в список _tiles (в правильном порядке, это очень важно для проверки AI / win позже). Последний вызов addChild () предназначен для сетки, которую мы хотим разместить поверх всех плиток. И последнее, но не менее важное: мы добавляем прослушиватели событий ко всем плиткам с помощью функции addTileListeners () . Событие, которое мы слушаем, еще не существует, но позже мы отправим его из класса тайлов. Когда событие инициируется, оно запускает функцию nextTurn, которая будет обрабатывать ходы и проверять выигрышные линии. Конечно, мы вызываем функцию nextTurn () в конструкторе, чтобы начать игру.


Можно создать очень сложную систему искусственного интеллекта, но так как Tic-Tac-Toe — довольно маленькая игра с ограниченными возможностями, лучше выбрать легкий путь. Мы добавили все плитки в массив _tiles . Первая плитка ( tile1 ) добавляется первой, поэтому она имеет индекс = 0 . Вторая плитка имеет индекс = 1 и т. Д. Если мы представим это на экране, это будет выглядеть так:

1
2
3
0 1 2
3 4 5
6 7 8

Вот как мы проверим, есть ли у кого-то три подряд: мы создадим массив всех возможных комбинаций, с которыми вы можете выиграть. Например, 0 1 2 является выигрышной комбинацией, а также 1 4 7 и 2 4 6 (диагональ). Добавьте следующую переменную в ваш скрипт Game.as :

1
2
3
private var _combos:Array = new Array(new Array(0, 1, 2), new Array(3, 4, 5), new Array(6, 7, 8),
                                      new Array(0, 3, 6), new Array(1, 4, 7), new Array(2, 5, 8),
                                      new Array(0, 4, 8), new Array(2, 4, 6));

Позже мы проверим индекс всех плиток O и X в массиве _tiles . Когда индексы, например, четырех тайлов X равны 0 4 5 и 8 , мы можем проверить, соответствует ли выигрышная комбинация этим индексам. Например, есть 0 , но нет 1 и 2 , поэтому плитки X не соответствуют выигрышной комбинации 0 1 2 . Мы сделаем это для каждой комбинации, и в итоге увидим, что комбинация 0 4 8 была сделана. Сценарий для этого будет предоставлен несколькими шагами позже.


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

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package
{
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.MouseEvent;
     
    public class Tile extends MovieClip
    {
        public var isSet:String;
         
        private var tileB:TileB = new TileB();
        private var tileE:TileE = new TileE();
        private var tileO:TileO = new TileO();
        private var tileX:TileX = new TileX();
         
        public function Tile():void
        {
            isSet = «not set»;
            tileO.visible = false;
            tileX.visible = false;
            tileE.visible = false;
            addChild(tileO);
            addChild(tileX);
            addChild(tileE);
            addChild(tileB);
            addButtonListeners();
        }
         
        public function addButtonListeners():void // Public, as we want to use it from the game class later on
        {
            this.buttonMode = true;
            tileB.addEventListener(MouseEvent.MOUSE_OVER, showGraphic);
            tileB.addEventListener(MouseEvent.MOUSE_OUT, hideGraphic);
            tileB.addEventListener(MouseEvent.CLICK, chooseGraphic);
        }
         
        public function removeButtonListeners():void // Public, as we want to use it from the game class later on
        {
            this.buttonMode = false;
            tileB.removeEventListener(MouseEvent.MOUSE_OVER, showGraphic);
            tileB.removeEventListener(MouseEvent.MOUSE_OUT, hideGraphic);
            tileB.removeEventListener(MouseEvent.CLICK, chooseGraphic);
        }
         
        private function showGraphic(e:MouseEvent):void
        {
            if (Game(this.parent).turn % 2 == 0) { // turn % 2 is always either 0 or 1: 0%2 = 0, 1%2 = 1, 2%2 = 0, 3%2 = 1, etc.
                tileO.visible = true;
            } else {
                tileX.visible = true;
            }
        }
         
        private function hideGraphic(e:MouseEvent):void
        {
            tileO.visible = false;
            tileX.visible = false;
        }
         
        private function chooseGraphic(e:MouseEvent):void
        {
            removeButtonListeners();
            isSet = (Game(this.parent).turn % 2 == 0) ?
            trace(isSet);
            dispatchEvent(new Event(«TILE_ACTIVE»));
        }
    }
}

Вы уже проверили это? Потому что, если все прошло хорошо, вы увидите рабочую игру сейчас! Давайте немного обсудим сценарий.

Мы начнем с создания всей графики. Каждая из девяти плиток имеет графику TileB, TileE, TileX и TileO. В конструкторе ( публичная функция Tile () ) мы добавляем эту графику в список отображения и устанавливаем для видимости значение false. Только visible TileB остается на visible = true , потому что мы хотим добавить наших слушателей событий к этому графику. Нет проблем; Вот почему мы устанавливаем альфа на 0%.

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

В вычислениях операция по модулю находит остаток от деления одного числа на другое — Википедия .

Когда мы щелкаем плитку, срабатывает функция ChooseGraphic (e: MouseEvent) . В этой функции мы удаляем всех слушателей, устанавливаем переменную isSet (отслеживаем ее) и запускаем событие TILE_ACTIVE . Следующий шаг: проверьте, кто победил!


Давайте начнем с простого: добавьте следующий скрипт в файл Tile.as для обработки выигрышной ситуации:

1
2
3
public function showEnd():void { // Added to show the tileE
        tileE.visible = true;
}

Легко ли? Это открытая функция, поэтому мы можем вызывать ее из класса Game , и все, что она делает, это устанавливает видимость tileE в значение true. Теперь снова откройте Game.as и приготовьтесь к серьезным сценариям. Мы добавим одну переменную, отредактируем функцию nextTurn () и добавим две новые функции:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
package
{
    import flash.display.MovieClip;
    import flash.events.Event;
 
    public class Game extends MovieClip
    {
        public var numPlayers:uint;
        public var turn:uint;
        public var left:uint = 10;
        private var _grid:Grid = new Grid();
        private var _tiles:Array = new Array();
        private var _combos:Array = new Array(new Array(0, 1, 2), new Array(3, 4, 5), new Array(6, 7, 8),
                                              new Array(0, 3, 6), new Array(1, 4, 7), new Array(2, 5, 8),
                                              new Array(0, 4, 8), new Array(2, 4, 6));
         
        public function Game(num:uint):void
        {
            numPlayers = num;
            turn = 0;
            addVisuals();
            addTileListeners();
            nextTurn();
        }
         
        private function addVisuals():void
        {
            var tile1:Tile = new Tile();
            var tile2:Tile = new Tile();
            var tile3:Tile = new Tile();
            var tile4:Tile = new Tile();
            var tile5:Tile = new Tile();
            var tile6:Tile = new Tile();
            var tile7:Tile = new Tile();
            var tile8:Tile = new Tile();
            var tile9:Tile = new Tile();
            tile1.x = tile4.x = tile7.x = tile1.y = tile2.y = tile3.y = 0;
            tile2.x = tile5.x = tile8.x = tile4.y = tile5.y = tile6.y = 100;
            tile3.x = tile6.x = tile9.x = tile7.y = tile8.y = tile9.y = 200;
            addChild(tile1);
            addChild(tile2);
            addChild(tile3);
            addChild(tile4);
            addChild(tile5);
            addChild(tile6);
            addChild(tile7);
            addChild(tile8);
            addChild(tile9);
            addChild(_grid);
            _tiles.push(tile1, tile2, tile3, tile4, tile5, tile6, tile7, tile8, tile9);
        }
         
        private function addTileListeners():void
        {
            for (var i:int = 0; i < _tiles.length; i++) {
                _tiles[i].addEventListener(«TILE_ACTIVE», nextTurn);
            }
        }
         
        public function nextTurn(e:Event = null):void
        {
            if (!checkWin()) { // Only if the checkWin function returns false, continue
                left -= 1;
                if (left > 0) { // While there are tiles left…
                    turn += 1;
                } else {
                    endGame();
                }
            }
        }
         
        private function checkWin():Boolean
        {
            var won:Boolean = false;
            for each (var combi:Array in _combos) { // For each combi (array) in our _combos array
                var xwin = true;
                var owin = true;
                for each (var tile:int in combi) { // For each tile index (number) in our combi array
                    xwin = xwin && _tiles[tile].isSet == «X»;
                    owin = owin && _tiles[tile].isSet == «O»;
                }
                if (xwin || owin) { // If either xwin or owin is true
                    endGame(combi);
                    won = true;
                    break;
                } else {
                    won = false;
                }
            }
            return won;
        }
         
        private function endGame(combi:Array = null):void
        {
            for (var i:int = 0; i < _tiles.length; i++) { // For each tile in the tiles Array
                _tiles[i].removeButtonListeners();
                if (combi) { // If there is a combi supplied (someone won)
                    for each (var j:int in combi) { // Check each number in that combi…
                        if (i == j) _tiles[i].showEnd();
                    }
                }
            }
        }
    }
}

Начнем с левой переменной. Когда это достигает нуля, это означает, что больше нет плиток, и игра окончена. Обычно это означает ничью.

Далее идет функция nextTurn (e: Event = null) , в которой сначала был только поворот + = 1 . Как видите, теперь у него больше условий. Каждый раз, когда вызывается функция, мы проверяем, кто победил. Если нет, мы знаем, что на выбор меньше одной плитки, поэтому мы оставляем — = 1 . Пока остаются плитки, мы можем позволить другому игроку сделать свой ход. Больше не осталось плиток, что означает конец игры (ничья, поскольку мы достигаем этой части сценария только в том случае, если еще не было выигрышной ситуации).

Функцию checkWin () лучше всего объяснить на примере. Давайте рассмотрим следующую ситуацию:

1
2
3
tile1 = X tile 4 = O
tile2 = X tile 5 = O
tile3 = X

Tile1 имеет индекс 0 в массиве _tiles . Первый для каждого оператора проверяет все массивы, вложенные в массив _combos . Первый содержит «0 1 2». Второй для каждого оператора обрабатывает эти числа (которые представляют индексы плиток). Вот что происходит во втором для каждого утверждения:

1
first combo = 0 1 2 — FIRST PASS _tiles[0].isSet = X var tile = 0 xwin (true) = xwin (true) && _tiles[0].isSet == X (true) owin (flase) = owin (true) && _tiles[0].isSet == O (false) — SECOND PASS _tiles[1].isSet = X var tile = 1 xwin (true) = xwin (true) && _tiles[1].isSet == X (true) owin (flase) = owin (false) && _tiles[1].isSet == O (false) — THIRD PASS _tiles[2].isSet = X var tile = 2 xwin (true) = xwin (true) && _tiles[2].isSet == X (true) owin (flase) = owin (false) && _tiles[2].isSet == O (false)

Как вы можете видеть, когда второе для каждого утверждения будет завершено, xwin будет истинным, а owin — ложным. Это запускает оператор if и вызывает функцию endGame (combi: Array = null) . С его помощью он предоставляет выигрышную комбинацию, которую мы будем использовать в функции. Функция checkWin () возвращает true или false, что я объяснил ранее.

Функция endGame () проста для понимания. Во-первых, для каждой плитки в массиве _tiles мы отключаем прослушиватели событий. Мы также проверяем, есть ли поставленная комбинация (имеется в виду, что кто-то выиграл вместо ничьей). Если это так, мы проверяем, какие плитки были частью комбинации, и вызываем функцию showEnd () для этих плиток. Это покажет график «TileE».


Теперь у нас есть рабочая игра. За источником всего, что мы сделали до сих пор, проверьте _milestone1.zip . Результаты!


Надеюсь, вас не смущают все сценарии, которые мы делали до сих пор. мы продолжим с ИИ-частью игры, которая требует некоторых функций. Во-первых, давайте внесем некоторые изменения и добавим функцию добавления AI. В TicTacToe.as измените следующую строку:

1
var game:Game = new Game(1);

Затем в Game.as добавьте следующие переменные:

1
2
3
private var _xlist:Array = new Array();
private var _olist:Array = new Array();
private var _flist:Array = new Array();

В функции addVisuals () добавьте следующую строку (после того, как вы поместите плитки в массив _tiles ):

1
_flist.push(tile1, tile2, tile3, tile4, tile5, tile6, tile7, tile8, tile9);

Затем измените функцию nextTurn (e: Event = null) на следующую:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public function nextTurn(e:Event = null):void
{
    if (!checkWin()) {
        if (e) { // added check for an event (we call the function once without marking a tile)
            if (e.currentTarget.isSet == «X») { // if the tile is set to X
                _xlist.push(e.currentTarget);
            }
            if (e.currentTarget.isSet == «O») { // if the tile is set to O
                _olist.push(e.currentTarget);
            }
            _flist.splice(_flist.indexOf(e.currentTarget), 1);
        }
        left -= 1;
        if (left > 0) {
            turn += 1;
            if (turn % 2 == 0 && numPlayers == 1) handleAI();
        } else {
            endGame();
        }
    }
}

Конечно, также добавьте функцию handleAI () в класс Game.as :

1
2
3
4
5
6
private function handleAI():void
{
    trace(_xlist);
    trace(_olist);
    trace(_flist);
}

Когда мы сейчас тестируем Flash-ролик, после нашего первого хода мы видим три списка в нашем окне вывода. _Xlist будет длиной в один элемент (вы выбрали плитку для отметки). _Olist пуст (второй игрок еще не выбрал плитку), а _flist имеет длину восемь элементов (осталось восемь плиток на выбор). Мы будем использовать эти списки на следующем шаге, чтобы позволить ИИ выбирать плитку.


Есть много способов написания сценариев поведения компьютера. Как вы, наверное, знаете, если оба игрока обращают внимание и знают правила, игра в крестики-нолики всегда заканчивается ничьей. Мы можем просто написать сценарий для всех ситуаций, чтобы результат всегда был ничьей или победой компьютера. Но что в этом весёлого? Я решил создать ИИ, который не будет умным, но и не глупым. Вот правила, которые мы будем писать:

  • для каждой бесплатной плитки выберите ее, если эта плитка плюс все уже выбранные O-плитки выиграют игру
  • для каждой бесплатной плитки выберите ее, если эта плитка плюс все уже выбранные X-плитки выиграют игру
  • в противном случае, выберите случайную бесплатную плитку

Поэтому компьютер сначала должен проверить, может ли он выиграть игру. Если компьютер не может выиграть с этой бесплатной плиткой, он должен проверить, может ли игрок-человек выиграть с этой плиткой. Когда ни компьютер, ни человек-игрок не могут победить, он должен выбрать случайный тайл. Мы можем заставить его задуматься, и вместо того, чтобы выбирать случайную плитку, пусть он выбирает из плиток, с которыми он позволяет выиграть следующий раунд. Но опять же, мы не будем этого делать, потому что это сделает игру довольно скучной.


Это не становится легче, чем этот шаг. Нам нужно включить плитки, чтобы компьютер мог их пометить. Обычно, когда мы играем с двумя игроками, мышь используется для запуска графического обмена и отправки события, которое устанавливает поворот на плюс один. Поскольку компьютер не использует MouseEvents, нам нужно изменить две функции в Tile.as, чтобы их можно было использовать из файла Game.as. Вот эти функции:

1
public function showGraphic(e:MouseEvent = null):void // Changed to public and set MouseEvent to null
1
public function chooseGraphic(e:MouseEvent = null):void // Changed to public and set MouseEvent to null

Таким образом, мы можем вызывать функции showGraphic () и chooseGraphic () (потому что они общедоступны) без предоставления MouseEvent (потому что, если оно не предоставлено, мы предоставляем null).


Давайте сейчас напишем скрипт для ИИ. Вот как должен выглядеть ваш файл Game.as. Я написал комментарии рядом с каждой измененной строкой.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package
{
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.TimerEvent;
    import flash.utils.Timer;
 
    public class Game extends MovieClip
    {
        public var numPlayers:uint;
        public var turn:uint;
        public var left:uint = 10;
        private var _grid:Grid = new Grid();
        private var _tiles:Array = new Array();
        private var _combos:Array = new Array(new Array(0, 1, 2), new Array(3, 4, 5), new Array(6, 7, 8),
                                              new Array(0, 3, 6), new Array(1, 4, 7), new Array(2, 5, 8),
                                              new Array(0, 4, 8), new Array(2, 4, 6));
        private var _xlist:Array = new Array();
        private var _olist:Array = new Array();
        private var _flist:Array = new Array();
        private var _choose:Boolean = false;
         
        public function Game(num:uint):void
        {
            numPlayers = num;
            turn = 0;
            addVisuals();
            addTileListeners();
            nextTurn();
        }
         
        private function addVisuals():void
        {
            var tile1:Tile = new Tile();
            var tile2:Tile = new Tile();
            var tile3:Tile = new Tile();
            var tile4:Tile = new Tile();
            var tile5:Tile = new Tile();
            var tile6:Tile = new Tile();
            var tile7:Tile = new Tile();
            var tile8:Tile = new Tile();
            var tile9:Tile = new Tile();
            tile1.x = tile4.x = tile7.x = tile1.y = tile2.y = tile3.y = 0;
            tile2.x = tile5.x = tile8.x = tile4.y = tile5.y = tile6.y = 100;
            tile3.x = tile6.x = tile9.x = tile7.y = tile8.y = tile9.y = 200;
            addChild(tile1);
            addChild(tile2);
            addChild(tile3);
            addChild(tile4);
            addChild(tile5);
            addChild(tile6);
            addChild(tile7);
            addChild(tile8);
            addChild(tile9);
            addChild(_grid);
            _tiles.push(tile1, tile2, tile3, tile4, tile5, tile6, tile7, tile8, tile9);
            _flist.push(tile1, tile2, tile3, tile4, tile5, tile6, tile7, tile8, tile9);
        }
         
        private function addTileListeners():void
        {
            for (var i:int = 0; i < _tiles.length; i++) {
                _tiles[i].addEventListener(«TILE_ACTIVE», nextTurn);
            }
        }
         
        public function nextTurn(e:Event = null):void
        {
            if (!checkWin()) {
                if (e) {
                    if (e.currentTarget.isSet == «X») {
                        _xlist.push(e.currentTarget);
                    }
                    if (e.currentTarget.isSet == «O») {
                        _olist.push(e.currentTarget);
                    }
                    _flist.splice(_flist.indexOf(e.currentTarget), 1);
                }
                left -= 1;
                if (left > 0) {
                    turn += 1;
                    if (turn % 2 == 0 && numPlayers == 1) handleAI();
                } else {
                    endGame();
                }
            }
        }
         
        private function handleAI():void
        {
            _choose = false;
            for (var i:int = 0; i < _flist.length; i++) {
                _flist[i].removeButtonListeners();
            }
            var timer:Timer = new Timer(500, 1);
            timer.addEventListener(TimerEvent.TIMER_COMPLETE, handleAIChoice);
            timer.start();
        }
         
        private function handleAIChoice(e:TimerEvent):void
        {
            var i:int;
            for (i = 0; i < _flist.length; i++) {
                if (!_choose) { // If the computer didn’t make a choice yet
                    var ocheck:Array = _olist.concat();
                    var xcheck:Array = _xlist.concat();
                    ocheck.push(_flist[i]);
                    xcheck.push(_flist[i]);
                    if (checkAIWin(ocheck)) break;
                    if (checkAIWin(xcheck)) break;
                }
            }
            if (!_choose) {
                // If the computer didn’t pick a tile yet (no win possible for x or o)
                trace(«Pick random spot»);
                var tile:int = Math.floor(Math.random() * _flist.length);
                _flist[tile].showGraphic();
                _flist[tile].chooseGraphic();
            }
            for (i = 0; i < _flist.length; i++) {
                if (!checkWin()) _flist[i].addButtonListeners();
            }
        }
         
        private function checkAIWin(check:Array):Boolean
        {
            for each (var combi in _combos) {
                var win = true;
                for each (var tile in combi) {
                    if (check.indexOf(_tiles[tile]) == -1) { // Same function, but now we supply a check array
                        win = false // If the check array doesnt include the tile of the combination, it’s not a winning situation
                    }
                }
                if (win) {
                    // If there is a winning situation, choose the tile
                    trace(«Block or check winning tile»);
                    check[check.length — 1].showGraphic();
                    check[check.length — 1].chooseGraphic();
                    _choose = true;
                    break;
                }
            }
            return _choose;
        }
         
        private function checkWin():Boolean
        {
            var won:Boolean = false;
            for each (var combi in _combos) {
                var xwin = true;
                var owin = true;
                for each (var tile in combi) {
                    xwin = xwin && _tiles[tile].isSet == «X»;
                    owin = owin && _tiles[tile].isSet == «O»;
                }
                if (xwin || owin) {
                    endGame(combi);
                    won = true;
                    break;
                } else {
                    won = false;
                }
            }
            return won;
        }
         
        private function endGame(combi:Array = null):void
        {
            for (var i:int = 0; i < _tiles.length; i++) {
                _tiles[i].removeButtonListeners();
                if (combi) {
                    for each (var j in combi) {
                        if (i == j) _tiles[i].showEnd();
                    }
                }
            }
        }
    }
}

Сделай глубокий вдох. Первые изменения, которые я должен объяснить, — это функция handleAI , которая срабатывает, когда компьютер выбирает плитку. Здесь мы используем переменную _choose и устанавливаем ее в false (компьютер еще не выбрал плитку). Затем мы удаляем всех слушателей из свободных плиток. Таким образом, игрок-человек не может щелкнуть в любой момент, чтобы сделать ход за компьютером. И последнее, но не менее важное: мы установили таймер. Я сделал это, потому что выглядит немного круче, если компьютер действительно «думает» о своем движении. Скрипт для его выбора будет запущен за несколько миллисекунд, но таким образом вы можете сделать так, чтобы он действительно выглядел так, как будто ему нужно подумать.

Второе большое изменение — функция handleAIChoice . Я добавил комментарии, которые должны объяснить все здесь. Мы используем трюк concat () для создания копии _olist и _xlist ( var copy = original не копирует исходный массив! Если вы измените копию, вы также будете изменять оригинал ). Наиболее важной частью является вызов функции checkAIWin () , которой мы снабжаем все уже выбранные плитки X или O бесплатной плиткой (мы делаем это для каждой свободной плитки).

CheckAIWin () выглядит как функция checkWin (), которую мы создали ранее, но на этот раз мы предоставляем массив. Мы будем проверять не все плитки X или O, а массив поставленных нами плиток (что означает: все плитки X плюс одна бесплатная плитка или все плитки O плюс одна бесплатная плитка). Если предоставленный массив содержит все плитки, необходимые для выигрышной комбинации, переменная win останется верной . Если нет, то будет установлено значение false .

В случае win == true это означает, что бесплатный тайл создаст выигрышную ситуацию. Компьютер всегда хочет выбрать эту плитку, потому что, если она заставляет компьютер побеждать, это хорошо. И если свободный тайл позволяет игроку-человеку выиграть, он должен заблокировать свободный тайл, чтобы человек-игрок не мог выиграть.


Теперь у нас есть рабочая игра с ИИ. Для всего, что мы сделали до сих пор, проверьте _milestone2.zip . Результаты!


Создайте две кнопки для кнопки «Один игрок» и «Два игрока». Экспортируйте их для ActionScript с именами классов B1 и B2 (B для «Button»). Это мои:


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

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
package
{
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.MouseEvent;
     
    public class TicTacToe extends MovieClip
    {
        private var b1:B1 = new B1();
        private var b2:B2 = new B2();
        private var game:Game;
         
        public function TicTacToe():void
        {
            b1.x = 120;
            b1.y = 60;
            b2.x = 180;
            b2.y = 240;
            addChild(b1);
            addChild(b2);
            b1.addEventListener(MouseEvent.CLICK, startGame);
            b2.addEventListener(MouseEvent.CLICK, startGame);
            addMenu();
        }
         
        private function startGame(e:MouseEvent):void
        {
            b1.visible = false;
            b2.visible = false;
            game = (e.currentTarget == b1) ?
            game.addEventListener(«restart», addMenu);
            addChild(game);
        }
         
        private function addMenu(e:Event = null):void
        {
            if (game != null) {
                removeChild(game);
                game = null;
            }
            b1.visible = true;
            b2.visible = true;
        }
    }
}

Поскольку весь приведенный выше скрипт является новым, я не написал ни одной строки комментария. Сначала мы делаем три переменные. Мы создаем кнопки и переменную игры. В конструкторе ( TicTacToe () ) мы устанавливаем x- и y-позиции наших кнопок. Убедитесь, что вы размещаете свои собственные кнопки в правильных координатах. Затем мы добавляем кнопки в список отображения и добавляем слушателей MouseEvent. И последнее, но не менее важное: мы вызываем функцию addMenu () .

В startGame (e: MouseEvent) мы устанавливаем обе кнопки в видимый = ложь . Таким образом, мы их больше не увидим. Если вы хотите пропустить эту часть, убедитесь, что Grid MovieClip имеет сплошной фон. Вы не сможете увидеть кнопки, потому что вы помещаете игру поверх нее. Я предпочитаю быть уверенным (вы можете расположить свою игру в другом месте) и установить видимость на false . Затем мы проверяем, какая кнопка была нажата. Если это b1 , мы хотим вызвать новую игру (1) (1 игрок против компьютера). Если это не так, мы призываем к новой игре (2) (2 человека игроков). Затем мы добавляем в игру прослушиватель событий RESTART , чтобы мы могли проверить, когда игра закончится. И последнее, но не менее важное: добавьте игру в список отображения, чтобы мы могли на самом деле играть.

Функция addMenu (e: Event = null) мало что делает. Это называется первый раз, когда мы открываем наш Flash-фильм, и каждый раз, когда игра заканчивается. Сначала он проверяет, есть ли игра, и если да, то удаляет ее из списка отображения и устанавливает для игры значение NULL. Затем он устанавливает видимость кнопок в true, чтобы мы могли нажать на них снова.


Когда вы сейчас тестируете игру, она работает нормально. Но когда вы заканчиваете свою первую игру, меню не отображается. Это потому, что мы еще не добавили диспетчер событий RESTART в файл Game.as. Давайте сделаем это. В функцию endGame (combi: Array = null) добавьте следующие строки:

1
2
3
var timer:Timer = new Timer(1500, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, restart);
timer.start();

Это запускает таймер на 1,5 секунды (так что у вас будет время посмотреть, кто на самом деле победил). Затем создайте функцию, которая вызывается таймером:

1
2
3
4
private function restart(e:TimerEvent):void
{
    dispatchEvent(new Event(«restart»));
}

Все, что делает эта функция — отправляет событие RESTART , которое мы ожидаем в классе TicTacToe . Игра завершена!


Теперь у нас есть рабочая игра с ИИ и меню. Для всего, что мы сделали до сих пор, проверьте _milestone3.zip . Результаты!


Я надеюсь, что вы все еще с нами. Сейчас у нас есть рабочая игра, но если вы чрезмерно протестировали игру, вы могли заметить ошибку. Компьютерный ИИ проверяет каждую свободную плитку, может ли она выиграть с этой плиткой. Если нет, он проверяет, может ли он проиграть по этой плитке. Так что подумайте: компьютер может выбрать плитку и может выиграть с плиткой 4. Он также может проиграть с плиткой 2. Поэтому, когда он проверяет плитку 2, он выберет эту плитку, потому что проиграет в следующем раунде, если не сделает этого. Никогда не достигайте чека на тайле 4, с которым он выигрывает этот раунд. Решение: сначала проверьте все свободные плитки на наличие выигрышной ситуации. Тогда проверьте на проигрышную ситуацию. Это будет ваша новая функция handleAIChoice (e: TimerEvent) (разделите первый оператор for на два):

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 handleAIChoice(e:TimerEvent):void
{
    var i:int;
    for (i = 0; i < _flist.length; i++) {
        if (!_choose) {
            var ocheck:Array = _olist.concat();
            ocheck.push(_flist[i]);
            if (checkAIWin(ocheck)) break;
        }
    }
    for (i = 0; i < _flist.length; i++) {
        if (!_choose) {
            var xcheck:Array = _xlist.concat();
            xcheck.push(_flist[i]);
            if (checkAIWin(xcheck)) break;
        }
    }
    if (!_choose) {
        var tile:int = Math.floor(Math.random() * _flist.length);
        _flist[tile].showGraphic();
        _flist[tile].chooseGraphic();
    }
    for (i = 0; i < _flist.length; i++) {
        if (!checkWin()) _flist[i].addButtonListeners();
    }
}

Хотя это учебное пособие, мне нравится, когда люди учатся чему-то и опираются на это. Чтобы помочь вам на вашем пути, я напишу несколько советов о том, что вы можете изменить и / или добавить.

Визуально у вас очень мало ограничений. Обратите внимание, что вы можете редактировать мувиклипы сетки , плитки и кнопок . Не ограничивайте себя одним кадром: эти активы являются MovieClips по определенной причине. Например, вы можете создать анимацию летящей птицы и использовать ее в качестве плитки. Вы можете изменить цвет вашей Grid, позволить вашим кнопкам подпрыгивать, вы называете это. Вы можете создать хороший загружаемый мувиклип и добавить его в игру. Установите для видимости значение false и значение true, пока компьютер «думает». Вместо того, чтобы заканчивать игру паузой, покажите изображение того, кто победил с фейерверком и аплодисментами. Будь креативным.


Хотя у вас уже есть работающая игра Tic-Tac-Toe, вы всегда можете добавить больше функциональности и изменить уже имеющиеся функциональные возможности. Например, вместо правил, которые я вам дал, вы можете создавать новые правила для ИИ. Всегда выбирай угол первым. Начните с центральной плитки, если она доступна. Или пусть компьютер думает на шаг впереди — ты на полпути!

Еще одно предложение, которое я могу сделать, — это случайное решение, кто может начать играть против компьютера. Это может выглядеть довольно легко, но убедитесь, что вы продумали это. Он начинается с изменения следующей строки в Game.as :


if (turn % 2 == 0 && numPlayers == 1) handleAI(); // Change turn % 2 == 0 to turn % 2 == randomBegin

В конструкторе Game (num: uint) установите randomBegin в 0 или 1. Это все, что вам нужно сделать, но взгляните на нашу функцию handleAIChoice . В этой функции мы сначала проверяем ситуации выигрыша для O, затем для X. Это правильно, когда компьютер играет с тайлами O, но когда вы делаете эту часть случайной … Итак, возможно, измените два оператора for на функции и вызывайте их в правильном порядке, в зависимости от randomBegin .


Я действительно надеюсь, что вам понравился этот урок. Это было мое первое, я старался быть простым, но веселым и надеюсь, что мне это удалось. Если у вас есть вопросы по поводу этого урока, пожалуйста, оставьте комментарии здесь. Я также хотел бы увидеть, что вы создаете с помощью этого учебного пособия, поэтому не стесняйтесь отправлять URL-адреса для ваших проектов.

Спасибо за чтение и наслаждайтесь Flash и ActionScript!