//
/*
* 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));
}
//