package main /* Sabre Simulator in Go by moshix */ /* Original implementation by Ernietech in REXX for VM/370 CMS */ /* then expanded with dynamic data and more SABRE commands, first in REXX and now in Go */ /* June 2025 in Gaschurn, Austria */ import ( "bufio" "fmt" "math/rand" "os" "os/exec" "runtime" "sort" "strconv" "strings" "time" ) // Data Structures var airports = make(map[string]string) var eqDesc = make(map[string]string) var seatConfig = make(map[string]string) var eqSeats = make(map[string]int) var airlines = make(map[int]string) var airlineCodes = make(map[string]string) var flightNumRange = make(map[string]string) var ffPartners = make(map[string]string) var queueType = make(map[int]string) var fareTypes = make(map[string]string) var paxTypes = make(map[string]string) var pnrData []PNR var flightSeats = make(map[string]int) var seatMap = make(map[string]map[string]string) var seatMapInit = make(map[string]bool) var ffData = make(map[string]FrequentFlyer) var queues = make(map[int][]string) var currentPNR = "" var matchList []Flight type PNR struct { PnrID string Name string Seat string FlightNum int Cancelled bool PaxType string } type Flight struct { Airline string FlightNum string DepCity string Date string DepTime string ArrTime string ArrCity string AvailSeats int EquipType string DepMinutes int // For sorting } type FrequentFlyer struct { Number string Airline string } // Initialization func initData() { // North America airports["ATL"] = "Atlanta Hartsfield-Jackson" airports["LAX"] = "Los Angeles International" airports["ORD"] = "Chicago O'Hare" airports["DFW"] = "Dallas/Fort Worth" airports["DEN"] = "Denver International" airports["JFK"] = "New York Kennedy" airports["SFO"] = "San Francisco International" airports["LAS"] = "Las Vegas McCarran" airports["SEA"] = "Seattle-Tacoma" airports["MCO"] = "Orlando International" airports["EWR"] = "Newark Liberty" airports["MIA"] = "Miami International" airports["PHX"] = "Phoenix Sky Harbor" airports["IAH"] = "Houston Bush" airports["BOS"] = "Boston Logan" airports["MSP"] = "Minneapolis-St Paul" airports["DTW"] = "Detroit Metro" airports["FLL"] = "Fort Lauderdale" airports["CLT"] = "Charlotte Douglas" airports["LGA"] = "New York LaGuardia" airports["BWI"] = "Baltimore/Washington" airports["SLC"] = "Salt Lake City" airports["YYZ"] = "Toronto Pearson" airports["YVR"] = "Vancouver International" airports["YUL"] = "Montreal Trudeau" // Europe airports["LHR"] = "London Heathrow" airports["CDG"] = "Paris Charles de Gaulle" airports["AMS"] = "Amsterdam Schiphol" airports["FRA"] = "Frankfurt International" airports["IST"] = "Istanbul International" airports["MAD"] = "Madrid Barajas" airports["BCN"] = "Barcelona El Prat" airports["LGW"] = "London Gatwick" airports["MUC"] = "Munich International" airports["FCO"] = "Rome Fiumicino" airports["SVO"] = "Moscow Sheremetyevo" airports["DME"] = "Moscow Domodedovo" airports["DUB"] = "Dublin International" airports["ZRH"] = "Zurich International" airports["CPH"] = "Copenhagen Kastrup" airports["OSL"] = "Oslo Gardermoen" airports["ARN"] = "Stockholm Arlanda" airports["VIE"] = "Vienna International" airports["BRU"] = "Brussels International" airports["MXP"] = "Milan Malpensa" // Asia Pacific airports["PEK"] = "Beijing Capital" airports["HND"] = "Tokyo Haneda" airports["DXB"] = "Dubai International" airports["HKG"] = "Hong Kong International" airports["ICN"] = "Seoul Incheon" airports["BKK"] = "Bangkok Suvarnabhumi" airports["SIN"] = "Singapore Changi" airports["CGK"] = "Jakarta Soekarno-Hatta" airports["KUL"] = "Kuala Lumpur International" airports["DEL"] = "Delhi Indira Gandhi" airports["BOM"] = "Mumbai International" airports["SYD"] = "Sydney Kingsford Smith" airports["MEL"] = "Melbourne International" airports["AKL"] = "Auckland International" airports["KIX"] = "Osaka Kansai" airports["TPE"] = "Taipei Taoyuan" airports["MNL"] = "Manila Ninoy Aquino" airports["CAN"] = "Guangzhou Baiyun" airports["PVG"] = "Shanghai Pudong" airports["NRT"] = "Tokyo Narita" // Middle East & Africa airports["DOH"] = "Doha Hamad" airports["AUH"] = "Abu Dhabi International" airports["CAI"] = "Cairo International" airports["JNB"] = "Johannesburg O.R. Tambo" airports["CPT"] = "Cape Town International" airports["TLV"] = "Tel Aviv Ben Gurion" airports["BAH"] = "Bahrain International" airports["RUH"] = "Riyadh King Khalid" airports["JED"] = "Jeddah King Abdulaziz" airports["MCT"] = "Muscat International" // Latin America airports["GRU"] = "Sao Paulo Guarulhos" airports["MEX"] = "Mexico City International" airports["BOG"] = "Bogota El Dorado" airports["LIM"] = "Lima Jorge Chavez" airports["SCL"] = "Santiago International" airports["GIG"] = "Rio de Janeiro Galeao" airports["EZE"] = "Buenos Aires Ezeiza" airports["PTY"] = "Panama City Tocumen" airports["CUN"] = "Cancun International" airports["UIO"] = "Quito International" // Equipment eqDesc["A320"] = "Airbus A320-100" eqDesc["B738"] = "Boeing 737-800" eqDesc["B789"] = "Boeing 787-9" eqDesc["A350"] = "Airbus A350-100" eqDesc["B77W"] = "Boeing 777-300ER" eqDesc["B747"] = "Boeing 747-800" eqDesc["B767"] = "Boeing 767-300" eqDesc["B757"] = "Boeing 757-200" eqDesc["MD981"] = "MD-8-81" // Format: ROWS|LAYOUT|EXIT ROWS|FIRST CLASS ROWS|WHEELCHAIR ROWS|BASSINET ROWS|BLOCKED SEATS seatConfig["A320"] = "27|ABC DEF|11 12|1-4|1 27|12|14B 14E" seatConfig["B738"] = "28|ABC DEF|14 15|1-4|1 28|15|16B 16E" seatConfig["B789"] = "42|ABC DEF GHJ|24 25|1-5|1 42|25|26E 26F" seatConfig["A350"] = "44|ABC DEF GHJ|24 25|1-5|1 44|25|26E 26F" seatConfig["B77W"] = "51|ABC DEFG HJK|24 25|1-4|1 51|25|26E 26F" seatConfig["B747"] = "58|ABC DEFG HJK|24 25|1-4|1 58|25|26E 26F" seatConfig["B767"] = "34|ABC DEF GH|20 21|1-3|1 34|21|22D 22E" seatConfig["B757"] = "25|ABC DEF|14 15|1-3|1 25|15|16B 16E" seatConfig["MD981"] = "23|ABC DE|10 11|1-2|1 23|11|12B 12D" eqSeats["A320"] = 158 eqSeats["B738"] = 162 eqSeats["B789"] = 290 eqSeats["A350"] = 308 eqSeats["B77W"] = 357 eqSeats["B747"] = 412 eqSeats["B767"] = 203 eqSeats["B757"] = 152 eqSeats["MD981"] = 109 // Airlines airlines[1] = "AAL" airlines[2] = "DAL" airlines[3] = "LH" airlineCodes["AAL"] = "American Airlines" airlineCodes["DAL"] = "Delta Airlines" airlineCodes["LH"] = "Lufthansa" flightNumRange["AAL"] = "100 999" flightNumRange["DAL"] = "1000 1999" flightNumRange["LH"] = "400 799" // Partners ffPartners["AAL"] = "BA QF CX JL" ffPartners["DAL"] = "AF KL VS KE" ffPartners["LH"] = "AC OS SN LX" // Queues queueType[1] = "GENERAL" queueType[2] = "TICKETING" queueType[3] = "SCHEDULE" queueType[4] = "WAITLIST" queueType[5] = "SPECIAL" // Fares fareTypes["Y"] = "FULL" fareTypes["B"] = "FLEX" fareTypes["M"] = "ECON" fareTypes["Q"] = "DISC" // Passenger Types paxTypes["ADT"] = "ADULT" paxTypes["CHD"] = "CHILD" paxTypes["INF"] = "INFANT" paxTypes["SNR"] = "SENIOR" paxTypes["STU"] = "STUDENT" } // Main Function func main() { rand.Seed(time.Now().UnixNano()) initData() clearScreen() fmt.Println(strings.Repeat("-", 14), "SABRE Computer Reservation System (CRS)", strings.Repeat("-", 15)) today := time.Now().Format("02.01.2006") fmt.Println(today, "WAFL:CONE") scanner := bufio.NewScanner(os.Stdin) // Sign-in loop for { fmt.Println() fmt.Println("* Enter Agent Sign-in (e.g. SIA*01762):") if !scanner.Scan() { return } input := strings.ToUpper(strings.TrimSpace(scanner.Text())) if strings.HasPrefix(input, "SIA*") { break } fmt.Println("*** Sign-in must start with SIA*") } fmt.Println() fmt.Println("* SABRE DEMO: Type HELP for instructions") // Main command loop for { fmt.Println() if !scanner.Scan() { break } cmd := strings.ToUpper(strings.TrimSpace(scanner.Text())) if cmd == "" { continue } parts := strings.Fields(cmd) verb := parts[0] switch { case verb == "HELP": showHelp() case verb == "SO*": fmt.Println("Agent Sign Out complete") return case strings.HasPrefix(cmd, "4G"): handleSeatMap(cmd) case verb == "PNR": handlePnrLookup(parts) case verb == "BOOK": handleBook(parts, scanner) case strings.HasPrefix(verb, "FF"): handleFrequentFlyer(cmd) case strings.HasPrefix(verb, "Q/"): handleQueue(cmd) case strings.HasPrefix(verb, "WP/"): handlePricing(cmd) case strings.HasPrefix(verb, "N/"): handleNameChange(cmd) case verb == "CANCEL": handleCancel(parts) case strings.HasPrefix(verb, "W/EQ"): handleEquipQuery(cmd) default: handleFlightQuery(parts) } } } // Command Handlers func showHelp() { fmt.Println("QUERY FORMAT: 18OCT JFK ZRH (optional: 9A)") fmt.Println("BOOK Book flight Index number (e.g. BOOK 1 or BOOK 3)") fmt.Println("CANCEL Cancel booking (e.g. CANCEL 001 or CANCEL PNR001)") fmt.Println("PNR Display booking details (e.g. PNR 001 or PNR PNR001)") fmt.Println("W/EQ* Filter flights by equipment code (e.g. A320 or B738)") fmt.Println("FF Display FF info for PNR (e.g. FF001 or FFPNR001)") fmt.Println("FF/ Add FF number to PNR (e.g. FF001/AA123456)") fmt.Println("FF/* Delete FF number from PNR") fmt.Println("Q/C Display queue counts") fmt.Println("Q/P// Place PNR in queue n") fmt.Println("WP/NCB Price and book lowest available fare for segment n") fmt.Println("WP/NI Display alternate fare options") fmt.Println("N/ADD- Add passenger name (N/ADD-1 DOE/JOHN ADT)") fmt.Println("N/CHG- Change passenger name (N/CHG-1 DOE/JANE)") fmt.Println("4G* Display seat map for segment n") fmt.Println("4GPNR Display seat map for PNR") fmt.Println("4G...S Assign seat (e.g., 4G1S2A or 4GPNR001S2A)") fmt.Println("SO* Sign Out all Work Areas") } func handleFlightQuery(parts []string) { if len(parts) < 3 { fmt.Println("*** Invalid query format. Use: DATE FROM TO [TIME]") fmt.Println("*** Example: 18OCT JFK ZRH 9A") return } date, from, to := parts[0], parts[1], parts[2] var timeFilter string if len(parts) > 3 { timeFilter = parts[3] } if !isValidAirport(from) { fmt.Println("*** Invalid departure airport code:", from) return } if !isValidAirport(to) { fmt.Println("*** Invalid arrival airport code:", to) return } if from == to { fmt.Println("*** Departure and arrival airports cannot be the same") return } fmt.Printf("Route: %s (%s) to %s (%s)\n", from, getAirportName(from), to, getAirportName(to)) flights := generateFlights(date, from, to) displayFlights(date, from, to, timeFilter, flights) } func handleBook(parts []string, scanner *bufio.Scanner) { if len(parts) < 2 { fmt.Println("*** Invalid command") return } num, err := strconv.Atoi(parts[1]) if err != nil || num < 1 || num > len(matchList) { fmt.Println("*** Invalid flight number") return } flight := matchList[num-1] flightKey := getFlightKey(flight) seats, ok := flightSeats[flightKey] if !ok { // If not in map, initialize from original flight data seats = flight.AvailSeats flightSeats[flightKey] = seats } if seats <= 0 { fmt.Println("*** Flight is full") return } fmt.Println("* Enter passenger name as (no spaces):") scanner.Scan() pname := strings.ToUpper(scanner.Text()) seat := findNextAvailableSeat(flight) if seat == "" { fmt.Println("*** No seats available on this flight.") return } pnrID := fmt.Sprintf("PNR%03d", len(pnrData)+1) pnr := PNR{ PnrID: pnrID, Name: pname, Seat: seat, FlightNum: num, PaxType: "ADT", } pnrData = append(pnrData, pnr) flightSeats[flightKey]-- fmt.Printf("BOOKED %s PASSENGER %s SEAT %s\n", pnrID, pname, seat) currentPNR = pnrID } func handleCancel(parts []string) { if len(parts) < 2 { fmt.Println("*** PNR number required") return } pnrID := normalizePnr(parts[1]) found := false for i := range pnrData { if pnrData[i].PnrID == pnrID { if pnrData[i].Cancelled { fmt.Println("*** PNR already cancelled") found = true break } pnrData[i].Cancelled = true flight := matchList[pnrData[i].FlightNum-1] flightKey := getFlightKey(flight) flightSeats[flightKey]++ // Free up seat if _, ok := seatMap[flightKey]; ok { delete(seatMap[flightKey], pnrData[i].Seat) } fmt.Println("CANCELLED booking", pnrID) found = true break } } if !found { fmt.Println("*** PNR", pnrID, "not found.") } } func handlePnrLookup(parts []string) { if len(parts) < 2 { fmt.Println("*** PNR number required") return } pnrID := normalizePnr(parts[1]) found := false for _, pnr := range pnrData { if pnr.PnrID == pnrID { if pnr.Cancelled { flight := matchList[pnr.FlightNum-1] flightStr := fmt.Sprintf("%s%s %s-%s", flight.Airline, flight.FlightNum, flight.DepCity, flight.ArrCity) fmt.Printf("PNR %s => %s %s CANCELLED\n", pnr.PnrID, pnr.Name, flightStr) } else { flight := matchList[pnr.FlightNum-1] flightStr := fmt.Sprintf("%s%s %s-%s", flight.Airline, flight.FlightNum, flight.DepCity, flight.ArrCity) fmt.Printf("PNR %s => %s SEAT %s %s\n", pnr.PnrID, pnr.Name, pnr.Seat, flightStr) fmt.Printf(" Type: %s\n", paxTypes[pnr.PaxType]) if ff, ok := ffData[pnr.PnrID]; ok { fmt.Printf(" FF: %s (%s)\n", ff.Number, ff.Airline) } currentPNR = pnr.PnrID } found = true break } } if !found { fmt.Println("*** PNR", pnrID, "not found.") } } func handleEquipQuery(cmd string) { parts := strings.Split(cmd, "*") if len(parts) < 2 || parts[1] == "" { fmt.Println("Available Equipment Types:") fmt.Println("------------------------") // Sort codes for consistent output var codes []string for code := range eqDesc { codes = append(codes, code) } sort.Strings(codes) for _, code := range codes { fmt.Printf("%-5s - %-20s %4d seats\n", code, eqDesc[code], eqSeats[code]) } return } eqQuery := parts[1] eqName, ok := eqDesc[eqQuery] if !ok { eqName = "Unknown" } fmt.Printf("Flights with equipment %s - %s\n", eqQuery, eqName) fmt.Println(" # ARLN FLTN DEPC/ARVC DEPT ARRT AVST EQTYP") fmt.Println(strings.Repeat("-", 71)) foundCount := 0 for i, f := range matchList { if f.EquipType == eqQuery { fmt.Printf("%2d %-5s %-7s %-11s %-7s %-7s %-6d %s\n", i+1, f.Airline, f.FlightNum, f.DepCity+"/"+f.ArrCity, f.DepTime, f.ArrTime, f.AvailSeats, f.EquipType) foundCount++ } } if foundCount == 0 { fmt.Println("*** NO FLIGHTS WITH THAT EQUIPMENT.") } } func handleFrequentFlyer(cmd string) { cmd = strings.TrimPrefix(cmd, "FF") parts := strings.Split(cmd, "/") pnrRaw := parts[0] pnrID := normalizePnr(pnrRaw) pnr, pnrIdx := findPNR(pnrID) if pnrIdx == -1 { fmt.Println("*** PNR", pnrID, "not found") return } if pnr.Cancelled { fmt.Println("*** PNR", pnrID, "is cancelled") return } if len(parts) == 1 { // Display FF if ff, ok := ffData[pnrID]; ok { fmt.Printf("FF: %s (%s) for %s\n", ff.Number, ff.Airline, pnrID) } else { fmt.Println("No FF number stored for", pnrID) } } else if len(parts) > 1 { ffNum := parts[1] if ffNum == "*" { // Delete FF delete(ffData, pnrID) fmt.Println("Frequent flyer number deleted for", pnrID) } else { // Add/Update FF ff := FrequentFlyer{ Number: ffNum, Airline: ffNum[:2], } ffData[pnrID] = ff fmt.Printf("Frequent flyer number %s added for %s\n", ffNum, pnrID) } } } func handleQueue(cmd string) { parts := strings.Split(cmd, "/") if len(parts) < 2 { fmt.Println("*** Invalid queue command") return } qCmd := parts[1] switch qCmd { case "C": fmt.Println("Queue Counts:") fmt.Println("------------") for i := 1; i <= len(queueType); i++ { qList := queues[i] fmt.Printf("Queue %d (%s): %d PNRs\n", i, queueType[i], len(qList)) if len(qList) > 0 { fmt.Println(" PNRs:") for _, pnrID := range qList { fmt.Println(" -", pnrID) } } } case "P": if len(parts) < 4 { fmt.Println("*** Invalid format. Use Q/P//") return } qNum, err := strconv.Atoi(parts[2]) if err != nil || qNum < 1 || qNum > len(queueType) { fmt.Println("*** Invalid queue number") return } pnrID := normalizePnr(parts[3]) _, pnrIdx := findPNR(pnrID) if pnrIdx == -1 { fmt.Println("*** PNR", pnrID, "not found") return } // Add to queue queues[qNum] = append(queues[qNum], pnrID) fmt.Printf("PNR %s placed in queue %d (%s)\n", pnrID, qNum, queueType[qNum]) default: fmt.Println("*** Invalid queue command") } } func handlePricing(cmd string) { parts := strings.Split(cmd, "/") if len(parts) < 2 { fmt.Println("*** Invalid pricing command") return } pCmd := parts[1] switch pCmd { case "NCB": // Price and book lowest if len(parts) < 3 { fmt.Println("*** Segment number required") return } fmt.Println("Searching for lowest available fare...") // Mock implementation for code, name := range fareTypes { price := 100 + rand.Intn(901) fmt.Printf("%s class (%s): $%d.00\n", code, name, price) } case "NI": // Display alternate fares fmt.Println("Searching for alternative fares...") // Mock implementation for code, name := range fareTypes { price := 100 + rand.Intn(901) fmt.Printf("%s class (%s): $%d.00\n", code, name, price) } default: fmt.Println("*** Invalid pricing command. Use WP/NCB or WP/NI") } } func handleNameChange(cmd string) { cmd = strings.TrimPrefix(cmd, "N/") parts := strings.Split(cmd, "-") if len(parts) < 2 { fmt.Println("*** Invalid name command.") return } cmdType := parts[0] rest := strings.Join(parts[1:], "-") paxData := strings.Fields(rest) paxNum, err := strconv.Atoi(paxData[0]) if err != nil { fmt.Println("*** Invalid passenger number") return } if paxNum < 1 || paxNum > len(pnrData) { fmt.Println("*** Invalid PNR number") return } pnrIdx := paxNum - 1 pnr := &pnrData[pnrIdx] switch cmdType { case "ADD", "CHG": nameParts := strings.Split(paxData[1], "/") if len(nameParts) < 2 { fmt.Println("*** Invalid name format. Use LAST/FIRST") return } newName := nameParts[0] + "/" + nameParts[1] pnr.Name = newName if cmdType == "ADD" { paxType := "ADT" if len(paxData) > 2 { if _, ok := paxTypes[paxData[2]]; ok { paxType = paxData[2] } else { fmt.Println("*** Invalid passenger type. Defaulting to ADT.") } } pnr.PaxType = paxType fmt.Printf("Added/Updated passenger: %s Type: %s to %s\n", pnr.Name, paxTypes[pnr.PaxType], pnr.PnrID) } else { // CHG fmt.Printf("Changed passenger name to: %s in %s\n", pnr.Name, pnr.PnrID) } default: fmt.Println("*** Invalid name command. Use N/ADD- or N/CHG-") } } func handleSeatMap(cmd string) { cmd = strings.TrimPrefix(cmd, "4G") var flight Flight var pnr *PNR // Seat assignment command if strings.Contains(cmd, "S") { parts := strings.Split(cmd, "S") if len(parts) < 2 { fmt.Println("*** Invalid seat assignment format.") return } target := parts[0] seatAssign := parts[1] var pnrID string if strings.HasPrefix(target, "PNR") { pnrID = normalizePnr(strings.TrimPrefix(target, "PNR")) } else { // 4G1S2A format, use current PNR pnrID = currentPNR } if pnrID == "" { fmt.Println("*** NO ACTIVE PNR - CANNOT ASSIGN SEAT") return } pnr, pnrIdx := findPNR(pnrID) if pnrIdx == -1 { fmt.Println("*** PNR", pnrID, "NOT FOUND") return } flight = matchList[pnr.FlightNum-1] flightKey := getFlightKey(flight) config := getSeatConfig(flight.EquipType) // Validate seat var seatRow int var seatLetter string for i, r := range seatAssign { if r < '0' || r > '9' { seatRow, _ = strconv.Atoi(seatAssign[:i]) seatLetter = seatAssign[i:] break } } if seatRow == 0 || seatLetter == "" { fmt.Println("*** Invalid seat format - must be ROW+LETTER (e.g., 2A)") return } if seatRow < 1 || seatRow > config.rows { fmt.Printf("*** Invalid row number %d for %s\n", seatRow, eqDesc[flight.EquipType]) return } if !strings.Contains(config.layout, seatLetter) { fmt.Printf("*** Invalid seat letter %s for %s\n", seatLetter, eqDesc[flight.EquipType]) return } // Check availability newSeat := fmt.Sprintf("%d%s", seatRow, seatLetter) if status, ok := seatMap[flightKey][newSeat]; ok && status == "X" { fmt.Println("*** Seat", newSeat, "is not available") return } if strings.Contains(config.blockedSeats, newSeat) { fmt.Println("*** Seat", newSeat, "is blocked") return } // Release old seat if pnr.Seat != "" && seatMap[flightKey] != nil { delete(seatMap[flightKey], pnr.Seat) } // Assign new seat if seatMap[flightKey] == nil { seatMap[flightKey] = make(map[string]string) } seatMap[flightKey][newSeat] = "X" pnrData[pnrIdx].Seat = newSeat fmt.Printf("Seat changed to %s for %s\n", newSeat, pnrID) return } // Seat map display command if strings.HasPrefix(cmd, "PNR") { pnrID := normalizePnr(strings.TrimPrefix(cmd, "PNR")) var pnrIdx int pnr, pnrIdx = findPNR(pnrID) if pnrIdx == -1 { fmt.Println("*** PNR", pnrID, "NOT FOUND") return } currentPNR = pnrID } if currentPNR == "" { fmt.Println("*** NO ACTIVE PNR - DISPLAY PNR FIRST") return } pnr, pnrIdx := findPNR(currentPNR) if pnrIdx == -1 { fmt.Println("*** PNR", currentPNR, "NOT FOUND") return } flight = matchList[pnr.FlightNum-1] displaySeatMapForFlight(flight) } func displaySeatMapForFlight(f Flight) { flightKey := getFlightKey(f) config := getSeatConfig(f.EquipType) if config.rows == 0 { fmt.Println("*** Invalid equipment type:", f.EquipType) return } // Initialize seat map with random occupied seats if not already done if !seatMapInit[flightKey] { totalSeats := eqSeats[f.EquipType] availSeats := f.AvailSeats seatsToOccupy := totalSeats - availSeats if seatsToOccupy > 0 { allSeats := []string{} for r := 1; r <= config.rows; r++ { for _, c := range config.layout { if c == ' ' { continue } seatCode := fmt.Sprintf("%d%c", r, c) if strings.Contains(config.blockedSeats, seatCode) { continue } allSeats = append(allSeats, seatCode) } } rand.Shuffle(len(allSeats), func(i, j int) { allSeats[i], allSeats[j] = allSeats[j], allSeats[i] }) if seatMap[flightKey] == nil { seatMap[flightKey] = make(map[string]string) } for i := 0; i < seatsToOccupy && i < len(allSeats); i++ { seatMap[flightKey][allSeats[i]] = "X" } } seatMapInit[flightKey] = true } fmt.Printf("SEAT MAP FOR %s%s %s\n", f.Airline, f.FlightNum, eqDesc[f.EquipType]) fmt.Printf("DATE: %s SEGMENT: 1\n\n", f.Date) // Display header fmt.Printf(" %s\n", config.layout) // Display map for r := 1; r <= config.rows; r++ { fmt.Printf("%2d ", r) for _, c := range config.layout { if c == ' ' { fmt.Print(" ") continue } seatCode := fmt.Sprintf("%d%c", r, c) status := "A" // Available if s, ok := seatMap[flightKey][seatCode]; ok { status = s // Occupied } else if strings.Contains(config.blockedSeats, seatCode) { status = "M" // Blocked Middle } else if strings.Contains(config.firstRows, fmt.Sprintf(" %d ", r)) { status = "F" // First Class } else if strings.Contains(config.exitRows, fmt.Sprintf(" %d ", r)) { status = "E" // Exit } else if strings.Contains(config.wheelRows, fmt.Sprintf(" %d ", r)) { status = "W" // Wheelchair } else if strings.Contains(config.bassRows, fmt.Sprintf(" %d ", r)) { status = "C" // Bassinet } fmt.Print(status) } fmt.Println() } fmt.Println() fmt.Println("A=AVAILABLE X=OCCUPIED E=EXIT ROW F=FIRST CLASS") fmt.Println("B=BLOCKED W=WHEELCHAIR C=BASSINET M=BLOCKED MIDDLE") } // Flight Generation and Display func generateFlights(date, depApt, arrApt string) []Flight { var flights []Flight usedNums := make(map[string]bool) routeEquipment := getRouteEquipment(depApt, arrApt) for i := 1; i <= len(airlines); i++ { airline := airlines[i] numFlights := rand.Intn(4) + 2 // 2-5 flights for f := 0; f < numFlights; f++ { flightNumStr := getUniqueFlightNum(airline, usedNums) usedNums[airline+flightNumStr] = true depTimeMins := rand.Intn(1380-360) + 360 // 6:00 to 23:00 depTimeMins = (depTimeMins / 5) * 5 // Round to 5 mins eqType := routeEquipment[rand.Intn(len(routeEquipment))] flightDur := getFlightTime(depApt, arrApt) arrTimeMins := depTimeMins + flightDur maxSeats, _ := eqSeats[eqType] minSeats := int(float64(maxSeats) * 0.2) maxAvail := int(float64(maxSeats) * 0.95) availSeats := rand.Intn(maxAvail-minSeats+1) + minSeats flights = append(flights, Flight{ Airline: airline, FlightNum: flightNumStr, DepCity: depApt, Date: date, DepTime: formatTime(depTimeMins), ArrTime: formatTime(arrTimeMins), ArrCity: arrApt, AvailSeats: availSeats, EquipType: eqType, DepMinutes: depTimeMins, }) } } // Sort flights by departure time sort.Slice(flights, func(i, j int) bool { return flights[i].DepMinutes < flights[j].DepMinutes }) return flights } func displayFlights(date, from, to, timeFilter string, flights []Flight) { fmt.Println() fmt.Printf("%s %s/%s ----------------------\n", date, from, to) fmt.Println(" # ARLN FLTN DEPC/ARVC DEPT ARRT AVST EQTYP") fmt.Println(strings.Repeat("-", 71)) matchList = nil // Clear previous list var targetMins = -1 if timeFilter != "" { ampm := timeFilter[len(timeFilter)-1:] hrStr := timeFilter[:len(timeFilter)-1] hr, err := strconv.Atoi(hrStr) if err == nil { targetMins = hr * 60 if ampm == "P" && hr != 12 { targetMins += 12 * 60 } if ampm == "A" && hr == 12 { targetMins = 0 // Midnight } } } for _, f := range flights { if targetMins != -1 { diff := f.DepMinutes - targetMins if diff < 0 { diff = -diff } if diff > 120 { // 2 hour window continue } } matchList = append(matchList, f) idx := len(matchList) flightKey := getFlightKey(f) flightSeats[flightKey] = f.AvailSeats // Initialize seat count fmt.Printf("%2d %-5s %-7s %-11s %-7s %-7s %-6d %s\n", idx, f.Airline, f.FlightNum, f.DepCity+"/"+f.ArrCity, f.DepTime, f.ArrTime, f.AvailSeats, f.EquipType) } if len(matchList) == 0 { fmt.Println("NO FLIGHTS AVAILABLE FOR THAT QUERY") } } // Utility Functions func clearScreen() { if runtime.GOOS == "windows" { cmd := exec.Command("cmd", "/c", "cls") cmd.Stdout = os.Stdout cmd.Run() } else { cmd := exec.Command("clear") cmd.Stdout = os.Stdout cmd.Run() } } func isValidAirport(code string) bool { _, ok := airports[code] return ok && len(code) == 3 } func getAirportName(code string) string { return airports[code] } func getRouteEquipment(dep, arr string) []string { depRegion := getAirportRegion(dep) arrRegion := getAirportRegion(arr) if depRegion == arrRegion { if depRegion == "NA" { return []string{"A320", "A320", "B738", "B738", "B738"} } return []string{"A320", "B738", "A320", "B738", "A320"} } if (depRegion == "NA" && arrRegion == "EU") || (depRegion == "EU" && arrRegion == "NA") { return []string{"B789", "A350", "B77W", "B789", "A350"} } if (depRegion == "NA" && arrRegion == "AP") || (depRegion == "AP" && arrRegion == "NA") { return []string{"B789", "B77W", "B77W", "B789", "B77W"} } return []string{"B789", "A350", "B77W", "A350", "B789"} } func getAirportRegion(code string) string { na := "JFK LAX ORD DFW DEN SFO LAS SEA MCO EWR MIA PHX IAH BOS MSP DTW FLL CLT LGA BWI SLC YYZ YVR YUL" eu := "LHR CDG AMS FRA IST MAD BCN LGW MUC FCO SVO DME DUB ZRH CPH OSL ARN VIE BRU MXP" ap := "PEK HND HKG ICN BKK SIN CGK KUL DEL BOM SYD MEL AKL KIX TPE MNL CAN PVG NRT" me := "DXB DOH AUH CAI JNB CPT TLV BAH RUH JED MCT" la := "GRU MEX BOG LIM SCL GIG EZE PTY CUN UIO" if strings.Contains(na, code) { return "NA" } if strings.Contains(eu, code) { return "EU" } if strings.Contains(ap, code) { return "AP" } if strings.Contains(me, code) { return "ME" } if strings.Contains(la, code) { return "LA" } return "OT" } func getUniqueFlightNum(airline string, used map[string]bool) string { ranges := strings.Fields(flightNumRange[airline]) min, _ := strconv.Atoi(ranges[0]) max, _ := strconv.Atoi(ranges[1]) for { num := rand.Intn(max-min+1) + min numStr := fmt.Sprintf("%04d", num) if !used[airline+numStr] { return numStr } } } func getFlightTime(dep, arr string) int { return 60 + rand.Intn(661) // 1-12 hours } func formatTime(mins int) string { hours := mins / 60 minutes := mins % 60 ampm := "A" if hours >= 12 { ampm = "P" if hours > 12 { hours -= 12 } } if hours == 0 { hours = 12 } return fmt.Sprintf("%02d%02d%s", hours, minutes, ampm) } func getFlightKey(f Flight) string { return fmt.Sprintf("%s%s%s%s%s%s", f.Airline, f.FlightNum, f.DepCity, f.ArrCity, f.Date, f.DepTime) } func findNextAvailableSeat(f Flight) string { flightKey := getFlightKey(f) config := getSeatConfig(f.EquipType) if _, ok := seatMap[flightKey]; !ok { seatMap[flightKey] = make(map[string]string) } for r := 1; r <= config.rows; r++ { for _, c := range config.layout { if c == ' ' { continue } seatCode := fmt.Sprintf("%d%c", r, c) if strings.Contains(config.blockedSeats, seatCode) { continue } if _, occupied := seatMap[flightKey][seatCode]; !occupied { seatMap[flightKey][seatCode] = "X" return seatCode } } } return "" } func normalizePnr(pnrStr string) string { pnrStr = strings.ToUpper(pnrStr) if strings.HasPrefix(pnrStr, "PNR") { return pnrStr } num, err := strconv.Atoi(pnrStr) if err != nil { return "PNR" + pnrStr // Return as is if not numeric } return fmt.Sprintf("PNR%03d", num) } func findPNR(pnrID string) (*PNR, int) { for i := range pnrData { if pnrData[i].PnrID == pnrID { return &pnrData[i], i } } return nil, -1 } type seatConfigParts struct { rows int layout string exitRows string firstRows string wheelRows string bassRows string blockedSeats string } func getSeatConfig(eqType string) seatConfigParts { configStr, ok := seatConfig[eqType] if !ok { return seatConfigParts{} } parts := strings.Split(configStr, "|") rows, _ := strconv.Atoi(parts[0]) // Pad row numbers with spaces for accurate word-based searching padRows := func(s string) string { var padded []string for _, w := range strings.Fields(s) { padded = append(padded, " "+w+" ") } return strings.Join(padded, "") } return seatConfigParts{ rows: rows, layout: parts[1], exitRows: padRows(parts[2]), firstRows: padRows(parts[3]), wheelRows: padRows(parts[4]), bassRows: padRows(parts[5]), blockedSeats: parts[6], } }