Friday, August 2, 2013

Flex: Creating the Sudoku Base Grid

I have been trying out all sorts of method in creating a Sudoku Grid and I finally managed to do it. The solution was far more easier than I thought ...

Let's take a look at the source code - SimpleSudokuGrid.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx" 
      creationComplete="creationCompleteEvent(event)">
 <fx:Declarations>
  <!-- Place non-visual elements (e.g., services, value objects) here -->
 </fx:Declarations>
 <fx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;
   import mx.events.FlexEvent;
   
   import spark.components.Button;
   import spark.events.IndexChangeEvent;
   
   /**
    * Holds the data to form a Grid
    */
   private var baseGrid:ArrayCollection;
   
   /**
    * Boolean to determine if there's a need to recreate the grid
    */
   private var needToReset:Boolean = true;
   
   /**
    * Number to count the number of failure in the current
    * grid data forming.
    */
   private var tries:Number = 0;
   
   /**
    * Maximum number of failure before recreate.
    */
   private static const MAX_TRIES:Number = 9;
   
   /**
    * A set of data that will be used to populate the ButtonBar
    */
   [Bindable]
   private var dummyData:ArrayCollection = new ArrayCollection(
    [{label: "1", value:1},
     {label: "2", value:2},
     {label: "3", value:3},
     {label: "4", value:4},
     {label: "5", value:5},
     {label: "6", value:6},
     {label: "7", value:7},
     {label: "8", value:8},
     {label: "9", value:9}]);
   
   /**
    * Upon Create Complete, we will create a Sudoku(9x9) Grid
    */
   protected function creationCompleteEvent(e:FlexEvent):void
   {
    //Will keep recreating the grid if 'needToReset' is
    //true
    while(needToReset)
    {
     //Set 'needToReset' to false
     needToReset = false;
     createGrid();
     createNumbers();
    }
    
    createGridUI();
    btnBar.dispatchEvent(
     new IndexChangeEvent(IndexChangeEvent.CHANGE));
   }
   
   /**
    * Create new (9*9) DataCollection and fill it up with 0.
    */
   private function createGrid():void
   {
    baseGrid = new ArrayCollection();
    
    var tempCol:ArrayCollection;
    for(var i:int = 0; i < 9; i ++)
    {
     tempCol = new ArrayCollection();
     for(var j:int = 0; j < 9; j++)
     {
      tempCol.addItem(0);
     }
     baseGrid.addItem(tempCol);
    }
   }
   
   /**
    * Start populating the numbers from 1 - 9.
    * If 'needToReset' is true, move out of this function.
    */ 
   private function createNumbers():void
   {
    for(var i:int = 1; i < 10; i ++)
    {
     insertNumber(i);
     
     if(needToReset)
     {
      break; 
     }
    }
    
    if(needToReset)
    {
     return; 
    }
    
    printNumbers();
   }
   
   /**
    * Find the empty boxes that you can slot the value
    * @param value a value that you are going to slot in
    */   
   private function insertNumber(value:Number):void
   {
    //Used to store the rows avaliable in a column
    var numbersFree:Array;
    //Is the 1st segment taken?
    var fromSegment1:Boolean = false;
    //Rows that we will need to remove if 1st segment
    //is taken
    var seg1Array:Array = [0,1,2];
    //Is the 2nd segment taken?
    var fromSegment2:Boolean = false;
    //Rows that we will need to remove if 2nd segment
    //is taken
    var seg2Array:Array = [3,4,5];
    //Is the 3rd segment taken?
    var fromSegment3:Boolean = false;
    //Rows that we will need to remove if 3rd segment
    //is taken
    var seg3Array:Array = [6,7,8];
    //Stores the current selected row of a column
    var rand:Number;
    //Stores all the boxes that has the value 
    var tempArray:Array = new Array();
    for(var i:int = 0; i < 9; i ++)
    {
     //In sudoku, it's divided into 9 (3*3) boxes.
     //Each each of this box, the same number can 
     //appear once.
     //Therefore every 3 column, we need to reset
     //these 3 values.
     if(i % 3 == 0)
     {
      fromSegment1 = false;
      fromSegment2 = false;
      fromSegment3 = false;
     }
     //Find the empty slots of the column first
     numbersFree = findEmptySlot(i);
     //Remove the rows based on the previous selections
     if(fromSegment1)
     {
      numbersFree = removeSlot(numbersFree, seg1Array);
     }
     if(fromSegment2)
     {
      numbersFree = removeSlot(numbersFree, seg2Array);
     }
     if(fromSegment3)
     {
      numbersFree = removeSlot(numbersFree, seg3Array);
     }
     //Remove the rows that have been selected already.
     if(tempArray.length > 0)
     {
      numbersFree = removeSlot(numbersFree, tempArray);
     }
     
     //If there are rows left
     if(numbersFree.length > 0)
     {
      //Select one of them
      rand = Math.floor(Math.random() 
       * numbersFree.length);
      rand = numbersFree[rand];
      //Check which Segment the row number is in
      if(rand < 3)
      {
       fromSegment1 = true;
      }else if(rand < 6)
      {
       fromSegment2 = true;
      }else{
       fromSegment3 = true;
      }
      //Add it to the list of boxes with the 
      //same value
      tempArray.push(rand);
      
      //Update the value in the main DataCollection
      insertSlot(i, rand, value);
     }else{
      //else if '0' row is avaliable
      //increase the number of failures/tries
      tries ++;
      //Revert back all the slots
      revertSlots(tempArray);
      tempArray = new Array();
      i = -1;
      //Check if it has reach the Maximum tries/failures
      if(tries == MAX_TRIES)
      {
       //Need to recreate the grid again.
       tries = 0;
       needToReset = true;
       return;
      }
     }
    }
   }
   
   /**
    * Find all the empty rows of a selected Column
    * @param col the selected column index
    * @return Array of rows that currently has a value of '0'
    */
   private function findEmptySlot(col:Number):Array
   {
    var tempArray:Array = new Array();
    var tempArrayCol:ArrayCollection;
    tempArrayCol = ArrayCollection(baseGrid.getItemAt(col));
    for(var i:int = 0; i < tempArrayCol.length; i ++)
    {
     if(Number(tempArrayCol.getItemAt(i)) == 0)
     {
      tempArray.push(i);
     }
    }
    return tempArray;
   }
   
   /**
    * Compare 2 Arrays.
    * @param tempArray1 Array of values
    * @param tempArray2 Array of values
    * @return the Array of values tempArray1 that are different from 
    * tempArray2
    */ 
   private function removeSlot(tempArray1:Array, tempArray2:Array):Array
   {
    var tempArray:Array = new Array();
    for(var i:int = 0; i < tempArray1.length; i ++)
    {
     tempArray.push(tempArray1[i]);
    }
    for(i = 0; i < tempArray.length; i ++)
    {
     for(var j:int = 0; j < tempArray2.length; j ++)
     {
      if(tempArray[i] == tempArray2[j])
      {
       tempArray.splice(i, 1);
       i --;
       break;
      }
     }
    }
    return tempArray;
   }
   
   /**
    * Replace a particular slot with a new value.
    * @param col selected column
    * @param row selected row
    * @param value the new value
    */
   private function insertSlot(col:Number, row:Number, value:Number):void
   {
    var tempArrayCol:ArrayCollection;
    tempArrayCol = ArrayCollection(baseGrid.getItemAt(col));
    tempArrayCol.setItemAt(value, row);
   }
   
   /**
    * This function will change the newly numbers of the same group 
    * back to 0. For example, halfway through the number 7, a problem 
    * has occur, therefore need to replace all the number 7 from the 
    * first box again. Hence, need to reset all the existing number 
    * 7 box to '0' first.
    * @param tempArray1 Boxes that requires the values to be reset to '0'
    */
   private function revertSlots(tempArray1:Array):void
   {
    var tempArrayCol:ArrayCollection;
    for(var i:int = 0; i < tempArray1.length; i ++)
    {
     tempArrayCol = ArrayCollection(baseGrid.getItemAt(i));
     tempArrayCol.setItemAt(0,tempArray1[i])
    }
   }
   
   /**
    * This is merely for debugging purposes. You can use
    * this function to track the valus in the (9x9) DataCollection.
    */
   private function printNumbers():void
   {
    var tempArrayCol:ArrayCollection;
    var tempArray:Array = new Array();
    for(var i:int = 0; i < 9; i ++)
    {
     tempArray.push("");     
    }
    for(var col:int = 0; col < baseGrid.length; col ++)
    {
     tempArrayCol = 
      ArrayCollection(baseGrid.getItemAt(col));
     for(var row:int = 0; row < tempArrayCol.length; row ++)
     {
      tempArray[row] += 
       Number(tempArrayCol.getItemAt(row));
     }
    }
    for(i = 0; i < tempArray.length; i ++)
    {
     trace(tempArray[i]);
    }
   }
   
   /**
    * Create all the buttons base on the values in the ArrayCollection
    */ 
   private function createGridUI():void
   {
    var btn:Button;
    var tempArrayCol:ArrayCollection;
    
    for(var row:int = 0; row < 9; row ++)
    {
     for(var col:int = 0; col < baseGrid.length; col ++)
     {
      tempArrayCol = 
       ArrayCollection(baseGrid.getItemAt(col));
      btn = new Button();
      btn.width = 30;
      btn.height = 30;
      btn.label = 
       Number(tempArrayCol.getItemAt(row)).toString();
      btn.name = "btn_" + row + "_" + col; 
      grpGrid.addElement(btn);
     }
    }
   }
   
   /**
    * Upon clicking on one of the buttons of the ButtonBar, we will
    * show buttons with the same value with a alpha value of 1 and
    * those that are different will have a alpha value of 0.3.
    */
   protected function changeEvent(event:IndexChangeEvent):void
   {
    var tempBar:ButtonBar = ButtonBar(event.target);
    var tempObj:* = tempBar.selectedItem;
    var selectedValue:Number = -1;
    if(tempObj)
    {
     selectedValue = tempObj.value;
    }
    
    var btn:Button;
    for (var i:int = 0; i < grpGrid.numElements; i ++)
    {
     btn = Button(grpGrid.getElementAt(i));
     if(Number(btn.label) == selectedValue)
     {
      btn.alpha = 1;
     }else{
      btn.alpha = 0.3;
     }
    }
   }
  ]]>
 </fx:Script>
 <s:VGroup verticalCenter="0" 
     horizontalCenter="0" 
     horizontalAlign="center">
  <s:Group id="grpGrid" >
   <s:layout>
    <s:TileLayout requestedRowCount="9" 
         requestedColumnCount="9"
         horizontalGap="3" 
         verticalGap="3"/>
   </s:layout>
  </s:Group>
  <s:Label text="Click on the following to show the buttons with the same value."/>
  <s:ButtonBar id="btnBar" 
      dataProvider="{dummyData}"  
      selectedIndex="-1"
      labelField="label"
      change="changeEvent(event)"/>
 </s:VGroup>
</s:Application>
* Click here for the demo shown in this post.
^ Click here for the source files for the demo.

No comments:

Post a Comment