Pz1c / WavingHands

Android client for web base Waving Hands (spell caster) game on games.ravenblack.net
6 stars 0 forks source link

Improve the order of new turn story #279

Closed Prulon closed 1 year ago

Prulon commented 1 year ago

Hey @Pz1c

I used chatGPT and wrote the following code to re-order the turn order history in a much more meaningful way for the new animation you worked on.

You can try it out on the following website, what you need to do is copy paste a list of turn history from a game that finished and click submit. (the code is at the end of this post)

For example copy paste the following: Turn 7 Prulon proffers the palm of his left hand. Prulon snaps the fingers of his right hand. Prulon casts Shield at Fat Goblin. Ashai proffers the palm of his left hand. Ashai points the digit of his right hand. Ashai casts Counter Spell at himself. Ashai casts Magic Missile at Fat Goblin. Ashai is covered by a magical glowing shield. Fat Goblin is covered by a shimmering shield. A magic missile bounces off Fat Goblin's shield. Prulon directs Fat Goblin to attack Ashai. Fat Goblin attacks Ashai, but is deflected by a shield. Turn 8 Prulon proffers the palm of his left hand. Prulon wiggles the fingers of his right hand. Prulon casts Counter Spell at himself. Prulon casts Maladroitness at Ashai. Ashai proffers the palm of his left hand. Ashai wiggles the fingers of his right hand. Ashai casts Shield at himself. Ashai casts Charm Person at Prulon. Prulon is covered by a magical glowing shield. Ashai starts to lose coordination. Prulon's shield blurs for a moment. Ashai is covered by a shimmering shield. Prulon directs Fat Goblin to attack Ashai. Fat Goblin attacks Ashai, but is deflected by a shield.

Code here:

The function customSortLines(inputString) below will take the orders of a single turn and sort them in a more logical way

const spellOrder = [ "Dispel Magic", "Counter Spell", "Magic Mirror", "Summon Goblin", "Summon Ogre", "Summon Troll", "Summon Giant", "Summon Fire Elemental", "Summon Ice Elemental", "Haste", "Time Stop", "Protection", "Resist Heat", "Resist Cold", "Paralysis", "Amnesia", "Fear", "Confusion", "Maladroit", "Charm Monster", "Charm Person", "Disease", "Poison", "Cure Light Wounds", "Cure Heavy Wounds", "Anti-spell", "Blindness", "Invisibility", "Permanency", "Delay Effect", "Remove Enchantment", "Shield", "Magic Missile", "Cause Light Wounds", "Cause Heavy Wounds", "Clap of Lightning", "Lightning Bolt", "Fireball", "Finger of Death", "Fire Storm", "Ice Storm" ];

const specialSpellWords = [ "Coordination", "Wounds", "Goblin is summoned", "Ogre is summoned", "Troll is summoned", "Giant is summoned", "a magical glowing shield", "a thick shimmering shield", "a shimmering shield", "reflective shield", "intrigued", "healed", "bolt of lightning", "erased!", "magic missile bounces", "blurs", "shimmer", "scared", "speeds up!", "look blank", "stiffen", "baser instincts", "fizzle", "glassy-eyed" ];

const specialSpellNames = [ "Maladroit", "Cause Heavy Wounds", "Summon Goblin", "Summon Ogre", "Summon Troll", "Summon Giant", "Counter Spell", "Protection", "Shield", "Magic Mirror", "Charm Person", "Cure Heavy Wounds", "Lightning Bolt", "Dispel Magic", "Magic Missile", "Counter Spell", "Invisibility", "Fear", "Haste", "Amnesia", "Paralysis", "Charm Monster", "Anti-spell", "Charm Monster" ];

const introWords = [ "Says", "charmed into", "rendered maladroit!", "quakes in fear!", "sneak in", "same gestures", "paralysed" ];

const endWords = [ "attacks", "dies" ];

const wordsToRemove = [ "directs", "wiggles", "proffers", "stabs", "waves", "points", "snaps", "claps", "no gesture", "bows" ];

function customSortLines(inputString) {

//console.log(inputString);

let lines = breakIntoLines(inputString);

//console.log("split");
//console.log(lines.join('\n'));

// Remove sentences containing specific words
lines = lines.filter(sentence => {
    let lowercaseSentence = sentence.toLowerCase();
    for (let word of wordsToRemove) {
        if (lowercaseSentence.includes(word)) {
            return false;
        }
    }
    return true;
});

console.log("filtered " + lines.length);
console.log(lines.join('\n'));
//for (let i = 0; i <= lines.length - 1; i++) {
//    console.log(i + " " + lines[i] + '\n');
//
//}
if (lines.length == 0) {
    return '';
}

// Custom sort algorithm
for (let i = 0; i < lines.length - 1; i++) {
    for (let j = 0; j < lines.length - i - 1; j++) {
        console.log("i=" + i + " " + "j=" + j);

        let sentenceA = lines[j].toLowerCase();
        let sentenceB = lines[j + 1].toLowerCase();

        let spellA = getFirstSpell(sentenceA);
        let spellB = getFirstSpell(sentenceB);

        let indexA = spellOrder.indexOf(spellA);
        let indexB = spellOrder.indexOf(spellB);
        console.log(sentenceA);
        console.log(sentenceB);

        //console.log("i=" + i + " " + "j=" + j);
        console.log(spellA);
        console.log(spellB);
        console.log(indexA);
        console.log(indexB);

        let isIntroA = isIntroPart(sentenceA);
        let isEndB = isEndPart(sentenceB);

        let isSameSpell = (indexA === indexB && spellA !== '');
        let isHigherTarget = false;
        let isHigherSpell = (indexA > indexB);
        let isBothSpells = (indexA >=0) && (indexB >=0);

        if (isSameSpell) {
            let targetA = getTarget(sentenceA);
            let targetB = getTarget(sentenceB);

            let castA = isCastPart(sentenceA);
            let castB = isCastPart(sentenceB);

            //if first spell is actual "cast", don't flip
            if (!castA) {
                //switch is second spell is actual cast, or target higher in ABC
                isHigherTarget = castB || (targetA.localeCompare(targetB) > 0);
            }
        }

        if (!isIntroA && !isEndB && isBothSpells && (isHigherSpell || (isHigherTarget))) {
            switchLines(lines, j);
            console.log("replace " + isIntroA + " " + isEndB + " " + isBothSpells + " " + isHigherSpell + " " + isHigherTarget + " " + spellA + " " + spellB);
            //console.log(lines[j]);
            //console.log(lines[j + 1]);
        } else {
            console.log("keep " + isIntroA + " " +  isEndB + " " + isBothSpells+" " + isHigherSpell + " " + isHigherTarget + " " + spellA + " " + spellB);

        }

    }

}
let sortedString = lines.join('\n');

return sortedString;

}

function breakIntoLines(inputString) { let lines = []; //this will be the output let splitLines = inputString.split('\n');

let currentLine = '';
let isInQuote = false;

for (let i = 0; i < splitLines.length; i++) {
    let line = splitLines[i].trim();

    currentLine += line;

    //console.log(currentLine);

    //check if there's a quote mark
    let quoteCount = line.split('"').length - 1;
    let hasQuote = (quoteCount % 2 !== 0);

    if (hasQuote) {
        isInQuote = !isInQuote;
    }

    if (!isInQuote) {
        if (currentLine.trim() != '') {
            //console.log("add: " + currentLine);

            lines.push(currentLine);
        }
        currentLine = '';

    }
}

if (currentLine.trim() !== '') {
    //console.log("add last: " + currentLine);

    lines.push(currentLine);
}

return lines;

}

function switchLines(lines, j) { let temp = lines[j]; lines[j] = lines[j + 1]; lines[j + 1] = temp;

}

function isIntroPart(sentenceA) {

for (let i = 0; i < introWords.length; i++) {

    if (sentenceA.includes(introWords[i].toLowerCase())) {

        return true;
    }

}

return false

}

function isEndPart(sentenceB) {

for (let i = 0; i < endWords.length; i++) {

    if (sentenceB.includes(endWords[i].toLowerCase())) {

        return true;
    }

}

return false

}

function isCastPart(sentenceA) {

//Keep order if includes "Cast"
if (sentenceA.includes("casts")) {
    return true;
}
return false;

}

function getFirstSpell(sentence) { if (sentence.includes("casts")) {

    for (let i = 0; i < spellOrder.length; i++) {
        if (sentence.includes(spellOrder[i].toLowerCase())) {
            return spellOrder[i];
        }

    }
}

for (let i = 0; i < specialSpellWords.length; i++) {

    if (sentence.includes(specialSpellWords[i].toLowerCase())) {

        return specialSpellNames[i];
    }

}

for (let i = 0; i < spellOrder.length; i++) {
    if (sentence.includes(spellOrder[i].toLowerCase())) {
        return spellOrder[i];
    }

}
return '';

}

function getTarget(sentence) { let spell = getFirstSpell(sentence); if (spell !== '') { let targetIndex = sentence.indexOf("at") + 3; let target = sentence.substring(targetIndex).trim(); let targetWords = target.split(" ", 3); return targetWords.join(" "); } return ''; }

Prulon commented 1 year ago

Note - There may be more cases which I did not cover, whenever you find a new case for example: "Prulon begins to shimmer." --> means Invisibility So I added the word "shimmer" to const specialSpellWords[], and added the word "Invisibility" to const specialSpellNames[]

Prulon commented 1 year ago

How I think you can use all this: Before starting your own manipulations on the text, use this code to sort the text, and then run your code.

Prulon commented 1 year ago

@Pz1c

I asked ChatGPT to translate the above code to C++, I don't know if it works, but it can give you a quick start I hope :)

Here's the code:

include

include

include

std::vector spellOrder = { "Dispel Magic", "Counter Spell", "Magic Mirror", "Summon Goblin", "Summon Ogre", "Summon Troll", "Summon Giant", "Summon Fire Elemental", "Summon Ice Elemental", "Haste", "Time Stop", "Protection", "Resist Heat", "Resist Cold", "Paralysis", "Amnesia", "Fear", "Confusion", "Maladroit", "Charm Monster", "Charm Person", "Disease", "Poison", "Cure Light Wounds", "Cure Heavy Wounds", "Anti-spell", "Blindness", "Invisibility", "Permanency", "Delay Effect", "Remove Enchantment", "Shield", "Magic Missile", "Cause Light Wounds", "Cause Heavy Wounds", "Clap of Lightning", "Lightning Bolt", "Fireball", "Finger of Death", "Fire Storm", "Ice Storm" };

std::vector specialSpellWords = { "Coordination", "Wounds", "Goblin is summoned", "Ogre is summoned", "Troll is summoned", "Giant is summoned", "a magical glowing shield", "a thick shimmering shield", "a shimmering shield", "reflective shield", "intrigued", "healed", "bolt of lightning", "erased!", "magic missile bounces", "blurs", "shimmer", "scared", "speeds up!", "look blank", "stiffen", "baser instincts", "fizzle", "glassy-eyed" };

std::vector specialSpellNames = { "Maladroit", "Cause Heavy Wounds", "Summon Goblin", "Summon Ogre", "Summon Troll", "Summon Giant", "Counter Spell", "Protection", "Shield", "Magic Mirror", "Charm Person", "Cure Heavy Wounds", "Lightning Bolt", "Dispel Magic", "Magic Missile", "Counter Spell", "Invisibility", "Fear", "Haste", "Amnesia", "Paralysis", "Charm Monster", "Anti-spell", "Charm Monster" };

std::vector introWords = { "Says", "charmed into", "rendered maladroit!", "quakes in fear!", "sneak in", "same gestures", "paralysed" };

std::vector endWords = { "attacks", "dies" };

std::vector wordsToRemove = { "directs", "wiggles", "proffers", "stabs", "waves", "points", "snaps", "claps", "no gesture", "bows" };

std::vector breakIntoLines(const std::string& inputString) { std::vector lines; std::vector splitLines;

size_t pos = 0;
while (pos < inputString.length()) {
    size_t nextPos = inputString.find('\n', pos);
    if (nextPos == std::string::npos)
        nextPos = inputString.length();

    splitLines.push_back(inputString.substr(pos, nextPos - pos));
    pos = nextPos + 1;
}

std::string currentLine;
bool isInQuote = false;

for (const std::string& line : splitLines) {
    std::string trimmedLine = line;
    trimmedLine.erase(0, trimmedLine.find_first_not_of(" \t"));
    trimmedLine.erase(trimmedLine.find_last_not_of(" \t") + 1);

    currentLine += trimmedLine;

    size_t quoteCount = std::count(line.begin(), line.end(), '"');
    bool hasQuote = (quoteCount % 2 != 0);

    if (hasQuote)
        isInQuote = !isInQuote;

    if (!isInQuote) {
        if (!currentLine.empty())
            lines.push_back(currentLine);
        currentLine.clear();
    }
}

if (!currentLine.empty())
    lines.push_back(currentLine);

return lines;

}

void switchLines(std::vector& lines, size_t j) { std::swap(lines[j], lines[j + 1]); }

bool isIntroPart(const std::string& sentenceA) { for (const std::string& intro : introWords) { if (sentenceA.find(intro) != std::string::npos) return true; } return false; }

bool isEndPart(const std::string& sentenceB) { for (const std::string& end : endWords) { if (sentenceB.find(end) != std::string::npos) return true; } return false; }

bool isCastPart(const std::string& sentenceA) { return (sentenceA.find("casts") != std::string::npos); }

std::string getFirstSpell(const std::string& sentence) { if (sentence.find("casts") != std::string::npos) { for (const std::string& spell : spellOrder) { if (sentence.find(spell) != std::string::npos) return spell; } }

for (size_t i = 0; i < specialSpellWords.size(); i++) {
    if (sentence.find(specialSpellWords[i]) != std::string::npos)
        return specialSpellNames[i];
}

for (const std::string& spell : spellOrder) {
    if (sentence.find(spell) != std::string::npos)
        return spell;
}

return "";

}

std::string getTarget(const std::string& sentence) { std::string spell = getFirstSpell(sentence); if (!spell.empty()) { size_t targetIndex = sentence.find("at") + 3; std::string target = sentence.substr(targetIndex); std::string targetWords[3]; size_t targetWordCount = 0;

    for (char c : target) {
        if (c == ' ') {
            if (targetWordCount == 2)
                break;
            targetWordCount++;
        }
        else {
            targetWords[targetWordCount] += c;
        }
    }

    return targetWords[0] + " " + targetWords[1] + " " + targetWords[2];
}

return "";

}

std::string customSortLines(const std::string& inputString) { std::vector lines = breakIntoLines(inputString);

lines.erase(std::remove_if(lines.begin(), lines.end(), [](const std::string& sentence) {
    std::string lowercaseSentence = sentence;
    std::transform(lowercaseSentence.begin(), lowercaseSentence.end(), lowercaseSentence.begin(), ::tolower);
    for (const std::string& word : wordsToRemove) {
        if (lowercaseSentence.find(word) != std::string::npos)
            return true;
    }
    return false;
}), lines.end());

if (lines.empty())
    return "";

for (size_t i = 0; i < lines.size() - 1; i++) {
    for (size_t j = 0; j < lines.size() - i - 1; j++) {
        std::string sentenceA = lines[j];
        std::string sentenceB = lines[j + 1];
        std::string spellA = getFirstSpell(sentenceA);
        std::string spellB = getFirstSpell(sentenceB);
        size_t indexA = std::find(spellOrder.begin(), spellOrder.end(), spellA) - spellOrder.begin();
        size_t indexB = std::find(spellOrder.begin(), spellOrder.end(), spellB) - spellOrder.begin();
        bool isIntroA = isIntroPart(sentenceA);
        bool isEndB = isEndPart(sentenceB);
        bool isSameSpell = (indexA == indexB && !spellA.empty());
        bool isHigherTarget = false;
        bool isHigherSpell = (indexA > indexB);
        bool isBothSpells = (indexA < spellOrder.size() && indexB < spellOrder.size());

        if (isSameSpell) {
            std::string targetA = getTarget(sentenceA);
            std::string targetB = getTarget(sentenceB);
            bool castA = isCastPart(sentenceA);
            bool castB = isCastPart(sentenceB);

            if (!castA)
                isHigherTarget = castB || (targetA.compare(targetB) > 0);
        }

        if (!isIntroA && !isEndB && isBothSpells && (isHigherSpell || isHigherTarget))
            switchLines(lines, j);
    }
}

std::string sortedString;
for (const std::string& line : lines) {
    sortedString += line + '\n';
}

return sortedString;

}

Prulon commented 1 year ago

customSortLines() is the place to start - it gets the turn history, and then uses bubble sort to re-order the items.

The logic is this: Start by removing unuseful lines like the Gestures.

Then, If the line is an "intro line" like "Prulon says" --> keep its location. Same also for end lines. If the line is a "cast spell" or an effect of a spell, then reorder based on the order of spells in RB. If 2 lines belong to the same spell then the line that has the "Casts" word starts, like "Prulon casts magic missile" comes before the damage of the magic missile. If 2 lines belong to the same spell but different targets then sort the targets by ABC

Pz1c commented 1 year ago

move to 2.1.3 because want finish 2.1.2 today

Pz1c commented 1 year ago

should be fixed in 2.2.0