home .. externals .. leech 2000 .. source code ..
main.py
# ######################################################################
# Copyright 2005 by Michael Alyn Miller.  All rights reserved.
# vi:et:sts=4
#


# ######################################################################
# IMPORTS
#

# Python imports.
import time

# Hermes imports.
from hermes import *

# Leech imports.
import Hack
import LLL
import Leech



# ######################################################################
# INITIALIZATION FUNCTIONS
#

def initializePrefs():
    
prefs.data.playsPerDay = 3
    
prefs.data.bbsCallsPerPlay = 10
    
prefs.data.leechCallsPerPlay = 3
    
prefs.data.bankHacksPerPlay = 3
    
prefs.data.unlimitedSysopPlay = True


def initializeBbs():
    
bbs.data.highScores = []

    
bbs.data.lllBulletinByline = 'Michael Alyn Miller'
    
bbs.data.lllBulletin = """Welcome to Leech 2000!

Leech for a whole new millenium!
Or something like that..."""



def initializeUser():
    
user.data.playsToday = 0
    
user.data.firstTimePlayed = time.localtime()
    
user.data.lastTimePlayed = time.localtime()

    
user.data.level = 1
    
user.data.totalMegs = 10
    
user.data.drivesLeftToNextLevel = 10

    
user.data.softwareType = 0
    
user.data.backupType = 0
    
user.data.fastBackups = 1
    
user.data.trojanHorses = 1

    
user.data.money = 10
    
user.data.moneyInBank = 0

    
# TODO Add a per-user message list so that we can send users messages
    
# if another leech hacks them, steals their bank account, etc.


def initializeInstance():
    
instance.data.megsLeft = user.data.totalMegs
    
instance.data.bankHacks = 0
    
instance.data.bbsCalls = 0
    
instance.data.leechCalls = 0


def newDay():
    
user.data.playsToday = 0



# ######################################################################
# GAME FUNCTIONS
#

def callBbs():
    
# Make sure that the user has not called too many BBSs this play.
    
if instance.data.bbsCalls >= prefs.data.bbsCallsPerPlay:
        
print
        
print NoticeStyle('You have already called %d BBSs this play.' % (
            
prefs.data.bbsCallsPerPlay))
        
return


    
# Clear the screen and welcome the user to the terminal program.
    
clearScreen()
    
print 'Welcome to %s!' % (Leech.Software[user.data.softwareType].name)
    
print
    
print 'At the prompt, enter the number of the BBS you want to call.  This'
    
print 'is also the number of hard drives you will have to crash to hack'
    
print 'that BBS.  The larger the number, the harder the BBS will be to'
    
print 'defeat.  But it will also be worth more if you beat it...'

    
# Prompt the user for a BBS number until they quit the menu or
    
# select a BBS to call.
    
while True:
        
# Find out which BBS they want to call.
        
print
        
response = textPrompt(
            
'Enter the number of the BBS to call (1-99, ? for list): ',
            
maxChars=2, forceUppercase=2, validChars='0123456789?Q')

        
# Process commands (numbers are processed later).
        
if response == 'Q':
            
# We're done.
            
break
        
elif response == '?':
            
# Sort the BBS list by number.
            
bbsList = Leech.BbsList.items()
            
bbsList.sort(lambda a, b: a[0] - b[0])

            
# Split the BBS list into pages, with three columns on each
            
# page.  We put screenHeight-3 (2 for the header and 1 for
            
# the pause prompt) items on each page.
            
bbsListPages = splitPages(
                
bbsList, rowsPerPage=user.screenHeight - 3, numColumns=3)

            
# List the BBSs.
            
firstPage = True
            
for pageNumber, page in enumerate(bbsListPages):
                
# Pause if this is not the first page of BBSs.
                
if pageNumber > 0:
                    
pausePrompt()

                
# Clear the screen.
                
clearScreen()

                
# Print the header.
                
print Leech.HeaderStyle(
                    
'## %-21s  ## %-21s  ## %-21s' % (
                        
'BBS Name', 'BBS Name', 'BBS Name'))
                
print Leech.HeaderStyle(
                    
'%s  %s  %s' % (
                        
'='*24, '='*24, '='*24))

                
# Print each of the rows.
                
for row in page:
                    
# List each of the BBSs in this row.
                    
for number, bbs in row:
                        
# Print this BBS.
                        
print Leech.BbsListNumberStyle('%2d' % number),
                        
print Leech.BbsListNameStyle('%-21s' % bbs.name),

                        
# Print an extra space (in addition to the one
                        
# after the BBS name) between each BBS.
                        
print '',

                    
# Finish the row.
                    
print

            
# Print the prompt again.
            
continue

        
# Everything else had better be a number.
        
try:
            
# Get the BBS number.
            
bbsToCall = int(response)

            
# Check the number.
            
if not 0 < bbsToCall <= 99:
                
raise ValueError, 'Invalid BBS number.'
        
except ValueError:
            
print
            
print NoticeStyle('Invalid BBS number, try again.')
            
continue

        
# Increment the user's BBS calls counter.
        
instance.data.bbsCalls += 1

        
# Call the BBS.
        
print
        
print PromptHeaderStyle(
            
'Dialing %s...' % (Leech.BbsList[bbsToCall].name))
        
Leech.dialModem(
            
Leech.BbsList[bbsToCall].phoneNumber.replace('-', ''), 2400)

        
# Switch to hacking mode.
        
Hack.hackBbs(bbsToCall)

        
# We're done.
        
print
        
Leech.hangupModem()
        
break


def hackLeech():
    
# Make sure that the user has not called too many other leeches this
    
# play.
    
if instance.data.leechCalls >= prefs.data.leechCallsPerPlay:
        
print
        
print NoticeStyle('You have already called %d Leeches this play.' % (
            
prefs.data.leechCallsPerPlay))
        
return

    
# Get the name of the leech the user wants to call.
    
# TODO Should use a userPrompt here.
    
print
    
name = textPrompt(
        
'Enter the name of the leech to call: ', 32, forceUppercase=True)

    
# Nothing to do if we were not given a name.
    
if not name:
        
print 'Aborted.'
        
return


    
# Find the requested user.
    
try:
        
otherUser = bbs.users[name]
    
except KeyError:
        
print 'Unknown user.'
        
return

    
# See if that user is playing Leech.
    
if not otherUser.data:
        
print 'That user is not playing Leech!'
        
return

    
# Don't let users hack themselves.
    
if otherUser == user:
        
print 'You want to hack your own computer?  Try FORMAT...'
        
return

    
# Make sure that the other user is not in Leech at the moment.
    
if otherUser in external.activeUsers:
        
print 'That user is currently playing Leech... better wait until they logoff.'
        
return

    
# Increment the user's leech calls counter.
    
instance.data.leechCalls += 1

    
# Call the user.
    
print
    
print PromptHeaderStyle('Dialing %s...' % (otherUser.name))
    
Leech.dialModem(str(instance.randrange(2000000, 9999999)), 2400)

    
# Switch to hacking mode.
    
Hack.hackUser(otherUser)

    
# We're done.
    
print
    
Leech.hangupModem()


def resetPlayer():
    
# See if the user really wants to reset their leech player.
    
print
    
if not yesNoPrompt(
            
'Format hard drive -- will erase all stats.  Are you sure? '):
        
print 'Aborted.'
        
return

    
# Cache the value of the user's playsToday counter so that we can
    
# preserve that across the reset.
    
playsToday = user.data.playsToday

    
# They want to do it; re-initialize the user's data.
    
initializeUser()
    
initializeInstance()

    
# Put back the user's playsToday counter.
    
user.data.playsToday = playsToday

    
# The user has been reset.
    
print 'Your hard drive has been erased.  You are now a complete n00b.'


def deposit():
    
# Deposit all of the user's money in the bank.
    
user.data.moneyInBank += user.data.money
    
user.data.money = 0

    
# The user's money has been deposited.
    
print
    
print 'Deposit complete.  %s in possession.  %s in LLL bank account.' % (
        
formatCurrency(user.data.money, 0),
        
formatCurrency(user.data.moneyInBank, 0))


def withdraw():
    
# Withdraw all of the user's money from the bank.
    
user.data.money += user.data.moneyInBank
    
user.data.moneyInBank = 0

    
# The user's money has been deposited.
    
print
    
print 'Withdraw complete.  %s in possession.  %s in LLL bank account.' % (
        
formatCurrency(user.data.money, 0),
        
formatCurrency(user.data.moneyInBank, 0))


def unerase():
    
# Calculate the number of deleted megs the user has and the per-meg
    
# recovery price (the user's level times 4).
    
deletedMegs = user.data.totalMegs - instance.data.megsLeft
    
perMegRecoveryPrice = user.data.level * 4

    
# Make sure the user has some deleted megs.
    
if not deletedMegs:
        
print
        
print 'All of your megs are fine.'
        
return


    
# Let the user know how many deleted megs they have, and how much it
    
# will cost to fix them.
    
print
    
print PromptHeaderStyle(
        
'You have %d deleted megs.' % (
            
deletedMegs))
    
print PromptHeaderStyle(
        
'It costs %s to recover each meg (%s for all megs).' % (
            
formatCurrency(perMegRecoveryPrice, 0),
            
formatCurrency(deletedMegs * perMegRecoveryPrice, 0)))
    
print PromptHeaderStyle(
        
'You have %s on you (%s in the bank).' % (
            
formatCurrency(user.data.money, 0),
            
formatCurrency(user.data.moneyInBank, 0)))

    
# Find out what the user wants to do.
    
response = multipleChoicePrompt(
            
'Unerase [A]ll megs, [S]ome megs, or [N]o megs? ',
            
defaultChar='N',
            
choiceList = {
                
'A': ('All megs', 'all'),
                
'S': ('Some megs', 'some'),
                
'N': ('No megs', None),
            
})
    
if not response:
        
print
        
print 'No megs recovered.'
        
return

    
# Use the total number of damaged megs if the user selected `All
    
# megs' otherwise prompt them for the number of megs to recover.
    
if response == 'all':
        
megsToRecover = deletedMegs
    
else:
        
megsToRecover = numericPrompt(
            
'How many megs to unerase? ',
            
minValue=0, maxValue=deletedMegs, requireResponse=False)
        
if not megsToRecover:
            
print
            
print 'No megs recovered.'
            
return


    
# The user has told us how many megs they want to recover.  But can
    
# they afford it?
    
recoveryPrice = megsToRecover * perMegRecoveryPrice
    
if recoveryPrice > user.data.money:
        
print
        
print NoticeStyle(
            
'It costs %s to recover that many megs.  You need more money!' % (
                
formatCurrency(recoveryPrice, 0)))
        
return

    
# Recover the user's megs and take their money.
    
instance.data.megsLeft += megsToRecover
    
user.data.money -= recoveryPrice

    
# Let the user know about their new megs.
    
print
    
print Style(fgGreen)('We unerased %d %s.' % (
        
megsToRecover, plural(megsToRecover, 'meg', 'megs')))


def quit():
    
# Make sure that the user wants to quit.
    
print
    
if yesNoPrompt('Are you sure you want to exit the game? '):
        
instance.data.stillPlaying = False


def lllBattle():
    
# Tell the user about the LLL battle.
    
clearScreen()

    
print Style(fgGreen)('Congratulations, %s!' % (user.name))
    
print
    
print 'You have become a level 31 leech.  It is now time for the BIG'
    
print 'challenge.  You have to crash the LLL computer!  You have all'
    
print "of your megs back, but you'll need them against the LLL.  If"
    
print 'you fail, the LLL will be mad and demote you to level 30'
    
print
    
print 'Good luck!'

    
# Give the user all of their megs back.
    
instance.data.megsLeft = user.data.totalMegs

    
# Call the LLL BBS.
    
print
    
print PromptHeaderStyle('Dialing the LLL BBS...')
    
Leech.dialModem(LLL.phoneNumber, 2400)

    
# Start the battle.
    
Hack.hackLLL()

    
# Disconnect from LLL.
    
print
    
Leech.hangupModem()




# ######################################################################
# MAIN ENTRY POINT
#

def main():
    
# Initialize our preferences if necessary.
    
if not prefs.data:
        
initializePrefs()

    
# Initialize our BBS data if necessary.
    
if not bbs.data:
        
initializeBbs()


    
# Print the welcome screen.
    
printTextFile('Welcome')


    
# Create an account for this user if necessary.
    
if user not in external.users:
        
# New user.  See if they want to play.
        
print 'Looks like you are a new leech!'
        
if not yesNoPrompt('Do you want to join the game? '):
            
return

        
# They want to play; initialize their user data.
        
initializeUser()


    
# Reset the user's counters if they have not played today.
    
if user.data.lastTimePlayed[:3] != time.localtime()[:3]:
        
newDay()

    
# See if this user gets unlimited plays.
    
unlimitedPlays = user.sysop and prefs.data.unlimitedSysopPlay

    
# Make sure that the user has not played too many times today.
    
if not unlimitedPlays and user.data.playsToday >= prefs.data.playsPerDay:
        
print 'Hey, you ARE a leech!  Only %d plays per day!' % (
            
prefs.data.playsPerDay)
        
return

    
# Update the user's plays-today counter and initialize the data for
    
# this instance of Leech.
    
user.data.playsToday += 1
    
user.data.lastTimePlayed = time.localtime()
    
initializeInstance()


    
# Display the top scores.  This also clears the screen.
    
Leech.printTopScores()
    
print

    
# Display the current LLL bulletin.
    
Leech.printBulletin()

    
# Wait for the user to read the top scores and the bulletin.
    
print
    
pausePrompt()


    
# Enter the menu loop.  Print the menu the first time through the
    
# loop.
    
mainMenuCommands = {
        
'':         lambda: printTextFile('MainMenu'),
        
'?':        lambda: printTextFile('MainMenu'),
        
'DIR':      lambda: printTextFile('MainMenu'),
        
'HELP':     lambda: printTextFile('Instructions'),
        
'LLL':      LLL.menu,
        
'CALL':     callBbs,
        
'HACK':     hackLeech,
        
'FORMAT':   resetPlayer,
        
'STATUS':   Leech.printStats,
        
'CREDITS':  lambda: printTextFile('Credits'),
        
'+':        deposit,
        
'-':        withdraw,
        
'UE':       unerase,
        
'QUIT':     quit,
    
}
    
printTextFile('MainMenu')
    
instance.data.stillPlaying = True
    
while instance.data.stillPlaying:
        
# The user must battle the LLL if they get past level 30.
        
if user.data.level > 30:
            
# Battle the LLL.
            
lllBattle()

            
# Go back to the beginning of the loop.  The play is
            
# probably over (unless the user ran away).
            
continue

        
# Get the command from the user.
        
print
        
cmdName = textPrompt(
            
'C:\> ', maxChars=8, autoAccept=False, forceUppercase=True)

        
# Process the command.
        
if mainMenuCommands.has_key(cmdName):
            
# Get the command.
            
cmd = mainMenuCommands[cmdName]

            
# Run the command.
            
cmd()
        
else:
            
# Unknown command.
            
print 'Unknown command!  Type ? or DIR for the menu.'


    
# We're all done.
    
print
    
print 'Now returning you to an environment with even WORSE users...'
    
print


if __name__ == '__main__':
    
main()