Misunderstanding of the OOP in a chess game design in java - swing

I've a problem with code that I'm writing to practice my java skills.
I'm trying to write a chess game. Anyway, in my project I have a class called ChessGUI which contains the GUI and the methods I need to play (multiplayer). I'm willing to change the code later to a MVC pattern so it can be easier to read/edit/understand.
I've an abstract class called Figure and i extended it in 12 classes (6 figures per color):
However in my ChessGui class I have a method called CheckAvailableSquares() which I call whenever a piece on the board has been selected (by a mouse click) to show me on the board the available squares which I can move my selected piece to.
private void CheckAvailableSquares(Figure figure){
for (int row = 0; row < 8; row++){
for (int col = 0; col < 8; col++){
if (row == figure.getRowPos() && col == figure.getColPos()) //bypass the actual square where my piece stands
continue;
if (figure.isValidMove(board,figure.getRowPos(), figure.getColPos(),row,col)){
board[row][col].setBackground(Color.GREEN);
}
}
}
}
In every figure sub-class there is a method called isValidMove() which becomes the current position of the piece and the wanted position and return a boolean value if the move is valid.
As you know the pawn can move 2 squares per time on it's first move, that's why I added a boolean attribute to the classes WhitePawn&BlackPawn called firstMovePlayed. It has the false value and whenever the isMoveValid() method is called and the wanted move is valid it should be changed to true. like that:
public class WhitePawn extends Figure {
private boolean firstMovePlayed = false;
public WhitePawn(){
super.setColor(Color.WHITE);
}
#Override
public boolean isValidMove(Square[][] board, int currentRow, int currentCol, int wantedRow, int wantedCol){
int rowDelta = currentRow - wantedRow; //no Math.abs because a pawn can't move backwards.
int colDelta = Math.abs(wantedCol - currentCol);
//if we have an own piece on the wanted square
if (!board[wantedRow][wantedCol].isEmpty() && board[wantedRow][wantedCol].getFigure().getColor().equals(Color.WHITE)) {
return false;
}
//on the first move a pawn can move 2 square at a time, and when moving 2 square he can't capture. A pawn can't move two square if there is another piece in the way. TODO: en passant ??????
if (rowDelta == 2 && colDelta == 0 && board[wantedRow][wantedCol].isEmpty() && board[wantedRow + 1][wantedCol].isEmpty() && !firstMovePlayed) {
firstMovePlayed = true;
return true;
}
else if (rowDelta == 1 && colDelta == 1 && !board[wantedRow][wantedCol].isEmpty()) { //capture
if (board[wantedRow][wantedCol].getFigure().getColor() != board[currentRow][currentCol].getFigure().getColor())
firstMovePlayed = true;
return true;
}
else if (rowDelta == 1 && colDelta == 0 && board[wantedRow][wantedCol].isEmpty()) { //moving one square forward without a capture
firstMovePlayed = true;
return true;
}
//those were the only possibilities to move a pawn
return false;
}}
The problem is:
whenever I call the method CheckAvailableSquares() to check the availability of the squares the firstMovePlayed boolean will be set to true!!
I'm really sorry for taking too long of your time but I really can't figure it out :/
here is the rest of the relevant code from ChessGui class:
public void actionPerformed(ActionEvent e) {
//looking for the square which fired the ActionListener:
for (int i = 0; i < 8; i++){
for (int j = 0; j < 8; j++){
if(board[i][j] == e.getSource()){
processClick(i, j);
}
}
}
}
private void processClick(int i, int j){
if(bufRow == 99 && bufCol == 99){ //select a figure to move and wait for the next click
if (board[i][j].isEmpty()) { //if there is no figure on the square that has benn clicked
System.out.println("no Figures on this square");
return;
}
else {//save the chosen square (button) in buffers so that we can move it later.
//decide who's turn it is:
if (board[i][j].getFigure().getColor().equals(Color.WHITE) && !whiteToPlay)
System.out.println("It's black's turn to play!");
else if (board[i][j].getFigure().getColor().equals(Color.BLACK) && !blackToPlay)
System.out.println("It's white's turn to play!");
if ((board[i][j].getFigure().getColor().equals(Color.WHITE) && whiteToPlay) || board[i][j].getFigure().getColor().equals(Color.BLACK) && blackToPlay) {
board[i][j].setHighleightedIcon(board[i][j].getFigure().getHilightedIcon()); //change the icon of the chosen square
bufRow = i; //save the chosen figure to move it later
bufCol = j;
System.out.println("Selectd figure on Raw: " + bufRow + " Column: " + bufCol);
switchTurns();
CheckAvailableSquares(board[bufRow][bufCol].getFigure());
}
}
}
else
moveFigure(i, j);
}
private void moveFigure(int wantedRow, int wantedCol) {
if (board[bufRow][bufCol].getFigure().isValidMove(board, bufRow, bufCol, wantedRow, wantedCol)) { // check if the move is valid for the figure
// if (board[wantedRow][wantedCol].isEmpty()) { //if the wanted square is empty (not a capture)
//move the figure to the wanted square after deleting it from the old square
board[wantedRow][wantedCol].setFigure(board[bufRow][bufCol].getFigure());
board[wantedRow][wantedCol].setSquareIcon(board[bufRow][bufCol].getFigure().getIcon());
board[bufRow][bufCol].emptySquare();
//update the location of the piece
board[wantedRow][wantedCol].getFigure().setNewPos(wantedRow,wantedCol);
System.out.println("Moved [" + bufRow + ", " + bufCol + "] To: [" + wantedRow + ", " + wantedCol + "].");
//reset the buffers
bufRow = 99;
bufCol = 99;
resetSquaresColors();
/*}
else if (!board[wantedRow][wantedCol].isEmpty()){ //if it's a capture
if(board[wantedRow][wantedCol].getFigure().getColor() != board[bufRow][bufCol].getFigure().getColor()) { //can't capture own pieces!
board[wantedRow][wantedCol].setFigure(board[bufRow][bufCol].getFigure());
board[wantedRow][wantedCol].setSquareIcon(board[wantedRow][wantedCol].getFigure().getIcon());
board[bufRow][bufCol].emptySquare();
//update the location of the piece
board[wantedRow][wantedCol].getFigure().setRowPos(wantedRow);
board[wantedRow][wantedCol].getFigure().setColPos(wantedCol);
System.out.println("Moved [" + bufRow + ", " + bufCol + "] To: [" + wantedRow + ", " + wantedCol + "].");
//reset the buffers
bufRow = 99;
bufCol = 99;
}
}*/
} else { //if it's not a valid move
board[bufRow][bufCol].setIcon(board[bufRow][bufCol].getFigure().getIcon());
//reset the buffers
bufRow = 99;
bufCol = 99;
resetSquaresColors();
System.out.println("Not a valid move");
switchTurns();
}
}
private void CheckAvailableSquares(Figure figure){
for (int row = 0; row < 8; row++){
for (int col = 0; col < 8; col++){
if (row == figure.getRowPos() && col == figure.getColPos()) //bypass the actual square where my piece stands
continue;
if (figure.isValidMove(board,figure.getRowPos(), figure.getColPos(),row,col)){
Square sqr = board[row][col];
if (sqr.isEmpty()) //if the square is empty
sqr.setAvailableIcon(new ImageIcon("Icons/available_square.png"));
else //if there is a piece on the square
sqr.setAvailableIcon(sqr.getFigure().getAvailableIcon());
}
}
}
}
Thanks alot & forgive any mistakes in my poor English!

Related

Pairing a draggable object to a target object in AS3

I'm currently stuck with my approach below. I'm not entirely sure if using "hitTestObject" method is appropriate in pairing the pieces to their respective place. I was able to at least match the chess piece to their respective location (that's the best I can do and I feel i'm doing it wrong) but I'm now stuck in counting how many pieces are actually in their correct places. e.g. when I move the pawn to a different tile, it will still count as one, I also want to avoid duplicate counting, example, If pawn is already in the correct location, it will just count as 1, and if it was moved, then that count will be removed. Only count the pieces that are in the correct tile.
My goal here is to be able to make all the chess pieces draggable and determine if they're in their respective location. If ALL the chess pieces are in their location, it will trace or call a function.
Thank you!
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.MouseEvent;
/* Declaring an X and Y variable to be used as a reset container */
var xPos: int, yPos: int;
/* Attaching event listeners for each chess piece */
addListeners(
king, queen, bishop_1, bishop_2, knight_1, knight_2, rook_1, rook_2,
pawn_1, pawn_2, pawn_3, pawn_4, pawn_5, pawn_6, pawn_7, pawn_8);
/* Getting the original x and y postion to be used as a reset */
function getPosition(currentTarget: Object): void {
xPos = currentTarget.x;
yPos = currentTarget.y;
}
/* Function to get the suffix value of an object. example, I need to get the value 4 from "pawn_4" */
function getLastCharInString($s: String, $pos: Number): String {
return $s.substr($s.length - $pos, $s.length);
}
/* A simple function that rotates the chess piece */
function lift(object: Object, rot: Number) {
object.rotation = rot;
}
function dragObject(e: MouseEvent): void {
getPosition(e.currentTarget);
lift(e.currentTarget, -10);
getChildByName(e.currentTarget.name + "_hs").alpha = 1;
e.currentTarget.startDrag();
}
/* This variable is supposed to hold the value of each piece that is correctly placed in each tile.
The total score should be 16 as there are 16 pieces. Only correcly placed piece should be added in the total score. */
var counter:int;
function stopDragObject(e: MouseEvent): void {
var curretTarget = e.currentTarget.name;
lift(e.currentTarget, 0);
/* Hide active hotspots */
getChildByName(e.currentTarget.name + "_hs").alpha = 0;
var multiplePieceSufix = Number(getLastCharInString(curretTarget, 1));
if (multiplePieceSufix >= 1) {
/* Boolean variables that checks whether the current piece is active*/
var isPawn: Boolean = false,
isBishop: Boolean = false,
isKnight: Boolean = false,
isRook: Boolean = false,
currentTargeName;
var widthDiff = getChildByName(e.currentTarget.name + "_hs").width - getChildByName(e.currentTarget.name).width / 2;
var heightDiff = getChildByName(e.currentTarget.name + "_hs").height - getChildByName(e.currentTarget.name).height / 2;
if (curretTarget.substr(0, 4) == "pawn") {
isPawn = true;
} else if (curretTarget.substr(0, 6) == "bishop") {
isBishop = true;
} else if (curretTarget.substr(0, 6) == "knight") {
isKnight = true;
} else if (curretTarget.substr(0, 4) == "rook") {
isRook = true;
}
if (isPawn == true) {
/* there are total of 8 pieces of pawn */
for (var w = 1; w < 9; w++) {
currentTargeName = this["pawn_" + w + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
/* For some reason the chess pieces are not aligning with their "_hs" version, I already checked their registry point and it seem to be normal.
so to fix, I had to manually add some hard coded values to adjust their location. */
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}
}
} else if (isBishop == true) {
for (var x = 1; x < 3; x++) {
currentTargeName = this["bishop_" + x + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 9;
e.currentTarget.y = currentTargeName.y + currentTargeName.height - 18;
}
}
} else if (isKnight == true) {
for (var y = 1; y < 3; y++) {
currentTargeName = this["knight_" + y + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}
}
} else if (isRook == true) {
for (var z = 1; z < 3; z++) {
currentTargeName = this["rook_" + z + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + 62;
}
}
}
} else {
if (e.target.hitTestObject(getChildByName(e.currentTarget.name + "_hs"))) {
/* Again, I'm not sure why the pieces are not aligning as intended.
modX and modY is a holder for the adjustment value. I'm not comfortable
seeing this approach myself, but I also run out of ideas how to fix it. */
var modX: Number, modY: Number;
if (e.currentTarget.name == "king") {
modX = 11;
modY = 53;
} else {
modX = 11;
modY = 29;
}
e.currentTarget.x = getChildByName(e.currentTarget.name + "_hs").x - modX;
e.currentTarget.y = getChildByName(e.currentTarget.name + "_hs").y + getChildByName(e.currentTarget.name + "_hs").height - modY;
}
}
/* This is supposed to add to the total score or count of how many pieces are placed correctly.
Thie problem with thi scounter, as it also counts any piece that is places to any "_hs" */
counter++;
trace(counter);
e.currentTarget.stopDrag();
}
function addListeners(...objects): void {
for (var i: int = 0; i < objects.length; i++) {
objects[i].addEventListener(MouseEvent.MOUSE_DOWN, dragObject);
objects[i].addEventListener(MouseEvent.MOUSE_UP, stopDragObject);
// hide hotspots
getChildByName( objects[i].name + "_hs" ).alpha = 0;
}
}
Source: Download the FLA here
--
Updates:
I have added comments in my code to clarify what I'm trying to accomplish.
I'm planning to do board game in flash which has similar function and behaviour to this. User can drag the object to a specified tile and check wether that object belongs there or not.
After reviewing your code, your question is quite broad. I'm going pair it down to what seems to be your main concern - the score / counting correctly moved pieces.
Right now, you do the following every time an object is dragged:
counter++;
This means that the counter will increment no matter where you drag the object, and no matter how times you drag the object. (so even if the piece was already in the correct spot, if you dragged it a second time it will still increment your counter).
What you need to do, is associate a flag with each object to indicate whether it is in the correct location or not, and set that flag to the appropriate value every time that object is done dragging.
Something like this:
//don't use target, use currentTarget
if (e.currentTarget.hitTestObject(currentTargeName)) {
e.currentTarget.correct = true; //since MovieClips are dynamic, you can just make up a property on them and assign a value to it.
//to fix your alignment:
e.currentTarget.x = currentTargeName.x + ((currentTargetName.width - e.currentTarget.width) * 0.5);
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}else{
//if the hit test is false, mark it as NOT correct
e.currentTarget.correct = false;
}
Then, later to know the current count, iterate over all the pieces and check their correct value. This would be much easier if all your pieces were in an array.
var allPieces:Array = [king, queen, bishop_1, bishop_2, knight_1, knight_2, rook_1, rook_2,
pawn_1, pawn_2, pawn_3, pawn_4, pawn_5, pawn_6, pawn_7, pawn_8];
function countCorrect():Boolean {
var ctr:int = 0;
for(var i:int=0;i<allPieces.length;i++){
if(allPieces[i].correct) ctr++;
}
return ctr;
}
trace(countCorrect() + " of " allPieces.length " are correct");
As an aside, this best way to do this would be with some custom class files. That would however require a complete refactoring of your code.
Also, you probably don't want to use hitTestObject, as even if a piece is mostly over a neighbor, it will still be true as long as 1 pixel of it's bound touch 1 pixel of the tile. Better would be to do a hitTestPoint on the tile, and pass in the center point of the piece (the the middle of the piece has to be touching the tile for it to count).
//a point that is the center of the events current target (the piece)
var point:Point = new Point();
point.x = e.currentTarget.x + (e.currentTarget.width * 0.5);
point.y = e.currentTarget.y - (e.currentTarget.height * 0.5);
if (currentTargetName.hitTestPoint(point)) {

Operations carry over on next random

function roll(){
randomNumber = Math.ceil(Math.random() * range);
randomNumber2 = Math.ceil(Math.random() * range);
randomNumber3 = Math.ceil(Math.random() * range);
dice1_mc.gotoAndStop(randomNumber);
dice2_mc.gotoAndStop(randomNumber2);
dice3_mc.gotoAndStop(randomNumber3);
num1 = int(randomNumber);
num2 = int(randomNumber2);
num3 = int(randomNumber3);
trace(num1);
trace(num2);
trace(num3)
}
function AddCheck(e:MouseEvent):void {
ans = num1+num2+num3;
if (displayText.text == String(ans)){
//score++;
trace("Correct");
trace(ans);
displayText.text ="";
score+=1;
displayScore.text = String(score);
opsymbol=0;
RandomizeOperation();
}else{
trace("answer is " + ans + "------")
clearTxt();
//RandomizeOperation()
}
}
function MultiCheck(e:MouseEvent):void {
ans = num1*num3;
if (displayText.text == String(ans)){
//score++;
trace("Correct");
displayText.text ="";
score+=1;
displayScore.text = String(score);
opsymbol=0;
RandomizeOperation();
}else{
trace("answer is " + ans + "------")
clearTxt();
//RandomizeOperation()
}
}
function SubCheck(e:MouseEvent):void {
ans = num1-num2-num3;
if (displayText.text == String(ans)){
//score++;
trace("Correct");
trace(ans);
displayText.text ="";
score+=1;
opsymbol=0;
displayScore.text = String(score);
RandomizeOperation();
}else{
trace("answer is " + ans + "------")
clearTxt();
//RandomizeOperation()
}
}
function RandomizeOperation(){
var oprange:uint = 2;
opsymbol = Math.ceil(Math.random() * oprange);
//opsymbol = 2;
//trace(opsymbol);
if(opsymbol == 1){
dice2_mc.visible= true;
trace(opsymbol + " addition");
enterAns_btn.addEventListener(MouseEvent.CLICK, AddCheck);
roll();
}
if(opsymbol == 2){
dice2_mc.visible= true;
trace(opsymbol + " subtraction");
enterAns_btn.addEventListener(MouseEvent.CLICK, SubCheck);
roll();
}
}
The operation carry over everytime the question changes. I dont know whats wrong.
example first question is 1+2+3 (which is 6) and the next question is Subtraction (3-3-1- RIGHT answer is suppose to be -1) but it add. i cant figure out whats wrong
Nothing is carrying over here. You're just not removing the eventListener for mouse from one type to another, instead you are adding to the already there MouseEvent(s). Two traces at once is a big clue:
You need inside { } of your if (opsymbol == 2):
//will remove ANY existing current listeners to some function
enterAns_btn.removeEventListener(event.type, arguments.callee);
//adds a new listener to some function for this new Check type
enterAns_btn.addEventListener(MouseEvent.CLICK, SubCheck);
Why it happens:
if (opsymbol == 1)
{
//CHECK ONE: enterAns_btn listens for ADD
enterAns_btn.addEventListener(MouseEvent.CLICK, AddCheck);
}
if (opsymbol == 2)
{
//CHECK TWO: enterAns_btn listens for SUBTRACT
enterAns_btn.addEventListener(MouseEvent.CLICK, SubCheck);
}
Because CHECK TWO happened after CHECK ONE, At this point you're now telling Flash that enterAns_btn must actually do two functions on mouse click.. Essentially it heard this command:
enterAns_btn.addEventListener(MouseEvent.CLICK, AddCheck); //added by check one
enterAns_btn.addEventListener(MouseEvent.CLICK, SubCheck); //added by check two
And thats why you get two traces results at once.. One for adding to give you the 7 and the other for subtracting to give the -1.. Hope it helps.

Contiguous Block Plotting/Removal Logic

I've been attempting to write some logic for a program I have been working on and have run into some difficulty.
Essentially what I'm creating on my stage programmatically is a 4x4 grid (16 blocks), which looks just like this:
The user's task is to plot a contiguous shape onto the grid by clicking on the blocks and their shape should feature no gaps and no diagonally plotted blocks, for example the following would be a legal shape:
However, the following shape wouldn't be and would throw out an error to the user in the form of a pop-up graphic:
The plotting process for the grid is associated with a 4x4 virtual representation of the grid in Boolean Array form and looks like this:
public static var ppnRowArray1:Array = [false,false,false,false];
public static var ppnRowArray2:Array = [false,false,false,false];
public static var ppnRowArray3:Array = [false,false,false,false];
public static var ppnRowArray4:Array = [false,false,false,false];
public static var ppnColumnArray:Array = [ppnRowArray1,ppnRowArray2,ppnRowArray3,ppnRowArray4];
As the user clicks and selects a block, changing the colour property to brown, the relevant boolean property in my 'virtual grid representation' array will change from false to true. If a plot is illegally made then this property is changed back to false and the user is then invited to try their next plot again.
I have managed to write the code which forces the user to plot a legal shape and works out when an illegal plot has been made, but I now need to write the logic for when a user de-selects a block from an existing legal shape, making it non-contiguous and this is where my problem lies.
Here is the working solution as it stands.
//---------------------------------------------------------------------
public static function ppnCountSetCells():int
{
//Count Each 4x4 Grid Cell
var count:int = 0;
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row])
{
count++;
}
}
}
return count;
}
//---------------------------------------------------------------------
public static function ppnBlockValid():Boolean
{
if (ppnCountSetCells() > 1)
{
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row] == true)
{
// Check if we are connected to another set square
var validNeighbours:int = 0;
// Check North
if (row > 0)
{
if (ppnColumnArray[col][row - 1] == true)
{
validNeighbours++;
}
}
// Check South
if (row < 3)
{
if (ppnColumnArray[col][row + 1] == true)
{
validNeighbours++;
}
}
// Check West
if (col > 0)
{
if (ppnColumnArray[col - 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------------------------
// Check East
if (col < 3)
{
if (ppnColumnArray[col + 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------
if (validNeighbours < 1)
{
return false;
}
//-----------------------------------------------------------------------
}
}
}
}
return true;
}
//---------------------------------------------------------------------
function addBlock(e:MouseEvent):void
{
//trace("You Have Clicked On Grid Block Number: " + e.currentTarget.id);
if (InterfaceButtons.panelOpen == false)
{
//Listen to see if the block click is adjoining and pass back to see if it is valid on the grid
var col:int = (e.currentTarget.id - 1) % 4;
var row:int = (e.currentTarget.id - 1) / 4;
ppnColumnArray[col][row] = true;
addOrRemove = "add";
ppnBlockValid();
//Get the Block Valid Result (True or False) and pass it into a Boolean variable to use later
ppnGridError = ppnBlockValid();
trace("Is This Valid? " + ppnBlockValid());
//----------------------------------------------------------------------------------------------
//Push Blocks Selected into Array
ppnShapeArray[e.currentTarget.id] = true;
trace(ppnShapeArray);
//----------------------------------------------------------------------------------------------
//Add 1 to the block count which directly effects the final outcome depending on ++ or --
ppnBlocksSelected++;
PlantPopNitDesignPlot.ppnPlotMade = false;
//Hide Block to Reveal Brown One
e.currentTarget.alpha = 0;
//-----------------------------------------------------------------------------------------------
//Output an error if one is present on Click based on gridError Boolean Variable
ppnOutputAddError();
if (ppnGridError == false)
{
//Restore the block's alpha property as it isn't allowed to be selected, removing counter by one -- and changing final output accordingly
e.currentTarget.alpha = 1;
ppnBlocksSelected--;
ppnColumnArray[col][row] = false;
ppnShapeArray[e.currentTarget.id] = false;
ppnPopulateTotalSiteUnitsTxt();
}
//Update final total
ppnPopulateTotalSiteUnitsTxt();
//Call again to do dynamic colour font change should total exceed 10
ppnPopulateTotalSiteUnitsTxt();
//Added in to make sure it executes every time if an error is made.
if (ppnGridError == true)
{
e.currentTarget.removeEventListener(MouseEvent.CLICK, addBlock);
e.currentTarget.addEventListener(MouseEvent.CLICK, removeBlock);
}
}
}
function removeBlock(e:MouseEvent):void
{
if (InterfaceButtons.panelOpen == false)
{
var col:int = (e.currentTarget.id - 1) % 4;
var row:int = (e.currentTarget.id - 1) / 4;
ppnColumnArray[col][row] = false;
addOrRemove = "remove";
ppnBlockValid();
ppnGridError = ppnBlockValid();
trace("Is This Removal Valid? " + ppnBlockValid());
//trace("You Have Clicked On Grid Block Number: " + e.currentTarget.id);
e.currentTarget.alpha = 1;
ppnShapeArray[e.currentTarget.id] = false;
//trace("ppnShapeArray - " + ppnShapeArray);
//---------------------------------------------------------------------
ppnBlocksSelected--;
PlantPopNitDesignPlot.ppnPlotMade = false;
//Output an error if one is present on Click based on gridError Boolean Variable
ppnOutputRemoveError();
if (ppnGridError == false)
{
//Restore the block's alpha property as it isn't allowed to be selected, removing counter by one -- and changing final output accordingly
e.currentTarget.alpha = 0;
ppnBlocksSelected--;
ppnColumnArray[col][row] = true;
ppnShapeArray[e.currentTarget.id] = true;
ppnPopulateTotalSiteUnitsTxt();
}
//Update Final Total
ppnPopulateTotalSiteUnitsTxt();
//Call again to do dynamic colour font change should total falls below 10
ppnPopulateTotalSiteUnitsTxt();
//Added in to make sure it executes every time.
if (ppnGridError == true)
{
e.currentTarget.addEventListener(MouseEvent.CLICK, addBlock);
e.currentTarget.removeEventListener(MouseEvent.CLICK, removeBlock);
}
}
}
}
}
//---------------------------------------------------------------------
Now, for most occurences this logic works and detects illegal plots when adding or removing a block to the shape, however recently I discovered that when I have 5 > blocks in the shape, the logic for detecting an error on removal fails in certain circumstances.
A few examples of shapes being declared true and legal when they are not (when a block has been removed) are as follows:
I can see that it is the logic written in my 'ppnBlockValid():Boolean' function that needs adjusting to compensate for these outputs. It seems you can only remove a block providing that the neighbouring blocks are still joined to something else. While this works for smaller shapes, larger shapes (e.g. 5 blocks or more) can theoretically be split down the middle, so I think the code needs adjusting to account for this.
But how? Any help on this would be greatly appreciated.
Many thanks in advance and if you need any further information from me please let me know.
Cheers,
Joel
Edited
Thank you very much for providing that enhanced code and explanation #dhc I really appreciate it, but I'm still a little confused about how to implement all this properly.
Here is my current 'ppnBlockValid' function code based on your suggestion below:
public static function ppnBlockValid():Boolean
{
ppnIslands = [];
ppnAddNewIsland = [];
ppnAddToExistingIsland = [];
if (ppnCountSetCells() > 1)
{
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row] == true)
{
var addedToIsland = false;
// Check if we are connected to another set square
var validNeighbours:int = 0;
// Check North
if (row > 0)
{
if (ppnColumnArray[col][row - 1] == true)
{
validNeighbours++;
}
//----------------------------------
//ISLAND CHECK
if (ppnColumnArray[col][row - 1])
{
ppnAddToExistingIsland.push([col,row - 1],[col,row]);
addedToIsland = true;
}
}
// Check South
if (row < 3)
{
if (ppnColumnArray[col][row + 1] == true)
{
validNeighbours++;
}
}
// Check West
if (col > 0)
{
if (ppnColumnArray[col - 1][row] == true)
{
validNeighbours++;
}
//----------------------------------
//ISLAND CHECK
if (ppnColumnArray[col - 1][row])
{
ppnAddToExistingIsland.push([col - 1,row],[col,row]);
addedToIsland = true;
}
}
//-----------------------------------------------------------------------------------------
// Check East
if (col < 3)
{
if (ppnColumnArray[col + 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------
if (! addedToIsland)
{
ppnIslands.push([col,row]);
}
//-----------------------------------------------------------------------
if (ppnIslands.length >= 2 && addOrRemove == "remove")
{
trace("TWO ISLANDS HAVE BEEN FORMED AND AN ERROR SHOULD BE OUTPUT");
validNeighbours--;
}
/**/
//return (ppnIslands.length<=1);// 0 islands is valid also!
//-----------------------------------------------------------------------
if (validNeighbours < 1)
{
return false;
}
//-----------------------------------------------------------------------
}
}
}
}
return true;
}
//---------------------------------------------------------------------
I have been using the following shape as my experiment with the code:
Based on the above code and example shape, my current trace output is:
ppnIsland = |0,0|,0,2
ppnAddNewIsland =
ppnAddToExistingIsland = 0,0, 1,0, 1,0, 2,0, 2,0, 3,0, 1,0, 1,1, 1,1, 1,2, 0,2, 1,2, 1,2, 2,2, 2,2, 3,2
It appears that despite the shape being contiguous, the code I've tried interpreting from you is finding an additional island, in this case 'Col: 0, Row: 2', before a block removal has even taken place? Is this right?
This code of course does output an error if I try to remove the middle (red) block as the 'ppnIsland' array contains > 1 island, however I don't think its detecting the correct output?
Do I need to cross-reference the 'ppnIsland' and 'ppnAddToExistingIsland' arrays using the indexOf command to check if either element is part of an existing island?
Joel
You could track "islands" as separate lists as you process (in ppnBlockValid). So, for instance (caps are selected tiles):
a B c d
e F g H
i J k L
m n o p
When you process row one, you create an island for B. When you process row 2, you find B connected to F, so the island becomes [B,F]. Then, when you encounter H, which is not connected, you have two islands: [B,F],[H]. On row 3, your islands would look like [B,F,J],[H,L]. If K were selected, you'd find J and L connected, and you'd consolidate them into one island. You only have to check the current row for connections between islands, so you'd only have to check J and L in this case.
If you end up with one island, you're fine, otherwise not. This is, unfortunately, an order n**2 search, but your grid is small so it probably doesn't mean much.
Something like this (WARNING: not debugged code):
private var islands : Array;
public function ppnBlockValid():Boolean {
islands = [];
if (ppnCountSetCells() > 1) {
for (var row=0; row<=3; row++) {
for (var col=0; col<=3; col++) {
if (ppnColumnArray[col][row]) {
var addedToIsland = false;
// Check if we are connected to another set square
// Check North
if (row > 0) {
if (ppnColumnArray[col][row-1]) {
addToExistingIsland([col,row-1],[col,row]);
addedToIsland = true;
}
}
// Check South - not needed since next row will catch this on North
// Check West
if (col > 0) {
if (ppnColumnArray[col-1][row]) {
addToExistingIsland([col-1,row],[col,row]);
addedToIsland = true;
}
}
// Check East - not needed since next col will catch this on West
if (!addedToIsland) { addNewIsland([col,row]); }
}
}
}
}
return (islands.length<=1); // 0 islands is valid also!
}
You just have to implement "addNewIsland" and "addToExistingIsland". addNewIsland is easy: just add a new element to the islands array. You may want to use a string ID for the tiles instead of an array (e.g. "01" instead of [0,1]) since it may make checking for existing elements easier (e.g. you can use indexOf to find a tile in an island).
addToExistingIsland is a bit trickier: you have to check if either element is part of an existing island, and consolidate those islands if so. If not, the new tile just gets appended to the island.
You don't need to maintain 3 arrays, just one island array that, at the end of ppnBlockValid, will either be length > 1 (invalid), or less (valid).
You cannot just populate the "ppnAddNewIsland" and "ppnAddToExistingIsland" arrays-- these should be functions. If ppnIslands is a class variable, then your addNewIsland function may look something like this:
private function addNewIsland(who) {
ppnIslands.push([who]);
}
... the only optimization I'd probably make is to turn who (passed as an array [col,row]) into a character string, as I mentioned, to make finding whether a tile exists in an island easier.
Then, you need a function like:
private function addToExistingIsland( existing_island_tile, new_tile ) {
}
This function must:
1. check whether these two tiles already exist in the same island (just return if so)
2. check whether new_tile is already on an island (consolidate the two islands into one if so)
3. otherwise, just add new_tile to the island that existing_island_tile is a member of
The code I presented should work pretty much as-is-- although you could add back in the "validNeighbours" logic if you want a quick exit for isolated tiles.

As3 textarea limit

I made a simple chat application but i have this problem the text area will fill up and the users will have to then clear it.. How can i make it so if the text area reaches a certain amount of characters it will clear the old chat text ?
public function receiveMsg(userName:String,type:String,msg:String,txtColor:String):void{
if(type == "user"){
ConsoleTxt.htmlText += "<b>"+userName+"</b>: <font color='"+txtColor+"'>"+msg+"</font> \n"
}
if(type == "server"){
ConsoleTxt.htmlText += "<b><font color='#6ade57'>SERVER</font></b> "+userName+" "+msg+"\n"
}
if(type == "disconnect"){
ConsoleTxt.htmlText += "<b><font color='#6ade57'>SERVER</font></b> "+userName+" <font color='#fc0000'>"+msg+"</font>\n"
}
ConsoleTxt.verticalScrollPosition = ConsoleTxt.maxVerticalScrollPosition
}
In my opinion, you should work on number of lines & not on characters in the textarea.
Something like :
textarea.addEventListener(Event.CHANGE, changeHandler);
var maxLines = 5;
function changeHandler(event:Event):void {
if(textarea.numLines > maxLines)
textarea.text = textarea.text.substring(textarea.getLineLength(0));
}
should ensure that there are always max 5 lines in the textarea, else the oldest line gets deleted.
For anyone who has the same problem fixed the issue with setting all data into an array collection.
private var chatHolder:Array = new Array();
public function receiveMsg(userName:String,type:String,msg:String,txtColor:String):void{
for(var i:int = 0;i < chatHolder.length;i ++){
if(i > 4){
chatHolder.shift();
}
}
if(type == "user"){
chatHolder.push("<b><a href='event:"+userName+"'>"+userName+"</a></b>: <font color='"+txtColor+"'>"+msg+"</font> \n")
}
}

Is it possible to get the label of the next frame in flash?

Had a look for this but nothing seemed clear at the moment I have a script which will only play if the frame is the currentFrameLabel or rewind.
However in order for it not to go one frame too far I need to be able to stop it on the frame before the change not on the change.
Or am I just going about this the wrong way?
For example:
Frame 10 Label: Up
Frame 12-36 Label: Idle Loop
Frame 37 Label: Hand Up
I need it to only play from frames 12 to 36 but at the moment it plays from frames 12-37.
var reverse:Boolean = false;
var robotlabel:String = 'Up/Down';
what.addEventListener(MouseEvent.MOUSE_OVER, botAction);
what.addEventListener(MouseEvent.MOUSE_OUT, botAction2);
function botAction(evt:MouseEvent):void{
reverse = false;
robotlabel = 'Hand up/Down';
robot.gotoAndPlay('Hand up/Down');
robot.addEventListener(Event.ENTER_FRAME,run);
}
function botAction2(evt:MouseEvent):void{
reverse = true;
robot.prevFrame();
}
function run(e:Event):void{
trace("label:" + robotlabel);
trace("current" + robot.currentFrameLabel);
if(robot.currentFrameLabel != robotlabel && robot.currentFrameLabel != null){
trace("stoooooppppp");
robot.stop();
}
if(reverse == true && currentFrameLabel==robotlabel){
robot.prevFrame();
trace("reversing!");
}else if(reverse == false && (currentFrameLabel==robotlabel || robot.currentFrameLabel == null)){
robot.nextFrame();
}else{
trace("destroy");
reverse = false;
robot.stop();
robot.removeEventListener(Event.ENTER_FRAME,run);
}
}
There is not a "nextFrameLabel" property as such in as3, however you can get an array of all the frame labels and numbers in your target movieclip using the currentLabel property of the MovieClip and work it out from there, since you know the currentFrame at all times.
Quick example from the docs:
import flash.display.FrameLabel;
var labels:Array = mc1.currentLabels;
for (var i:uint = 0; i < labels.length; i++)
{
var label:FrameLabel = labels[i];
trace("frame " + label.frame + ": " + label.name);
}
In case this is useful to anybody else I solved it by looping through each frame and then storing the corresponding label to the frame so it can later be checked against.
for (i = 1; i < robot.totalFrames; i++)
{
for (var n:uint = 0; n < this.robolabels.length; n++)
{
if(this.robolabels[n].frame == i){
newLabel = this.robolabels[n].name
}
}
this.roboframes[i] = newLabel;
}