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

Программирование игр на Flash/Flex Логическая игра Ним (Окончание)


Здравствуйте, уважаемые читатели!

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

За это время я несколько изменил интерфейс в сторону упрощения.

Большое удобство Flex, в частности, в том, что можно просто скопировать текст Главного модуля через буфер обмена, и в режиме редактирования исходного модуля (Source) вставить эти строки. Потом можно перейти в режим дизайна и посмотреть, что получилось. Давайте воспользуемся этой возможностью. Вот текст Главного модуля нашей программы:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="400" height="220" cornerRadius="6" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#FEFCFC, #FEFCFC]">
<mx:NumericStepper x="339" y="22" id="nstones1" value="3" minimum="0" maximum="6"/>
<mx:NumericStepper x="339" y="126" id="nstones3" value="5" minimum="0" maximum="6"/>
<mx:NumericStepper x="339" y="75" id="nstones2" value="4" minimum="0" maximum="6"/>
<mx:Canvas x="10" y="22" width="310" height="45" id="canvas1">
</mx:Canvas>
<mx:Canvas x="10" y="75" width="310" height="45" id="canvas2">
</mx:Canvas>
<mx:Canvas x="10" y="126" width="310" height="45" id="canvas3">
</mx:Canvas>
<mx:Button x="10" y="190" label="Загрузка..." width="244" enabled="true" id="begBtn" click="begClick()"/>
<mx:Script><![CDATA[
import Stones; // Связь данного файла с файлом класса
private var _st:Stones = new Stones(this); // Вызов конструктора класса и передача экземпляру класса указателя на самого себя (this)
public var rules:XML=<rules/>
private function begClick():void{
_st.logic();
}
]]></mx:Script>
<mx:CheckBox x="262" y="188" label="Ход компьютера" width="128" id="nextMove" selected="true"/>
</mx:Application>

Скопировали, посмотрели, увидели интерфейс, представленный на рисунке.
Как видите, имеются три элемента NumericStepper, с которыми мы знакомились
в прошлый раз, и три элемента класса Canvas, на которые мы будем помещать
камни.Кнопка и флажок пояснений не требуют.







Алгоритм игры

Математически доказано следующее. Пусть количество камней в каждой кучке n1, n2 и n3 соответственно. Если сумма по модулю 2 этих трех чисел равна 0, то такая позиция безопасна, в противном случае она опасна. Из опасной позиции при своем ходе можно получить безопасную, а из безопасной - только опасную. Следовательно, стратегия игры состоит в том, чтобы получить при своем ходе безопасную позицию, если это возможно.

1. Напишем функцию, которая возвращает сумму по модулю 2 трех чисел, передаваемых ей в качестве аргументов:

private function knimSum(n1:uint,n2:uint,n3:uint):uint{
return n1 ^ n2 ^ n3;
}

Как видите, совсем просто.

2. Взаимодействие игрока и компьютера будем описывать в терминах переходов игры из одного состояния в другое. Для этого создадим в нашем классе свойство State и переменную private var _state:int = -1; У меня немного изменился план игры по ходу его реализации. Первоначально планировалось использовать значения этого свойства от 0 до 3. Затем я счел нужным добавить дополнительное значение, которое соответствует начальному состоянию, когда пользователь может просмотреть правила игры, и способ работы с программой. Чтобы не менять остальные, уже назначенные значения, пришлось назначить начальное состояние свойства _state = -1. При переходе программы от одного свойства к другому изменяется текст, отображаемый на кнопке, и, что самое главное, реакция программы на нажатие кнопки (обработка события) также зависит от текущего состояния.
На своем сайте http://master-teacher.ru я помещу специальный небольшой материал, посвященный свойствам классов и двум методом, называемым Геттер и Сеттер. В тексте модуля Вы найдете метод сеттер, то есть, метода класса, вызваемого тогда, когда изменяется свойство State.

private function set State(newState:uint):void{


3. Каждый раз, когда пользователь нажмет на кнопку, будет вызван метод logic:

public function logic():void{ // Вызов при нажатии на кнопку


4. В этом методе используются значения свойств: _currentCanvas, и _currentNs, используемые в функции logic. Когда игрок изменяет значение количества камней, выбираемых в одной из кучек, требуется запомнить, какая кучка и какой элемент NumericStepper используются. Эта информация и хранится в данных свойствах:
private var _currentCanvas:Canvas;
private var _currentNs:NumericStepper;
Значения этих свойств изменяются в обработчике события (см. текст модуля:

private function nstChange(e:Event):void{ //Если пользователь изменил значение одного элемента

Связь этого обработчика с соответствующими событиями описывается так:
nstones1.addEventListener("change",nstChange);
nstones2.addEventListener("change",nstChange);
nstones3.addEventListener("change",nstChange);

5. Осталось рассмотреть, как компьютер находит и выполняет очередной ход. Алгоритм несложен: перебирая все возможные ходы, пытаемся найти ход, приводящий к безопасной позиции. Если это удается, то перебор завершается, и делается выбранный ход. Если это не удается (позиция опасна и не удается превратить ее в безопасную), делается случайный ход. Алгоритм реализован в функции:
private function determMove():void

6. Как я уже отмечал, в окончательном варианте игры добавлен небольшой справочник по правилам игры, использованию программы и возможным результатам. Этот справочник я оформил в виде XML-файла, который читается отдельным модулем GetKbFromFile. Использование языка XML в программировании игр заслуживает отдельного рассмотрения, этому будет посвящен отдельный выпуск (статья). Здесь ограничимся лишь текстом данного модуля:

package {
import flash.events.*;
import flash.net.*;
import flash.utils.*;
public class GetKbFromFile{
private var __parent:Stones;
public function GetKbFromFile(pa:Stones) {
__parent = pa
}
public function ask(what:String):void {
var loader:URLLoader=new URLLoader();
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.addEventListener(Event.COMPLETE, handleComplete);
loader.load( new URLRequest(what));
}
private function handleComplete( event:Event ):void {
__parent.answer(event.target.data);
}
}
}

А вот и полный текст модуля Stones на ActionScript:

// Класс Stones для игры Ним
package{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;

import mx.containers.Canvas;
import mx.controls.Button;
import mx.controls.CheckBox;
import mx.controls.Image;
import mx.controls.NumericStepper;
import mx.controls.TextArea;
public class Stones extends Sprite{ //Имя класса должно совпадать с именем файла.
private var __parent:Knim; //Это свойство будет использоваться для связи с главным модулем
private var _state:int = -1;
private var _list1:Array = new Array();
private var _list2:Array = new Array();
private var _list3:Array = new Array();
private var _panel:Canvas = new Canvas;
private var _area:TextArea = new TextArea;
private var _currentCanvas:Canvas;
private var _currentNs:NumericStepper;
private var _getKb:GetKbFromFile;
private var _texts:Array = new Array(); //тексты из файла помощи
public function Stones(pa:Object){
__parent = pa as Knim;
_getKb = new GetKbFromFile(this);
_getKb.ask("stoneRules.xml"); //чтение файла помощи с сервера. Формат файла XML
}
public function answer(txt:String):void{ //Вызывается объектом _getKb при завершении загрузки файла.
with(__parent){
rules = XML(txt);
var str:String = rules.toXMLString();
begBtn.enabled = true;
}
State =0;
}
private function crArea():void{ //Вызов при State = 0
with (__parent){
_panel.height = begBtn.y + begBtn.height;
_panel.width = _area.width = stage.width;
_area.height = 0.7 * _panel.height;
_panel.addChild(_area);
var xx:uint = 5;
var yy:uint = _area.height-45;
var npart:uint =0;
for each(var element:XML in rules.elements()){ //чтение файла помощи и создание кнопок для его демонстрации
_texts.push(element.text());
var txt:String = element.@txt;
var bt:Button = new Button();
bt.width = 95;
bt.label = txt;
bt.x=xx;
bt.y = _area.y+ _area.height;
bt.name = "b"+npart.toString();
bt.addEventListener(MouseEvent.CLICK,btclick);
xx += bt.width + 2;
_panel.addChild(bt);
npart++;
}
addChild(_panel);
}
State = 1;
}

private function crNewGame():void{//Вызов при State = 1
with (__parent){
nstones1.addEventListener("change",nstChange);
nstones2.addEventListener("change",nstChange);
nstones3.addEventListener("change",nstChange);
crStones(nstones1.value,45,canvas1);
crStones(nstones2.value,45,canvas2);
crStones(nstones3.value,45,canvas3);
_list1 = canvas1.getChildren();
_list2 = canvas2.getChildren();
_list3 = canvas3.getChildren();
_currentCanvas = canvas1;
_currentNs = nstones1;
nextMove.addEventListener(Event.CHANGE,nextMoveChange);
}
State = 2;
}
private function nextMoveChange(e:Event):void{
with (e.target as CheckBox) {
if (selected){
State = 2;
}else{
State = 3;
}
}
}
private function btclick(e:MouseEvent):void{ //Демонстрация текста из файла помощи
var na:uint = Number(e.target.name.charAt(1));
var txt:String = _texts[na];
if(txt) _area.text = txt;
else __parent.removeChild(_panel);

}

private function nstChange(e:Event):void{ //Если пользователь изменил значение одного элемента
// NumericStepper, остальные обнуляются.
//поэтому не удастся за один ход снять камни из разных наборов
with (__parent){
switch (e.target){
case(nstones1):
nstones2.value = nstones3.value = 0;
_currentCanvas = canvas1;
_currentNs = nstones1;
break;
case(nstones2):
nstones1.value = nstones3.value = 0;
_currentCanvas = canvas2;
_currentNs = nstones2;
break;
case(nstones3):
nstones1.value = nstones2.value = 0;
_currentCanvas = canvas3;
_currentNs = nstones3;
break;
}
}

}
private function crStones(nSt:uint,dx:uint,canvas:Canvas):void{
var cx:uint = 0;
for (var i:uint = 0; i<nSt;i++){
var img:Image = new Image();
img.source = "almaz.jpg";
img.x = cx;
cx += dx;
img.y = 0;
canvas.addChild(img);
}
}
private function delStones(canvas:Canvas,nstones:NumericStepper):void{
if(canvas.numChildren >= nstones.value){
while ( nstones.value > 0) {
canvas.removeChildAt(0);
nstones.value--;
}
}
if (gameFinished()) resGame("Я проиграл.");
else State = 2;
}

private function restore():void{
for each (var im:Image in _list1)
__parent.canvas1.addChild(im);

for each (im in _list2)
__parent.canvas2.addChild(im);

for each (im in _list3)
__parent.canvas3.addChild(im);
if(__parent.nextMove.selected)
State = 2;
else
State = 3;

}
// Логика игры
public function logic():void{ // Вызов при нажатии на кнопку
switch(_state){
case(0): crArea(); break;//Окно с правилами игры и с информацией о ее завершении.
case(1): crNewGame(); break; //Начальное состояние
case(2): determMove(); break;
case(3): delStones(_currentCanvas,_currentNs); break;
case(4): restore();
}
}
private function set State(newState:uint):void{
switch(newState){
case(0): var txt:String = "Правила игры";break;
case(1): txt = "Новая игра";break;
case(2):
txt = "Ход компьютера";
__parent.nextMove.selected = true;
break;
case(3):
txt = "Ход игрока";
__parent.nextMove.selected = false;
break;
case(4): txt = "Еще раз";break;
}
with (__parent){
begBtn.label = txt;
if ((newState == 2)||(newState == 3)){
nextMove.visible = true;
}else{
nextMove.visible = false;
}
}
_state = newState;
}
private function gameFinished():Boolean{
with(__parent)
return ((canvas1.numChildren + canvas2.numChildren + canvas3.numChildren) == 0);
}

private function knimSum(n1:uint,n2:uint,n3:uint):uint{
return n1 ^ n2 ^ n3;
}
private function resGame(str:String):void{
_area.text = str;
__parent.addChild(_panel);
State = 4;
}
private function determMove():void{
with (__parent){
var n1:uint = canvas1.numChildren;
var n2:uint = canvas2.numChildren;
var n3:uint = canvas3.numChildren;
var n:uint = 1;
while ((n <= n1) && knimSum(n1-n,n2,n3)) n++;
if(n<=n1){ //Найден результат
nstones1.value = n;
delStones(canvas1,nstones1);
}else{
n = 1;
while ((n <= n2) && knimSum(n1,n2-n,n3)) n++;
if(n <= n2){
nstones2.value = n;
delStones(canvas2,nstones2);
}else{
n = 1;
while ((n <= n3) && knimSum(n1,n2,n3-n)) n++;
if(n <= n3){
nstones3.value = n;
delStones(canvas3,nstones3);
}else{ // Позиция опасна, и не удается найти ход
if (canvas1.numChildren){
var canvas:Canvas = canvas1;
var nStepper:NumericStepper = nstones1;
nStepper.value
}else{
if (canvas2.numChildren){
canvas =canvas2;
nStepper = nstones2;
}else{
canvas =canvas3;
nStepper = nstones3;
}
}
n = canvas.numChildren;
n *= int(Math.random() + 1 );
nStepper.value = n;
delStones(canvas,nStepper);
}
}
}
if (gameFinished()) resGame("Я выиграл!");
else State = 3;
}
}
}
}


В избранное