//
/* * Example ATM simulation - file atmparts.cc * * This file implements the classes that manage the ATM's components * - as declared in atmparts.h * * Copyright (c) 1996, 1997 - Russell C. Bjork * */ #include#ifndef VMS #include #else #include #endif #include #include #include "sysdep.h" #include "status.h" #include "money.h" #include "bank.h" #include "session.h" #include "transaction.h" #include "atmparts.h" #include "atm.h" // A large part of the responsibility of the classes implemented in this file // concerns the user interface. This particular implementation is designed // for use on a standard 24 x 80 text-based terminal, and uses the Curses // package to manage windows that simulate the various components The class // Window (built on top of the Curses package) will manage these for us. To // avoid putting the details of this in the class interface, we declare a static // Window here for each component of the ATM. // The interface to class Window is in file window.h #include "window.h" // Widths and heights below must allow room for drawing a box around the // window - one line at top, one at bottom, one char at left and one at right /* * Arrangement: --------- DISPLAY ---------- * CASH CARD RECEIPT * " ENVELOPE " * ---miscellaneous (no box)--- * */ #define DISPLAY_WINDOW_HEIGHT 10 #define DISPLAY_WINDOW_WIDTH 80 #define DISPLAY_WINDOW_TOP 0 #define DISPLAY_WINDOW_LEFT 0 #define CASH_WINDOW_HEIGHT 9 #define CASH_WINDOW_WIDTH 23 #define CASH_WINDOW_TOP (DISPLAY_WINDOW_TOP + DISPLAY_WINDOW_HEIGHT) #define CASH_WINDOW_LEFT 0 #define CARD_WINDOW_HEIGHT 5 #define CARD_WINDOW_WIDTH 28 #define CARD_WINDOW_TOP (DISPLAY_WINDOW_TOP + DISPLAY_WINDOW_HEIGHT) #define CARD_WINDOW_LEFT (CASH_WINDOW_LEFT + CASH_WINDOW_WIDTH) #define ENVELOPE_WINDOW_HEIGHT 4 #define ENVELOPE_WINDOW_WIDTH 28 #define ENVELOPE_WINDOW_TOP (CARD_WINDOW_TOP + CARD_WINDOW_HEIGHT) #define ENVELOPE_WINDOW_LEFT (CASH_WINDOW_LEFT + CASH_WINDOW_WIDTH) #define RECEIPT_WINDOW_HEIGHT 9 #define RECEIPT_WINDOW_WIDTH 29 #define RECEIPT_WINDOW_TOP (DISPLAY_WINDOW_TOP + DISPLAY_WINDOW_HEIGHT) #define RECEIPT_WINDOW_LEFT (CARD_WINDOW_LEFT + CARD_WINDOW_WIDTH) #define MISC_WINDOW_HEIGHT 2 #define MISC_WINDOW_WIDTH 80 #define MISC_WINDOW_TOP (CASH_WINDOW_TOP + CASH_WINDOW_HEIGHT) #define MISC_WINDOW_LEFT 0 static BoxedWindow _displayWindow("DISPLAY", DISPLAY_WINDOW_HEIGHT, DISPLAY_WINDOW_WIDTH, DISPLAY_WINDOW_TOP, DISPLAY_WINDOW_LEFT), _cashWindow("CASH DISPENSER", CASH_WINDOW_HEIGHT, CASH_WINDOW_WIDTH, CASH_WINDOW_TOP, CASH_WINDOW_LEFT), _cardWindow("CARD SLOT", CARD_WINDOW_HEIGHT, CARD_WINDOW_WIDTH, CARD_WINDOW_TOP, CARD_WINDOW_LEFT), _envelopeWindow("ENVELOPE SLOT", ENVELOPE_WINDOW_HEIGHT, ENVELOPE_WINDOW_WIDTH, ENVELOPE_WINDOW_TOP, ENVELOPE_WINDOW_LEFT), _receiptWindow("RECEIPT", RECEIPT_WINDOW_HEIGHT, RECEIPT_WINDOW_WIDTH, RECEIPT_WINDOW_TOP, RECEIPT_WINDOW_LEFT); static Window _miscWindow(MISC_WINDOW_HEIGHT, MISC_WINDOW_WIDTH, MISC_WINDOW_TOP, MISC_WINDOW_LEFT); // The operator switch and the card reader insertion test make use of the // _miscWindow. The user types a C in this window to insert a card, and an // S to toggle the switch. The following variable holds one of three // values: ' ' - no prompt yet displayed, no value read; 'C'/'c' or 'S'/'s' - // one of these has been read static char _miscWindowInput = ' '; CardReader::CardReader() : _status(NO_CARD) { } CardReader::ReaderStatus CardReader::checkForCardInserted() { if (_status != NO_CARD) return _status; if (_miscWindowInput == ' ') { _miscWindow << clearW << "Type C to insert card, S to toggle operator switch " << refreshW; _miscWindowInput = _miscWindow.inkey("CcSs"); } if (_miscWindowInput == 'C' || _miscWindowInput == 'c') { _miscWindowInput = ' '; _miscWindow << clearW << refreshW; _cardWindow << "Card number: "; char cardNumberString[20]; _cardWindow >> cardNumberString; if (sscanf(cardNumberString, "%d", & _cardNumberRead) == 1) _status = CARD_HAS_BEEN_READ; else _status = UNREADABLE_CARD; _cardWindow << clearW << refreshW; return _status; } else return NO_CARD; } int CardReader::cardNumber() const { return _cardNumberRead; } void CardReader::ejectCard() { _status = NO_CARD; int i; for (i = 2; i >= 0; i --) { _cardWindow.putCursor(i, 0); _cardWindow << " EJECTING CARD" << refreshW; sleep(1); } for (i = 2; i >= 0; i --) { _cardWindow.putCursor(i, 0); _cardWindow << " " << refreshW; sleep(1); } _cardWindow << clearW << refreshW; } void CardReader::retainCard() { _status = NO_CARD; } Display::Display() { } void Display::requestCard() { _displayWindow << clearW << " Please insert your card to begin" << refreshW; } void Display::requestPIN() { _displayWindow << clearW << " Please enter your Personal Identification Number (PIN)\n" " Press ENTER when finished or DELETE to start over" << refreshW; } void Display::displayMenu(const char * whatToChoose, int numItems, const char * items[]) { _displayWindow << clearW << " " << whatToChoose; int menuLinesAvailable = DISPLAY_WINDOW_HEIGHT - 2 - 2; int itemsPerLine = 1 + (numItems - 1) / menuLinesAvailable; int menuColsPerItem = (DISPLAY_WINDOW_WIDTH - 2) / itemsPerLine; for (int i = 0; i < menuLinesAvailable; i ++) { int row = i + 2; int col = 0; for (int j = i; j < numItems; j += menuLinesAvailable) { _displayWindow.putCursor(row, col); _displayWindow << " " << ((char) j) + '1' << ") " << items[j]; col += menuColsPerItem; } } _displayWindow << refreshW; } void Display::requestAmountEntry() { _displayWindow << clearW << " Please enter amount." " Press ENTER when finished or DELETE to start over" << refreshW; } void Display::requestDepositEnvelope() { _displayWindow << clearW << " Please deposit an envelope in the slot" << refreshW; } void Display::reportCardUnreadable() { _displayWindow << clearW << " Sorry, your card was inserted incorrectly or is unreadable.\n" " Please try inserting your card again" << refreshW; } void Display::reportTransactionFailure(const char * explanation) { _displayWindow << clearW << " " << explanation << "\n\n" << " Do you want to perform another transaction (1 = Yes, 2 = No)?" << refreshW; } void Display::requestReEnterPIN() { _displayWindow << clearW << " Your PIN was entered incorrectly.\n" " Please re-enter it" << refreshW; } void Display::reportCardRetained() { _displayWindow << clearW << " Your PIN was entered incorrectly.\n" " Your card has been retained - please contact the bank" << refreshW; sleep(20); } void Display::echoInput(const char * echo) { _displayWindow.putCursor(DISPLAY_WINDOW_HEIGHT - 2 - 1, 0); _displayWindow << ' ' << echo; for (int i = strlen(echo) + 1; i < DISPLAY_WINDOW_WIDTH - 2; i ++) _displayWindow << ' '; _displayWindow.putCursor(DISPLAY_WINDOW_HEIGHT - 2 - 1, strlen(echo) + 1); _displayWindow << refreshW; } void Display::clearDisplay() { _displayWindow << clearW << refreshW; } Keyboard::Keyboard() { } // Note that the keyboard uses the miscellaneous window to read from - since // nothing is actually echoed during the read. Echoing is done by calling // Display::echoInput int Keyboard::readPIN(Display & echoOn) { char PINstring[20], echoString[20]; int i; for (i = 0; i < 19; ) { echoString[i] = '\0'; echoOn.echoInput(echoString); char c = _miscWindow.inkey("0123456789\177\r"); if (c == '\177') i = 0; else if (c == '\r' && i > 0) break; else if (c == '\r') _miscWindow << bell; else { PINstring[i] = c; echoString[i ++] = '*'; } } PINstring[i] = '\0'; int PIN; sscanf(PINstring, "%d", & PIN); return PIN; } int Keyboard::readMenuChoice(int numItems) { char valid[256]; for (int i = 0; i < numItems; i ++) valid[i] = (char) (i + '1'); valid[numItems] = '\0'; return (int) _miscWindow.inkey(valid) - '0'; } Money Keyboard::readAmountEntry(Display & echoOn) { char dollarString[17], centString[3]; int dollars, cents; bool done = false; do { strcpy(dollarString, " "); strcpy(centString, " "); while (dollarString[0] == ' ' && ! done) { char echoString[22]; sprintf(echoString, "%s.%s", dollarString, centString); echoOn.echoInput(echoString); char c = _miscWindow.inkey("0123456789\177\r"); if (c == '\177') break; else if (c == '\r' && centString[1] != ' ') done = true; else if (c == '\r') _miscWindow << bell; else { strncpy(dollarString, dollarString + 1, 15); dollarString[15] = centString[0]; dollarString[16] = '\0'; centString[0] = centString[1]; centString[1] = c; } } } while (! done); if (dollarString[15] != ' ') sscanf(dollarString, "%d", & dollars); else dollars = 0; sscanf(centString, "%d", & cents); return Money(dollars, cents); } CashDispenser::CashDispenser() : _currentCash(0) { } void CashDispenser::setCash(Money initialCash) { _currentCash = initialCash; } void CashDispenser::dispenseCash(Money amount) { char amountString[10]; sprintf(amountString, "%d.%02d", amount.dollars(), amount.cents()); _cashWindow << clearW << refreshW; for (int i = CASH_WINDOW_HEIGHT - 3; i >= 0; i --) { _cashWindow << clearW; _cashWindow.putCursor(i,0); _cashWindow << "Dispensing $" << amountString << refreshW; sleep(1); } _cashWindow << clearW << refreshW; _currentCash -= amount; } Money CashDispenser::currentCash() const { return _currentCash; } EnvelopeAcceptor::EnvelopeAcceptor() { } bool EnvelopeAcceptor::acceptEnvelope() { _envelopeWindow << clearW << "Press E to enter envelope\nT to time-out" << refreshW; char choice = _envelopeWindow.inkey("EeTt"); _envelopeWindow << clearW << refreshW; return choice == 'E' || choice == 'e'; } ReceiptPrinter::ReceiptPrinter() { } void ReceiptPrinter::printReceipt(int theATMnumber, const char * theATMlocation, int cardNumber, int serialNumber, const char * description, Money amount, Money balance, Money availableBalance) { // Set up the receipt // Width of line allows for full line of text + newline + null char receiptLine[7][RECEIPT_WINDOW_WIDTH - 2 + 2]; time_t now = time(NULL); sprintf(receiptLine[0], ctime(& now)); sprintf(receiptLine[1], "#%4d %s\n", theATMnumber, theATMlocation); sprintf(receiptLine[2], "CARD %7d TRANS %7d\n", cardNumber, serialNumber); sprintf(receiptLine[3], "%s\n", description); if (amount == Money(0)) sprintf(receiptLine[4], "\n"); else sprintf(receiptLine[4], "AMOUNT: %12d.%02d\n", amount.dollars(), amount.cents()); sprintf(receiptLine[5], "CURR BAL: %12d.%02d\n", balance.dollars(), balance.cents()); sprintf(receiptLine[6], "AVAILABLE: %12d.%02d\n", availableBalance.dollars(), availableBalance.cents()); // Animate it _receiptWindow << clearW << refreshW; for (int i = 6; i >= 0; i --) { _receiptWindow << clearW; _receiptWindow.putCursor(i, 0); for (int j = 0; i + j <= 6; j ++) _receiptWindow << receiptLine[j]; _receiptWindow << refreshW; sleep(1); } sleep(5); _receiptWindow << clearW << refreshW; } OperatorPanel::OperatorPanel() { } bool OperatorPanel::switchOn() { // An actual switch is a mechanical device that can be moved from one // state to another. We will simulate this with a static variable that // can be toggled by the user typing "S" static bool isOn = false; if (_miscWindowInput == ' ' && ! isOn) { _miscWindow << clearW << "Type S to toggle operator switch " << refreshW; _miscWindowInput = _miscWindow.inkey("Ss"); } if (_miscWindowInput == 'S' || _miscWindowInput == 's') { _miscWindowInput = ' '; _miscWindow << clearW << refreshW; isOn = ! isOn; } return isOn; } Money OperatorPanel::getInitialCash() { char numBillsString[100]; int numBills = -1; do { _miscWindow << clearW << "How many $20 bills are in the cash dispenser? " << refreshW; _miscWindow >> numBillsString; if (sscanf(numBillsString, "%d", & numBills) != 1 || numBills < 0) _miscWindow << bell; } while (numBills < 0); return Money((20 * numBills)); } //