Showing posts with label pentomino. Show all posts
Showing posts with label pentomino. Show all posts

Thursday, February 5, 2015

Creating a Pentomino game using AS3 Part 17

Today well add the ability to draw holes and cells on the grid.

First of all well declare 3 new variables, 2 of which are booleans set to false by default, and third is a Point variable with default coordinates -1;-1.

private var canDraw:Boolean = false;
private var mouseDown:Boolean = false;
private var currentCell:Point = new Point(-1, -1);

In the constructor, add 3 new event listeners for MOUSE_DOWN, MOUSE_UP and ENTER_FRAME events:

addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
addEventListener(Event.ENTER_FRAME, onEnterFrame);

The canDraw value will be off when the canvas size window is open, and on when the window is closed. Thats why we can set canDraw to false in the beginning of newLevel() and to true in the end of its editContinue() function:

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;
}
}

The mouseDown value is set to true in onMouseDown() and to false in onMouseUp(). Moreover, we set currentCell to -1;-1 point in onMouseUp():

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

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

Were using the currentCell variable because our drawing tool will be similar to a brush, but it also has to toggle cells (from a hole to a cell, and from a cell to a hole). If the user once clicked on a cell, it toggles its value. But since we are going to have our drawing code written in an ENTER_FRAME event handler, we need to make sure the same cell doesnt get toggled more than once.

In the onEnterFrame() function, we check if we are allowed to draw and if the mouse is held down. Then we check if the users mouse is rolled over a cell. If it is, we check if that cell does not have the coordinates of currentCell. And if so, we toggle the value, set currentCells coordinates to currently selected cells coordinates and call drawGrid():

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();
}
}
}
}

Full code so far:

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);

public function pentomino_editor()
{
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()});

// new level
newLevel();

addChild(gridShape);
addChild(canPutShape);
}

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);
}
}
}

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 {
(root as MovieClip).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();
}
}
}
}

}

}

Thanks for reading!

The results:

Read more »

Wednesday, February 4, 2015

Creating a Pentomino game using AS3 Part 19

In this tutorial well add a custom alert system to our game.

With this alert system well be able to pop up a window with an error/warning message to the user, with an "OK" button that closes the window.

Start by opening your Flash project and creating a new MovieClip. You can do it by duplicating any of the _screen movieclips you have so far (win_screen or new_edit_screen). Draw a window box, add a button with an id "btn_continue" and a dynamic text field with id "tAlert". Give this MovieClip a class path of alert_screen.

Create the alert_screen.as file. Make the class extend MovieClip, and declare 1 variable - onClose, typed Function.

The constructor shall have two paramaters - one required and one optional. The required one is message:String, which value we apply to tAlerts text property in the constructor. The optional one is closeHandler:Function, set it to null by default. This will be the reference to a function that will be called when the user closes the alert window. In the constructor set onClose to reference this value and also add a CLICK event listener for btn_continue. In the handler function, remove the event listener, remove the movie clip and call onClose function:

package  
{
import flash.display.MovieClip;
import flash.events.MouseEvent;

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

public class alert_screen extends MovieClip
{

var onClose:Function;

public function alert_screen(message:String, closeHandler:Function = null)
{
onClose = closeHandler;
tAlert.text = message;
btn_continue.addEventListener(MouseEvent.CLICK, onContinue);
}

private function onContinue(evt:MouseEvent):void {
btn_continue.removeEventListener(MouseEvent.CLICK, onContinue);
this.parent.removeChild(this);
onClose.call();
}

}

}

Now go to pentomino_editor.as script file. Here we will add 2 new functions - alert() and alertClose(). The first function has 1 parameter message:String, the function simply creates an instance of alertWindow and sets canDraw to false. When creating the alertWindow object, set its message value to the functions parameter, and its closeFunction parameter to alertClose.

The alertClose() function sets canDraw to 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;
}

We can now use alert() instead of trace() in checkSave() function to display errors:

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!
Incorrect 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!
Not enough shapes available: " + totalShapes + " out of " + totalCells/5);
return false;
}

return true;
}

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);

public function pentomino_editor()
{
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);

// new level
newLevel();

addChild(gridShape);
addChild(canPutShape);
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);
}
}
}

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 {
(root as MovieClip).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()) {
trace("Save!");
}
}

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!
Incorrect 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!
Not 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:

Read more »

Tuesday, February 3, 2015

Creating a Pentomino game using AS3 Part 43

Today we continue working on level selection screen.

Firstly, go to level_item.as class and delete all the code from the constructor, move it to a separate public function called setLevel():

public function setLevel(mapGrid:Array, shapes:Array, title:String):void {
tTitle.text = title;
levelPreview.mouseEnabled = false;
drawPreview(levelPreview, mapGrid);
btn.addEventListener(MouseEvent.CLICK, function() { (root as MovieClip).playLevel(mapGrid, shapes); } );
}

Full class:

package  
{
import flash.display.MovieClip;
import flash.events.MouseEvent;

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

public class level_item extends MovieClip
{

public function level_item()
{
}

public function setLevel(mapGrid:Array, shapes:Array, title:String):void {
tTitle.text = title;
levelPreview.mouseEnabled = false;
drawPreview(levelPreview, mapGrid);
btn.addEventListener(MouseEvent.CLICK, function() { (root as MovieClip).playLevel(mapGrid, shapes); } );
}

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.x = gridStartX;
levelPreview.y = gridStartY;
levelPreview.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);
}
}
}

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);
}
}

}

Now go to level_select.as.

I want my level selection menu to work like a horizontally scrolling line of thumbnails, operated by two arrows that display the next and previous level previews. The thumbnails will smoothly scroll to focus on the selected level.

To achieve this, we need to create a holder of 5 level previews. Why 5? Because at the same time only 3 pictures are visible on screen - 1 fully (the selected level), and surrounding 2 - partially. The 2 remaining level items that are invisible are needed, because they will be partially visible when scrolling animation is occuring. Were not going to create a separate level preview item for all the levels in the database, were just going to reuse these 5.

First thing we need to do in level_select.as is create an array of levels. Right now were only going to store 5 levels, 4 of which are dummy levels (with only difference being the title).

private var levels:Array = [
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 10x6"
},
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 2"
},
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 3"
},
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 4"
},
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 5"
}
];

Now declare these 5 variables:

private var currentLevel:int = 0;
private var distance:int = 100;
private var levelHolder:MovieClip;
private var levelItems:Array = [];
private var goX:int;

The currentLevel value represents the index of the level in "levels" array thats currently selected.

The distance value is the distance in pixels between each level preview object.

The levelHolder MovieClip is a holder for all 5 level previews.

The levelItems array will hold references to all 5 level preview movie clips.

The goX integer value is the coordinate to which the levelHolder movieclip must smoothly move to. This will be used for the scrolling animation.

Open Flash and go to the sixth frame. Add 2 buttons on the left and on the right - set their ids to btn_next and btn_preview.

In constructor, set levelHolder to a new MovieClip() and add 5 level objects one by one to levelHolder and levelItems array.

Add levelHolder to stage, set its coordinates. When setting the x coordinate, calculate the needed position to display the 3rd (central) level preview in the middle.

Set goX to levelHolders x value (we dont want any animation in the beginning).

Call a function called selectLevel(). Put btn_next and btn_prev buttons on top of all the rest elements using setChildIndex() method. Add an ENTER_FRAME event listener with handler onFrame:

public function level_select() 
{
(root as MovieClip).stop();
btn_back.addEventListener(MouseEvent.CLICK, doBack);

levelHolder = new MovieClip();
for (var i:int = 0; i < 5; i++) {
var level:MovieClip = new level_item();
levelHolder.addChild(level);
levelItems.push(level);
level.x = (level.width + distance) * i;
}
addChild(levelHolder);
levelHolder.y = 120;
levelHolder.x = -(level.width + distance) * 2 + level.width / 2;
goX = levelHolder.x;
selectLevel();
setChildIndex(btn_next, numChildren - 1);
setChildIndex(btn_prev, numChildren - 1);
addEventListener(Event.ENTER_FRAME, onFrame);
}

Now add a function called selectLevel(). Well be calling it every time we need to update our level item MovieClips and arrow buttons.

Call a function called setLevelPreview() 5 times - pass 2 values as parameters: index of the level item in levelItems array (from 0 to 4) and the index of level data from levels array.

Then add 2 if...statements that enable/disable arrows when needed:

private function selectLevel():void {
setLevelPreview(0, currentLevel - 2);
setLevelPreview(1, currentLevel - 1);
setLevelPreview(2, currentLevel);
setLevelPreview(3, currentLevel + 1);
setLevelPreview(4, currentLevel + 2);

if (currentLevel < levels.length - 1) {
btn_next.alpha = 1;
btn_next.mouseEnabled = true;
}else {
btn_next.alpha = 0;
btn_next.mouseEnabled = false;
}

if (currentLevel > 0) {
btn_prev.alpha = 1;
btn_prev.mouseEnabled = true;
}else {
btn_prev.alpha = 0;
btn_prev.mouseEnabled = false;
}
}

The setLevelPreview() enables or disables a level item object based on whether the respective level data exists. On success, also call the level items setLevel() function to update it with values:

private function setLevelPreview(ind:int, num:int):void {
if (num >= 0 && num < levels.length) {
levelItems[ind].alpha = 1;
levelItems[ind].mouseChildren = true;
levelItems[ind].setLevel(levels[num].grid, levels[num].shapes, levels[num].title);
}else {
levelItems[ind].alpha = 0;
levelItems[ind].mouseChildren = false;
}
}

The onFrame() function modifies levelHolders x value to smoothly move to goX coordinate:

private function onFrame(evt:Event):void {
levelHolder.x += Math.round((goX - levelHolder.x) / 5);
}

Full code so far:

package  
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;

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

public class level_select extends MovieClip
{

private var levels:Array = [
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 10x6"
},
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 2"
},
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 3"
},
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 4"
},
{grid:
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
],
shapes:
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
title:
"Standard 5"
}
];

private var currentLevel:int = 0;
private var distance:int = 100;
private var levelHolder:MovieClip;
private var levelItems:Array = [];
private var goX:int;

public function level_select()
{
(root as MovieClip).stop();
btn_back.addEventListener(MouseEvent.CLICK, doBack);

levelHolder = new MovieClip();
for (var i:int = 0; i < 5; i++) {
var level:MovieClip = new level_item();
levelHolder.addChild(level);
levelItems.push(level);
level.x = (level.width + distance) * i;
}
addChild(levelHolder);
levelHolder.y = 120;
levelHolder.x = -(level.width + distance) * 2 + level.width / 2;
goX = levelHolder.x;
selectLevel();
setChildIndex(btn_next, numChildren - 1);
setChildIndex(btn_prev, numChildren - 1);
addEventListener(Event.ENTER_FRAME, onFrame);
}

private function selectLevel():void {
setLevelPreview(0, currentLevel - 2);
setLevelPreview(1, currentLevel - 1);
setLevelPreview(2, currentLevel);
setLevelPreview(3, currentLevel + 1);
setLevelPreview(4, currentLevel + 2);

if (currentLevel < levels.length - 1) {
btn_next.alpha = 1;
btn_next.mouseEnabled = true;
}else {
btn_next.alpha = 0;
btn_next.mouseEnabled = false;
}

if (currentLevel > 0) {
btn_prev.alpha = 1;
btn_prev.mouseEnabled = true;
}else {
btn_prev.alpha = 0;
btn_prev.mouseEnabled = false;
}
}

private function setLevelPreview(ind:int, num:int):void {
if (num >= 0 && num < levels.length) {
levelItems[ind].alpha = 1;
levelItems[ind].mouseChildren = true;
levelItems[ind].setLevel(levels[num].grid, levels[num].shapes, levels[num].title);
}else {
levelItems[ind].alpha = 0;
levelItems[ind].mouseChildren = false;
}
}

private function onFrame(evt:Event):void {
levelHolder.x += Math.round((goX - levelHolder.x) / 5);
}

private function doBack(evt:MouseEvent):void {
(root as MovieClip).gotoAndStop(1);
}
}

}

Well work on arrow buttons and more in the next part.

Thanks for reading!
Read more »