Last active
July 18, 2025 14:04
-
-
Save fukubaya/ad4eb94e223669dbf9cd7ca99cfc2e29 to your computer and use it in GitHub Desktop.
find avalilable skills and lookup the unit with maximum points in staplo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (async () => { | |
| const userId = 123456789; // Your user ID | |
| const MAX_CARD_PAGE = 10; // Maximum number of pages to fetch for each rarity | |
| const stacommuHighlightName = "スタコミュハイライト vol.1"; | |
| const stacommuHighlightName2 = "スタコミュハイライト vol.2"; | |
| const stacommuHighlightName3 = "スタコミュハイライト vol.3"; | |
| const stacommuAward2025Name1 = "スタコミュAWARD 2025 vol.1"; | |
| const stacommuAward2025Name2 = "スタコミュAWARD 2025 vol.2"; | |
| const skills = [ | |
| {name: "三国同盟", names: ["百田 夏菜子", "玉井 詩織", "高城 れに"], point: 300}, | |
| {name: "あやかなこ", names: ["百田 夏菜子", "佐々木 彩夏(ももいろクローバーZ)"], point: 300}, | |
| //{name: "あやかなこ", names: ["百田 夏菜子", "佐々木 彩夏(LumiUnion)"], point: 300}, | |
| {name: "ももたかぎ", names: ["百田 夏菜子", "高城 れに"], point: 300}, | |
| {name: "ももたまい", names: ["百田 夏菜子", "玉井 詩織"], point: 300}, | |
| {name: "た行", names: ["玉井 詩織", "高城 れに"], point: 300}, | |
| {name: "うさぎ先輩後輩", names: ["玉井 詩織", "佐々木 彩夏(ももいろクローバーZ)"], point: 300}, | |
| //{name: "うさぎ先輩後輩", names: ["玉井 詩織", "佐々木 彩夏(LumiUnion)"], point: 300}, | |
| {name: "チーム最年長最年少", names: ["高城 れに", "佐々木 彩夏(ももいろクローバーZ)"], point: 300}, | |
| //{name: "チーム最年長最年少", names: ["高城 れに", "佐々木 彩夏(LumiUnion)"], point: 300}, | |
| {name: "Wあーりん", names: ["佐々木 彩夏(ももいろクローバーZ)", "佐々木 彩夏(LumiUnion)"], point: 300}, | |
| {name: "エビ中姉メン", names: ["真山 りか", "安本 彩花", "中山 莉子"], point: 300}, | |
| {name: "エビ中妹メン", names: ["桜木 心菜", "小久保 柚乃", "風見 和香", "桜井 えま", "仲村 悠菜"], point: 300}, | |
| {name: "ココユノノカ", names: ["桜木 心菜", "小久保 柚乃", "風見 和香"], point: 300}, | |
| {name: "えまゆな", names: ["桜井 えま", "仲村 悠菜"], point: 300}, | |
| {name: "第2回スタコミュAWARD選抜(私立恵比寿中学)", names: ["真山 りか", "仲村 悠菜", "桜木 心菜"], point: 300}, | |
| {name: "第3回スタコミュAWARD選抜(私立恵比寿中学)", names: ["仲村 悠菜", "小久保 柚乃", "風見 和香"], point: 300}, | |
| {name: "杏仁", names: ["坂井 仁香", "杏 ジュリア"], point: 300}, | |
| {name: "よしこい", names: ["吉川 ひより", "小泉 遥香"], point: 300}, | |
| {name: "ジュリあき", names: ["杏 ジュリア", "菅田 愛貴"], point: 300}, | |
| {name: "かなはる", names: ["辻野 かなみ", "小泉 遥香"], point: 300}, | |
| {name: "ひーはー", names: ["坂井 仁香", "小泉 遥香"], point: 300}, | |
| {name: "あきぴよ", names: ["菅田 愛貴", "吉川 ひより"], point: 300}, | |
| {name: "ほのなお", names: ["秋本 帆華", "咲良 菜緒"], point: 300}, | |
| {name: "親子ケミ", names: ["咲良 菜緒", "大黒 柚姫"], point: 300}, | |
| {name: "さめぽて", names: ["秋本 帆華", "大黒 柚姫"], point: 300}, | |
| {name: "ほのハル", names: ["秋本 帆華", "坂本 遥奈"], point: 300}, | |
| {name: "はなおん", names: ["坂本 遥奈", "咲良 菜緒"], point: 300}, | |
| {name: "ばっしょー姉メン", names: ["希山 愛", "上田 理子", "春乃 きいな"], point: 300}, | |
| {name: "ばっしょー妹メン", names: ["蒼井 りるあ", "柳 美舞", "浅井 アヤネ", "逢里 みう", "日南 心那"], point: 300}, | |
| {name: "ばっしょー新世代", names: ["浅井 アヤネ", "逢里 みう", "日南 心那"], point: 300}, | |
| {name: "あいりこ", names: ["希山 愛", "上田 理子"], point: 300}, | |
| {name: "りるみゆ", names: ["柳 美舞", "蒼井 りるあ"], point: 300}, | |
| {name: "イヤギハジャ", names: ["吉瀬 真珠", "北美 梨寧"], point: 300}, | |
| {name: "ぱんださん", names: ["橘 花怜", "律月 ひかる"], point: 300}, | |
| {name: "轟", names: ["伊達 花彩", "桜 ひなの"], point: 300}, | |
| {name: "トーホク・ラブ・ストーリー", names: ["葉月 結菜", "藤谷 美海", "安杜 羽加"], point: 300}, | |
| {name: "とうほくちゃん", names: ["律月 ひかる", "北美 梨寧", "葉月 結菜", "安杜 羽加", "吉瀬 真珠"], point: 300}, | |
| {name: "いぎなりちゃん", names: ["橘 花怜", "桜 ひなの", "藤谷 美海", "伊達 花彩"], point: 300}, | |
| {name: "そらりじゅ", names: ["村星 りじゅ", "茜 空"], point: 300}, | |
| {name: "そらりじゅもあ", names: ["村星 りじゅ", "茜 空", "芹澤 もあ"], point: 300}, | |
| {name: "ゆうこは", names: ["宮沢 友", "若菜 こはる"], point: 300}, | |
| {name: "りなるり", names: ["結城 りな", "葵 るり"], point: 300}, | |
| {name: "第2回スタコミュAWARD選抜(ukka)", names: ["結城 りな", "芹澤 もあ"], point: 300}, | |
| {name: "第3回スタコミュAWARD選抜(ukka)", names: ["結城 りな", "葵 るり", "宮沢 友"], point: 300}, | |
| {name: "ゆづはな", names: ["市川 優月", "小島 はな"], point: 300}, | |
| {name: "あいらもえか", names: ["愛来", "鈴木 萌花"], point: 300}, | |
| {name: "第2回スタコミュAWARD選抜(AMEFURASSHI)", names: ["市川 優月", "鈴木 萌花", "小島 はな"], point: 300}, | |
| //{name: "圧", names: ["播磨 かな", "佐々木 彩夏(ももいろクローバーZ)"], point: 300}, | |
| {name: "圧", names: ["播磨 かな", "佐々木 彩夏(LumiUnion)"], point: 300}, | |
| {name: "ロマンスはエチュード", names: ["佐々木 彩夏(LumiUnion)", "里菜", "雪月 心愛", "青山 菜花"], point: 300}, | |
| {name: "あやなの", names: ["白浜 あや", "青山 菜花"], point: 300}, | |
| {name: "バディ・フィルム", names: ["播磨 かな", "内藤 るな"], point: 300}, | |
| {name: "二十歳", names: ["田中 咲帆", "千浜 もあな"], point: 300}, | |
| {name: "第2回スタコミュAWARD選抜(LumiUnion)", names: ["田中 咲帆", "雪月 心愛"], point: 300}, | |
| {name: "第3回スタコミュAWARD選抜(LumiUnion)", names: ["雪月 心愛", "内藤 るな"], point: 300}, | |
| {name: "みずき×みう×るり", names: ["安井 泉妃", "黒坂 美羽", "白石 るり"], point: 300}, | |
| {name: "あや×りり×りんな×みうみ", names: ["白浜 あや", "高月 凛々", "嶋田 莉奈", "西村 美海"], point: 300}, | |
| {name: "じゅりまい", names: ["原田 麻衣", "鈴江 珠莉"], point: 300}, | |
| {name: "ゆぴしのん", names: ["渡辺 ゆう", "百瀬 詩音"], point: 300}, | |
| {name: "ゆめみる", names: ["ゆめ", "みる"], point: 300}, | |
| {name: "ゆのりおな", names: ["華川 ゆの", "香月 りおな"], point: 300}, | |
| {name: "スタコミュAWARD 2025 最多配信部門", names: ["内藤 るな", "結城 りな", "咲良 菜緒"], point: 300}, | |
| {name: "スタコミュAWARD 2025 ベストクリエイター部門", names: ["風見 和香", "葵 るり", "雪月 心愛"], point: 300}, | |
| {name: "スタコミュAWARD 2025 ベストクセスゴ部門", names: ["仲村 悠菜", "秋本 帆華", "宮沢 友"], point: 300}, | |
| {name: "スタコミュAWARD 2025 スタコミュスタッフから推され部門", names: ["小久保 柚乃", "坂本 遥奈"], point: 300}, | |
| ]; | |
| const birthmonthToNames = new Map([ | |
| [1, new Set([ | |
| "杏 ジュリア", "小泉 遥香", "希山 愛", "芹澤 もあ", "田中 咲帆", | |
| ])], | |
| [2, new Set([ | |
| "坂本 遥奈", "宮沢 友", "鈴木 萌花", "小島 はな", "播磨 かな", "安井 泉妃", "西村 美海", | |
| ])], | |
| [3, new Set([ | |
| "小久保 柚乃", "藤谷 美海", "伊達 花彩", "茜 空", "若菜 こはる", | |
| ])], | |
| [4, new Set([ | |
| "結城 りな", "白浜 あや", "華川 ゆの", | |
| ])], | |
| [5, new Set([ | |
| "仲村 悠菜", "柳 美舞", "北美 梨寧", "桜 ひなの", "高月 凛々", "渡辺 ゆう", "原田 麻衣", | |
| ])], | |
| [6, new Set([ | |
| "玉井 詩織", "高城 れに", "佐々木 彩夏(ももいろクローバーZ)", "佐々木 彩夏(LumiUnion)", | |
| "安本 彩花", "辻野 かなみ", "逢里 みう", "吉瀬 真珠", "里菜", | |
| ])], | |
| [7, new Set([ | |
| "百田 夏菜子", "大黒 柚姫", "坂井 仁香", "春乃 きいな", "律月 ひかる", "葵 るり", | |
| ])], | |
| [8, new Set([ | |
| "風見 和香", "吉川 ひより", "日南 心那", | |
| ])], | |
| [9, new Set([ | |
| "桜木 心菜", "桜井 えま", "咲良 菜緒", "葉月 結菜", "千浜 もあな", "青山 菜花", | |
| ])], | |
| [10, new Set([ | |
| "中山 莉子", "橘 花怜", "安杜 羽加", "村星 りじゅ", "嶋田 莉奈", "鈴江 珠莉", "百瀬 詩音", | |
| "ゆめ", "みる", | |
| ])], | |
| [11, new Set([ | |
| "秋本 帆華", "上田 理子", "市川 優月", "黒坂 美羽", | |
| ])], | |
| [12, new Set([ | |
| "真山 りか", "菅田 愛貴", "蒼井 りるあ", "浅井 アヤネ", "愛来", "内藤 るな", "雪月 心愛", | |
| "白石 るり", "香月 りおな", | |
| ])], | |
| ]); | |
| // return true if all names in skill.names are in nameSet | |
| const matchSkill = (nameSet, skill) => { | |
| return skill.names.reduce((hasAll, name) => hasAll && nameSet.has(name), true); | |
| }; | |
| // select the cards with the highest points | |
| const selectMaxCardSet = (nameSet, cardsMap) => { | |
| const baseResult = selectMaxCardSetForRarity(nameSet, cardsMap); | |
| let maxCardSet = baseResult.cardSet; | |
| let maxCardPoint = baseResult.cardPoint; | |
| for (const onlyRarity of [5, 4, 3, 2]) { | |
| const bonus = (onlyRarity - 1) * 10 + 100; // bonus points for rarity | |
| const result = selectMaxCardSetForRarity(nameSet, cardsMap, onlyRarity); | |
| if (result.cardPoint >= 0) { | |
| if (maxCardPoint < result.cardPoint + bonus) { | |
| maxCardSet = result.cardSet; | |
| maxCardPoint = result.cardPoint + bonus; | |
| } | |
| } | |
| } | |
| return {cardSet: maxCardSet, cardPoint: maxCardPoint}; | |
| } | |
| // select the cards with the highest points | |
| const selectMaxCardSetForRarity = (nameSet, cardsMap, onlyRarity) => { | |
| const cardSet = new Set(); | |
| let sum = 0; | |
| let hasStacommuHighlight = false; | |
| let hasStacommuHighlight2 = false; | |
| let hasStacommuHighlight3 = false; | |
| let hasStacommuAward2025vol1 = false; | |
| let hasStacommuAward2025vol2 = false; | |
| for (const name of nameSet){ | |
| const sorted = cardsMap.get(name).filter((c) => { return onlyRarity === undefined ? true : c.rarity === onlyRarity}).sort((a, b) => { return b.rarity - a.rarity; }); | |
| if (sorted.length <= 0) { | |
| return {cardSet: new Set(), cardPoint: -1}; | |
| } | |
| const stacomuHighlight = sorted.filter((c) => { return c.name == stacommuHighlightName }); | |
| const stacomuHighlight2 = sorted.filter((c) => { return c.name == stacommuHighlightName2; }); | |
| const stacomuHighlight3 = sorted.filter((c) => { return c.name == stacommuHighlightName3; }); | |
| const stacommuAward2025vol1 = sorted.filter((c) => { return c.name == stacommuAward2025Name1; }); | |
| const stacommuAward2025vol2 = sorted.filter((c) => { return c.name == stacommuAward2025Name2; }); | |
| /*if (!hasStacommuHighlight && stacomuHighlight.length > 0) { | |
| cardSet.add(stacomuHighlight[0]); | |
| sum += stacomuHighlight[0].rarity; | |
| sum += 500; // stacommu highlight bonus | |
| hasStacommuHighlight = true; | |
| } else if (!hasStacommuHighlight2 && stacomuHighlight2.length > 0) { | |
| cardSet.add(stacomuHighlight2[0]); | |
| sum += stacomuHighlight2[0].rarity; | |
| sum += 500; // stacommu highlight bonus | |
| hasStacommuHighlight2 = true; | |
| } else if (!hasStacommuHighlight3 && stacomuHighlight3.length > 0) { | |
| cardSet.add(stacomuHighlight3[0]); | |
| sum += stacomuHighlight3[0].rarity; | |
| sum += 500; // stacommu highlight bonus | |
| hasStacommuHighlight3 = true; | |
| } else*/ if (!hasStacommuAward2025vol1 && stacommuAward2025vol1.length > 0 ) { | |
| cardSet.add(stacommuAward2025vol1[0]); | |
| sum += stacommuAward2025vol1[0].rarity; | |
| sum += 500; // stacommu award 2025 vol.1 bonus | |
| hasStacommuAward2025vol1 = true; | |
| } else if (!hasStacommuAward2025vol2 && stacommuAward2025vol2.length > 0 ) { | |
| cardSet.add(stacommuAward2025vol2[0]); | |
| sum += stacommuAward2025vol2[0].rarity; | |
| sum += 500; // stacommu award 2025 vol.2 bonus | |
| hasStacommuAward2025vol2 = true; | |
| } else { | |
| cardSet.add(sorted[0]); | |
| sum += sorted[0].rarity; | |
| } | |
| } | |
| return {cardSet: cardSet, cardPoint: sum}; | |
| } | |
| // Fetch all cards for the user | |
| const fetchCards = async (userId) => { | |
| const rarities = [5, 4, 3, 2]; | |
| const cardsMap = new Map(); | |
| // Fetch all cards for the user | |
| for (const r of rarities) { | |
| let page = 1; | |
| while (true) { | |
| const url = `https://api-stacommu.orical.jp/cards?partner_id=18&season_id=18000001&user_id=${userId}&card_type=non_memorial&ownership_type=owned&rarity=${r}&page=${page}&per=25`; | |
| const cardList = await (await fetch(url)).json(); | |
| for (const card of cardList) { | |
| if (card.person === undefined) { | |
| continue; // Skip cards without person info | |
| } | |
| const personName = card.person.name; | |
| if (!cardsMap.has(personName)) { | |
| cardsMap.set(personName, []); | |
| } | |
| cardsMap.get(card.person.name).push({ | |
| rarity: card.rarity, | |
| name: card.name, | |
| personName: personName, | |
| }); | |
| } | |
| if (cardList.length < 25 || page > MAX_CARD_PAGE) { | |
| break; | |
| } | |
| page++; | |
| } | |
| } | |
| return cardsMap; | |
| }; | |
| // Find available skills based on the user's cards | |
| const findAvailableSkills = (nameSet, skills) => { | |
| return skills.filter((skill) => { | |
| return matchSkill(nameSet, skill); | |
| }); | |
| }; | |
| // Lookup the unit with maximum points | |
| const lookupMaxUnit = (cardsSet, birthmonthToNames, skills) => { | |
| const thisMonth = (new Date()).getMonth() + 1; | |
| const birthMonthNames = birthmonthToNames.get(thisMonth); | |
| const nameList = Array.from(cardsMap.keys()); | |
| // list of cardsSet at maximum point | |
| let cardsSetsAtMax = []; | |
| // maximum point | |
| let maxPoint = 0; | |
| // list of skills at maximum point | |
| let skillsAtMax = []; | |
| for(let i = 0; i < nameList.length - 4; i++) { | |
| for(let j = i + 1; j < nameList.length - 3; j++) { | |
| for(let k = j + 1; k < nameList.length - 2; k++) { | |
| for(let l = k + 1; l < nameList.length - 1; l++) { | |
| for(let m = l + 1; m < nameList.length; m++) { | |
| const candidateNameSet = new Set([nameList[i], nameList[j], nameList[k], nameList[l], nameList[m]]); | |
| const skillNames = []; | |
| let sum = 0; | |
| // select cards | |
| const {cardSet, cardPoint} = selectMaxCardSet(candidateNameSet, cardsMap); | |
| sum += cardPoint; | |
| // add rarity bonus skill names | |
| for (const rarity of [5, 4, 3, 2]) { | |
| const cards = Array.from(cardSet).filter((c) => c.rarity === rarity); | |
| if (cards.length == 5) { | |
| skillNames.push(`☆${rarity}コンボブースト`); | |
| } | |
| } | |
| // birth month bonus | |
| if (Array.from(candidateNameSet).some((c) => birthMonthNames.has(c))) { | |
| sum += 100; | |
| skillNames.push(`${thisMonth}月バースデーメンバーブースト`); | |
| } | |
| // skill points | |
| for (const skill of skills) { | |
| if (matchSkill(candidateNameSet, skill)) { | |
| sum += skill.point; | |
| skillNames.push(skill.name); | |
| } | |
| } | |
| // add skill names with speficic cards | |
| /*if (Array.from(cardSet).some((c) => c.name === stacommuHighlightName)) { | |
| skillNames.push(stacommuHighlightName); | |
| } | |
| if (Array.from(cardSet).some((c) => c.name === stacommuHighlightName2)) { | |
| skillNames.push(stacommuHighlightName2); | |
| } | |
| if (Array.from(cardSet).some((c) => c.name === stacommuHighlightName3)) { | |
| skillNames.push(stacommuHighlightName3); | |
| }*/ | |
| if (Array.from(cardSet).some((c) => c.name === stacommuAward2025Name1)) { | |
| skillNames.push(stacommuAward2025Name1) | |
| } | |
| if (Array.from(cardSet).some((c) => c.name === stacommuAward2025Name2)) { | |
| skillNames.push(stacommuAward2025Name2); | |
| } | |
| if (sum > maxPoint) { | |
| maxPoint = sum; | |
| cardsSetsAtMax = [cardSet]; | |
| skillsAtMax = [skillNames]; | |
| } else if (sum === maxPoint) { | |
| cardsSetsAtMax.push(cardSet); | |
| skillsAtMax.push(skillNames); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return {cardsSetsAtMax, skillsAtMax, maxPoint}; | |
| }; | |
| // fetch cards | |
| const cardsMap = await fetchCards(userId); | |
| // avaliable skills | |
| const availableSkills = findAvailableSkills(new Set(cardsMap.keys()), skills); | |
| console.log("available skills"); | |
| availableSkills.forEach((skill) => { | |
| console.log(`skill found: ${skill.name}(${skill.names.join(",")}) with point ${skill.point}`); | |
| }); | |
| // lookup max unit | |
| const {cardsSetsAtMax, skillsAtMax, maxPoint} = lookupMaxUnit(cardsMap, birthmonthToNames, availableSkills); | |
| console.log("Max Point:", maxPoint); | |
| for (let i = 0; i < cardsSetsAtMax.length; i++) { | |
| const cardNames = Array.from(cardsSetsAtMax[i]).map((c) => {return `${c.personName}☆${c.rarity}[${c.name}]`;}); | |
| console.log(`Candidate ${i + 1}:`, Array.from(skillsAtMax[i]).join(", ") + "\n" + cardNames.join("\n")); | |
| } | |
| })(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment