//*********************** Copy right information ************************* // FAQ's // Q. Is this program copy right protected? // Ans. No. Even if it was you will copy it anyway so what is the point //************************************************************************ // Program: Free Cell card game // Programmer: H K Gupta Date: 25 Oct1997 //************************************************************************ import java.awt.*; import java.awt.image.*; import java.applet.Applet; public class FreeCell extends Applet implements Runnable{ Panel pTop; Panel pTopLeft; Button bNewGame; Button bUndo; Button bHelp; Label lTitle; Button bSingleCard; FreeCellPlayArea playArea; Label results; HridayeshCardDeck deck; // card deck for playing the game boolean flagMoveSingleCard = false; Thread runThread = null; boolean firstTime = true; boolean pauseInEffect = false; public void init() { } public void start() { if (firstTime) { deck = new HridayeshCardDeck(); // get a new card deck setUpDisplay() ; results.setText("*F*R*E*E* *C*E*L*L*"); firstTime = false; } if (runThread == null) { if(!pauseInEffect) { runThread = new Thread(this, "FreeCell"); runThread.start(); } } } public void run() { while (Thread.currentThread() == runThread) { if(playArea.moveInProgress) { playArea.move(); } else { if(playArea.autoInProgress) { playArea.moveAuto(); } else { if(playArea.undoInProgress) { playArea.moveUndo(); } } } if(!gameOnDisplay) playArea.doGameOver(); try { Thread.sleep(40); } catch (InterruptedException e){} } } public void stop() { runThread = null; } void setUpDisplay() { setLayout(new BorderLayout()); pTop = new Panel(); pTop.setLayout(new BorderLayout()); add("North", pTop); pTopLeft = new Panel(); pTop.add("West", pTopLeft); bNewGame = new Button("New Game"); pTopLeft.add("West", bNewGame); bUndo = new Button("undo F10"); pTopLeft.add("West", bUndo); bHelp = new Button("Help!!!"); pTopLeft.add("West", bHelp); lTitle = new Label("FreeCell by HRIDAYESH", Label.CENTER); pTop.add("Center", lTitle); bSingleCard = new Button("Move Multiple Cards"); pTop.add("East", bSingleCard); playArea = new FreeCellPlayArea(this); add("Center", playArea); results = new Label("Loading Card Images", Label.CENTER); add("South", results); validate(); } boolean gameOnDisplay = false; public boolean action(Event e, Object arg) { Object target = e.target; if(target == bNewGame) { if(playArea.autoInProgress || playArea.undoInProgress || playArea.moveInProgress) return true; playArea.flagMoveStored = false; if(gameOnDisplay) updateScore(false); gameOnDisplay = true; playArea.dealHand(); return true; } if(target == bSingleCard) { if(flagMoveSingleCard) { bSingleCard.setLabel("Move multiple cards"); } else { bSingleCard.setLabel("Move single card"); } flagMoveSingleCard = !flagMoveSingleCard; } if(target == bUndo) return playArea.undo(); if(target == bHelp) { processHelp(); return true;} return false; } public boolean keyDown(Event e, int key) { if(key == Event.F10) return playArea.undo(); return false; } int gamesPlayed = 0; // statistics variables int gamesWon = 0; int gamesLostInARow = 0; int gamesWonInARow = 0; int gamesCurrentStreak = 0; void updateScore(boolean wonFlag) { gamesPlayed++; if(wonFlag) { gamesWon++; if(gamesCurrentStreak >= 0) { gamesCurrentStreak++; } else { gamesCurrentStreak = 1; } } else { if(gamesCurrentStreak <= 0) { gamesCurrentStreak--; } else { gamesCurrentStreak = -1;} } if(gamesCurrentStreak > gamesWonInARow) gamesWonInARow = gamesCurrentStreak; if(gamesCurrentStreak < 0) { if((- gamesCurrentStreak) > gamesLostInARow) gamesLostInARow = - gamesCurrentStreak; } dispScore(); } void dispScore() { results.setText("Stats:: Total: " + gamesPlayed + " Won: " + gamesWon + " Winning Streak: " + gamesWonInARow + " Losing Streak: " + gamesLostInARow + " Current: " + gamesCurrentStreak); } private void processHelp() { HridayeshHelp window = null; try { window = new HridayeshHelp(); } catch (Exception e) { bHelp.setLabel("Error"); bHelp.disable(); return; } window.setTitle("FreeCellHelp"); window.setText( "\n Free Cell Help \n\n" + "\nObjective of the game is to bring all cards to four right hand top" + "\ncorner spots. You have four temporary spots on the left top corner" + "\nto move cards.\n"+ "\n When you click (using mouse) on any card it becomes selected" + "\nand if you click on any valid spot, it will move automatically along" + "\nwith the entire suit of cards to destination. Any card which can go" + "\nto right top corner without disturbing the game is also moved " + "\nautomatically to save you mouse clicks.\n" + "\nValid moves: 1. Any card can be moved to an empty spot," + "\n 2. On right top corner spots only an Ace can be placed " + "\n and subsequently the immediate higher card of the same" + "\n suite if the spot is already occupied." + "\n 3. On all other occupied spots you can move a card" + "\n immediately lower than the current card but of opposite" + "\n colour." + "\n 4. You can also move a series of cards. For example you can" + "\n move (red 7, black 6, red 5, black 4 to black 8 or empty" + "\n spot in regular play area if that move is possible by " + "\n moving individual cards.\n" + "\nI hope you can make some sense out of the above because I can't. " ); window.pack(); window.show(); } } class FreeCellPlayArea extends Canvas { private FreeCell controller; private int nPlayStacks = 8; private HridayeshLoadCards deckImages; int flagPaint = -1; FreeCellStack[] stack; final int typeTemp = 0; final int typeDone = 1; final int typePlay = 2; HridayeshCardShow drawOver; public FreeCellPlayArea(FreeCell controller) { super(); this.controller = controller; deckImages = new HridayeshLoadCards((Applet)controller); drawOver = new HridayeshCardShow(deckImages); stack = new FreeCellStack[16]; for(int i = 0; i < 16; i++) stack[i] = new FreeCellStack(this, i, deckImages.getCardW(), deckImages.getCardH()); moveArray = new FreeCellStack[100][2]; displayReset(); try { moveCursor = new Cursor(Cursor.MOVE_CURSOR); defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR); } catch (NoSuchMethodError e) {} catch (SecurityException s) {} } private void displayReset() { for(int i = 0; i < 16; i++) stack[i].reset(); fcsSelected = null; resetProgressFlags(); flagPaint = -1; } public void resetProgressFlags() { moveInProgress = false; autoInProgress = false; undoInProgress = false; } public void dealHand() { displayReset(); int card; controller.deck.reset(); for(int k = 0; k < 10; k++) { for(int i = 0; i < nPlayStacks; i++) { card = controller.deck.newCard(); if(card < 0) { flagPaint = -1; repaint(); return; } stack[i].addCard(card); } } } private boolean flagSetOffsets = true; public void update(Graphics g) { if(flagSetOffsets) { for(int i = 0; i < 16; i++) stack[i].setOffsets(g); flagSetOffsets = false; } if(flagPaint == 10) return; // some painting in progress int tmp = flagPaint; flagPaint = 10; // prevent others from issuing a repaint(); switch(tmp) { case -1: case 0: paintEveryThing(g); break; case 2: if(fcsSelected != null) { fcsSelected.paintSelection(g); } break; case 3: if(fcsDeselected != null) { fcsDeselected.drawTopCard(g); } break; case 4: paintMove(g); break; case 5: moveAutoCard(g); break; case 6: paintUndo(g); break; case 11: drawOver.draw(g, this); break; default: } flagPaint = -1; } boolean flagMoveStored = false; void paintUndo(Graphics g) { if(!undoInProgress) return; if(fcsSelected != null) { fcsSelected.drawTopCard(g); fcsSelected = null; } moveCardFromSrcToDst(g, moveArray[moveIndex][1], moveArray[moveIndex][0]); moveIndex--; if(moveIndex < 0) { undoInProgress = false; flagMoveStored = false; flagPaint = -1; } } void moveAutoCard(Graphics g) { int lowSuitNo = 13; for(int i = 12; i < 16; i++) { if(suitNo(stack[i].topCard()) < lowSuitNo) lowSuitNo = suitNo(stack[i].topCard()); } lowSuitNo++; for(int i = 0; i < 12; i++) { int card = stack[i].topCard(); if(card != -1) { if(suitNo(card) == lowSuitNo) { for(int j = 12; j < 16; j++) { if(stack[j].isEmpty()) { putAndMoveCard(g, stack[i], stack[j]); return; } if((int)(card / 13) == (int)(stack[j].topCard() / 13)) { putAndMoveCard(g, stack[i], stack[j]); return; } } } else { if(suitNo(card) == (lowSuitNo + 1)) { for(int j = 12; j < 16; j++) { if(!stack[j].isEmpty()) { if((suitNo(card) == (suitNo(stack[j].topCard()) + 1)) && ((card / 13) == (stack[j].topCard() / 13))) { putAndMoveCard(g, stack[i], stack[j]); return; } } } } } } } autoInProgress = false; moveIndex = totalMoves - 1; flagMoveStored = true; if(gameOver()) { controller.updateScore(true); controller.gameOnDisplay = false; flagMoveStored = false; } if(gameLost()) { controller.results.setText("YOU HAVE LOST THE GAME. NO POSSIBLE MOVE"); } } void putAndMoveCard(Graphics g, FreeCellStack fcsSrc, FreeCellStack fcsDst) { moveCardFromSrcToDst(g, fcsSrc, fcsDst); moveArray[totalMoves][0] = fcsSrc; moveArray[totalMoves][1] = fcsDst; totalMoves++; } FreeCellStack fcsDeselected = null; FreeCellStack moveTarget = null; FreeCellStack moveColumn; int moveCount; FreeCellStack[][] moveArray; int totalCardsToMove; int moveIndex; int totalMoves; void paintMove(Graphics g) { if(!moveInProgress) return; moveCardFromSrcToDst(g, moveArray[moveIndex][0], moveArray[moveIndex][1]); moveIndex++; if(moveIndex >= totalMoves) { moveInProgress = false; autoInProgress = true; flagPaint = -1; } } void moveCardFromSrcToDst(Graphics g, FreeCellStack fcsSrc, FreeCellStack fcsDst) { int card = fcsSrc.removeCard(g); fcsDst.putCard(g, card); } void paintEveryThing(Graphics g) { Dimension d = size(); g.setColor(Color.green); g.fillRect(0, 0, d.width, d.height); for(int i = 0; i < 16; i++) stack[i].drawStack(g); if(fcsSelected != null) fcsSelected.paintSelection(g); } void drawSelectionImage(Graphics g, int x, int y) { g.setColor(Color.blue); for(int i = 0; i < 5; i++) { g.drawRect(x + 10 + i * 5, y + 10 + i * 5, (53 - i * 10), (77 - i * 10)); } } void paintCard(Graphics g, boolean drawFlag, int x, int y, int cardNo) { if(drawFlag && (cardNo >= 0)) { g.drawImage(deckImages.getCardImage(cardNo), x, y, this); } else { g.setColor(Color.green); g.fillRect(x, y, deckImages.getCardW(), deckImages.getCardH()); if(y < deckImages.getCardH()) { g.setColor(Color.black); g.drawRect(x, y, deckImages.getCardW(), deckImages.getCardH()); } } } private FreeCellStack fcsSelected = null; boolean autoInProgress = false; boolean undoInProgress = false; boolean moveInProgress = false; private int oldX = -1; private int oldY = -1; public boolean mouseDown(Event event, int x, int y) { if(autoInProgress || undoInProgress || moveInProgress) return false; if(flagPaint != -1) return false; for(int i = 0; i < 16; i++) { if(stack[i].isInside(x, y)) { if(((oldX - x) * (oldX - x) + (oldY - y) * (oldY - y)) < 1) { if(fcsSelected == stack[i]) { // roundabout way of looking for double click if(moveDoubleClick()) { oldX = x; oldY = y; return true; } } } oldX = x; oldY = y; return columnSelect(stack[i]); } } oldX = x; oldY = y; return false; } Cursor moveCursor; Cursor defaultCursor; public boolean mouseMove(Event event, int x, int y) { setMyCursor(defaultCursor); if(autoInProgress || undoInProgress || moveInProgress) return false; if(flagPaint != -1) return false; if(fcsSelected == null) return false; if(!controller.gameOnDisplay) return false; for(int i = 0; i < 16; i++) { if(stack[i].isInside(x, y)) { if(isMovePossible(fcsSelected, stack[i])) { setMyCursor(moveCursor); return true; } } } setMyCursor(defaultCursor); return false; } void setMyCursor(Cursor cursor) { try { setCursor(cursor); } catch (NoSuchMethodError e) {} catch (SecurityException s) {} } boolean columnSelect(FreeCellStack fcsDst) { if(fcsSelected == fcsDst) { flagPaint = 3; fcsDeselected = fcsSelected; fcsSelected = null; repaint(); return true; } switch(fcsDst.getType()) { case typeTemp: if(fcsSelected == null) { if(fcsDst.isEmpty()) return true; fcsSelected = fcsDst; flagPaint = 2; repaint(); return true; } if (isMovePossible(fcsSelected, fcsDst)) return setMove(fcsDst); break; case typeDone: if (isMovePossible(fcsSelected, fcsDst)) return setMove(fcsDst); break; case typePlay: if(fcsSelected == null) { if(fcsDst.isEmpty()) return true; fcsSelected = fcsDst; flagPaint = 2; repaint(); return true; } if(isMovePossible(fcsSelected, fcsDst)) return setMove(fcsDst); break; default: } return true; } boolean moveDoubleClick() { for(int i = 12; i < 16; i++) { if(isMovePossible(fcsSelected, stack[i])) return setMove(stack[i]); } for(int i = 8; i < 12; i++) { if(stack[i].isEmpty()) { if(isMovePossible(fcsSelected, stack[i])) return setMove(stack[i]); } } return false; } boolean setMove(FreeCellStack fcsDst) { flagMoveStored = false; autoInProgress = false; moveInProgress = true; moveTarget = fcsDst; totalMoves = 1; moveArray[0][0] = fcsSelected; moveArray[0][1] = moveTarget; if(totalCardsToMove > 1) { moveIndex = -1; int cardsRemaining = totalCardsToMove; cardsRemaining = fillTemp(fcsSelected, cardsRemaining); if(cardsRemaining > 1) { fillOne(fcsSelected, moveColumn); cardsRemaining--; int cardsOnMiddleColumn = totalCardsToMove - cardsRemaining; if(cardsRemaining > 1) { clearTemp(moveColumn); cardsRemaining = fillTemp(fcsSelected, cardsRemaining); fillOne(fcsSelected, moveTarget); clearTemp(moveTarget); fillTemp(moveColumn, cardsOnMiddleColumn); } else { fillOne(fcsSelected, moveTarget); } fillOne(moveColumn, moveTarget); } else { fillOne(fcsSelected, moveTarget); } clearTemp(moveTarget); totalMoves = moveIndex + 1; } moveIndex = 0; fcsSelected = null; return true; } int[] fillArray = {0, 0, 0, 0}; int fillTemp(FreeCellStack fcsSrc, int count) { for(int i = 0; i < 4; i ++) fillArray[i] = -1; for(int i = 0; i < 4; i ++) { if(count < 2) return count; if(stack[i + nPlayStacks].isEmpty()) { fillArray[i] = 0; fillOne(fcsSrc, stack[i + nPlayStacks]); count--; } } return count; } void clearTemp(FreeCellStack fcsDst) { for(int i = 3; i >= 0; i--) { if(fillArray[i] != -1) fillOne(stack[i + nPlayStacks], fcsDst); } } void fillOne(FreeCellStack fcsSrc, FreeCellStack fcsDst) { moveIndex++; moveArray[moveIndex][0] = fcsSrc; moveArray[moveIndex][1] = fcsDst; } boolean isMovePossible(FreeCellStack fcsSrc, FreeCellStack fcsDst) { if(fcsSrc == null) return false; if(fcsSrc == fcsDst) return false; // no move on self if(fcsSrc.getType() == typeDone) return false; // no move from doneStack totalCardsToMove = 1; if(fcsDst.getType() == typePlay) { int dstTop = fcsDst.topCard(); if(dstTop == -1) { if(fcsSrc.getType() != typePlay) return true; int movableCards = countMovableCards(fcsSrc, fcsDst); totalCardsToMove = fcsSrc.countMovableSuit(); if(controller.flagMoveSingleCard) totalCardsToMove = 1; if(totalCardsToMove > movableCards) totalCardsToMove = movableCards; return true; } int srcTop = fcsSrc.topCard(); if(suitNo(dstTop) <= suitNo(srcTop)) return false; if(fcsSrc.getType() == typePlay) { int cardsToMove = suitNo(dstTop) - suitNo(srcTop); if(fcsSrc.getNCards() < cardsToMove) return false; int prevCard = dstTop; for(int i = fcsSrc.getNCards() - cardsToMove; i < fcsSrc.getNCards(); i++) { if(!isCardAllowed(prevCard, fcsSrc.getIthCard(i))) return false; prevCard = fcsSrc.getIthCard(i); } int movableCards = countMovableCards(fcsSrc, fcsDst); totalCardsToMove = cardsToMove; if(cardsToMove <= movableCards) return true; } else { if(isCardAllowed(dstTop, srcTop)) return true; } } else { if(fcsDst.getType() != typeDone) { if(fcsDst.isEmpty()) return true; } else { int cardS = fcsSrc.topCard(); if(cardS == -1) return false; // source is empty if(fcsDst.isEmpty()) { // empty if((cardS % 13) == 12) return true; return false; } if((cardS / 13) == (fcsDst.topCard() / 13)) { if(suitNo(cardS) == (suitNo(fcsDst.topCard()) + 1)) { return true; } } } } return false; } int countMovableCards(FreeCellStack fcsSrc, FreeCellStack fcsDst) { int movableCards = 1; for(int i = 8; i < 12; i++) { if(stack[i].isEmpty()) movableCards++; } boolean flagBlank = false; for(int i = 0; i < 8; i++) { if(stack[i].isEmpty() && (fcsSrc != stack[i]) && (fcsDst != stack[i])) { flagBlank = true; moveColumn = stack[i]; } } if(flagBlank) movableCards = movableCards * 2; return movableCards; } boolean isCardAllowed(int high, int low) { if(suitColor(high) == suitColor(low)) return false; if(suitNo(high) == (suitNo(low) + 1)) return true; return false; } int suitNo(int card) { if(card < 0) return -2; int suitNum = card % 13; if(suitNum == 12) suitNum = -1; return suitNum; } int suitColor(int card) { int clr = card / 13; if((clr == 0) || (clr == 3)) return 0; return 1; } void move() { if(flagPaint != -1) return; flagPaint = 4; repaint(); } void moveAuto() { if(flagPaint != -1) return; flagPaint = 5; repaint(); } void moveUndo() { if(flagPaint != -1) return; flagPaint = 6; repaint(); } boolean gameOver() { for(int i = 0; i < 12; i++) { if(!stack[i].isEmpty()) return false; } return true; } boolean undo() { if(flagMoveStored) { flagMoveStored = false; controller.dispScore(); undoInProgress = true; } return true; } boolean gameLost() { for(int i = 0; i < 12; i++) { for(int j = 0; j < 16; j++) { if(i != j) { if(isMovePossible(stack[i], stack[j])) return false; } } } return true; } public void paint(Graphics g) { if(flagPaint != -1) return; update(g); } public void doGameOver() { if(flagPaint != -1) return; flagPaint = 11; repaint(); } } class FreeCellStack { private int crdW, crdH; private int nCards; // No of cards on stack private int[] stack; private int type; private int index; private FreeCellPlayArea parent; public FreeCellStack(FreeCellPlayArea parent, int index, int crdW, int crdH) { this.parent = parent; this.index = index; if(index < 8) { type = parent.typePlay; } else { if(index < 12) { type = parent.typeTemp; } else { type = parent.typeDone; } } this.crdW = crdW; this.crdH = crdH; stack = new int[21]; reset(); } public void reset() { for(int i = 0; i < 21; i++) stack[i] = -1; nCards = 0; } public int topCard() { if(nCards < 1) return -1; return stack[nCards - 1]; } public boolean isEmpty() { if(nCards == 0) return true; return false; } private int xOff = 0; private int yOff = 0; public void setOffsets(Graphics g) { Dimension d = parent.size(); switch(type) { case parent.typeTemp: yOff = 0; xOff = (index - 8) * crdW; break; case parent.typeDone: yOff = 0; xOff = d.width - (4 - (index - 12)) * crdW; break; default: yOff = crdH + 3; int xGap = (d.width - 8 * crdW) / 9; xOff = xGap + index * (xGap + crdW); } } public boolean isInside(int x, int y) { if((x < xOff) || ( x > (xOff + crdW))) return false; if(y < yOff) return false; if((type != 2) && (y > (yOff + crdH))) return false; return true; } public int countMovableSuit() { if(nCards <= 1) return nCards; int cnt = 1; for(int i = nCards - 1; i > 0; i--) { if(!parent.isCardAllowed(stack[i - 1], stack[i])) return cnt; cnt++; } return cnt; } void addCard(int card) { switch(type) { case parent.typeTemp: nCards = 1; break; case parent.typeDone: nCards = 1; break; case parent.typePlay: nCards++; default: } stack[nCards - 1] = card; } void putCard(Graphics g, int card) { addCard(card); drawTopCard(g); } int removeCard(Graphics g) { int retCard = topCard(); switch(type) { case parent.typePlay: drawCard(g, false, nCards); nCards--; stack[nCards] = -1; break; case parent.typeTemp: stack[0] = -1; nCards = 0; break; case parent.typeDone: switch(parent.suitNo(retCard)) { case - 2: break; // already empty case -1: stack[0] = -1; nCards = 0; break; case 0: stack[0] += 12; break; default: stack[0]--; } break; default: } drawTopCard(g); return retCard; } int getNCards() { return nCards; } int getIthCard(int i) { return stack[i]; } int getType() { return type; } private int crdShow = 17; void paintSelection(Graphics g) { parent.drawSelectionImage(g, xOff, yOff + crdShow * (nCards - 1)); } void drawCard(Graphics g, boolean drawFlag, int j) { parent.paintCard(g, drawFlag, xOff, yOff+ j * crdShow, stack[j]); } void drawTopCard(Graphics g) { if(isEmpty()) { drawCard(g, false, 0); } else { drawCard(g, true, nCards - 1); } } void drawStack(Graphics g) { if(isEmpty()) { drawCard(g, false, 0); } else { for(int j = 0; j < nCards; j++) drawCard(g, true, j); } } }