はじめに
突然ですが、私の趣味はミュージカル観劇です。
特に、宝塚歌劇は華やかな世界に連れて行ってもらえるため大好きです。(チケット代もお財布に優しい)
本拠地の兵庫県・宝塚市にある宝塚大劇場は2500席以上ある大きな劇場で、2階席の最後列から見てもほぼ見切れがなく、どこに座っても楽しめる素晴らしい劇場だと思います。
どこに座っても楽しめるとはいえ、やっぱり最前列という素敵なお席に座ってみたいものです。
SS席1列目、なんて素敵な響きなんでしょう。
最前列に座ってみたい、銀橋階段目の前とか、1列目でなくても花道席とか、座ってみたい。
現実で当たらないのなら、せめて当選気分だけでも味わいたい。
ということで、宝塚大劇場でのチケット抽選シミュレーションアプリを作ってみました。
※コードのほとんどは生成AIによるものです
アプリの中身
条件
- 2026年1月の座席改定後の宝塚大劇場の座席表をもとにシミュレーションする
- プレイガイドでの販売席などは無視し、すべての座席が1度の抽選で配分されるものとする
必要モジュール
import random
import csv
席種辞書を作成
宝塚大劇場の座席表をもとに、席種、列、席番号を seat_data
辞書に格納。(2617席分)
座席表
def expand_seat_ranges():
seat_data = {
'SS': {
1: {
1: [(21, 26), (41, 51), (61, 66)],
2: [(21, 28), (41, 52), (61, 68)],
3: [(21, 28), (41, 51), (61, 68)],
4: [(21, 28), (41, 52), (61, 68)],
5: [(21, 29), (41, 51), (61, 69)],
6: [(21, 29), (41, 52), (61, 69)],
7: [(21, 29), (41, 51), (61, 69)],
}
},
'S+': {
1: {
2: [(1, 4), (81, 84)],
3: [(1, 4), (81, 84)],
4: [(1, 5), (81, 85)],
5: [(1, 6), (81, 86)],
6: [(1, 6), (81, 86)],
7: [(1, 7), (81, 87)],
8: [(1, 7), (21, 29), (41, 52), (61, 69), (81, 87)],
9: [(1, 8), (21, 29), (41, 51), (61, 69), (81, 88)],
10: [(1, 9), (21, 29), (41, 52), (61, 70), (81, 89)],
11: [(1, 10), (21, 30), (41, 51), (61, 70), (81, 90)],
12: [(1, 11), (21, 30), (41, 52), (61, 70), (81, 91)],
13: [(1, 12), (21, 30), (41, 51), (61, 70), (81, 92)],
14: [(1, 12), (21, 30), (41, 52), (61, 70), (81, 92)],
15: [(1, 12), (21, 31), (41, 51), (61, 71), (81, 92)],
16: [(1, 12), (21, 31), (41, 52), (61, 71), (81, 92)],
17: [(1, 12), (21, 31), (41, 51), (61, 71), (81, 92)],
18: [(1, 12), (21, 31), (41, 52), (61, 71), (81, 92)],
19: [(1, 12), (21, 31), (41, 51), (61, 71), (81, 92)],
20: [(1, 12), (21, 32), (41, 52), (61, 72), (81, 92)],
21: [(1, 12), (21, 32), (41, 51), (61, 72), (81, 92)],
}
},
'S': {
1: {
22: [(1, 12), (21, 32), (41, 52), (61, 72), (81, 92)],
23: [(1, 12), (21, 32), (41, 51), (61, 72), (81, 92)],
24: [(1, 12), (21, 32), (41, 52), (61, 72), (81, 92)],
25: [(1, 12), (21, 32), (41, 51), (61, 72), (81, 92)],
26: [(1, 12), (21, 32), (41, 52), (61, 72), (81, 92)],
27: [(1, 12), (21, 32), (41, 51), (61, 72), (81, 92)],
28: [(1, 12), (21, 32), (41, 52), (61, 72), (81, 92)],
29: [(1, 12), (21, 32), (41, 51), (61, 72), (81, 92)],
30: [(1, 11), (21, 31), (61, 71), (81, 91)],
31: [(1, 11), (81, 91)],
},
2: {
1: [(1, 9), (21, 29), (41, 52), (61, 69), (81, 89)],
2: [(1, 9), (21, 29), (41, 51), (61, 69), (81, 89)],
3: [(1, 9), (21, 29), (41, 52), (61, 69), (81, 89)],
4: [(1, 9), (21, 29), (41, 51), (61, 69), (81, 89)],
5: [(1, 10), (21, 30), (41, 52), (61, 70), (81, 90)],
6: [(1, 10), (21, 30), (41, 51), (61, 70), (81, 90)],
7: [(1, 10), (21, 30), (41, 52), (61, 70), (81, 90)],
}
},
'A': {
2: {
1: ['L1-L6', 'R1-R6'],
2: ['L1-L6', 'R1-R6'],
3: ['L1-L6', 'R1-R6'],
4: ['L1-L6', 'R1-R6'],
5: ['L1-L6', 'R1-R6'],
6: ['L1-L6', 'R1-R6'],
7: ['L1-L6', 'R1-R6'],
8: [(1, 10), (21, 31), (41, 51), (61, 71), (81, 90)],
9: [(1, 10), (21, 30), (41, 52), (61, 70), (81, 90)],
}
},
'B': {
2: {
10: [(1, 11), (21, 31), (41, 51), (61, 71), (81, 91)],
11: ['L1-L3', (1, 11), (21, 31), (41, 52), (61, 71), (81, 91), 'R1-R3'],
12: ['L1-L4', (1, 11), (21, 31), (41, 51), (61, 71), (81, 91), 'R1-R4'],
13: ['L1-L4', (1, 11), (21, 31), (41, 52), (61, 71), (81, 91), 'R1-R4'],
14: ['L1-L3', (1, 12), (21, 31), (41, 51), (61, 71), (81, 92), 'R1-R3'],
15: ['L1-L3', (1, 12), (21, 31), (41, 52), (61, 71), (81, 92), 'R1-R3'],
16: ['L1-L3', (1, 12), (21, 31), (41, 53), (61, 69), (81, 92), 'R1-R3'],
17: ['L1-L2', (1, 12), (21, 27), (61, 67), (81, 92), 'R1-R2'],
}
}
}
seat_data
辞書をseats
リストに追加
seats = []
for category, floors in seat_data.items():
for floor, rows in floors.items():
for row, ranges in rows.items():
for part in ranges:
if isinstance(part, tuple):
start, end = part
for num in range(start, end + 1):
seats.append((floor, row, num, category))
elif isinstance(part, str) and '-' in part:
prefix = part[0]
start = int(part[1:part.find('-')])
end = int(part[part.find('-') + 2:])
for num in range(start, end + 1):
seats.append((floor, row, f"{prefix}{num}", category))
return seats
実際に seatsリストに格納されるのはこんな感じ
seats = [
(1, 2, 21, 'SS'),
(1, 2, 22, 'SS'),
(1, 2, 23, 'SS'),
..略..
(2, 10, 'L1', 'B'),
(2, 10, 'L2', 'B'),
(2, 10, 'L3', 'B'),
..略..
]
応募データを編集
宝塚友の会(以下、友の会)のチケット申し込みは抽選タイミングによって枚数・席種が異なるので、自由に書き換えられるようにしました。
【参考:友の会抽選方式】2025年9月現在
- 一次抽選:SS席のみ1公演1枚までMAX4公演4枚まで
- 二次抽選:S席~B席1公演2枚までMAX4公演8枚まで
- 三次抽選:S席~B席1公演2枚までMAX6公演12枚まで
ひとつの申し込みで2枚を申し込むことはできません。申し訳ないのですがぼっち観劇でお願いします。
#performance:公演No/priority:優先順位/category:席種/day_type:平日休日で倍率変化
applications = [
{'performance': 1, 'priority': 1, 'category': 'S', 'day_type': 'holiday'},
{'performance': 2, 'priority': 1, 'category': 'S+', 'day_type': 'weekday'},
{'performance': 2, 'priority': 1, 'category': 'S', 'day_type': 'weekday'},
{'performance': 2, 'priority': 1, 'category': 'A', 'day_type': 'weekday'},
{'performance': 5, 'priority': 1, 'category': 'B', 'day_type': 'weekday'},
{'performance': 6, 'priority': 1, 'category': 'A', 'day_type': 'holiday'},
{'performance': 7, 'priority': 1, 'category': 'S', 'day_type': 'holiday'},
{'performance': 8, 'priority': 1, 'category': 'S+', 'day_type': 'holiday'},
{'performance': 9, 'priority': 1, 'category': 'S', 'day_type': 'holiday'},
{'performance': 10, 'priority': 1, 'category': 'S', 'day_type': 'holiday'},
{'performance': 11, 'priority': 1, 'category': 'B', 'day_type': 'weekday'},
{'performance': 12, 'priority': 1, 'category': 'B', 'day_type': 'holiday'},
]
友の会の申し込みは希望順番をつけることができませんが、ほかプレイガイドは同じ公演も別席種であれば希望順で申し込むことができるので、priority
を入れました。
(友の会抽選を模すのであればすべて1で大丈夫です)
また、友の会抽選では同一公演を別席種で申し込むことはできませんがコードは「同一公演別席種申し込み可、重複当選無し」としています。
当選確率の調整
いちばん大事なところ。
ですが実際の確率はわからないので、数値は完全に私の肌感覚です。
- 10公演申し込んで、1公演当たるかどうか
- 休日S席のみ10公演だと1枚も当たらないことが多い
- 休日B席のみ10公演申し込むと1公演は当たる感じがする
- S+席は新設のため未知数
といった経験から、数値を調整。
何度か試してみて「たしかにこのくらいの当選率かも」というくらいに落ち着かせました。
BASE_WIN_PROB = 0.1 # 基本当選確率
DAY_TYPE_MULTIPLIER = {
'weekday': 1.0,
'holiday': 0.6
}
CATEGORY_MULTIPLIER = {
'SS': 0.4, # 平日: 4%, 休日: 2.4%
'S+': 0.6, # 平日: 6%, 休日: 3.6%
'S': 0.8, # 平日: 8%, 休日: 4.8%
'A': 1.0, # 平日: 10%, 休日: 6%
'B': 2.0 # 平日: 20%, 休日: 12%
}
100回試行し、結果をcsv出力
どのくらい申し込めばSS席最前列に座れるのだろうか、という検証のため、100回申し込んで結果を出力することにしました。
# CSV出力用データ
output_rows = []
# priority順に並べ替え
applications.sort(key=lambda x: x['priority'])
# 100回試行
for trial in range(1, 101):
seats = expand_seat_ranges()
used_seats = set()
assigned_performances = set()
for app in applications:
perf = app['performance']
cat = app['category']
day = app['day_type']
result = ['miss']
if perf not in assigned_performances:
win_prob = BASE_WIN_PROB * DAY_TYPE_MULTIPLIER[day] * CATEGORY_MULTIPLIER[cat]
if random.random() < win_prob:
available = [s for s in seats if s[3] == cat and s not in used_seats]
if available:
seat = random.choice(available)
used_seats.add(seat)
assigned_performances.add(perf)
result = [seat[0], seat[1], seat[2], seat[3]]
output_rows.append([trial, perf, cat, day] + result)
# CSV出力
with open("lottery100.csv", "w", newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["Trial", "Performance", "Category", "DayType", "Floor", "Row", "Number","SeatCategory"])
for row in output_rows:
writer.writerow(row)
print("100回の抽選結果を 'lottery100.csv' に出力しました。")
私はSS最前列に座れるのか
実際に友の会の一次抽選を模して、100回抽選してみました。
すべて平日で応募するパターンと、すべて休日で応募するパターン、それぞれ抽選します。
1回の抽選につき、4公演申し込んでいるので、試行回数は400回です。
applications = [
{'performance': 1, 'priority': 1, 'category': 'SS', 'day_type': 'weekday'},
{'performance': 2, 'priority': 1, 'category': 'SS', 'day_type': 'weekday'},
{'performance': 3, 'priority': 1, 'category': 'SS', 'day_type': 'weekday'},
{'performance': 4, 'priority': 1, 'category': 'SS', 'day_type': 'weekday'},
]
applications = [
{'performance': 1, 'priority': 1, 'category': 'SS', 'day_type': 'holiday'},
{'performance': 2, 'priority': 1, 'category': 'SS', 'day_type': 'holiday'},
{'performance': 3, 'priority': 1, 'category': 'SS', 'day_type': 'holiday'},
{'performance': 4, 'priority': 1, 'category': 'SS', 'day_type': 'holiday'},
]
いざ、抽選…!
すべて平日の場合
1回目の申し込みでさっそく当ててくれました!しかも41番とセンターブロック
4回目にもなんと2列目を当ててくれています。こちらもセンター
すべて休日の場合でも5回目の申し込みで3列目が当たっています!
400公演分の当落結果
平日応募
- 当選公演数:16公演 (16/400 当選確率4%)
- 最前列当選数:0公演 (0/400 当選確率0%)
▼列ごとの当選公演数
1列 | 2列 | 3列 | 4列 | 5列 | 6列 | 7列 |
---|---|---|---|---|---|---|
0 | 1 | 2 | 7 | 4 | 1 | 1 |
▼ブロックごとの当選公演数
サブL | センター | サブR |
---|---|---|
2 | 9 | 5 |
休日応募
- 当選公演数:12公演 (12/400 当選確率3%)
- 最前列当選数:2公演 (2/400 当選確率0.5%)
▼列ごとの当選公演数
1列 | 2列 | 3列 | 4列 | 5列 | 6列 | 7列 |
---|---|---|---|---|---|---|
2 | 0 | 4 | 1 | 1 | 2 | 2 |
▼ブロックごとの当選公演数
サブL | センター | サブR |
---|---|---|
5 | 5 | 2 |
だいたい想定通り
平日は当選率4%と想定通り、
休日は当選率3%と、想定の2.4%よりも少し上回る結果になりました。
平日は当選数は多かったですが、最前列(17/194)を引くことができませんでした。
休日はなんと2回も最前列を。席確定後のランダム引きが強かったですね。
実際に抽選100回申し込むにはどのくらい時間がかかる?
さて、ここからは現実を見るお時間です。
検証のため100回・400公演分申し込んでみましたが、現実世界の友の会抽選に100回・400公演分申し込むにはどのくらいの年月がかかるのでしょうか。
2025年9月現在、宝塚歌劇には5つの組があり、それぞれの組が順番に、1年間ほぼ休みなく本拠地の宝塚大劇場(兵庫県宝塚市)、と東京宝塚劇場(東京都千代田区)で公演しています。
2025年、宝塚大劇場で上演される演目7つ、東京宝塚劇場で上演される演目は7つです。
2024年は8つずつでしたが、働き方改革で休暇を増やしているため、今後も年14回のSS席抽選があると考えます。(別箱公演はSS席の有無は会場によるので大劇場のみで考えます。)
100回/14演目 ≒ 7.143年
つまり、7年ちょっと、休まずに宝塚も東京も応募し続ければ、100回申し込めることになります。
それでも1列目が当たらない可能性があるの、運ですね… そう…すべては運…
おわりに
pythonの勉強のために作り始めましたが、どんどん複雑になっていき理解が追い付かないところもありながら、なんとか完成しました。
これまではリストを使用するときは直接リストに値を格納していましたが、辞書を使うとすっきりするんだな~と学びになりました。
いつか本当にSS最前列が当たりますように