1.はじめに
どうも、趣味でデータ分析している猫背なエンジニアです。
今回はクソアプリ企画に参加していこうと思います。
警告
これはクソアプリです。
実際の情報とは乖離します。ご注意ください。
2.「MS-DOS風」フライト情報CLIアプリ
■ 概要
みなさまは、よく飛行機に乗りますか?私はもちろん乗りません。ですが、たまにフライト情報を見たい時があります。そんなときに気軽に情報を取得できるものがあれば最高ですよね。しかもローカルで動くフライト情報表示アプリ。
■ 機能設計
機能としては以下のようになっています。
〇 それっぽいスケジュール
〇 ANSIカラーで無理やりDOS感を演出
〇 ENTERでリロードすると状況が変わる
〇 たまに勝手に遅延・欠航する
〇 うるさい広告バナー付き
〇 何もしてないのにビープ音が鳴る
■ 動作イメージ
MS-DOS Flight Info v0.13 (Crappy Edition)
┌──────────────────────────────────────────────────────────────┐
│ コマンド: [L]ist [V]iew <flight> [S]earch [R]efresh [Q]uit │
└──────────────────────────────────────────────────────────────┘
!!! 飛行機保険? NO THANKS!! Buy Now!! (typo free?)
FLIGHT FROM TO SCH STATUS GATE REMARKS
--------------------------------------------------------------------------------
ANA4890 SIN HND 2025-12-20 12:55 Departed 6A Check-in closes 30 min before
ND7095 SFO CTS 2025-12-20 13:01 On Time 2A Crew change
JAL6949 KIX FUK 2025-12-20 13:16 Cancelled 24A Weather hold
TOK3761 SFO HND 2025-12-20 13:19 Arrived 33B
FUJ1193 SFO KIX 2025-12-20 13:28 Boarding 26B Check-in closes 30 min before
FUJ125 LAX CTS 2025-12-20 13:55 On Time 3A Crew change
ANA9325 SFO CTS 2025-12-20 14:32 Delayed 26C Check-in closes 30 min before
NRT9509 KIX SFO 2025-12-20 14:53 Delayed 16A
SKY9547 SIN ICN 2025-12-20 15:19 Boarding 12B Check-in closes 30 min before
SKY6205 OKA NGO 2025-12-20 15:34 Arrived 33B Weather hold
NRT8216 FUK OKA 2025-12-20 16:07 Departed 12C Check-in closes 30 min before
JAL6727 LAX KIX 2025-12-20 16:08 Delayed 9B Check-in closes 30 min before
SKY4682 NGO KIX 2025-12-20 16:14 Gate Changed 14C
FUJ5161 OKA HKG 2025-12-20 16:40 Gate Changed 22A
ND158 NGO NRT 2025-12-20 16:49 On Time 32C Operated by partner
NRT8900 LAX NGO 2025-12-20 18:11 Arrived 32B
JC7446 ICN NGO 2025-12-20 18:12 Departed 34B
JAL2422 OKA CTS 2025-12-20 18:17 Gate Changed 15A Operated by partner
コマンドを入力して下さい >
■ フライト情報の生成
特殊なライブラリは一つも使っていません。
航空会社コード / 発着地 / 出発予定時刻 / ステータス(On Time / Delayed / Cancelled など) / ゲート番号は気合で生成しています。
最近のPCは素晴らしいですね。
■ 重要イベントでビープ音
遅延・欠航が発生すると…… ピッと音が鳴る設計にしています。
これで急いでいるときに何かあっても安心ですね。
■ 検索・詳細表示も一応ある
検索機能も搭載しており、自分が搭乗する便が通常通り動いているかも確認できますね。
S で検索(便名 / 出発地 / 到着地)
V JC012 で詳細表示
■ ソースコード
import os
import sys
import time
import random
from datetime import datetime, timedelta
# --- MS-DOS風カラー(ANSI) ---
CSI = "\x1b["
RESET = CSI + "0m"
BOLD = CSI + "1m"
INVERT = CSI + "7m"
FG_GREEN = CSI + "32m"
FG_CYAN = CSI + "36m"
FG_YELLOW = CSI + "33m"
FG_RED = CSI + "31m"
FG_WHITE = CSI + "37m"
def cls():
if os.name == "nt":
os.system("cls")
else:
os.system("clear")
# --- Data model (ランダム生成) ---
AIRLINES = ["JC", "ND", "SKY", "FUJ", "TOK", "NRT", "ANA", "JAL"]
CITIES = ["NRT", "HND", "KIX", "CTS", "FUK", "OKA", "NGO", "SFO", "LAX", "HKG", "SIN", "ICN"]
GATES = [f"{i}{c}" for i in range(1,35) for c in ["A","B","C"]][:200]
STATUSES = ["On Time", "Boarding", "Delayed", "Cancelled", "Gate Changed", "Departed", "Arrived"]
def random_time_from(now, mins_min=10, mins_max=360):
return now + timedelta(minutes=random.randint(mins_min, mins_max))
def generate_flights(n=18):
now = datetime.now()
flights = []
for i in range(n):
airline = random.choice(AIRLINES)
num = random.randint(1,9999)
flight_no = f"{airline}{num:03d}"
origin = random.choice(CITIES)
dest = random.choice([c for c in CITIES if c != origin])
sched = random_time_from(now, mins_min=5, mins_max=480)
# random status with bias toward On Time
status = random.choices(STATUSES, weights=[50,15,15,2,6,8,4])[0]
# if status is Departed/Arrived adjust times
if status == "Departed":
actual = sched - timedelta(minutes=random.randint(1,30))
elif status == "Arrived":
actual = sched + timedelta(minutes=random.randint(-30,30))
elif status == "Delayed":
delay = random.randint(10,120)
actual = sched + timedelta(minutes=delay)
else:
actual = None
gate = random.choice(GATES)
flights.append({
"flight_no": flight_no,
"airline": airline,
"origin": origin,
"dest": dest,
"sched": sched,
"actual": actual,
"status": status,
"gate": gate,
"remarks": random.choice(["", "Operated by partner", "Check-in closes 30 min before", "Crew change", "Weather hold"])
})
# sort by scheduled time
flights.sort(key=lambda f: f["sched"])
return flights
# --- UI helpers ---
def header():
print(FG_CYAN + BOLD + "MS-DOS Flight Info v0.13 (Crappy Edition)".ljust(64) + RESET)
print(FG_YELLOW + "┌" + "─"*62 + "┐" + RESET)
print(FG_YELLOW + "│" + RESET + " コマンド: [L]ist [V]iew <flight> [S]earch [R]efresh [Q]uit ".ljust(62) + FG_YELLOW + "│" + RESET)
print(FG_YELLOW + "└" + "─"*62 + "┘" + RESET)
def ads_banner():
# intentionally ugly ad with a typo
print()
print(FG_WHITE + INVERT + " !!! 飛行機保険? NO THANKS!! Buy Now!! (typo free?) ".center(62) + RESET)
print()
def draw_table(flights, show_count=18):
print(FG_GREEN + BOLD + f"{'FLIGHT':8} {'FROM':6} {'TO':6} {'SCH':16} {'STATUS':12} {'GATE':6} REMARKS" + RESET)
print("-"*80)
for f in flights[:show_count]:
sched = f["sched"].strftime("%Y-%m-%d %H:%M")
status = f["status"]
# make delayed show red
status_display = status
if "Delayed" in status or "Cancelled" in status:
status_display = FG_RED + status + RESET
elif status in ("Boarding","Departed"):
status_display = FG_YELLOW + status + RESET
print(f"{f['flight_no']:8} {f['origin']:6} {f['dest']:6} {sched:16} {status_display:12} {f['gate']:6} {f['remarks']}")
def beep():
# terminal bell
print("\a", end="", flush=True)
def show_flight_detail(f):
cls()
print(FG_CYAN + BOLD + f"Flight {f['flight_no']} — Detailed Info" + RESET)
print("-"*50)
print(f"Airline : {f['airline']}")
print(f"From : {f['origin']}")
print(f"To : {f['dest']}")
print(f"Sched : {f['sched'].strftime('%Y-%m-%d %H:%M')}")
if f["actual"]:
print(f"Actual : {f['actual'].strftime('%Y-%m-%d %H:%M')}")
else:
print("Actual : --")
print(f"Status : {f['status']}")
print(f"Gate : {f['gate']}")
print(f"Remarks : {f['remarks']}")
print()
print(FG_YELLOW + "Press Enter to go back..." + RESET)
input()
def search_flights(flights, q):
q = q.strip().upper()
res = [f for f in flights if q in f["flight_no"].upper() or q == f["origin"].upper() or q == f["dest"].upper()]
return res
# --- Main loop ---
def main():
flights = generate_flights(24)
# intentionally broken greeting
cls()
print(FG_WHITE + INVERT + " WELCOME! THIS APP IS BUGGY & PROUD. ".center(62) + RESET)
print()
time.sleep(0.6)
while True:
cls()
header()
ads_banner()
draw_table(flights, show_count=18)
print()
cmd = input(FG_CYAN + "コマンドを入力して下さい > " + RESET).strip()
if cmd == "":
# treat Enter as refresh (lazy)
# small chance to randomly mutate a status (simulate 'live' chaotic updates)
if random.random() < 0.35:
idx = random.randrange(len(flights))
old = flights[idx]["status"]
flights[idx]["status"] = random.choice(STATUSES)
# sometimes change gate
if random.random() < 0.3:
flights[idx]["gate"] = random.choice(GATES)
# if change to delayed, bump actual
if flights[idx]["status"] == "Delayed":
if flights[idx]["actual"]:
flights[idx]["actual"] += timedelta(minutes=random.randint(10,60))
else:
flights[idx]["actual"] = flights[idx]["sched"] + timedelta(minutes=random.randint(10,90))
# beep if important
if "Cancelled" in flights[idx]["status"] or "Delayed" in flights[idx]["status"]:
beep()
continue
c = cmd.lower().split()
if c[0] in ("q","quit","exit"):
cls()
print(FG_RED + "Bye! このクソアプリを使ってくれてありがとう (not)".center(60) + RESET)
break
elif c[0] in ("l","list"):
# just redraw (loop already shows list)
continue
elif c[0] in ("r","refresh"):
# manual refresh = mutate some flights and re-generate tiny chance
for _ in range(random.randint(0,3)):
idx = random.randrange(len(flights))
flights[idx]["status"] = random.choice(STATUSES)
print(FG_YELLOW + "Refreshed (but maybe not)..." + RESET)
time.sleep(0.6)
continue
elif c[0] in ("v","view"):
if len(c) < 2:
print(FG_RED + "使い方: V <FLIGHT_NO> 例: V JC012" + RESET)
time.sleep(1.2)
continue
q = c[1].upper()
found = [f for f in flights if f["flight_no"].upper() == q]
if not found:
print(FG_RED + "フライトが見つかりません (当然かも)..." + RESET)
time.sleep(1.2)
continue
show_flight_detail(found[0])
continue
elif c[0] in ("s","search"):
if len(c) < 2:
q = input("検索ワード (便名 or FROM or TO) > ").strip()
else:
q = " ".join(c[1:])
res = search_flights(flights, q)
if not res:
print(FG_RED + "該当なし。Your search sucks." + RESET)
time.sleep(1.2)
continue
# show small result table
cls()
print(FG_CYAN + f"Search results for '{q}':" + RESET)
draw_table(res, show_count=50)
print()
print(FG_YELLOW + "Enter flight no to view details, or press Enter to go back." + RESET)
choice = input("> ").strip().upper()
if choice:
fnd = [f for f in res if f["flight_no"].upper() == choice]
if fnd:
show_flight_detail(fnd[0])
else:
print(FG_RED + "Not found in search results." + RESET)
time.sleep(1.0)
continue
else:
# unknown command: show snarky message
print(FG_RED + "??? コマンドが無効です。あなたは何をしているの?" + RESET)
time.sleep(1.0)
continue
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n" + FG_RED + "Interrupted. クソアプリを強制終了しました。" + RESET)
sys.exit(0)
3. おわりに
今回は前回のコウメ.pyに引き続き、実用性はない満足度は高いアプリを作りました。
今後はここから得た技術要素をもとに何か得られたらええなと思います(笑)

