Untitled

 avatar
unknown
python
4 months ago
22 kB
13
Indexable
from tabulate import tabulate
import os
import random

# global variables and constant
g_destinationList = ["Perlis", "Kedah", "Penang", "Perak", "Selangor", "Negeri Sembilan", "Melaka", "Johor", "Kelantan", "Terengganu", "Pahang"]
REFUNDRATE = 0.75

# generate refund policy
def refundPolicy():

    print(tabulate([["\nPlease note that our train ticket system does not support a full refund.\nInstead, a 75% refund of the ticket fee will be provided in the event of a cancellation.\nWe kindly request your understanding and encourage you to proceed with your bookings and cancellation at your own discretion.\nThank you for your cooperation.\n"]], tablefmt="rounded_grid"))

# update the train schedule whenever we change the number of available seats
def updateTable(trains):
    table = tabulate(
        trains,
        headers=["Train No", "Train Type", "Origin", "Destination", "Available Seat", "Fare/Pax", "Departure", "Arrival"],
        tablefmt="fancy_grid",
        numalign="right",
        colalign=("center", "center", "center", "center", "center", "center", "center")
    )
    
    return table

# converts an alphabet into its corresponding index (A -> 0, B -> 1, etc.)
def alphabetToInt(alphabet):
    if alphabet == "A":
        return 0
    elif alphabet == "B":
        return 1
    elif alphabet == "C":
        return 2
    elif alphabet == "D":
        return 3
    elif alphabet == "E":
        return 4
    elif alphabet == "F":
        return 5
    elif alphabet == "G":
        return 6
    elif alphabet == "H":
        return 7
    else:
        return -1 # if the user input the alphabet is not in our seat, throw error

# generate the default seats for each train
def templateSeats():
    seatsAbove = [ ["A01", "B01", "C01", "D01", "E01", "F01", "G01", "H01"],
                   ["A02", "B02", "C02", "D02", "E02", "F02", "G02", "H02"]
                 ]
    seatsBelow = [ ["A03", "B03", "C03", "D03", "E03", "F03", "G03", "H03"],
                   ["A04", "B04", "C04", "D04", "E04", "F04", "G04", "H04"]
                 ]
    seats = [seatsAbove, seatsBelow]

    return seats

# clear the console with better visualization
def refresh():
    print("\n\n\n\n\n\n\n\n\n\n")

# randomize the destination/origin
def destinationRandomize():
    destination = random.choice(g_destinationList)
    g_destinationList.remove(destination)
    return destination

# randomize the fare in 50, 100, 150, 200
def fareRandomize():
    fare = random.randint(1,4)
    return fare * 50

# draw the main menu here using tabulate
def drawMenu():
    menuChoice = [ ["(1) View Train Schedules"],
                   ["(2) Book Tickets"],
                   ["(3) View Bookings"],
                   ["(4) Cancel Tickets"],
                   ["(5) Exit Program"]
                 ]
    menuTable = tabulate(
        menuChoice,
        tablefmt="fancy_grid",
        headers=["  Malaysia Boleh Train Service"]
    )
    
    refresh()
    print(menuTable)

# Yik Yang part
def viewTrainSchedules(trains, bookings, trainsSeats, fareList):
    refresh()
    print(tabulate([["Train Schedules"]], tablefmt="rounded_grid"))
    print()

    # draw the view train schedules out
    print(updateTable(trains))
    
    cont = input("Enter 2 to proceed to book tickets or 0 to exit >> ")
    
    if cont == "2":        
        bookTickets(trains, bookings, trainsSeats, fareList)
    elif cont == "0":
        return
    else:
        return
         
    os.system("cls")

# Li Hen part
def bookTickets(trains, bookings, trainsSeats, fareList):
    # this variable will increase by 1 everytime a ticket is successfully generated
    totalGeneratedTicketID = 1

    while True:
        refresh()
        print(tabulate([["Available Trains to Book"]], tablefmt="rounded_grid"))
        print()
        print(updateTable(trains))

        book = input("\nEnter Train Number (101, 102, 103) to view seats, or 0 to exit >> ")
        if book == "0":
            os.system("cls")
            return

        selectedTrain = None
        trainIndex = None  # Index of the selected train

        # get the corresponding train index so can be easily access in later use
        for no in range(len(trains)):
            if trains[no][0] == book:
                selectedTrain = trains[no][0]
                trainIndex = no
                break
        
        # handle invalid input
        while not selectedTrain:
            book = input("Enter Train Number (101, 102, 103) to view seats, or 0 to exit >> ")
            if book == "0":
                os.system("cls")
                return
            
            selectedTrain = None
            trainIndex = None  # Index of the selected train
            for no in range(len(trains)):
                if trains[no][0] == book:
                    selectedTrain = trains[no][0]
                    trainIndex = no
                    break

        pax = 0
        while True:
            pax = input("How many people? >> ")

            # handle invalid input
            if pax.isdigit() and 0 < int(pax) <= trains[trainIndex][4]:
                pax = int(pax)
                break
            else:
                print("Invalid input! The number must be between 1 and 32. Try again.")
                continue
        
        fare = fareList[trainIndex] * pax # get the total price of the current booking 
        isBooked = False
        bookedSeats = []  # keep track of seats booked in this session

        while True:
            refresh()
            print(tabulate([[f"\nHere are the available seats for {trains[trainIndex][1]} Express (Train No: {selectedTrain})"]], tablefmt="rounded_grid"))
            print()

            # Draw the seats with seperately above and below
            seatAboveTable = tabulate(
                trainsSeats[trainIndex][0],
                tablefmt="heavy_grid"
            )

            print(seatAboveTable)
            print("--> Walk way")

            seatBelowTable = tabulate(
                trainsSeats[trainIndex][1],
                tablefmt="heavy_grid"
            )

            print(seatBelowTable)

            while pax > 0:
                print(f"Total pax that haven't chosen a seat: {pax}")
                seatChoice = input("Select a seat (e.g., A01, B04) per pax at a time, or 0 to exit >> ").upper()
                
                if seatChoice == "0":
                    for seat in bookedSeats:
                        row = seat[0]
                        col = seat[1]  # seperate the seat into row and col
                        seatLabel = "ABCDEFGH"[col]  # map column index to the corresponding letter
                        seatLabel += "0" + str(row + 1)
                        
                        # check if the seat is in the upper rows (row < 2) or lower rows then restore the seat
                        if row < 2:
                            trainsSeats[trainIndex][0][row][col] = seatLabel
                        else:
                            trainsSeats[trainIndex][1][row - 2][col] = seatLabel
                    os.system("cls")
                    return

                # handle invalid input
                while len(seatChoice) != 3 or not seatChoice[1:].isdigit():
                    seatChoice = input("Invalid format, select a seat (e.g., A01, B04) per pax at a time, or 0 to exit >> ").upper()

                    if seatChoice == "0":
                        for seat in bookedSeats:
                            row = seat[0]
                            col = seat[1]  # seperate the seat into row and col
                            seatLabel = "ABCDEFGH"[col]  # map column index to the corresponding letter
                            seatLabel += "0" + str(row + 1)
                            
                            # check if the seat is in the upper rows (row < 2) or lower rows then restore the seat
                            if row < 2:
                                trainsSeats[trainIndex][0][row][col] = seatLabel
                            else:
                                trainsSeats[trainIndex][1][row - 2][col] = seatLabel
                        os.system("cls")
                        return
                
                # get the row and col from the seatChoice to easily access through corresponding seat in list
                row = int(seatChoice[-1]) - 1
                col = alphabetToInt(seatChoice[0])

                # handle invalid input
                while row < 0 or row >= 4 or col == -1:
                    seatChoice = input("Invalid format, the range is in between (A-H) and (01-04) >> ").upper()

                    if len(seatChoice) != 3 or not seatChoice[1:].isdigit():
                        continue

                    row = int(seatChoice[-1]) - 1
                    col = alphabetToInt(seatChoice[0])

                if row < 2:  # if the seat is in upper row
                    if trainsSeats[trainIndex][0][row][col] == "XXX":
                        input("The seat is already booked. Please choose another available seat.\n")
                        continue
                    trainsSeats[trainIndex][0][row][col] = "XXX"
                else:  # if the seat is in lower row
                    if trainsSeats[trainIndex][1][row - 2][col] == "XXX":
                        input("The seat is already booked. Please choose another available seat.\n")
                        continue
                    trainsSeats[trainIndex][1][row - 2][col] = "XXX"

                bookedSeats.append((row, col))  # store the booked seat into list
                pax -= 1
                print(f"The seat {seatChoice} has been booked successfully.\n")
                isBooked = True
                
            # generate the random 3 digits number for the ticket id to look better
            ticketIDGenerator = random.randint(100,999)
            if pax == 0 and isBooked:
                print(f"Your total payment is: RM{fare} (RM{fareList[trainIndex]} per pax)")
                print()
                confirmation = input("Are you confirm with your booking seats? (Y/N) >> ").upper()

                while confirmation not in ["Y", "N", "YES", "NO"]:
                    confirmation = input("Invalid input. Are you confirm with your booking seats? (Y/N) >> ").upper()

                if confirmation in ["Y", "YES"]:
                    person = int(fare / fareList[trainIndex])

                    print("\nGenerating your receipt (Ticket ID)...")
                    ticketID = f"{trains[trainIndex][1]} Express {selectedTrain} - {int(fare / fareList[trainIndex])}{ticketIDGenerator}{totalGeneratedTicketID}{trainIndex + 1}"
                    totalGeneratedTicketID += 1

                    # use dict to store the confirmed booking
                    bookings.append({"Ticket ID": ticketID, "Seats": bookedSeats})
                    trains[trainIndex][4] -= person
                    print()

                    print("Your receipt (Ticket ID) is generated successfully. Please head to 'View Bookings' to check your details.")
                    cont = input("Enter '2' to View Bookings or any key to Main Menu >> ")

                    if cont == "2":
                        os.system("cls")
                        viewBookings(bookings, trains, fareList, trainsSeats)
                    else:
                        return
                    
                elif confirmation in ["N", "NO"]:  # cancel the booking
                    print("Booking cancelled. Reverting seat availability...")
                    # loop through each seat in bookedSeats
                    for seat in bookedSeats:
                        row = seat[0]
                        col = seat[1]  # seperate the seat into row and col
                        seatLabel = "ABCDEFGH"[col]  # map column index to the corresponding letter
                        seatLabel += "0" + str(row + 1)
                        
                        # check if the seat is in the upper rows (row < 2) or lower rows then restore the seat
                        if row < 2:
                            trainsSeats[trainIndex][0][row][col] = seatLabel
                        else:
                            trainsSeats[trainIndex][1][row - 2][col] = seatLabel

                    input("Seats have been restored. Returning to the main menu. Press any key to continue >> ")    
                    os.system("cls")                    
                    return

# Malcolm part
def viewBookings(bookings, trains, fareList, trainsSeats):
    while True:
        refresh()

        # check if user haven't book any ticket
        if not bookings:
            print(tabulate([["You don't have any booking yet."]], tablefmt="rounded_grid"))
            input("\nPress any key to Main Menu >> ")
            os.system("cls")
            return
        else:
            print(tabulate([["These are the detail(s) about your recent booking"]], tablefmt="rounded_grid"))

            # loop through bookings to get all the ticket id that had booked
            for i in range(len(bookings)):
                print(f"-> ({i + 1}) {bookings[i]["Ticket ID"]}")
                
            print()

        while True:
            choice = input(f"Input 1 to {len(bookings)} to view corresponding details, or 0 to exit >> ")

            # handle invalid input
            while not choice.isdigit():
                choice = input("Invalid input, try again >> ")

            choice = int(choice)

            if choice == 0:
                os.system("cls")
                return

            # if input out of boundary, throw exception
            if choice > len(bookings):
                continue
            
            # display the booking details if found
            else:
                os.system("cls")

                refresh()
                print(tabulate([[f"Booking Details of {bookings[choice - 1]["Ticket ID"]}"]], tablefmt="rounded_grid"))
                print()
                
                # get all the details so can be easily access later
                pax = int(bookings[choice - 1]["Ticket ID"][-7:-5])
                trainNo = int(bookings[choice - 1]["Ticket ID"][-1]) - 1
                payment = f"RM {fareList[trainNo] * pax:5.2f}"
                departure = trains[trainNo][6]
                arrival = trains[trainNo][7]

                # initialize an empty list to store formatted seat details
                seatDetailsList = []

                # loop through each seat in the selectedBooking["Seats"] list
                for seat in bookings[choice - 1]["Seats"]:
                    row = seat[0]  # rol index
                    col = seat[1]  # column index
                    seatLabel = "ABCDEFGH"[col]  # get the corresponding column letter
                    seatLabel += "0" + str(row + 1)
                    seatDetailsList.append(seatLabel)

                # join the seat labels into a single string
                seatDetails = ", ".join(seatDetailsList)
                
                details = [ [pax, departure, arrival, payment, seatDetails] ]
                table = tabulate(
                    details,
                    headers=["Pax", "Departure Time", "Arrival Time", "Fare/Pax", "Seats"],
                    tablefmt="fancy_grid",
                    colalign=("center", "center", "center", "center", "center")
                )

                print(table)
                
                cont = input("Enter 4 to proceed to cancel tickets or 0 to exit >> ")
    
                if cont == "4":        
                    cancelTickets(bookings, trains, trainsSeats, fareList)
                elif cont == "0":
                    input("\nPress any key to Main Menu >> ")
                else:
                    print("Invalid input. Please try again.")
                    input("\nPress any key to main menu >> ")

                os.system("cls")
                return

# Qinyi part
def cancelTickets(bookings, trains, trainsSeats, fareList):
    while True:
        refresh()
        print(tabulate([["Cancel Your Ticket(s) Here"]], tablefmt="rounded_grid"))

        cancelList = []

        if not bookings:
            print("You haven't purchased your ticket yet.")
            input("Enter any key to main menu >> ")
            os.system("cls")
            return
        else:
            print()

            # loop the bookings list to get all the booked seats
            for i in range(len(bookings)):
                print(f"-> ({i + 1}) {bookings[0]["Ticket ID"]}")
                cancelList.append(i)
            print()

        while True:
            choice = input(f"Enter the number(1 to {len(cancelList)}) to cancel the corresponding ticket, or '0' to exit >> ")

            # handle invalid input
            while not choice.isdigit():
                choice = input(f"Invalid input, enter the number in between 1 to {len(cancelList)} >> ")
            
            choice = int(choice) - 1 

            if choice == - 1:
                os.system("cls")
                return
            
            if choice > len(cancelList):
                continue

            if choice in cancelList:
                refresh()

                trainNo = int(bookings[choice]["Ticket ID"][-1]) - 1
                pax = int(bookings[choice]["Ticket ID"][-7:-5])
                payment = fareList[trainNo] * pax
                refundRec = [
                    ["The total amount of payment", f"RM{payment:5.2f}"],
                    ["The refund rate            ", f"{REFUNDRATE*100:5.2f}%"],
                    ["The total refundable fees  ", f"RM{payment * REFUNDRATE:5.2f}"]
                ]

                refundPolicy()

                refundTable = tabulate(
                    refundRec,
                    tablefmt="fancy_grid",
                    headers=["Description", "Amount"]
                )

                print(refundTable)

                while True:
                    answer = input("Are you sure you want to cancel? (Y/N) >> ").upper()  

                    if answer in ["YES", "Y"]:
                        print("Cancelling ticket...")
                        trains[trainNo][4] += pax

                        seatList = []

                        # retrieve the seat from the bookings list and restore back the seats
                        for seats in bookings[i]["Seats"]:
                            row, col = seats
                            seatLabel = "ABCDEFGH"[col]
                            seatLabel += "0" + str(row + 1)
                            
                            if row < 2:
                                trainsSeats[trainNo][0][row][col] = seatLabel
                            else:
                                trainsSeats[trainNo][0][row - 2][col] = seatLabel

                        input(f"Ticket ID {bookings[choice]["Ticket ID"]} has been cancelled successfully.")

                        del bookings[choice]


                        os.system("cls")
                        break

                    elif answer in ["NO", "N"]:
                        os.system("cls")
                        break
                    
                    else:
                        continue
                break     
            continue

# main entry point of the program
def main():
    # declare variables here
    fareList = [fareRandomize(), fareRandomize(), fareRandomize()]
    tierList = ["Gold" , "Silver", "Platinum"]

    # check train tier list
    # less than 100 is silver, less than 150 is gold, less than 200 is platinum
    for i in range(len(fareList) - 1):
        if fareList[i] < 100:
            tierList[i] = "Silver"
        elif fareList[i] < 150:
            tierList[i] = "Gold"
        else:
            tierList[i] = "Platinum"


    trains = [  ["101", f"{tierList[0]}", f"{destinationRandomize()}", f"{destinationRandomize()}", 32, f"RM {fareList[0]}", "8.00am", "11.00am"],
                ["102", f"{tierList[1]}", f"{destinationRandomize()}", f"{destinationRandomize()}", 32, f"RM {fareList[1]}", "22.00pm", "23.00pm"],
                ["103", f"{tierList[2]}", f"{destinationRandomize()}", f"{destinationRandomize()}", 32, f"RM {fareList[2]}", "18.00pm", "20.00pm"]
             ]
    
    table = tabulate(
        trains,
        headers=["Train No", "Train Type", "Origin", "Destination", "Available Seat", "Fare/Pax", "Departure", "Arrival"],
        tablefmt="fancy_grid",
        numalign="right",
        colalign=("center", "center", "center", "center", "center", "center", "center")
    )

    # store the trains seat for each train
    trainsSeats = [ templateSeats(), templateSeats(), templateSeats() ]

    bookings = []

    # main loop 
    while (True):
        
        drawMenu()

        choice = input("\nEnter your choice >> ")
        if choice == "1":
            os.system("cls")
            viewTrainSchedules(trains, bookings, trainsSeats, fareList)
        elif choice == "2":
            os.system("cls")
            bookTickets(trains, bookings, trainsSeats, fareList)
        elif choice == "3":
            os.system("cls")
            viewBookings(bookings, trains, fareList, trainsSeats)
        elif choice == "4":
            os.system("cls")
            cancelTickets(bookings, trains, trainsSeats, fareList)
        elif choice == "5":
            os.system("cls")
            refresh()
            print(tabulate([["Thanks for using our service, bye!"]], tablefmt="rounded_grid"))
            break

# call the main() function
main()
Editor is loading...
Leave a Comment