# Solver

Solver try to remember what cards are played before and what was the previous moves of the opponent. 

this is the 2nd version not finished yet (and it is a big mess now)

to make it work on node 10 add `competitors.append(Player("Solver", ["node", "--experimental-modules", "./solver.mjs"]))`

if node 12 `competitors.append(Player("Solver", ["node", "./solver.js"]))`

be carefuller with the file type

```lang-js
import  fs from 'fs'
import { promisify } from 'util'

const appendFile = promisify(fs.appendFile)
const writeFile = promisify(fs.writeFile)
const readFile = promisify(fs.readFile)

const delay = ms => new Promise(_ => setTimeout(_, ms));

let [filename, othercoins, mycoins, mycards ] = process.argv.slice(2)
othercoins = +othercoins
mycoins = +mycoins

const move = async m => await appendFile(filename, m)
const message = m => process.stdout.write(m)
const endTurn = async _ => await move(`\n`) 

const stateFileName = `./state.json`

const defaultState = {
    inTheDeck: [],
    oponentCards: [],
    oponentMissingTempCard: "",
    oponentMissingCards: [],
    oponentDropedCards: [],
    oponentCardModifier: 0
}

const CardTypes = Object.freeze({
    Ambassador : `Ambassador`,
    Assassin : `Assassin`,
    Captain  : `Captain`,
    Contessa : `Contessa`,
    Duke     : `Duke`,
})

const revealTable = Object.freeze({
    [CardTypes.Ambassador]: `~`,
    [CardTypes.Assassin]: `^`,
    [CardTypes.Captain]: `*`,
    [CardTypes.Contessa]: `!`,
    [CardTypes.Duke]: `$`,
})

const giveUpTable = Object.freeze({
    [CardTypes.Ambassador]: `_`,
    [CardTypes.Assassin]: `'`,
    [CardTypes.Captain]: `<`,
    [CardTypes.Contessa]: `=`,
    [CardTypes.Duke]: `0`,
})

function GetRevealCardChar(cardType) {
    return revealTable[cardType]
}

function GetRevealCardType(char) {
    return Object.keys(revealTable).find(key => revealTable[key] === char)
}

function GetGiveUpCardChar(cardType) {
    return giveUpTable[cardType]
}

function GetGiveUpCardType(char) {
    return Object.keys(giveUpTable).find(key => giveUpTable[key] === char)
}

async function GiveUpCard(cardType, endTurn = false) {
    return await move(
        GetGiveUpCardChar(cardType) + `${endTurn?`\n`:``}`
    );
}

function OponentCanHave(cardType) {
    // it has it
    if (!!~state.oponentCards.indexOf(cardType)) return true
    
    if (state.oponentCards.length + state.oponentDropedCards.length >= 2) return false

    return true
}


function GiveCard(getOrder = false) {
    // TODO: Tactic
    const giveAwayOrder = [
        CardTypes.Captain,
        CardTypes.Ambassador,
        CardTypes.Contessa,
        CardTypes.Assassin,
        CardTypes.Duke,
    ]

    const tmp =  mycards
        .split('')
        .map(GetRevealCardType)
    
    let [unique, duplicate] = tmp.reduce(([unique, duplicate], item) => {
        return unique.includes(item) ? 
            [unique, [...duplicate, item]] :
            [[...unique, item], duplicate]
    }
    , [[],[]])

    unique.sort(
        (a, b) => giveAwayOrder.indexOf(a) - giveAwayOrder.indexOf(b));
    duplicate.sort(
        (a, b) => giveAwayOrder.indexOf(a) - giveAwayOrder.indexOf(b))

    const out = [...duplicate, ...unique]

    return getOrder? out: GetGiveUpCardChar(out[0]);
}

const iHaveAmbassador = !!~mycards.indexOf(`~`)
const iHaveAssassin = !!~mycards.indexOf(`^`)
const iHaveCaptain = !!~mycards.indexOf(`*`)
const iHaveContessa = !!~mycards.indexOf(`!`)
const iHaveDuke = !!~mycards.indexOf(`$`)

let state = defaultState

;(async ()=>{
    const data = (await readFile(filename, `utf8`)).replace(/\r/g, ``)
    
    const isNewGame = data === "" && mycoins === 1 && othercoins === 1

    if (isNewGame) {
        await writeFile(stateFileName, JSON.stringify(state))
    } else {
        state = JSON.parse(await readFile(stateFileName, `utf8`))
    }
    //console.error(state);
    let line = data.split(/\n/g).pop()
    
    // I am in the exchnage
    if (mycards.length >= 3) {
        const [c1, c2] = GiveCard(true).reverse()
        
        if (mycards.length === 3) {
            state.inTheDeck.push(c1)
            message(GetRevealCardChar(c1))
        }
        if (mycards.length === 4) {
            state.inTheDeck.push(c1, c2)
            message(`${GetRevealCardChar(c1)}${GetRevealCardChar(c2)}`)
        }
        return await move(`\n`)
    }

    const newTurn = line === ``

    if (newTurn) {
        if (mycoins >= 7) {
            return await move(`C`)
        }

        if (othercoins >= 6) {
            if (iHaveCaptain) 
                return await move(`S`)

            
            if (mycoins <= 6 && mycards.length <= 1) {
                // TODO: bluff 
            }
        }

        if (
            !iHaveDuke &&
            !iHaveContessa &&
            iHaveAmbassador
        ) {
            return await move(`E`)
        }

        // Assasinate if oponent has no Contessa
        if (
            mycoins >= 3 &&
            iHaveAssassin &&
            !~state.oponentCards.indexOf(CardTypes.Contessa)
        ) {
            return await move(`A`)
        }

        if (iHaveDuke) {
            
            return await move(`T`)
        }
        
        return await move(`I\n`)
    }

    // Exchange
    if (line === `Eq`) {
        if (iHaveAmbassador)
            return await move(GetRevealCardChar(CardTypes.Ambassador))

        return await GiveUpCard(GiveCard(true)[0])
    }

    // Tax Challenge
    if(line === `Tq`) {
        if (iHaveDuke)
            return await move(GetRevealCardChar(CardTypes.Duke))

        return await GiveUpCard(GiveCard(true)[0])
    }


    if (line === `Sa`) {
        if (!~state.oponentCards.indexOf(CardTypes.Ambassador)) {
            state.oponentMissingTempCard = CardTypes.Ambassador
            return await move(`q`)
        }
        return await endTurn()
    }

    if (line === `Sc`) {
        if (!~state.oponentCards.indexOf(CardTypes.Captain)) {
            state.oponentMissingTempCard = CardTypes.Captain
            return await move(`q`)
        }
        return await endTurn()
    }

    if (line=== `Saq${GetRevealCardChar(CardTypes.Ambassador)}`) {
        state.oponentMissingTempCard = ``
        state.oponentCards.push(
            CardTypes.Ambassador
        );
        return await GiveUpCard(GiveCard(true)[0], true)
    }

    if (line=== `Scq${GetRevealCardChar(CardTypes.Captain)}`) {
        state.oponentMissingTempCard = ``
        state.oponentCards.push(
            CardTypes.Captain
        );
        return await GiveUpCard(GiveCard(true)[0], true)
    }

    if (line === `Sq`) {
        if (iHaveCaptain)
            return await move(GetRevealCardChar(CardTypes.Captain))
    
        return await GiveUpCard(GiveCard(true)[0])
    }

    // Assassinate Block and Chalange it
    if (line === `As`) {
        state.oponentMissingTempCard = CardTypes.Contessa
        return await move(`q`)
    }

    // Assasint blocked by Contessa
    if (line === `Asq${GetRevealCardChar(CardTypes.Contessa)}`) {
        state.oponentMissingTempCard = ``
        state.oponentCards.push(
            CardTypes.Contessa
        )
        // Assassin useless here lets give it up
        return await GiveUpCard(CardTypes.Assassin, true)
    }

    // Assassinate challenge
    if (line === `Aq`) {
        if (iHaveAssassin)
            return await move(GetRevealCardChar(CardTypes.Assassin))
        
        return await GiveUpCard(GiveCard(true)[0])
    }


    // Defense Moves
    if (line === `C`) {
        return await GiveUpCard(GiveCard(true)[0])
    }


    if (line === `A`) {
        if (iHaveContessa)
            return await move(`s`)

        
        if (!!~state.oponentCards.indexOf(CardTypes.Assassin)) {
            // If oponent has an Assasin card lets bluff
            return await move(`s`)
        } else {
            state.oponentMissingTempCard = CardTypes.Assassin
            return await move(`q`)
        }
        
    }

    if (line === `Aq${GetRevealCardChar(CardTypes.Assassin)}`) {
        state.oponentMissingTempCard = ``
        state.oponentCards.push(
            CardTypes.Assassin
        );
        return await GiveUpCard(GiveCard(true)[0], true)
    }

    if (line === `Asq`) {
        if (iHaveContessa)
            return await move(GetRevealCardChar(CardTypes.Contessa))

        return await GiveUpCard(GiveCard(true)[0])
    }

    if (line === `S`) {
        if (iHaveAmbassador)
            return await move(`a`)

        if (iHaveCaptain)
            return await move(`c`)

        return await move(`p`)
    }

    if (line === `Saq`) {
        if (iHaveAmbassador)
            return await move(GetRevealCardChar(CardTypes.Ambassador))

        return await GiveUpCard(GiveCard(true)[0])
    }

    if (line === `Scq`) {
        if (iHaveCaptain)
            return await move(GetRevealCardChar(CardTypes.Captain))

        return await GiveUpCard(GiveCard(true)[0])
    }

    if (line === `F`) {
        if (iHaveDuke)
            return await move(`d`)
        
        return await move(`p`)
    }

    if (line === `Fdq`) {
        if (iHaveDuke)
            return await move(GetRevealCardChar(CardTypes.Duke))

        return await GiveUpCard(GiveCard(true)[0])
    }
    
    if (line === `E`) {
        if (!OponentCanHave(CardTypes.Ambassador)) {
            return await move(`q`)
        }

        state.oponentCards = []
        state.oponentMissingCards = []
        state.oponentMissingTempCard = ''

        return await move(`p`)
    }


    if (line === `Eq${GetRevealCardChar(CardTypes.Ambassador)}`) {
        console.error(111, `THIS SHOULD NEVER HAPPEN`)
        return await GiveUpCard(GiveCard(true)[0])
    }

    if (line === `T`) {
        if (!OponentCanHave(CardTypes.Duke)) {
            return await move(`q`)
        }
        return await move(`p`)
    }
    
    if (line === `Tq${GetRevealCardChar(CardTypes.Duke)}`) {
        console.error(111, `THIS SHOULD NEVER HAPPEN`)
        return await GiveUpCard(GiveCard(true)[0])
    }

    
    // remove oponents drop card from the state
    // can't detect if oponent has the same card twice
    if (!!~Object.values(giveUpTable).indexOf(line.substr(-1))) {
        // Catch the bluff
        if (state.oponentMissingTempCard !== "") {
            state.oponentMissingCards.push(state.oponentMissingTempCard)
            state.oponentMissingTempCard = ""
        }

        // maybe we should asume user doeas not has the same card?

        const cardType = GetGiveUpCardType(line.substr(-1))
        state.oponentCards.filter(c => c !== cardType)
        state.inTheDeck.push(cardType)
        state.oponentDropedCards.push(cardType)
    }

    return await endTurn()

})()
.then(async () => {
    await writeFile(stateFileName, JSON.stringify(state))
})
.catch(console.error.bind(console))


```