Showing posts with label game. Show all posts
Showing posts with label game. Show all posts

Thursday, February 26, 2015

Virtual Trader UKs free Stockmarket simulation game

Link to Virtual Trader
Virtual Trader is UKs largest and best Stockmarket simulation game. Experience the thrill of investing with no risk. Its completely free to play. Virtual Trader is the most comprehensive Investment Simulation Game available, offering its users the opportunity to gain practical experience trading a wide range of investment vehicles, such as Stocks, Options, Futures, Mutual Funds etc. Start with a portfolio of ?100,000 of virtual money and trade stocks listed on the London Stock Exchange and Euronext.. Conduct limit and stop orders, build your own private game and more!"

(Lecturers, Teachers, Facilitators, etc! This is an excellent free tool to incorporate or to share with the students in your business, e-commerce, trading, economics, etc. classes, which will enable students to learn and experience trading through simulation and fun without any risk. )
Read more »

Thursday, February 5, 2015

Creating a Pentomino game using AS3 Part 3

In this part well add our first draggable shape.

By saying "first" I mean that only one type of shape is going to be available, and all it is going to do is follow the mouse and scale itself to have cell sizes of the ones in the grid.

Start by creating a new movie clip in your Flash project. Draw a 5x1vertical tiled shape inside of it, make sure each cells dimensions are exactly 100x100 pixels and the center of the movieclip is exactly in the center of the middle square:



Later on, well be able to add more shapes to this movieclip in other frames. One frame holds one shape, basically. The reason were doing this like that is because this way it is very easy to use nicely drawn game assets instead of simple squares drawn by Flash API. The example above is simply a placeholder.

Now, give this movie clip a class name of "game_shape". Then remove this movie clip from the scene, keeping it only in the library.

Go to pentomino_game.as file. Declare a new "cursor" MovieClip:

private var cursor:MovieClip;

In the constructor, set this cursor to a new game_shape instance and add it to the stage. Disable its mouseEnabled, mouseChildren and visible properties, add a MOUSE_MOVE event listener.

// cursor
cursor = new game_shape();
addChild(cursor);
cursor.mouseEnabled = false;
cursor.mouseChildren = false;
cursor.visible = false;
addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);

Then we also call startDragShape(1). This is temporary line, were only leaving it in the constructor for now:

// temporary shape
startDragShape(1);

The mouseMove() function simply makes the cursor follow the mouse:

private function mouseMove(evt:MouseEvent):void {
cursor.x = mouseX;
cursor.y = mouseY;
}

Now create the startDragShape() function. The "1" that we passed in the parameter is a variable called shapeType, it represents the frame in game_shape MovieClip that holds the wanted graphic.

Because we set the width and height of each single cell in the shape to 100 pixels, we can now easily find the scale multiplier to properly size the shape. Update the cursors size, alpha, visibility and make it go to the shapeType frame.

private function startDragShape(shapeType:int):void {
cursor.scaleX = gridCellWidth / 100;
cursor.scaleY = gridCellWidth / 100;
cursor.alpha = 0.75;
cursor.visible = true;
cursor.gotoAndStop(shapeType);
}

Full code:

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

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

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

private var gridShape:Sprite = new Sprite();
private var gridStartX:int;
private var gridStartY:int;
private var gridCellWidth:int;

private var cursor:MovieClip;

public function pentomino_game()
{
// default map
mapGrid = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];

// grid settings
calculateGrid();
addChild(gridShape);
gridShape.x = gridStartX;
gridShape.y = gridStartY;

// draw tiles
drawGrid();

// cursor
cursor = new game_shape();
addChild(cursor);
cursor.mouseEnabled = false;
cursor.mouseChildren = false;
cursor.visible = false;
addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);

// temporary shape
startDragShape(1);
}

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 mouseMove(evt:MouseEvent):void {
cursor.x = mouseX;
cursor.y = mouseY;
}

private function startDragShape(shapeType:int):void {
cursor.scaleX = gridCellWidth / 100;
cursor.scaleY = gridCellWidth / 100;
cursor.alpha = 0.75;
cursor.visible = true;
cursor.gotoAndStop(shapeType);
}

}

}

Thanks for reading!

The results:

Read more »

Monday, February 2, 2015

Creating a Pentomino game using AS3 Part 44

Today we continue working on the level selection menu.

We first need to go to level_item.as and fix some fundamental problems with selecting a level there.

Move the line that adds Click event listener for the button to constructor, and use theGrid and theShapes values instead of mapGrid and shapes.

public function level_item() 
{
btn.addEventListener(MouseEvent.CLICK, function() { (root as MovieClip).playLevel(theGrid, theShapes); } );
}

Declare these variables:

private var theGrid:Array;
private var theShapes:Array;

And set their values in setLevel():

public function setLevel(mapGrid:Array, shapes:Array, title:String):void {
tTitle.text = title;
levelPreview.mouseEnabled = false;
drawPreview(levelPreview, mapGrid);
theGrid = mapGrid;
theShapes = shapes;
}

Full level_item.as code:

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

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

public class level_item extends MovieClip
{
private var theGrid:Array;
private var theShapes:Array;

public function level_item()
{
btn.addEventListener(MouseEvent.CLICK, function() { (root as MovieClip).playLevel(theGrid, theShapes); } );
}

public function setLevel(mapGrid:Array, shapes:Array, title:String):void {
tTitle.text = title;
levelPreview.mouseEnabled = false;
drawPreview(levelPreview, mapGrid);
theGrid = mapGrid;
theShapes = 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);
}
}

}

Go to level_select.as and add a new variable levelWidth. We will use it store the value of a level thumbnails width:

private var levelWidth:int;

We set that value in the constructor, and immediately used it a few lines below - when setting levelHolders x value.

Also, add CLICK event handlers for btn_next and btn_prev buttons in the constructor. Add a line that calls updatePage():

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;
levelWidth = level.width;
}
addChild(levelHolder);
levelHolder.y = 120;
levelHolder.x = -(levelWidth + distance) * 2 + levelWidth / 2;
goX = levelHolder.x;
selectLevel();
setChildIndex(btn_next, numChildren - 1);
setChildIndex(btn_prev, numChildren - 1);
addEventListener(Event.ENTER_FRAME, onFrame);
btn_next.addEventListener(MouseEvent.CLICK, goNext);
btn_prev.addEventListener(MouseEvent.CLICK, goPrev);
updatePage();
}

The goNext() and goPrev() functions increase or decrease currentLevel value by one, call selectLevel and then reposition levelHolder on the x axis. Call updatePage().

private function goNext(evt:MouseEvent):void {
currentLevel++;
selectLevel();
levelHolder.x = -(levelWidth + distance) * 1 + levelWidth / 2;
updatePage();
}

private function goPrev(evt:MouseEvent):void {
currentLevel--;
selectLevel();
levelHolder.x = -(levelWidth + distance) * 3 + levelWidth / 2;
updatePage();
}

The updatePage() function updates the displayed page value. This is displayed using a dynamic text field in the MovieClip - so open Flash and add a text field with id "tPage" somewhere on the screen on the sixth frame inside level_select MC. Heres the function that updates the value:

private function updatePage():void {
tPage.text = (currentLevel + 1) + "/" + levels.length;
}

Full level_select.as 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;
private var levelWidth: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;
levelWidth = level.width;
}
addChild(levelHolder);
levelHolder.y = 120;
levelHolder.x = -(levelWidth + distance) * 2 + levelWidth / 2;
goX = levelHolder.x;
selectLevel();
setChildIndex(btn_next, numChildren - 1);
setChildIndex(btn_prev, numChildren - 1);
addEventListener(Event.ENTER_FRAME, onFrame);
btn_next.addEventListener(MouseEvent.CLICK, goNext);
btn_prev.addEventListener(MouseEvent.CLICK, goPrev);
updatePage();
}

private function updatePage():void {
tPage.text = (currentLevel + 1) + "/" + levels.length;
}

private function goNext(evt:MouseEvent):void {
currentLevel++;
selectLevel();
levelHolder.x = -(levelWidth + distance) * 1 + levelWidth / 2;
updatePage();
}

private function goPrev(evt:MouseEvent):void {
currentLevel--;
selectLevel();
levelHolder.x = -(levelWidth + distance) * 3 + levelWidth / 2;
updatePage();
}

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

}

Thanks for reading!

The results:

Read more »

Creating a Pentomino game using AS3 Part 10

In this tutorial well make the placed shapes moveable.

Well start by modifying the startDragShape() function. Add 3 new function parameters - rot, scalex, scaley, set their default values to 0. Apply the rot value to cursors rotation property, scalex to scaleX and scaley to scaleY. Then check if scalex is 0 (the value was not changed) and set scaleX and scaleY to gridCellWidth / 100:

private function startDragShape(shapeType:int, rot:int = 0, scalex:Number = 0, scaley:Number = 0):void {
cursor.rotation = rot;
cursor.scaleX = scalex;
cursor.scaleY = scaley;
if (scalex == 0) {
cursor.scaleX = gridCellWidth / 100;
cursor.scaleY = gridCellWidth / 100;
}
cursor.alpha = 0.75;
cursor.visible = true;
cursor.gotoAndStop(shapeType);
}

Now go to mouseUp() function and add a line that adds a MOUSE_DOWN event listener to newShape, also push the newShape value to placedShapes array:

private function mouseUp(evt:MouseEvent):void {
stopDragShape();
if (selectedShape > 0) {
if (!canPutHere) {
availableShapes[selectedShape-1]++;
shapeButtons[selectedShape-1].count.text = availableShapes[selectedShape-1];
selectedShape = 0;
}
if (canPutHere) {
var mousePos:Point = new Point(Math.floor((mouseX - gridStartX) / gridCellWidth), Math.floor((mouseY - gridStartY) / gridCellWidth));
var newShape:MovieClip = new game_shape();
addChild(newShape);
newShape.x = gridStartX + (mousePos.x+1) * gridCellWidth - gridCellWidth / 2;
newShape.y = gridStartY + (mousePos.y + 1) * gridCellWidth - gridCellWidth / 2;
newShape.rotation = cursor.rotation;
newShape.scaleX = cursor.scaleX;
newShape.scaleY = cursor.scaleY;
newShape.gotoAndStop(selectedShape);
newShape.addEventListener(MouseEvent.MOUSE_DOWN, placedShapeDown);
placedShapes.push(newShape);
for (var i:int = 0; i < currentValues.length; i++) {
var cellX:int = currentValues[i][0] + mousePos.x;
var cellY:int = currentValues[i][1] + mousePos.y;
mapGrid[cellY][cellX] = 2;
}
cursor.parent.setChildIndex(cursor, cursor.parent.numChildren - 1);
selectedShape = 0;
}
}
}

Create the placeShapeDown() function. In the first line, we set the selectedShape value to the current frame of the placed shape:

selectedShape = evt.currentTarget.currentFrame;

After that we call startDragShape() function and pass values to all of the parameters as shown below:

// start dragging
startDragShape(evt.currentTarget.currentFrame, evt.currentTarget.rotation, evt.currentTarget.scaleX, evt.currentTarget.scaleY);

Then we need to call updateCurrentValues() to fill the currentValues array with relative cell coordinates of an already rotated and flipped shape. Calculate the position of the said shape on the grid, and using these values, loop through the 5 cells in mapGrid and set their values to 1.

// update grid values
updateCurrentValues();
var shapePos:Point = new Point(Math.floor((evt.currentTarget.x - gridStartX) / gridCellWidth), Math.floor((evt.currentTarget.y - gridStartY) / gridCellWidth));
for (var i:int = 0; i < 5; i++) {
mapGrid[shapePos.y + currentValues[i][1]][shapePos.x + currentValues[i][0]] = 1;
}

Next we simply remove the listener and the shape:

// remove shape
evt.currentTarget.removeEventListener(MouseEvent.MOUSE_DOWN, placedShapeDown);
evt.currentTarget.parent.removeChild(evt.currentTarget);

And finally we call checkPut():

checkPut();

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.utils.ByteArray;

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

public class pentomino_game extends MovieClip
{
private var mapGrid:Array = [];
private var availableShapes:Array = [];
private var shapeButtons:Array = [];
private var shapeValues:Array = [];
private var placedShapes: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 cursor:MovieClip;
private var rolledShape:MovieClip;
private var selectedShape:int = 0;
private var canPutHere:Boolean = false;
private var currentValues:Array = [];

public function pentomino_game()
{
// default map
mapGrid = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];

availableShapes = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];

shapeValues = [
[[0, 0], [0, 1], [0, 2], [0, -1], [0, -2]],
[[0, 0], [0, -1], [0, 1], [1, 0], [1, -1]],
[[0, 0], [0, 1], [0, 2], [0, -1], [ -1, -1]],
[[0, 0], [0, 1], [0, -1], [ -1, 0], [1, -1]],
[[0, 0], [ -1, 0], [ -2, 0], [0, -1], [1, -1]],
[[0, 0], [0, 1], [0, -1], [1, -1], [ -1, -1]],
[[0, 0], [1, 0], [ -1, 0], [1, -1], [ -1, -1]],
[[0, 0], [ -1, 0], [ -2, 0], [0, -1], [0, -2]],
[[0, 0], [0, 1], [ -1, 1], [1, 0], [1, -1]],
[[0, 0], [0, 1], [0, -1], [1, 0], [ -1, 0]],
[[0, 0], [ -1, 0], [ -2, 0], [1, 0], [0, -1]],
[[0, 0], [0, 1], [1, 1], [0, -1], [-1, -1]]
];

// grid settings
calculateGrid();
addChild(gridShape);
gridShape.x = gridStartX;
gridShape.y = gridStartY;

// draw tiles
drawGrid();

// can put shape
addChild(canPutShape);
canPutShape.x = gridStartX;
canPutShape.y = gridStartY;

// add shape buttons
for (var i:int = 0; i < 4; i++) {
for (var u:int = 0; u < 3; u++) {
var shapeButton:MovieClip = new select_shape();
shapeButton.x = 528 + u * 62;
shapeButton.y = 55 + i * 62;
addChild(shapeButton);
shapeButton.bg.alpha = 0.3;
shapeButton.count.text = String(availableShapes[3 * i + u]);
shapeButton.shape.gotoAndStop(3 * i + u + 1);
shapeButtons.push(shapeButton);
shapeButton.addEventListener(MouseEvent.ROLL_OVER, buttonOver);
shapeButton.addEventListener(MouseEvent.ROLL_OUT, buttonOut);
shapeButton.addEventListener(MouseEvent.MOUSE_DOWN, buttonDown);
}
}

// cursor
cursor = new game_shape();
addChild(cursor);
cursor.mouseEnabled = false;
cursor.mouseChildren = false;
cursor.visible = false;
addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);
addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheel);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
addEventListener(Event.ENTER_FRAME, everyFrame);
addEventListener(MouseEvent.MOUSE_UP, mouseUp);
}

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 mouseMove(evt:MouseEvent):void {
cursor.x = mouseX;
cursor.y = mouseY;
checkPut();
}

private function checkPut():void {
if (selectedShape > 0) {
var mousePos:Point = new Point(Math.floor((mouseX - gridStartX) / gridCellWidth), Math.floor((mouseY - gridStartY) / gridCellWidth));
canPutHere = true;
updateCurrentValues();
for (var i:int = 0; i < currentValues.length; i++) {
var cellX:int = currentValues[i][0] + mousePos.x;
var cellY:int = currentValues[i][1] + mousePos.y;
if (cellX < 0 || cellY < 0 || cellX >= mapGrid[0].length || cellY >= mapGrid.length || mapGrid[cellY][cellX]!=1) {
canPutHere = false;
}
}
if (canPutHere) {
cursor.alpha = 0.8;
}
if (!canPutHere) {
canPutShape.graphics.clear();
cursor.alpha = 0.4;
}
}
}

private function drawCanPut():void {
canPutShape.graphics.clear();
var mousePos:Point = new Point(Math.floor((mouseX - gridStartX) / gridCellWidth), Math.floor((mouseY - gridStartY) / gridCellWidth));
for (var i:int = 0; i < currentValues.length; i++) {
var cellX:int = (currentValues[i][0] + mousePos.x) * gridCellWidth;
var cellY:int = (currentValues[i][1] + mousePos.y) * gridCellWidth;
canPutShape.graphics.beginFill(0x00ff00, 0.2);
canPutShape.graphics.drawRect(cellX, cellY, gridCellWidth, gridCellWidth);
}
}

private function mouseWheel(evt:MouseEvent):void {
if (evt.delta > 0) cursor.rotation += 90;
if (evt.delta < 0) cursor.rotation -= 90;
if (selectedShape > 0) checkPut();
}

private function keyDown(evt:KeyboardEvent):void {
if (evt.keyCode == 68) cursor.rotation += 90;
if (evt.keyCode == 65) cursor.rotation -= 90;
if (evt.keyCode == 32) cursor.scaleX *= -1;
if (selectedShape > 0) checkPut();
}

private function updateCurrentValues():void {
currentValues = clone(shapeValues[selectedShape-1]);
for (var i:int = 0; i < 5; i++) {
if (cursor.rotation == 90) {
currentValues[i].reverse();
currentValues[i][0] *= -1;
}
if (cursor.rotation == 180 || cursor.rotation == -180) {
currentValues[i][0] *= -1;
currentValues[i][1] *= -1;
}
if (cursor.rotation == -90) {
currentValues[i].reverse();
currentValues[i][1] *= -1;
}
if (cursor.scaleX < 0 && (cursor.rotation != -90 || cursor.rotation != 90)) {
currentValues[i][0] *= -1;
}
if (cursor.scaleX < 0 && (cursor.rotation == -90 || cursor.rotation == 90)) {
currentValues[i][0] *= -1;
currentValues[i][1] *= -1;
}
}
drawCanPut();
}

private function clone(source:Object):*{
var myBA:ByteArray = new ByteArray();
myBA.writeObject(source);
myBA.position = 0;
return(myBA.readObject());
}

private function startDragShape(shapeType:int, rot:int = 0, scalex:Number = 0, scaley:Number = 0):void {
cursor.rotation = rot;
cursor.scaleX = scalex;
cursor.scaleY = scaley;
if (scalex == 0) {
cursor.scaleX = gridCellWidth / 100;
cursor.scaleY = gridCellWidth / 100;
}
cursor.alpha = 0.75;
cursor.visible = true;
cursor.gotoAndStop(shapeType);
}

private function stopDragShape():void {
canPutShape.graphics.clear();
cursor.alpha = 0;
cursor.visible = false;
}

private function everyFrame(evt:Event):void {
if (rolledShape != null) {
rolledShape.rotation += 4;
}
}

private function buttonOver(evt:MouseEvent):void {
if(selectedShape==0){
evt.currentTarget.bg.alpha = 1;
rolledShape = evt.target.shape;
}
}

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

private function buttonDown(evt:MouseEvent):void {
selectedShape = evt.currentTarget.shape.currentFrame;
if(availableShapes[selectedShape-1]>0){
startDragShape(selectedShape);
availableShapes[selectedShape-1]--;
evt.currentTarget.count.text = availableShapes[selectedShape-1];
evt.currentTarget.bg.alpha = 0.3;
rolledShape = null;
}else {
selectedShape = 0;
}
}

private function mouseUp(evt:MouseEvent):void {
stopDragShape();
if (selectedShape > 0) {
if (!canPutHere) {
availableShapes[selectedShape-1]++;
shapeButtons[selectedShape-1].count.text = availableShapes[selectedShape-1];
selectedShape = 0;
}
if (canPutHere) {
var mousePos:Point = new Point(Math.floor((mouseX - gridStartX) / gridCellWidth), Math.floor((mouseY - gridStartY) / gridCellWidth));
var newShape:MovieClip = new game_shape();
addChild(newShape);
newShape.x = gridStartX + (mousePos.x+1) * gridCellWidth - gridCellWidth / 2;
newShape.y = gridStartY + (mousePos.y + 1) * gridCellWidth - gridCellWidth / 2;
newShape.rotation = cursor.rotation;
newShape.scaleX = cursor.scaleX;
newShape.scaleY = cursor.scaleY;
newShape.gotoAndStop(selectedShape);
newShape.addEventListener(MouseEvent.MOUSE_DOWN, placedShapeDown);
placedShapes.push(newShape);
for (var i:int = 0; i < currentValues.length; i++) {
var cellX:int = currentValues[i][0] + mousePos.x;
var cellY:int = currentValues[i][1] + mousePos.y;
mapGrid[cellY][cellX] = 2;
}
cursor.parent.setChildIndex(cursor, cursor.parent.numChildren - 1);
selectedShape = 0;
}
}
}

private function placedShapeDown(evt:MouseEvent):void {
selectedShape = evt.currentTarget.currentFrame;

// start dragging
startDragShape(evt.currentTarget.currentFrame, evt.currentTarget.rotation, evt.currentTarget.scaleX, evt.currentTarget.scaleY);

// update grid values
updateCurrentValues();
var shapePos:Point = new Point(Math.floor((evt.currentTarget.x - gridStartX) / gridCellWidth), Math.floor((evt.currentTarget.y - gridStartY) / gridCellWidth));
for (var i:int = 0; i < 5; i++) {
mapGrid[shapePos.y + currentValues[i][1]][shapePos.x + currentValues[i][0]] = 1;
}

// remove shape
evt.currentTarget.removeEventListener(MouseEvent.MOUSE_DOWN, placedShapeDown);
evt.currentTarget.parent.removeChild(evt.currentTarget);

checkPut();
}
}

}

Thanks for reading!

Results:

Read more »

Friday, January 30, 2015

Creating a Pentomino game using AS3 Part 39

In this tutorial well improve our solution finding algorithm by fixing some bugs and adding a more advanced pre-check.

Go to tryRemainingShapes() function and before the if..statement that calls hasSpace() function add another if..statement that checks if the cell with provided coordinates has value of 1. This is basically what we have in hasSpace() right now, but well be completely rewriting that function and checking for different things in it.

We need to label the while... loop by writing "outerLoop: " before the loop. This will allow us to break this loop from inside another loop. We break outerLoop if hasSpace() returns false.

Another change here is that we declare a new passedShapes Array that we can use to later remove the first element from and pass it to the next tryRemaininShapes() cycle. Simply passing a clone of innerTempShapes with .splice() does not work - it returns the element instead of the array.

private function tryRemainingShapes(tShapes:Array, tGrid:Array):void {
var innerTempShapes:Array = clone(tShapes);
var innerTempGrid:Array = clone(tGrid);
var e:int;
var t:int;
var passedShapes:Array;
// ...take each remaining shape...
outerLoop: while (innerTempShapes.length > 0) {
var currentShapeValues:Array = clone(shapeValues[innerTempShapes[0]]);
// ...try putting it in each cell...
for (e = 0; e < allCells.length; e++) {
// ...rotate the shape...
if (innerTempGrid[allCells[e].y][allCells[e].x] == 1) {
if (hasSpace(innerTempGrid, allCells[e].x, allCells[e].y)) {
for (t = 0; t < 8; t++) {
// ...check if it can be put...
if (canPut(currentShapeValues, allCells[e].x, allCells[e].y, innerTempGrid, t)) {
// ...on success, put this shape and try all remaining shapes...
putHere(currentShapeValues, allCells[e].x, allCells[e].y, innerTempGrid, t, innerTempShapes[0] + 2);
passedShapes = innerTempShapes.concat();
passedShapes.splice(0, 1);
tryRemainingShapes(passedShapes, innerTempGrid);
// ...if no empty cells remaining, then the puzzle is solved.
if (checkWin(innerTempGrid)) doWin(clone(innerTempGrid));
// ...step backwards...
removeHere(currentShapeValues, allCells[e].x, allCells[e].y, innerTempGrid, t);
}
}
}else {
break outerLoop;
}
}
}
// ...get rid of the shape and continue...
innerTempShapes.shift();
}
}

Go to onFrame() and add the hasSpace() check there as well:

private function onFrame(evt:Event):void {
// set temporary values
tempGrid = clone(givenGrid);
tempShapes = clone(givenShapes);
// perform a new cycle
var i:int;
var u:int;
var currentShapeValues:Array;
// Take the first shape in this cycle...
currentShapeValues = clone(shapeValues[tempShapes[cycleNum]]);
// ...remove current shape from shapes array (so that it isnt used twice)...
var shapeType:int = tempShapes[cycleNum] + 2;
tempShapes.splice(cycleNum, 1);
// ...try putting it in each available cell...
for (i = 0; i < allCells.length; i++) {
// ...try putting the shape in all 8 possible positions...
if(hasSpace(tempGrid, allCells[i].x, allCells[i].y)){
for (u = 0; u < 8; u++) {
// ...if successful, continue placing other cells...
if (canPut(currentShapeValues, allCells[i].x, allCells[i].y, tempGrid, u)) {
// ...first put the first shape on the grid...
putHere(currentShapeValues, allCells[i].x, allCells[i].y, tempGrid, u, shapeType);
// ...try all remaining shapes...
tryRemainingShapes(tempShapes, tempGrid);
removeHere(currentShapeValues, allCells[i].x, allCells[i].y, tempGrid, u);
}
}
}
}
// display info
tInfo.text = Solving level " + levelName + "
Attempts: + attempts + "
Solutions: " + solutions.length;
// next cycle
nextCycle();
}

The hasSpace() function checks if the area on the grid that the cell is located in has more than 5 cells in it. Well call these cells neighbours. However, the cell itself counts as a neighbour too.

Declare a variable called "neighbours" and set its value to 1. Clone the grid data and set the current cells value in the grid to "n". All cells that are connected to this cell will have value "n".

Then add a line that adds returned value of getNeighbours() function to neighbours. Check if the number is greater than or equals 5, return true. Otherwise, return false.

private function hasSpace(mapGrid:Array, cX:int, cY:int):Boolean {
var neighbours:int = 1; // count self as neighbour
var nGrid:Array = clone(mapGrid);
nGrid[cY][cX] = "n"; // set to "n" if together
neighbours += getNeighbours(cX, cY, nGrid);
if (neighbours >= 5) return true;
return false;
}

The getNeighbours() function is a recursive function that loops through neighbours and checks their neighbours as well, and goes on before it meets a wall.

private function getNeighbours(cX:int, cY:int, grid:Array):int {
var n:int = 0;
if (cY != 0 && grid[cY - 1][cX] == 1) {
n++;
grid[cY - 1][cX] = "n";
n += getNeighbours(cX, cY - 1, grid);
}
if (cY != grid.length - 1 && grid[cY + 1][cX] == 1) {
n++;
grid[cY + 1][cX] = "n";
n += getNeighbours(cX, cY + 1, grid);
}
if (cX != 0 && grid[cY][cX - 1] == 1) {
n++;
grid[cY][cX - 1] = "n";
n += getNeighbours(cX - 1, cY, grid);
}
if (cX != grid[0].length - 1 && grid[cY][cX + 1] == 1) {
n++;
grid[cY][cX + 1] = "n";
n += getNeighbours(cX + 1, cY, grid);
}
return n;
}

Our solving algorithm is now more efficient (uses considerably less attempts => computes faster) and more accurate in finding solutions. Still, it is not fast enough to solve medium or bigger sized puzzles.

Full code so far:

package  
{
import fl.controls.TextInput;
import flash.display.MovieClip;
import flash.display.Shape;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.net.SharedObject;
import flash.text.TextField;
import flash.utils.ByteArray;

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

public class level_solver extends MovieClip
{

private var allCells:Array;
private var tempGrid:Array;
private var tempShapes:Array;
private var givenGrid:Array;
private var givenShapes:Array;
private var cycleNum:int;
private var attempts:int;
private var solutions:Array;
private var levelName:String;
private var shapeValues:Array;
private var currentSol:int = 0;

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;
var i:int
var u:int;
givenShapes = [];
for (i = 1; i <= 12; i++) {
this["tShape" + i].text = shapes[i - 1];
for (u = 0; u < shapes[i - 1]; u++) {
givenShapes.push(i - 1);
}
}
allCells = [];
for (i = 0; i < levelItem.grid.length; i++) {
for (u = 0; u < levelItem.grid.length; u++) {
if (levelItem.grid[i][u] == 1) {
allCells.push(new Point(u, i));
}
}
}
givenGrid = levelItem.grid;
tempGrid = [];
tempShapes = [];
solutions = [];
levelName = levelItem.name;
attempts = 0;
cycleNum = 0;
shapeValues = [
[[0, 0], [0, 1], [0, 2], [0, -1], [0, -2]],
[[0, 0], [0, -1], [0, 1], [1, 0], [1, -1]],
[[0, 0], [0, 1], [0, 2], [0, -1], [ -1, -1]],
[[0, 0], [0, 1], [0, -1], [ -1, 0], [1, -1]],
[[0, 0], [ -1, 0], [ -2, 0], [0, -1], [1, -1]],
[[0, 0], [0, 1], [0, -1], [1, -1], [ -1, -1]],
[[0, 0], [1, 0], [ -1, 0], [1, -1], [ -1, -1]],
[[0, 0], [ -1, 0], [ -2, 0], [0, -1], [0, -2]],
[[0, 0], [0, 1], [ -1, 1], [1, 0], [1, -1]],
[[0, 0], [0, 1], [0, -1], [1, 0], [ -1, 0]],
[[0, 0], [ -1, 0], [ -2, 0], [1, 0], [0, -1]],
[[0, 0], [0, 1], [1, 1], [0, -1], [-1, -1]]
];
addEventListener(Event.ENTER_FRAME, onFrame);
btn_prev.addEventListener(MouseEvent.CLICK, goPrev);
btn_next.addEventListener(MouseEvent.CLICK, goNext);
}

private function onFrame(evt:Event):void {
// set temporary values
tempGrid = clone(givenGrid);
tempShapes = clone(givenShapes);
// perform a new cycle
var i:int;
var u:int;
var currentShapeValues:Array;
// Take the first shape in this cycle...
currentShapeValues = clone(shapeValues[tempShapes[cycleNum]]);
// ...remove current shape from shapes array (so that it isnt used twice)...
var shapeType:int = tempShapes[cycleNum] + 2;
tempShapes.splice(cycleNum, 1);
// ...try putting it in each available cell...
for (i = 0; i < allCells.length; i++) {
// ...try putting the shape in all 8 possible positions...
if(hasSpace(tempGrid, allCells[i].x, allCells[i].y)){
for (u = 0; u < 8; u++) {
// ...if successful, continue placing other cells...
if (canPut(currentShapeValues, allCells[i].x, allCells[i].y, tempGrid, u)) {
// ...first put the first shape on the grid...
putHere(currentShapeValues, allCells[i].x, allCells[i].y, tempGrid, u, shapeType);
// ...try all remaining shapes...
tryRemainingShapes(tempShapes, tempGrid);
removeHere(currentShapeValues, allCells[i].x, allCells[i].y, tempGrid, u);
}
}
}
}
// display info
tInfo.text = Solving level " + levelName + "
Attempts: + attempts + "
Solutions: " + solutions.length;
// next cycle
nextCycle();
}

private function tryRemainingShapes(tShapes:Array, tGrid:Array):void {
var innerTempShapes:Array = clone(tShapes);
var innerTempGrid:Array = clone(tGrid);
var e:int;
var t:int;
var passedShapes:Array;
// ...take each remaining shape...
outerLoop: while (innerTempShapes.length > 0) {
var currentShapeValues:Array = clone(shapeValues[innerTempShapes[0]]);
// ...try putting it in each cell...
for (e = 0; e < allCells.length; e++) {
// ...rotate the shape...
if (innerTempGrid[allCells[e].y][allCells[e].x] == 1) {
if (hasSpace(innerTempGrid, allCells[e].x, allCells[e].y)) {
for (t = 0; t < 8; t++) {
// ...check if it can be put...
if (canPut(currentShapeValues, allCells[e].x, allCells[e].y, innerTempGrid, t)) {
// ...on success, put this shape and try all remaining shapes...
putHere(currentShapeValues, allCells[e].x, allCells[e].y, innerTempGrid, t, innerTempShapes[0] + 2);
passedShapes = innerTempShapes.concat();
passedShapes.splice(0, 1);
tryRemainingShapes(passedShapes, innerTempGrid);
// ...if no empty cells remaining, then the puzzle is solved.
if (checkWin(innerTempGrid)) doWin(clone(innerTempGrid));
// ...step backwards...
removeHere(currentShapeValues, allCells[e].x, allCells[e].y, innerTempGrid, t);
}
}
}else {
break outerLoop;
}
}
}
// ...get rid of the shape and continue...
innerTempShapes.shift();
}
}

private function hasSpace(mapGrid:Array, cX:int, cY:int):Boolean {
var neighbours:int = 1; // count self as neighbour
var nGrid:Array = clone(mapGrid);
nGrid[cY][cX] = "n"; // set to "n" if together
neighbours += getNeighbours(cX, cY, nGrid);
if (neighbours >= 5) return true;
return false;
}

private function getNeighbours(cX:int, cY:int, grid:Array):int {
var n:int = 0;
if (cY != 0 && grid[cY - 1][cX] == 1) {
n++;
grid[cY - 1][cX] = "n";
n += getNeighbours(cX, cY - 1, grid);
}
if (cY != grid.length - 1 && grid[cY + 1][cX] == 1) {
n++;
grid[cY + 1][cX] = "n";
n += getNeighbours(cX, cY + 1, grid);
}
if (cX != 0 && grid[cY][cX - 1] == 1) {
n++;
grid[cY][cX - 1] = "n";
n += getNeighbours(cX - 1, cY, grid);
}
if (cX != grid[0].length - 1 && grid[cY][cX + 1] == 1) {
n++;
grid[cY][cX + 1] = "n";
n += getNeighbours(cX + 1, cY, grid);
}
return n;
}

private function checkWin(mapGrid:Array):Boolean {
var i:int;
var u:int;
var didWin:Boolean = true;
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) {didWin = false;}
}
}

return didWin;
}

private function doWin(grid:Array):void {
var exists:Boolean = false;
for (var i:int = 0; i < solutions.length; i++) {
if (compare(solutions[i], grid)) {
exists = true;
break;
}
}
if (!exists) solutions.push(grid);
}

private function compare(arr1:Array, arr2:Array):Boolean {
for (var i:int = 0; i < arr1.length; i++) {
for (var u:int = 0; u < arr1[i].length; u++){
if (arr1[i][u] != arr2[i][u]) {
return false;
break;
}
}
}
return true;
}

private function goPrev(evt:MouseEvent):void {
currentSol--;
updateCurrentInfo();
}

private function goNext(evt:MouseEvent):void {
currentSol++;
updateCurrentInfo();
}

private function updateCurrentInfo():void {
var sol:int = (solutions.length > 0)?(currentSol + 1):(0);
tSol.text = sol + "/" + solutions.length;
if (sol <= 1) {
btn_prev.alpha = 0.5;
btn_prev.mouseEnabled = false;
}else {
btn_prev.alpha = 1;
btn_prev.mouseEnabled = true;
}
if (sol == solutions.length) {
btn_next.alpha = 0.5;
btn_next.mouseEnabled = false;
}else {
btn_next.alpha = 1;
btn_next.mouseEnabled = true;
}
if (solutions.length > 0) drawPreview(levelPreview, solutions[currentSol]);
}

private function nextCycle():void {
cycleNum++;
updateCurrentInfo();
if (cycleNum == givenShapes.length) {
removeEventListener(Event.ENTER_FRAME, onFrame);
// display info
tInfo.text = Finished solving " + levelName + "
Attempts: + attempts + "
Solutions: " + solutions.length;
}
}

private function canPut(cValues:Array, cellX:int, cellY:int, mapGrid:Array, rotation:int):Boolean {
attempts++;
var canPutHere:Boolean = true;
var currentValues:Array = clone(cValues);
updateCurrentValues(currentValues, rotation);
for (var i:int = 0; i < currentValues.length; i++) {
var cX:int = currentValues[i][0] + cellX;
var cY:int = currentValues[i][1] + cellY;
if (cX < 0 || cY < 0 || cX >= mapGrid[0].length || cY >= mapGrid.length || mapGrid[cY][cX]!=1) {
canPutHere = false;
}
}
return canPutHere;
}

private function putHere(currentValues:Array, cellX:int, cellY:int, mapGrid:Array, rotation:int, shape:int):void {
currentValues = clone(currentValues);
updateCurrentValues(currentValues, rotation);
for (var i:int = 0; i < currentValues.length; i++) {
var cX:int = currentValues[i][0] + cellX;
var cY:int = currentValues[i][1] + cellY;
mapGrid[cY][cX] = shape;
}
}

private function removeHere(currentValues:Array, cellX:int, cellY:int, mapGrid:Array, rotation:int):void {
currentValues = clone(currentValues);
updateCurrentValues(currentValues, rotation);
for (var i:int = 0; i < currentValues.length; i++) {
var cX:int = currentValues[i][0] + cellX;
var cY:int = currentValues[i][1] + cellY;
mapGrid[cY][cX] = 1;
}
}

private function updateCurrentValues(currentValues:Array, rot:int):void{
for (var i:int = 0; i < 5; i++) {
// do nothing if rot == 0
if (rot == 1) {
currentValues[i].reverse();
currentValues[i][0] *= -1;
}
if (rot == 2) {
currentValues[i][0] *= -1;
currentValues[i][1] *= -1;
}
if (rot == 3) {
currentValues[i].reverse();
currentValues[i][1] *= -1;
}
if (rot == 4) {
currentValues[i][0] *= -1;
}
if (rot == 5) {
currentValues[i].reverse();
}
if (rot == 6) {
currentValues[i][1] *= -1;
}
if (rot == 7) {
currentValues[i].reverse();
currentValues[i][1] *= -1;
currentValues[i][0] *= -1;
}
}
}

private function clone(source:Object):*{
var myBA:ByteArray = new ByteArray();
myBA.writeObject(source);
myBA.position = 0;
return(myBA.readObject());
}

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);
if (mapGrid[i][u] == 2) drawCell(u, i, 0xff66cc, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 3) drawCell(u, i, 0x00FF66, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 4) drawCell(u, i, 0x99ff00, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 5) drawCell(u, i, 0x00ccff, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 6) drawCell(u, i, 0xffcc66, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 7) drawCell(u, i, 0x66cc33, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 8) drawCell(u, i, 0x9900ff, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 9) drawCell(u, i, 0xff5858, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 10) drawCell(u, i, 0xff6600, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 11) drawCell(u, i, 0x00ffff, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 12) drawCell(u, i, 0xff6b20, 1, 0x000000, gridCellWidth, levelPreview.shape);
if (mapGrid[i][u] == 13) drawCell(u, i, 0xffff66, 1, 0x000000, 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);
}
}

}

Thanks for reading!
Read more »

Creating a Pentomino game using AS3 Part 23

In this tutorial well start working on the Saved Levels screen.

The Saved Levels screen is basically a browser for levels that are stored in the users local Shared Object.

Go to the main menu screen and add a new button with an id "btn_saved". Go to main_menu.as script file and add a click event handler for this button, which directs the user to the fourth frame:

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

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

public class main_menu extends MovieClip
{

public function main_menu()
{
(root as MovieClip).stop();
btn_play.addEventListener(MouseEvent.CLICK, doPlay);
btn_editor.addEventListener(MouseEvent.CLICK, doEditor);
btn_saved.addEventListener(MouseEvent.CLICK, doSaved);
}

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

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

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

}

Go to the 4th frame on the main timeline in Flash and create a new MovieClip there. Give the new MC a class path saved_levels.

The saved levels screen consists of 3 parts - header, body and footer:



The header contains info about current page of the level browser and buttons to navigate through the pages.

The body of the screen contains up to 3 level items. Each level item is a saved_item object instance which I will talk about in details shortly.

The footer contains info about how many levels in total there are, how much space on local drive they all take and a button to return to main menu.

You need to add at least 5 objects right now - btn_previous and btn_next buttons in the header, tPage dynamic text field in the header, tInfo dynamic text field in the footer and a btn_back button in the footer.

As I said before, each item level is an instance of a saved_item object. Create a new MovieClip with class path saved_item and add the following objects: a rectangular MovieClip with id levelPreview; 3 dynamic text fields: tTitle, tSubtitle, tSubtitle2; 12 dynamic text fields with ids tShape1, tShape2, ... , tShape12; buttons btn_play and btn_edit.

The 3 dynamic text fields will be used to display level name, its statistics and number of solutions. The levelPreview object will be used to display the level grid. The 12 tShape text fields will be used to display how many of each shape is available. The two buttons do exactly what the name suggests - play the level and edit the level.

Add 3 of saved_item object instances to the screen.

Now create a new class saved_levels.as.

Right now we will just add functionality to the Back button and display the SharedObject data. Were storing an array called "levels" in a SharedObject and read its length to see how many levels there are. If the array is null, then there are 0 levels. We can use the size property to see how many bytes the object currently takes.

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

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

public class saved_levels extends MovieClip
{
private var savedLevels:SharedObject;

public function saved_levels()
{
savedLevels = SharedObject.getLocal("myLevels");
btn_back.addEventListener(MouseEvent.CLICK, doBack);
tInfo.text = ((savedLevels.data.levels == null)?("0"):(savedLevels.data.levels.length)) + " levels (" + savedLevels.size + "B)";
}

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

}

Thats all for today.

Thanks for reading! The results so far:

Read more »

Thursday, January 29, 2015

Creating a Pentomino game using AS3 Part 1

Today well start making a game based on pentomino.

Basically, I have a school project to do on Pentomino, and Im going to create a game based on the principle. I also want to share the code with other programmers, so Im writing a step-by-step tutorial series and then releasing the game as an open-source project.

The game is made using Flash and FlashDevelop, although you dont need to follow my example strictly and as long as you understand the code, you can use any IDE you like.

I am going to use Flash for animations and graphics mostly, and have all my code written in FlashDevelop (because I like it a lot better than the built in actions editor in Flash).

If you want to do this the same way as me, create a new AS3 project and add a new empty MovieClip to stage. Make sure its coordinates are 0,0. Set its class path to "pentomino_game".

Now open FlashDevelop, Project > New Project. Choose Flash IDE project and select the folder your Flash file is in. When the project is created, add a new .as class to the directory and edit it using FlashDevelop. Import two classes for now - MovieClip and Sprite. Make the pentomino_game class extend MovieClip.

Declare five private variables - mapGrid, gridShape, gridStartX, gridStartY and gridCellWidth and set their values as shown below:

package  
{
import flash.display.MovieClip;
import flash.display.Sprite;

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

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

private var gridShape:Sprite = new Sprite();
private var gridStartX:int = 30;
private var gridStartY:int = 30;
private var gridCellWidth:int = 30;

public function pentomino_game() {}

}

}

Now, in the constructor, were going to add a temporary value for mapGrid. Why temporary? Because in the future, all the maps will be stored somewhere and not hardcoded in the constructor function. Right now, make it a 10x6 grid with values "1" and "0".

Basically, "1" means a tile and "0" means a hole. Using this method youll be able to create more complex grids than just boring rectangles.

// default map
mapGrid = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];

Note that the map I created is not a valid one. It will not work for a pentomino puzzle. The reason for that is the number of tiles cant be divided by 5 and remain a round number. But just for the sake of this tutorial and debugging, were going to leave these 4 holes there.

Now we add the gridShape Sprite to stage and set its coordinates:

// grid settings
addChild(gridShape);
gridShape.x = gridStartX;
gridShape.y = gridStartY;

Then we call a function drawGrid() that draws the map.

public function pentomino_game() 
{
// default map
mapGrid = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];

// grid settings
addChild(gridShape);
gridShape.x = gridStartX;
gridShape.y = gridStartY;

// draw tiles
drawGrid();
}

The map is drawn by first clearing gridShapes graphics, then looping through all mapGrid items and drawing a cell where it is needed:

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

You can see that we use the drawing API to draw the tiles.

Now we have a working tile map system. You can create maps and change their appearance. The results may vary, but it should look something like this:



Full code so far:

package  
{
import flash.display.MovieClip;
import flash.display.Sprite;

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

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

private var gridShape:Sprite = new Sprite();
private var gridStartX:int = 30;
private var gridStartY:int = 30;
private var gridCellWidth:int = 30;

public function pentomino_game()
{
// default map
mapGrid = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];

// grid settings
addChild(gridShape);
gridShape.x = gridStartX;
gridShape.y = gridStartY;

// draw tiles
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);
}
}
}

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

}

}

Thanks for reading!
Read more »