Saturday, August 11, 2012

Creating a Pentomino game using AS3: Part 24

In this tutorial we'll add the ability to save levels to the SharedObject and also add an indicator to the Editor to display how many cells there are currently drawn.

To save a level to the local drive, we use a saveLevelLocal() function in main.as. It's empty right now, so let's fill it with code.

Inside the function create a SharedObject instance and set its value to getLocal("myLevels") - the same object that we read in saved_levels. Then check if the levels property of sharedObject's data is null. If it is - turn it into an empty array. Create an Object with two properties - grid and shapes, set their values to the parameters received in the function. Then add this object to the levels array and flush() the shared object to save the data.

package  
{
import flash.display.MovieClip;
import flash.net.SharedObject;

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

public class main extends MovieClip
{

public function main() 
{
}

public function playLevel(grid:Array, shapes:Array):void {
gotoAndStop(2);
game.playLevel(grid, shapes);
}

public function saveLevelLocal(grid:Array, shapes:Array):void {
var sharedObject:SharedObject = SharedObject.getLocal("myLevels");
if (sharedObject.data.levels == null) sharedObject.data.levels = [];
var levelObject:Object = new Object;
levelObject.grid = grid;
levelObject.shapes = shapes;
sharedObject.data.levels.push(levelObject);
sharedObject.flush();
}

}

}

Now for the indicator feature - go to pentomino_editor object and add a new MovieClip there, give it an id of stat_display. Inside the MC, have some kind of small rectangle with a text field in it. Set the text field's id to tCells.

In pentomino_editor.as we'll create a new function that counts total cells and then performs a check to see if it's an illegal amount of cells. If the total number is a multiplier of 5, then we set textColor property of the text area to green, otherwise - red. This way the user will see when the number of cells is correct or incorrect.

private function displayTotalCells():void {
var totalCells:int = 0;
var i:int;
var u:int;
var width:int = mapGrid[0].length;
var height:int = mapGrid.length;

for (i = 0; i < height; i++) {
for (u = 0; u < width; u++) {
if (mapGrid[i][u] == 1) totalCells++;
}
}
stat_display.tCells.text = totalCells;

if (totalCells / 5 != Math.round(totalCells / 5)) {
stat_display.tCells.textColor = 0xFF0000;
}else {
stat_display.tCells.textColor = 0x009900;
}
}

We can call this function in the end of drawGrid():

private function drawGrid():void {
gridShape.graphics.clear();
var width:int = mapGrid[0].length;
var height:int = mapGrid.length;

var i:int;
var u:int;

// draw background
for (i = 0; i < height; i++) {
for (u = 0; u < width; u++) {
if (mapGrid[i][u] == 1) drawCell(u, i, 0xffffff, 1, 0x999999);
}
}
displayTotalCells();
}

We've got to add 2 listeners for stat_display - listen to MOUSE_OVER and MOUSE_OUT events. Turn the object invisible when the user's mouse is over (because the indicator might be in the way) and turn it back visible when mouse is out. Also add a line that puts stat_display on top of gridShape and canPutShape after adding those to stage.

public function pentomino_editor() 
{
Pentomino = (root as MovieClip);
addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
addEventListener(Event.ENTER_FRAME, onEnterFrame);

// add shape buttons
for (var i:int = 0; i < 4; i++) {
for (var u:int = 0; u < 3; u++) {
var shapeButton:MovieClip = new edit_shape();
shapeButton.x = 528 + u * 62;
shapeButton.y = 15 + i * 84;
addChild(shapeButton);
shapeButton.bg.alpha = 0.3;
shapeButton.shape.gotoAndStop(3 * i + u + 1);
shapeButtons.push(shapeButton);
shapeButton.addEventListener(MouseEvent.ROLL_OVER, buttonOver);
shapeButton.addEventListener(MouseEvent.ROLL_OUT, buttonOut);
}
}

// buttons
btn_mainmenu.addEventListener(MouseEvent.CLICK, doMainmenu);
btn_reset.addEventListener(MouseEvent.CLICK, function():void { newLevel() } );
btn_save.addEventListener(MouseEvent.CLICK, doSave);
stat_display.addEventListener(MouseEvent.MOUSE_OVER, function() { stat_display.alpha = 0 } );
stat_display.addEventListener(MouseEvent.MOUSE_OUT, function() { stat_display.alpha = 1 } );

// new level
newLevel();

addChild(gridShape);
addChild(canPutShape);
stat_display.parent.setChildIndex(stat_display, stat_display.parent.numChildren - 1);
canPutShape.mouseEnabled = false;
canPutShape.mouseChildren = false;
}

Full pentomino_editor.as code:

package  
{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.sampler.NewObjectSample;
import flash.utils.ByteArray;

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

public class pentomino_editor extends MovieClip
{
private var mapGrid:Array = [];
private var shapeButtons:Array = [];

private var gridShape:Sprite = new Sprite();
private var canPutShape:Sprite = new Sprite();
private var gridStartX:int;
private var gridStartY:int;
private var gridCellWidth:int;
private var canDraw:Boolean = false;
private var mouseDown:Boolean = false;
private var currentCell:Point = new Point( -1, -1);

private static var Pentomino:MovieClip;

public function pentomino_editor() 
{
Pentomino = (root as MovieClip);
addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
addEventListener(Event.ENTER_FRAME, onEnterFrame);

// add shape buttons
for (var i:int = 0; i < 4; i++) {
for (var u:int = 0; u < 3; u++) {
var shapeButton:MovieClip = new edit_shape();
shapeButton.x = 528 + u * 62;
shapeButton.y = 15 + i * 84;
addChild(shapeButton);
shapeButton.bg.alpha = 0.3;
shapeButton.shape.gotoAndStop(3 * i + u + 1);
shapeButtons.push(shapeButton);
shapeButton.addEventListener(MouseEvent.ROLL_OVER, buttonOver);
shapeButton.addEventListener(MouseEvent.ROLL_OUT, buttonOut);
}
}

// buttons
btn_mainmenu.addEventListener(MouseEvent.CLICK, doMainmenu);
btn_reset.addEventListener(MouseEvent.CLICK, function():void { newLevel() } );
btn_save.addEventListener(MouseEvent.CLICK, doSave);
stat_display.addEventListener(MouseEvent.MOUSE_OVER, function() { stat_display.alpha = 0 } );
stat_display.addEventListener(MouseEvent.MOUSE_OUT, function() { stat_display.alpha = 1 } );

// new level
newLevel();

addChild(gridShape);
addChild(canPutShape);
stat_display.parent.setChildIndex(stat_display, stat_display.parent.numChildren - 1);
canPutShape.mouseEnabled = false;
canPutShape.mouseChildren = false;
}

private function onMouseMove(evt:MouseEvent):void {
if(mapGrid.length>0){
var mousePos:Point = new Point(Math.floor((mouseX - gridStartX) / gridCellWidth), Math.floor((mouseY - gridStartY) / gridCellWidth));
canPutShape.x = mousePos.x * gridCellWidth + gridStartX;
canPutShape.y = mousePos.y * gridCellWidth + gridStartY;
if (mousePos.x < mapGrid[0].length && mousePos.y < mapGrid.length && mousePos.x >= 0 && mousePos.y >= 0) {
canPutShape.alpha = 1;
}else {
canPutShape.alpha = 0;
}
}
}

private function newLevel():void {
canDraw = false;

var newScreen:MovieClip = new new_edit_screen();
addChild(newScreen);
newScreen.tWidth.restrict = "0-9";
newScreen.tHeight.restrict = "0-9";
newScreen.tWidth.text = 10;
newScreen.tHeight.text = 6;
newScreen.incorrect.alpha = 0;
newScreen.btn_continue.addEventListener(MouseEvent.CLICK, editContinue);

function editContinue(evt:MouseEvent):void {
if (newScreen.tWidth.text == "" || newScreen.tHeight.text == "" || newScreen.tWidth.text == "0" || newScreen.tHeight.text == "0") {
newScreen.incorrect.alpha = 1;
return;
}
newScreen.parent.removeChild(newScreen);
mapGrid = [];
var width:int = newScreen.tWidth.text;
var height:int = newScreen.tHeight.text;
for (var i:int = 0; i < height; i++) {
mapGrid[i] = [];
for (var u:int = 0; u < width; u++) {
mapGrid[i][u] = 1;
}
}
// grid settings
calculateGrid();
gridShape.x = gridStartX;
gridShape.y = gridStartY;

// draw tiles
drawGrid();

// canPutShape settings
canPutShape.graphics.clear();
canPutShape.graphics.lineStyle(2, 0xff0000);
canPutShape.graphics.drawRect(0, 0, gridCellWidth, gridCellWidth);
canPutShape.alpha = 0;

canDraw = true;
}
}

private function calculateGrid():void {
var columns:int = mapGrid[0].length;
var rows:int = mapGrid.length;

// free size: 520x460
// fit in: 510x450

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

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

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

if (height < 450) {
gridStartY = (450 - height) / 2;
}
if (height >= 450) {
gridCellWidth = Math.round(450 / rows);
height = rows * gridCellWidth;
width = columns * gridCellWidth;
gridStartY = (460 - height) / 2;
gridStartX = (520 - width) / 2;
}
}

private function drawGrid():void {
gridShape.graphics.clear();
var width:int = mapGrid[0].length;
var height:int = mapGrid.length;

var i:int;
var u:int;

// draw background
for (i = 0; i < height; i++) {
for (u = 0; u < width; u++) {
if (mapGrid[i][u] == 1) drawCell(u, i, 0xffffff, 1, 0x999999);
}
}
displayTotalCells();
}

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

private function buttonOver(evt:MouseEvent):void {
evt.currentTarget.bg.alpha = 1;
}

private function buttonOut(evt:MouseEvent):void {
evt.currentTarget.bg.alpha = 0.3;
}

private function doMainmenu(evt:MouseEvent):void {
Pentomino.gotoAndStop(1);
}

private function onMouseDown(evt:MouseEvent):void {
mouseDown = true;
}

private function onMouseUp(evt:MouseEvent):void {
mouseDown = false;
currentCell = new Point(-1, -1)
}

private function onEnterFrame(evt:Event):void {
// if drawing is allowed and mouse is down
if (canDraw && mouseDown) {
var mousePos:Point = new Point(Math.floor((mouseX - gridStartX) / gridCellWidth), Math.floor((mouseY - gridStartY) / gridCellWidth));
// if valid coordinates
if (mousePos.x < mapGrid[0].length && mousePos.y < mapGrid.length && mousePos.x >= 0 && mousePos.y >= 0) {
// if the cell is not "current cell"
if (mousePos.x != currentCell.x || mousePos.y != currentCell.y) {
currentCell.x = mousePos.x;
currentCell.y = mousePos.y;
if (mapGrid[mousePos.y][mousePos.x] == 1) {
mapGrid[mousePos.y][mousePos.x] = 0;
}else {
mapGrid[mousePos.y][mousePos.x] = 1;
}
drawGrid();
}
}
}
}

private function doSave(evt:MouseEvent):void {
if (checkSave()) {
var shapes:Array = [];
for (var i:int = 0; i < shapeButtons.length; i++) {
shapes.push(shapeButtons[i].count.value);
}
var saveScreen:MovieClip = new save_screen(Pentomino, mapGrid, shapes, alertClose);
addChild(saveScreen);
canDraw = false;
}
}

private function displayTotalCells():void {
var totalCells:int = 0;
var i:int;
var u:int;
var width:int = mapGrid[0].length;
var height:int = mapGrid.length;

for (i = 0; i < height; i++) {
for (u = 0; u < width; u++) {
if (mapGrid[i][u] == 1) totalCells++;
}
}
stat_display.tCells.text = totalCells;

if (totalCells / 5 != Math.round(totalCells / 5)) {
stat_display.tCells.textColor = 0xFF0000;
}else {
stat_display.tCells.textColor = 0x009900;
}
}

private function checkSave():Boolean {
// count total cells
var totalCells:int = 0;
var i:int;
var u:int;
var width:int = mapGrid[0].length;
var height:int = mapGrid.length;

for (i = 0; i < height; i++) {
for (u = 0; u < width; u++) {
if (mapGrid[i][u] == 1) totalCells++;
}
}

// check if total cells can be divided by 5
if (totalCells / 5 != Math.round(totalCells / 5)) {
alert("Error!\nIncorrect cell count: " + totalCells + ", number must be divideable by 5.");
return false;
}

// count total available shape count
var totalShapes:int = 0;

for (i = 0; i < shapeButtons.length; i++) {
totalShapes += shapeButtons[i].count.value;
}

// check if there are enough shapes available
if (totalCells > totalShapes * 5) {
alert("Error!\nNot enough shapes available: " + totalShapes + " out of " + totalCells/5);
return false;
}

return true;
}

private function alert(message:String):void {
var alertWindow:MovieClip = new alert_screen(message, alertClose);
addChild(alertWindow);
canDraw = false;
}

private function alertClose():void {
canDraw = true;
}

}

}

Thanks for reading!

Results:


No comments:

Post a Comment