/*
* FreeCell for C.L.I. (Command Line Interface)
*
* File : freecell.cpp
* Author : Anupam Srivastava
* E-Mail : anupam.srivastava@gmail.com
* Purpose : Main program file. All needed classes have been defined in
* freecell.h
*
* NOTE:
* This program is derived from FreeCell for DOS v 1.1 by me. It was for Turbo
* C++ v 3.0, and used custom libraries. All of that code is now gone and aim
* of this program is to just be in standard c++. The original program was not
* throughly checked for bugs.
* Any comments and improvements are invited at anupam.srivastava@gmail.com.
*
* Indentation is left to Vim editor.
*
* LICENSE:
* This program is in public domain. If you find this program helpful in
* anyway, you can send me an email and satisfy my narcissistic needs, thus
* making me happy. ;)
*
* TODO:
* 1. Follow MS FreeCell game number pattern with option to specify it.
* 2. Add client-server model. Write more clients (curses, Qt).
* 3. Add solver.
* 4. Add support for moving columns by just selecting the last card.
* 5. Implement Undo.
*/
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib> // for srand()
#include <ctime> // for time()
#include <curses.h>
//#include <cassert>
//#include <sstream>
#include "card.h"
#include "freecell.h"
#include "singleton.h"
using namespace std;
const int ESCAPE = 27;
const int COM_CANCEL = 0;
const int COM_NOEXIST = 1;
const int COM_EMPTY = 2;
const int COM_INCORRECT = 3;
const int COM_UNRECOG = 4;
const string VERSION = "0.2";
const string INDENT = "\t";
typedef Singleton<Deck> Deck_Singleton;
typedef Singleton<Free_Cell> Free_Cell_Singleton;
typedef Singleton<Home_Cell> Home_Cell_Singleton;
inline void type(string msg) {
printw(msg.c_str());
}
inline void type(char ch) {
printw("%c", ch);
}
inline void print_card_value(Card card, bool is_color_available) {
string str = card.get_output_string();
string output = str.substr(0, str.length() - 1);
attron(A_BOLD);
if (is_color_available) {
init_pair(3, COLOR_WHITE, COLOR_GREEN);
attron(COLOR_PAIR(3));
type(output);
attroff(COLOR_PAIR(3));
} else {
type(output);
}
attroff(A_BOLD);
}
inline void print_card_face(Card card, bool is_color_available) {
string str = card.get_output_string();
char output = str.substr(str.length() - 1, 1)[0];
if (is_color_available) {
init_pair(4, COLOR_RED, COLOR_GREEN);
init_pair(5, COLOR_BLACK, COLOR_GREEN);
if (output == DIAMONDS || output == HEARTS) {
attron(COLOR_PAIR(4));
} else {
attron(COLOR_PAIR(5));
}
type(output);
attroff(COLOR_PAIR(4));
attroff(COLOR_PAIR(5));
} else {
type(output);
if (output == DIAMONDS || output == HEARTS) {
type(" (red)");
} else {
type(" (black)");
}
}
}
inline void print_card(Card card, bool is_color_available) {
print_card_value(card, is_color_available);
print_card_face(card, is_color_available);
}
inline void print_card_padded(Card card, bool is_color_available) {
string str = card.get_output_string();
if (str.length() < 2) {
type(" ");
} else {
if (str.length() == 2) {
type(" ");
}
print_card(card, is_color_available);
}
}
inline void error(int error_code) {
type("\n");
switch (error_code) {
case COM_CANCEL:
type("Command cancelled\n");
break;
case COM_NOEXIST:
type("No such column or cell exists\n");
break;
case COM_EMPTY:
type("Column/Cell empty\n");
break;
case COM_INCORRECT:
type("Incorrect move: move not possible\n");
break;
case COM_UNRECOG:
type("Unrecognized command given\n");
break;
default:
type("Unknown error encountered\n");
}
type("\n");
type("Press h for help...\n");
}
/* This function takes a number between 1 and 52 and
* returns a unique card according to that number.
*
* Replacing the card's number with actual value+face
* Values:
* 1 to 13 = 2 to 14
* where it is implicit that
* 11 = J, 12 = Q, 13 = K, 14 = A
* Faces:
* 1st 13 = SPADES
* 2nd 13 = DIAMONDS
* 3rd 13 = CLUBS
* 4th 13 = HEARTS
*/
inline Card generate_card(int card_num) {
Card temp;
if (card_num <= 13) {
temp.set_face(SPADES);
temp.set_value(card_num + 1);
} else if (card_num <= 26) {
temp.set_face(DIAMONDS);
temp.set_value(card_num -12);
} else if (card_num <= 39) {
temp.set_face(CLUBS);
temp.set_value(card_num - 25);
} else {
temp.set_face(HEARTS);
temp.set_value(card_num - 38);
}
return temp;
}
void generate() {
/* Knuth shuffle (modern Fisher-Yates shuffle), from Wikipedia:
* To initialize an array of a of m elements to a randomly shuffled copy
* of source, both 0-based:
*
* a[0] = source[0]
* for i from 1 to m - 1 do
* j = random (0..i)
* a[i] = a[j]
* a[j] = source[i]
*
* Below we assume that source has numbers in ascending order and so
* we replace it with i over 1 to 52
*/
int array_of_numbers[52];
srand(time(0));
for (int i = 1; i <= 52; ++i) {
int j = rand()%i;
array_of_numbers[i - 1] = array_of_numbers[j];
array_of_numbers[j] = i;
}
Home_Cell_Singleton::Instance()->reset();
Free_Cell_Singleton::Instance()->reset();
Deck_Singleton::Instance()->reset();
/* First fill 6 full rows of deck
*/
for (int row = 0; row < 6; ++row) {
for (int col = 0; col < 8; ++col) {
Deck_Singleton::Instance()
->put_card(generate_card(array_of_numbers[row * 8 + col]), col, row);
}
}
/* Then fill last row (half-full) of deck
*/
for (int col = 0; col < 4; ++col) {
Deck_Singleton::Instance()
->put_card(generate_card(array_of_numbers[6 * 8 + col]), col, 6);
}
}
void display(bool is_color_available) {
Card temp;
Home_Cell *home_cell = Home_Cell_Singleton::Instance();
Free_Cell *free_cell = Free_Cell_Singleton::Instance();
Deck *deck = Deck_Singleton::Instance();
type("\n");
if (is_color_available) {
init_pair(2, COLOR_YELLOW, COLOR_GREEN);
attron(COLOR_PAIR(2));
}
type("Home Cells:");
print_card_padded(home_cell->get_card(SPADES), is_color_available);
type(INDENT);
print_card_padded(home_cell->get_card(DIAMONDS), is_color_available);
type(INDENT);
print_card_padded(home_cell->get_card(CLUBS), is_color_available);
type(INDENT);
print_card_padded(home_cell->get_card(HEARTS), is_color_available);
type("\n\n");
if (is_color_available) {
attron(COLOR_PAIR(2));
}
type("Free Cells:");
for (int col = 0; col < 4; ++col) {
temp = free_cell->get_card(col);
print_card_padded(temp, is_color_available);
type(INDENT);
}
type("\n\n");
if (is_color_available) {
attron(COLOR_PAIR(2));
}
type("Deck:\n\n");
attroff(COLOR_PAIR(2));
int max_last_row = 0;
for (int col = 0; col < 8; ++col) {
int last_row = deck->get_first_empty_row(col) - 1;
if (last_row > max_last_row) {
max_last_row = last_row;
}
}
for (int row = 0; row <= max_last_row; ++row) {
for (int col = 0; col < 8; ++col) {
type(INDENT);
print_card_padded(deck->get_card(col, row), is_color_available);
}
type("\n");
}
if (deck->get_total_cards() == 0) {
type("\n\n");
type(" C O N G R A T U L A T I O N S ! ! !\n");
type("\n");
type("You have won the game of FreeCell. On command line!\n");
type("\n");
type("You may start a new game by pressing 'n' or quit by pressing ");
type("'q'\n");
}
}
/* Makes valid moves to Home Cell from deck
* (and not from Free Cell, which it could
* if it wanted to)
*/
void check() {
bool made_move;
/* do loop ensures there are no cards left
* to be moved, by going again and again
* through the deck until it cannot find
* one to move anymore
*/
do {
made_move = false;
for (int col = 0; col < 8; ++col) {
Card temp = Deck_Singleton::Instance()->get_card(col);
if (temp.is_valid()) {
if (Home_Cell_Singleton::Instance()->put_card(temp)) {
Deck_Singleton::Instance()->remove_card(col);
made_move = true;
}
}
}
}while (made_move);
}
int main() {
/* Initialize curses
*/
initscr();
cbreak();
noecho();
nl();
// We use TRUE defined in curses.h for curses functions
keypad(stdscr, TRUE);
intrflush(stdscr, TRUE);
scrollok(stdscr, TRUE);
bool is_color_available = false;
string about_str = "\n";
about_str += " F R E E C E L L for C. L. I.\n";
about_str += " by Anupam Srivastava \n";
about_str += "\n";
about_str += "Version: ";
about_str += VERSION;
about_str += "\n";
about_str += "(Wed Oct 4 14:21:08 IST 2006) Project started.\n";
about_str += "(Wed Oct 4 17:04:45 IST 2006) 0.1alpha1 completed.\n";
about_str += "(Thu Oct 5 15:48:32 IST 2006) 0.1alpha2 completed. ";
about_str += "NCurses is needed.\n";
about_str += "(Sun Mar 21 20:40:13 CET 2010) 0.2 started. Adding ";
about_str += "colors. Big rewrite.\n";
about_str += "(Fri Apr 16 20:29:29 IST 2010) 0.2 complete. Bugs ";
about_str += "expected.\n";
about_str += "(Tue May 11 15:57:29 IST 2010) 0.2 released.";
string help_str = "\n";
help_str += " KEY ACTION\n";
help_str += " [n] new game\n";
help_str += " [u] undo last move(NOT IMPLEMENTED)\n";
help_str += " [h] help\n";
help_str += " [m] move card from\n";
help_str += " column #[1,2,3,4,5,6,7,8] or\n";
help_str += " [f]ree cell #[1,2,3,4]\n";
help_str += " to column #[1,2,3,4,5,6,7,8] or\n";
help_str += " [f]ree cell #[1,2,3,4] or\n";
help_str += " [h]ome cell\n";
help_str += " [q] quit\n";
help_str += " [e] quit\n";
help_str += " [c] check deck for win and valid moves\n";
help_str += " [r] refresh screen\n";
help_str += " [ESC] cancel command\n";
type(about_str);
// type(help_str);
type("\n");
type("Press any key to continue...");
getch();
if (has_colors()) {
start_color();
assume_default_colors(COLOR_BLUE,COLOR_GREEN);
is_color_available = true;
}
bool quit = false;
Deck *deck = Deck_Singleton::Instance();
Free_Cell *free_cell = Free_Cell_Singleton::Instance();
Home_Cell *home_cell = Home_Cell_Singleton::Instance();
Card_Collection card_collection(deck, free_cell);
/* Function pointer for removing a card
*/
bool (Card_Collection::*ptr_remove_card)(int) = NULL;
clear();
type("Press h for help");
generate();
display(is_color_available);
while (quit == false) {
type("\n");
type("freecell> ");
int col_to_remove_from, col;
bool is_card_selected = false;
Card temp;
char command = tolower(getch());
/* Valid input:
* ESCAPE Cancel
* h Help
* n New game
* q Quit
* e Quit
* c Check
* u Undo
* r Refresh screen
* m Move
*/
switch (command) {
case ESCAPE:
error(COM_CANCEL);
break;
case 'h':
type("help\n");
type(help_str);
break;
case 'n':
type("new game? (y/n)");
if (tolower(getch()) == 'y') {
clear();
type("Starting new game");
generate();
display(is_color_available);
} else {
type(" No.\n");
}
break;
case 'e':
case 'q':
type("quit? (y/n)");
if (tolower(getch()) == 'y') {
quit = true;
} else {
type(" No.\n");
}
break;
case 'c':
type("check\n");
check();
display(is_color_available);
break;
case 'u':
type("undo\n");
type("Not yet implemented\n");
break;
case 'r':
clear();
display(is_color_available);
break;
case 'm':
type("move ");
command = tolower(getch());
/* Valid input:
* ESCAPE Cancel
* 1,2,3,4,5,6,7,8 Column number to pick the card from last
* position
* f Free cell
*/
switch (command) {
case ESCAPE:
error(COM_CANCEL);
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
/* Convert to int, and decrease by 1 for array indices
*/
col_to_remove_from = command - 49;
temp = deck->get_card(col_to_remove_from);
if (!temp.is_valid()) {
error(COM_EMPTY);
} else {
ptr_remove_card = &Card_Collection::
remove_card_from_deck;
print_card(temp, is_color_available);
type(" to ");
is_card_selected = true;
}
break;
case 'f':
type("Free Cell #");
command = tolower(getch());
/* Valid input:
* ESCAPE Cancel
* 1,2,3,4 Free cell column to pick the card from
*/
switch (command) {
case ESCAPE:
error(COM_CANCEL);
break;
case '1':
case '2':
case '3':
case '4':
type(command);
/* Convert to int, and decrease by 1 for array
* indices
*/
col_to_remove_from = command - 49;
temp = free_cell->get_card(col_to_remove_from);
if (!temp.is_valid()) {
error(COM_EMPTY);
} else {
ptr_remove_card = &Card_Collection::
remove_card_from_free_cell;
type("(");
print_card(temp, is_color_available);
type(") to ");
is_card_selected = true;
}
break;
default:
type(command);
error(COM_NOEXIST);
break;
}
break;
case 'h':
type("Home Cell\n");
type("Cannot move from Home Cell\n");
break;
default:
type(command);
error(COM_NOEXIST);
break;
}
if (is_card_selected) {
command = tolower(getch());
/* Valid input:
* ESCAPE Cancel
* 1,2,3,4,5,6,7,8 Column number to put the card in last
* position
* f Free cell
* h Home cell
*/
switch (command) {
case ESCAPE:
error(COM_CANCEL);
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
type("Column #");
type(command);
type("\n");
/* Convert to int, and decrease by 1 for array
* indices
*/
col = command - 49;
if(!deck->put_card(temp, col)) {
error(COM_INCORRECT);
} else {
(card_collection.*ptr_remove_card)
(col_to_remove_from);
display(is_color_available);
}
break;
case 'f':
type("Free Cell #");
command = tolower(getch());
/* Valid input:
* ESCAPE Cancel
* 1,2,3,4 Free cell number to put the card in
*/
switch (command) {
case ESCAPE:
error(COM_CANCEL);
break;
case '1':
case '2':
case '3':
case '4':
type(command);
type("\n");
/* Convert to int, and decrease by 1 for
* array indices
*/
col = command - 49;
if (!free_cell->put_card(temp, col)) {
error(COM_INCORRECT);
} else {
(card_collection.*ptr_remove_card)
(col_to_remove_from);
display(is_color_available);
}
break;
default:
type(command);
error(COM_NOEXIST);
break;
}
break;
case 'h':
type("Home Cell\n");
if (!home_cell->put_card(temp)) {
error(COM_INCORRECT);
} else {
(card_collection.*ptr_remove_card)
(col_to_remove_from);
display(is_color_available);
}
break;
default:
type(command);
error(COM_NOEXIST);
break;
}
}
break;
default:
type(command);
error(COM_UNRECOG);
break;
}
} while (!quit);
clear();
endwin();
cout << "FreeCell for C.L.I. v" << VERSION;
cout << ", compatible with PDCurses." << endl;
cout << "Ask for source code (in public domain) at ";
cout << "anupam.sriastava@gmail.com" << endl;
return 0;
}