Форум программистов, компьютерный форум, киберфорум
ActionScript
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.77/13: Рейтинг темы: голосов - 13, средняя оценка - 4.77
Модератор
 Аватар для TanaTiX
2931 / 1790 / 180
Регистрация: 19.02.2011
Сообщений: 6,550
AS 3.0

Урок. Тестирование

28.04.2014, 13:35. Показов 2453. Ответов 4

Студворк — интернет-сервис помощи студентам
Дамы и господа, будем для разнообразия писать тест.
Постить буду по ходу создания проекта. Тема будет открыта после завершения проекта. Не ждите сразу всего, т.к. данная тема будет пополняться только по мере появления у меня свободных минут. По ходу написания проекта в более ранних постах могут быть правки, не прозевайте.

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

Начнем-с...

Создаем проект (работаю во FlashDevelop, далее ФД или FD), обычный AS3 проект.

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

В качестве формата хранения данных выбран json. Поэтому я создал файл data.json, которому сразу назначил кодировку UTF-8 (без BOM). В качестве текстового редактора использую Notepad++

Собственно содержимое:
data.json
JSON
1
2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
{
    "settings":{
        "totalQuestions":5,
        "timeForQuestion":10000
    },
    "questions":[
        {
            "title":"Из чего были созданы согласно библии первые люди?",
            "answers":[
                {
                    "value":"Все началось с коацерватных капель",
                    "correct":0
                },
                {
                    "value":"Из глины",
                    "correct":1
                },
                {
                    "value":"Из ребра",
                    "correct":1
                },
                {
                    "value":"Из обезьяны",
                    "correct":0
                },
                {
                    "value":"Люди созданы инопланетным разумом",
                    "correct":0
                }
            ]
        },
        {
            "title":"Сколько на небе созвездий?",
            "answers":[
                {
                    "value":"Безсчетное количество",
                    "correct":0
                },
                {
                    "value":"Около миллиона",
                    "correct":0
                },
                {
                    "value":"12",
                    "correct":0
                },
                {
                    "value":"13",
                    "correct":0
                },
                {
                    "value":"88",
                    "correct":1
                }
            ]
        },
        {
            "title":"Года жизни Грушевского?",
            "answers":[
                {
                    "value":"1855-1921",
                    "correct":0
                },
                {
                    "value":"1866-1934",
                    "correct":1
                },
                {
                    "value":"1888-1941",
                    "correct":0
                }
            ]
        },
        {
            "title":"Что объединяет Кекуле и Менделеева?",
            "answers":[
                {
                    "value":"Обы были химиками",
                    "correct":1
                },
                {
                    "value":"Оба совершили открытия во сне",
                    "correct":1
                },
                {
                    "value":"Ухаживали за одной женщиной",
                    "correct":0
                },
                {
                    "value":"Родились в один год",
                    "correct":0
                },
                {
                    "value":"Были католиками",
                    "correct":0
                }
            ]
        },
        {
            "title":"Сколько будет 2+2?",
            "answers":[
                {
                    "value":"11",
                    "correct":1
                },
                {
                    "value":"5",
                    "correct":0
                },
                {
                    "value":"3",
                    "correct":0
                },
                {
                    "value":"1",
                    "correct":0
                },
                {
                    "value":"А вы покупаете или продаете?",
                    "correct":0
                }
            ]
        },
        {
            "title":"Квадрат гипотенузы равен?",
            "answers":[
                {
                    "value":"Сумме катетов",
                    "correct":0
                },
                {
                    "value":"Сумме квадратов катетов",
                    "correct":1
                },
                {
                    "value":"Произведению катета на количество углов",
                    "correct":0
                },
                {
                    "value":"Произведению синуса угла противоположного угла на длину прилегающего катета",
                    "correct":0
                },
                {
                    "value":"100",
                    "correct":0
                }
            ]
        },
        {
            "title":"Первый человек в космосе?",
            "answers":[
                {
                    "value":"Гагарин",
                    "correct":1
                },
                {
                    "value":"Мюнхгаузен",
                    "correct":0
                },
                {
                    "value":"Паллок",
                    "correct":0
                },
                {
                    "value":"Терешкова",
                    "correct":0
                },
                {
                    "value":"Гавриил",
                    "correct":0
                }
            ]
        },
        {
            "title":"Это можно считать промежуточным звеном между одноклеточным и многоклеточным",
            "answers":[
                {
                    "value":"Вольвокс",
                    "correct":1
                },
                {
                    "value":"Амеба",
                    "correct":0
                },
                {
                    "value":"Инфузория-туфелька",
                    "correct":0
                },
                {
                    "value":"Гидра",
                    "correct":0
                },
                {
                    "value":"Власоглав",
                    "correct":0
                }
            ]
        },
        {
            "title":"Кто был у римлян верховным богом?",
            "answers":[
                {
                    "value":"Юпитер",
                    "correct":1
                },
                {
                    "value":"Зевс",
                    "correct":0
                },
                {
                    "value":"Триединый Бог",
                    "correct":0
                },
                {
                    "value":"Шива",
                    "correct":0
                },
                {
                    "value":"Вишну",
                    "correct":0
                }
            ]
        },
        {
            "title":"Какова  в действительности 'лошадиная фамилия' у героя известного произведения Чехова?",
            "answers":[
                {
                    "value":"Овсов",
                    "correct":1
                },
                {
                    "value":"Упряжкин",
                    "correct":0
                },
                {
                    "value":"Ложкин",
                    "correct":0
                },
                {
                    "value":"Копытов",
                    "correct":0
                },
                {
                    "value":"Седлов",
                    "correct":0
                },
                {
                    "value":"Иванов",
                    "correct":0
                }
            ]
        }
    ]
}


Добавлено через 14 минут
Основные модули можно создать сразу, можно после загрузки. Т.к. я стараюсь активно использовать паттерн MVC, то сразу создадим модель, вью и контроллер, настроим между ними простые связи.
Кликните здесь для просмотра всего текста
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package 
{
    import flash.display.Sprite;
    import flash.events.Event;
    
    /**
     * ...
     * @author TanaTiX
     */
    public class Main extends Sprite 
    {
        
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            new Controller(this);
        }
        
    }
    
}
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package  
{
    import flash.display.DisplayObjectContainer;
    import flash.display.Stage;
    /**
     * ...
     * @author TanaTiX
     */
    public class Controller 
    {
        private var _rootContainer:DisplayObjectContainer;//ссылка на документ-класс. Для нас он является просто контейнером.
        private var _stage:Stage;//ссылка на сцену. Может понадобиться для работы с некоторыми событиями. Про запас.
        private var _model:Model;//модель
        private var _view:View;//отображение, собственно внешний вид проекта
        
        public function Controller(main:DisplayObjectContainer) 
        {
            internalInit(main);
        }
        
        private function internalInit(main:DisplayObjectContainer):void 
        {
            _rootContainer = main;
            _stage = main.stage;
            _model = new Model();
            _view = new View(_model);
            
            _rootContainer.addChild(_view);
        }
        
    }
 
}
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package  
{
    /**
     * ...
     * @author TanaTiX
     */
    public class Model 
    {
        
        public function Model() 
        {
            
        }
        
    }
 
}
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package  
{
    import flash.display.Sprite;
    
    /**
     * ...
     * @author TanaTiX
     */
    public class View extends Sprite 
    {
        private var _model:Model;
        
        public function View(model:Model) 
        {
            _model = model;
            
        }
        
    }
 
}


Добавлено через 12 минут
Теперь создадим класс, который загрузит наш json, распарсит полученные значения и поместит их в модель. Ссылку на модель для этого нужно передать. Также передадим ссылку на загружаемый файл, таким образом, если предположить, что таких файлов может быть несколько (например, выбору темы соответствовал бы один файл), то нам не придется переделывать этот файл.
Кликните здесь для просмотра всего текста
ActionScript 3
1
new DataLoader(_model, "data.json");
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package  
{
    import flash.events.Event;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    /**
     * ...
     * @author TanaTiX
     */
    public class DataLoader 
    {
        private var _model:Model;//модель
        private var _link:String;//ссылка на файл с данными
        
        public function DataLoader(model:Model, link:String) 
        {
            _link = link;
            _model = model;
            internalInit();
        }
        
        private function internalInit():void 
        {
            var _loader:URLLoader = new URLLoader();//загрузчик
            _loader.addEventListener(Event.COMPLETE, onLoadComplete);
            _loader.load(new URLRequest(_link));//начинаем загружать
        }
        
        private function onLoadComplete(e:Event):void 
        {
            var data:Object = JSON.parse((e.target as URLLoader).data);
            //Данные загружены, теперь необходимо их распарсить
        }
        
    }
 
}
3
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
28.04.2014, 13:35
Ответы с готовыми решениями:

Урок. Структура АС3-проекта
Данный урок предназначен для новичков. В нем постараюсь дать ответы на часто возникающие вопросы (прямо или косвенно) относительно...

Урок: создаем простые молнии используя AS3
import flash.events.Event; import flash.utils.Timer; //Создаем бесконечный таймер, срабатывающий раз в 3 секунды var timer = new...

Урок. Реализация Drag&Drop на ActionScrip3.0
Добрый день дорогие читали! Сегодня хочу Вам представить урок по одному из вариантов реализации механизма Drag&Drop на AS3. Надеюсь...

4
Модератор
 Аватар для TanaTiX
2931 / 1790 / 180
Регистрация: 19.02.2011
Сообщений: 6,550
30.04.2014, 12:47  [ТС]
Пора создать еще несколько классов: класс вопроса со списком ответов, собственно ответы, позже наверное придется добавить еще те ответы, которые дал пользователь для того, что бы показать ему, что он отвечал и в каком порядке.
Собственно данные
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package data 
{
    /**
     * ...
     * @author TanaTiX
     */
    public class Answer 
    {
        private var _title:String;//текст ответа
        private var _correct:Boolean;//правильный ответ или нет
        
        public function Answer(title:String, correct:Boolean) 
        {
            _correct = correct;
            _title = title;
            
        }
        
        public function get title():String 
        {
            return _title;
        }
        
        public function get correct():Boolean 
        {
            return _correct;
        }
        
    }
 
}
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package data 
{
    /**
     * ...
     * @author TanaTiX
     */
    public class Question 
    {
        private var _answer:String;//текст вопроса
        private var _questions:Vector.<Answer>;//список ответов
        
        public function Question(answer:String, questions:Vector.<Answer>) 
        {
            _questions = questions;
            _answer = answer;
            
        }
        /**
         * текст вопроса
         */
        public function get answer():String 
        {
            return _answer;
        }
        /**
         * метод для перемешивания ответов
         */
        public function shuffle():void {
            
        }
        /**
         * количество ответов
         */
        public function get length():uint {
            return _questions.length;
        }
        /**
         * получение объекта ответа по его индексу в массиве
         * @param   index
         * @return
         */
        public function getAnswerByIndex(index:uint):Answer {
            return _questions[index];
        }
    }
 
}

Обратите внимание что зависимость между объектами на данный момент построена таким образом, что, однажды создав вопрос, изменить его уже не получится. Таким образом мы соблюдаем один из принципов SOLID: Принцип открытости/закрытости

Также я создал еще один класс, в котором будут описываться константы для событий. Можно было бы обойтись (может и не везде) стандартными событиями, например из класса Event, но это не столь наглядно.
константы для событий
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package data 
{
    /**
     * Класс для строковых констант, описывающих события в данном приложении
     * @author TanaTiX
     */
    public class TestEvents 
    {
        /**
         * добавление вопросов в базу данных
         */
        public static const ADD_QUESTIONS:String = "addQuestions";
        
        public function TestEvents() 
        {
            
        }
        
    }
 
}
Обратите внимание на комментарий к константе. Если во ФД попытаться обратиться к данной константе, то помимо значения в автокомплите также отобразиться и текст комментария.


Добавлено через 1 час 17 минут
Настало время после создания основных заготовок приступить к заполнению модели (по сути это можно условно назвать нашей базой данных). Теперь наш загрузчик будет выглядеть так:
Загрузчик
ActionScript 3
1
2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
package  
{
    import data.Answer;
    import data.Question;
    import flash.events.Event;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    /**
     * ...
     * @author TanaTiX
     */
    public class DataLoader 
    {
        private var _model:Model;//модель
        private var _link:String;//ссылка на файл с данными
        
        public function DataLoader(model:Model, link:String) 
        {
            _link = link;
            _model = model;
            internalInit();
        }
        
        private function internalInit():void 
        {
            var _loader:URLLoader = new URLLoader();//загрузчик
            _loader.addEventListener(Event.COMPLETE, onLoadComplete);
            _loader.load(new URLRequest(_link));//начинаем загружать
        }
        
        private function onLoadComplete(e:Event):void 
        {
            var data:Object = JSON.parse((e.target as URLLoader).data);
            //Данные загружены, теперь необходимо их распарсить
            
            //тут также необходимо не забыть про настройки, создадим такую запись, что бы не забыть:
            //TODO: добавить настройки
            
            var questions:Array = data["questions"] as Array;//создаем массив, с которым далее активно работаем
            var lengthQuestions:int = questions.length;//определяем длину массива
            //длину массива лучше выносить за цикл, что бы в цикле его длина каждый раз не прочитывалась
            var question:Question;//объект вопроса, также выносим за пределы цикла, что бы работать только с одной ссылкой, а не создавать каждый раз по новой
            
            var answers:Array;//массив ответов, массив потому что до парсинга данных полученный объект можно изобразить только в вие массива (хеш не в счет)
            var answersLength:int;//и его длина
            var answer:Answer;//объект варианта ответа
            
            
            var vAnswers:Vector.<Answer>;//типизированные ответы
            var vQuestions:Vector.<Question> = new Vector.<Question>();//типизированные вопросы, то, что поместим в модель
            
            
            var itemQuestion:Object;//нетипизированный вопрос
            var itemAnswer:Object;//нетипизированный ответ
            var correct:Boolean;//показатель того, правильный ответ или нет. Вынес в отдельную переменную только для наглядности и профилактики путаницы
            for (var i:int = 0; i < lengthQuestions; i++) //запускаем цикл
            {
                itemQuestion = questions[i];
                answers = itemQuestion["answers"] as Array;
                answersLength = answers.length;
                vAnswers = new Vector.<Answer>();
                for (var j:int = 0; j < answersLength; j++) //вложенный цикл для ответов
                {
                    itemAnswer = answers[j];
                    correct = (itemAnswer["correct"] == 1)?true:false;//это называется "тернарный оператор"
                    vAnswers.push(new Answer(itemAnswer["value"], correct));
                }
                vQuestions.push(new Question(itemQuestion["title"], vAnswers));
            }
            _model.addQuestions(vQuestions);//помещаем результаты в модель
        }
        
    }
 
}

А в модель на данном этапе так:
Модель
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package  
{
    import adobe.utils.CustomActions;
    import data.Question;
    import data.TestEvents;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    /**
     * ...
     * @author TanaTiX
     */
    public class Model extends EventDispatcher
    {
        private var _questions:Vector.<Question>;//ссылка на массив с нашими вопросами
        
        public function Model() 
        {
            internalInit();
        }
        
        /**
         * инициализация
         */
        private function internalInit():void 
        {
            _questions = new Vector.<Question>();//создаем массив с вопросами
        }
        
        /**
         * заполняем массив
         * @param   value
         */
        public function addQuestions(value:Vector.<Question>):void 
        {
            if (!value || value.length == 0) {//простая проверка на валидность
                return;
            }
            _questions = _questions.concat(value);
            dispatchEvent(new Event(TestEvents.ADD_QUESTIONS));//сообщаем, что появились новые вопросы
        }
        
        public function shuffleQuestioins():void {
            
        }
    }
 
}
2
Модератор
 Аватар для TanaTiX
2931 / 1790 / 180
Регистрация: 19.02.2011
Сообщений: 6,550
05.05.2014, 23:06  [ТС]
Как вы обратили внимание в классе Model есть пустой метод shuffleQuestioins, а в классе Question я создам аналогичный метод shuffleAnswers для перемешивания вопросов и ответов соответственно.
На алгоритме подробно не останавливаюсь, не это есть цель данного урока, а разбор разных алгоритмов - достаточно большой пласт знаний, поэтому особенности реализации смотрим в комментариях.

Но стоит несколько задержаться на реализации самого перемешивания. Ведь удобно было бы иметь некий статический метод в нужном классе, куда в качестве аргумента передавать для перемешивания любой вектор. Но так не получится ввиду того, что передать "любой"! вектор не получится. Такое можно было бы реализовать с не типизированным массивом (обычный Array), но я считаю, что мы имеем не такое большое количество по сути дублирующих методов, а отсутствие типизации не та цена, которой стоит жертвовать в данной ситуации.

Не по теме:

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


Я пока реализую это вот такими методами:
перемешиваем
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
         * сортировка
         * отсортировать можно и лучше, для этого существует большое количество разных алгоритмов, но мы не ставим целью погружаться во все нюансы алгоритмов сортировок, поступаем просто и наглядно
         */
        public function shuffleQuestioins():void {
            var newQuestions:Vector.<Question> = new Vector.<Question>();//временный массив (здесь для удобства понимания договоримся, что вектор - это типизированный массив)
            var index:uint;//текущий выбранный индекс
            var length:int = _questions.length;//длин массива
            while (length > 0) {//пока исходный массив существует..
                index = Math.random() * length;//берем случайный индекс, согласно длин исходного массива
                newQuestions.push(_questions[index]);//добавляем выбранный элемент во временный массив
                _questions.splice(index, 1);//удаляем ранее перемещенный во временный массив элемент из исходного массива
                length--;//переопределяем длину массива, что бы каждый раз при работе цикла не вычислять ее снова (_questions.length)
            }
            _questions = newQuestions;//переопределяем выделенный в поле класса массив
        }
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
         * сортировка
         * отсортировать можно и лучше, для этого существует большое количество разных алгоритмов, но мы не ставим целью погружаться во все нюансы алгоритмов сортировок, поступаем просто и наглядно
         */
        public function shuffleAnswers():void {
            var newQuestions:Vector.<Answer> = new Vector.<Answer>();//временный массив (здесь для удобства понимания договоримся, что вектор - это типизированный массив)
            var index:uint;//текущий выбранный индекс
            var length:int = _questions.length;//длин массива
            while (length > 0) {//пока исходный массив существует..
                index = Math.random() * length;//берем случайный индекс, согласно длин исходного массива
                newQuestions.push(_questions[index]);//добавляем выбранный элемент во временный массив
                _questions.splice(index, 1);//удаляем ранее перемещенный во временный массив элемент из исходного массива
                length--;//переопределяем длину массива, что бы каждый раз при работе цикла не вычислять ее снова (_questions.length)
            }
            _questions = newQuestions;//переопределяем выделенный в поле класса массив
        }


Следующим этапом планирую создавать список вопросов, которые реально будут предложены при тестировании, сохранять результаты, запустить таймер.
1
Модератор
 Аватар для TanaTiX
2931 / 1790 / 180
Регистрация: 19.02.2011
Сообщений: 6,550
30.05.2014, 12:30  [ТС]
Долго не доходили руки, но появилось немного времени. На этом этапе у меня есть список вопросов и ответов, а также визуальная часть. За дизайн не ругайте, я не художник, да и не дизайн мы тут изучаем.
Итак, приступим. Для начала пройдемся по имеющимся классам в порядке их вовлечения в процесс работы приложения.

Не по теме:

Комментарии будут в виде текста и комментариев в коде.



Main
Main - без изменений. В этом классе если что-то и появится, что очень маловероятно, то только в перспективе и это могут быть самые общие надстройки над проектом.
Controller
В контроллере мы начали слушать модель. Слушаем при этом самые основные моменты: появление вопросов для возможности начала тестирования и окончание тестирования. В обработчике окончания тестирования пока ничего кроме трэйса нет - до этого этапа еще не добрался.
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package  
{
    import data.TestEvents;
    import flash.display.DisplayObjectContainer;
    import flash.display.Stage;
    import flash.events.Event;
    /**
     * ...
     * @author TanaTiX
     */
    public class Controller 
    {
        private var _rootContainer:DisplayObjectContainer;//ссылка на документ-класс. Для нас он является просто контейнером.
        private var _stage:Stage;//ссылка на сцену. Может понадобиться для работы с некоторыми событиями. Про запас.
        private var _model:Model;//модель
        private var _view:View;//отображение, собственно внешний вид проекта
        
        public function Controller(main:DisplayObjectContainer) 
        {
            internalInit(main);
        }
        
        /**
         * конструктор класса рекомендуется максимально очищать
         * @param   main
         */
        private function internalInit(main:DisplayObjectContainer):void 
        {
            _rootContainer = main;
            _stage = main.stage;
            _model = new Model();
            _model.addEventListener(TestEvents.ADD_QUESTIONS, onAddQuestions);//событие появиления новых вопросов
            _model.addEventListener(TestEvents.TEST_COMPLETE, onTestComplete);//событие окончания тестирования
            _view = new View(_model, _stage);//создаем вью, т.е. отображение
            
            _rootContainer.addChild(_view);//добавляем отображение на сцену
            
            new DataLoader(_model, "data.json");//начинаем загружать вопросы
        }
        /**
         * событие добавления новых вопросов
         * @param   e
         */
        private function onAddQuestions(e:Event):void 
        {
            _model.removeEventListener(TestEvents.ADD_QUESTIONS, onAddQuestions);//добавление вопросов предполагается только однократное, без их постепенного добавления в процессе работы программы, поэтому в текущей реализации нет необходимости оставлять соответствующий слушатель - удаляем
            _view.updateForStartTesting();//меняем отображение для возможности начать тестирование
            _model.createNewUserQuestions(_model.defaultQuestionsForCurrentTest);
        }
        
        /**
         * завершение тестирвоания
         * @param   e
         */
        private function onTestComplete(e:Event):void 
        {
            trace("test complete");
        }
        
    }
 
}

Произошли небольшие изменения и при загрузке вопросов. Была добавлена настройка для определения количества вопросов в тесте. Возможно, список настроек еще увеличится.
DataLoader
ActionScript 3
1
2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
package  
{
    import data.Answer;
    import data.Question;
    import flash.events.Event;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    /**
     * ...
     * @author TanaTiX
     */
    public class DataLoader 
    {
        private var _model:Model;//модель
        private var _link:String;//ссылка на файл с данными
        
        public function DataLoader(model:Model, link:String) 
        {
            _link = link;
            _model = model;
            internalInit();
        }
        
        private function internalInit():void 
        {
            var _loader:URLLoader = new URLLoader();//загрузчик
            _loader.addEventListener(Event.COMPLETE, onLoadComplete);
            _loader.load(new URLRequest(_link));//начинаем загружать
        }
        
        private function onLoadComplete(e:Event):void 
        {
            var data:Object = JSON.parse((e.target as URLLoader).data);
            //Данные загружены, теперь необходимо их распарсить
            
            //тут также необходимо не забыть про настройки, создадим такую запись, что бы не забыть:
            //TODO: добавить настройки
            var settings:Object = data["settings"];
            _model.defaultQuestionsForCurrentTest = uint(settings["defaultQuestionsForFirstTest"]);
            
            var questions:Array = data["questions"] as Array;//создаем массив, с которым далее активно работаем
            var lengthQuestions:int = questions.length;//определяем длину массива
            //длину массива лучше выносить за цикл, что бы в цикле его длина каждый раз не прочитывалась
            var question:Question;//объект вопроса, также выносим за пределы цикла, что бы работать только с одной ссылкой, а не создавать каждый раз по новой
            
            var answers:Array;//массив ответов, массив потому что до парсинга данных полученный объект можно изобразить только в вие массива (хеш не в счет)
            var answersLength:int;//и его длина
            var answer:Answer;//объект варианта ответа
            
            
            var vAnswers:Vector.<Answer>;//типизированные ответы
            var vQuestions:Vector.<Question> = new Vector.<Question>();//типизированные вопросы, то, что поместим в модель
            
            
            var itemQuestion:Object;//нетипизированный вопрос
            var itemAnswer:Object;//нетипизированный ответ
            var correct:Boolean;//показатель того, правильный ответ или нет. Вынес в отдельную переменную только для наглядности и профилактики путаницы
            for (var i:int = 0; i < lengthQuestions; i++) //запускаем цикл
            {
                itemQuestion = questions[i];
                answers = itemQuestion["answers"] as Array;
                answersLength = answers.length;
                vAnswers = new Vector.<Answer>();
                for (var j:int = 0; j < answersLength; j++) //вложенный цикл для ответов
                {
                    itemAnswer = answers[j];
                    correct = (itemAnswer["correct"] == 1)?true:false;//это называется "тернарный оператор"
                    vAnswers.push(new Answer(itemAnswer["value"], correct));
                }
                vQuestions.push(new Question(itemQuestion["title"], vAnswers));
            }
            _model.addQuestions(vQuestions);//помещаем результаты в модель
            _model.shuffleQuestioins();//перемешиваем вопросы
        }
        
    }
 
}
data.json
JSON
1
2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
{
    "settings":{
        "defaultQuestionsForFirstTest":5,
        "timeForQuestion":10000
    },
    "questions":[
        {
            "title":"Из чего были созданы согласно библии первые люди?",
            "answers":[
                {
                    "value":"Все началось с коацерватных капель",
                    "correct":0
                },
                {
                    "value":"Из глины",
                    "correct":1
                },
                {
                    "value":"Из ребра",
                    "correct":1
                },
                {
                    "value":"Из обезьяны",
                    "correct":0
                },
                {
                    "value":"Люди созданы инопланетным разумом",
                    "correct":0
                }
            ]
        },
        {
            "title":"Сколько на небе созвездий?",
            "answers":[
                {
                    "value":"Безсчетное количество",
                    "correct":0
                },
                {
                    "value":"Около миллиона",
                    "correct":0
                },
                {
                    "value":"12",
                    "correct":0
                },
                {
                    "value":"13",
                    "correct":0
                },
                {
                    "value":"88",
                    "correct":1
                }
            ]
        },
        {
            "title":"Года жизни Грушевского?",
            "answers":[
                {
                    "value":"1855-1921",
                    "correct":0
                },
                {
                    "value":"1866-1934",
                    "correct":1
                },
                {
                    "value":"1888-1941",
                    "correct":0
                }
            ]
        },
        {
            "title":"Что объединяет Кекуле и Менделеева?",
            "answers":[
                {
                    "value":"Обы были химиками",
                    "correct":1
                },
                {
                    "value":"Оба совершили открытия во сне",
                    "correct":1
                },
                {
                    "value":"Ухаживали за одной женщиной",
                    "correct":0
                },
                {
                    "value":"Родились в один год",
                    "correct":0
                },
                {
                    "value":"Были католиками",
                    "correct":0
                }
            ]
        },
        {
            "title":"Сколько будет 2+2?",
            "answers":[
                {
                    "value":"11",
                    "correct":1
                },
                {
                    "value":"5",
                    "correct":0
                },
                {
                    "value":"3",
                    "correct":0
                },
                {
                    "value":"1",
                    "correct":0
                },
                {
                    "value":"А вы покупаете или продаете?",
                    "correct":0
                }
            ]
        },
        {
            "title":"Квадрат гипотенузы равен?",
            "answers":[
                {
                    "value":"Сумме катетов",
                    "correct":0
                },
                {
                    "value":"Сумме квадратов катетов",
                    "correct":1
                },
                {
                    "value":"Произведению катета на количество углов",
                    "correct":0
                },
                {
                    "value":"Произведению синуса угла противоположного угла на длину прилегающего катета",
                    "correct":0
                },
                {
                    "value":"100",
                    "correct":0
                }
            ]
        },
        {
            "title":"Первый человек в космосе?",
            "answers":[
                {
                    "value":"Гагарин",
                    "correct":1
                },
                {
                    "value":"Мюнхгаузен",
                    "correct":0
                },
                {
                    "value":"Паллок",
                    "correct":0
                },
                {
                    "value":"Терешкова",
                    "correct":0
                },
                {
                    "value":"Гавриил",
                    "correct":0
                }
            ]
        },
        {
            "title":"Это можно считать промежуточным звеном между одноклеточным и многоклеточным",
            "answers":[
                {
                    "value":"Вольвокс",
                    "correct":1
                },
                {
                    "value":"Амеба",
                    "correct":0
                },
                {
                    "value":"Инфузория-туфелька",
                    "correct":0
                },
                {
                    "value":"Гидра",
                    "correct":0
                },
                {
                    "value":"Власоглав",
                    "correct":0
                }
            ]
        },
        {
            "title":"Кто был у римлян верховным богом?",
            "answers":[
                {
                    "value":"Юпитер",
                    "correct":1
                },
                {
                    "value":"Зевс",
                    "correct":0
                },
                {
                    "value":"Триединый Бог",
                    "correct":0
                },
                {
                    "value":"Шива",
                    "correct":0
                },
                {
                    "value":"Вишну",
                    "correct":0
                }
            ]
        },
        {
            "title":"Какова  в действительности 'лошадиная фамилия' у героя известного произведения Чехова?",
            "answers":[
                {
                    "value":"Овсов",
                    "correct":1
                },
                {
                    "value":"Упряжкин",
                    "correct":0
                },
                {
                    "value":"Ложкин",
                    "correct":0
                },
                {
                    "value":"Копытов",
                    "correct":0
                },
                {
                    "value":"Седлов",
                    "correct":0
                },
                {
                    "value":"Иванов",
                    "correct":0
                }
            ]
        }
    ]
}

Далее отображение. Но прежде чем приступить к отображению необходимо создать хоть какой-то дизайн. Поэтому открываем Flash (в моем случае это Flash CS 5.5), создаем fla. Я его назвал test.fla и поместил в папку fla в корне проекта.

Не по теме:

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


В новом fla 1м делом иду в настройки (File > Publish Settings), там выбираю только Flash и SWC, полностью отключаю все что в OTHER FORMATS. SWC оставлю по умолчанию, а в Flash в ADVANCED оставлю только Compress Movie и Include hidden layers. Это сжатие и включение скрытых слоев соответственно. Проект у меня настроен по умолчанию на AS3, а версия ФП по умолчанию тоже, стоит 10.2. Если в вашем проекте используется класс TLFTextField (это текстовое поле в IDE с расширенным набором настроек), то версия должна быть не ниже 10 (см. описание класса), но если есть возможность избежать использования этого класса в пользу старого доброго динамического текстового поля, то это лучше сделать.
Model

Обращаем внимание, что модель у нас может слать события, а, следовательно, она наследуется от EventDispatcher. Также в моделе у нас имеется 2 типизированных массива (Vector) одного типа (Question): один содержит все загруженные вопросы, второй только те, которые участвуют в текущем тестировании. Также в классе есть методы для добавления вопросов, перемешивания (описание было приведено ранее), сброс индекса текущего вопроса, получение ссылки на текущий вопрос и его индекс, геттер и сеттер для получения количества вопросов для текущего тестирования, а также метод, который увеличивает индекс текущего вопроса.
ActionScript 3
1
2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package  
{
    import data.Question;
    import data.TestEvents;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    /**
     * ...
     * @author TanaTiX
     */
    public class Model extends EventDispatcher
    {
        private var _defaultQuestionsForCurrentTest:uint;
        private var _totalQuestions:Vector.<Question>;//ссылка на массив с нашими вопросами
        private var _currentTestQuestions:Vector.<Question>;
        private var _currentQuestionIndex:int;
        
        public function Model() 
        {
            internalInit();
        }
        
        /**
         * инициализация
         */
        private function internalInit():void 
        {
            _totalQuestions = new Vector.<Question>();//создаем массив с вопросами
            _currentTestQuestions = new Vector.<Question>();//создаем массив с вопросами для текущего теста
        }
        
        /**
         * заполняем массив
         * @param   value
         */
        public function addQuestions(value:Vector.<Question>):void 
        {
            if (!value || value.length == 0) {//простая проверка на валидность
                return;
            }
            _totalQuestions = _totalQuestions.concat(value);
            dispatchEvent(new Event(TestEvents.ADD_QUESTIONS));//сообщаем, что появились новые вопросы
        }
        /**
         * сортировка
         * отсортировать можно и лучше, для этого существует большое количество разных алгоритмов, но мы не ставим целью погружаться во все нюансы алгоритмов сортировок, поступаем просто и наглядно
         */
        public function shuffleQuestioins():void {
            var newQuestions:Vector.<Question> = new Vector.<Question>();//временный массив (здесь для удобства понимания договоримся, что вектор - это типизированный массив)
            var index:uint;//текущий выбранный индекс
            var length:int = _totalQuestions.length;//длин массива
            while (length > 0) {//пока исходный массив существует..
                index = Math.random() * length;//берем случайный индекс, согласно длин исходного массива
                newQuestions.push(_totalQuestions[index]);//добавляем выбранный элемент во временный массив
                _totalQuestions.splice(index, 1);//удаляем ранее перемещенный во временный массив элемент из исходного массива
                length--;//переопределяем длину массива, что бы каждый раз при работе цикла не вычислять ее снова (_questions.length)
            }
            _totalQuestions = newQuestions;//переопределяем выделенный в поле класса массив
        }
        
        public function resetCurrentTestQuestions():void 
        {
            _currentQuestionIndex = 0;
        }
        public function get currentQuestion():Question {
            return _currentTestQuestions[_currentQuestionIndex];
        }
        
        public function get currentQuestionIndex():int 
        {
            return _currentQuestionIndex;
        }
        
        public function get defaultQuestionsForCurrentTest():uint 
        {
            return _defaultQuestionsForCurrentTest;
        }
        
        public function set defaultQuestionsForCurrentTest(value:uint):void 
        {
            _defaultQuestionsForCurrentTest = value;
        }
        
        public function nextQuestion():void {
            _currentQuestionIndex++;
            if (_currentQuestionIndex == _currentTestQuestions.length) {
                dispatchEvent(new Event(TestEvents.TEST_COMPLETE));
            }
        }
        
        public function createNewUserQuestions(count:uint):void 
        {
            if (!_totalQuestions || _totalQuestions.length < count) {
                throw new Error("Количество вопросов в базе данных меньше " + count);
            }
            _currentTestQuestions = _totalQuestions.slice(0, count);
            for each (var item:Question in _currentTestQuestions) 
            {
                item.shuffleAnswers();
            }
        }
    }
 
}
Тут есть ряд принципиальных моментов. Взять, к примеру, _currentQuestionIndex. Это свойство из вне можно поменять только с помощью методов nextQuestion и resetCurrentTestQuestions. Это сделано специально. Да, мы могли бы создать геттер и сеттер для этого свойства и управлять его значением из вне, но вспомним о принципах ООП и подумаем, чем это черевато на практике. Никто ведь не застрахован к примеру от такого участка кода:
ActionScript 3
1
if(currentQuestionIndex = _model.totalQuestionsCount){
Да, в этом сравнении я специально сделал ошибку, написав один символ "=" вместо двух, т.е. реализовал присвоение вместо сравнения. А это может быть ошибкой, которую будем искать долго и нудно. Ну и не стоит создавать это свойство просто публичным, т.к. мы не можем отследить кто, когда и как меняет его значения. В данном же случае мы имеем геттер, т.е. значение можем только читать, но не менять и 2 метода. Один запускается при окончании тестирования и в нем, возможно, помимо изменения значения текущего индекса, будут производиться какие-либо действия. Другой метод не только увеличивает значение текущего индекса, но и осуществляет проверку на окончание тестирования, о чем шлет соответствующее событие.
Что еще хорошо бы реализовать, что на данный момент отсутствует. Это интерфейсы. Зачем нам это нужно? Все дело в загрузке. В экземпляре класса DataLoader используются методы модели, которые больше нигде и никогда не должны использоваться. С другой стороны в загрузчике должны быть методы только для добавления вопросов. Остальное - не забота модели. Кстати, пока писал, обнаружил, что загрузчик занимается не тем, чем должен, он, помимо добавления вопросов, занимается перемешиванием, а это не его обязанность. Нужно отметить на будущее и попробовать исправить.

Далее я покажу класс отображения, поэтому лучше скачать проект в архиве, что бы можно было сравнивать код с содержимым fla/swc файла.
View
ActionScript 3
1
2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package  
{
    import data.Question;
    import flash.display.Sprite;
    import flash.display.Stage;
    import flash.events.MouseEvent;
    import flash.text.TextFieldAutoSize;
    import swc.test.PreloaderTemplate;
    import swc.test.TitleTemplate;
    
    /**
     * ...
     * @author TanaTiX
     */
    public class View extends Sprite 
    {
        public static const STAGE_BORDER_GAP:uint = 10;//зазор для выравнивания элементов дизайна относительно сцены
        public static const ANSWERS_GAP:uint = 10;//зазор между вариантами ответов
        
        private var _model:Model;//ссылка на модель
        private var _titleView:TitleTemplate;//ссылка на заголовок
        private var _preloader:PreloaderTemplate;//предзагрузчик
        private var _answerContainer:Sprite;//контэйнер для 
        
        public function View(model:Model, stage:Stage) 
        {
            _model = model;
            internalInit(stage);
        }
        
        /**
         * максимально разгружаем конструктор
         * @param   stage - ссылка на сцену только для получения ее размеров. Ссылку на сцену не сохраняем. Можно было бы получить ее непосредственно в этом методе. Идеологически так было бы правильнее.
         */
        private function internalInit(stage:Stage):void 
        {
            _titleView = new TitleTemplate();//вью-элемент для отображения вопросов
            _titleView._txAnswer.autoSize = TextFieldAutoSize.LEFT;
            _titleView._txAnswer.width = stage.stageWidth - 2 * STAGE_BORDER_GAP;//выравниваем текстовое поле для вопроса
            _answerContainer = new Sprite();//создаем контейнер для вариантов ответов
            addChild(_answerContainer);
            
            _preloader = new PreloaderTemplate();//добавляем прелоадер
            addChild(_preloader);
        }
        
        public function updateForStartTesting():void 
        {
            _titleView._txIndex.text = "Начать тестирование";
            _titleView._txAnswer.visible = false;
            _titleView._btStart.addEventListener(MouseEvent.CLICK, onQuestionUpdate);//подписываемся на клик для начала тестирования и/или отображения следующего вопроса
            removeChild(_preloader);//удаляем прелоадер, больше он не понадобится
            _preloader = null;
            addChild(_titleView);//добавляем на сцену элемент для простейшей навигации и вопросов
        }
        
        
        private function onQuestionUpdate(e:MouseEvent):void 
        {
            removeOldQuestion();
            _titleView._txAnswer.visible = true;
            addNewQuestion(_model.currentQuestionIndex, _model.currentQuestion);
        }
        
        private function removeOldQuestion():void 
        {
            _answerContainer.removeChildren();
        }
        
        /**
         * создаем вопросы и варианты ответов
         * @param   currentQuestionIndex
         * @param   currentQuestion
         */
        private function addNewQuestion(currentQuestionIndex:int, currentQuestion:Question):void 
        {
            _titleView._txIndex.text = "Вопрос № " + (_model.currentQuestionIndex + 1);//счет идет с 0, поэтому для отображения добавляем 1
            _titleView._txAnswer.text = _model.currentQuestion.answer;
            
            _answerContainer.y = _titleView.y + _titleView.height;
            var length:int = _model.currentQuestion.length;
            var verticalPosition:Number = ANSWERS_GAP;
            var answer:AnswerView;
            for (var i:int = 0; i < length; i++) 
            {
                answer = new AnswerView(_model.currentQuestion.getAnswerByIndex(i));
                answer.y = verticalPosition;
                _answerContainer.addChild(answer);
                verticalPosition += answer.height + ANSWERS_GAP;
            }
        }
        
    }
 
}
В этом классе ничего сложного: ссылка на модель, прелоадер (его при реальной работе вы можете даже не замтетить, т.к. контент в проекте загржается локально и весит не много), часть дизайна для собственно вопроса (там же копка для начала тестирования или перехода к следующему вопросу) и контейнер для вариантов ответов.
updateForStartTesting - преобразуем верхнюю часть.
onQuestionUpdate - обработчик, в котором удаляем предыдущий вопрос и пишем следующий.
addNewQuestion - собственно отображение нового вопроса. Данные активно получаем из модели.
Question
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package data 
{
    /**
     * ...
     * @author TanaTiX
     */
    public class Question 
    {
        private var _answer:String;//текст вопроса
        private var _questions:Vector.<Answer>;//список ответов
        
        public function Question(answer:String, questions:Vector.<Answer>) 
        {
            _questions = questions;
            _answer = answer;
            
        }
        /**
         * текст вопроса
         */
        public function get answer():String 
        {
            return _answer;
        }
        /**
         * сортировка
         * отсортировать можно и лучше, для этого существует большое количество разных алгоритмов, но мы не ставим целью погружаться во все нюансы алгоритмов сортировок, поступаем просто и наглядно
         */
        public function shuffleAnswers():void {
            var newQuestions:Vector.<Answer> = new Vector.<Answer>();//временный массив (здесь для удобства понимания договоримся, что вектор - это типизированный массив)
            var index:uint;//текущий выбранный индекс
            var length:int = _questions.length;//длин массива
            while (length > 0) {//пока исходный массив существует..
                index = Math.random() * length;//берем случайный индекс, согласно длин исходного массива
                newQuestions.push(_questions[index]);//добавляем выбранный элемент во временный массив
                _questions.splice(index, 1);//удаляем ранее перемещенный во временный массив элемент из исходного массива
                length--;//переопределяем длину массива, что бы каждый раз при работе цикла не вычислять ее снова (_questions.length)
            }
            _questions = newQuestions;//переопределяем выделенный в поле класса массив
        }
        /**
         * количество ответов
         */
        public function get length():uint {
            return _questions.length;
        }
        /**
         * получение объекта ответа по его индексу в массиве
         * @param   index
         * @return
         */
        public function getAnswerByIndex(index:uint):Answer {
            return _questions[index];
        }
    }
 
}
Answer
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package data 
{
    /**
     * ...
     * @author TanaTiX
     */
    public class Answer 
    {
        private var _title:String;//текст ответа
        private var _correct:Boolean;//правильный ответ или нет
        private var _select:Boolean;
        
        public function Answer(title:String, correct:Boolean) 
        {
            _correct = correct;
            _title = title;
            
        }
        
        public function get title():String 
        {
            return _title;
        }
        
        public function get correct():Boolean 
        {
            return _correct;
        }
        
        public function get select():Boolean 
        {
            return _select;
        }
        
        public function set select(value:Boolean):void 
        {
            _select = value;
        }
        
    }
 
}
TestEvents
ActionScript 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package data 
{
    /**
     * Класс для строковых констант, описывающих события в данном приложении
     * @author TanaTiX
     */
    public class TestEvents 
    {
        /**
         * добавление вопросов в базу данных
         */
        public static const ADD_QUESTIONS:String = "addQuestions";
        public static const TEST_COMPLETE:String = "testComplete";
        
        public function TestEvents() 
        {
            throw new Error("Этот класс только для хранения констант, не нужно на его основе создавать экземпляры класса");
        }
        
    }
 
}

И еще один класс, в котором у нас будет отображение варианта ответа
AnswerView
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package  
{
    import data.Answer;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import swc.test.AnswerTemplate;
    /**
     * ...
     * @author TanaTiX
     */
    public class AnswerView extends Sprite
    {
        private var _data:Answer;
        private var _template:AnswerTemplate;
        private var _selected:Boolean;
        
        public function AnswerView(data:Answer) 
        {
            _data = data;
            internalInit();
        }
        
        private function internalInit():void 
        {
            _selected = false;
            _template = new AnswerTemplate();
            
            _template._mcToggle.gotoAndStop(2);
            _template._mcToggle.buttonMode = true;
            _template._txAnswer.text = _data.title;
            addChild(_template);
            
            _template._mcToggle.addEventListener(MouseEvent.CLICK, onSelecting);
            addEventListener(Event.REMOVED_FROM_STAGE, onRemove);
        }
        
        private function onRemove(e:Event):void 
        {
            _data.select = _selected;
            removeEventListener(Event.REMOVED_FROM_STAGE, onRemove);
            _template._mcToggle.removeEventListener(MouseEvent.CLICK, onSelecting);
        }
        
        private function onSelecting(e:MouseEvent):void 
        {
            _selected = !_selected;
            if (_selected) {
                _template._mcToggle.gotoAndStop(1);
            }else {
                _template._mcToggle.gotoAndStop(2);
            }
        }
        
    }
 
}
Выделение варианта ответов построено на разных кадрах. При удалении экземпляра класса со сцены он автоматически чистится в методе onRemove для того что бы garbage collector мог полностью удалить объект из памяти. Данный подход применим в тех случаях, когда объект нам нужен только до тех пор, пока находится на сцене. Также в этом методе мы отмечаем выбранный ответ. Вероятно это будет дорабатываться.

Ну и теперь выкладываю архив со всем проектом на данный момент.

testTutorial.rar
2
Модератор
 Аватар для TanaTiX
2931 / 1790 / 180
Регистрация: 19.02.2011
Сообщений: 6,550
31.05.2014, 18:01  [ТС]
Добавил 2 новых класса для хранения выбранных результатов ответов, назвал с приставкой User: UserAnswer и UserQuestion.
Переименовал часть переменных. т.к. были перепутаны answer и question. Старайтесь давать внятные корректные имена переменным на английском языке, на худой конец хотя бы латиницей.
Попереносил ряд свойств/методов.
Несколько изменил способ генерации вопросов текущего тестирования с учетом появления новых классов.
Решил выделить эти изменения отдельным постом для большей наглядности сути происходящего. Без подобных сообщений можно было бы 1м постом описать идею, а 2м и последним сразу результат, что есть цель данного урока.
классы

ActionScript 3
1
2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package  
{
    import data.Question;
    import data.UserQuestion;
    import flash.display.Sprite;
    import flash.display.Stage;
    import flash.events.MouseEvent;
    import flash.text.TextFieldAutoSize;
    import swc.test.PreloaderTemplate;
    import swc.test.TitleTemplate;
    
    /**
     * ...
     * @author TanaTiX
     */
    public class View extends Sprite 
    {
        public static const STAGE_BORDER_GAP:uint = 10;//зазор для выравнивания элементов дизайна относительно сцены
        public static const ANSWERS_GAP:uint = 10;//зазор между вариантами ответов
        
        private var _model:Model;//ссылка на модель
        private var _titleView:TitleTemplate;//ссылка на заголовок
        private var _preloader:PreloaderTemplate;//предзагрузчик
        private var _answerContainer:Sprite;//контэйнер для 
        
        public function View(model:Model, stage:Stage) 
        {
            _model = model;
            internalInit(stage);
        }
        
        /**
         * максимально разгружаем конструктор
         * @param   stage - ссылка на сцену только для получения ее размеров. Ссылку на сцену не сохраняем. Можно было бы получить ее непосредственно в этом методе. Идеологически так было бы правильнее.
         */
        private function internalInit(stage:Stage):void 
        {
            _titleView = new TitleTemplate();//вью-элемент для отображения вопросов
            _titleView._txAnswer.autoSize = TextFieldAutoSize.LEFT;
            _titleView._txAnswer.width = stage.stageWidth - 2 * STAGE_BORDER_GAP;//выравниваем текстовое поле для вопроса
            _answerContainer = new Sprite();//создаем контейнер для вариантов ответов
            addChild(_answerContainer);
            
            _preloader = new PreloaderTemplate();//добавляем прелоадер
            addChild(_preloader);
        }
        
        public function updateForStartTesting():void 
        {
            _titleView._txIndex.text = "Начать тестирование";
            _titleView._txAnswer.visible = false;
            _titleView._btStart.addEventListener(MouseEvent.CLICK, onQuestionUpdate);//подписываемся на клик для начала тестирования и/или отображения следующего вопроса
            removeChild(_preloader);//удаляем прелоадер, больше он не понадобится
            _preloader = null;
            addChild(_titleView);//добавляем на сцену элемент для простейшей навигации и вопросов
        }
        
        
        private function onQuestionUpdate(e:MouseEvent):void 
        {
            removeOldQuestion();
            _model.saveUserQuestion();
            _titleView._txAnswer.visible = true;
            addNewQuestion(_model.currentQuestionIndex, _model.currentQuestion);
        }
        
        private function removeOldQuestion():void 
        {
            _answerContainer.removeChildren();
        }
        
        /**
         * создаем вопросы и варианты ответов
         * @param   currentQuestionIndex
         * @param   currentQuestion
         */
        private function addNewQuestion(currentQuestionIndex:int, currentQuestion:UserQuestion):void 
        {
            _titleView._txIndex.text = "Вопрос № " + (_model.currentQuestionIndex + 1);//счет идет с 0, поэтому для отображения добавляем 1
            _titleView._txAnswer.text = _model.currentQuestion.answer;
            
            _answerContainer.y = _titleView.y + _titleView.height;
            var length:int = _model.currentQuestion.length;
            var verticalPosition:Number = ANSWERS_GAP;
            var answer:AnswerView;
            for (var i:int = 0; i < length; i++) 
            {
                answer = new AnswerView(_model.currentQuestion.getAnswerByIndex(i));
                answer.y = verticalPosition;
                _answerContainer.addChild(answer);
                verticalPosition += answer.height + ANSWERS_GAP;
            }
        }
        
    }
 
}
ActionScript 3
1
2
3
4
5
6
7
8
9
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package  
{
    import data.Question;
    import data.TestEvents;
    import data.UserQuestion;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    /**
     * ...
     * @author TanaTiX
     */
    public class Model extends EventDispatcher
    {
        private var _defaultQuestionsForCurrentTest:uint;
        private var _totalQuestions:Vector.<Question>;//ссылка на массив с нашими вопросами
        private var _currentTestQuestions:Vector.<UserQuestion>;
        private var _currentQuestionIndex:int;
        
        public function Model() 
        {
            internalInit();
        }
        
        /**
         * инициализация
         */
        private function internalInit():void 
        {
            _totalQuestions = new Vector.<Question>();//создаем массив с вопросами
            _currentTestQuestions = new Vector.<UserQuestion>();//создаем массив с вопросами для текущего теста
        }
        
        /**
         * заполняем массив
         * @param   value
         */
        public function addQuestions(value:Vector.<Question>):void 
        {
            if (!value || value.length == 0) {//простая проверка на валидность
                return;
            }
            _totalQuestions = _totalQuestions.concat(value);
            dispatchEvent(new Event(TestEvents.ADD_QUESTIONS));//сообщаем, что появились новые вопросы
        }
        /**
         * сортировка
         * отсортировать можно и лучше, для этого существует большое количество разных алгоритмов, но мы не ставим целью погружаться во все нюансы алгоритмов сортировок, поступаем просто и наглядно
         */
        public function shuffleQuestioins():void {
            var newQuestions:Vector.<Question> = new Vector.<Question>();//временный массив (здесь для удобства понимания договоримся, что вектор - это типизированный массив)
            var index:uint;//текущий выбранный индекс
            var length:int = _totalQuestions.length;//длин массива
            while (length > 0) {//пока исходный массив существует..
                index = Math.random() * length;//берем случайный индекс, согласно длин исходного массива
                newQuestions.push(_totalQuestions[index]);//добавляем выбранный элемент во временный массив
                _totalQuestions.splice(index, 1);//удаляем ранее перемещенный во временный массив элемент из исходного массива
                length--;//переопределяем длину массива, что бы каждый раз при работе цикла не вычислять ее снова (_questions.length)
            }
            _totalQuestions = newQuestions;//переопределяем выделенный в поле класса массив
        }
        
        public function resetCurrentTestQuestions():void 
        {
            _currentQuestionIndex = 0;
        }
        public function get currentQuestion():UserQuestion {
            return _currentTestQuestions[_currentQuestionIndex];
        }
        
        public function get currentQuestionIndex():int 
        {
            return _currentQuestionIndex;
        }
        
        public function get defaultQuestionsForCurrentTest():uint 
        {
            return _defaultQuestionsForCurrentTest;
        }
        
        public function set defaultQuestionsForCurrentTest(value:uint):void 
        {
            _defaultQuestionsForCurrentTest = value;
        }
        
        public function nextQuestion():void {
            _currentQuestionIndex++;
            if (_currentQuestionIndex == _currentTestQuestions.length) {
                dispatchEvent(new Event(TestEvents.TEST_COMPLETE));
            }
        }
        
        public function createNewUserQuestions(count:uint):void 
        {
            if (!_totalQuestions || _totalQuestions.length < count) {
                throw new Error("Количество вопросов в базе данных меньше " + count);
            }
            _currentTestQuestions.length = 0;
            var i:int = 0;
            for each (var item:Question in _totalQuestions) 
            {
                _currentTestQuestions.push(_totalQuestions[i].cloneForAnswers());
                i++;
                if (i == count) {
                    return;
                }
            }
        }
        
        public function saveUserQuestion():void 
        {
            
        }
    }
 
}
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package  
{
    import data.Answer;
    import data.UserAnswer;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import swc.test.AnswerTemplate;
    /**
     * ...
     * @author TanaTiX
     */
    public class AnswerView extends Sprite
    {
        private var _data:UserAnswer;
        private var _template:AnswerTemplate;
        private var _selected:Boolean;
        
        public function AnswerView(data:UserAnswer) 
        {
            _data = data;
            internalInit();
        }
        
        private function internalInit():void 
        {
            _selected = false;
            _template = new AnswerTemplate();
            
            _template._mcToggle.gotoAndStop(2);
            _template._mcToggle.buttonMode = true;
            _template._txAnswer.text = _data.title;
            addChild(_template);
            
            _template._mcToggle.addEventListener(MouseEvent.CLICK, onSelecting);
            addEventListener(Event.REMOVED_FROM_STAGE, onRemove);
        }
        
        private function onRemove(e:Event):void 
        {
            _data.select = _selected;
            
            removeEventListener(Event.REMOVED_FROM_STAGE, onRemove);
            _template._mcToggle.removeEventListener(MouseEvent.CLICK, onSelecting);
        }
        
        private function onSelecting(e:MouseEvent):void 
        {
            _selected = !_selected;
            if (_selected) {
                _template._mcToggle.gotoAndStop(1);
            }else {
                _template._mcToggle.gotoAndStop(2);
            }
        }
        
    }
 
}
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package data 
{
    /**
     * ...
     * @author TanaTiX
     */
    public class Question 
    {
        private var _question:String;//текст вопроса
        private var _answers:Vector.<Answer>;//список ответов
        
        public function Question(question:String, answers:Vector.<Answer>) 
        {
            _answers = answers;
            _question = question;
            
        }
        /**
         * текст вопроса
         */
        public function get answer():String 
        {
            return _question;
        }
        /**
         * сортировка
         * отсортировать можно и лучше, для этого существует большое количество разных алгоритмов, но мы не ставим целью погружаться во все нюансы алгоритмов сортировок, поступаем просто и наглядно
         */
        public function shuffleAnswers():void {
            var newQuestions:Vector.<Answer> = new Vector.<Answer>();//временный массив (здесь для удобства понимания договоримся, что вектор - это типизированный массив)
            var index:uint;//текущий выбранный индекс
            var length:int = _answers.length;//длин массива
            while (length > 0) {//пока исходный массив существует..
                index = Math.random() * length;//берем случайный индекс, согласно длин исходного массива
                newQuestions.push(_answers[index]);//добавляем выбранный элемент во временный массив
                _answers.splice(index, 1);//удаляем ранее перемещенный во временный массив элемент из исходного массива
                length--;//переопределяем длину массива, что бы каждый раз при работе цикла не вычислять ее снова (_questions.length)
            }
            _answers = newQuestions;//переопределяем выделенный в поле класса массив
        }
        /**
         * количество ответов
         */
        public function get length():uint {
            return _answers.length;
        }
        /**
         * получение объекта ответа по его индексу в массиве
         * @param   index
         * @return
         */
        public function getAnswerByIndex(index:uint):Answer {
            return _answers[index];
        }
        
        public function cloneForAnswers():UserQuestion {
            return new UserQuestion(_question, _answers);
        }
    }
 
}
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package data 
{
    /**
     * ...
     * @author TanaTiX
     */
    public class UserQuestion 
    {
        private var _answer:String;
        private var _questions:Vector.<UserAnswer>;
        
        public function UserQuestion(answer:String, questions:Vector.<Answer>) 
        {
            _answer = answer;
            internalInit(questions);
        }
        
        private function internalInit(questions:Vector.<Answer>):void 
        {
            _questions = new Vector.<UserAnswer>();
            for each (var item:Answer in questions) 
            {
                _questions.push(new UserAnswer(item.title, item.correct));
            }
        }
        
        public function get answer():String 
        {
            return _answer;
        }
        public function get length():uint {
            return _questions.length;
        }
        public function getAnswerByIndex(index:uint):UserAnswer {
            return _questions[index];
        }
    }
 
}
ActionScript 3
1
2
3
4
5
6
7
8
9
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
package data 
{
    /**
     * ...
     * @author TanaTiX
     */
    public class UserAnswer
    {
        private var _select:Boolean;
        private var _title:String;
        private var _correct:Boolean;
        public function UserAnswer(title:String, correct:Boolean) 
        {
            _correct = correct;
            _title = title;
            //trace(title, correct);
        }
        public function get select():Boolean 
        {
            return _select;
        }
        
        public function get title():String 
        {
            return _title;
        }
        
        public function set select(value:Boolean):void 
        {
            _select = value;
        }
    }
 
}

Следующим этапом нужно обобщить вопросы и ответы и реализовать систему наследования, ну и причесать если чего не учел на этом этапе.
Также обратите внимание, что не смотря на изменение структуры проекта я нисколько не менял его визуальную часть.
И, как полагается, выкладываю исходники в архиве (только классы)
src.rar

ЗЫ. не забывайте, что в коде все еще могут быть ошибки
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
31.05.2014, 18:01
Помогаю со студенческими работами здесь

Давайте создадим свой урок по созданию игры!?
Доброго вечера пользователи форума! Предлагаю все желающим новичкам пытающимся разобраться в технологи создания игр на ActionScript 3.0 ,...

Тестирование флешки
Подскажите как правильно оттестировать ролик, какими прогами, утилитами, или есть штатные методы. Как посмотреть сколько памяти жрет,...

Локальное тестирование VK API
Добрый вечер. Вот возникли у меня проблемы с локальным тестированием... Вообщем так.... запросы от VK API отправляются на сервер, и...

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

Урок по рисованию кнопок с двойным градиентом, есть ли такой урок
Помогите найти урок по рисованию вот таких кнопок с двойным градиентов


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
5
Закрытая тема Создать тему
Новые блоги и статьи
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
Расскажи мне о Мире, бродяга
kumehtar 12.11.2025
— Расскажи мне о Мире, бродяга, Ты же видел моря и метели. Как сменялись короны и стяги, Как эпохи стрелою летели. - Этот мир — это крылья и горы, Снег и пламя, любовь и тревоги, И бескрайние. . .
PowerShell Snippets
iNNOKENTIY21 11.11.2025
Модуль PowerShell 5. 1+ : Snippets. psm1 У меня модуль расположен в пользовательской папке модулей, по умолчанию: \Documents\WindowsPowerShell\Modules\Snippets\ А в самом низу файла-профиля. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru