text:
# TFT combat simulator (C) 2018, Bill Burdick
#
# Original C/AL code by Nils Lindeberg, Aug 17, 2018 (in a different file)
# Translated to Julia and modified by Bill Burdick, Oct 28, 2018

# TODO replace many old ints with booleans
# TODO add cmdline option for defend-against-charge

using Profile

struct Weapon
    id::Int
    name::String
    minimumst::Int
    dice::Int
    mod::Int
    polearm::Int
    twohanded::Int
    extraattacks::Int
    dxpenaltyallattacks::Int
    dxpenaltysecondaryattack::Int
    thrown::Int
    Weapon(id, name) = new(id, name, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    Weapon(id, name, st, dice, mod) = new(id, name, st, dice, mod, 0, 0, 0, 0, 0, 0)
    function Weapon(w::Weapon; id=w.id, name=w.name, minimumst=w.minimumst, dice=w.dice, mod=w.mod, mods...)
        Weapon(id, name, minimumst, dice, mod; mods...)
    end
    function Weapon(id, name, st, dice, mod; polearm=0, twoh=0, extraatks=0, dxpenaltyall=0, dxpenaltysecondary=0, thrown=0)
        new(id, name, st, dice, mod, polearm, twoh, extraatks, dxpenaltyall, dxpenaltysecondary, thrown)
    end
    function Weapon(id, name, st, dice, mod, polearm, twoh, extratks, dxpenaltyall, dxpenaltysecondary, thrown)
        new(id, name, st, dice, mod, polearm, twoh, extratks, dxpenaltyall, dxpenaltysecondary, thrown)
    end
end

struct Armor
    id::Int
    armornamecombo::String
    armor::Int
    shield::Int
    dxpenalty::Int
    Armor(armor) = Armor(armor.id, armor.armornamecombo, armor.armor, armor.shield)
    Armor(armor, penalty) = Armor(armor.id, armor.armornamecombo, armor.armor, armor.shield, penalty)
    Armor(id, name, value, shield) = Armor(id, name, value, shield, value + max(0, shield - 1))
    Armor(id, name, value, shield, penalty) = new(id, name, value, shield, penalty)
end

mutable struct Warrior
    id::Int
    name::String
    st::Int
    dx::Int
    iq::Int
    adjdx::Int
    weapon::Weapon
    armor::Int
    armordata::Armor
    enemyadjdx::Int
    expert::Int
    master::Int
    shrewd::Int
    uclevel::Int
    numberofhits::Int
    numberofstrikes::Int
    numberofcharges::Int
    numberofmatches::Int
    damagedone::Int
    damagetaken::Int
    armorabsorbed::Int
    numberofwins::Int
    numberofturns::Int
    nemesislosses::Int
    nemesisofhowmany::Int
    nemesis

    function Warrior(w::Warrior; name=w.name, st=w.st, dx=w.dx, iq=w.iq, weapon=w.weapon, armor=w.armor, armordata=w.armordata, enemyadjdx=w.enemyadjdx, expert=w.expert, master=w.master, shrewd=w.shrewd, uclevel=w.uclevel, id=w.id)
        Warrior(id, name, st, dx, iq, weapon, armor, armordata, enemyadjdx, expert, master, shrewd, uclevel)
    end
    function Warrior(id, name, st, dx, iq, weapon, armor, armordata, enemyadjdx, expert, master, shrewd, uclevel)
        adjdx = dx - armordata.dxpenalty - weapon.dxpenaltyallattacks
        new(id, name, st, dx, iq, adjdx, weapon, armor, armordata, enemyadjdx, expert, master, shrewd, uclevel, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nothing)
    end
end

winrate(w) = w.numberofmatches > 0 ? w.numberofwins / w.numberofmatches : 0
avghitspermatch(w) =  w.numberofmatches > 0 ? w.numberofhits / w.numberofmatches : 0
avgdmgdealtpermatch(w) = w.numberofmatches > 0 ? w.damagedone / w.numberofmatches : 0
avgdmgtakenpermatch(w) = w.numberofmatches > 0 ? w.damagetaken / w.numberofmatches : 0
avgturnspermatch(w) = w.numberofmatches > 0 ? w.numberofturns / w.numberofmatches : 0
avgstrikesperturn(w) = w.numberofturns > 0 ? w.numberofstrikes / w.numberofturns : 0
avgchargesperturn(w) = w.numberofturns > 0 ? w.numberofcharges / w.numberofturns : 0
avgdmgperhit(w) = w.numberofhits > 0 ? w.damagedone / w.numberofhits : 0
avgarmorabspermatch(w) = w.numberofmatches > 0 ? w.armorabsorbed / w.numberofmatches : 0
armorname(w) = w.armordata.armornamecombo
weaponname(w) = w.weapon.name
weapondice(w) = w.weapon.dice
weaponmod(w) = w.weapon.mod
extraattacks(w) = w.weapon.extraattacks
polearm(w) = w.weapon.polearm
nemesisid(w) = w.nemesis == nothing ? "" : w.nemesis.id
nemesisname(w) = w.nemesis == nothing ? "" : w.nemesis.name
twohanded(w) = w.weapon.twohanded
dxpenaltysecondaryattack(w) = w.weapon.dxpenaltysecondaryattack

outputfields = (
    id="ID",
    winrate="Win Rate",
    avghitspermatch="Avg Hits Per Match",
    avgdmgdealtpermatch="Avg Dmg Delt Per Match",
    avgdmgtakenpermatch="Avg Dmg Taken Per Match",
    avgturnspermatch="Avg Turns Per Match",
    avgstrikesperturn="Avg Strikes Per Turn",
    avgchargesperturn="Avg Charges Per Turn",
    avgdmgperhit="Avg Dmg per Hit",
    avgarmorabspermatch="Avg Armor Abs Per Match",
    name="Name",
    st="ST",
    dx="DX",
    iq="IQ",
    adjdx="adjDX",
    armor="A",
    enemyadjdx="Enemy Adj DX",
    armorname="Armor Name",
    weaponname="Weapon Name",
    weapondice="Weapon Dice",
    weaponmod="Weapon Mod",
    extraattacks="Extra Attacks",
    dxpenaltysecondaryattack="DX Penalty Second Attack",
    polearm="Pole Arm",
    uclevel="UC Level",
    numberofhits="Number of Hits",
    numberofstrikes="Number of Strikes",
    numberofcharges="Number of Charges",
    damagedone="Damage Done",
    numberofmatches="Number of Matches",
    damagetaken="Damage Taken",
    armorabsorbed="Armor Absorbed",
    numberofwins="Number of Wins",
    numberofturns="Number of Turns",
    expert="Expert",
    master="Master",
    shrewd="Shrewd",
    nemesisname="Nemesis Name",
    nemesisid="Nemesis",
    nemesislosses="Nemesis Losses",
    nemesisofhowmany="Nemesis of How Many"
)

getwarriorproperty(w, k::Symbol) = k in fieldnames(Warrior) ? getfield(w, k) : getfield(Main, k)(w)

mutable struct WarriorState
    warrior::Warrior         #
                             # gP1[1]: warrior.st
                             # gP1[3]: warrior.dx
                             # gP1[4]: warrior.adjdx
                             # gP1[5]: warrior.armor
                             # gP1[6]: warrior.weapondice
                             # gP1[7]: warrior.weaponmod
                             # gP1[9]: warrior.polearm
                             # gP1[18]: warrior.enemyadjdx
                             # gP1[20]: warrior.shrewd
                             # gP1[21]: warrior.weapon.extraattacks
                             # gP1[22]: warrior.weapon.dxpenaltysecondaryattack
                             # gP1[23]: warrior.uclevel
    curst::Int               # gP1[2]
    tmpdxmod::Int            # gP1[8]
    chargechance::Int        # gP1[10]
    fallen::Int              # gP1[11]
    lastattackdamaged::Int   # gP1[12]
    numberofhits::Int        # gP1[13]
    damagedone::Int          # gP1[14]
    setvschargebonus::Int    # gP1[15]
    damagetaken::Int         # gP1[16]
    armorabsorbed::Int       # gP1[17]
    weaponmastry::Int        # gP1[19]: 0, 1 = Expert, 2 = Master, 3 = Fencer Expert, 4 = Fencer Master
    function WarriorState(warrior)
        mastry = warrior.expert + 2 * warrior.master
        if mastry > 0 && warrior.weapon.name in ["Rapier", "Cutlass"]
            mastry += 2
        end
        new(warrior, warrior.st, 0, warrior.weapon.polearm * (Gsettings.new3hexcharge ? Gsettings.initialchargechance : 100), 0, 0, 0, 0, 0, 0, 0, mastry)
    end
end

protection(armor::Armor) = armor.armor + armor.shield

Gwarriors = []
Gpairs = Dict{Warrior, Warrior}()
pushwarrior(w::Warrior) = push!(Gwarriors, w)
Gwarriorkeys = Set([:name :st :dx :iq :weapon :armor :armordata :enemyadjdx :expert :master :shrewd :uclevel :expertisedxadj :adjdx])

Gweapontable = [
    Weapon(1, "Dagger", 8, 1, -1, thrown=1)
    Weapon(2, "Rapier", 9, 1, 0)
    Weapon(3, "Javelin", 9, 1, -1, polearm=1, thrown=1)
    Weapon(4, "Cutlass", 10, 2, -2)
    Weapon(5, "Hatchet", 9, 1, 0, thrown=1)
    Weapon(6, "Spear 1H", 11, 1, 0, polearm=1, thrown=1) # see adjweapontable
    Weapon(7, "Spear 2H", 11, 1, 1, polearm=1, thrown=1, twoh=1) # see adjweapontable
    Weapon(8, "Mace", 11, 2, -1, thrown=1)
    Weapon(9, "Broadsword", 12, 2, 0)
    Weapon(10, "Bastard Sword 2H", 13, 3, -2, twoh=1)
    Weapon(11, "Halberd", 13, 2, 0, polearm=1, twoh=1)
    Weapon(12, "Morningstar", 13, 2, 1)
    Weapon(13, "2-handed Sword", 14, 3, -1, twoh=1)
    Weapon(14, "Pike Ax", 15, 2, 2, polearm=1, twoh=1)
    Weapon(15, "Battle Axe", 15, 3, 0, twoh=1)
    Weapon(16, "Great Sword", 16, 3, 1, twoh=1)
    Weapon(17, "Double Nunchuks", 9, 1, -1, twoh=1, extraatks=1, dxpenaltyall=4)
]
Gweaponsbyname = Dict([weapon.name => weapon for weapon in Gweapontable])

makeflorentine(weaponname; mods...) = Weapon(Gweaponsbyname[weaponname]; id=length(Gweapontable) + 1, extraatks=1, dxpenaltysecondary=4, mods...)

Gucweapons = Dict()
Garmortable = [
    Armor(1, "No Armor", 0, 0)
    Armor(2, "No Armor, Small Shield", 0, 1)
    Armor(3, "No Armor, Large Shield", 0, 2)
    Armor(4, "No Armor, Tower Shield", 0, 3)
    Armor(5, "Cloth", 1, 0)
    Armor(6, "Cloth, Tower Shield", 1, 3)
    Armor(7, "Leather", 2, 0)
    Armor(8, "Leather, Tower Shield", 2, 3)
    Armor(9, "Chainmail", 3, 0)
    Armor(10, "Chainmail, Tower Shield", 3, 3)
    Armor(11, "Half-Plate", 4, 0)
    Armor(12, "Plate", 5, 0)
    Armor(13, "Plate, Large Shield", 5, 2)
    Armor(14, "Plate, Tower Shield", 5, 3)
]
Garmorbyname = Dict([armor.armornamecombo => armor for armor in Garmortable])
noarmor() = Garmortable[1]
smshield() = Garmortable[2]
clotharmor() = Garmortable[5]
Gbarehanddamage = cat([[(range.start, dice, mod) for i in range] for (range, (dice, mod)) in [
    1:8=>(1,-4)
    9:10=>(1,-3)
    11:12=>(1,-2)
    13:14=>(1,-1)
    15:16=>(1,0)
    17:20=>(1,1)
    21:24=>(1,2)
    25:30=>(1,3)
    31:40=>(2,1)
    41:50=>(3,1)
    51:60=>(4,1)
]]..., dims=1)
Gucevadepenalty = [0, 1, 2, 2, 2]
Gucevadeprotection = [0, 1, 2, 2, 3]
Gwin1 = 0
Gwin2 = 0
Gdraw = 0
Gstatforcedretreats = 0
Gstatnumberofcharges = 0
Gstatnumberofattacksonprone = 0
Gstatavgtohitp1 = 0
Gstatavgtohitp2 = 0
Gstatavgnumberofturns = 0
Gnumberofdisengages = 0
Gtotalturns = 0
Gnemesis = []
okst = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
reptilest = [12, 13, 15, 17, 21]
Gsettings = (
    newrules = false,
    #numberofmatches = 3,
    numberofmatches = 15,
    debug = false,
    new3hexcharge = true,       # +2 for set vs charge, 50/50 if you get a charge.
    setvschargechance = 50,     # If FALSE old vanilla rule, all charges 100 and all get +2.
    twohexchargechance = 75,    # IF MA 10 - enemy must charge if you win, otherwise jab.
    onehexchargechance = 50,    # IF MA > 10
    initialchargechance = 75,   # You almost always get a charge, but not always a set vs. charge.
    newarmors = true,           # flat armor progression.
    nopolearmdouble = true,     # Instead of x2 damage, pole arms get +1d6.
    minimum1dmg = false,
    defending = false,          # Defend when opponents gets a charge. Chargers gets first strike so defender never goes first.
                                # Defend only when you have higher DX. Othwerwise pole armed opponent will disengage.
    disengaging = true,
    dxdifdisengage = true,
    newpoleweapons = false,
    minadjdx = 7,               # 0 = Allow all. 7 Scrap all builds below 7.
    maxadjdx = 20,              # With a lot of experts and mastery around and shrewd attacks of your own...
    uc = true,
    florentine = true,
    isbrawlingstacking = false, # Do UC stack with Brawling damage?
    expertise = true,           # If adjDX 14+ THEN adjDX -2(IQ) and +1/-1.
                                # Expertise talent allowed. Min IQ 10, DX 12 +1dmg
                                # -1 enemy tohit, (+1d6/-5DX. When?) Shrewd.
                                # Fencer -4DX. Only dagger,rapier,cutlass. Shrewd.
                                # Shield IQ? -1DX enemy, +1Armor.
    talents = false,            # Warrior IQ10 ST14, Veteran IQ10 ST16. Only good after Weapon Expert or max armor.
                                # Since we don't take MA into account.
    points = 40,
    monster = false,
    maxnumberofturns = 30,
    reptiles = false,
)
Gmatcher = zeros(Int, Gsettings.numberofmatches)
Gprofile = false

function setglobals(ARGS)
    if ARGS == ["-help"] || ARGS == ["--help"]
        println("""
            -help    -- output this message
            --help   -- output this message
            -point N -- use N point characters
            -orig    -- simulate using old rules
            -prof    -- output profiling information
                """)
        exit(0)
    end
    points = 40
    orig = false
    i = 1
    while i <= length(ARGS)
        arg = ARGS[i]
        i += 1
        if arg == "-points"
            points = parse(Int, ARGS[i])
            println(stderr, "SET POINTS TO ", points)
            i += 1
        elseif arg == "-orig"
            orig = true
        elseif arg == "-prof"
            global Gprofile = true
        end
    end

    if orig
        global Gsettings = (Gsettings..., points=points)
    else
        global Gsettings = (
            Gsettings...,
            points = points,
            newrules = true,
            talents = true,
            reptiles = true,
            #debug = true,
            #new3hexcharge = false,
            #setvschargechance = 50,
            #twohexchargechance = 75,
            #onehexchargechance = 50,
            #initialchargechance = 75,
            #newarmors = false,
            #nopolearmdouble = false,
            #minimum1dmg = true,
            #defending = false,
            #disengaging = false,
            #dxdifdisengage = false,
            #newpoleweapons = true,
            #minadjdx = 7,
            #maxadjdx = 20,
            #uc = false,
            #florentine = false,
            #isbrawlingstacking = true,
            #expertise = false,
            #monster = true,
            #maxnumberofturns = 30,
        )
    end
    Gmatcher = zeros(Int, Gsettings.numberofmatches)
end

function runsim()
    global Gtotalturns, Gsettings, Gprofile, Gpairs

    Gpairs = Set([(warrior, warrior) for warrior in Gwarriors])
    warriors = 0
    lastrest = 0
    for warrior1 in Gwarriors
        global Gp1 = WarriorState(warrior1)
        println(stderr, Gp1.warrior.id, ", TOTAL TURNS: ", Gtotalturns)
        for warrior2 in Gwarriors
            Gsettings.newrules && (warrior2, warrior1) in Gpairs && break
            push!(Gpairs, (warrior1, warrior2))
            global Gstatavgnumberofturns = 0
            global Gwin1 = 0
            global Gwin2 = 0
            global Gp2 = WarriorState(warrior2)
            fightfightfight()
            endmatchup(Gp1, Gwin1)
            endmatchup(Gp2, Gwin2)
            finishfighting(Gp2)
            warriornemesis(warrior1, warrior2, Gwin2)
            warriornemesis(warrior2, warrior1, Gwin1)
            Gtotalturns += Gstatavgnumberofturns
            if Gtotalturns - lastrest > 5000
                # This overheats my laptop unless it rests for 1 millisecond every so often!
                sleep(0.001)
                lastrest = Gtotalturns
            end
        end
        finishfighting(Gp1)
        warriors += 1
        if Gprofile && warriors > 10
            return
        end
    end
    setnemesistally()
end

function endmatchup(state, win)
    state.warrior.numberofwins += win
    state.warrior.numberofmatches += Gsettings.numberofmatches
    state.warrior.numberofturns += Gstatavgnumberofturns
end

function finishfighting(state)
    state.warrior.numberofhits += state.numberofhits
    state.warrior.damagedone += state.damagedone
    state.warrior.damagetaken += state.damagetaken
    state.warrior.armorabsorbed += state.armorabsorbed
end

function warriornemesis(warrior1, warrior2, losses)
    if warrior1.nemesislosses < losses
        warrior1.nemesis = warrior2
        warrior1.nemesislosses = losses
    end
end

function fightfightfight()
    global Gp1, Gp2

    for i in 1:Gsettings.numberofmatches
        resetplayer(Gp1)
        resetplayer(Gp2)
        for ii in 1:Gsettings.maxnumberofturns
            (Gp1.curst < (Gsettings.newrules ? 1 : 2) || Gp2.curst < (Gsettings.newrules ? 1 : 2)) && break
            chargeworking(Gp1, Gp2)
            if strikefirst(Gp1, Gp2) == 1
                firstplayer(Gp1, Gp2, true)
            else
                firstplayer(Gp2, Gp1, false)
            end
            turnmopup()
        end
        matchmopup(i)
    end
    matchsetmopup()
end

# return whether the first warrior should strike first
function strikefirst(p1, p2)
    charge1 = p1.warrior.weapon.polearm > 0 && p1.chargechance == 100 && p1.fallen == 0
    charge2 = p2.warrior.weapon.polearm > 0 && p2.chargechance == 100 && p2.fallen == 0
    debugprint("First strike - charge chances: ", p1.chargechance, ":", p2.chargechance)
    if charge1 != charge2 # only one is charging, whoever is goes first
        charge1 ? 1 : 2
    else
        pri1 = priority(p1, p2, charge1)
        pri2 = priority(p2, p1, charge2)
        debugprint("First strike - tohit chances: ", pri1, ":", pri2)
        pri1 > pri2 ? 1 : pri2 > pri1 ? 2 : random(2)
    end
end

priority(p1, p2, charge) = p1.warrior.adjdx + p1.tmpdxmod + charge * p1.setvschargebonus + p2.warrior.enemyadjdx

function strike(attacker, defender, defenderdefending, extraattack)
    attacker.warrior.numberofstrikes += 1
    tohit = 0
    charge = 0
    pronedefender = 0
    setvscharge = 0
    expertdefender = 0
    shrewdpenalty = 0
    secondattackpenalty = 0
    if attacker.fallen == 0
        if extraattack
            secondattackpenalty = attacker.warrior.weapon.dxpenaltysecondaryattack
        end
        tohit = d6(3)
        if attacker.warrior.weapon.polearm > 0 && attacker.chargechance == 100
            debugprint("chrage attack")
            if Gsettings.new3hexcharge
                if random(100) <= Gsettings.setvschargechance
                    debugprint("lucky set vs charge arrack")
                    setvscharge = 2
                end
            else
                debugprint("set vs charge attack")
                setvscharge = 2
            end
            global Gstatnumberofcharges += 1
        end
        if defender.fallen > 0
            pronedefender = 4
            global Gstatnumberofattacksonprone += 1
            debugprint("defender prone")
        end
        # basedx leaves out setvscharge
        basedx = attacker.warrior.adjdx + attacker.tmpdxmod + pronedefender - defender.warrior.enemyadjdx - secondattackpenalty
        expertdefender = defender.warrior.enemyadjdx
        if Gsettings.expertise && attacker.warrior.shrewd == 1
            shrewdpenalty = if attacker.weaponmastry == 1
                5
            elseif attacker.weaponmastry in [2, 3]
                4
            elseif attacker.weaponmastry == 4
                basedx >= 14 ? 3 : 4
            else
                0
            end
        end
        finaldx = basedx + setvscharge - expertdefender - shrewdpenalty
        tohit = if !(Gsettings.defending && defenderdefending) # inverted this check and moved to top
            d6(3)
        elseif defender.weaponmastry in [2, 4] || defender.warrior.uclevel == 5
            debugprint("defender master defending")
            d6(6)
        elseif defender.weaponmastry in [1, 3] || defender.warrior.uclevel == 4
            debugprint("defender expert defending")
            d6(5)
        else
            debugprint("defender defending")
            d6(4)
        end
        debugprint("Adjdx ", basedx + setvscharge, " to hit: ", tohit)
        if Gsettings.defending && defenderdefending
            if defender.weaponmastry in [2, 4] || defender.warrior.uclevel == 5 # Master Defending = 6d6
                if tohit <= max(14, min(27, finaldx))
                    hit(attacker, defender, false, false)
                elseif tohit > 28
                    attacker.fallen = 2 # Simulate dropped and broken weapon w/ knockdown.
                end
            elseif defender.weaponmastry in [1, 3] || defender.warrior.uclevel == 4 # Master Defending = 5d6
                if tohit <= max(11, min(23, finaldx))
                    hit(attacker, defender, false, false)
                elseif tohit > 24
                    attacker.fallen = 2 # Simulate dropped and broken weapon w/ knockdown.
                end
            # normal defending = 4d6
            elseif tohit <= max(8, min(19, finaldx))
                hit(attacker, defender, tohit == 4, tohit == 5)
            elseif tohit > 20
                attacker.fallen = 2 # Simulate dropped and broken weapon w/ knockdown.
            end
        # not defending
        elseif tohit <= max(5, min(15, finaldx))
            hit(attacker, defender, tohit == 3, tohit == 4)
        elseif tohit > 16
            attacker.fallen = 2 # Simulate dropped and broken weapon w/ knockdown.
        end
    end
    attacker.tmpdxmod = attacker.curst <= 3 ? -3 : 0
    attacker.chargechance = 0
end

function hit(attacker, defender, triple, double)
    dice = attacker.warrior.weapon.dice
    mod = attacker.warrior.weapon.mod
    dmg = d6(dice) + mod
    charge = attacker.warrior.weapon.polearm > 0 && attacker.chargechance == 100
    debugprint("Base dmg: d", dice, mod > 0 ? "+" : mod == 0 ? "" : "-", mod != 0 ? mod : "")
    pronedefender = defender.fallen > 0 ? 4 : 0
    if Gsettings.expertise
        if attacker.warrior.uclevel == 0 # UC damage is included in their base weapon stats
            if attacker.weaponmastry in [1, 3]
                dmg += 1 + (attacker.warrior.shrewd == 1 ? d6(1) : 0) # Expert/Fencer Shrewd / Expert/Fencer
            elseif attacker.weaponmastry in [2, 4]
                dmg += 2 + (attacker.warrior.shrewd == 1 ? d6(1) + 2 : 0) # Master/Fencer Shrewd / Master/Fencer
            end
        end
    end
    dmg *= double ? 2 : triple ? 3 : 1
    if charge
        dmg += Gsettings.nopolearmdouble ? d6(1) : dmg
    end
    defender.armorabsorbed += max(0, min(defender.warrior.armor, dmg))
    dmg = max((Gsettings.minimum1dmg ? 1 : 0), dmg - defender.warrior.armor)
    debugprint("Dmg after armor: ", dmg)
    defender.curst -= dmg
    if (defender.warrior.st >= 30 && dmg > 15) || (defender.warrior.st < 30 && dmg > 7)
        defender.fallen = 2
    elseif dmg > 4
        defender.tmpdxmod = -2
    end
    if dmg > 0
        attacker.lastattackdamaged = 1
        attacker.numberofhits += 1
        attacker.damagedone += dmg
        defender.damagetaken += dmg
    end
    debugprint("ST/adjST: ", defender.warrior.st, "/", defender.curst)
end

function chargeworking(p1, p2)
    if p1.chargechance > 0 && p2.chargechance > 0 # If both players have a charge ready they both charge.
        p1.chargechance = p1.chargechance = 100
    elseif p1.chargechance > 0
        p1.chargechance = (random(100) <= p1.chargechance ? 100 : 0)
        if p1.chargechance == 0
            debugprint("P1: lost initiative and no charge")
        end
    elseif p2.chargechance > 0
        p2.chargechance = (random(100) <= p2.chargechance ? 100 : 0)
        if p2.chargechance == 0
            debugprint("P2: lost initiative and no charge")
        end
    end
end

function dodisengage(attacker, defender)
    Gsettings.disengaging && attacker.fallen == 0 && # Must be standing
        attacker.warrior.weapon.polearm == 1 &&   # Rule of thumb: disengage if only you can charge
        defender.warrior.weapon.polearm == 0 &&
        attacker.chargechance == 0 &&      # Exception: you already have charge active
        (!Gsettings.dxdifdisengage ||                # Parting blow disengage
         defender.warrior.adjdx * 2 - attacker.warrior.adjdx < Gsettings.minadjdx) # Only disengage if low adj blow
end

function isdefenderdefending(attacker, defender)
    attackerhurtpremanently = attacker.curst <= 3 ? -3 : 0
    defenderhurtpremanently = defender.curst <= 3 ? -3 : 0
    # Defending is only a good option when the charging enemy can't disengage and do it again with higher adjDX.
    Gsettings.defending &&
        attacker.chargechance == 100 &&
        attacker.fallen == 0 &&
        defender.fallen == 0 &&
        defender.warrior.adjdx + defenderhurtpremanently - attacker.warrior.enemyadjdx > attacker.warrior.adjdx + attackerhurtpremanently - defender.warrior.enemyadjdx
end

function adjustforforcedretreat(p1, p2)
    if min(p1.curst, p2.curst) > (Gsettings.newrules ? 0 : 1)
        checkretreat(p1, p2, "P1")
        checkretreat(p2, p1, "P2")
    end
    p1.lastattackdamaged = p2.lastattackdamaged = 0
end

function checkretreat(p1, p2, name)
    if p1.lastattackdamaged == 1 && p2.lastattackdamaged == 0 &&
        p1.warrior.weapon.polearm == 1 && # Rule of thumb: Force retreat if only you can charge, or both can and you have a lower adjDX.
        (p2.warrior.weapon.polearm == 0 || p1.warrior.adjdx + p1.tmpdxmod < p2.warrior.adjdx + p2.tmpdxmod)
        global Gstatforcedretreats += 1
        debugprint(name * " forced a retreat")
        if Gsettings.new3hexcharge
            p1.chargechance = Gsettings.twohexchargechance # New charge possible.
            if p2.warrior.weapon.polearm == 1
                p2.chargechance = Gsettings.twohexchargechance # Enemy also gets a charge.
            end
        else
            p1.chargechance = 100 # New charge possible.
            if p2.warrior.weapon.polearm == 1
                p2.chargechance = 100 # Enemy also gets a charge.
            end
        end
    end
end

function adjarmortable()
    if !Gsettings.newarmors
        for a in 11:min(14, length(Garmortable))
            armor = Garmortable[a]
            Garmortable[a] = Armor(armor, armor.armor + 1 + armor.shield)
        end
    end
end

function adjweapontable()
    if Gsettings.newpoleweapons
        # javelin is same in both cases
        replaceweapon("Spear 1H", dice=1, mod=1)
        replaceweapon("Spear 2H", dice=2, mod=-2)
        # pike ax is same in both cases
        # halberd is same in both cases
   #else # TODO these are the values in the table
        # javelin is same in both cases
        #replaceweapon("Spear 1H", dice=1, mod=0)
        #replaceweapon("Spear 2H", dice=1, mod=1)
        # pike ax is same in both cases
        # halberd is same in both cases
    end
    if Gsettings.newrules
        replaceweapon("Bastard Sword 2H", minimumst=13, dice=2, mod=2, twoh=1)
    else
        push!(Gweapontable, Weapon(18, "Naginata", 10, 1, 2, twoh=1, polearm=1))
    end
end

function replaceweapon(name::String; mods...)
    weapon = Weapon(Gweaponsbyname[name]; mods...)
    Gweaponsbyname[weapon.name] = weapon
    Gweapontable[weapon.id] = weapon
end

makename(st, dx, iq, weapon, armor) = "ST$st,DX$dx,IQ$iq,$(weapon.name),$(armor.armornamecombo)"

"""
    popwarriortable

Create a warrior for each weapon/armor combination
"""
function popwarriortable()
    for weapon in Gweapontable
        st = weapon.minimumst
        if Gsettings.points - st >= 16
            dx = Gsettings.points - 8 - st
            for armor in Garmortable
                # two-handed weapon -> no shield, one-handed weapon -> shield
                if ((weapon.twohanded == 1 && armor.shield == 0) || (weapon.twohanded == 0 && armor.shield > 0)) &&
                    (!(Gsettings.new3hexcharge && weapon.polearm == 1) || armor.id <= 8)
                    name=makename(st, dx, 8, weapon, armor)
                    warrior = Warrior(length(Gwarriors) + 1, name, st, dx, 8, weapon, protection(armor), armor, 0, 0, 0, 0, 0)
                    if Gsettings.minadjdx <= warrior.adjdx <= Gsettings.maxadjdx && isdxbelow20(warrior)
                        pushwarrior(warrior)
                    end
                    if Gsettings.expertise
                        if weapon.id in [2, 4]
                            addfencingwarriors(warrior)
                        else
                            addexpertisewarriors(warrior)
                        end
                    end
                    Gsettings.talents && addtalentwarriors(warrior)
                end
            end
        end
    end
    Gsettings.uc && adducwarriors()
    Gsettings.monster && addmonster()
    Gsettings.florentine && addflorentine()
end

function addexpertisewarriors(warrior)
    st = warrior.st
    dx = warrior.dx
    iq = 8
    adjdx = warrior.adjdx
    enemyadjdx = 0
    armor = warrior.armor
    armordata = warrior.armordata
    name = warrior.name
    weapon = warrior.weapon
    addwarrior(; expert=0, master=0, shrewd=0, name=name) = pushwarrior(Warrior(warrior; dx=dx, iq=iq, enemyadjdx=enemyadjdx, name=name, expert=expert, master=master, shrewd=shrewd, armor=armor, id=length(Gwarriors) + 1))
    if dx >= 14
        dx -= 2
        adjdx -= 2
        iq = 10
        enemyadjdx = 1
        name = makename(st, dx, iq, weapon, armordata) * ",Expert"
        if isdxbelow20(dx, adjdx)
            armor, enemyadjdx = shieldexpert(iq, warrior, armordata, enemyadjdx) #check for shield expert as well
            if adjdx >= Gsettings.minadjdx
                name, armor = toughness(st, iq, name, armor)  # if you already paid for IQ...
                addwarrior(expert=1)
            end
            if adjdx >= Gsettings.minadjdx + 5
                addwarrior(expert=1, shrewd = 1, name = name * ",Shrewd")
            end
        end
    end
    if dx >= 17
        dx -= 3
        adjdx -= 3
        if isdxbelow20(dx, adjdx)
            iq = 13
            name = makename(st, dx, iq, warrior.weapon, armordata) * ",Master"
            enemyadjdx = 2
            armor, enemyadjdx = shieldexpert(iq, warrior, armordata, enemyadjdx) #check for shield expert as well
            if dx >= Gsettings.minadjdx
                name, armor = toughness(st, iq, name, armor)  # if you already paid for IQ...
                addwarrior(master=1)
            end
            if adjdx >= Gsettings.minadjdx + 5
                addwarrior(master=1, shrewd=1, name = name * ",Shrewd")
            end
        end
    end
end

function addfencingwarriors(warrior)
    st = warrior.st
    dx = warrior.dx
    adjdx = warrior.adjdx
    iq = 8
    enemyadjdx = expert = master = shrewd = 0
    armor = warrior.armor
    armordata = warrior.armordata
    name = warrior.name
    weapon = warrior.weapon
    addwarrior(; expert=0, master=0, shrewd=0, name=name) = pushwarrior(Warrior(warrior; dx=dx, iq=iq, enemyadjdx=enemyadjdx, name=name, expert=expert, master=master, shrewd=shrewd, armor=armor, id=length(Gwarriors) + 1))
    if dx >= 15
        dx -= 2
        adjdx -= 2
        if isdxbelow20(dx, adjdx)
            iq = 10
            enemyadjdx = 1
            name = makename(st, dx, iq, weapon, armordata) * ",E.Fencer"
            armor, enemyadjdx = shieldexpert(iq, warrior, armordata, enemyadjdx) #check for shield expert as well
            addwarrior(expert=1)
            if adjdx >= 14 # good cut off point for shrewd
                addwarrior(expert=1, shrewd=1, name=name * ",Shrewd")
            end
        end
    end
    if dx >= 17
        dx -= 3
        adjdx -= 3
        if isdxbelow20(dx, adjdx)
            iq = 13
            enemyadjdx = 3
            name = makename(st, dx, iq, weapon, armordata) * ",M.Fencer"
            armor, enemyadjdx = shieldexpert(iq, warrior, armordata, enemyadjdx) # check for shield expert as well
            addwarrior(master=1)
            if adjdx >= 14 # good cut off point for shrewd
                addwarrior(master=1, shrewd=1, name=name * ",Shrewd")
            end
        end
    end
end

function addtalentwarriors(warrior)
    st = warrior.st
    dx = warrior.dx
    adjdx = warrior.adjdx
    iq = 8
    armor = warrior.armor
    name = warrior.name
    addwarrior() = pushwarrior(Warrior(warrior; dx=dx, iq=iq, name=name, armor=armor, id=length(Gwarriors) + 1))
    twohanded = warrior.weapon.twohanded
    if st >= (Gsettings.newrules ? 12 : 14) &&
        (dx >= (Gsettings.newrules ? 9 : 10)) &&
        (dx < 14 || !Gsettings.expertise) && # Combo of expertise and veterans talents are handled by Expertise.
        ((armor >= 3 && !Gsettings.newarmors && twohanded == 1) || # not until bad medium armor
         (armor == 5 && Gsettings.newarmors && twohanded == 1) || # not until good heavy armor
         (armor >= 6 && !Gsettings.newarmors && twohanded == 0) || # Not until bad heavy+shield armor
         (armor == 8 && Gsettings.newarmors && twohanded == 0)) # Not until good heavy+shield armor.
        dx -= (Gsettings.newrules ? 1 : 2)
        adjdx -= (Gsettings.newrules ? 1 : 2)
        iq = (Gsettings.newrules ? 9 : 10)
        name, armor = toughness(st, iq, name, armor)
        adjdx >= Gsettings.minadjdx && isdxbelow20(dx, adjdx) && addwarrior()
    end
end

function adducwarriors()
    adducwarriorlevel(1, 8, 8, 10, (0, 1), (0, 3), 4, 0)
    adducwarriorlevel(2, 8, 11, 11, (0, 2), (0, 5), 2, 1)
    adducwarriorlevel(3, 8, 12, 12, (0, 3), (1, 3), 0, 2)
    adducwarriorlevel(4, 11, 13, 13, (1, 0), (2, 1), 0, 2)
    adducwarriorlevel(5, 12, 14, 14, (1, 1), (2, 3), 0, 2)
    Gsettings.reptiles && addbrawlers()
end

# level, minst, mindx, iq, punchstats, kickstats, kickadj
function adducwarriorlevel(level, minst, mindx, iq, punchstats, kickstats, kickadj, enemyadjdx)
    local st, dx, name, armor, armordata
    warrior = Warrior(0, "", 0, 0, 0, Gweapontable[1], 0, Garmortable[1], 0, 0, 0, 0, 0)
    adjdx = 0
    addwarrior(weapon; name=name, extra="") = pushwarrior(Warrior(warrior; st=st, dx=dx, iq=iq, enemyadjdx=Gucevadepenalty[level], name=name * extra, armor=armor, armordata=armordata, id=length(Gwarriors) + 1, uclevel=level, weapon=weapon))
    basename() = "ST$st,DX$dx,IQ$iq,Unarmed $level$(armordata == noarmor() ? "" : "," * armordata.armornamecombo)"
    function setarmor(data::Armor)
        armordata = data
        adjdx = dx - armordata.dxpenalty
        name, armor = toughness(st, iq, basename(), protection(armordata) + Gucevadeprotection[level])
        #println(stderr, "$name: UC ARMOR: $(Gucevadeprotection[level]), final armor: $armor")
    end
    reptileweapon((dice, mod)::Tuple{Int, Int}) = getucweapon(level, st, dice, mod + 2)
    for i in mindx:Gsettings.points - iq - minst
        dx = adjdx = i
        st = Gsettings.points - i - iq
        punch = getucweapon(level, st, punchstats...)
        doublepunch = getucweapon(level, st, punchstats[1], punchstats[2] - 1, extraatks=1, name="Double Unarmed")
        reptiledouble = getucweapon(level, st, punchstats[1], punchstats[2] + 1, extraatks=1, name="Double Unarmed")
        kick = getucweapon(level, st, kickstats..., name="Kick")
        # no armor
        setarmor(noarmor())
        if isstok(st)
            if isdxbelow20(dx, adjdx)
                addwarrior(punch)
                Gsettings.newrules && level == 5 && addwarrior(doublepunch, extra=",Double")
                Gsettings.reptiles && st >= 12 && addwarrior(reptileweapon(punchstats), extra=",Reptile Man")
                Gsettings.reptiles && st >= 12 && level == 5 && addwarrior(reptiledouble, extra=",Reptile Man,Double")
                #kick
                if adjdx - kickadj >= Gsettings.minadjdx
                    addwarrior(kick, extra=",Kick")
                    Gsettings.reptiles && st >= 12 && addwarrior(reptileweapon(kickstats), extra=",Reptile Man,Kick")
                end
            end
            if dx >= 9
                # cloth
                setarmor(clotharmor())
                if isdxbelow20(dx, adjdx)
                    addwarrior(punch)
                    Gsettings.newrules && level == 5 && addwarrior(doublepunch, extra=",Double")
                    Gsettings.reptiles && st >= 12 && addwarrior(reptileweapon(punchstats), extra=",Reptile Man")
                    Gsettings.reptiles && st >= 12 && level == 5 && addwarrior(reptiledouble, extra=",Reptile Man,Double")
                    # cloth with kick
                    if adjdx - kickadj >= Gsettings.minadjdx
                        addwarrior(kick, extra=",Kick")
                        Gsettings.reptiles && st >= 12 && addwarrior(reptileweapon(kickstats), extra=",Reptile Man,Kick")
                    end
                end
            end
        end
    end
end

function getucweapon(level, st, bonusdice, bonusmod; extraatks=0, name="")
    minst, dice, mod = Gbarehanddamage[st]
    dice += bonusdice # dice and mod are different for punches and kicks
    mod += bonusmod
    get!(Gucweapons, (level, minst, dice, mod)) do
        Weapon(length(Gweapontable) + 1, "$(name == "" ? "Unarmed" : name) $level $(dice)d$(mod == 0 ? "" : mod < 0 ? "- $(-mod)" : "+ $mod")", minst, dice, mod; extraatks=extraatks)
    end
end

"""
    addbrawlers()

Brawlers fight dirty and do not defend bare handed
"""
function addbrawlers()
    local st, dx, iq, name, armor, armordata
    warrior = Warrior(length(Gwarriors) + 1, "", 12, 12, 8, Gweaponsbyname["Dagger"], 0, noarmor(), 0, 0, 0, 0, 0)
    enemyadjdx = adjdx = 0
    addwarrior(weapon, armor, name) = pushwarrior(Warrior(warrior; st=st, dx=dx, iq=iq, name=name, armor=armor, armordata=armordata, id=length(Gwarriors) + 1, weapon=weapon, enemyadjdx=enemyadjdx))
    basename() = "ST$st,DX$dx,IQ$iq,Reptile Brawler, $(armordata.armornamecombo)"
    setarmor(data) = (armordata = data; armor = protection(armordata); adjdx = dx - armordata.dxpenalty; name = basename())
    for rarmordata in Garmortable
        armordata = rarmordata
        for rst in reptilest
            iq = 8
            st = rst
            dx = Gsettings.points - st - iq
            dx < 8 && break
            enemyadjdx = 0
            weapon = getucweapon(0, st, 0, 4, name="Claws") # These Reptile Men fight dirty
            addwarrior(weapon, protection(armordata), basename())
            if dx >= 9
                dx -= 1
                iq += 1
                name, armor = toughness(st, iq, basename(), protection(armordata))
                addwarrior(weapon, armor, name)
                if dx >= 9 && armordata.shield > 0
                    dx -= 1
                    iq += 1
                    name, armor, enemyadjdx = shieldexpert(name, iq, warrior, armordata, enemyadjdx)
                    name, armor = toughness(st, iq, basename(), armor)
                    addwarrior(weapon, armor, name)
                end
            end
        end
    end
end

function shieldexpert(name, iq, warrior, armordata, enemyadjdx)
    a, e = shieldexpert(iq, warrior, armordata, enemyadjdx)
    (protection(armordata) < a ? name * ",Shield Expert" : name), a, e
end

function shieldexpert(iq, warrior, armordata, enemyadjdx)
    if iq >= 10 && armordata.shield > 0 && warrior.weapon.twohanded == 0
        protection(armordata) + 1, enemyadjdx + 1
    else
        protection(armordata), enemyadjdx
    end
end

function toughness(st, iq, name, armor)
    if !(Gsettings.talents || Gsettings.expertise) || st < (Gsettings.newrules ? 12 : 14) || iq < (Gsettings.newrules ? 9 : 10)
        name, armor
    elseif st >= (Gsettings.newrules ? 14 : 16)
        name * ",Veteran", armor + 2
    else
        name * ",Warrior", armor + 1
    end
end

random(num) = ceil(rand() * num)

function d6(dice)
    #reduce(+, random(6) for i in 1:dice)
    tot = 0
    for i in 1:dice
        tot += random(6)
    end
    tot
end

function firstplayer(pvfirstplayer, pvsecondplayer, isp1first)
    first = isp1first ? "P1" : "P2"
    debugprint(first * " goes first")
    if dodisengage(pvfirstplayer, pvsecondplayer)
        debugprint(first * " disengages")
        global Gnumberofdisengages += 1
        playerdisengages(pvfirstplayer, pvsecondplayer)
    elseif isdefenderdefending(pvfirstplayer, pvsecondplayer)
        if pvfirstplayer.fallen == 0
            strike(pvfirstplayer, pvsecondplayer, true, false)  # defender is defending. No attack after charge
            if pvfirstplayer.warrior.weapon.extraattacks == 1
                strike(pvfirstplayer, pvsecondplayer, true, true) # second attack
            end
        end
    else
        if pvfirstplayer.fallen == 0 # standing?
            strike(pvfirstplayer, pvsecondplayer, false, false)
            if pvfirstplayer.warrior.weapon.extraattacks == 1 # second attack
                strike(pvfirstplayer, pvsecondplayer, true, true)
            end
        end
        if pvsecondplayer.curst > (Gsettings.newrules ? 0 : 1) && pvsecondplayer.fallen == 0 # still alive and standing
            if dodisengage(pvsecondplayer, pvfirstplayer)
                debugprint((isp1first ? "P2" : "P1") * " disengages")
                global Gnumberofdisengages += 1
                playerdisengages(pvsecondplayer, pvfirstplayer)
            else
                strike(pvsecondplayer, pvfirstplayer, false, false)
                if pvsecondplayer.warrior.weapon.extraattacks == 1
                    strike(pvsecondplayer, pvfirstplayer, false, true)
                end
            end
        end
    end
end

function matchsetmopup()
    for i in 1:Gsettings.numberofmatches
        Gmatcher[i] == 1 && (global Gwin1 += 1)
        Gmatcher[i] == 2 && (global Gwin2 += 1)
        Gmatcher[i] == 3 && (global Gdraw += 1)
    end
end

function matchmopup(i)
    global Gmatcher
    Gmatcher[i] = if Gp2.curst < (Gsettings.newrules ? 1 : 2)
        debugprint("P1 won match")
        1
    elseif Gp1.curst < (Gsettings.newrules ? 1 : 2)
        debugprint("P2 won match")
        2
    else
        3
    end
end

function turnmopup()
    global Gp1, Gp2
    Gp1.fallen == 1 && (Gp1.fallen = 0)
    Gp1.fallen == 2 && (Gp1.fallen = 1)
    Gp2.fallen == 1 && (Gp2.fallen = 0)
    Gp2.fallen == 2 && (Gp2.fallen = 1)
    adjustforforcedretreat(Gp1, Gp2)
    global Gstatavgnumberofturns += 1
end

function resetplayer(player)
    player.curst = player.warrior.st
    player.tmpdxmod = 0
    player.chargechance = player.warrior.weapon.polearm * (Gsettings.new3hexcharge ? Gsettings.initialchargechance : 100)
    player.fallen = 0
    player.lastattackdamaged = 0
end

function playerdisengages(player1, player2)
    if Gsettings.new3hexcharge
        player1.chargechance = Gsettings.twohexchargechance
        player2.warrior.weapon.polearm == 1 && (player2.chargechance = Gsettings.twohexchargechance)
    else
        player1.chargechance = 100
        player2.warrior.weapon.polearm == 1 && (player2.chargechance = 100)
    end
end

isdxbelow20(warrior) = isdxbelow20(warrior.dx, warrior.weapon, warrior.armordata)
isdxbelow20(dx, weapon, armor) = isdxbelow20(dx, dx - armor.dxpenalty - weapon.dxpenaltyallattacks)
isdxbelow20(dx, adjdx) = dx <= (Gsettings.newrules ? 24 : 20) && adjdx <= Gsettings.maxadjdx

function addmonster()
    scales = Armor(15, "Scales", 3, 0)
    claws = Weapon(19, "Claws", 30, 2, -1, extraatks=1)
    base = Warrior(length(Gwarriors) + 1, "4-Hex Dragon", 30, 13, 16, claws, protection(scales), scales, 0, 0, 0, 0, 0)
    addmon = (;mods...)-> pushwarrior(Warrior(base; id = length(Gwarriors) + 1, mods...))
    addmon()
    addmon(name = "4-Hex Dragon, Claw Expert", expert = 1, enemyadjdx = 1)
    addmon(name = "4-Hex Dragon, Claw Master, Shrewd, Veteran", master = 1, enemyadjdx = 2, armor = 5, shrewd = 1)
    addmon(name = "4-Hex Dragon, Claw Master, Veteran", master = 1, enemyadjdx = 2, armor = 5)
end

function addflorentine()
    dagger = makeflorentine("Dagger")
    rapier = makeflorentine("Rapier")
    morningstar = makeflorentine("Morningstar")
    machete = makeflorentine("Cutlass", name="Machete")
    armordata = noarmor()
    base = Warrior(length(Gwarriors) + 1, "", 12, 12, 8, rapier, 0, armordata, 0, 0, 0, 0, 0)
    namefor(; extra="", st, dx, iq, weapon, armordata, mods...) = "ST$st,DX$dx,IQ$iq,$(weapon.name),$(armordata.armornamecombo)$extra"
    prep(mods) = filter(p-> p.first in Gwarriorkeys, pairs(mods))
    function addwarrior(w = base; mods...)
        warrior = Warrior(w; name=namefor(;st=w.st, dx=w.dx, iq=w.iq, weapon=w.weapon, armordata=w.armordata, mods...), id = length(Gwarriors) + 1, prep(mods)...)
        pushwarrior(warrior)
        warrior
    end
    if Gsettings.points >= 32
        dx = 13 + Gsettings.points - 32
        if isdxbelow20(dx, dx)
            for weapon in [rapier, dagger]
                w = addwarrior(;st=weapon.minimumst, dx=dx, iq=32 - weapon.minimumst - 13, expert=1, weapon=weapon, enemyadjdx=1, extra=",E.Fencer,Florentine")
                addwarrior(w; extra = ",E.Fencer,Florentine,Shrewd", shrewd=1)
            end
        end
    end
    if Gsettings.points >= 36
        dx = Gsettings.points - 13 - 11 # min 12
        if isdxbelow20(dx, dx)
            w = addwarrior(;st=13, dx=dx, iq=11, expert=1, weapon=morningstar, enemyadjdx=1, extra=",Expert,Florentine")
            if dx >= 14
                addwarrior(w; extra = ",Expert,Florentine,Shrewd", shrewd=1)
            end
        end
        dx = Gsettings.points - 9 - 11 # min 16
        if isdxbelow20(dx, dx)
            w = addwarrior(st=9, dx=dx, iq=13, master=1, weapon=rapier, enemyadjdx=3, extra=",M.Fencer,Florentine")
            addwarrior(w, extra = ",M.Fencer,Florentine,Shrewd", shrewd=1)
        end
        dx = Gsettings.points - 10 - 11 # min 15
        if isdxbelow20(dx, dx)
            w = addwarrior(st=10, dx=dx, iq=11, expert=1, weapon=machete, enemyadjdx=1, extra=",Expert,Florentine")
            addwarrior(w, extra = ",Expert,Florentine,Shrewd", shrewd=1)
        end
    end
    if Gsettings.points >= 40
        dx = Gsettings.points - 13 - 13 # min 14
        if isdxbelow20(dx, dx)
            w = addwarrior(st=13, dx=dx, iq=13, master=1, weapon=morningstar, enemyadjdx=2, extra=",Master,Florentine")
            addwarrior(w, extra = ",Master,Florentine,Shrewd", shrewd=1)
        end
    end
end

function setnemesistally()
    for w in Gwarriors
        if w.nemesis != nothing
            w.nemesis.nemesisofhowmany += 1
        end
    end
end

#debugprint(args...) = Gsettings.debug && println(stderr, args...)
debugprint(args...) = ()

isstok(st) = st in okst

#function filter(func, pp::Base.Iterators.Pairs)
#    p = pairs([p for p in pp if func(p)]
#end

function testTft(ARGS)
    global Gprofile

    setglobals(ARGS)
    adjarmortable()
    adjweapontable()
    popwarriortable()
    println(stderr, "$(length(Gweapontable)) Weapons, $(length(Gwarriors)) Warriors")
    if Gprofile
        println(stderr, "PROFILING...")
        @profile runsim()
        out = open("/tmp/profile", "w")
        #Profile.print(out, format=:flat)
        Profile.print(out)
        close(out)
    else
        runsim()
    end
    output()
end

function output()
    println(join(values(outputfields), ","))
    for w in Gwarriors
        println(join(map(k-> begin
                             p = getwarriorproperty(w, k)
                             isa(p, String) ? "\"$p\"" : p == nothing ? "" : isa(p, Real) ? round(p, digits=2) : p
                         end, keys(outputfields)), ","))
    end
end

#Profile.init(n=5000000)

profileSim() = @profile testTft([
    "-points"
    "32"
    "-orig"
])

if !isempty(ARGS)
    testTft(ARGS)
end

max_stars_repo_name: zot/TFT-sim

max_issues_repo_name: zot/TFT-sim

max_forks_repo_name: zot/TFT-sim


text:
using Spark

# using master from setup.jl

function input_test(master::Spark.Master)
    rdd = Spark.input(master, "RDDA.txt", "test_reader")
    @assert rdd != false
    @assert length(keys(rdd.partitions)) == 3
end

function collect_test(master::Spark.Master)
    rdd = Spark.input(master, "RDDA.txt", "int_reader")
    collection = Spark.collect(master, rdd)
    for kv in collection
        @assert kv[1] <= 10
        @assert length(kv[2]) == 1
        @assert kv[2][1] <= 10
    end
    @assert length(collection) == 10
end

function count_test(master::Spark.Master)
    rdd = Spark.input(master, "RDDA.txt", "test_reader")
    count = Spark.count(master, rdd)
    @assert count == 10
end

function lookup_test(master::Spark.Master)
    rdd = Spark.input(master, "RDDA.txt", "int_reader")
    val = Spark.lookup(master, rdd, 3)
    @assert val == {3}
end

function partition_by_test(master::Spark.Master)
    rdd = Spark.input(master, "RDDA.txt", "int_reader")
    collection = Spark.collect(master, rdd)
    partitioned_rdd = Spark.partition_by(master, rdd, Spark.HashPartitioner())
    @assert Spark.count(master, rdd) == Spark.count(master, partitioned_rdd)
    for kv in collection
        @assert kv[2] == Spark.lookup(master, partitioned_rdd, kv[1])
    end
end

function filter_test(master::Spark.Master)
    rdd = Spark.input(master, "RDDA.txt", "int_reader")
    filtered = Spark.filter(master, rdd, "number_filter")
    count = Spark.count(master, filtered)
    val = Spark.lookup(master, filtered, 7)
    @assert count == 5
    @assert val == {}
end

function map_test(master::Spark.Master)
    rdd = Spark.input(master, "RDDA.txt", "int_reader")
    collection = Spark.collect(master, rdd)
    mapped_rdd = Spark.map(master, rdd, "double_map")
    @assert Spark.count(master, rdd) == Spark.count(master, mapped_rdd)
    for kv in collection
        values = Spark.lookup(master, mapped_rdd, kv[1])
        @assert 2*(kv[2][1]) == values[1]
    end
end

function group_by_key_test(master::Spark.Master)
    rdd = Spark.input(master, "RDDA.txt", "int_reader")
    collection = Spark.collect(master, rdd)
    mapped_rdd = Spark.map(master, rdd, "one_map")
    collection = Spark.collect(master, mapped_rdd)
    grouped_rdd = Spark.group_by_key(master, mapped_rdd)
    collection = Spark.collect(master, grouped_rdd)
    lookedup = Spark.lookup(master, grouped_rdd, 1)
    @assert Spark.count(master, grouped_rdd) == 1
    @assert length(Spark.lookup(master, grouped_rdd, 1)) == 10
end

function join_test(master::Spark.Master)
    rdd_a = Spark.input(master, "RDDA.txt", "int_reader")
    rdd_b = Spark.input(master, "RDDA.txt", "int_reader")
    joined_rdd = Spark.join(master, rdd_a, rdd_b)
    joined_collection = Spark.collect(master, joined_rdd)
    @assert length(joined_collection) == 10
    for kv in joined_collection
        @assert length(kv[2]) == 2
    end
end

ext: jl


text:
#### Surface functions

using Distributions
using Statistics

export update_surface!

"""
    update_surface!

Update surface conditions including
 - `windspeed`
 - `ρq_tot_flux`
 - `ρθ_liq_flux`
 - `bflux`
 - `obukhov_length`
 - `rho_uflux`
 - `rho_vflux`
"""
function update_surface! end

"""
    buoyancy_flux(shf::FT, lhf::FT, T_b::FT, q_tot::FT, α_0::FT) where {FT<:Real}
Old method (used in SCAMPY) for buoyancy flux
TODO: Remove this method.
"""
function buoyancy_flux(
    param_set::PS,
    shf::FT,
    lhf::FT,
    T_b::FT,
    q_tot::FT,
    α_0::FT,
) where {FT <: Real, PS}
    cp_ = cp_m(param_set, PhasePartition(q_tot))
    lv = latent_heat_vapor(param_set, T_b)
    return (
        FT(grav(param_set)) * α_0 / cp_ / T_b * (
            shf +
            ((FT(R_v(param_set)) / FT(R_d(param_set))) - 1) * cp_ * T_b * lhf /
            lv
        )
    )
end

function update_surface!(
    aux::StateVec,
    q::StateVec,
    grid::Grid{FT},
    params,
    model::SurfaceFixedFlux,
) where {FT}
    gm, en, ud, sd, al = allcombinations(aux)
    @unpack param_set = params
    k_1 = first_interior(grid, Zmin())
    T_1 = aux[:T, k_1, gm]
    q_tot_1 = q[:q_tot, k_1, gm]
    v_1 = q[:v, k_1, gm]
    u_1 = q[:u, k_1, gm]

    params[:windspeed] = compute_windspeed(q, k_1, gm, FT(0.0))
    params[:bflux] = buoyancy_flux(
        param_set,
        model.shf,
        model.lhf,
        T_1,
        q_tot_1,
        model.α_0_surf,
    )

    params[:obukhov_length] =
        compute_MO_len(params[:k_Karman], model.ustar, params[:bflux])
    params[:rho_uflux] =
        -model.ρ_0_surf * model.ustar * model.ustar / params[:windspeed] * u_1
    params[:rho_vflux] =
        -model.ρ_0_surf * model.ustar * model.ustar / params[:windspeed] * v_1
end

function percentile_bounds_mean_norm(
    low_percentile::FT,
    high_percentile::FT,
    n_samples::I,
) where {FT <: Real, I}
    x = rand(Normal(), n_samples)
    xp_low = quantile(Normal(), low_percentile)
    xp_high = quantile(Normal(), high_percentile)
    filter!(y -> xp_low < y < xp_high, x)
    return Statistics.mean(x)
end

"""
    surface_tke(ustar::FT, wstar::FT, zLL::FT, obukhov_length::FT) where FT<:Real

computes the surface tke

 - `ustar` friction velocity
 - `wstar` convective velocity
 - `zLL` elevation at the first grid level
 - `obukhov_length` Monin-Obhukov length
"""
function surface_tke(
    ustar::FT,
    wstar::FT,
    zLL::FT,
    obukhov_length::FT,
) where {FT <: Real}
    if obukhov_length < 0
        return (
            (3.75 + cbrt(zLL / obukhov_length * zLL / obukhov_length)) *
            ustar *
            ustar + 0.2 * wstar * wstar
        )
    else
        return (3.75 * ustar * ustar)
    end
end

"""
    surface_variance(flux1::FT, flux2::FT, ustar::FT, zLL::FT, oblength::FT) where FT<:Real

computes the surface variance given

 - `ustar` friction velocity
 - `wstar` convective velocity
 - `zLL` elevation at the first grid level
 - `oblength` Monin-Obhukov length
"""
function surface_variance(
    flux1::FT,
    flux2::FT,
    ustar::FT,
    zLL::FT,
    oblength::FT,
) where {FT <: Real}
    c_star1 = -flux1 / ustar
    c_star2 = -flux2 / ustar
    if oblength < 0
        return 4 * c_star1 * c_star2 * (1 - 8.3 * zLL / oblength)^(-2 / 3)
    else
        return 4 * c_star1 * c_star2
    end
end

"""
    compute_convective_velocity(bflux, inversion_height)

Computes the convective velocity scale, given the buoyancy flux
`bflux`, and inversion height `inversion_height`.
FIXME: add reference
"""
compute_convective_velocity(bflux::FT, inversion_height::FT) where {FT} =
    cbrt(max(bflux * inversion_height, FT(0)))

"""
    compute_windspeed(q::StateVec, k::IT, i::IT, windspeed_min::FT) where {FT, IT}

Computes the windspeed
"""
function compute_windspeed(
    q::StateVec,
    k::IT,
    i::IT,
    windspeed_min::FT,
) where {FT, IT}
    return max(hypot(q[:u, k, i], q[:v, k, i]), windspeed_min)
end

"""
    compute_inversion_height(aux::StateVec, q::StateVec, grid::Grid, params)

Computes the inversion height (a non-local variable)
FIXME: add reference
"""
function compute_inversion_height(
    aux::StateVec,
    q::StateVec,
    grid::Grid{FT},
    params,
) where {FT}
    @unpack Ri_bulk_crit, param_set, SurfaceModel = params
    gm, en, ud, sd, al = allcombinations(q)
    k_1 = first_interior(grid, Zmin())
    windspeed = compute_windspeed(q, k_1, gm, 0.0)^2
    _grav::FT = grav(param_set)

    # test if we need to look at the free convective limit
    z = grid.zc
    h = 0
    Ri_bulk, Ri_bulk_low = 0, 0
    ts = ActiveThermoState(param_set, q, aux, k_1, gm)
    θ_ρ_b = virtual_pottemp(ts)
    k_star = k_1
    if windspeed <= SurfaceModel.tke_tol
        for k in over_elems_real(grid)
            if aux[:θ_ρ, k] > θ_ρ_b
                k_star = k
                break
            end
        end
        h =
            (z[k_star] - z[k_star - 1]) /
            (aux[:θ_ρ, k_star] - aux[:θ_ρ, k_star - 1]) *
            (θ_ρ_b - aux[:θ_ρ, k_star - 1]) + z[k_star - 1]
    else
        for k in over_elems_real(grid)
            Ri_bulk_low = Ri_bulk
            Ri_bulk =
                _grav * (aux[:θ_ρ, k] - θ_ρ_b) * z[k] / θ_ρ_b /
                (q[:u, k, gm]^2 + q[:v, k, gm]^2)
            if Ri_bulk > Ri_bulk_crit
                k_star = k
                break
            end
        end
        h =
            (z[k_star] - z[k_star - 1]) / (Ri_bulk - Ri_bulk_low) *
            (Ri_bulk_crit - Ri_bulk_low) + z[k_star - 1]
    end
    return h
end

"""
    compute_MO_len(ustar::FT, bflux::FT) where {FT<:Real}

Compute Monin-Obhukov length given
 - `ustar` friction velocity
 - `bflux` buoyancy flux
"""
function compute_MO_len(
    k_Karman::FT,
    ustar::FT,
    bflux::FT,
) where {FT <: Real, PS}
    return abs(bflux) < FT(1e-10) ? FT(0) :
           -ustar * ustar * ustar / bflux / k_Karman
end


text:
struct CauchyPoint end

function (cp::CauchyPoint)(state, B, Δ)
    p = similar(state.x)
    ∇f = state.∇f
    norm_∇f = norm(∇f)
    prod = ∇f'*B*∇f
    _cauchypoint!(p, Δ, ∇f, norm_∇f, prod)
    return p
end

function _cauchypoint!(p, Δ, ∇f, norm_∇f, prod)
    p .= -Δ/norm_∇f*∇f
    if prod > 0
        p .*= min(1, norm_∇f^3/(Δ*prod))
    end
    return p
end


text:
module TestRelationalPrograms
using Test

using Catlab.CategoricalAlgebra.CSets
using Catlab.WiringDiagrams.UndirectedWiringDiagrams
using Catlab.Programs.RelationalPrograms

# Relations
###########

# Untyped relations

parsed = @relation (x,z) where (x,y,z) begin
  R(x,y)
  S(y,z)
end

d = RelationDiagram{Symbol}(2)
add_box!(d, 2, name=:R); add_box!(d, 2, name=:S)
add_junctions!(d, 3, variable=[:x,:y,:z])
set_junction!(d, [1,2,2,3])
set_junction!(d, [1,3], outer=true)
@test parsed == d

parsed = @relation ((x,z) where (x,y,z)) -> (R(x,y); S(y,z))
@test parsed == d

parsed = @relation function (x,z) where (x,y,z); R(x,y); S(y,z) end
@test parsed == d

# Typed relations

parsed = @relation (x,y,z) where (x::X, y::Y, z::Z, w::W) begin
  R(x,w)
  S(y,w)
  T(z,w)
end
d = RelationDiagram{Symbol}([:X,:Y,:Z])
add_box!(d, [:X,:W], name=:R)
add_box!(d, [:Y,:W], name=:S)
add_box!(d, [:Z,:W], name=:T)
add_junctions!(d, [:X,:Y,:Z,:W], variable=[:x,:y,:z,:w])
set_junction!(d, [1,4,2,4,3,4])
set_junction!(d, [1,2,3], outer=true)
@test parsed == d

# Tensor networks
#################

# Parsing
#--------

parsed = @tensor_network (i,j,k,ℓ) D[i,j,k] = A[i,ℓ] * B[j,ℓ] * C[k,ℓ]
d = RelationDiagram{Symbol}(3)
add_box!(d, 2, name=:A); add_box!(d, 2, name=:B); add_box!(d, 2, name=:C)
add_junctions!(d, 4, variable=[:i,:j,:k,:ℓ])
set_junction!(d, [1,4,2,4,3,4])
set_junction!(d, 1:3, outer=true)
@test parsed == d

parsed = @tensor_network D[i,j,k] = A[i,ℓ] * B[j,ℓ] * C[k,ℓ]
@test parsed == d
parsed = @tensor_network D[i,j,k] := A[i,ℓ] * B[j,ℓ] * C[k,ℓ]
@test parsed == d

# Degenerate case: single term.
parsed = @tensor_network B[i,j,k] = A[i,j,k]
d = singleton_diagram(RelationDiagram{Symbol}, 3, name=:A)
set_subpart!(d, :variable, [:i,:j,:k])
@test parsed == d

# Degenerate case: no terms.
parsed = @tensor_network A[i,j] = 1
d = RelationDiagram{Symbol}(2)
add_junctions!(d, 2, variable=[:i,:j])
set_junction!(d, 1:2, outer=true)
@test parsed == d

# Round trip
#-----------

# Parse and then compile.
macro roundtrip_tensor(tensor)
  quote
    compiled = compile_tensor_expr(@tensor_network($tensor),
                                   assign_op=:(=), assign_name=:out)
    @test compiled == $(QuoteNode(tensor))
  end
end

@roundtrip_tensor out[i,k] = A[i,j] * B[j,k]
@roundtrip_tensor out[i,j,k] = A[i,ℓ] * B[j,ℓ] * C[k,ℓ]
@roundtrip_tensor out = u[i] * A[i,j] * v[j]
@roundtrip_tensor out[j] = α * a[i] * B[i,j]

end


text:
using RedPitayaDAQServer
using PyPlot
using ThreadPools

# obtain the URL of the RedPitaya
include("config.jl")

rp = RedPitayaCluster([URLs[1]])

dec = 64
modulus = 4800
base_frequency = 125000000
periods_per_step = 5
samples_per_period = div(modulus, dec)
periods_per_frame = 50 # about 0.5 s frame length
frame_period = dec*samples_per_period*periods_per_frame / base_frequency
slow_dac_periods_per_frame = div(20, periods_per_step)

decimation(rp, dec)
samplesPerPeriod(rp, samples_per_period)
periodsPerFrame(rp, periods_per_frame)
passPDMToFastDAC(master(rp), true)

modeDAC(rp, "STANDARD")
ramWriterMode(rp, "TRIGGERED")
triggerMode(rp, "EXTERNAL")

# Sequence
# Global Settings
slowDACStepsPerFrame(rp, slow_dac_periods_per_frame) # This sets PDMClockDivider, but it can also be set directly and with slowDACStepsPerSequence
numSlowDACChan(master(rp), 1)
# Per Sequence settings
lut = collect(range(0,0.7,length=slow_dac_periods_per_frame))
seq = ArbitrarySequence(lut, nothing, slow_dac_periods_per_frame, 1, 0, 0)
config = fastDACConfig(seq)
frequencyDAC(config,1,1, base_frequency / modulus)
signalTypeDAC(config, 1 , "SINE")
amplitudeDAC(config, 1, 1, 0.2)
phaseDAC(config, 1, 1, 0.0 )
appendSequence(master(rp), seq)

seq2 = ConstantSequence([-0.2], nothing, slow_dac_periods_per_frame, 1, 0, 0)
config2 = fastDACConfig(seq2)
# Only updates values
amplitudeDAC(config2, 1, 1, 0.8)
appendSequence(master(rp), seq2)

seq3 = ConstantSequence([-0.2], nothing , slow_dac_periods_per_frame, 1, 0, 0)
config3 = fastDACConfig(seq3)
amplitudeDAC(config3, 1, 1, 0.4)
appendSequence(master(rp), seq3)
success = prepareSequence(master(rp))

masterTrigger(rp, false)
startADC(rp)
masterTrigger(rp, true)

sleep(0.1)

uCurrentFrame = readFrames(rp, 0, 3)
stopADC(rp)
masterTrigger(rp, false)
clearSequence(rp)

fig = figure(1)
clf()
plot(vec(uCurrentFrame[:,1,:,:]))
plot(vec(uCurrentFrame[:,2,:,:]))
legend(("Rx1"))
fig

max_stars_repo_name: tknopp/RedPitayaDAQServer

max_issues_repo_name: tknopp/RedPitayaDAQServer

max_forks_repo_name: tknopp/RedPitayaDAQServer


text:
### Specify simulation parameters ###
@with_kw mutable struct Simulation_Parameters
    TTC_car_freq::Int64
    max_load_gov_restr::Float64 # 1.0 means there is no restrictions, because TTC car can take 1.0*max_load.
    when_to_run_people::Float64
    when_to_run_wagons::Float64
	sim_run_time_in_secs::Int
	N_agents::Int
	agents_speed_in_sec_per_m::Float64 # 0.8 means 0.8s per 1m of distance. The average, healthy human walks at a pace of 100m in 80 sec. Here we assume 100m=80s, so 1m = 0.8s.
    max_load_subway::Int64
    max_load_streetcar::Int64
	p0::Float64
end

### Agent definition ###
@with_kw mutable struct Commuter
    id::Int
    pos::Int64
    work_loc::Int64
    home_loc::Int64
    direction::Symbol = :work_to_home # It will be changed to home --> work in a first step

    infected::Bool = false
    infected_in::Symbol = :not_infected

    path::Dict{Int,Int} = Dict{Int,Int}()
    path_distances::Dict{Int,Float64} = Dict{Int,Float64}()
    paths_home_work::Dict{Int, Dict} = Dict{Int, Dict}()
    distances_home_work::Dict{Int,Dict} = Dict{Int,Dict}()
    paths_work_home::Dict{Int, Dict} = Dict{Int, Dict}()
    distances_work_home::Dict{Int,Dict} = Dict{Int,Dict}()
    no_of_trips::Int = 0

    wagon_id::Int64 = -999
	met_infected_in_wagon::Set{Int} = Set{Int}()

end

Commuter(id, pos, home_loc, work_loc) = Commuter(id = id, pos = pos, home_loc = home_loc, work_loc = work_loc)

### Articial agents definition ###
@with_kw mutable struct Artificial_Commuter_Stats
    id::Int = 999
	enter_every_sec::Int = 60
end

### Wagon definition ###
@with_kw mutable struct Wagon
    id::Int
    pos::Int64
    line::Symbol
    type::Symbol

    path::OrderedDict{Int,Int} = OrderedDict{Int,Int}() #seqence of nodes
    path_distances::OrderedDict{Int,Float64} = OrderedDict{Int,Float64}() # distance from each node in the sequence

    passengers::Set{Commuter} = Set{Commuter}()

    max_load::Union{Int, Float64}
    run_every::Int64 #sec

    time_start::Float64 = 0.0
    next_wagon_requested::Bool = false

    max_passengers_ever::Int64 = 0
end

### Simulation definiton ###
# EventTime represents time when event occurs. the second value in tuple is for sort ordering when 2 or more events are scheduled for the same point in time.
const EventTime = Tuple{Float64,Float64}

@with_kw mutable struct Simulation
    m::OpenStreetMapX.MapData

    agents::Vector{Commuter}
    wagons::Vector{Wagon}
    artificial_agent_stats::Artificial_Commuter_Stats

    recent_wagon::Int = -9999
    nodes_agents::Vector{Set{Int}}
    nodes_wagons::Vector{Set{Int}}

	infected_agents_count_current::Int
	infected_agents_wagon_current::Int
	infected_agents_street_current::Int

    infected_agents_count::Vector{Int}
    infected_agents_wagon::Vector{Int}
    infected_agents_street::Vector{Int}

    events = PriorityQueue{Union{Commuter,Wagon,Artificial_Commuter_Stats}, EventTime}()

    p0::Float64 # = 0.01

    max_load_subway::Int # = 15
    max_load_streetcar::Int # = 15
    max_load_gov_restr::Float64 # 1.0 means there is no restrictions, because TTC car can take 1.0*max_load.

    max_passengers_per_TTC_car::Dict{Tuple,Int64} = Dict{Tuple,Int64}() # [string(wagon.id,"|") * wagon.line * string("|",t[1])] = wagon.max_passengers_ever

end

function Simulation(m::OpenStreetMapX.MapData
					, orig_map_nodes_num::Int
					, routes_path::OrderedDict{Symbol, OrderedDict{Int64,Int64}}
					, routes_distances::OrderedDict{Symbol, OrderedDict{Int64,Float64}}
					, routes_types_both_dir::Dict{Symbol,Symbol}
					, N_agents::Int
					, sim_run_time_in_secs::Int
					, TTC_freq::Int64
                    , max_load_subway::Int64
                    , max_load_streetcar::Int64
					, max_load_gov_restr::Float64
					, p0::Float64)

	println("TTC_freq: ",TTC_freq)
    println("max_load_subway: ",max_load_subway)
	println("max_load_streetcar: ",max_load_streetcar)
	println("max_load_gov_restr: ",max_load_gov_restr)
	println("N_agents: ",N_agents)
	println("sim_run_time_in_secs: ",sim_run_time_in_secs)
	println("p0: ", p0)

    vv = size(m.g)[1] # number of vertices

    ### Agents creation ###
    home_loc = rand(1:orig_map_nodes_num,N_agents)
    work_loc = rand(1:orig_map_nodes_num,N_agents)
    agents = Commuter.(1:N_agents, home_loc, home_loc, work_loc)
    for ag in agents
       while ag.home_loc == ag.work_loc
            ag.work_loc = rand(1:orig_map_nodes_num)
       end
    end
    # Routes from home to work and inverse
    for agent in agents
        # Home to work
        ys = yen_k_shortest_paths(m.g, agent.home_loc, agent.work_loc, m.w, 3, maxdist=100_000)
        for (index, nodes) in enumerate(ys.paths)
            agent.paths_home_work[index] = Dict()
            agent.distances_home_work[index] = Dict()
            setindex!.(Ref(agent.paths_home_work[index]), nodes[2:end], nodes[1:end-1])
            for i in 1:(length(nodes)-1)
                agent.distances_home_work[index][nodes[i]] = m.w[nodes[i], nodes[i+1]]
            end
        end

        # Work to home
        ys = yen_k_shortest_paths(m.g, agent.work_loc, agent.home_loc, m.w, 3, maxdist=100_000)
        for (index, nodes) in enumerate(ys.paths)
            agent.paths_work_home[index] = Dict()
            agent.distances_work_home[index] = Dict()
            setindex!.(Ref(agent.paths_work_home[index]), nodes[2:end], nodes[1:end-1])
            for i in 1:(length(nodes)-1)
                agent.distances_work_home[index][nodes[i]] = m.w[nodes[i], nodes[i+1]]
            end
        end
        # Current path: home --> work
        # !!! We should not assign it here, because it will negatively influence timing.
        # It will be 1-step delayed comparing to the reality.
        # That is why it is mentioned here, but commented!
        # agent.path = deepcopy(agent.path_home_work)
        # agent.path_distances = deepcopy(agent.path_distances_home_work)

    end

    ### Wagons creation ###
    wagons = Wagon[]
    wagon_id = 1
    for r in collect(keys(routes_path))
        push!(wagons, Wagon(id = wagon_id
                ,pos = collect(keys(routes_path[r]))[1]
                ,line = r
                ,type = routes_types_both_dir[r]
                ,path = routes_path[r]
                ,path_distances = routes_distances[r]
                ,run_every = TTC_freq
                ,max_load = routes_types_both_dir[r] == :subway ? max_load_subway : max_load_streetcar
                ))
        wagon_id += 1
    end
    nodes_agents = [Set{Int}() for _ in 1:vv]
    nodes_wagons = [Set{Int}() for _ in 1:vv]
	# Add agents and wagons current positions.
    for agent in agents
        push!(nodes_agents[agent.pos], agent.id)
    end

    for wagon in wagons
        push!(nodes_wagons[wagon.pos], wagon.id)
    end
	# Identify patient zero and ensure that his/her trip is long enough, so we are sure that the only infected agent is not locked at home.
	target_n_of_patients_zero = N_agents*0.01
	n_of_patients_zero = 0
	while n_of_patients_zero < target_n_of_patients_zero
		patient_zero = rand(agents)
		if patient_zero.infected == false
			patient_zero.infected = true
			n_of_patients_zero += 1
		end
	end
    println("Number of patients zero: ", n_of_patients_zero)
    Simulation(agents = agents, wagons = wagons, recent_wagon = length(collect(keys(routes_path))), m = m, artificial_agent_stats = Artificial_Commuter_Stats(),
        nodes_agents = nodes_agents, nodes_wagons = nodes_wagons,
		infected_agents_count_current = n_of_patients_zero, infected_agents_wagon_current = 0, infected_agents_street_current = 0,
        infected_agents_count = fill(n_of_patients_zero, sim_run_time_in_secs + 1), infected_agents_wagon = zeros(Int, sim_run_time_in_secs + 1),
        infected_agents_street = zeros(Int, sim_run_time_in_secs + 1), max_load_subway = max_load_subway,
        max_load_streetcar = max_load_streetcar, max_load_gov_restr = max_load_gov_restr, p0 = p0)
end

println("Simulations objects were created")

max_stars_repo_name: NykPol/EpidemicInUrbanNetworkToronto

max_issues_repo_name: NykPol/EpidemicInUrbanNetworkToronto

max_forks_repo_name: NykPol/EpidemicInUrbanNetworkToronto


text:
export proxNuclear!, normNuclear

"""
    proxNuclear!(x::Vector{T}, λ::Float64; svtShape::NTuple=[],kargs...)

applies singular value soft-thresholding - i.e. the proximal map for the nuclear norm regularization.

# Arguments:
* `x::Array{T}`          - Vector to apply proximal map to
* `λ::Float64`           - regularization paramter
* `svtShape::NTuple=[]`  - size of the underlying matrix
"""
function proxNuclear!(x::Vector{T}, λ::Float64; svtShape::NTuple=[],kargs...) where T
  U,S,V = svd(reshape(x, svtShape))
  proxL1!(S,λ)
  x[:] = vec(U*Matrix(Diagonal(S))*V')
end

"""
    normNuclear(x::Vector{T}, λ::Float64; svtShape::NTuple=[],kargs...) where T

returns the value of the nuclear norm regularization term.
Arguments are the same as in `proxNuclear!`
"""
function normNuclear(x::Vector{T}, λ::Float64; svtShape::NTuple=[],kargs...) where T
  U,S,V = svd( reshape(x, svtShape) )
  return λ*norm(S,1)
end


text:
import Pkg

pkgs = [
    "KernelAbstractions",
    "CUDAKernels",
]
if !(VERSION < v"1.6-")
    push!(pkgs, "ROCKernels")
end

Pkg.test(pkgs; coverage = true)


text:
include("magmaCgesvd.jl")
include("magmaDgesvd.jl")
include("magmaSgesvd.jl")
include("magmaZgesvd.jl")

# wrappers of the low-level MAGMA functions
for (function_name, element_type, singular_value_type) in
	((:magmaCgesvd,:ComplexF32,:Cfloat),
                      (:magmaZgesvd,:ComplexF32,:Cdouble))
	@eval begin
		# magma_int_t magma_cgesvd 	( 	magma_vec_t  	jobu,
		# 	magma_vec_t  	jobvt,
		# 	magma_int_t  	m,
		# 	magma_int_t  	n,
		# 	magmaFloatComplex *  	A,
		# 	magma_int_t  	lda,
		# 	float *  	s,
		# 	magmaFloatComplex *  	U,
		# 	magma_int_t  	ldu,
		# 	magmaFloatComplex *  	VT,
		# 	magma_int_t  	ldvt,
		# 	magmaFloatComplex *  	work,
		# 	magma_int_t  	lwork,
		# 	float *  	rwork,
		# 	magma_int_t *  	info
		# )
		function gesvd!(jobu,
                      jobvt,
                      A::CuMatrix{$element_type},
                      ldu,
                      ldvt,
					  lwork,
					  rwork)
			convert(Cint,jobu)
			convert(Cint,jobvt)
           m, n = size(A)
           # there should be some certain checking
		   # for the input matrix and arrays
           # before we can calculate further input
		   # and call the lower C function wrappers
           lda = max(1,stride(A,2))
           s = zeros(min(m,n))

		   # there should be some conditions for the size of U and VT
		   # but we will deal with them later
		   U = zeros($element_type,ldu,m)
		   VT = zeros($element_type,ldvt,n)

		   work = zeros($element_type,max(1,lwork))
		   info = zeros(Cint,1)

           $function_name(jobu, jobvt, m, n, A, lda, s, U, ldu, VT,
		    ldvt, work, lwork, rwork, info)
           return U, s, VT, work, info
	   end
    end
end

for (function_name, element_type, singular_value_type) in
	 ((:magmaDgesvd,:Cdouble,:Cdouble),
                      (:magmaSgesvd,:Cfloat,:Cfloat))
	@eval begin
		# magma_int_t magma_dgesvd 	( 	magma_vec_t  	jobu,
		# 	magma_vec_t  	jobvt,
		# 	magma_int_t  	m,
		# 	magma_int_t  	n,
		# 	magmaFloatComplex *  	A,
		# 	magma_int_t  	lda,
		# 	float *  	s,
		# 	magmaFloatComplex *  	U,
		# 	magma_int_t  	ldu,
		# 	magmaFloatComplex *  	VT,
		# 	magma_int_t  	ldvt,
		# 	magmaFloatComplex *  	work,
		# 	magma_int_t  	lwork,
		# 	magma_int_t *  	info
		# )
		#Base.unsafe_convert(::CuPtr{$element_type},x::CuArray{$element_type})=Base.unsafe_convert(CuPtr{$element_type},Base.cconvert(CuPtr{($element_type)}x))
		#Base.unsafe_convert(::PtrOrCuPtr{$element_type},x::CuArray{$element_type})=Base.unsafe_convert(PtrOrCuPtr{$element_type},Base.cconvert(PtrOrCuPtr{($element_type)}x))
		function gesvd!(jobu,
                      jobvt,
                      A::Array{$element_type, 2},
                      ldu,
                      ldvt,
					  lwork)
			convert(Cint,jobu)
			convert(Cint,jobvt)
           m, n = size(A)
           # there should be some certain checking
		   # for the input matrix and arrays
           # before we can calculate further input
		   # and call the lower C function wrappers
           lda = max(1,stride(A,2))
           s = (zeros(min(m,n)))

		   # there should be some conditions for the size of U and VT
		   # but we will deal with them later
		   U = (zeros($element_type,ldu,m))
		   VT = (zeros($element_type,ldvt,n))

		   work = (zeros($element_type,max(1,lwork)))

		   info = (zeros(Cint,1))

		   $function_name(jobu, jobvt, m, n, A, lda, s, U, ldu, VT,
		    ldvt, work, lwork, info)
           return U, s, VT, work, info
	   end
    end
end


text:
using ReachabilityBenchmarks, LazySets, MathematicalPredicates, MAT

function iss_specification()
    # initial set: xᵢ ∈ [-1e-4, 1e-4] for all i
    X0 = BallInf(zeros(270), 1e-4)

    # safety property: y ≤ 7e-4 for linear combination y (defined in out.mat)
    y = read(matopen(@relpath "out.mat"), "M")[1, :]
    property = is_contained_in(HalfSpace(y, 7e-4))

    # time horizon: 20 time units
    time_horizon = 20.0

    # specification
    O = Dict(:T => time_horizon, :property => property)

    return X0, O
end

max_stars_repo_name: JuliaReach/ReachabilityBenchmarks

max_issues_repo_name: JuliaReach/ReachabilityBenchmarks

max_forks_repo_name: JuliaReach/ReachabilityBenchmarks


text:
#!/usr/bin/julia

# Copyright (C) 2021 Matheus Fernandes Bigolin <mfrdrbigolin@disroot.org>
# SPDX-License-Identifier: MIT

"Day Fourteen, Extended Polymerization."

include("Utils.jl")

# N.b.: external package, used for the Accumulator data structure.
using DataStructures

using .Utils

"""Return the difference between the most common element with the least common
element after `steps`, according to `rules` and starting with `template`."""
function solve(template :: Array{Char}, rules :: Dict{String, Char}, steps :: Int)
    # Group the template letters in adjacent pairs.
    pairs = join.(zip(collect(template[1:end-1]), collect(template[2:end])))

    space = counter(pairs)
    letters = counter(template)

    for _ in 1:steps
        new_space = Accumulator{String, Int}()

        for (k, v) in space
            letters[rules[k]] += v

            new_space[join([first(k), rules[k]])] += v
            new_space[join([rules[k], last(k)])] += v
            new_space[k] -= v
        end

        merge!(+, space, new_space)
    end

    return abs(reduce(-, extrema(values(letters))))
end

const RULES_REX = r"(\w{2}) -> (\w)"

function main()
    usage_and_exit(length(ARGS) != 1)

    data = readlines(first(ARGS))

    template = collect(data[1])

    rules_data = data[3:end]
    rules_array = [Tuple(m.captures) for m in match.(RULES_REX, rules_data)]
    rules = Dict{String, Char}([(p, first(r)) for (p, r) in rules_array])

    println(solve(template, rules, 10))
    println(solve(template, rules, 40))
end

if abspath(PROGRAM_FILE) == @__FILE__
    main()
end


text:
using SeaPearl

struct Edge
    vertex1     :: Int
    vertex2     :: Int
end

struct InputData
    edges               :: Array{Edge}
    numberOfEdges       :: Int
    numberOfVertices    :: Int
end

struct OutputData
    numberOfColors      :: Int
    edgeColors          :: Array{Int}
    optimality          :: Bool
end

include("IOmanager.jl")

function outputFromSeaPearl(sol::SeaPearl.Solution; optimality=false)
    numberOfColors = 0
    edgeColors = Int[]

    for key in keys(sol)
        color = sol[key]
        if !(color in edgeColors)
            numberOfColors += 1
        end
        push!(edgeColors, color)
    end

    return OutputData(numberOfColors, edgeColors, optimality)
end

function solve_coloring(input_file; benchmark=false)
    input = getInputData(input_file)

    trailer = SeaPearl.Trailer()
    model = SeaPearl.CPModel(trailer)

    ### Variable declaration ###
    x = SeaPearl.IntVar[]
    for i in 1:input.numberOfVertices
        push!(x, SeaPearl.IntVar(1, input.numberOfVertices, string(i), trailer))
        SeaPearl.addVariable!(model, last(x))
    end

    ### Constraints ###
    # Breaking some symmetries
    push!(model.constraints, SeaPearl.EqualConstant(x[1], 1, trailer))
    push!(model.constraints, SeaPearl.LessOrEqual(x[1], x[2], trailer))

    # Edge constraints
    degrees = zeros(Int, input.numberOfVertices)
    for e in input.edges
        push!(model.constraints, SeaPearl.NotEqual(x[e.vertex1], x[e.vertex2], trailer))
        degrees[e.vertex1] += 1
        degrees[e.vertex2] += 1
    end
    sortedPermutation = sortperm(degrees; rev=true)

    ### Objective ###
    numberOfColors = SeaPearl.IntVar(0, input.numberOfVertices, "numberOfColors", trailer)
    SeaPearl.addVariable!(model, numberOfColors)
    for var in x
        push!(model.constraints, SeaPearl.LessOrEqual(var, numberOfColors, trailer))
    end
    SeaPearl.addObjective!(model, numberOfColors)

    ### Variable selection heurstic ###
    function selectVariable(model::SeaPearl.CPModel, sortedPermutation, degrees)
        maxDegree = 0
        toReturn = nothing
        for i in sortedPermutation
            if !SeaPearl.isbound(model.variables[string(i)])
                if isnothing(toReturn)
                    toReturn = model.variables[string(i)]
                    maxDegree = degrees[i]
                end
                if degrees[i] < maxDegree
                    return toReturn
                end

                if length(model.variables[string(i)].domain) < length(toReturn.domain)
                    toReturn = model.variables[string(i)]
                end
            end
        end
        return toReturn
    end

    return model

    status = SeaPearl.solve!(model; variableHeuristic=((m; cpmodel=nothing) -> selectVariable(m, sortedPermutation, degrees)))
    if !benchmark
        for oneSolution in model.statistics.solution
            output = outputFromSeaPearl(oneSolution)
            printSolution(output)
        end
    end
    return status
end

using Gadfly
using LightGraphs
using GraphPlot

function testGraph(input_file)
    model = solve_coloring(input_file)
    g = SeaPearl.CPLayerGraph(model)
    nodelabel = map((x) -> SeaPearl.labelOfVertex(g, x)[1], collect(1:nv(g)))

    nlist = Vector{Int}[] # two shells
    push!(nlist, collect((g.numberOfConstraints+1):(g.numberOfConstraints+g.numberOfVariables))) # second shell
    push!(nlist, collect((g.numberOfConstraints+g.numberOfVariables+1):(g.numberOfConstraints+g.numberOfVariables+g.numberOfValues))) # second shell
    push!(nlist, collect(1:g.numberOfConstraints)) # first shell

    membership = map((x) -> SeaPearl.labelOfVertex(g, x)[2], collect(1:nv(g)))
    nodecolor = [colorant"red", colorant"purple", colorant"green"]
    # membership color
    nodefillc = nodecolor[membership]
    # locs_x, locs_y = shell_layout(g, nlist)
    return g, nodefillc, nodelabel
end

max_stars_repo_name: corail-research/SeaPearlZoo

max_issues_repo_name: corail-research/SeaPearlZoo

max_forks_repo_name: corail-research/SeaPearlZoo


text:
@doc raw"""
    Direction3

An object `d` of the class [`Direction_2`](@ref) is a vector in the
three-dimensional vector space ``ℝ^3`` where we forget about its length.

They can be viewed as unit vectors, although there is no normalization
internally, since this is error prone. Directions are used whenever the
length of a vector does not matter. They also characterize a set of parallel
oriented lines that have the same orientations. For example, you can ask for
the direction orthogonal to an oriented plane, or the direction of an
oriented line.
"""
Direction3

"""
    Direction3(v::Vector3)

Introduces a direction `d` initialized with the direction of vector `v`.
"""
Direction3(v::Vector3)

"""
    Direction3(l::Line3)

Introduces the direction `d` of line `l`.
"""
Direction3(l::Line3)

"""
    Direction3(r::Ray3)

Introduces the direction `d` of ray `r`.
"""
Direction3(r::Ray3)

"""
    Direction3(s::Segment3)

Introduces the direction `d` of segment `s`.
"""
Direction3(s::Segment3)

"""
    Direction3(x::RT, y::RT, z::RT)
    Direction3(x::Real, y::Real, z::Real)

Introduces a direction `d` initialized with the direction from the origin and
the point with Cartesian coordinates ``(x, y, z)``.
"""
Direction3(x::Real, y::Real, z::Real) =
    Direction3(convert(FT, x), convert(FT, y), convert(FT, z))

@doc raw"""
    delta(d::Direction3, i::Integer)

Returns values, such that
`d == [Direction3](@ref)(delta(d, 0), delta(d, 1), delta(d, 3))`.

!!! info "Precondition"

    ``0 ≤ i ≤ 2``
"""
delta(d::Direction3, i::Integer)

"""
    dx(d::Direction3)

Returns `delta(d, 0)`.
"""
dx(d::Direction3)

"""
    dy(d::Direction3)

returns `delta(d, 1)`.
"""
dy(d::Direction3)

"""
    dz(d::Direction3)

returns `delta(d, 2)`.
"""
dz(d::Direction3)

"""
    -(d::Direction3)

The direction opposite to `d`.
"""
-(d::Direction3)

"""
    vector(d::Direction3)

Returns a vector that has the same direction as `d`.
"""
vector(d::Direction3)

"""
    transform(d::Direction3, t::AffTransformation2)

Returns the direction obtained by applying `t` on `d`.
"""
transform(d::Direction3, t::AffTransformation2)


text:
# GenBank Reader

struct Reader{S <: TranscodingStream} <: BioGenerics.IO.AbstractReader
    io::S
end

"""
    GenBank.Reader(input::IO)

Create a data reader of the GenBank file format.
"""
function Reader(input::IO)
    if input isa TranscodingStream
        Reader(input)
    else
        stream = TranscodingStreams.NoopStream(input)
        Reader(stream)
    end
end

function Base.eltype(::Type{<:Reader})
    return Record
end

function Base.close(reader::Reader)
    close(reader.io)
end

function Base.open(::Type{<:Reader}, input::AbstractString)
    if input[end-2:end] == ".gz"
        return Reader(GzipDecompressorStream(open(input)))
    else
        return Reader(TranscodingStreams.NoopStream(open(input)))
    end
end

function BioGenerics.IO.stream(reader::Reader)
    reader.io
end

function Base.iterate(reader::Reader, nextone::Record = Record())
    if BioGenerics.IO.tryread!(reader, nextone) === nothing
        return nothing
    end
    return nextone, Record()
end

function BioGenerics.IO.tryread!(reader::Reader, output)
    record = parsechromosome!(reader.io, output)
    return record
end

"""
Return the LOCUS entry of the header.
"""
function parseheader(header::String)
    lines = split(header, "\n")
    firstline = lines[findfirst(line -> occursin("LOCUS", line), lines)]
    locus = split(firstline, r" +")[2]
    return locus
end

"""
Parse lines encoding genomic position, returning the feature as a `Symbol`, and an instance of `Locus`.
"""
function parseposition(line::String)
    feature, posstring = split(strip(line), r" +")
    if occursin(r"(\.\.|\^)", posstring)
        position = UnitRange(parse.(Int, filter.(c->isnumeric(c), split(posstring, r"(\.\.|\^)(.*(\.\.|\^))?")))...)
        strand = occursin("complement", posstring) ? '-' : '+'
    else
        position = parse(Int, posstring)
        position = position:position
        strand = '.'
    end
    complete_left = !occursin('<', posstring)
    complete_right = !occursin('>', posstring)
    order = Vector{UnitRange{Int}}()
    join = occursin("join", posstring)
    if join || occursin("order", posstring)
        for m in eachmatch(r"\d+(\.\.|\^)\d+", posstring)
            r = Meta.parse.(split(m.match, r"(\.\.|\^)"))
            push!(order, r[1]:r[2])
        end
    end
    return Symbol(feature), Locus(position, strand, complete_left, complete_right, order, join)
end

"""
Parse footer (sequence) portion of a GenBank file, returning a `String`.
"""
function filterseq(io::IOBuffer)
    line = String(take!(io))
    String(replace(filter(x -> !(isspace(x) || isnumeric(x)), line), r"[^ATGCNatgcn]" => "n"))
end

"""
    parsechromosome(stream::IO)

Parse and return one chromosome entry, and the line number that it ends at.
"""
function parsechromosome!(stream::IO, record::Record{G}) where G <: AbstractGene
    eof(stream) && return nothing
    iobuffer = IOBuffer()
    isheader = true
    isfooter = false
    spanning = false
    position_spanning = false
    qualifier = String("")
    content = String("")
    header = ""

    feature = :source
    locus = Locus()

    linecount = 0
    while !eof(stream)
        line = readline(stream)
        linecount += 1

        if length(line) == 0
            continue
        end

        # Catch cases where there's no header
        if linecount == 1 && occursin(r"gene", line)
            isheader = false
        end

        ### HEADER
        if isheader && occursin(r"FEATURES", line)
            record.header = String(take!(iobuffer))
            isheader = false

        elseif isheader
            linecount == 1 ? print(iobuffer, line) : print(iobuffer, '\n', line)

        # Check if the footer has been reached
        elseif !isheader && !isfooter && (occursin(r"^BASE COUNT", line) || occursin(r"ORIGIN", line))
            # Stop parsing the file when the list of genes is over
            isfooter = true
            iobuffer = IOBuffer()

        ### BODY
        elseif !isheader && !isfooter
            if position_spanning && occursin(r"  /", line)
                position_spanning = false
                spanningline = filter(x -> x != ' ', String(take!(iobuffer)))
                try
                    feature, locus = parseposition(spanningline)
                catch
                    println(spanningline)
                    println(line)
                    @error "parseposition(spanningline) failed at line $linecount"
                end
            elseif position_spanning
                print(iobuffer, line)
            end
            if occursin(r"^ {5}\S", line)
                spanning = false
                try
                    feature, locus = parseposition(line)
                catch
                    println(line)
                    @error "parseposition(line) failed at line $linecount"
                end
                addgene!(record, feature, locus)
            elseif !spanning && occursin(r"^ +/", line)
                if occursin(r"=", line)
                    if occursin("=\"", line)
                        (qualifier, content) = match(r"^ +/([^=]+)=\"?([^\"]*)\"?$", line).captures
                        content = String(content)
                    else
                        (qualifier, content) = match(r"^ +/(\S+)=(\S+)$", line).captures
                        try
                            tmpcontent = Meta.parse(content)
                            tmpcontent isa Expr && throw(Meta.ParseError)
                            content = tmpcontent
                        catch
                            content = Symbol(content)
                        end
                    end

                    if occursin(r"\".*[^\"]$", line)
                        spanning = true
                    end

                    isempty(names(record.genedata)) ?
                        (record.genedata[!, Symbol(qualifier)] = Union{Missing, typeof(content)}[content]) :
                        pushproperty!(record.genes[end], Symbol(qualifier), content)

                else
                    # Qualifiers without a value assigned to them end up here
                    qualifier = split(line, '/')[2]
                    pushproperty!(record.genes[end], Symbol(qualifier), true)
                end
            elseif spanning
                try
                    content = match(r" {21}([^\"]*)\"?$", line)[1]
                catch
                    @warn "Couldn't read content (line $linecount)"
                end
                if line[end] == '"'
                    spanning = false
                end
                if eltype(record.genedata[!, Symbol(qualifier)]).b <: AbstractArray
                    record.genedata[!, Symbol(qualifier)][end][end] = oneline(Base.getproperty(record.genes[end], Symbol(qualifier))[end] * "\n" * content)
                else
                    Base.setproperty!(record.genes[end], Symbol(qualifier), oneline(Base.getproperty(record.genes[end], Symbol(qualifier)) * "\n" * content))
                end
            end

        ### FOOTER
        elseif isfooter
            if line == "//"
                break
            end
            if occursin(r"^ ", line)
                print(iobuffer, line)
            end
        end
    end
    record.name = parseheader(record.header)
    record.sequence = LongDNASeq(filterseq(iobuffer))
    return record
end


text:
# An implementation for an acyclic directed graph.

module ArbiterGraphs

export ArbiterGraph, nodes, roots, children, parents, ancestor_of, prune!
export Strategy, Orphan, Promote, Remove
export NodeSet, ImmutableNodeSet

import Base: push!, pop!, in, ==
import FunctionalCollections: PersistentSet
import DataStructures: Stack

typealias NodeSet Set{Symbol}
typealias ImmutableNodeSet PersistentSet{Symbol}
typealias AbstractNodeSet Union{NodeSet, ImmutableNodeSet}

immutable Node{T<:AbstractNodeSet}
    name::Symbol
    children::NodeSet
    parents::T
end

function Node(name::Symbol, children::NodeSet)
    Node(name, children, ImmutableNodeSet())
end

function ==(node1::Node, node2::Node)
    return node1.name === node2.name && node1.children == node2.children && node1.parents == node2.parents
end

@enum Strategy Orphan Promote Remove

immutable ArbiterGraph
    _nodes::Dict{Symbol,Node}
    _stubs::NodeSet
    _roots::NodeSet

    """
    An acyclic directed graph.
    """
    function ArbiterGraph()
        new(Dict{Symbol,Node}(), NodeSet(), NodeSet())
    end
end

"""
The set of nodes in the graph.
"""
nodes(graph::ArbiterGraph) = ImmutableNodeSet(keys(graph._nodes))

"""
The set of nodes in the graph which have no parents.
"""
roots(graph::ArbiterGraph) = ImmutableNodeSet(graph._roots)

"""
Get the set of children a node has.

name: The name of the node.

An exception will be raised if the node doesn't exist.
"""
children(graph::ArbiterGraph, name::Symbol) = ImmutableNodeSet(graph._nodes[name].children)

"""
Get the set of parents a node has.

name: The name of the node.

An exception will be raised if the node doesn't exist.
"""
parents(graph::ArbiterGraph, name::Symbol) = ImmutableNodeSet(graph._nodes[name].parents)

"""
Check whether a node has another node as an ancestor.

name: The name of the node being checked.
ancestor: The name of the (possible) ancestor node.
visited: (optional, None) If given, a set of nodes that have
    already been traversed. NOTE: The set will be updated with
    any new nodes that are visited.

NOTE: If node doesn't exist, the method will return False.
"""
function ancestor_of(graph::ArbiterGraph, name::Symbol, ancestor::Symbol, visited::NodeSet)
    if !haskey(graph._nodes, name)
        return false
    end

    node = graph._nodes[name]

    stack = Stack(Symbol)

    # Note: append! is not available for Stack
    for parent in node.parents
        push!(stack, parent)
    end

    while !isempty(stack)
        current = pop!(stack)

        if current === ancestor
            return true
        end

        if !(current in visited)
            push!(visited, current)

            if haskey(graph._nodes, current)
                node = graph._nodes[current]

                for parent in node.parents
                    push!(stack, parent)
                end
            end
        end
    end

    return false
end

function ancestor_of(graph::ArbiterGraph, name::Symbol, ancestor::Symbol)
    return ancestor_of(graph, name, ancestor, NodeSet())
end

"""
add a node to the graph.

Raises an exception if the node cannot be added (i.e., if a node
that name already exists, or if it would create a cycle.

NOTE: A node can be added before its parents are added.

name: The name of the node to add to the graph.
parents: (optional, ()) The name of the nodes parents.
"""
function push!(graph::ArbiterGraph, name::Symbol, parents::NodeSet)
    is_stub = false

    if haskey(graph._nodes, name)
        if name in graph._stubs
            node = Node(name, graph._nodes[name].children, parents)
            is_stub = true
        else
            error(name)
        end
    else
        node = Node(name, NodeSet(), parents)
    end

    # cycle detection
    visited = NodeSet()

    for parent in parents
        if ancestor_of(graph, parent, name, visited)
            error(parent)
        elseif parent === name
            error(parent)
        end
    end

    # Node safe to add
    if is_stub
        pop!(graph._stubs, name)
    end

    if !isempty(parents)
        for parent_name in parents
            if haskey(graph._nodes, parent_name)
                parent_node = graph._nodes[parent_name]
                push!(parent_node.children, name)
            else
                graph._nodes[parent_name] = Node(parent_name, NodeSet((name,)))
                push!(graph._stubs, parent_name)
            end
        end
    else
        push!(graph._roots, name)
    end

    graph._nodes[name] = node

    return nothing
end

function push!(graph::ArbiterGraph, name, parents=())
    return push!(graph, symbol(name), NodeSet(parents))
end

"""
Remove a node from the graph. Returns the set of nodes that were
removed.

If the node doesn't exist, an exception will be raised.

name: The name of the node to remove.
strategy: (Optional, Strategy.promote) What to do with children
    or removed nodes. The options are:

    orphan: remove the node from the child's set of parents.
    promote: replace the node with the the node's parents in the
        childs set of parents.
    remove: recursively remove all children of the node.
"""
function pop!(graph::ArbiterGraph, name::Symbol; strategy::Strategy=Promote)
    removed = NodeSet()

    stack = Stack(Symbol)
    push!(stack, name)

    while !isempty(stack)
        current = pop!(stack)
        node = pop!(graph._nodes, current)

        if strategy == Remove
            for child_name in node.children
                child_node = graph._nodes[child_name]

                pop!(child_node.parents, current)

                push!(stack, child_name)
            end
        else
            for child_name in node.children
                child_node = graph._nodes[child_name]

                pop!(child_node.parents, current)

                if strategy == Promote
                    for parent_name in node.parents
                        parent_node = graph._nodes[parent_name]

                        push!(parent_node.children, child_name)
                        push!(child_node.parents, parent_name)
                    end
                end

                if isempty(child_node.parents)
                    push!(graph._roots, child_name)
                end
            end
        end

        if current in graph._stubs
            pop!(graph._stubs, current)
        elseif current in graph._roots
            pop!(graph._roots, current)
        else  # stubs and roots (by definition) don't have parents
            for parent_name in node.parents
                parent_node = graph._nodes[parent_name]

                pop!(parent_node.children, current)

                if parent_name in graph._stubs && isempty(parent_node.children)
                    push!(stack, parent_name)
                end
            end
        end

        push!(removed, current)
    end

    return removed
end

"""
Remove any tasks that have stubs as ancestors (and the stubs
themselves).

Returns the set of nodes which were removed.
"""
function prune!(graph::ArbiterGraph)
    pruned = NodeSet()
    stubs = ImmutableNodeSet(graph._stubs)

    for stub in stubs
        union!(pruned, pop!(graph, stub; strategy=Remove))
    end

    return setdiff!(pruned, stubs)
end

"""
Check whether a node is in the graph
"""
function in(node::Symbol, graph::ArbiterGraph)
    return haskey(graph._nodes, node)
end

"""
Equality checking
"""
function ==(graph1::ArbiterGraph, graph2::ArbiterGraph)
    return graph1._nodes == graph2._nodes && graph1._stubs == graph2._stubs
end

end


text:
mutable struct ListAccelerator{T,O} <: Accelerator{T,O}
    scene::Scene{O}
end
ListAccelerator(scene::Scene{O}) where {T,O<:Intersectable{T}} =
    ListAccelerator{T,O}(scene)

Base.intersect(ray::Ray{T}, acc::ListAccelerator{T}) where T =
    any(o -> intersect(ray,o), acc.scene.objs)

Base.intersect!(intersection::Intersection{T}, acc::ListAccelerator{T}) where T =
    foreach(o -> intersect!(intersection, o), acc.scene.objs)

export ListAccelerator


text:
# This file was generated, do not modify it. # hide
forest = EnsembleModel(model=DecisionTreeRegressor())
forest.model.n_subfeatures = 3

max_issues_repo_name: alan-turing-institute/MLJTutorials

max_forks_repo_name: alan-turing-institute/MLJTutorials


text:
using MiniOPF
using Base.Test

include("core/elements.jl")
include("core/opf.jl")