Zend - The PHP Company




Games

Add Code


Klondike  

Type: application
Added by: codewalkers
Entered: 16/06/2002
Last modified: 06/12/2001
Rating: - (fewer than 3 votes)
Views: 6081
This is a winner of the Codewalkers.com PHP coding contest. This script automatically plays a game of klondike. For more information on this script and to see it in action goto http://codewalkers.com/php-contest


<?php

/*
Written by Laurent as a submission to codewalker php contest #4
I can be reached at <removed>

goal is to write a script that would make best score / execute faster / displays each plays nicely (in this order of
importance) at klondike solitaire game

See http://codewalkers.com/php-contest.php


usage : 
card.php -> will use default deck and default graphic mode
card.php?stock=deck -> will use "deck.txt" (note the added extension) and default graphic mode
card.php?stock=deck&graphic=on -> will use deck.txt and graphic mode
card.php?stock=deck&graphic=off -> will use deck.txt and text mode

timer is started just before starting the algo and right before displaying the result

temporary storage of each try's results and display is made through php's output_buffering (ob_xxx functions)

    

*/

define('PATH_TO_IMAGES','./');
define('DEFAULT_STOCK_FILE','deck.txt');
define('DEFAULT_GRAPHIC','true');

ob_start();

//error_reporting(E_ALL);

if (isset($stock)) $stockfilename=$stock.'.txt';
else 
$stockfilename=DEFAULT_STOCK_FILE;

if (isset(
$graphic) && ($graphic=='on')) $graphic=true;
else if (isset(
$graphic) && ($graphic=='off')) $graphic=false;
else 
$graphic=DEFAULT_GRAPHIC;


srand((double)microtime()*1000000);

function 
getmicrotime(){ 
    list(
$usec$sec) = explode(" ",microtime()); 
    return ((float)
$usec + (float)$sec); 
    } 


$order = array('1'=>0,'2'=>1,'3'=>2,'4'=>3,'5'=>4,'6'=>5,'7'=>6,'8'=>7,'9'=>8,'10'=>9,'J'=>10,'Q'=>11,'K'=>12);
$reverse_order array_keys($order);


function 
Message($string) {
echo(
'<center><table border=1 width="100%"><tr><td align="center" bgcolor="#C0C0C0">'.$string.'</td></tr></table></center>'."n");

}


class 
card {
    var 
$kind;   //HSDC
    
var $color;  //RB
    
var $pos;    //0..12
    
var $symbol;
    var 
$name;

    var 
$visible;
    var 
$moveable;

    function 
card($c) {
        global 
$order;

        
$this->name=$c;
        
$val substr($c,0,strlen($c)-1);
        
$this->symbol=$val;
        
$this->pos=$order[$val];
        
$this->kind substr($c,-1);
        if (
$this->kind=='H' || $this->kind=='D'$this->color='R'; else $this->color='B';

        
$this->visible=false;
    }

    function 
set_visible($yes) {
        
$this->visible=$yes;
    }

    function 
is_visible() {return $this->visible;}

    function 
name() {return $this->name;}
    
//Colored name
    
function c_name() {
      return ( (
$this->color=='R'?'<font color="red">':'').
                
$this->name.
               (
$this->color=='R'?'</font>':'')
             );
    
    }
    function 
color() {return $this->color;}
    function 
val() {return $this->pos;}
    function 
kind() {return $this->kind;}
    function 
symbol() {return $this->symbol;}
}



//Class responsible for the stock/talon management
class stock{

    var 
$stock = array();   //Main stock of cards
    
var $talon = array();   //returned cards

    
var $turn 0;      //Will count how many times we played 3 cards

    //Initialize the class by loading the deck from file
    
function stock($filename='deck1.txt') {
        if (
file_exists($filename)) {
            
$this->stock file($filename);
            foreach(
$this->stock as $id=>$val) if (trim($val) != ''$this->stock[$id]=new card(strtoupper(trim($val)));
        } else return 
false;
        
        
$this->turn=0;     //Initialize turn counter    
        
        //shuffle ($this->stock); uncomment this line if you want to play with random (shuffled) stock
    
}   
    
//Play $skip cards and return the next card from the stock 
    //(that is every one out of $skip or the last one if less than $skip remaining)
    //Return the card if stock is not empty, 'empty string' if there are no more cards in the stock
    
function GetCard($skip='3') {
        if (
count($this->stock)==0) return ''//No more card left in stock

        
if (count($this->stock) < $skip$skip count($this->stock);
        for(
$i=0;$i<$skip;$i++) {
            
$card array_shift($this->stock);
            
array_unshift($this->talon,$card); 
        }
        
$this->turn++;
        return 
$card;
    }

    
//Uses the current available card, that is remove it from talon
    
function UseCard() {
        return 
array_shift($this->talon);
    }
    
    
    
//will return the current available card from the talon or 'empty string' if talon is empty
    
function CurrentCard() {
        if (
count($this->talon) == 0) return '';
        return 
reset($this->talon);
    }

    
//Reset the stock to the remaining cards after it has been emptied
    //Return number of cards in stock (0 probably means we won)
    
function DrawOver() {
        
$this->stock array_reverse($this->talon);     //turn talon over to stock
        
$this->talon=array();       //Empties talon
        
return count($this->stock);     //return number of cards in stock
    
}
//End class stock


//Class responsible for the management of a column in the tableau
class column {
    var 
$cards = array();   //Arrays of cards in this column

    //Initialize the column to an empty one
    
function column () {
        
$this->cards=array();
    }


    
//Append a card (or an array of cards) down to a colum.
    
function append ($c) {    
  
//$c can be a single card or an array of cards      
        
if (is_array($c)) foreach($c as $cardarray_push($this->cards$card);
        else 
array_push($this->cards$c);
    }


    
//Remove cards from a column, start at position $pos in the column
    
function remove($pos) {
        
$cards array_splice($this->cards,$pos);
        
//Just a test to make sure rules are correctly followed. Could be removed to increase speed performance
        
if ($cards[0]->is_visible()) return $cards;

          echo(
"serious problem here, trying to move a column starting with a non visible card<br>");
          return array();
    }

    
// Return position of card $c if it is found in the column, -1 otherwise
    // "Found" means exists AND is visible
    
function find($c) {
        foreach(
$this->cards as $pos=>$card) {
            if (
$card->is_visible() && $card->name()==$c->name()) return $pos;
        }
        return -
1;
    }

    
//Will turn topmost card of the column face up
    //Return number of cards returned (0 or 1 actually).
    
function faceup() {
        if (
count($this->cards)==0) return 0//nothing left to face up
        
$card array_pop($this->cards);
        if (!
$card->is_visible()) {
            
$card->set_visible(true);
            
array_push($this->cards,$card);
            return 
1;
        } else {
            
array_push($this->cards,$card);
            return 
0;
        }
    }

    
//Return the top most card of a column
    
function topmost() {
        if (
count($this->cards)==0) return '';
        
$card end($this->cards);
        
        
//Again, just a test to make sure rules are correctly followed. Could be removed to increase speed performance
        
if ($card->is_visible()) return $card; else return '';
    }


    
//return moveable cards in a column (that is, all the visible ones)
    
function moveable() {
        if (
count($this->cards)==0) return array(); //If column is empty, return no cards

        //Let's find out the first visible card of the column, which should be the head of the moveable cards
        
foreach($this->cards as $pos=>$card) {
      
//No reason to move a column headed by a king         
            
if ($pos==&& $card->is_visible() && substr($card->name(),0,1)=='K') return array();  
            
            if (
$card->is_visible()) { //We found it, let's fetch the moveable cards 
                
return array_slice($this->cards,$pos);
            }
        }
        
//No card is visible in this column, this should never happen (if there are cards of course) !!!
        
return array(); 

    }
    
    
    
//Return number of cards in this column
    
function card_count() { return count($this->cards);}
    
    function 
get_card($pos) {
     if (
count($this->cards)>$pos) return $this->cards[$pos]; else return '';
    }
    
     
    
//Draw a nice table to display our column vertically. Each card occupy a row
    
function draw($graphic=false) {
        
reset($this->cards);
        echo(
'<table border=1>');
        if (
$graphic) {
            
$count=count($this->cards);
            foreach(
$this->cards as $card) {
                echo(
'<tr><td>');
                if (
$card->is_visible()) {
                    if (
$count>1$src='c_'.$card->name().'.jpg'; else $src=$card->name().'.jpg';
                    echo(
'<img src="'.PATH_TO_IMAGES.$src.'">');
                } else {
              echo(
'<img src="'.PATH_TO_IMAGES.'hidden.jpg">');
                }
                echo(
'</td></tr>');
                
$count--;
            }
        } else {
            foreach(
$this->cards as $card) {
                echo(
'<tr><td>');         //Start row
                
if ($card->is_visible()) {
                  echo(
$card->c_name());
                } else echo(
"X");
                echo(
'</td></tr>');         //End row
            
}
        }
        echo(
'</table>'."n");
    }

//End class column



//Class responsible to manage the main tableau
class tableau {
    var 
$columns = array();   //Those are the columns of the tableau
    
var $nb_column;     //Number of column in our tableau (could have been hardcoded though)

    //Initialize tableau by playing cards over columns
    
function tableau(&$stock,$nb_column=7) {
      
      
$this->nb_column $nb_column;  //Define number of columns in our tableau
      
        
for($i=0;$i<$this->nb_column;$i++) {  //Create our column
            
$this->columns[] = new column();
        }

        
$i=0;
        
$j=0;
        while (
$j<$this->nb_column) {   //Play cards from stock to the tableau to initialize it
            
for($i=$j;$i<$this->nb_column;$i++) {
                
$card $stock->GetCard(1); //We play one card at a time
                
$stock->UseCard();    
                if (
$i==$j$card->set_visible(true); //Set each last card visible
                
$this->columns[$i]->append($card);    //Append it down the column 
            
}
            
$j++;
        }
    }


    
//This function will return an array of cards which are the ones that are susceptible to be stacked onto fondation
    //ie, each latest card from each column
    
function get_topmost() {
        
$cards = array();
        foreach(
$this->columns as $column) {    //We simply retrieve the topmost card for each columns
            
$cards[] = $column->topmost();
        }
        return 
$cards;
    }

    
//Will return an array of row of cards that are moveable from a column to another
    //ie, all the cards in each row that are visible
    
function get_moveable() {
        
$moveable=array();
        foreach(
$this->columns as $column) {  //We simply retrieve the moveable cards for each columns
            
$moveable[] = $column->moveable();
        }
        return 
$moveable;
    }

    function 
empty_column_count() {
        
$count=0;
        foreach(
$this->columns as $column) if ($column->card_count()==0$count++;
        return 
$count;

    }


    
//Will check if a given card could be placed somewhere in the tableau 
    //Will return an array containing the columns where the given card could be placed
    
function is_placeable($card) {
        
$valid = array();

  
//Let's check each column to see if card could be placed there
        
foreach($this->columns as $id=>$column) {
            
$topmost $column->topmost();     //Retrieve column's topmost card
            
if (($topmost=='') && ($card->symbol=='K'))  //Only Kings can fit in an empty column
                
$valid[]=$id
            else {
              
//Let's check if card can fit over current topmost card : column not empty && color!= && next value
                
if ($topmost!='' && ($topmost->color()!=$card->color()) && ($topmost->val()==$card->val()+1)) { 
                    
$valid[]= $id;
                }
            }
        }
        return 
$valid;
    }


    
//Will browse the tableau to find out if given card is visible or not
    //This function is mainly used to see if a card is worth stacked or not
    
function is_visible($card) {
        foreach(
$this->columns as $id=>$column) {
            if ((
$pos=$column->find($card))!=-1) return true//Card is found in this column
        
}
  
//Card was not found, return false
        
return false;
    }

    
//Will check if all kings are heading a column or if there is a room (empty column) for them
    //Return true if Number of empty rows + number of kings heading a row >= 4
    //Mainly used to check if it's worth to move a card from a column to another one
    
function all_kings() {
        
$kings=0;
        foreach(
$this->columns as $id=>$column) {
            
$firstcard $this->columns[$id]->get_card(0);    //Fetching first card of a row (bottom most)
            
if ($firstcard=='' || ($firstcard->symbol()=='K' && $firstcard->is_visible())) $kings++;  //Count it if it's empty or headed by a king
        
}
        return (
$kings>=4);           //
    
}

    
//Will remove a card (or a row of cards) from a given column
    //if column is not supplied, a search will be made through each column to locate the given card
    //Return the card(s) if they are found, empty array otherwise
    
function remove($card,$column=-1) {
      
//Column was specified
        
if ($column != -1) {
           if ((
$pos $this->columns[$column]->find($card)) !=-1) {  //if card is found in specified column    
               
              
$cards $this->columns[$column]->remove($pos);     //remove it
              
return $cards;            //Return removed card
         
           
}
           return array();          
//Card was not found in specified column, return empty array
        
}
  
//No column supplied, so search for card
        
foreach($this->columns as $id=>$column) {
            if ((
$pos=$column->find($card))!=-1) {    //Card is found in this column
               
$cards $this->columns[$id]->remove($pos);    //Remove it
               
return $cards;           //Return removed card
            
}
        }
        return array();           
//Card was not found in the tableau, return empty array
    
}

      
    
//Will append an array of cards over specified column 
    
function append($cards,$column) {
        
$this->columns[$column]->append($cards);
    }

    
    
//Turn given column's last card face up if needed
    //If no column is given, turn all columns last card face up if needed
    //Return number of cards turned 
    
function turn_faceup($column=-1) {
        if (
$column!=-1) {
            return 
$this->columns[$column]->faceup();
        }    

        
$count=0;
        foreach(
$this->columns as $id=>$column) {
            
$count += $this->columns[$id]->faceup();
        }
        return 
$count;
    }

    
//Draw a nice table to display our tableau. Each column occupy a column 
    
function draw($graphic=false) {
        echo(
"<table><tr valign=top>n");
        foreach(
$this->columns as $column) {echo('<td>');$column->draw($graphic);echo('</td>');}
        echo(
"</tr></table>n");
    }
//End class tableau



//Class responsible to manage the fondation
class fondation {
    var 
$stack = array();   //Our stack of cards

    
function fondation () {

        
$this->stack['S']=array(); //spades
        
$this->stack['H']=array(); //Hearts
        
$this->stack['D']=array(); //Diamonds
        
$this->stack['C']=array(); //Clubs
        
    
}

    
//Check if a given card is stackable or not
    
function is_stackable($card) {

        if (
$card->val()=='0' && count($this->stack[$card->kind()])==0) return true;
        else if (
count($this->stack[$card->kind()])==0) return false;

    
        
$last_card end($this->stack[$card->kind()]);
        if (
$card->val()==$last_card->val()+1) {return true;}

        return 
false;
    }
    

    
//Check if a given card is stacked
    
function is_stacked($card) {
        global 
$order;

        
//split_card_name($card,$val,$kind,$color);
        //$pos = $order[$val];

        
if (isset($this->stack[$card->kind()][$card->val()])) return true; else return false;

    }

    
//Take a card and stack it. NO check is done !!
    
function stack($card) {
        
$this->stack[$card->kind()][] = $card;
    }

    
//Draw a nice output to diplay the fondation
    
function draw($graphic) {
        foreach(
$this->stack as $id=>$cards) {
            if (!
$graphic) echo("$id &nbsp;->&nbsp;");
            foreach(
$cards as $card) {
                if (
$graphic) echo('<img src="'.PATH_TO_IMAGES.$card->name.'.jpg">');
                else echo(
$card->symbol()."&nbsp;");
            }
            echo(
"<br>n");
        }
    }
    
    
    
//Check if the fondation is full (then we know we won)
    
function is_full() {
        foreach(
$this->stack as $id=>$cards) {
            if (
count($cards)<13) return false;  //if a stock holds less than 13 cards, it's not full
        
}
        return 
true;
    }
    
//End class fondation


//Draw the current situation (tableau + fondation)
function draw_situation() {

    global 
$tableau$fondation;
    global 
$graphic,$score;
    
    
    echo(
"<table border=1>");
    echo(
"<tr><td colspan=2 align="center">score : $score</td></tr>");
    echo(
"<tr><td>");
    
$tableau->draw($graphic);
    echo(
"</td><td>");
    
$fondation->draw($graphic);
    echo(
"</td></tr></table>n");
}



//This function will decide which column is the *best* one to move a card on when there are more than one possible choice (2 actually)
//Best column number will be returned
function best_column($columns,$cards)
{
    global 
$tableau;

    if (
count($columns) <= 1) return $columns[0];  //no choice, just return with only available position
    
if ($cards[0]->symbol()=='K') return $columns[0];  //No need to bother for a king
    
    //we'll never have more than 2 possible column for a card, so let's isolate them
    
$col1=$tableau->columns[$columns[0]];
    
$col2=$tableau->columns[$columns[1]];
    
    
//If number of cards in col1 < 2, let's first check if col 2 isn't better, otherwise check col1
    
if ($col1->card_count() < 2) {  //not enough cards on $col1 to decide if col1 is the best place to move the card
      //so, we'll check $col2 to see if current card's kind is the same as (last-1) card
      
if ($col2->card_count() >= 2) {
          
$match_card=$col2->get_card($col2->card_count()-2);
          if (
$match_card->kind() == $cards[0]->kind()) return $columns[1]; else return $columns[0];
      } else return 
$columns[0]; //both column have only 1 card, so there is no 'best' choice
      
    
} else { //$col1 holds enough cards to decide if it's the 'best' choice
      
$match_card=$col1->get_card($col1->card_count()-2);
      if (
$match_card->kind() == $cards[0]->kind()) return $columns[0]; else return $columns[1]; 
    }
    
    
//We should never reach here, but just in case, let's return a column
    
return $columns[0];
}


function 
best_move($moveable_cards,$column) {
    global 
$tableau;
    global 
$random_used;
    
    
$nb_empty=$tableau->empty_column_count();

    
$proposed_cards=$moveable_cards[$column];
    
$top_card=$proposed_cards[0];

    for(
$i=$column+1;$i<count($moveable_cards);$i++) {
        
$cards=$moveable_cards[$i];
        if (
count($cards)==0) continue;
        if ((
$cards[0]->val()==$top_card->val()) && ($cards[0]->color()==$top_card->color())) {
            
//This card could be moved in place of the other one. What shall we do ?

            //if the proposed will free a column and we don't really need one, better move the other card
            
if ($top_card == $tableau->columns[$column]->get_card(0) && ($nb_empty>|| $tableau->all_kings())) return false

            
//otherwise, move the proposed card if it's closer to the top of the column than the other one
            //if($tableau->columns[$column]->find($top_card) < $tableau->columns[$i]->find($cards[0]))return false;
            
$random_used=true;
            return (
rand(0,10) > 5);

        }
    }
    return 
true;

}


//Check if a card is worth moved
//The only case a card is absolutely not worth moved is when :
//- There are no more hidden cards in the card's column, and
//- All 4 kings are either heading a column or have a free column to be moved on, and
//- The second card with the same value / same color is already visible (on tableau or stacked) or there is a place for it

//If all 3 conditions matches, the card won't be moved



function safe_keep($cards,$column) {

    global 
$tableau;
    global 
$reverse_order;

    if (
$tableau->columns[$column]->find($cards[0]) != 0) return false;
    if (
$cards[0]->color() == 'R') {
        if (
$cards[0]->kind()=='H'$same=new card($reverse_order[$cards[0]->val()].'D');
        else 
$same = new card($reverse_order[$cards[0]->val()].'H');
    } else {
        if (
$cards[0]->kind=='S'$same=new card($reverse_order[$cards[0]->val()].'C');
        else 
$same = new card($reverse_order[$cards[0]->val()].'S');
    }
    
$places $tableau->is_placeable($same);
    if (
$tableau->is_visible($same) && count($places)>1) return false;

    if (!
$tableau->all_kings()) return false;
    
//Card is not worth moved if we reach here
    
return false;
}



//Check if a card is safe to stack
function safe_stack($card,$column) {

    global 
$fondation,$tableau;
    
    global 
$order;
    global 
$reverse_order;

    
//If moving this card means freeing a column for a king, then no hesitation, stack it !
    
if(($column!=-1) && !$tableau->all_kings() && ($tableau->columns[$column]->get_card(0)==$card)) return true;

        if ((
$card->val()==0) || ($card->val()==1)) return true;   //aces and 2 are always ok

    
if ($card->color=='R') {
        
$C1 = new card ($reverse_order[$card->val()-1].'C');
        
$C1stacked $fondation->is_stacked($C1);
        
$C1visible $tableau->is_visible($C1);
        
$S1 = new card($reverse_order[$card->val()-1].'S');
        
$S1stacked $fondation->is_stacked($S1);
        
$S1visible $tableau->is_visible($S1);

        
$C1present $C1stacked || $C1visible;
        
$S1present $S1stacked || $S1visible;

        if (
$C1present && $S1present) return true;

        
$C2 = new card ($reverse_order[$card->val()-2].'C');
        
$C2stacked $fondation->is_stacked($C2);
        
$S2 = new card($reverse_order[$card->val()-2].'S');
        
$S2stacked $fondation->is_stacked($S2);

        if (
$S1present && $C2stacked) return true;
        if (
$C1present && $S2stacked) return true;
        if (
$C2stacked && $S2stacked) return true;
        
    } else {
        
        
$H1=new card($reverse_order[$card->val()-1].'H');
        
$H1stacked $fondation->is_stacked($H1);
        
$H1visible $tableau->is_visible($H1);
        
$D1=new card($reverse_order[$card->val()-1].'D');
        
$D1stacked $fondation->is_stacked($D1);
        
$D1visible $tableau->is_visible($D1);
        
        
$H1present $H1stacked || $H1visible;
        
$D1present $D1stacked || $D1visible;

        if (
$H1present && $D1present) return true;

        
$H2=new card ($reverse_order[$card->val()-2].'H');
        
$H2stacked $fondation->is_stacked($H2);
        
$D2=new card ($reverse_order[$card->val()-2].'D');
        
$D2stacked $fondation->is_stacked($D2);

        if (
$H1present && $D2stacked) return true;
        if (
$D1present && $H2stacked) return true;
        if (
$D2stacked && $H2stacked) return true;
    }

    return 
false;
}


//This function is part of the main algo. It's used to check (and eventually move) the moveable cards from the
//tableau to the tableau.
//Main goal is to uncover hidden cards and/or free cells for kings
//Attempt is made to move a card to the best place if more than one possible choice is given
//Whenever a card is moved, hidden card will be turned face up, score adapted and situation draw to browser
function move()
{

    global 
$tableau;   //We need to access tableau
    
global $score;     //We'll update score


    
$moved=false;      //The function returns this
    
    
$moveable_cards $tableau->get_moveable();  //Get moveable cards from tableau
    
    //uasort($moveable_cards,'sort_cards');  //sort moveable cards by higher value first so they will be moved first

    //Now we will check each rows of moveable cards to find out if they are moveable somewhere else
    
foreach($moveable_cards as $column=>$cards) {   

        if (
count($cards) == 0) continue;     //No reason to move an empty column, so just skip it

  //Get possible new places for the given card. By rules a card can have up to maximum 2 possible positions
        
$valid_columns $tableau->is_placeable($cards[0]);

        if (
count($valid_columns)==0) continue;   //There is no way to move this row of card so skip to next one
        
        //Cards are moveable, let's check if it's safe to keep them nevertheless

        
if (safe_keep($cards,$column)) continue;        //Yes, so skip to next one

        //Let's now check if there is no a better move in this turn   
        
if (!best_move($moveable_cards,$column)) continue;  //Card not moved because it's not the best move

  //Finally, we can try to figure out a best move for this row of cards
        
$n_column best_column($valid_columns,$cards);

  
//And proceed to the actual move
  
Message("Moving cards (".$cards[0]->c_name().") from column $column to column $n_column in tableau");

        
$cards $tableau->remove($cards[0],$column);
        
$tableau->append($cards,$n_column);
        
$moved=true;
        
$score+=($tableau->turn_faceup($column)*5);   //Turn face up and increment score if applicable
        
draw_situation();         //let user know what happened
        
break;            //Once a card has been moved, chance will be given to talon, so we break here
    
}
    return 
$moved;
}


//This function is part of the main algo. It's used to check (and eventually stack) all stackable cards from the
//Tableau to the fondation.
//Main goal is to stack cards in a safe way if $always is false or to score if $always is true
//Whenever a card is stacked, hidden card will be turned face up, score adapted and situation draw to browser
function stack($always)

{
    global 
$tableau,$fondation;   //We need to access tableau and fondation
    
global $score;      //We'll update score

    
$stacked false;       //The function returns this
    
    
$cards $tableau->get_topmost();  //Get stackable cards from tableau

    //Now we'll check each stackable card to find out if they are stackable or not
    
foreach($cards as $column=>$card) {
        if (
$card=='') continue;      //No cards in this column, continue to next one
        
if (!$fondation->is_stackable($card)) continue; //It's not possible to stack this card, so continue to next one 
        
if (!$always && !safe_stack($card,$column)) continue;  //We want to play safe and it's not a safe stack, continue to next one
        //Finally, if all went fine, we can now proceed to stack the card
        
$cards $tableau->remove($card,$column);   //first remove it
        
if (count($cards)!=1) {
            echo(
"We got a serious problem here with ".count($cards)." cards<br>");
        } else {
            
Message("Moving card ".$card->c_name()." from tableau to fondation"); //Then stack it
            
$fondation->stack($cards[0]);
            
$score+=10;
            
$stacked true;
            
$score+=($tableau->turn_faceup($column)*5); //Turn face up and increment score if applicable
            
draw_situation();         //let user know what happened
            //break;
        
}
    }           
    return 
$stacked;
}

//This function is part of the main algo. It's used to check (and eventually move/stack) cards from the 
//talon to either the tableau or the fondation. Priority is given to tableau (to score more).
//stackable cards are evaluated to be safe/unsafe and, if unsafe, will be stacked to score max if $always is true
//Moveable cards are evaluated against moveable cards in tableau and priority is given to cards from tableau if it's not safe
//to keep them
function playfromtalon(&$card,$always,$skip_random)
{
    global 
$stock,$tableau,$fondation;   //We need access to stock, tableau and fondation
    
global $score;       //We'll update score

    
$stock_used=false;       //The function return this
     
    
do {
        
$played=false;

        
$card $stock->CurrentCard();  //Get current card from talon
        
if ($card=='') {      //If no more cards in talon, play 3 cards from stock
            
$card $stock->GetCard(3);
            if (
$card!=''Message('Playing 3 cards from stock ->'.$card->c_name());
        } 

        if (
$card!='') {      //If we have a card to play with, go on (We may have reach the end of the stock)
            
$valid_columns $tableau->is_placeable($card);
            if (
count($valid_columns)>0) {  //Check if card is placeable to tableau

                //Let's check if the card we want to play from talon is not available from the tableau
                
$moveable_cards=$tableau->get_moveable();
                foreach(
$moveable_cards as $column=>$moveable_card) {
                    
//If it's the case, *maybe* we are better to play card from tableau
                    
if (count($moveable_card)==0) continue; //No card to move in this column
                    
if (($card->val() == $moveable_card[0]->val()) && ($card->color()==$moveable_card[0]->color())) {
                        if (!
safe_keep($moveable_card,$column)) break 2;
                    }
                }
                if (
$skip_random && $stock_used) {  //We will randomly skip this card if we played at least one before
                    
if ($card->symbol() != 'K' && $card->symbol()!='1') {  //We won't skip a king or an ace !!
                        
if (rand(0,10)>6) {
                            
$stock->GetCard(3);
                            break;
                        }
                    }
                }
                
$n_column best_column($valid_columns,array($card));
                
$stock->UseCard(); //Yes, take card from stock
                
$card->set_visible(true);
                
$tableau->append(array($card),$n_column); //And move it into tableau
                
$score+=5;
                
$stock_used=true;
                
Message("moving card ".$card->c_name()." from stock to tableau, column ".$n_column);
                
$played=true;
            } else if (
$fondation->is_stackable($card)) { //Check if card from stock is stackable
                
if (!$always && !safe_stack($card,-1)) break;    //Not a safe stack -> don't stack
                
$stock->UseCard(); //Yes, take card from stock
                
$card->set_visible(true);
                
$fondation->stack($card); //And stack it on fondation
                
$score+=10;
                
$stock_used=true;
                
$played=true;
                
Message("moving card ".$card->c_name()." from stock to fondation");
            } else 
$played=false;
            if (
$playeddraw_situation();
        }
    } while (
$played && $card!='');
    return 
$stock_used;
}



function 
sort_cards($a,$b)
{
    global 
$order;


    if (
count($a)==|| count($b)==0) return (count($a)==0);

    if (
$a[0]->val()==$b[0]->val()) return 0;
    return (
$a[0]->val() > $b[0]->val()) ? -1;

}


$try 0;               //current tries counter
$random_used=false;     //Will be set if random moves have been used in any try

$time_start getmicrotime(); //Everything is set up, now we can start the timer


do {                    //main loop for tries
    
ob_start();         //Each try will be stored in its own output_buffer

    
$stock = new stock($stockfilename);
    
Message("Initializing Tableau");
    
$tableau = new tableau($stock);
    
$fondation = new fondation();
    
    
$score=0;           //score is 0
    
$loop=0;            //we can use up to 3 passes through the stock
    
$stock_used=false;  //
    
    
draw_situation();

    do {                 
//Do a pass through the stock
        
do {             //Keep playing to stack and to tableau till no move are available without a new card from stock
            
playfromtalon($card,$loop>=1,$try>2);

            
//Let's check if any cards is stackable on the fondation
            
do {
                
$stacked stack($loop>=($try%3));
                
//$stacked = stack(true);
                
if ($stackedplayfromtalon($card,$loop>=1,$try>2);
            } while (
$stacked && ($loop<$try%3));     
            
            
//Let's also check if any cards are moveable in the tableau
            
$moved move();
            if (
$movedplayfromtalon($card,$loop>=1,$try>2);
 
        } while (
$stacked || $moved);  //loop until nothing is moveable nor stackable

        
do {                           //Play a new card from stock
            
$card $stock->GetCard(3); 
            if (
$card!=''Message("Playing 3 cards from stock ".$card->c_name());
            if (
$card!=''$stock_used=playfromtalon($card,$loop>=1,$try>2);
    
        } while (!
$stock_used && $card!='');    //And loop till at least one card has been used (or stock is over)
        
if ($card=='') {                        //Stock is over :
            
$loop++;                            //Let's start a new pass through the stock
            
$remains $stock->DrawOver();
            if (
$loop<3Message("<font color="green">drawing stock over ($remains card remain) / pass n� ".($loop+1)."</font>"); 
        }

    } while(
$loop<3);                           //No more than 3 passes allowed

 
    //Stock is over or maximum passes is reached, let's move and stack until we can't do anything. 
    
do {    
        
//Let's check if any cards is stackable on the fondation
        
$stacked stack(true);
        
//Let's also check if any cards are moveable in the tableau
        
$moved move();
    } while (
$stacked || $moved);

    
//Game Over
    
Message("<font color="red" size=+1>Game Over ! Score = $score in ".$stock->turn." turns</font>");
 
    
//Store this try's score
    
$scores[$try]=$score;   
    
//And store the output_buffer
    
$display[$try] = ob_get_contents();
    
    
ob_end_clean();  //Start with a fresh output_buffer

    //Uncomment next line if you want to know the score for each tries
    //echo("$try : $score, "); if ($random_used) echo("<b>Random Used</b>"); echo("<br>");
    
    //Now check if it's worth running more tries (ie, is the fondation full)
    
if ($fondation->is_full()) break;
    
    
$try++;
} while (
$try<|| ($random_used && ($try<12)));  //If random move went to work, let's make a few more tries (2 times more)

//all tries processed, time to stop the timer
$time_end getmicrotime();
$time $time_end $time_start;

//Let's find out which tries gave the best score
$maxscore =0;
foreach(
$scores as $try=>$score
    if (
$score>$maxscore) {$maxscore=$score;$bestry $try;}
    
//And display that try
echo($display[$bestry]);
echo(
"<br><b>".$time." second</b>");
?>


Usage Example


See the example


Rate This Script





Search



This Category All Categories