/* Solitaire program 
   H. Conrad Cunningham
   Version #0: 25 Feb 2010

   This Scala program is adapted from a similar Java program written
   by Tim Budd in 1996-97 to accompany his textbook Understanding
   Object-Oriented Program with Java.

   WARNING: This code has not been tested.  The GUI code is not
   currently working.

123456789012345678901234567890123456789012345678901234567890123456789012345678

   Significant changes to design and implementation:
   * uses scala.collection.mutable.ListBuffer[Card] as the concrete
     data structure to replace the old, non-generic java.util.Stack 
     (which used oddly in the Java code)
   * uses scala.util.Random instead of java.util.Random   
   * restructured some logic to remove some of the nested returns and
     one one unnecessary use of an exception mechanism
   * pulled main method into separate TestSolitare object to enable it 
     to be found

*/

// old Java GUI components
import java.awt._
import java.awt.event._

// Internal data structure for card piles
// import java.util.Stack
// import java.util.EmptyStackException
import scala.collection.mutable.ListBuffer

// Old Java Random number generator usage switched to one in Scala API
// import java.util.Random
import scala.util.Random

// Old Java Enumeration interface no longer needed,
//   used Scala "for" to iterate through ListBuffer 
// import java.util.Enumeration


/* 
   Companion object for the Card abstraction.  

   The static attributes of the old Java Card class are collected here
   as public vals.  Note the references now have to be to "Card.att"
   rather than just "att" in the class Card code.

*/

object Card {

  // card dimensions
  val width:  Int  = 50
  val height: Int  = 70

  // card suits
  val heart: Int   = 0
  val spade: Int   = 1
  val diamond: Int = 2
  val club: Int    = 3
}


/*
   Class Card specifies the playing card abstraction for the Solitaire
   game.
*/

class Card(s: Int, r: Int) {

  private var faceup: Boolean = false

  // accessors
  def rank: Int       = r
  def suit: Int       = s
  def faceUp: Boolean = faceup

  // mutators
  def flip  { faceup = ! faceup }

  def color: Color = {
    if (faceUp) {
      if (suit == Card.heart || suit == Card.diamond)
        Color.red
      else
        Color.black
    }
    else 
      Color.yellow
  }

  def draw (g: Graphics, x: Int, y: Int) {
    val names = Array("A", "2", "3", "4", "5", "6", "7", "8", "9", "10", 
                      "J", "Q", "K")

    // clear rectangle, draw border
    g.clearRect(x, y, Card.width, Card.height)
    g.setColor(Color.blue)
    g.drawRect(x, y, Card.width, Card.height)

    // draw body of card
    g.setColor(color)
    if (faceUp) {
      g.drawString(names(rank), x+3, y+15)
      if (suit == Card.heart) {
        g.drawLine(x+25, y+30, x+35, y+20)
        g.drawLine(x+35, y+20, x+45, y+30)
        g.drawLine(x+45, y+30, x+25, y+60)
        g.drawLine(x+25, y+60, x+5,  y+30)
        g.drawLine(x+5,  y+30, x+15, y+20)
        g.drawLine(x+15, y+20, x+25, y+30)
      }
      else if (suit == Card.spade) {
        g.drawLine(x+25, y+20, x+40, y+50)
        g.drawLine(x+40, y+50, x+10, y+50)
        g.drawLine(x+10, y+50, x+25, y+20)
        g.drawLine(x+23, y+45, x+20, y+60)
        g.drawLine(x+20, y+60, x+30, y+60)
        g.drawLine(x+30, y+60, x+27, y+45)
      }
      else if (suit == Card.diamond) {
        g.drawLine(x+25, y+20, x+40, y+40)
        g.drawLine(x+40, y+40, x+25, y+60)
        g.drawLine(x+25, y+60, x+10, y+40)
        g.drawLine(x+10, y+40, x+25, y+20)
      }
      else if (suit == Card.club) {
        g.drawOval(x+20, y+25, 10, 10)
        g.drawOval(x+25, y+35, 10, 10)
        g.drawOval(x+15, y+35, 10, 10)
        g.drawLine(x+23, y+45, x+20, y+55)
        g.drawLine(x+20, y+55, x+30, y+55)
        g.drawLine(x+30, y+55, x+27, y+45)
      }
    }
    else { // face down
      g.drawLine(x+15, y+5, x+15, y+65)
      g.drawLine(x+35, y+5, x+35, y+65)
      g.drawLine(x+5, y+20, x+45, y+20)
      g.drawLine(x+5, y+35, x+45, y+35)
      g.drawLine(x+5, y+50, x+45, y+50)
    }
  }

}


/* 
   Class CardPile specifies the abstraction for piles of cards for
   Solitaire game.
*/

class CardPile(xl: Int, yl: Int) {

  // coordinates of the card pile
  protected val x: Int  = xl
  protected val y: Int  = yl

  // data structure to hold the card pile
  protected val thePile = new ListBuffer[Card]

  // access to cards are not overridden

  final def top: Card        = thePile.last
  final def isEmpty: Boolean = thePile.isEmpty

  final def pop: Card =  if (thePile.isEmpty)
                           null
                         else {
                           val theTop = top
                           thePile.trimEnd(1)
                           theTop
                         }

  // The following mehtods are sometimes overridden, but default
  //  behaviors are specified.

  def includes (tx: Int, ty: Int): Boolean =
    x <= tx && tx <= x + Card.width && y <= ty && ty <= y + Card.height
	
  def select (tx: Int, ty: Int) {  }  // do nothing

  def addCard (aCard: Card) { thePile += aCard }

  def display (g: Graphics) {
    g.setColor(Color.blue)
    if (isEmpty)
       g.drawRect(x, y, Card.width, Card.height)
    else
      top.draw(g, x, y)
  }

  def canTake (aCard: Card): Boolean = false

}


/* 
   Class SuitPile ...
*/

class SuitPile(x: Int, y: Int) extends CardPile(x,y) {

  override def canTake(aCard: Card): Boolean = {
    if (isEmpty) 
      aCard.rank == 0
    else {
      val topCard = top
      (aCard.suit == topCard.suit) && (aCard.rank == 1 + topCard.rank)
    }
  }

}


/* 
   Class DeckPile ...
*/

class DeckPile(x: Int, y: Int) extends CardPile(x,y) {

  // first initialize parent
  for (i <- 0 to 4; j <- 0 to 12)
    addCard(new Card(i, j))

  // then shuffle the cards
  val generator = new Random
  for (i <- 0 to 52) {
    val j = Math.abs(generator.nextInt % 52)
    // swap the two card values
    val temp   = thePile(i)
    thePile(i) = thePile(j)
    thePile(j) = temp
  }

  override def select(tx: Int, ty: Int) {
    if (!isEmpty) 
      Solitare.discardPile.addCard(pop)
  }

}


/* 
   Class DiscardPile ...
*/

class DiscardPile(x: Int, y: Int) extends CardPile(x,y) {
	
  override def addCard (aCard: Card) {
    if (!aCard.faceUp) 
      aCard.flip
    super.addCard(aCard)
  }

  override def select (tx: Int, ty: Int) {
    if (isEmpty)
      return
    val topCard = pop
    for (i <- 0 to 4)
      if (Solitare.suitPile(i).canTake(topCard)) {
        Solitare.suitPile(i).addCard(topCard)
        return
       }
    for (i <- 0 to 7)
      if (Solitare.tableau(i).canTake(topCard)) {
        Solitare.tableau(i).addCard(topCard)
        return
      }
    // nobody can use it, put it back on our list
    addCard(topCard)
  }
}


/* 
   Class TablePile ...
*/


class TablePile(x: Int, y: Int, c: Int) extends CardPile(x,y) {

  for (i <- 0 to c) 
    addCard(Solitare.deckPile.pop)

  // flip topmost card face up
  top.flip

  override def canTake(aCard: Card): Boolean = {
    if (isEmpty)
      aCard.rank == 12
    else {
      val topCard = top
      (aCard.color != topCard.color) && (aCard.rank == topCard.rank - 1)
    }
  }

  override def includes (tx: Int, ty: Int): Boolean = 
    x <= tx && tx <= x + Card.width  && y <= ty
    // don't test bottom of card

  override def select(tx: Int, ty: Int) {
    if (isEmpty)
      return

    // if face down, then flip
    var topCard = top
    if (! topCard.faceUp) {
      topCard.flip
      return
    }
 
    // else see if any suit pile can take card
    topCard = pop
    for (i <- 0 to 4)
      if (Solitare.suitPile(i).canTake(topCard)) {
        Solitare.suitPile(i).addCard(topCard)
        return
      }

    // else see if any other table pile can take card
    for (i <- 0 to 7) 
      if (Solitare.tableau(i).canTake(topCard)) {
        Solitare.tableau(i).addCard(topCard)
        return
      }

     // else put it back on our pile
     addCard(topCard)
  }

  override def display (g: Graphics) {
    var localy = y
    for (aCard <- thePile) {  // check out whether order is correct???
      aCard.draw(g, x, localy)
      localy += 35
    }
  }
}


/* 
   Companion object for Solitare game class
*/

object Solitare {

  var deckPile:    DeckPile         = null
  var discardPile: DiscardPile      = null
  var tableau:     Array[TablePile] = null
  var suitPile:    Array[SuitPile]  = null
  var allPiles:    Array[CardPile]  = null

}


/* 
   Companion object for Solitare game class
*/

class Solitare  {

  val window: Frame = new SolitareFrame

  init
  window.show

  def init  {
    // first, allocate the arrays
    Solitare.allPiles = new Array[CardPile](13)
    Solitare.suitPile = new Array[SuitPile](4)
    Solitare.tableau  = new Array[TablePile](7)

    // then fill them in
    Solitare.deckPile    = new DeckPile(335, 30)
    Solitare.allPiles(0) = Solitare.deckPile 

    Solitare.discardPile = new DiscardPile(268, 30)
    Solitare.allPiles(1) = Solitare.discardPile 

    for (i <- 0 to 3) {
      Solitare.suitPile(i)   = new SuitPile(15 + (Card.width+10) * i, 30)
      Solitare.allPiles(2+i) = Solitare.suitPile(i)
    }
    
    for (i <- 0 to 6) {
      Solitare.tableau(i) = 
        new TablePile(15+(Card.width+5)*i, Card.height+35, i+1)
      Solitare.allPiles(6+i) = Solitare.tableau(i)
    }

  }
    private class SolitareFrame extends Frame {

      setSize(600, 500)
      setTitle("Solitaire Game")
      addMouseListener (new MouseKeeper)
      val restartButton = new Button("New Game")
      restartButton.addActionListener(new RestartButtonListener)
      add("South", restartButton)

      private class RestartButtonListener extends ActionListener {
        override def actionPerformed (e: ActionEvent) {
          init
          window.repaint
        }
      }

      private class MouseKeeper extends MouseAdapter {
        override def mousePressed (e: MouseEvent) { 
          val x = e.getX
          val y = e.getY
          for (i <- 0 to 13) {
            if (Solitare.allPiles(i).includes(x, y)) {
              Solitare.allPiles(i).select(x, y)
              repaint
            }
          }
      }

      def paint(g: Graphics) {
        for (i <- 0 to 13) 
          Solitare.allPiles(i).display(g)
      }
    }
  }

}


/* 
   Object to test game as Scala application
*/

object TestSolitare {

  def main (args: Array[String]) {
    val world = new Solitare
    println("Done")
  }

}
