Skip to content

Instantly share code, notes, and snippets.

@sethmlarson
Created March 24, 2026 03:36
Show Gist options
  • Select an option

  • Save sethmlarson/8ab1b0c7524ac2c642d56cd56c1bbb72 to your computer and use it in GitHub Desktop.

Select an option

Save sethmlarson/8ab1b0c7524ac2c642d56cd56c1bbb72 to your computer and use it in GitHub Desktop.
LAN Party Calculator for Mario Kart, Kirby Air Riders, and F-Zero
# LAN Party Calculator for Mario Kart, Kirby Air Riders, and F-Zero
# Code License: MIT, Copyright 2026 Seth Larson
# Data License: CC-BY-SA 4.0, Wikipedia
import dataclasses
import enum
import math
from dataclasses import dataclass
from textwrap import dedent as d
class Game(enum.Enum):
SMK = "Super Mario Kart"
FZ = "F-Zero"
MK64 = "Mario Kart 64"
MKSS = "Mario Kart: Super Circuit"
FZMV = "F-Zero: Maximum Velocity"
FZGL = "F-Zero: GP Legend"
MKDD = "Mario Kart: Double Dash!!"
KAR = "Kirby Air Ride"
FZGX = "F-Zero GX"
MKWII = "Mario Kart Wii"
MKDS = "Mario Kart DS"
MK7 = "Mario Kart 7"
MK8 = "Mario Kart 8"
MK8D = "Mario Kart 8 Deluxe"
FZ99 = "F-Zero 99"
MKWRD = "Mario Kart World"
KARS = "Kirby Air Riders"
class Console(enum.Enum):
SNES = "SNES"
N64 = "N64"
GBA = "GBA"
GCN = "GameCube"
WII = "Wii"
DS = "DS"
DS3 = "3DS/2DS"
WIIU = "Wii U"
SWITCH = "Switch"
SWITCH2 = "Switch 2"
@dataclass(eq=True)
class ConsoleData:
name: Console
price_url: str
controller_price_url: str | None = None
link_cable_price_url: str | None = None
ethernet_adapter_price_url: str | None = None
controller_max: int = 1
controller_multiplier: int = 1
force_tv: bool = False # 'Docked' mode for hybrid consoles.
def is_portable(self) -> bool:
return (
self.name
in {
Console.GBA,
Console.DS,
Console.DS3,
Console.SWITCH,
Console.SWITCH2,
}
and not self.force_tv
)
def price(self) -> int:
return get_price(self.price_url)
def supports_ethernet(self) -> bool:
return self.name in {
Console.GCN,
Console.SWITCH,
Console.SWITCH2,
}
def requires_cable(self, multiplayer: "Multiplayer") -> bool:
return (
(self.name == Console.GBA)
and multiplayer
in (
Multiplayer.LAN,
Multiplayer.SHARE,
)
or (multiplayer == Multiplayer.LAN and self.supports_ethernet())
)
def requires_adapter(self, multiplayer: "Multiplayer") -> bool:
return (
self.name == Console.GCN
or self.name == Console.SWITCH
or (self.name == Console.SWITCH2 and not self.force_tv)
) and multiplayer == Multiplayer.LAN
def controller_price(self) -> int:
return get_price(self.controller_price_url) if self.controller_price_url else 0
def cable_price(self) -> int:
if self.supports_ethernet():
return 5 # $15 for 3 7ft Ethernet cables.
return get_price(self.link_cable_price_url) if self.link_cable_price_url else 0
def adapter_price(self) -> int:
# ETH2GC Memory Card adapter ($25)
if self.name == Console.GCN:
return 25
# Un-docked Switch/Switch 2
# requires USB-C -> Ethernet ($20)
if self.name in (Console.SWITCH, Console.SWITCH2) and not self.force_tv:
return 20
# Docked Switch 1 requires USB-A -> Ethernet adapter ($15)
if self.name == Console.SWITCH and self.force_tv:
return 15
return 0
def with_tv(self) -> "ConsoleData":
if self.name not in {Console.SWITCH, Console.SWITCH2}:
return self
return dataclasses.replace(self, force_tv=True)
class Multiplayer(enum.Enum):
LOCAL = "Local"
LAN = "LAN"
ONLINE = "Online"
SHARE = "Share"
def display_name(self, console: Console):
if self != Multiplayer.SHARE:
return self.value
return {
Console.GBA: "Single-Pak Link",
Console.DS: "Download Play",
Console.DS3: "Download Play",
Console.SWITCH: "GameShare",
Console.SWITCH2: "GameShare",
}[console]
@dataclass
class GameData:
name: Game
console_name: Console
year: int
players: dict[Multiplayer, tuple[int, int]]
price_url: str | None = None
price_exact: int | None = None
@property
def console(self) -> ConsoleData:
return CONSOLE_DATA[self.console_name]
def price(self) -> int:
if self.price_exact is not None:
return self.price_exact
return get_price(self.price_url)
def get_price(url) -> int | None:
return PRICE_DATA[url]
@dataclass
class GameRequires:
consoles_required: int
games_required: int
controllers_required: int
cables_required: int
adapters_required: int
tvs_required: int
def calculate_requirements(
game: GameData, multiplayer: Multiplayer, players: int, with_tv: bool = False
) -> GameRequires | None:
if players == 1 and multiplayer != Multiplayer.LOCAL:
return None
if multiplayer not in game.players:
return None
players_per_console, max_players = game.players[multiplayer]
if players > max_players:
return None
console = game.console
if with_tv:
console = console.with_tv()
consoles_required = int(math.ceil(players / players_per_console))
# Because local play will always be cheaper, this
# script assumes you want to play with multiple
# TVs/consoles for non-local modes
if multiplayer != Multiplayer.LOCAL and consoles_required == 1:
consoles_required = 2
controllers_from_consoles = consoles_required * console.controller_multiplier
controllers_required = max(players - controllers_from_consoles, 0)
games_required = consoles_required if multiplayer != multiplayer.SHARE else 1
tvs_required = 0 if console.is_portable() else consoles_required
cables_required = 0
adapters_required = 0
if console.requires_cable(multiplayer):
if console.supports_ethernet():
cables_required = consoles_required
else: # GBA Link Cable
cables_required = consoles_required - 1
if console.requires_adapter(multiplayer):
adapters_required = consoles_required
return GameRequires(
consoles_required=consoles_required,
games_required=games_required,
controllers_required=controllers_required,
cables_required=cables_required,
adapters_required=adapters_required,
tvs_required=tvs_required,
)
PRICE_DATA = {
"https://www.pricecharting.com/game/nintendo-64/nintendo-64-system": 87,
"https://www.pricecharting.com/game/nintendo-64/mario-kart-64": 46,
"https://www.pricecharting.com/game/nintendo-64/gray-controller": 16,
"https://www.pricecharting.com/game/gameboy-advance/indigo-gameboy-advance-system": 73,
"https://www.pricecharting.com/game/gameboy-advance/mario-kart-super-circuit": 17,
"https://www.pricecharting.com/game/gameboy-advance/gameboy-advance-game-link-cable": 24,
"https://www.pricecharting.com/game/gameboy-advance/f-zero-maximum-velocity": 20,
"https://www.pricecharting.com/game/gameboy-advance/f-zero-gp-legend": 30,
"https://www.pricecharting.com/game/gamecube/indigo-gamecube-system": 119,
"https://www.pricecharting.com/game/gamecube/mario-kart-double-dash": 60,
"https://www.pricecharting.com/game/gamecube/indigo-controller": 28,
"https://www.pricecharting.com/game/gamecube/kirby-air-ride": 71,
"https://www.pricecharting.com/game/gamecube/f-zero-gx": 61,
"https://www.pricecharting.com/game/wii/white-nintendo-wii-system": 60,
"https://www.pricecharting.com/game/wii/mario-kart-wii": 35,
"https://www.pricecharting.com/game/wii/white-wii-remote": 13,
"https://www.pricecharting.com/game/nintendo-ds/black-nintendo-ds-lite": 60,
"https://www.pricecharting.com/game/nintendo-ds/mario-kart-ds": 17,
"https://www.pricecharting.com/game/nintendo-3ds/nintendo-2ds-crimson-red": 94,
"https://www.pricecharting.com/game/nintendo-3ds/mario-kart-7": 14,
"https://www.pricecharting.com/game/wii-u/wii-u-console-basic-white-8gb": 129,
"https://www.pricecharting.com/game/wii-u/mario-kart-8": 10,
"https://www.pricecharting.com/game/wii-u/wii-u-pro-controller-white": 30,
"https://www.pricecharting.com/game/nintendo-switch/nintendo-switch-with-blue-and-red-joy-con": 135,
"https://www.pricecharting.com/game/nintendo-switch/mario-kart-8-deluxe": 35,
"https://www.pricecharting.com/game/nintendo-switch/joy-con-neon-red-&-neon-blue": 36,
"https://www.pricecharting.com/game/nintendo-switch-2/nintendo-switch-2-console": 395,
"https://www.pricecharting.com/game/nintendo-switch-2/mario-kart-world": 58,
"https://www.pricecharting.com/game/nintendo-switch-2/kirby-air-riders": 50,
"https://www.pricecharting.com/game/super-nintendo/super-nintendo-system": 129,
"https://www.pricecharting.com/game/super-nintendo/super-mario-kart": 34,
"https://www.pricecharting.com/game/super-nintendo/super-nintendo-controller": 17,
"https://www.pricecharting.com/game/super-nintendo/f-zero": 18,
}
CONSOLE_DATA = {
Console.SNES: ConsoleData(
name=Console.SNES,
price_url="https://www.pricecharting.com/game/super-nintendo/super-nintendo-system",
controller_max=2,
controller_price_url="https://www.pricecharting.com/game/super-nintendo/super-nintendo-controller",
),
Console.N64: ConsoleData(
name=Console.N64,
price_url="https://www.pricecharting.com/game/nintendo-64/nintendo-64-system",
controller_max=4,
controller_price_url="https://www.pricecharting.com/game/nintendo-64/gray-controller",
),
Console.GBA: ConsoleData(
name=Console.GBA,
price_url="https://www.pricecharting.com/game/gameboy-advance/indigo-gameboy-advance-system",
link_cable_price_url="https://www.pricecharting.com/game/gameboy-advance/gameboy-advance-game-link-cable",
),
Console.GCN: ConsoleData(
name=Console.GCN,
price_url="https://www.pricecharting.com/game/gamecube/indigo-gamecube-system",
controller_max=4,
controller_price_url="https://www.pricecharting.com/game/gamecube/indigo-controller",
ethernet_adapter_price_url="https://www.pricecharting.com/game/gamecube/gamecube-broadband-adapter",
),
Console.WII: ConsoleData(
name=Console.WII,
price_url="https://www.pricecharting.com/game/wii/white-nintendo-wii-system",
controller_max=4,
controller_price_url="https://www.pricecharting.com/game/wii/white-wii-remote",
),
Console.DS: ConsoleData(
name=Console.DS,
price_url="https://www.pricecharting.com/game/nintendo-ds/black-nintendo-ds-lite",
),
Console.WIIU: ConsoleData(
name=Console.WIIU,
price_url="https://www.pricecharting.com/game/wii-u/wii-u-console-basic-white-8gb",
controller_max=4,
controller_price_url="https://www.pricecharting.com/game/wii-u/wii-u-pro-controller-white",
),
Console.DS3: ConsoleData(
name=Console.DS3,
price_url="https://www.pricecharting.com/game/nintendo-3ds/nintendo-2ds-crimson-red",
),
Console.SWITCH: ConsoleData(
name=Console.SWITCH,
price_url="https://www.pricecharting.com/game/nintendo-switch/nintendo-switch-with-blue-and-red-joy-con",
controller_max=4,
controller_price_url="https://www.pricecharting.com/game/nintendo-switch/joy-con-neon-red-&-neon-blue",
controller_multiplier=2,
),
Console.SWITCH2: ConsoleData(
name=Console.SWITCH2,
price_url="https://www.pricecharting.com/game/nintendo-switch-2/nintendo-switch-2-console",
controller_max=4,
controller_price_url="https://www.pricecharting.com/game/nintendo-switch/joy-con-neon-red-&-neon-blue",
controller_multiplier=2,
),
}
GAME_DATA = [
GameData(
name=Game.SMK,
console_name=Console.SNES,
year=1992,
players={
Multiplayer.LOCAL: (2, 2),
},
price_url="https://www.pricecharting.com/game/super-nintendo/super-mario-kart",
),
GameData(
name=Game.FZ,
console_name=Console.SNES,
year=1990,
players={
Multiplayer.LOCAL: (2, 2),
},
price_url="https://www.pricecharting.com/game/super-nintendo/f-zero",
),
GameData(
name=Game.MK64,
console_name=Console.N64,
year=1996,
players={
Multiplayer.LOCAL: (4, 4),
},
price_url="https://www.pricecharting.com/game/nintendo-64/mario-kart-64",
),
GameData(
name=Game.MKSS,
console_name=Console.GBA,
year=2001,
players={
Multiplayer.LOCAL: (1, 1),
Multiplayer.LAN: (1, 4),
Multiplayer.SHARE: (1, 4),
},
price_url="https://www.pricecharting.com/game/gameboy-advance/mario-kart-super-circuit",
),
GameData(
name=Game.FZMV,
console_name=Console.GBA,
year=2003,
players={
Multiplayer.LOCAL: (1, 1),
Multiplayer.LAN: (1, 4),
Multiplayer.SHARE: (1, 4),
},
price_url="https://www.pricecharting.com/game/gameboy-advance/f-zero-maximum-velocity",
),
GameData(
name=Game.FZGL,
console_name=Console.GBA,
year=2003,
players={
Multiplayer.LOCAL: (1, 1),
Multiplayer.LAN: (1, 4),
Multiplayer.SHARE: (1, 4),
},
price_url="https://www.pricecharting.com/game/gameboy-advance/f-zero-gp-legend",
),
GameData(
name=Game.MKDD,
console_name=Console.GCN,
year=2003,
players={
Multiplayer.LOCAL: (4, 4),
Multiplayer.LAN: (4, 16),
},
price_url="https://www.pricecharting.com/game/gamecube/mario-kart-double-dash",
),
GameData(
name=Game.KAR,
console_name=Console.GCN,
year=2003,
players={
Multiplayer.LOCAL: (4, 4),
Multiplayer.LAN: (2, 4),
},
price_url="https://www.pricecharting.com/game/gamecube/kirby-air-ride",
),
GameData(
name=Game.FZGX,
console_name=Console.GCN,
year=2003,
players={
Multiplayer.LOCAL: (4, 4),
},
price_url="https://www.pricecharting.com/game/gamecube/f-zero-gx",
),
GameData(
name=Game.MKDS,
console_name=Console.DS,
year=2005,
players={
Multiplayer.LOCAL: (1, 1),
Multiplayer.LAN: (1, 8),
Multiplayer.ONLINE: (1, 4),
Multiplayer.SHARE: (1, 8),
},
price_url="https://www.pricecharting.com/game/nintendo-ds/mario-kart-ds",
),
GameData(
name=Game.MKWII,
console_name=Console.WII,
year=2008,
players={
Multiplayer.LOCAL: (4, 4),
Multiplayer.ONLINE: (2, 12),
},
price_url="https://www.pricecharting.com/game/wii/mario-kart-wii",
),
GameData(
name=Game.MK7,
console_name=Console.DS3,
year=2011,
players={
Multiplayer.LOCAL: (1, 1),
Multiplayer.LAN: (1, 8),
Multiplayer.ONLINE: (1, 4),
Multiplayer.SHARE: (1, 8),
},
price_url="https://www.pricecharting.com/game/nintendo-3ds/mario-kart-7",
),
GameData(
name=Game.MK8,
console_name=Console.WIIU,
year=2014,
players={
Multiplayer.LOCAL: (4, 4),
Multiplayer.ONLINE: (2, 8),
},
price_url="https://www.pricecharting.com/game/wii-u/mario-kart-8",
),
GameData(
name=Game.MK8D,
console_name=Console.SWITCH,
year=2017,
players={
Multiplayer.LOCAL: (4, 4),
Multiplayer.ONLINE: (2, 8),
Multiplayer.LAN: (2, 12),
},
price_url="https://www.pricecharting.com/game/nintendo-switch/mario-kart-8-deluxe",
),
GameData(
name=Game.FZ99,
console_name=Console.SWITCH,
year=2023,
players={
Multiplayer.LOCAL: (1, 1),
Multiplayer.ONLINE: (1, 99),
},
price_exact=0,
),
GameData(
name=Game.MKWRD,
console_name=Console.SWITCH2,
year=2025,
players={
Multiplayer.LOCAL: (4, 4),
Multiplayer.ONLINE: (2, 16),
Multiplayer.LAN: (2, 24),
},
price_url="https://www.pricecharting.com/game/nintendo-switch-2/mario-kart-world",
),
GameData(
name=Game.KARS,
console_name=Console.SWITCH2,
year=2025,
players={
Multiplayer.LOCAL: (4, 4),
Multiplayer.LAN: (1, 16),
Multiplayer.ONLINE: (2, 8),
Multiplayer.SHARE: (1, 4),
},
price_url="https://www.pricecharting.com/game/nintendo-switch-2/kirby-air-riders",
),
]
def color_as_percentage(x: float, zero, one) -> str:
x = max(0.0, min(1.0, x))
red, gre, blu = (int((one[i] * x) + (zero[i] * (1 - x))) for i in range(3))
return (
f"#{hex(int(red))[2:].zfill(2)}{hex(gre)[2:].zfill(2)}{hex(blu)[2:].zfill(2)}"
)
def print_prices_json():
min_price = 100
max_price = 2000
keys = []
for game in GAME_DATA:
for multiplayer in Multiplayer:
for players in range(1, 25):
keys.append((game, multiplayer, players))
print("var GameData = [")
for game, multiplayer, players in keys:
seen_rows = set()
def print_row(with_tv: bool):
rq = calculate_requirements(game, multiplayer, players, with_tv)
if rq is None:
return
total_price_consoles = rq.consoles_required * game.console.price()
# Switch 2 'Game Share' can be shared to
# Switch 1 consoles, not only Switch 2.
if (
game.console_name == Console.SWITCH2
and multiplayer == Multiplayer.SHARE
):
switch_console = CONSOLE_DATA[Console.SWITCH]
total_price_consoles = game.console.price() + (
(rq.consoles_required - 1) * switch_console.price()
)
total_price = sum(
[
total_price_consoles,
(rq.games_required * game.price()),
(rq.controllers_required * game.console.controller_price()),
(rq.cables_required * game.console.cable_price()),
(rq.adapters_required * game.console.adapter_price()),
]
)
seen_key = (game.name, multiplayer, players, total_price)
if seen_key in seen_rows:
return
seen_rows.add(seen_key)
price_percentage = (total_price - min_price) / (
max_price - min_price
)
print(
f" [{game.name.name!r}, {multiplayer.name!r}, '{players}', {total_price}, {rq.consoles_required}, {rq.games_required}, {rq.cables_required}, {rq.adapters_required}, {rq.tvs_required}, '{color_as_percentage(price_percentage, (128, 255, 128), (255, 128, 128))}'],"
)
print_row(with_tv=False)
if game.console.with_tv() != game.console:
print_row(with_tv=True)
print("];")
def main():
print("""<p>
<blockquote>
<form>
<label for='game'>Game</label><br>
<select onchange="updateTable()" style="max-width: 100%; border: 2px black solid; border-radius: 0;" id="game" name="game">
<option value='any'>(Any)</option>""")
for console in Console:
print(f"<optgroup label='{console.value}'>")
for game in sorted(GAME_DATA, key=lambda g: g.name.value):
if game.console_name == console:
print(
f"<option value='{game.name.name}'>{game.name.value} ({game.name.name})</option>"
)
print("</optgroup>")
print("""</select>
<br>
<label for="players">Players</label><br>
<select onchange="updateTable()" style="max-width: 100%; border: 2px black solid; border-radius: 0;" id="players" name="players">""")
print("<option value='any'>(Any)</option>")
for i in range(1, 25):
print(f"<option value='{i}'>{i}</option>")
print("""</select>
<label for="mode">Mode</label>
<select onchange="updateTable()" style="max-width: 100%; border: 2px black solid; border-radius: 0;" id="mode" name="mode">
<option value='any'>(Any)</option>""")
for mode in Multiplayer:
print(f"<option value='{mode.name}'>{mode.value}</option>")
print("""</select>
</form>
<br>
<table id='results'>
<thead><th>Game</th><th>Mode</th><th>#P</th><th>Price</th><th>Consoles</th><th>Games</th><th>Cables</th><th>Adapters</th><th>TVs</th></thead>
<tbody></tbody>
</table>
</blockquote>
</p>
<script>""")
print_prices_json()
print("""function updateTable() {
var game = document.getElementById("game").value;
var players = document.getElementById("players").value;
var mode = document.getElementById("mode").value;
var rows = [];
GameData.forEach(function(item) {
if (game !== 'any' && game !== item[0]) {
return;
}
if (players !== 'any' && players !== item[2]) {
return;
}
if (mode !== 'any' && mode !== item[1]) {
return;
}
rows.push(item);
});
rows = rows.sort(function (a, b) { return parseInt(a[3]) - parseInt(b[3]) }).slice(0, 10);
var tbody = document.querySelector("#results tbody");
var newTbody = document.createElement("tbody");
rows.forEach(function(element) {
var newRow = newTbody.insertRow();
element.forEach(function(cell, index) {
if (index === 9) {return}
var html = cell.toString();
var cell = newRow.insertCell()
if (index === 3) {
html = '$' + html;
cell.style["background-color"] = element[9];
}
cell.innerHTML = html;
});
});
tbody.parentNode.replaceChild(newTbody, tbody);
}
updateTable();
</script>""")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment