中文 ptcg 牌組編輯強化

強化繁體中文 ptcg 官網牌組編輯功能

// ==UserScript==
// @name         中文 ptcg 牌組編輯強化
// @namespace    https://github.com/douduck08/enhance-tw-ptcg-builder
// @version      1.2.0
// @description  強化繁體中文 ptcg 官網牌組編輯功能
// @author       douduck08 (https://www.douduck08.com/)
// @license      MIT
// @match        https://asia.pokemon-card.com/*/deck-build/
// @match        https://asia.pokemon-card.com/*/deck-build/?*
// @require      https://code.jquery.com/jquery-3.4.1.slim.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js
// ==/UserScript==

const styles = `
.toolkit-flexWrapper {
    display: flex;
    justify-content: center;
}

#toolkit {
    box-sizing: border-box;
    color: #222222;
    font-size: 16px;
    font-weight: bold;
    background-color: #efefef;
    align-items: center;
    padding: 8px;
    margin-bottom: 8px;
}
#toolkit > div > span {
    margin-right: 8px;
}
#toolkit > div.searchFromToolkit {
    width: 260px;
    margin-right: 20px;
    padding: 0 16px;
}
#toolkit > div.searchResultToolkit {
    margin-right: 8px;
    padding: 0 16px;
}
#toolkit > div.searchResultToolkit > button {
    width: 70px;
    height: 40px;
}
#toolkit > div.deckToolkit {
    width: 462px;
    padding: 0 16px;
}
#toolkit > div.deckToolkit > button {
    width: 110px;
}

button.close-button {
    width: 40px;
    height: 40px;
    margin: 2px;
    position: absolute;
}
div.ex-cardZone {
    border: 1px dashed #707070;
    border-radius: 6px;
    background-color: #ffffff;
    display: flex;
    flex-direction: column;
    padding: 4px;
    margin: 2px;
}
div.ex-cardZone > h3 {
    font-size: 1rem;
    text-align: center;
    background-color: #efefef;
}
div.ex-cardZone > h3:hover {
    background-color: #adadad;
}
div.ex-cards {
    width: 100%;
    height: 100%;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
}
img.ex-card {
    width: 76px;
    height: auto;
    margin: 4px;
}

#testStartupZone {
    box-sizing: border-box;
    margin-bottom: 8px;
}
div.startup-prizeZone {
    width: 294px;
}
div.startup-handZone {
    width: 378px;
}

#testPlayZone {
    box-sizing: border-box;
    margin-bottom: 8px;
}
div.play-cardZone {
    width: 196px;
    height: 200px;
}
div.play-viewZone {
    width: 396px;
    height: 200px;
}
div.play-handZone {
    width: 596px;
    min-height: 266px;
}
div.play-discardZone {
    width: 396px;
    min-height: 266px;
}
div.play-prizeZone {
    width: 186px;
    height: 404px;
}
div.play-deckZone {
    width: 996px;
    height: 404px;
}
div.play-deckMenu {
    width: 186px;
    height: 404px;
}

button.play-menuButton1 {
    width: -webkit-fill-available;
    height: 32px;
    border: 1px solid #000000;
    border-radius: 10px;
    background-color: #000000;
    color: #ffffff;
    margin: 2px;
}
button.play-menuButton2 {
    width: -webkit-fill-available;
    height: 32px;
    border: 1px solid #adadad;
    border-radius: 10px;
    background-color: #efefef;
    margin: 2px;
}
button.play-menuButton3 {
    width: -webkit-fill-available;
    height: 32px;
    border: 1px solid #000000;
    border-radius: 10px;
    background-color: #ffffff;
    margin: 2px;
}

div.overlay {
    z-index: 100;
    display: block;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: rgba(0, 0, 0, 0.2);
}
div.play-zoneMenu {
    z-index: 200;
    position: fixed;
    width: 186px;
    height: auto;
    background-color: #4b4b4b;
}

div.play-cards {
    width: 100%;
    height: 100%;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-content: flex-start;
}
div.play-card > img {
    width: 76px;
    height: auto;
}
div.play-card {
    z-index: 10;
    margin: 4px;
}
div.deck-cards {
    justify-content: left;
    overflow-y: scroll;
}

div.prize-cards {
    justify-content: left;
}
div.prize-cards > div {
    border: 1px solid #dedede;
    background-color: #efefef;
    border-radius: 4px;
}
div.prize-cards > div > img {
    visibility: hidden;
}

div.stack-cards > div:nth-of-type(4n+1) {
    margin-left: 4px
}
div.stack-cards > div:nth-of-type(4n+2) {
    margin-left: -48px;
}
div.stack-cards > div:nth-of-type(4n+3) {
    margin-left: -48px;
}
div.stack-cards > div:nth-of-type(4n) {
    margin-left: -48px;
}
div.stack-cards > div:nth-of-type(n+4) ~ div {
    margin-top: -75px;
}

div.view-cards  > div:nth-of-type(n+4) ~ div {
    margin-top: -75px;
}

div.discard-cards > div:nth-of-type(10n+1) {
    margin-left: 4px
}
div.discard-cards > div:nth-of-type(10n+2) {
    margin-left: -48px;
}
div.discard-cards > div:nth-of-type(10n+3) {
    margin-left: -48px;
}
div.discard-cards > div:nth-of-type(10n+4) {
    margin-left: -48px;
}
div.discard-cards > div:nth-of-type(10n+5) {
    margin-left: -48px;
}
div.discard-cards > div:nth-of-type(10n+6) {
    margin-left: -48px;
}
div.discard-cards > div:nth-of-type(10n+7) {
    margin-left: -48px;
}
div.discard-cards > div:nth-of-type(10n+8) {
    margin-left: -48px;
}
div.discard-cards > div:nth-of-type(10n+9) {
    margin-left: -48px;
}
div.discard-cards > div:nth-of-type(10n) {
    margin-left: -48px;
}
div.discard-cards  > div:nth-of-type(n+10) ~ div {
    margin-top: -75px;
}
`;

const toolkitDom = `
<div id="toolkit" class="toolkit-flexWrapper">
    <div class="searchFromToolkit"></div>    
    <div id="searchResultToolkit" class="searchResultToolkit">
        <span>搜尋結果</span>
        <button type="button" id="reduceColumn" class="button secondary">縮減</button>
        <button type="button" id="addColumn" class="button secondary">加寬</button>
    </div>
    <div class="deckToolkit">
        <button type="button" id="testStartup" class="button">測試起手</button>
        <button type="button" id="testPlay" class="button">測試運作</button>
    </div>
</div>
<div id="testStartupZone" class="toolkit-flexWrapper">
    <div class="ex-cardZone startup-prizeZone">
        <h3>獎賞卡</h3>
        <div id="startupPrize" class="ex-cards"></div>
    </div>
    <div class="ex-cardZone startup-handZone">
        <h3>手牌</h3>
        <div id="startupHand" class="ex-cards"></div>
    </div>
    <div class="ex-cardZone">
        <h3>抽牌</h3>
        <div id="startupDraw" class="ex-cards"></div>
    </div>
    <div>
        <button type="button" id="testStartupClose" class="button secondary close-button">X</button>
    </div>
</div>
<div id="testPlayZone" class="toolkit-flexWrapper">
    <div>
        <div class="ex-cardZone play-prizeZone">
            <h3>獎賞卡</h3>
            <div id="play-prize" class="play-cards prize-cards"></div>
        </div>
    </div>
    <div>
        <div id="play-deckZone" class="ex-cardZone play-deckZone">
            <h3>牌庫</h3>
            <div id="play-deck" class="play-cards deck-cards"></div>
        </div>
        <div id="play-zoneRow1" class="toolkit-flexWrapper">
            <div class="ex-cardZone play-cardZone">
                <h3>放逐區</h3>
                <div id="play-lost" class="play-cards stack-cards"></div>
            </div>
            <div class="ex-cardZone play-cardZone">
                <h3>競技場</h3>
                <div id="play-stadium" class="play-cards stack-cards"></div>
            </div>
            <div class="ex-cardZone play-cardZone">
                <h3>戰鬥場</h3>
                <div id="play-active" class="play-cards stack-cards"></div>
            </div>
            <div class="ex-cardZone play-viewZone">
                <h3>查看</h3>
                <div id="play-view" class="play-cards view-cards"></div>
            </div>
        </div>
        <div id="play-zoneRow2" class="toolkit-flexWrapper">
            <div class="ex-cardZone play-cardZone">
                <h3>備戰區A</h3>
                <div id="play-bench-a" class="play-cards stack-cards"></div>
            </div>
            <div class="ex-cardZone play-cardZone">
                <h3>備戰區B</h3>
                <div id="play-bench-b" class="play-cards stack-cards"></div>
            </div>
            <div class="ex-cardZone play-cardZone">
                <h3>備戰區C</h3>
                <div id="play-bench-c" class="play-cards stack-cards"></div>
            </div>
            <div class="ex-cardZone play-cardZone">
                <h3>備戰區D</h3>
                <div id="play-bench-d" class="play-cards stack-cards"></div>
            </div>
            <div class="ex-cardZone play-cardZone">
                <h3>備戰區E</h3>
                <div id="play-bench-e" class="play-cards stack-cards"></div>
            </div>
        </div>
        <div class="toolkit-flexWrapper">
            <div class="ex-cardZone play-handZone">
                <h3>手牌</h3>
                <div id="play-hand" class="play-cards"></div>
            </div>
            <div class="ex-cardZone play-discardZone">
                <h3>棄牌區</h3>
                <div id="play-discard" class="play-cards discard-cards"></div>
            </div>
        </div>
    </div>
    <div>
        <div class="ex-cardZone play-deckMenu">
            <h3>牌庫</h3>
            <div id="deckMenu-1">
                <button type="button" id="deckMenu-open" class="play-menuButton2">查看牌庫</button>
                <button type="button" id="deckMenu-shuffle" class="play-menuButton2">重洗牌庫</button>
                <button type="button" id="deckMenu-viewOne" class="play-menuButton2">查看一張牌</button>
                <button type="button" id="deckMenu-drawOne" class="play-menuButton1">抽一張牌</button>
            </div>
            <div id="deckMenu-2">
                <button type="button" id="deckMenu-close" class="play-menuButton2">關閉並重洗</button>
                <button type="button" id="deckMenu-sort" class="play-menuButton2">排序</button>
            </div>
        </div>
    </div>
    <div>
        <button type="button" id="testPlayClose" class="button secondary close-button">X</button>
    </div>
    <div id="zoneMenuContainer">
        <div id="zoneMenu-overlay" class="overlay"></div>
        <div id="zoneMenu" class="ex-cardZone play-zoneMenu" data-id=''>
            <button type="button" id="zoneMenu-toBattle" class="play-menuButton3">移至對戰區</button>
            <button type="button" id="zoneMenu-viewPrize" class="play-menuButton3">查看獎賞卡</button>
            <button type="button" id="zoneMenu-toDiscard" class="play-menuButton3">放入棄牌區</button>
            <button type="button" id="zoneMenu-toHand" class="play-menuButton3">放入手牌</button>
            <button type="button" id="zoneMenu-toDeck" class="play-menuButton3">放入牌庫並重洗</button>
            <button type="button" id="zoneMenu-toDeckTop" class="play-menuButton3">重洗並放到牌庫上方</button>
            <button type="button" id="zoneMenu-toDeckBottom" class="play-menuButton3">重洗並放到牌庫下方</button>
            <button type="button" id="zoneMenu-toPrize" class="play-menuButton3">放入獎賞卡並重洗</button>
        </div>
    </div>
</div>
`;

let column = 4;

function updateSearchResultZone() {
    $('#searchResultToolkit').attr('style', 'width: ' + (118 * column + 26).toString() + 'px');
    $('#searchResultZone').attr('style', 'width: ' + (118 * column + 26).toString() + 'px');
    $('#searchResultZoneCardContainer').attr('style', 'width: ' + (118 * column).toString() + 'px');
}

function getDeckListData() {
    let cards = $('#decklistZoneCardContainer > .card');
    let deckListData = [];
    for (let i = 0; i < cards.length; i++) {
        deckListData[i] = {
            'cardId': cards[i].dataset.cardId,
            'cardName': cards[i].dataset.cardName,
            'cardImage': $('img', cards[i]).attr('src'),
            'count': $('div', cards[i]).html()
        };
    }
    return deckListData;
}

function createLibrary(deckListData) {
    let library = [];
    let index = 0;
    for (let i = 0; i < deckListData.length; i++) {
        let count = deckListData[i].count;
        for (let j = 0; j < count; j++) {
            let card = {
                'cardIndex': i,
                'index': index
            };
            library.push(card);
            index += 1;
        }
    }
    return library;
}

function shuffle(array) {
    array.sort(function () {
        return (Math.round(Math.random()) - 0.5);
    });
    return array;
}

function clearStartupCards() {
    $('#startupHand').empty();
    $('#startupPrize').empty();
    $('#startupDraw').empty();
}

function setupStartupCards() {
    clearStartupCards();
    let deckListData = getDeckListData();
    let library = shuffle(createLibrary(deckListData))
    for (let i = 0; i < 14; i++) {
        const card = '<img src="' + deckListData[library[i].cardIndex].cardImage + '" class="ex-card">';
        if (i < 7) {
            $('#startupHand').append(card);
        } else if (i < 13) {
            $('#startupPrize').append(card);
        } else {
            $('#startupDraw').append(card);
        }
    }
}

const playCardListIds = [
    'play-deck', 'play-hand', 'play-prize', 'play-discard',
    'play-lost', 'play-stadium', 'play-active', 'play-view',
    'play-bench-a', 'play-bench-b', 'play-bench-c', 'play-bench-d', 'play-bench-e',
];

function clearPlayCards() {
    for (let i = 0; i < playCardListIds.length; i++) {
        $('#' + playCardListIds[i]).empty();
    }
}

function setupPlayCards() {
    clearPlayCards();
    let deckListData = getDeckListData();
    let library = shuffle(createLibrary(deckListData))
    for (let i = 0; i < library.length; i++) {
        const card = '<div class="play-card" data-index="' + library[i].index +
            '"><img src="' + deckListData[library[i].cardIndex].cardImage + '"></div>';
        if (i < 7) {
            $('#play-hand').append(card);
        } else if (i < 13) {
            $('#play-prize').append(card);
        } else {
            $('#play-deck').append(card);
        }
    }
}

function sortCards(selector) {
    let parent = $(selector);
    let items = parent.children().sort(function (a, b) {
        let a_index = $(a).data('index');
        let b_index = $(b).data('index');
        return a_index > b_index ? 1 : -1;
    });
    parent.append(items);
}

function shuffleCards(selector) {
    let parent = $(selector);
    let items = parent.children().sort(function () {
        return (Math.round(Math.random()) - 0.5);
    });
    parent.append(items);
}

function appendCardsTo(fromSelector, toSelector) {
    $(fromSelector).children().appendTo(toSelector);
}

function prependCardsTo(fromSelector, toSelector) {
    $(fromSelector).children().prependTo(toSelector);
}

function swapCards(aSelector, bSelector) {
    let items = $(aSelector).children().detach();
    $(bSelector).children().appendTo(aSelector);
    items.appendTo(bSelector);
}

function openPlayDeck() {
    $('#play-deckZone').show();
    $('#play-zoneRow1').hide();
    $('#play-zoneRow2').hide();
    $('#deckMenu-1').hide();
    $('#deckMenu-2').show();
}

function closePlayDeck() {
    $('#play-deckZone').hide();
    $('#play-zoneRow1').show();
    $('#play-zoneRow2').show();
    $('#deckMenu-1').show();
    $('#deckMenu-2').hide();
}

function openZoneMenu(cardListId) {
    if (cardListId === 'play-deck' || cardListId === 'play-lost') { return; }

    let parent = $('#' + cardListId).parent();
    parent.css('z-index', 1000);
    $('#zoneMenu').css({
        top: parent.position().top,
        left: parent.position().left + parent.width() + 14
    });
    $('#zoneMenuContainer').show();
    $('#zoneMenu').data('id', cardListId);

    $('#zoneMenu-toBattle').hide();
    $('#zoneMenu-viewPrize').hide();
    $('#zoneMenu-toDiscard').hide();
    $('#zoneMenu-toHand').hide();
    $('#zoneMenu-toDeck').hide();
    $('#zoneMenu-toDeckTop').hide();
    $('#zoneMenu-toDeckBottom').hide();
    $('#zoneMenu-toPrize').hide();

    if (cardListId === 'play-prize') {
        $('#zoneMenu-viewPrize').show();
    }
    else if (cardListId === 'play-stadium') {
        $('#zoneMenu-toDiscard').show();
        $('#zoneMenu-toHand').show();
        $('#zoneMenu-toDeck').show();
        $('#zoneMenu-toDeckTop').show();
        $('#zoneMenu-toDeckBottom').show();
    }
    else if (cardListId === 'play-active') {
        $('#zoneMenu-toDiscard').show();
        $('#zoneMenu-toHand').show();
        $('#zoneMenu-toDeck').show();
        $('#zoneMenu-toDeckTop').show();
        $('#zoneMenu-toDeckBottom').show();
    }
    else if (cardListId === 'play-view') {
        $('#zoneMenu-toDiscard').show();
        $('#zoneMenu-toHand').show();
        $('#zoneMenu-toDeck').show();
        $('#zoneMenu-toDeckTop').show();
        $('#zoneMenu-toDeckBottom').show();
        $('#zoneMenu-toPrize').show();
    }
    else if (cardListId === 'play-hand') {
        $('#zoneMenu-toDiscard').show();
        $('#zoneMenu-toDeck').show();
        $('#zoneMenu-toDeckTop').show();
        $('#zoneMenu-toDeckBottom').show();
    }
    else if (cardListId === 'play-discard') {
        $('#zoneMenu-toDeck').show();
        $('#zoneMenu-toDeckTop').show();
        $('#zoneMenu-toDeckBottom').show();
    }
    else { // bench
        $('#zoneMenu-toBattle').show();
        $('#zoneMenu-toDiscard').show();
        $('#zoneMenu-toHand').show();
        $('#zoneMenu-toDeck').show();
        $('#zoneMenu-toDeckTop').show();
        $('#zoneMenu-toDeckBottom').show();
    }
}

function closeZoneMenu() {
    let cardListId = $('#zoneMenu').data('id');
    $('#' + cardListId).parent().removeAttr("style");
    $('#zoneMenuContainer').hide();
}

$(document).ready(function () {
    let styleSheet = $('<style>').text(styles);
    $('head').append(styleSheet);
    $('body > main').prepend(toolkitDom);
    $('#testStartupZone').hide();
    $('#testPlayZone').hide();
    $('#zoneMenuContainer').hide();

    // Adjust search result zone width
    $('#reduceColumn').click(function () {
        column -= 1;
        column = Math.max(2, column);
        updateSearchResultZone();
    });
    $('#addColumn').click(function () {
        column += 1;
        updateSearchResultZone();
    });
    $('#searchResultContainer').on('DOMSubtreeModified', function () {
        updateSearchResultZone();
    });
    updateSearchResultZone();

    // Search cards when click enter at the input field
    const searchButton = $('#searchCardButton');
    $('#freeword').keyup(function (e) {
        if (e.which == 13) {
            searchButton.trigger('click');
        }
    });

    // Make deck list being sortable
    let decklistZoneCardContainer = document.getElementById('decklistZoneCardContainer');
    Sortable.create(decklistZoneCardContainer, {
        animation: 150
    });

    // Test startup
    $('#testStartup').click(function () {
        setupStartupCards();
        $('#testStartupZone').show();
        $('#testPlayZone').hide();
    });
    $('#testStartupClose').click(function () {
        clearStartupCards();
        $('#testStartupZone').hide();
    });

    // Test play
    $('#testPlay').click(function () {
        setupPlayCards()
        closePlayDeck();
        $('#testPlayZone').show();
        $('#testStartupZone').hide();
    });
    $('#testPlayClose').click(function () {
        clearPlayCards();
        $('#testPlayZone').hide();
    });

    // Deck menu buttons in test play
    $('#deckMenu-open').click(function () {
        openPlayDeck();
    });
    $('#deckMenu-close').click(function () {
        closePlayDeck();
        shuffleCards('#play-deck');
    });
    $('#deckMenu-drawOne').click(function () {
        $('#play-deck').children().first().detach().appendTo("#play-hand");
    });
    $('#deckMenu-viewOne').click(function () {
        $('#play-deck').children().first().detach().appendTo("#play-view");
    });
    $('#deckMenu-sort').click(function () {
        sortCards('#play-deck');
    });
    $('#deckMenu-shuffle').click(function () {
        shuffleCards('#play-deck');
    });

    for (let i = 0; i < playCardListIds.length; i++) {
        // Setup sortable list for test play
        Sortable.create(document.getElementById(playCardListIds[i]), {
            group: 'play-cards',
            animation: 150
        });
        // Setup zone menu click event
        $('h3', $('#' + playCardListIds[i]).parent()).click(function () {
            let cardListId = $('div', $(this).parent())[0].id;
            openZoneMenu(cardListId);
        });
    }

    // Close zone menu
    $('#zoneMenu-overlay').click(function () {
        closeZoneMenu();
    });

    // Zone menu buttons
    $('#zoneMenu-toBattle').click(function () {
        let cardListId = $('#zoneMenu').data('id');
        swapCards('#' + cardListId, '#play-active');
        closeZoneMenu();
    });
    $('#zoneMenu-viewPrize').click(function () {
        let cardListId = $('#zoneMenu').data('id');
        appendCardsTo('#' + cardListId, '#play-view');
        closeZoneMenu();
    });
    $('#zoneMenu-toDiscard').click(function () {
        let cardListId = $('#zoneMenu').data('id');
        appendCardsTo('#' + cardListId, '#play-discard');
        closeZoneMenu();
    });
    $('#zoneMenu-toHand').click(function () {
        let cardListId = $('#zoneMenu').data('id');
        appendCardsTo('#' + cardListId, '#play-hand');
        closeZoneMenu();
    });
    $('#zoneMenu-toDeck').click(function () {
        let cardListId = $('#zoneMenu').data('id');
        appendCardsTo('#' + cardListId, '#play-deck');
        shuffleCards('#play-deck');
        closeZoneMenu();
    });
    $('#zoneMenu-toDeckTop').click(function () {
        let cardListId = $('#zoneMenu').data('id');
        shuffleCards('#' + cardListId);
        prependCardsTo('#' + cardListId, '#play-deck');
        closeZoneMenu();
    });
    $('#zoneMenu-toDeckBottom').click(function () {
        let cardListId = $('#zoneMenu').data('id');
        shuffleCards('#' + cardListId);
        appendCardsTo('#' + cardListId, '#play-deck');
        closeZoneMenu();
    });
    $('#zoneMenu-toPrize').click(function () {
        let cardListId = $('#zoneMenu').data('id');
        appendCardsTo('#' + cardListId, '#play-prize');
        shuffleCards('#play-prize');
        closeZoneMenu();
    });
});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址