const fs = require("fs")
const { parse } = require("csv-parse/sync")
var card_records = parse(fs.readFileSync("tools/cards.csv", "utf-8"), { columns: true, records_with_empty_values: true })
var scenario_records = parse(fs.readFileSync("tools/scenarios.csv", "utf-8"), { columns: true, skip_records_with_empty_values: true })
var result = [
`
Table Battle Cards
`
]
const WING = { red: 0, pink: 1, blue: 2, dkblue: 3 }
const WING_ICON = [ "\u2666", "\u2665", "\u2663", "\u2660" ]
var cards = [ ]
var card_index = {}
var cards_show = [ ]
var scenarios = []
for (let c of card_records) {
if (!c.number) {
result.push(`
${c.scenario}
`)
continue
}
if (!c.name)
continue
let card = {
scenario: parseInt(c.scenario),
number: c.number,
name: c.name,
wing: WING[c.wing],
morale: 1,
}
if (c.alias)
card.alias = c.alias
let id = card_index[c.number] = cards.length
cards.push(card)
if (c.strength === "I") {
card.special = 1
}
else if (c.strength === "II") {
card.special = 2
}
else if (c.strength === "III") {
card.special = 3
}
else if (c.strength === "IV") {
card.special = 4
}
else if (c.strength === "V") {
card.special = 5
}
else if (c.strength.endsWith("*")) {
card.strength = parseInt(c.strength)
card.morale = 2
}
else if (c.strength.endsWith("-")) {
card.strength = parseInt(c.strength)
card.morale = 0
}
else {
card.strength = parseInt(c.strength)
}
card.dice = c.dice
card.actions = []
result.push(`
`)
if (c.symbol)
result.push(`
${c.name}
`)
else
result.push(`
${c.name}
`)
if (c.symbol === "inf") {
card.infantry = 1
result.push(``)
}
if (c.symbol === "cav") {
card.cavalry = 1
result.push(``)
}
if (card.strength)
result.push(`
${card.strength}
`)
else
result.push(`
${c.strength}
`)
if (c.link === "LR") {
card.link = [ id - 1, id + 1 ]
result.push(``)
result.push(``)
} else if (c.link === "L") {
result.push(``)
card.link = [ id - 1 ]
} else if (c.link === "R") {
result.push(``)
card.link = [ id + 1 ]
}
if (c.dice) {
if (card.morale === 2)
result.push(`
${c.dice}
★
`)
else
result.push(`
${c.dice}
`)
}
function make_action(type, requirement, target, effect) {
let a = { type }
if (requirement) a.requirement = requirement
if (target) a.target = target
if (effect) a.effect = effect
return a
}
if (c.action1_type) {
card.actions.push(make_action(c.action1_type, c.action1_req, c.action1_target, c.action1_effect))
result.push(`
`)
}
if (c.rule) {
card.rules = {}
for (let rule of c.rule.split("; ")) {
if (/=/.test(rule)) {
let [ key, vals ] = rule.split("=")
card.rules[key] = vals.split(",")
} else {
card.rules[rule] = 1
}
}
}
if (c.rule_text) {
card.rule_text = c.rule_text
result.push(`
${c.rule_text}
`)
}
if (c.lore_text) {
card.lore_text = c.lore_text
result.push(`
${c.lore_text}
`)
}
if (c.reserve) {
if (c.reserve === "RETIRE, Ward") {
card.retire = 1
card.reserve = [ "Ward" ]
}
else if (c.reserve === "RETIRE, PURSUIT") {
card.retire = 1
card.pursuit = 1
}
else if (c.reserve === "RETIRE")
card.retire = 1
else if (c.reserve === "PURSUIT")
card.pursuit = 1
else if (c.reserve === "Commanded" || c.reserve === "See Above" || c.reserve === "Special Rule")
card.reserve = c.reserve
else
card.reserve = c.reserve.split(" or ")
if (card.retire && card.reserve)
result.push(`
RETIRE; RESERVE (${card.reserve.join(", ")})
`)
else if (card.retire && card.pursuit)
result.push(`
RETIRE, PURSUIT)})
`)
else if (card.retire)
result.push(`
RETIRE
`)
else if (card.pursuit)
result.push(`
PURSUIT
`)
else
result.push(`
IN RESERVE (${c.reserve})
`)
}
result.push(`
${c.number}
`)
result.push(`
${WING_ICON[card.wing]}
`)
result.push(`
`)
}
function find_card(scenario, name) {
name = name.replace("*", "")
name = name.replace(" (Voluntary)", "")
for (let c of cards)
if (c.scenario === scenario && (c.name === name || c.alias === name))
return card_index[c.number]
throw new Error("CARD NOT FOUND: " + scenario + " " + name)
}
function find_enemy_cards(scenario, wing) {
let w1, w2
if (wing === 0 || wing === 1)
w1 = 2, w2 = 3
if (wing === 2 || wing === 3)
w1 = 0, w2 = 1
let list = []
for (let c of cards)
if (c.scenario === scenario && (c.wing === w1 || c.wing === w2))
list.push(card_index[c.number])
return list
}
function find_friendly_cards(scenario, wing) {
let w1, w2
if (wing === 0 || wing === 1)
w1 = 0, w2 = 1
if (wing === 2 || wing === 3)
w1 = 2, w2 = 3
let list = []
for (let c of cards)
if (c.scenario === scenario && (c.wing === w1 || c.wing === w2))
list.push(card_index[c.number])
return list
}
function find_wing_cards(scenario, wing) {
let list = []
for (let c of cards)
if (c.scenario === scenario && c.wing === wing)
list.push(card_index[c.number])
return list
}
/* process action and reserve targets */
function process_card(c, ix) {
for (let a of c.actions) {
if (a.target) {
let tname = a.target.replace(" out of reserve", "")
if (tname === "Any enemy attack" || tname === "Any enemy formation" || tname === "Any enemy attacking it")
a.target_list = find_enemy_cards(c.scenario, c.wing)
else if (tname === "Any friendly formation")
a.target_list = find_friendly_cards(c.scenario, c.wing)
else if (tname === "Any friendly Pink formation")
a.target_list = find_wing_cards(c.scenario, WING.pink)
else if (tname === "Any other Pink formation")
a.target_list = find_wing_cards(c.scenario, WING.pink).filter(x => x !== c)
else if (tname === "Any Red formation")
a.target_list = find_wing_cards(c.scenario, WING.red)
else if (tname === "Any Pink formation")
a.target_list = find_wing_cards(c.scenario, WING.pink)
else if (tname === "Any Blue formation")
a.target_list = find_wing_cards(c.scenario, WING.blue)
else if (tname === "Any Dark Blue formation")
a.target_list = find_wing_cards(c.scenario, WING.dkblue)
else if (tname.startsWith("Any attack on "))
a.target_list = tname.replace("Any attack on ", "").split(" or ").map(name => find_card(c.scenario, name))
else if (tname === "Any friendly but Little Round Top")
a.target_list = find_friendly_cards(c.scenario, c.wing).filter(x => x !== find_card(c.scenario, "Little Round Top"))
else if (tname === 'Activate "Retreat to Nivelles"')
a.target_list = [ "Retreat to Nivelles" ]
else if (tname === "The Fog Lifts...")
a.target_list = [ "The Fog" ]
else if (tname === "See Below")
a.target_list = [ ]
else
a.target_list = tname.split(/, | OR | or | and /).map(name => find_card(c.scenario, name))
}
if (a.type === "Absorb")
a.target_list = a.target_list.filter(x => x !== ix) // never absorb for self
}
if (c.rules) {
for (let key in c.rules) {
let val = c.rules[key]
if (Array.isArray(val))
c.rules[key] = val.map(number => card_index[number])
}
}
if (c.pursuit) {
if (c.actions[0].type !== "Attack" && c.actions[0].type !== "Counterattack")
throw new Error("PURSUIT without Attack or Counterattack as first action")
if (c.actions[0].target_list.length !== 1)
throw new Error("PURSUIT with more than one target!")
c.pursuit = c.actions[0].target_list[0]
}
if (Array.isArray(c.reserve))
c.reserve = c.reserve.map(name => find_card(c.scenario, name))
}
let failed = false
cards.forEach((c, ix) => {
try {
process_card(c, ix)
} catch (err) {
console.log(err)
failed = true
}
})
fs.writeFileSync("info/all-cards.html", result.join("\n"))
result = [
`
Table Battle Scenarios
`
]
function parse_cards(text) {
let out = []
let list = text.split(",")
for (let range of list) {
let [a, b] = range.split("-")
a = card_index[a]
b = card_index[b]
if (a === undefined || b === undefined)
throw new Error("MISSING CARDS FOR " + text)
for (let i = a; i <= b; ++i)
out.push(i)
}
return out
}
let last_exp = null
for (let s of scenario_records) {
if (!s.name)
continue
if (last_exp !== s.expansion) {
result.push(`