Monday, August 20, 2012

Creating a Pentomino game using AS3: Part 33

In this tutorial we'll start working on adding the automatic level solving feature. When its completed, the user will be able to see how many solutions there are for a specific puzzle.

Start by opening the project in Flash and creating 5th frame on the main timeline. Here add a new MovieClip, give it an id "solver".

Inside of solver we're going to display the level preview, how many of each shape is available and information text.

For the level preview, add a MovieClip which contains a rectangle (top left corner's coordinates are 0,0). Give this MovieClip an id "levelPreview". Inside of levelPreview, add an empty MovieClip with an id "shape".

Now go back to solver, and add 12 little shape icons and 12 text fields underneath - give them ids from tShape1 to tShape12, the same way we did it in each level item in saved level browser.

Finally add a dynamic text field with id "tInfo".

Now open the saved_levels MovieClip and open a level item MovieClip, add a new button to its panel (4th button so far), label it "Solve" and give it an id of btn_solve.

Go to saved_levels.as class and inside the constructor add 3 mouse event listeners for btn_solve buttons. In their inline handlers call solveLevel() function of root class, pass the level data object as parameter:

public function saved_levels() 
{
savedLevels = SharedObject.getLocal("myLevels");
btn_back.addEventListener(MouseEvent.CLICK, doBack);
if (savedLevels.data.levels != null) levels = savedLevels.data.levels;
tInfo.text = levels.length + " levels (" + savedLevels.size + "B)";
pages = Math.ceil(levels.length / 3);
goPage(1);
btn_previous.addEventListener(MouseEvent.CLICK, function() { goPage(currentPage - 1) } );
btn_next.addEventListener(MouseEvent.CLICK, function() { goPage(currentPage + 1) } );
item1.btn_play.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).playLevel(levels[3 * (currentPage-1)].grid, levels[3 * (currentPage-1)].shapes, 3 * (currentPage-1));} );
item2.btn_play.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).playLevel(levels[3 * (currentPage-1) + 1].grid, levels[3 * (currentPage-1) + 1].shapes, 3 * (currentPage-1)+1);} );
item3.btn_play.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).playLevel(levels[3 * (currentPage-1) + 2].grid, levels[3 * (currentPage-1) + 2].shapes, 3 * (currentPage-1)+2);} );
item1.btn_edit.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).editLevel(levels[3 * (currentPage-1)])} );
item2.btn_edit.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).editLevel(levels[3 * (currentPage-1) + 1])} );
item3.btn_edit.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).editLevel(levels[3 * (currentPage-1) + 2]) } );
item1.btn_delete.addEventListener(MouseEvent.CLICK, function () { deleteLevel(3 * (currentPage-1))} );
item2.btn_delete.addEventListener(MouseEvent.CLICK, function () { deleteLevel(3 * (currentPage-1) + 1)} );
item3.btn_delete.addEventListener(MouseEvent.CLICK, function () { deleteLevel(3 * (currentPage-1) + 2) } );
item1.btn_solve.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).solveLevel(levels[3 * (currentPage-1)])} );
item2.btn_solve.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).solveLevel(levels[3 * (currentPage-1) + 1])} );
item3.btn_solve.addEventListener(MouseEvent.CLICK, function () { (root as MovieClip).solveLevel(levels[3 * (currentPage-1) + 2]) } );
}

Go to main.as, add this solveLevel() function. Redirect the user to 5th frame and call solver's solve() function, pass the level data object as parameter:

public function solveLevel(levelItem:Object):void {
gotoAndStop(5);
solver.solve(levelItem);
}

Now find the "solver" MovieClip in Flash in the library and give it a class path "level_solver".

Create a new class file, level_solver.as. Leave the constructor empty, declare a solve() function.

The solve() function draws the level preview by calling drawPreview() method, pass the reference to the levelPreview MC and the grid data as parameters.

After that, set tInfo's text to "Solving level LEVELNAME", where LEVELNAME is the name of the level.

Then apply values to each tShape text field:

public function solve(levelItem:Object):void {
drawPreview(levelPreview, levelItem.grid);
tInfo.text = 'Solving level "' + levelItem.name + '"';
var shapes:Array = levelItem.shapes;
for (var i:int = 1; i <= 12; i++) {
this["tShape" + i].text = shapes[i - 1];
}
}

Add 2 more functions - drawPreview() and drawCell() - you can see that they are very similar to the functions with the same name in saved_levels.as, I modified it by removing some lines related to counting number of cells:

private function drawPreview(levelPreview:MovieClip, mapGrid:Array):void {
var columns:int = mapGrid[0].length;
var rows:int = mapGrid.length;
var frameWidth:int = levelPreview.width;
var frameHeight:int = levelPreview.height;
var padding:int = 5;
var fitWidth:int = levelPreview.width - (padding * 2);
var fitHeight:int = levelPreview.height - (padding * 2);
var gridStartX:int;
var gridStartY:int;

// calculate width of a cell:
var gridCellWidth:int = Math.round(fitWidth / columns);

var width:int = columns * gridCellWidth;
var height:int = rows * gridCellWidth;

// calculate side margin
gridStartX = (frameWidth - width) / 2;

if (height < fitHeight) {
gridStartY = (fitHeight - height) / 2;
}
if (height >= fitHeight) {
gridCellWidth = Math.round(fitHeight / rows);
height = rows * gridCellWidth;
width = columns * gridCellWidth;
gridStartY = (frameHeight - height) / 2;
gridStartX = (frameWidth - width) / 2;
}

// draw map
levelPreview.shape.x = gridStartX;
levelPreview.shape.y = gridStartY;
levelPreview.shape.graphics.clear();
var i:int;
var u:int;

for (i = 0; i < rows; i++) {
for (u = 0; u < columns; u++) {
if (mapGrid[i][u] == 1) {
drawCell(u, i, 0xffffff, 1, 0x999999, gridCellWidth, levelPreview.shape);
}
}
}
}

private function drawCell(width:int, height:int, fill:uint, thick:Number, line:uint, gridCellWidth:int, gridShape:MovieClip):void {
gridShape.graphics.beginFill(fill);
gridShape.graphics.lineStyle(thick, line);
gridShape.graphics.drawRect(width * gridCellWidth, height * gridCellWidth, gridCellWidth, gridCellWidth);
}

Full level_solver.as code so far:

package  
{
import fl.controls.TextInput;
import flash.display.MovieClip;
import flash.display.Shape;
import flash.events.MouseEvent;
import flash.net.SharedObject;
import flash.text.TextField;

/**
 * Open-source pentomino game engine
 * @author Kirill Poletaev
 */

public class level_solver extends MovieClip
{


public function level_solver() 
{
}

public function solve(levelItem:Object):void {
drawPreview(levelPreview, levelItem.grid);
tInfo.text = 'Solving level "' + levelItem.name + '"';
var shapes:Array = levelItem.shapes;
for (var i:int = 1; i <= 12; i++) {
this["tShape" + i].text = shapes[i - 1];
}
}

private function drawPreview(levelPreview:MovieClip, mapGrid:Array):void {
var columns:int = mapGrid[0].length;
var rows:int = mapGrid.length;
var frameWidth:int = levelPreview.width;
var frameHeight:int = levelPreview.height;
var padding:int = 5;
var fitWidth:int = levelPreview.width - (padding * 2);
var fitHeight:int = levelPreview.height - (padding * 2);
var gridStartX:int;
var gridStartY:int;

// calculate width of a cell:
var gridCellWidth:int = Math.round(fitWidth / columns);

var width:int = columns * gridCellWidth;
var height:int = rows * gridCellWidth;

// calculate side margin
gridStartX = (frameWidth - width) / 2;

if (height < fitHeight) {
gridStartY = (fitHeight - height) / 2;
}
if (height >= fitHeight) {
gridCellWidth = Math.round(fitHeight / rows);
height = rows * gridCellWidth;
width = columns * gridCellWidth;
gridStartY = (frameHeight - height) / 2;
gridStartX = (frameWidth - width) / 2;
}

// draw map
levelPreview.shape.x = gridStartX;
levelPreview.shape.y = gridStartY;
levelPreview.shape.graphics.clear();
var i:int;
var u:int;

for (i = 0; i < rows; i++) {
for (u = 0; u < columns; u++) {
if (mapGrid[i][u] == 1) {
drawCell(u, i, 0xffffff, 1, 0x999999, gridCellWidth, levelPreview.shape);
}
}
}
}

private function drawCell(width:int, height:int, fill:uint, thick:Number, line:uint, gridCellWidth:int, gridShape:MovieClip):void {
gridShape.graphics.beginFill(fill);
gridShape.graphics.lineStyle(thick, line);
gridShape.graphics.drawRect(width * gridCellWidth, height * gridCellWidth, gridCellWidth, gridCellWidth);
}
}

}

The auto solver doesn't solve anything yet, but it's a start.

Thanks for reading!

The results:


No comments:

Post a Comment