├── package.json ├── google5a3c504435f9bc87.html ├── kanon.ico ├── kanon.png ├── scripts ├── util │ └── utility.js ├── setting.js ├── simulator │ ├── class │ │ ├── PItemManager.js │ │ ├── PIdolLog.js │ │ ├── AutoContest.js │ │ ├── Contest.js │ │ ├── ConditionChecker.js │ │ ├── TurnType.js │ │ ├── Deck.js │ │ ├── Calculator.js │ │ ├── PIdolStatus.js │ │ └── PIdol.js │ ├── run.js │ └── data │ │ ├── gvgContestData.js │ │ ├── contestData.js │ │ └── pIdolData.js ├── worker.js ├── window.js ├── gvg │ └── window.js ├── test │ ├── window.js │ └── run.js └── combination │ └── window.js ├── .gitignore ├── styles ├── simulationLog.css └── styles.css ├── main.js ├── deno.lock ├── test.html ├── index.html ├── combination.html └── gvg.html /package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /google5a3c504435f9bc87.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google5a3c504435f9bc87.html -------------------------------------------------------------------------------- /kanon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanon511/new_gakumas_contest_simulator/HEAD/kanon.ico -------------------------------------------------------------------------------- /kanon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanon511/new_gakumas_contest_simulator/HEAD/kanon.png -------------------------------------------------------------------------------- /scripts/util/utility.js: -------------------------------------------------------------------------------- 1 | 2 | export const deep_copy = (obj) => { 3 | return JSON.parse(JSON.stringify(obj)); 4 | }; 5 | -------------------------------------------------------------------------------- /scripts/setting.js: -------------------------------------------------------------------------------- 1 | //是否开启测试模式 2 | export let onTest = false; 3 | 4 | //常量 5 | export const url = "https://kanon511.github.io/new_gakumas_contest_simulator" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | todo.md 2 | test.js 3 | main.js 4 | .vscode 5 | html_test/ 6 | trash/ 7 | public/ 8 | src/ 9 | deno.lock 10 | contest_memory_select/ 11 | 12 | node_modules 13 | -------------------------------------------------------------------------------- /scripts/simulator/class/PItemManager.js: -------------------------------------------------------------------------------- 1 | import { PItem } from '../data/pItemData.js'; 2 | 3 | export class PItemManager { 4 | 5 | // property 6 | #pItemList; 7 | 8 | /** 9 | * コンストラクタ 10 | * @param {Array[Number]} idList - PアイテムのIDリスト 11 | */ 12 | constructor (idList) { 13 | this.idList = idList; 14 | this.#pItemList = idList.map(id => new PItem(id)); 15 | } 16 | 17 | /** 18 | * Pアイテムリストを返します 19 | * @returns {Array} 20 | */ 21 | getPItemList () { 22 | return this.#pItemList; 23 | } 24 | 25 | /** 26 | * 条件に合ったPアイテムリストを返します 27 | * @param {String} 28 | * @returns {Array} 29 | */ 30 | getByTiming (timing) { 31 | return this.#pItemList.filter( 32 | item => item.isAvailable() && 33 | item.activate_timing == timing); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /scripts/simulator/class/PIdolLog.js: -------------------------------------------------------------------------------- 1 | export class PIdolLog { 2 | 3 | constructor () { 4 | this.log = []; 5 | this.currentTurnLog = null; 6 | this.currentTurn = 0; 7 | } 8 | 9 | getLog () { 10 | return this.log; 11 | } 12 | 13 | // addTextLog (text) { 14 | // this.currentTurnLog.history.push(text); 15 | // // console.log(text); 16 | // } 17 | 18 | addExecutionLog (executionLog) { 19 | this.currentTurnLog.executionLog.push(...executionLog); 20 | } 21 | 22 | nextTurn ({ score, hp, block, turnType }) { 23 | this.currentTurn++; 24 | this.currentTurnLog = { 25 | turn: this.currentTurn, 26 | turnType: turnType, 27 | executionLog: [], 28 | status: { 29 | score, hp, block, 30 | } 31 | }; 32 | this.log.push(this.currentTurnLog); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /scripts/worker.js: -------------------------------------------------------------------------------- 1 | import { run } from "./simulator/run.js"; 2 | 3 | self.onmessage = function(e) { 4 | const runs = e.data.runs; 5 | const data = e.data.data; 6 | 7 | let scoreList = []; 8 | let minLog; 9 | let maxLog; 10 | let rndLog; 11 | const rndLogNumber = Math.floor(Math.random()*runs); 12 | 13 | for (let i = 0; i < runs; i++) { 14 | const result = run(data); 15 | scoreList.push(result.finalStatus.score); 16 | if (!minLog || minLog.finalStatus.score > result.finalStatus.score) { 17 | minLog = result; 18 | } 19 | if (!maxLog || maxLog.finalStatus.score < result.finalStatus.score) { 20 | maxLog = result; 21 | } 22 | if (i == rndLogNumber) { 23 | rndLog = result; 24 | } 25 | } 26 | 27 | self.postMessage({ 28 | scoreList: scoreList, 29 | minLog: minLog, 30 | maxLog: maxLog, 31 | rndLog: rndLog, 32 | }); 33 | }; -------------------------------------------------------------------------------- /scripts/simulator/class/AutoContest.js: -------------------------------------------------------------------------------- 1 | import { Contest } from "./Contest.js"; 2 | 3 | /** 4 | * Contestを経由して手札から最も評価値の高いカードを選択するクラス。 5 | */ 6 | export class AutoContest { 7 | 8 | // property 9 | 10 | #contest; 11 | 12 | // method 13 | 14 | /** 15 | * コンストラクタ 16 | * @param {Contest} contest 17 | */ 18 | constructor (contest) { 19 | this.#contest = contest; 20 | } 21 | 22 | #findMaxValue (array) { 23 | if (array.length === 0) return undefined; 24 | let max = array[0]; 25 | for (let i = 1; i < array.length; i++) { 26 | if (array[i] > max) { 27 | max = array[i]; 28 | } 29 | } 30 | return max; 31 | } 32 | 33 | /** 34 | * 手札から使用可能で最も評価値(card.evaluation)の高い手札のインデックスを返します。 35 | * 使用可能なカードがない場合は-1を返します。 36 | * @returns {Number} 手札のインデックス 37 | */ 38 | select () { 39 | const handCards = this.#contest.getHands(); 40 | const availableIndex = handCards.map((item,i)=>[item, i]).filter(item=>item[0].isAvailable()).map(item=>item[1]); 41 | if (availableIndex.length == 0) return -1; 42 | const availableIndexValue = availableIndex.map(idx=>handCards[idx]).map(card=>card.evaluation); 43 | const maxValue = this.#findMaxValue(availableIndexValue); 44 | return availableIndex[availableIndexValue.indexOf(maxValue)]; 45 | } 46 | } -------------------------------------------------------------------------------- /styles/simulationLog.css: -------------------------------------------------------------------------------- 1 | 2 | #contest-log { 3 | font-size: 12px; 4 | } 5 | 6 | #contest-log i { 7 | vertical-align: 0px; 8 | margin-right: 5px; 9 | } 10 | 11 | .log-turn { 12 | /* border-top: solid 4px; 13 | border-bottom: solid 4px; */ 14 | padding: 5px 5px; 15 | font-size: 1.1em; 16 | color: white; 17 | font-weight: bold; 18 | display: flex; 19 | } 20 | 21 | .log-turn[data-turnType="vocal"] { 22 | background: linear-gradient(to right, #F13584, #F461A1); 23 | } 24 | .log-turn[data-turnType="dance"] { 25 | background: linear-gradient(to right, #1D84ED, #4B9EF0); 26 | } 27 | .log-turn[data-turnType="visual"] { 28 | background: linear-gradient(to right, #F7B12F, #F8C25D); 29 | } 30 | .log-turn > :first-child { 31 | margin-right: auto; 32 | } 33 | 34 | .log-block { 35 | margin-bottom: 5px; 36 | /* padding: 3px; */ 37 | border: solid 1px #ccc; 38 | border-radius: 5px; 39 | } 40 | .log-block-title { 41 | font-size: 1.1em; 42 | padding: 2px 5px; 43 | background-color: #eee; 44 | } 45 | .log-block-content { 46 | padding: 4px 8px; 47 | } 48 | .log-block .fa-book-open { 49 | color: #28A745; 50 | } 51 | .log-block .fa-chess-rook { 52 | color: #FF4136; 53 | } 54 | .log-block .fa-clone { 55 | color: #28A745; 56 | } 57 | .log-block .fa-forward { 58 | color: #007BFF; 59 | } 60 | .log-block .fa-link { 61 | color: #007BFF; 62 | } -------------------------------------------------------------------------------- /scripts/simulator/run.js: -------------------------------------------------------------------------------- 1 | import { Contest } from './class/Contest.js'; 2 | import { PIdol } from './class/PIdol.js'; 3 | import { AutoContest } from './class/AutoContest.js'; 4 | 5 | export const run = (data) => { 6 | 7 | const pIdol = new PIdol({ 8 | parameter: data.parameter, 9 | plan: data.plan, 10 | trend: data.trend, 11 | pItemIds: data.pItemIds, 12 | skillCardIds: data.skillCardIds, 13 | autoId: data.autoId 14 | }); 15 | 16 | const contest = new Contest({ 17 | pIdol: pIdol, 18 | maxTurn: data.turn, 19 | criteria: data.criteria, 20 | turnRank: data.turnRank, 21 | firstTurn: data.firstTurn, 22 | turnTypes: data.turnTypes, 23 | }); 24 | 25 | const autoContest = new AutoContest(contest); 26 | 27 | for (let breakout = 0; breakout < 100; breakout++) { 28 | contest.startTurn(); 29 | let loopout = 0; 30 | for (let endFlag = false; !endFlag;) { 31 | endFlag = contest.useCard(autoContest.select()); 32 | if (loopout > 100) { 33 | throw new Error('カード選択無限ループバグ | '+`${contest.getHands().map(item=>item.name+':'+item.evaluation).join(', ')} + ${autoContest.select()}`); 34 | } 35 | loopout++; 36 | } 37 | contest.finishTurn(); 38 | if (contest.checkkFinishContest()) break; 39 | } 40 | 41 | return contest.getResult(); 42 | 43 | }; -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import { Contest } from './scripts/simulator/class/Contest.js'; 2 | import { PIdol } from './scripts/simulator/class/PIdol.js'; 3 | import { AutoContest } from './scripts/simulator/class/AutoContest.js'; 4 | import { BufReader } from "https://deno.land/std/io/mod.ts"; 5 | import { skillCardData } from './scripts/simulator/data/skillCardData.js'; 6 | import { TurnType } from './scripts/simulator/class/TurnType.js'; 7 | 8 | const encoder = new TextEncoder(); 9 | 10 | async function print(str) { 11 | const bytes = encoder.encode(str); 12 | await Deno.stdout.write(bytes); 13 | } 14 | 15 | async function scanf() { 16 | const reader = new BufReader(Deno.stdin); 17 | print('>'); 18 | const { line } = await reader.readLine(); 19 | const input = new TextDecoder().decode(line); 20 | return input; 21 | } 22 | 23 | const vocal = 1738; 24 | const dance = 1502; 25 | const visual = 464; 26 | const hp = 200; 27 | const plan = 'logic'; 28 | const pItemIds = [ 29 | 4240623, // 開始時やる気 30 | 4240722, // 開始時集中、消費体力増加 31 | 2301010, // 使用時アクティブなら、好調、スキルカード使用数追加 32 | 2301021, // 勝へのこだわり 33 | 2306010, // ひみつ特訓カーデ 34 | 35 | ]; 36 | const skillCardIds = [ 37 | 2021020, 2021020, 2021020, 38 | 1021010, 1021020, 4020040, 1022020, 2011010, 2011020, 2011070, 4011030, 4300010, 4300011 39 | ]; 40 | 41 | // const skillCardIds = skillCardData.map(item=>item.id).filter(id=>id%10==1); 42 | 43 | const contestPIdol = new PIdol({ 44 | parameter: { 45 | vocal: vocal, 46 | dance: dance, 47 | visual: visual, 48 | hp: hp, 49 | }, 50 | plan: plan, 51 | pItemIds: pItemIds, 52 | skillCardIds: skillCardIds, 53 | }); 54 | 55 | 56 | const contest = new Contest({ 57 | pIdol: contestPIdol, 58 | maxTurn: 8, 59 | criteria: { 60 | vocal : 40, 61 | dance : 33, 62 | visual: 27, 63 | }, 64 | turnTypes: [4, 2, 2], 65 | }); 66 | 67 | while (true) { 68 | contest.startTurn(); 69 | for (let endFlag = false; !endFlag;) { 70 | // contest.printHands(); 71 | const inputNumber = Number(await scanf()); 72 | endFlag = contest.useCard(inputNumber); 73 | } 74 | contest.finishTurn(); 75 | if (contest.isFinish) break; 76 | } -------------------------------------------------------------------------------- /scripts/simulator/class/Contest.js: -------------------------------------------------------------------------------- 1 | import { SkillCard } from "../data/skillCardData.js"; 2 | 3 | /** 4 | * コンテスト会場クラス 5 | */ 6 | export class Contest { 7 | 8 | // property 9 | 10 | #pIdol; 11 | 12 | // method 13 | 14 | /** 15 | * コンストラクタ 16 | * @param {Object} parameters pIdol, maxTurn, criteria, turnTypeを設定します。 17 | */ 18 | constructor (parameters) { 19 | const { 20 | pIdol, 21 | maxTurn, 22 | criteria, 23 | turnRank, 24 | firstTurn, 25 | turnTypes, 26 | } = parameters; 27 | this.#pIdol = pIdol; 28 | this.#pIdol.init(maxTurn, criteria, turnRank, firstTurn, turnTypes); 29 | } 30 | 31 | /** 32 | * ターンを開始します 33 | */ 34 | startTurn () { 35 | this.#pIdol.start(); 36 | } 37 | 38 | /** 39 | * 指定された手札のスキルカードを使用するか休憩します。 40 | * @param {Number} cardNumber 使うカード番号または-1 41 | * @returns {Boolean} 行動終了するかどうか(または不正な行動でないか) 42 | */ 43 | useCard (cardNumber) { 44 | // -1は休憩 45 | if (cardNumber == -1) { 46 | this.#pIdol.rest(); 47 | return true; 48 | } 49 | // -1以外の不正な値 もしくは カードが使用不可 50 | const hands = this.getHands(); 51 | if ( 52 | cardNumber < 0 || 53 | cardNumber >= hands.length || 54 | !hands[cardNumber]?.isAvailable() 55 | ) { 56 | return false; 57 | } 58 | this.#pIdol.useCard(cardNumber); 59 | return !this.#pIdol.checkAdditionalAction(); 60 | } 61 | 62 | /** 63 | * ターンを終了します 64 | */ 65 | finishTurn () { 66 | this.#pIdol.end(); 67 | } 68 | 69 | /** 70 | * 手札にあるスキルカードの配列を返します。 71 | * @returns {Array[SkillCard]} 手札のスキルカード 72 | */ 73 | getHands () { 74 | return this.#pIdol.getDeck('handCards'); 75 | } 76 | 77 | /** 78 | * ターンが残っているかを返します。 79 | * @returns {Boolean} 80 | */ 81 | checkkFinishContest () { 82 | return this.#pIdol.checkFinished(); 83 | } 84 | 85 | /** 86 | * 結果を取得します。 87 | * @returns {Object} 88 | */ 89 | getResult () { 90 | return this.#pIdol.getResult(); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /scripts/simulator/class/ConditionChecker.js: -------------------------------------------------------------------------------- 1 | 2 | export class ConditionChecker { 3 | static check (query, status) { 4 | if (!query) return true; 5 | const result = query.split('|').map((orQuery) => { 6 | return orQuery.split('&').map((andQuery) => { 7 | return this.#evaluateSplitCondition(andQuery, status); 8 | }).every(value => value) 9 | }).some(value => value); 10 | return result; 11 | } 12 | 13 | static #evaluateSplitCondition (query, status) { 14 | const signList = ['==', '!=', '>=', '<=', '>', '<']; 15 | const sign = signList.find(sign=>~query.indexOf(sign)); 16 | if (sign == '') { 17 | throw new Error(`予期する記号が含まれません > ${query}`); 18 | } 19 | const [key, value] = query.split(sign); 20 | return this.#compareSplitCondition(key, value, sign, status); 21 | } 22 | 23 | static #compareSplitCondition (key, value, sign, status) { 24 | let targetValue = null; 25 | if (key == 'hpPer') { 26 | targetValue = status.hp / status.maxHp * 100; 27 | } else 28 | if (key == 'score') { 29 | targetValue = status.score; 30 | } else 31 | if (key == 'block') { 32 | targetValue = status.block; 33 | } else 34 | if (key == 'turn') { 35 | targetValue = status.turn; 36 | } else 37 | if (key == 'turnType') { 38 | targetValue = status.currentTurnType; 39 | } else 40 | if (key == 'turnMultiple') { 41 | targetValue = status.turn % Number(value) == 0 ? value : -1; 42 | } else 43 | if (key == 'cardType') { 44 | targetValue = status.lastUsedCard?.type; 45 | } else 46 | if (key == 'cardId') { 47 | targetValue = status.lastUsedCard?.id; 48 | } else 49 | if (key == 'cardEffectInclude') { 50 | targetValue = status.lastUsedCard?.effects.some(effect=>effect.target==value || effect.type == value) ? value : -1; 51 | } else 52 | if (key == 'usedCardCountMultiple') { 53 | targetValue = (status.usedCardCount + 1) % Number(value) == 0 ? value : -1;// 54 | } else 55 | if (key == 'usedCardTurnCountMultiple') { 56 | targetValue = (status.usedCardTurnCount + 1) % Number(value) == 0 ? value : -1;// 57 | } else 58 | if (key == 'remain_turn') { 59 | targetValue = status.remainTurn; 60 | } else { 61 | targetValue = status.pStatus.getValue(key); 62 | } 63 | switch (sign) { 64 | case '==': return targetValue == value; 65 | case '!=': return targetValue != value; 66 | case '>=': return targetValue >= value; 67 | case '<=': return targetValue <= value; 68 | case '>' : return targetValue > value; 69 | case '<' : return targetValue < value; 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /scripts/window.js: -------------------------------------------------------------------------------- 1 | import { SkillCardData } from './simulator/data/skillCardData.js'; 2 | import { setSelectImageCard,isEvolveButtonPressed } from './scripts.js'; 3 | 4 | export const imgPath = 'https://katabami83.github.io/gakumas_file/images/'; 5 | export const addImgPath = 'https://kanon511.github.io/new_gakumas_contest_simulator/kanon.png'; 6 | let plan = "" 7 | let category = "all" 8 | 9 | function createButtons(nodes) { 10 | const container = document.getElementById('buttonContainer'); 11 | container.innerHTML = ''; 12 | 13 | const button = document.createElement('div'); 14 | button.className = 'windowButton'; 15 | button.dataset.type = "-1"; 16 | button.dataset.plan = "free"; 17 | button.dataset.evolve = "-1"; 18 | button.onclick = () => { 19 | setSelectImageCard(-1); 20 | document.getElementById('modalOverlay').style.display = 'none'; 21 | } 22 | 23 | const img = document.createElement('img'); 24 | img.src = addImgPath; 25 | button.appendChild(img); 26 | 27 | container.appendChild(button); 28 | 29 | nodes.forEach(node => { 30 | if(node.id.toString()[0]=='1' || node.id.toString()[1]=='2'){ 31 | return; 32 | } 33 | const button = document.createElement('div'); 34 | button.className = 'windowButton'; 35 | button.dataset.type = node.id.toString()[0]; 36 | button.dataset.plan = node.plan; 37 | button.dataset.evolve = node.id.toString()[6]; 38 | button.onclick = () => { 39 | setSelectImageCard(node.id); 40 | document.getElementById('modalOverlay').style.display = 'none'; 41 | } 42 | 43 | const img = document.createElement('img'); 44 | img.src = imgPath+"cards/card_"+node.id+".webp"; 45 | button.appendChild(img); 46 | 47 | container.appendChild(button); 48 | }); 49 | } 50 | 51 | export function filterButtons() { 52 | const buttons = document.querySelectorAll('.windowButton'); 53 | buttons.forEach(button => { 54 | if ((category === 'all' || button.dataset.type === category) && (plan===button.dataset.plan || button.dataset.plan==='free') && (button.dataset.evolve=="-1" || button.dataset.evolve==(isEvolveButtonPressed?'1':'0'))) { 55 | button.style.display = 'flex'; 56 | } else { 57 | button.style.display = 'none'; 58 | } 59 | }); 60 | } 61 | 62 | export function init(){ 63 | document.querySelectorAll('.category-bar button').forEach((button, index) => { 64 | if(index<4){ 65 | button.addEventListener('click', () => { 66 | document.querySelectorAll('.category-bar button').forEach((btn, i) => { 67 | if(i<4) 68 | btn.classList.remove('active') 69 | }); 70 | button.classList.add('active'); 71 | category = button.dataset.category; 72 | filterButtons(); 73 | }); 74 | } 75 | }); 76 | 77 | createButtons(SkillCardData.getAll()); 78 | filterButtons("all") 79 | } 80 | 81 | export function setPlan(plan_name){ 82 | plan = plan_name 83 | filterButtons("all") 84 | } -------------------------------------------------------------------------------- /scripts/gvg/window.js: -------------------------------------------------------------------------------- 1 | import { SkillCardData } from '../simulator/data/skillCardData.js'; 2 | import { setSelectImageCard,isEvolveButtonPressed } from './scripts.js'; 3 | 4 | export const imgPath = 'https://katabami83.github.io/gakumas_file/images/'; 5 | export const addImgPath = 'https://kanon511.github.io/new_gakumas_contest_simulator/kanon.png'; 6 | let plan = "" 7 | let category = "all" 8 | 9 | function createButtons(nodes) { 10 | const container = document.getElementById('buttonContainer'); 11 | container.innerHTML = ''; 12 | 13 | const button = document.createElement('div'); 14 | button.className = 'windowButton'; 15 | button.dataset.type = "-1"; 16 | button.dataset.plan = "free"; 17 | button.dataset.evolve = "-1"; 18 | button.onclick = () => { 19 | setSelectImageCard(-1); 20 | document.getElementById('modalOverlay').style.display = 'none'; 21 | } 22 | 23 | const img = document.createElement('img'); 24 | img.src = addImgPath; 25 | button.appendChild(img); 26 | 27 | container.appendChild(button); 28 | 29 | nodes.forEach(node => { 30 | if(node.id.toString()[0]=='1' || node.id.toString()[1]=='2'){ 31 | return; 32 | } 33 | const button = document.createElement('div'); 34 | button.className = 'windowButton'; 35 | button.dataset.type = node.id.toString()[0]; 36 | button.dataset.plan = node.plan; 37 | button.dataset.evolve = node.id.toString()[6]; 38 | button.onclick = () => { 39 | setSelectImageCard(node.id); 40 | document.getElementById('modalOverlay').style.display = 'none'; 41 | } 42 | 43 | const img = document.createElement('img'); 44 | img.src = imgPath+"cards/card_"+node.id+".webp"; 45 | button.appendChild(img); 46 | 47 | container.appendChild(button); 48 | }); 49 | } 50 | 51 | export function filterButtons() { 52 | const buttons = document.querySelectorAll('.windowButton'); 53 | buttons.forEach(button => { 54 | if ((category === 'all' || button.dataset.type === category) && (plan===button.dataset.plan || button.dataset.plan==='free') && (button.dataset.evolve=="-1" || button.dataset.evolve==(isEvolveButtonPressed?'1':'0'))) { 55 | button.style.display = 'flex'; 56 | } else { 57 | button.style.display = 'none'; 58 | } 59 | }); 60 | } 61 | 62 | export function init(){ 63 | document.querySelectorAll('.category-bar button').forEach((button, index) => { 64 | if(index<4){ 65 | button.addEventListener('click', () => { 66 | document.querySelectorAll('.category-bar button').forEach((btn, i) => { 67 | if(i<4) 68 | btn.classList.remove('active') 69 | }); 70 | button.classList.add('active'); 71 | category = button.dataset.category; 72 | filterButtons(); 73 | }); 74 | } 75 | }); 76 | 77 | createButtons(SkillCardData.getAll()); 78 | filterButtons("all") 79 | } 80 | 81 | export function setPlan(plan_name){ 82 | plan = plan_name 83 | filterButtons("all") 84 | } -------------------------------------------------------------------------------- /scripts/test/window.js: -------------------------------------------------------------------------------- 1 | import { SkillCardData } from '../simulator/data/skillCardData.js'; 2 | import { setSelectImageCard,isEvolveButtonPressed } from './scripts.js'; 3 | 4 | export const imgPath = 'https://katabami83.github.io/gakumas_file/images/'; 5 | export const addImgPath = 'https://kanon511.github.io/new_gakumas_contest_simulator/kanon.png'; 6 | let plan = "" 7 | let category = "all" 8 | 9 | function createButtons(nodes) { 10 | const container = document.getElementById('buttonContainer'); 11 | container.innerHTML = ''; 12 | 13 | const button = document.createElement('div'); 14 | button.className = 'windowButton'; 15 | button.dataset.type = "-1"; 16 | button.dataset.plan = "free"; 17 | button.dataset.evolve = "-1"; 18 | button.onclick = () => { 19 | setSelectImageCard(-1); 20 | document.getElementById('modalOverlay').style.display = 'none'; 21 | } 22 | 23 | const img = document.createElement('img'); 24 | img.src = addImgPath; 25 | button.appendChild(img); 26 | 27 | container.appendChild(button); 28 | 29 | nodes.forEach(node => { 30 | if(node.id.toString()[0]=='1' || node.id.toString()[1]=='2'){ 31 | return; 32 | } 33 | const button = document.createElement('div'); 34 | button.className = 'windowButton'; 35 | button.dataset.type = node.id.toString()[0]; 36 | button.dataset.plan = node.plan; 37 | button.dataset.evolve = node.id.toString()[6]; 38 | button.onclick = () => { 39 | setSelectImageCard(node.id); 40 | document.getElementById('modalOverlay').style.display = 'none'; 41 | } 42 | 43 | const img = document.createElement('img'); 44 | img.src = imgPath+"cards/card_"+node.id+".webp"; 45 | button.appendChild(img); 46 | 47 | container.appendChild(button); 48 | }); 49 | } 50 | 51 | export function filterButtons() { 52 | const buttons = document.querySelectorAll('.windowButton'); 53 | buttons.forEach(button => { 54 | if ((category === 'all' || button.dataset.type === category) && (plan===button.dataset.plan || button.dataset.plan==='free') && (button.dataset.evolve=="-1" || button.dataset.evolve==(isEvolveButtonPressed?'1':'0'))) { 55 | button.style.display = 'flex'; 56 | } else { 57 | button.style.display = 'none'; 58 | } 59 | }); 60 | } 61 | 62 | export function init(){ 63 | document.querySelectorAll('.category-bar button').forEach((button, index) => { 64 | if(index<4){ 65 | button.addEventListener('click', () => { 66 | document.querySelectorAll('.category-bar button').forEach((btn, i) => { 67 | if(i<4) 68 | btn.classList.remove('active') 69 | }); 70 | button.classList.add('active'); 71 | category = button.dataset.category; 72 | filterButtons(); 73 | }); 74 | } 75 | }); 76 | 77 | createButtons(SkillCardData.getAll()); 78 | filterButtons("all") 79 | } 80 | 81 | export function setPlan(plan_name){ 82 | plan = plan_name 83 | filterButtons("all") 84 | } -------------------------------------------------------------------------------- /scripts/simulator/data/gvgContestData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * IDの付け方 3 | * 1_xxx_x_x 4 | * 1_期数_场地_轮数 5 | * 6 | * 実装: 7 | */ 8 | const contestData = [ 9 | { 10 | id: 1, 11 | name: '第1期公会战', 12 | stages: [ 13 | { 14 | name: 'A场', 15 | criteria: { 'vocal': 15, 'dance': 35, 'visual': 50 }, 16 | rank: {'vocal': 3, 'dance': 2, 'visual': 1}, 17 | turn: 10, 18 | stageEffectsPItemIds: [5000111, 5000112], 19 | stagePItemIds: [4241022], 20 | plan: 'sense', 21 | turnTypes: [2, 3, 5], 22 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 23 | }, 24 | { 25 | name: 'B场', 26 | criteria: { 'vocal': 35, 'dance': 30, 'visual': 35 }, 27 | rank: {'vocal': 1, 'dance': 3, 'visual': 2}, 28 | turn: 12, 29 | stageEffectsPItemIds: [5000121, 5000122], 30 | stagePItemIds: [4240722], 31 | plan: 'sense', 32 | turnTypes: [5, 3, 4], 33 | firstTurn: {'vocal': 80, 'dance': 0, 'visual': 20}, 34 | }, 35 | { 36 | name: 'C场', 37 | criteria: { 'vocal': 10, 'dance': 45, 'visual': 45 }, 38 | rank: {'vocal': 3, 'dance': 1, 'visual': 2}, 39 | turn: 10, 40 | stageEffectsPItemIds: [5000131, 5000132], 41 | stagePItemIds: [4240812], 42 | plan: 'sense', 43 | turnTypes: [2, 5, 3], 44 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 45 | }, 46 | { 47 | name: 'D场', 48 | criteria: { 'vocal': 35, 'dance': 30, 'visual': 35 }, 49 | rank: {'vocal': 1, 'dance': 3, 'visual': 2}, 50 | turn: 12, 51 | stageEffectsPItemIds: [5000141, 5000142], 52 | stagePItemIds: [4240723], 53 | plan: 'logic', 54 | turnTypes: [5, 3, 4], 55 | firstTurn: {'vocal': 80, 'dance': 0, 'visual': 20}, 56 | }, 57 | { 58 | name: 'E场', 59 | criteria: { 'vocal': 10, 'dance': 45, 'visual': 45 }, 60 | rank: {'vocal': 3, 'dance': 1, 'visual': 2}, 61 | turn: 12, 62 | stageEffectsPItemIds: [5000151, 5000152], 63 | stagePItemIds: [4240813], 64 | plan: 'logic', 65 | turnTypes: [2, 6, 4], 66 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 67 | }, 68 | ], 69 | }, 70 | ]; 71 | 72 | export class ContestData { 73 | 74 | // property 75 | static #contestData = contestData; 76 | static #index = Object.fromEntries(this.#contestData.map((item, i) => [item.id, i])); 77 | 78 | // method 79 | 80 | /** 81 | * IDと一致するコンテストオブジェクトを返します 82 | * @param {Number} id - コンテストID 83 | * @returns {Object} コンテストオブジェクト 84 | */ 85 | static getById (id) { 86 | if (!(id in this.#index)) { 87 | throw new Error('idと一致するコンテストがありません。'); 88 | } 89 | return this.#contestData[this.#index[id]]; 90 | } 91 | 92 | static has (id) { 93 | return id in this.#index; 94 | } 95 | 96 | /** 97 | * コンテストオブジェクトを返します※非推奨 98 | * @returns {Array} コンテストオブジェクトリスト 99 | */ 100 | static getAll () { 101 | return this.#contestData; 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3", 3 | "redirects": { 4 | "https://deno.land/std/io/mod.ts": "https://deno.land/std@0.224.0/io/mod.ts" 5 | }, 6 | "remote": { 7 | "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", 8 | "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", 9 | "https://deno.land/std@0.224.0/bytes/concat.ts": "86161274b5546a02bdb3154652418efe7af8c9310e8d54107a68aaa148e0f5ed", 10 | "https://deno.land/std@0.224.0/bytes/copy.ts": "08d85062240a7223e6ec4e2af193ad1a50c59a43f0d86ac3a7b16f3e0d77c028", 11 | "https://deno.land/std@0.224.0/io/_common.ts": "36705cdb4dfcd338d6131bca1b16e48a4d5bf0d1dada6ce397268e88c17a5835", 12 | "https://deno.land/std@0.224.0/io/_constants.ts": "3c7ad4695832e6e4a32e35f218c70376b62bc78621ef069a4a0a3d55739f8856", 13 | "https://deno.land/std@0.224.0/io/buf_reader.ts": "aa6d589e567c964c8ba1f582648f3feac45e88ab2e3d2cc2c9f84fd73c05d051", 14 | "https://deno.land/std@0.224.0/io/buf_writer.ts": "3fab3fbeae7a6ed1672207b640b6781a2369890878f8a7bb35f95d364dd6dae6", 15 | "https://deno.land/std@0.224.0/io/buffer.ts": "4d1f805f350433e418002accec798bc6c33ce18f614afa65f987c202d7b2234e", 16 | "https://deno.land/std@0.224.0/io/copy.ts": "63c6a4acf71fb1e89f5e47a7b3b2972f9d2c56dd645560975ead72db7eb23f61", 17 | "https://deno.land/std@0.224.0/io/copy_n.ts": "1bad1525b1f78737cb4c11084e2f7cf22359210fea89e2f0e784bd9a4b221fca", 18 | "https://deno.land/std@0.224.0/io/iterate_reader.ts": "1e5e4fea22d8965afb7df4ee9ab9adda0a0fc581adbea31bc2f2d25453f8a6e9", 19 | "https://deno.land/std@0.224.0/io/limited_reader.ts": "bbe3f7dafe9dd076d0c92d88b1b94e91ecad4825997064f44c2a767737ad2526", 20 | "https://deno.land/std@0.224.0/io/mod.ts": "f0a3f9d419394cec27099ba15ab0c8100da1e70928f95ebe646caaf667c6870c", 21 | "https://deno.land/std@0.224.0/io/multi_reader.ts": "dd8f06d50adec0e1befb92a1d354fcf28733a4b1669b23bf534ace161ce61b1c", 22 | "https://deno.land/std@0.224.0/io/read_all.ts": "876c1cb20adea15349c72afc86cecd3573335845ae778967aefb5e55fe5a8a4a", 23 | "https://deno.land/std@0.224.0/io/read_delim.ts": "d26cfed53b0c2c127ec3453ccd65f474ecc36a331209e246448885434afffc4e", 24 | "https://deno.land/std@0.224.0/io/read_int.ts": "2412f106103a271f5d770b2a61c97b13eebcd18de414a0a092ed82dfcb875903", 25 | "https://deno.land/std@0.224.0/io/read_lines.ts": "17b96f87dbebc5edb976b3d83dc7713bddb994e9c8cb688012d6c6c26803fb9e", 26 | "https://deno.land/std@0.224.0/io/read_long.ts": "d7cd367ab5c1263014c32a76afa0f0584c3bf3a3d860963c7368a6825094df46", 27 | "https://deno.land/std@0.224.0/io/read_range.ts": "2ddfedbfff44e4ea8d60c0463cddb2b1860293d932d6a72a0fb78bdf412538fc", 28 | "https://deno.land/std@0.224.0/io/read_short.ts": "f6dff570e685ade917dcb5188e8ecf0b701d6581b0cd186f08e6efe7f5ce33f7", 29 | "https://deno.land/std@0.224.0/io/read_string_delim.ts": "f6beafa8969e6d9d6d7d679f846cd6595d8b6e99091a20a0aecbd50eb30a391e", 30 | "https://deno.land/std@0.224.0/io/reader_from_stream_reader.ts": "a75bbc93f39df8b0e372cc1fbdc416a7cbf2a39fc4c09ddb057f1241100191c5", 31 | "https://deno.land/std@0.224.0/io/slice_long_to_bytes.ts": "bc59a7aaac64845371dbd44debf3e864ae7b7e453127751d96e30adb29fb633b", 32 | "https://deno.land/std@0.224.0/io/string_reader.ts": "279e9ea72e0ed7af6a9cb6da84f4148af93df849d308c31f124ca21f16d09cf2", 33 | "https://deno.land/std@0.224.0/io/string_writer.ts": "923954c2038a622b84c294b94a3a322565fa0d67e8b4c62942b154fc1ad3bb9b", 34 | "https://deno.land/std@0.224.0/io/to_readable_stream.ts": "ed03a44a1ec1cc55a85a857acf6cac472035298f6f3b6207ea209f93b4aefb39", 35 | "https://deno.land/std@0.224.0/io/to_writable_stream.ts": "ef422e0425963c8a1e0481674e66c3023da50f0acbe5ef51ec9789efc3c1e2ed", 36 | "https://deno.land/std@0.224.0/io/types.ts": "acecb3074c730b5ff487ba4fe9ce51e67bd982aa07c95e5f5679b7b2f24ad129", 37 | "https://deno.land/std@0.224.0/io/write_all.ts": "24aac2312bb21096ae3ae0b102b22c26164d3249dff96dbac130958aa736f038" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scripts/combination/window.js: -------------------------------------------------------------------------------- 1 | import { SkillCardData } from '../simulator/data/skillCardData.js'; 2 | 3 | export const imgPath = 'https://katabami83.github.io/gakumas_file/images/'; 4 | 5 | // 每个容器对应的图片URL和对应值 6 | let imageUrls = [ 7 | [],[],[],[],[],[] 8 | ]; 9 | 10 | function openImageSelector(containerIndex) { 11 | populateImageSelector(containerIndex); // 打开前填充图片选项 12 | document.getElementById(`imageSelector-${containerIndex}`).style.display = 'block'; 13 | document.getElementById('overlay').style.display = 'block'; // 显示背景遮罩 14 | } 15 | 16 | function closeImageSelector(containerIndex) { 17 | document.getElementById(`imageSelector-${containerIndex}`).style.display = 'none'; 18 | const options = document.querySelectorAll(`#imageSelectorContainer-${containerIndex} .image-option`); 19 | options.forEach(option => { 20 | option.classList.remove('selected'); 21 | }); 22 | } 23 | 24 | function populateImageSelector(containerIndex) { 25 | const cardImagePath = imgPath+"cards/card_"; 26 | 27 | const container = document.getElementById(`imageSelectorContainer-${containerIndex}`); 28 | container.innerHTML = ''; // 清空容器内容 29 | 30 | // 动态创建图片选项 31 | imageUrls[containerIndex].forEach(item => { 32 | const option = document.createElement('div'); 33 | option.className = 'image-option'; 34 | option.onclick = () => selectAndAddImage(containerIndex, item); // 直接选择并添加的函数 35 | option.innerHTML = `${item}`; 36 | container.appendChild(option); 37 | }); 38 | } 39 | 40 | function closeAllImageSelectors() { 41 | document.querySelectorAll('.modal').forEach(modal => { 42 | modal.style.display = 'none'; 43 | }); 44 | document.getElementById('overlay').style.display = 'none'; // 隐藏背景遮罩 45 | } 46 | 47 | export function selectAndAddImage(containerIndex, item) { 48 | const cardImagePath = imgPath+"cards/card_"; 49 | 50 | const newItem = document.createElement('div'); 51 | newItem.className = 'item'; 52 | newItem.innerHTML = ` 53 | ${item} 54 | 55 | `; 56 | newItem.setAttribute('data-value', item); // 将值附加到元素上 57 | document.getElementById(`container-${containerIndex}`).appendChild(newItem); 58 | 59 | // 关闭悬浮窗 60 | closeImageSelector(containerIndex); 61 | closeAllImageSelectors(); 62 | } 63 | 64 | export function getSelectedValues(containerIndex) { 65 | const container = document.getElementById(`container-${containerIndex}`); 66 | const selectedValues = Array.from(container.children).map(item => item.getAttribute('data-value')); 67 | return selectedValues.filter(item => item !== null); 68 | } 69 | 70 | export function setPlan(plan){ 71 | let allImageIds = SkillCardData.getAll() 72 | imageUrls = [ 73 | [],[],[],[],[],[] 74 | ]; 75 | for(let i of allImageIds){ 76 | if((i.plan == plan || i.plan == "free") && i.id[0]!= "0"){ 77 | if(i.card_cost){ 78 | if(i.card_cost<50){ 79 | imageUrls[0].push(i.id); 80 | } 81 | else if(i.card_cost<90){ 82 | imageUrls[1].push(i.id); 83 | } 84 | else if(i.card_cost<120){ 85 | imageUrls[2].push(i.id); 86 | } 87 | else if(i.card_cost<160){ 88 | imageUrls[3].push(i.id); 89 | } 90 | else if(i.card_cost<250){ 91 | imageUrls[4].push(i.id); 92 | } 93 | imageUrls[5].push(i.id); 94 | } 95 | } 96 | } 97 | } 98 | 99 | document.addEventListener('DOMContentLoaded', () => { 100 | for(let i=0;i<6;i++){ 101 | document.getElementById(`kanon-item-${i}`).onclick = () => openImageSelector(i); 102 | } 103 | }); -------------------------------------------------------------------------------- /scripts/simulator/class/TurnType.js: -------------------------------------------------------------------------------- 1 | import { deep_copy } from "../../util/utility.js"; 2 | 3 | /** 4 | * ターンごとのタイプ(vocal, dance, visual)を設定するクラス 5 | */ 6 | export class TurnType { 7 | 8 | /** 9 | * @type {Array} ターンごとのタイプ 10 | */ 11 | turnTypes; 12 | #turnCount; 13 | 14 | /** 15 | * ターンタイプクラスのインスタンスを作成する 16 | * @constructor 17 | * @param {Number} turnCount ターン数 18 | * @param {Object} critearia 評価基準オブジェクト 19 | */ 20 | constructor (turnCount, critearia, turnRank, firstTurn, turnTypes, autoId) { 21 | this.turnTypes = new Array(turnCount).fill(''); 22 | const criteariaRank = this.#setCriteariaRank(turnRank); 23 | const typeCount = this.#setTurnCount(turnCount, criteariaRank, turnTypes); 24 | 25 | // ラスト3ターンを流行3位->流行2位->流行1位の順にする 26 | this.turnTypes[this.turnTypes.length-3] = criteariaRank[2]; 27 | this.turnTypes[this.turnTypes.length-2] = criteariaRank[1]; 28 | this.turnTypes[this.turnTypes.length-1] = criteariaRank[0]; 29 | // 最初のターンを流行1位に固定する 30 | var firstTurnChance = 0; 31 | var randomNum = Math.random(); 32 | var turnCountStart = 0; 33 | for(let i in firstTurn){ 34 | firstTurnChance += firstTurn[i] * 0.01; 35 | if (randomNum <= firstTurnChance) { 36 | this.turnTypes[0] = i; 37 | typeCount[i] -= 1; 38 | turnCountStart += 1; 39 | break; 40 | } 41 | } 42 | // その分カウントを減らす 43 | typeCount[criteariaRank[0]] -= 1; 44 | typeCount[criteariaRank[1]] -= 1; 45 | typeCount[criteariaRank[2]] -= 1; 46 | 47 | const array = [typeCount[criteariaRank[0]], typeCount[ criteariaRank[1]], typeCount[criteariaRank[2]]]; 48 | 49 | for (let i = turnCountStart; i < turnCount - 3; i++) { 50 | let chooseIdx; 51 | chooseIdx = this.#getNewRandomIndex(array); 52 | this.turnTypes[i] = criteariaRank[chooseIdx]; 53 | array[chooseIdx]--; 54 | } 55 | } 56 | 57 | /** 58 | * 数値配列から数値の比率を確率としてランダムなインデックスを返す 59 | * @param {Array} array 数値配列 60 | * @returns {Number} 入力配列のindex 61 | */ 62 | #getRandomIndex (array) { 63 | const totalCount = array.reduce((pre, crt) => pre+crt, 0); 64 | const randomNumber = Math.floor(Math.random()*totalCount); 65 | for (let i = 0, currentNumber = 0; i < array.length; i++) { 66 | currentNumber += array[i]; 67 | if (randomNumber < currentNumber) { 68 | return i; 69 | } 70 | } 71 | } 72 | #getNewRandomIndex (array) { 73 | if(Math.random()<0.7&&array[0]>0) 74 | return 0 75 | if(Math.random()<0.7&&array[1]>0) 76 | return 1 77 | if(array[2]>0) 78 | return 2 79 | if(array[1]>0) 80 | return 1 81 | return 0 82 | } 83 | 84 | /** 85 | * 流行の順位順の流行順位配列を返します 86 | * @param {Object} critearia 評価基準オブジェクト 87 | * @returns {Array} 流行順位配列 88 | */ 89 | #setCriteariaRank (turnRank) { 90 | let criteariaRank = []; 91 | for (let i = 1; 3 > criteariaRank.length; i++){ 92 | if (i > 3) { 93 | console.error(turnRank,criteariaRank.length) 94 | throw new Error('赛季属性排名大于3,值:' + i); 95 | } 96 | 97 | for (let key in turnRank) { 98 | if (turnRank[key] == i) { 99 | criteariaRank.push(key); 100 | } 101 | } 102 | } 103 | return criteariaRank; 104 | } 105 | 106 | /** 107 | * 流行ごとのターン数を設定します 108 | * @param {Number} turnCount ターン数 109 | * @param {Array} criteariaRank 流行順位配列 110 | * @returns {Object} 111 | */ 112 | #setTurnCount (turnCount, criteariaRank, turnTypes) { 113 | // switch (turnCount) { 114 | // case 8 : return { [criteariaRank[0]] : 4, [criteariaRank[1]] : 2, [criteariaRank[2]] : 2 }; 115 | // case 10: return { [criteariaRank[0]] : 5, [criteariaRank[1]] : 3, [criteariaRank[2]] : 2 }; 116 | // case 12: return { [criteariaRank[0]] : 5, [criteariaRank[1]] : 4, [criteariaRank[2]] : 3 }; 117 | // } 118 | this.#turnCount = { 'vocal': turnTypes[0], 'dance': turnTypes[1], 'visual': turnTypes[2] }; 119 | return { 'vocal': turnTypes[0], 'dance': turnTypes[1], 'visual': turnTypes[2] } 120 | // throw new Error(`${turnCount}は想定されていないターン数です`); 121 | } 122 | 123 | /** 124 | * ターンタイプを取得します 125 | * @param {Number} turn ターン 126 | * @returns {String} ターンタイプ 127 | */ 128 | getType (turn) { 129 | const idx = turn-1; 130 | // 最大ターンを超過していたら最後の要素(流行1位)を返す 131 | if (idx > this.turnTypes.length - 1) { 132 | return this.turnTypes[this.turnTypes.length-1]; 133 | } 134 | return this.turnTypes[idx]; 135 | } 136 | 137 | getCount (type) { 138 | return this.#turnCount[type]; 139 | } 140 | 141 | /** 142 | * ターンタイプ配列を取得します 143 | * @returns {Array} 144 | */ 145 | getAllTypes () { 146 | return deep_copy(this.turnTypes); 147 | } 148 | } -------------------------------------------------------------------------------- /scripts/simulator/class/Deck.js: -------------------------------------------------------------------------------- 1 | import { SkillCard } from "../data/skillCardData.js"; 2 | 3 | /** 4 | * param1からparam2までの連番配列を返します。 5 | * @param {*} startNumber 開始番号 6 | * @param {*} endNumber 終了番号 7 | * @returns {Array[Number]} 連番配列 8 | */ 9 | function createRange (startNumber, endNumber) { 10 | const list = []; 11 | for (let i = startNumber; i < endNumber; i++) { 12 | list.push(i); 13 | } 14 | return list; 15 | } 16 | 17 | /** 18 | * スキルカードのデッキ 19 | */ 20 | export class Deck { 21 | 22 | // property 23 | 24 | index_drawPile = []; 25 | #index_handCards = []; 26 | #index_discardPile = []; 27 | #index_exhaustedCards = []; 28 | #flag_first_draw = true; 29 | index_first_draw = []; 30 | 31 | // method 32 | 33 | /** 34 | * コンストラクタ 35 | * @param {Array[Number]} skillCardIds デッキに入れるスキルカードのID配列 36 | */ 37 | constructor (skillCardIds) { 38 | this.skillCards = skillCardIds.map(id => new SkillCard(id)); 39 | this.init(); 40 | } 41 | 42 | /** 43 | * 配列の中身をランダムに入れ替えます。 44 | * @param {Array} list 45 | */ 46 | shuffle (list) { 47 | for (let i = list.length; 1 < i; i--) { 48 | const rnd = Math.floor(Math.random()*i); 49 | [list[rnd], list[i - 1]] = [list[i - 1], list[rnd]]; 50 | } 51 | } 52 | 53 | /** 54 | * 初期化 55 | */ 56 | init () { 57 | this.index_drawPile = createRange(0, this.skillCards.length); 58 | 59 | // レッスン開始時手札に入るカードのインデックスリスト 60 | // this.index_first_draw = this.index_drawPile 61 | // .filter(item=>this.skillCards[item].pre_effects 62 | // ?.map(effects=>effects.type) 63 | // .includes('レッスン開始時手札に入る')); 64 | 65 | this.shuffle(this.index_drawPile); 66 | 67 | this.index_first_draw = []; 68 | for (let i = 0; i < this.index_drawPile.length; i++) { 69 | if (!('pre_effects' in this.skillCards[this.index_drawPile[i]])) { 70 | continue; 71 | } 72 | if ( 73 | this.skillCards[this.index_drawPile[i]].pre_effects 74 | .map(effect=>effect.type) 75 | .includes('レッスン開始時手札に入る') 76 | ) { 77 | this.index_first_draw.push(this.index_drawPile[i]); 78 | this.index_drawPile.splice(i, 1); 79 | i--; 80 | } 81 | } 82 | 83 | // this.index_drawPile = [7, 3, 10, 5, 9, 18, 12, 10, 2, 16, 4, 13, 11, 15, 0, 17, 14, 6, 8, 19]; 84 | this.#index_handCards = []; 85 | this.#index_discardPile = []; 86 | this.#index_exhaustedCards = []; 87 | } 88 | 89 | draw (number) { 90 | const handCardsCount = this.#index_handCards.length; 91 | if (this.#flag_first_draw) { 92 | // 最初のドローでレッスン開始時手札に入るを手札に入れる 93 | this.#flag_first_draw = false; 94 | for (let i = 0; i < this.index_first_draw.length; i++) { 95 | if (i < 5) { 96 | this.addHandCards(this.index_first_draw[i]); 97 | } else { 98 | this.index_drawPile.unshift(this.index_first_draw[i]); 99 | } 100 | } 101 | // this.index_first_draw.forEach(item=>this.index_drawPile.splice(this.index_drawPile.indexOf(item), 1)); 102 | number -= this.#index_handCards.length; 103 | } 104 | for (let i = 0; i < number; i++) { 105 | if (this.index_drawPile.length == 0) { 106 | if (this.#index_discardPile.length == 0) break; // 山札と捨て札が両方0ならドローできない 107 | this.index_drawPile = this.#index_discardPile; 108 | this.#index_discardPile = []; 109 | this.shuffle(this.index_drawPile); 110 | } 111 | if (this.#index_handCards.length < 5) { 112 | this.addHandCards(this.index_drawPile.shift()); 113 | } else { 114 | this.#index_discardPile.push(this.index_drawPile.shift()); 115 | } 116 | } 117 | return { request: number, response: this.#index_handCards.length - handCardsCount } 118 | } 119 | 120 | addCardInDeck (cardId, position) { 121 | const card = new SkillCard(cardId); 122 | this.skillCards.push(card); 123 | const cardIdx = this.skillCards.length-1; 124 | switch (position) { 125 | case 'drawPile' : this.index_drawPile.push(cardIdx); break; 126 | case 'handCards' : this.addHandCards(cardIdx); break; 127 | case 'discardPile' : this.#index_discardPile.push(cardIdx); break; 128 | case 'exhaustedCards' : this.#index_exhaustedCards.push(cardIdx); break; 129 | } 130 | } 131 | 132 | addHandCards (card_index) { 133 | this.#index_handCards.push(card_index); 134 | } 135 | 136 | upgrade (type) { 137 | if (type == 'allhands') { 138 | for (const index of this.#index_handCards) { 139 | const targetCard = this.skillCards[index]; 140 | if (Number(targetCard.id) % 10 == 0) { // 強化前なら 141 | this.skillCards[index] = new SkillCard(Number(targetCard.id)+1); 142 | } 143 | } 144 | } 145 | } 146 | 147 | useCard (number) { 148 | const usedCard = this.getHandCardByNumber(number); 149 | if (usedCard.limit > 0) { 150 | if (--usedCard.limit == 0) { 151 | this.exhaust(number); 152 | } 153 | } else { 154 | this.discard(number); 155 | } 156 | } 157 | 158 | exhaust (number) { 159 | this.resetCard(this.getHandCardByNumber(number)); 160 | this.#index_handCards.splice(number, 1).forEach(card=>this.#index_exhaustedCards.push(card)); 161 | } 162 | 163 | resetCard (card) { 164 | card.executions = null; 165 | card.evaluation = 0; 166 | card.scheduledExecutions = null; 167 | } 168 | 169 | discard (number) { 170 | if (this.index_drawPile.length == 0) { 171 | this.index_drawPile = this.#index_discardPile; 172 | this.#index_discardPile = []; 173 | this.shuffle(this.index_drawPile); 174 | } 175 | this.resetCard(this.getHandCardByNumber(number)); 176 | this.#index_discardPile.push(...this.#index_handCards.splice(number, 1)); 177 | } 178 | 179 | discardAll() { 180 | while (this.#index_handCards.length) { 181 | this.discard(0); 182 | } 183 | } 184 | 185 | getHandCardByNumber (number) { 186 | return this.skillCards[this.#index_handCards[number]]; 187 | } 188 | 189 | getAllHandCards () { 190 | return this.handCards.map(idx => this.skillCards[idx]); 191 | } 192 | 193 | getAllHandCardCount () { 194 | return this.handCards.length; 195 | } 196 | 197 | get drawPile () { 198 | return this.index_drawPile.map(idx => this.skillCards[idx]); 199 | } 200 | 201 | get handCards () { 202 | //console.log(this.skillCards,this.#index_handCards,this.index_drawPile,this.#index_discardPile) 203 | return this.#index_handCards.map(idx => this.skillCards[idx]); 204 | } 205 | 206 | get discardPile () { 207 | return this.#index_discardPile.map(idx => this.skillCards[idx]); 208 | } 209 | 210 | get exhaustedCards () { 211 | return this.#index_exhaustedCards.map(idx => this.skillCards[idx]); 212 | } 213 | 214 | } -------------------------------------------------------------------------------- /scripts/test/run.js: -------------------------------------------------------------------------------- 1 | import { Contest } from '../simulator/class/Contest.js'; 2 | import { PIdol } from '../simulator/class/PIdol.js'; 3 | import { AutoContest } from '../simulator/class/AutoContest.js'; 4 | import { SkillCard, SkillCardData } from '../simulator/data/skillCardData.js'; 5 | 6 | function DOM_text_to_elememt (text) { 7 | const temporaryDiv = document.createElement('div'); 8 | temporaryDiv.innerHTML = text; 9 | return temporaryDiv.firstElementChild; 10 | } 11 | 12 | function DOM_delete_allChildren (parent) { 13 | while(parent.firstChild){ 14 | parent.removeChild(parent.firstChild); 15 | } 16 | } 17 | 18 | function parseExecutionLog(executeLog) { 19 | let htmlString = '
'; 20 | for (const log of executeLog) { 21 | if (log.type == 'use') { 22 | if (log.sourceType == 'skillCard') { 23 | htmlString += `
スキルカード「${log.source.name}」
`; 24 | } 25 | else if (log.sourceType == 'pItem') { 26 | htmlString += `
Pアイテム「${log.source.name}」
`; 27 | } 28 | else if (log.sourceType == 'pDrink') { 29 | htmlString += `
Pドリンク「${log.source.name}」
`; 30 | } 31 | else if (log.sourceType == 'pStatus') { 32 | htmlString += `
ステータス効果「${log.source.name}」
`; 33 | } 34 | else if (log.sourceType == 'pDelay') { 35 | htmlString += `
予約効果「${log.source.name}」
`; 36 | } 37 | // else if (log.sourceType == 'pIdol') { 38 | // htmlString += `
${log.source.name}を使った
`; 39 | // } 40 | else if (log.sourceType == 'pRest') { 41 | htmlString += `
${log.source.name}
`; 42 | } 43 | } 44 | else if (log.type == 'end') { 45 | htmlString += '
'; 46 | } 47 | else if (log.type == 'show') { 48 | htmlString += `
${log.message}
`; 49 | } 50 | else { 51 | htmlString += `
${log.message}
`; 52 | } 53 | } 54 | htmlString += '
'; 55 | return DOM_text_to_elememt(htmlString); 56 | } 57 | 58 | function parseSimulationLog(simulationLog) { 59 | const container = document.createElement('div'); 60 | for (let log = simulationLog.log.length - 1; log >= 0; log--) { 61 | const textElement = ` 62 |
63 |
64 |
${simulationLog.log[log].turn}ターン目 
65 |
66 |   67 | ${simulationLog.log[log].status.score} 68 | ${simulationLog.log[log].status.hp} 69 | ${simulationLog.log[log].status.block} 70 |
71 |
72 |
`; 73 | container.appendChild(DOM_text_to_elememt(textElement)); 74 | container.appendChild(parseExecutionLog(simulationLog.log[log].executionLog)); 75 | } 76 | return container; 77 | } 78 | 79 | export const run = (data,container) => { 80 | let handSkillCardIds = [] 81 | 82 | const pIdol = new PIdol({ 83 | parameter: data.parameter, 84 | plan: data.plan, 85 | trend: data.trend, 86 | pItemIds: data.pItemIds, 87 | skillCardIds: data.skillCardIds, 88 | autoId: data.autoId 89 | }); 90 | 91 | const contest = new Contest({ 92 | pIdol: pIdol, 93 | maxTurn: data.turn, 94 | criteria: data.criteria, 95 | turnRank: data.turnRank, 96 | firstTurn: data.firstTurn, 97 | turnTypes: data.turnTypes, 98 | }); 99 | 100 | const autoContest = new AutoContest(contest); 101 | 102 | const element_section_button = document.getElementById('section-button'); 103 | element_section_button.addEventListener('click', () => { 104 | const element_section_option = document.getElementById('use_card_list'); 105 | const element_selected_hand_card_text = document.getElementById('selected-hand-card-text'); 106 | if (element_section_option.value != '-1'){ 107 | handSkillCardIds.push(element_section_option.value); 108 | element_selected_hand_card_text.appendChild(DOM_text_to_elememt(`
${SkillCardData.getById(element_section_option.value).name}
`)) 109 | } 110 | element_section_option.value = '-1'; 111 | }); 112 | 113 | let kanon = 0 114 | const element_use_button = document.getElementById('use-button'); 115 | const element_turn_color = document.getElementById('turn-color'); 116 | function addEvent() { 117 | pIdol.turnType.turnTypes[kanon]=element_turn_color.value 118 | kanon++ 119 | pIdol.deck.skillCards = handSkillCardIds.map(id => new SkillCard(id)); 120 | pIdol.deck.index_first_draw = [] 121 | pIdol.deck.index_drawPile = [] 122 | 123 | if(kanon==1){ 124 | for (let i = 0; i < handSkillCardIds.length; i++) { 125 | if (!('pre_effects' in pIdol.deck.skillCards[i])) { 126 | continue; 127 | } 128 | if ( 129 | pIdol.deck.skillCards[i].pre_effects 130 | .map(effect=>effect.type) 131 | .includes('レッスン開始時手札に入る') 132 | ) { 133 | pIdol.deck.index_first_draw.push(i); 134 | handSkillCardIds[i] = -1; 135 | } 136 | } 137 | } 138 | 139 | for (let i = 0; i < handSkillCardIds.length; i++) { 140 | if (handSkillCardIds[i] == -1) continue; 141 | pIdol.deck.index_drawPile.push(i) 142 | } 143 | handSkillCardIds = [] 144 | DOM_delete_allChildren(document.getElementById('selected-hand-card-text')); 145 | contest.startTurn(); 146 | let loopout = 0; 147 | for (let endFlag = false; !endFlag;) { 148 | endFlag = contest.useCard(autoContest.select()); 149 | if (loopout > 100) { 150 | throw new Error('カード選択無限ループバグ | '+`${contest.getHands().map(item=>item.name+':'+item.evaluation).join(', ')} + ${autoContest.select()}`); 151 | } 152 | loopout++; 153 | } 154 | contest.finishTurn(); 155 | DOM_delete_allChildren(container); 156 | container.appendChild(parseSimulationLog(contest.getResult())) 157 | if (contest.checkkFinishContest()) element_use_button.removeEventListener('click', addEvent); 158 | } 159 | element_use_button.addEventListener('click', addEvent); 160 | 161 | return contest.getResult(); 162 | 163 | }; -------------------------------------------------------------------------------- /styles/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | font-size: 14px; 4 | margin: 0; 5 | padding: 20px; 6 | display: flex; 7 | justify-content: center; 8 | } 9 | 10 | #wrapper { 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | } 15 | 16 | .container { 17 | border: 1px solid #ccc; 18 | background-color: #f9f9f9; 19 | border-radius: 5px; 20 | padding: 10px; 21 | margin-bottom: 10px; 22 | width: 100%; 23 | box-sizing: border-box; 24 | } 25 | 26 | .modal-overlay { 27 | display: none; 28 | position: fixed; 29 | top: 0; 30 | left: 0; 31 | width: 100%; 32 | height: 100%; 33 | background: rgba(0, 0, 0, 0.5); 34 | justify-content: center; 35 | align-items: center; 36 | } 37 | 38 | .modal-content { 39 | width: 90%; 40 | background: white; 41 | border-radius: 8px; 42 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 43 | position: relative; 44 | height: 60%; 45 | } 46 | 47 | @media screen and (min-width: 750px) { 48 | #wrapper { 49 | flex-direction: row; 50 | align-items: flex-start; 51 | } 52 | 53 | .container { 54 | margin-bottom: 0; 55 | margin-right: 10px; 56 | /* width: calc(50% - 5px); */ 57 | } 58 | 59 | .container:last-child { 60 | margin-right: 0; 61 | } 62 | 63 | .modal-content { 64 | width: 50%; 65 | } 66 | } 67 | 68 | .section { 69 | margin-bottom: 0px; 70 | } 71 | .section label { 72 | display: block; 73 | margin-bottom: 0px; 74 | } 75 | .section input[type="text"], 76 | .section.character-select-box select, 77 | .section .input-group select { 78 | width: 100%; 79 | padding: 8px; 80 | margin-bottom: 5px; 81 | border: 1px solid #ccc; 82 | border-radius: 4px; 83 | } 84 | .section input[type="button"] { 85 | display: block; 86 | width: 100%; 87 | padding: 5px; 88 | background-color: #007BFF; 89 | color: white; 90 | border: none; 91 | border-radius: 4px; 92 | cursor: pointer; 93 | } 94 | .section input[type="button"]:hover { 95 | background-color: #0056b3; 96 | } 97 | .input-group { 98 | display: flex; 99 | justify-content: space-between; 100 | flex-wrap: wrap; 101 | } 102 | .input-group input[type="text"], 103 | .input-group select { 104 | flex: 1 1 calc(20% - 10px); 105 | margin-right: 10px; 106 | } 107 | .input-group input[type="text"]:last-child, 108 | .input-group select:last-child { 109 | margin-right: 0; 110 | } 111 | 112 | 113 | #card-box-container { 114 | display: flex; 115 | gap: 10px; 116 | } 117 | 118 | .container-character-cards-box { 119 | flex: 1 1 calc(50% - 10px); 120 | align-items: center; 121 | } 122 | 123 | .character-cards-box > .character-cards { 124 | margin-bottom: 10px; 125 | } 126 | 127 | .character-cards-box > .character-cards:last-child { 128 | margin-bottom: 0px; 129 | } 130 | 131 | .card-container-item { 132 | display: flex; 133 | width: 100%; 134 | align-items: center; 135 | background-color: #fff; 136 | border: 1px solid #ccc; 137 | border-radius: 4px; 138 | overflow: hidden; 139 | } 140 | 141 | .card-container-item[data-rarity="3"] { 142 | background-color: #ffc; 143 | } 144 | .card-container-item[data-rarity="4"] { 145 | /* background-color: #fcf; */ 146 | /* background : linear-gradient(118deg, 147 | red, 148 | orange, 149 | yellow, 150 | green, 151 | aqua, 152 | blue, 153 | purple); */ 154 | 155 | /* background: linear-gradient(to right, #ffffb2, #ffffb2, #ffffb2, #d8ffb2, #b2ffb2, #b2ffd8, #b2ffff, #b2d8ff, #b2b2ff, #d8b2ff, #ffb2ff, #ffb2d8); */ 156 | background: linear-gradient(to right, #FFFFE0, #FFFFE0, #FFFFE0, #EFFFE0, #E0FFE0, #E0FFEF, #E0FFFF, #E0EFFF, #E0E0FF, #EFE0FF, #FFE0FF, #FFE0EF); 157 | 158 | } 159 | 160 | 161 | .section.card-select-box select { 162 | background-color: transparent; 163 | flex: 1; 164 | height: 35px; 165 | padding: 8px; 166 | border: 0; 167 | border-radius: 4px; 168 | width: 100%; 169 | } 170 | 171 | .checkbox { 172 | display: none; 173 | } 174 | .card-container-item > .checkbox-label { 175 | width: 35px; 176 | height: 35px; 177 | margin-bottom: 0px; 178 | border-right: solid 1px #ccc; 179 | background-color: #ddd; 180 | display: flex; 181 | justify-content: center; 182 | align-items: center; 183 | cursor: pointer; 184 | position: relative; 185 | transition: all 100ms ease-in; 186 | } 187 | 188 | 189 | 190 | .checkbox-label::before, 191 | .checkbox-label::after { 192 | content: ""; 193 | background-color: #aaa; 194 | display: block; 195 | position: absolute; 196 | top: 50%; 197 | left: 50%; 198 | transform: translate(-50%, -50%); 199 | transition: all 100ms ease-in; 200 | } 201 | 202 | .checkbox-label::before { 203 | width: 4px; 204 | height: 50%; 205 | } 206 | 207 | .checkbox-label::after { 208 | width: 50%; 209 | height: 4px; 210 | } 211 | 212 | .checkbox:checked + .checkbox-label { 213 | background-color: #fd9; 214 | } 215 | 216 | .checkbox:checked + .checkbox-label::after, 217 | .checkbox:checked + .checkbox-label::before { 218 | background-color: #f60; 219 | } 220 | 221 | .run-button { 222 | text-align: center; 223 | } 224 | .run-button input[type="button"] { 225 | width: 100%; 226 | padding: 15px; 227 | background-color: #28a745; 228 | color: white; 229 | border: none; 230 | border-radius: 4px; 231 | cursor: pointer; 232 | } 233 | .run-button input[type="button"]:hover { 234 | background-color: #218838; 235 | } 236 | 237 | #contest-log { 238 | width: calc(100% - 10px); 239 | border: 1px solid #ccc; 240 | background-color: white; 241 | padding: 5px; 242 | overflow-x: scroll; 243 | } 244 | 245 | 246 | 247 | .result-table { 248 | width: 100%; 249 | text-align: center; 250 | border-collapse: collapse; 251 | border-spacing: 0; 252 | } 253 | .result-table th { 254 | padding: 3px; 255 | background: #e9faf9; 256 | border: solid 1px #778ca3; 257 | } 258 | .result-table td { 259 | padding: 3px; 260 | border: solid 1px #778ca3; 261 | } 262 | 263 | .result-log-button { 264 | display: flex; 265 | width: 100%; 266 | border-radius: 5px; 267 | overflow: hidden; 268 | border: 2px solid #007bdd; 269 | } 270 | 271 | .result-log-button > input[type="radio"] { 272 | display: none; 273 | } 274 | 275 | .result-log-button > .radio-box { 276 | flex: 1; 277 | padding: 5px 10px; 278 | cursor: pointer; 279 | text-align: center; 280 | transition: background-color 0.3s, color 0.3s; 281 | } 282 | 283 | .result-log-button > input[type="radio"]:checked + .radio-box { 284 | background-color: #007bdd; 285 | color: white; 286 | } 287 | 288 | .hide { 289 | display: none; 290 | } 291 | 292 | .center { 293 | text-align: center; 294 | } 295 | 296 | a { 297 | font-weight: bold; 298 | color: #007bdd; 299 | text-decoration: none; 300 | } 301 | 302 | #card-box-information { 303 | text-align: center; 304 | font-size: 12px; 305 | } 306 | 307 | .category-bar { 308 | display: flex; 309 | justify-content: center; 310 | margin: 3%; 311 | height: 10%; 312 | } 313 | .category-bar button { 314 | margin: 0 10px; 315 | border: none; 316 | border-radius: 10px; 317 | background: #f0f0f0; 318 | cursor: pointer; 319 | flex: 1; 320 | height: 100%; 321 | text-align: center; 322 | font-size: 1rem; 323 | transition: background 0.3s, color 0.3s; 324 | } 325 | .category-bar button.active { 326 | background: rgb(67, 138, 245); 327 | color: white; 328 | } 329 | .button-container { 330 | display: flex; 331 | flex-wrap: wrap; 332 | justify-content: space-around; 333 | align-content: flex-start; 334 | overflow-y: auto; 335 | height: 80%; 336 | padding: 20px; 337 | box-sizing: border-box; 338 | } 339 | .windowButton { 340 | width: 16%; 341 | max-width: 6rem; 342 | margin: 1%; 343 | display: flex; 344 | justify-content: center; 345 | align-items: center; 346 | overflow: hidden; 347 | } 348 | .windowButton img { 349 | width: 100%; 350 | height: 100%; 351 | object-fit: cover; 352 | } 353 | 354 | .character-cards-box-image{ 355 | display: flex; 356 | flex-wrap: wrap; 357 | justify-content: space-around; 358 | width: 100%; 359 | } 360 | .imgButton { 361 | width: 15%; 362 | height: 100%; 363 | border: none; 364 | padding: 0; 365 | background-color: #f9f9f9; 366 | overflow: hidden; 367 | } 368 | .imgButton img { 369 | width: 100%; 370 | height: 100%; 371 | object-fit: cover; 372 | } -------------------------------------------------------------------------------- /scripts/simulator/class/Calculator.js: -------------------------------------------------------------------------------- 1 | import { AutoEvaluationData } from "../data/ProduceExamAutoEvaluation.js"; 2 | export class Calculator { 3 | /** 4 | * 好印象と残りのターンから現在の好印象値で稼げるスコアを計算します。 5 | * @param {*} goodImp 6 | * @param {*} remainTurn 7 | * @returns 8 | */ 9 | static calcGoodImpScore (goodImp, remainTurn) { 10 | const goodImpActiveTurn = (goodImp > remainTurn) ? remainTurn : goodImp; 11 | return goodImpActiveTurn * goodImp - (goodImpActiveTurn * (goodImpActiveTurn-1) >> 1); 12 | } 13 | static calcActionEvaluation (action, status, parameter, trendVonusCoef, autoId, nowTurn) { 14 | if (autoId < 5){ 15 | let { type, args } = action; 16 | if(!args){ 17 | return 0; 18 | } 19 | const unitValue = parameter['avg'] / 100; 20 | 21 | if (type == 'status') { 22 | const statusType = args[1]; 23 | if(statusType=='スキルカード使用数追加' && status.pStatus.has('スキルカード使用数追加')){ 24 | return 0; 25 | } 26 | return AutoEvaluationData.get(status.trend,statusType,status.remainTurn-status.extraTurn,args[0],parameter[nowTurn]/100,autoId) 27 | } 28 | 29 | if (type == 'delay') { //延迟效果 30 | if (args[2]>status.remainTurn+status.turn) { 31 | return 0; 32 | } 33 | type = args[3].type; 34 | args = [args[0], args[1]]; 35 | } 36 | 37 | return AutoEvaluationData.get(status.trend,type,status.remainTurn-status.extraTurn,args[0],parameter[nowTurn]/100,autoId) 38 | }else{ 39 | let { type, args } = action; 40 | if(!args){ 41 | return 0; 42 | } 43 | 44 | if (type == 'status') { 45 | const statusType = args[1]; 46 | 47 | if (AutoEvaluationData.get_trigger_evaluation(status.trend,statusType,status.remainTurn,args[0],parameter,autoId) != 0) 48 | console.log(AutoEvaluationData.get_trigger_evaluation(status.trend,statusType,status.remainTurn,args[0],parameter,autoId)); 49 | 50 | return AutoEvaluationData.get_trigger_evaluation(status.trend,statusType,status.remainTurn,args[0],parameter,autoId) 51 | } 52 | 53 | if (AutoEvaluationData.get_trigger_evaluation(status.trend,type,status.remainTurn,args[0],parameter,autoId) != 0) 54 | console.log(AutoEvaluationData.get_trigger_evaluation(status.trend,type,status.remainTurn,args[0],parameter,autoId)); 55 | 56 | return AutoEvaluationData.get_trigger_evaluation(status.trend,type,status.remainTurn,args[0],parameter,autoId); 57 | } 58 | } 59 | 60 | static calcActualValue (effect, status, parameter) { 61 | if (effect.type == 'score') { 62 | const concentrationCoef = status.pStatus.getValue('集中'); 63 | const goodConditionCoef = status.pStatus.has('好調') ? 1.5 : 1; 64 | const badConditionCoef = status.pStatus.has('不調') ? 0.666 : 1; 65 | const greatConditionCoef = status.pStatus.has('絶好調') ? status.pStatus.getValue('好調') * 0.1 : 0; 66 | const parameterRateIncreasedCoef = 1 + status.pStatus.getValue('パラメータ上昇量増加')/100; 67 | const parameterRateCoef = (()=>{ 68 | if (!effect.delay) return parameter[status.currentTurnType] / 100; 69 | if (status.remainTurn <= effect.delay) return 0; 70 | return parameter[status.turnType.getType(status.turn+effect.delay)] / 100; 71 | })(); 72 | const optionCoef = { '集中': 1, 'score': 0 } 73 | if (effect.options) { 74 | effect.options.forEach(effectOption => { 75 | switch (effectOption.type) { 76 | case '集中' : optionCoef['集中'] = effectOption.value; break; 77 | case '使用したスキルカード数': optionCoef['score'] = effectOption.value * status.usedCardCount; break; 78 | case '好印象': optionCoef['score'] = (effectOption.value/100) * status.pStatus.getValue('好印象'); break; 79 | case 'block': optionCoef['score'] = (effectOption.value/100) * status.block; break; 80 | case 'やる気': optionCoef['score'] = (effectOption.value/100) * status.pStatus.getValue('やる気'); break; 81 | case '好調' : optionCoef['score'] = (effectOption.value/100) * status.pStatus.getValue('好調'); break; 82 | case 'consumedHp' : optionCoef['score'] = (effectOption.value/100) * status.consumedHp; break; 83 | } 84 | }); 85 | } 86 | const baseScore = effect.value ?? 0; 87 | const adjustScore = baseScore + concentrationCoef * optionCoef['集中'] + optionCoef['score']; 88 | const actualValue = 89 | Math.ceil( 90 | Math.ceil( 91 | adjustScore * (goodConditionCoef + greatConditionCoef) 92 | ) * parameterRateCoef * parameterRateIncreasedCoef * badConditionCoef 93 | ); 94 | // console.log(`base=${baseScore}, 集中=${concentrationCoef * optionCoef['集中']}, addition=${optionCoef['score']}, 好調=${goodConditionCoef}, 絶好調=${greatConditionCoef}, 上昇量=${parameterRateIncreasedCoef}, 倍率=${parameterRateCoef} => ${actualValue}`) 95 | return actualValue; 96 | } 97 | if (effect.type == 'block') { 98 | let baseValue = effect.value ?? 0; 99 | const optionCoef = { 'block': 0, '割合減少': 0, 'やる気': 1 }; 100 | if (effect.options) { 101 | effect.options.forEach(effectOption => { 102 | switch (effectOption.type) { 103 | case '使用したスキルカード数': optionCoef['block'] = effectOption.value * (status.usedCardCount-1); break; 104 | case '好印象': optionCoef['block'] = (effectOption.value/100) * status.pStatus.getValue('好印象'); break; 105 | case 'やる気' : optionCoef['やる気'] = effectOption.value; break; 106 | case '割合減少': baseValue = -Math.ceil(status.block * effectOption.value / 100); 107 | case 'consumedHp' : optionCoef['block'] = (effectOption.value/100) * status.consumedHp; break; 108 | } 109 | }); 110 | } 111 | let actualValue; 112 | if (baseValue >= 0) { 113 | actualValue = Math.ceil(baseValue + status.pStatus.getValue('やる気') * optionCoef['やる気'] + optionCoef['block']); 114 | if (status.pStatus.has('元気増加無効')) { 115 | actualValue = 0; 116 | } 117 | } else { 118 | actualValue = baseValue + optionCoef['割合減少'];// <- これいらなくね 119 | } 120 | return actualValue; 121 | } 122 | if (effect.type == 'hp' || effect.type == 'direct_hp') { 123 | let value = effect.value ?? 0; 124 | if (effect.options) { 125 | effect.options.forEach(effectOption => { 126 | switch (effectOption.type) { 127 | case 'hpPer': value += status.maxHp * effectOption.value / 100; break; 128 | } 129 | }); 130 | } 131 | value = Math.ceil(value); 132 | if (value <= 0) { 133 | const increaseHpConsumption = status.pStatus.has('消費体力増加') ? 2.0 : 1.0; 134 | const decreaseHpConsumption = status.pStatus.has('消費体力減少') ? 0.5 : 1.0; 135 | const reductionHpComsumption = status.pStatus.getValue('消費体力削減'); 136 | const increaseHpComsumption = status.pStatus.getValue('消費体力追加'); 137 | const actualValue = Math.ceil(value * increaseHpConsumption * decreaseHpConsumption) + reductionHpComsumption - increaseHpComsumption; 138 | return Math.min(0, actualValue); 139 | } 140 | return value; 141 | } 142 | if (effect.type == 'draw') { 143 | let actualValue = effect.value; 144 | if (effect.options) { 145 | effect.options.forEach(effectOption => { 146 | switch (effectOption.type) { 147 | case '手札枚数': actualValue = status.handCount-1; 148 | } 149 | }); 150 | } 151 | return actualValue; 152 | } 153 | if (effect.type == 'status') { 154 | const baseValue = effect.value ?? 0; 155 | let optionalValue = 0; 156 | if (effect.options) { 157 | effect.options.forEach(effectOption => { 158 | switch (effectOption.type) { 159 | case 'multiple': optionalValue = status.pStatus.getValue(effect.target) * (effectOption.value-1); break; 160 | case 'block': optionalValue = (effectOption.value/100) * status.block; break; 161 | case '好調' : optionalValue = (effectOption.value/100) * status.pStatus.getValue('好調'); break; 162 | } 163 | optionalValue = Math.ceil(optionalValue); 164 | }); 165 | } 166 | const actualValue = baseValue + optionalValue; 167 | return actualValue; 168 | } 169 | return effect.value; 170 | } 171 | 172 | } -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 学马仕竞技场AI模拟器 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 | 28 |
29 | 32 | 35 |
36 |
37 |
38 | 39 | 42 |
43 |
44 | 45 | 48 |
49 |
50 | 51 |
52 | 53 | 54 | 55 | 56 |
57 |
58 |
59 | 60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 |
68 |
69 |
0
70 |
71 |
72 |
73 |
0
74 |
75 |
76 |
77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
使用方法(第一次使用必看):
86 |
按照游戏中jjc配置数据进行配置,相同卡的第二张请选择「-」,无论是否可以重复,强化卡和未强化卡算两张不同的卡,卡牌位置没有影响,检查无误后运行
87 |
按照游戏内模拟记录,提供每回合的颜色和手牌信息,手动提供信息。通过判断模拟器ai和游戏内实际ai的行动对比来判断模拟器的准确性
88 | 89 |
当前尚无解决优先级的方法,当前网页仅供测试使用
90 |
项目github仓库地址,喜欢这个项目麻烦点个Star谢谢了
有建议和问题可以提issues,或者联系QQ:2750368249
91 |
92 |
93 | 94 |
95 | 99 |
100 |
101 |
102 | 103 | 104 |
105 |
106 | 107 |
108 |
109 | 110 | 115 |
116 |
117 | 118 |
119 | 120 |
121 | 124 |
125 |
126 | 127 | 128 |
129 |
130 | 131 | 132 |
133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
平均值中央值最频值
---
144 |
145 | 146 |
147 |
148 | ログ 149 |
150 | 151 | 152 | 153 | 154 | 155 | 156 |
157 |
158 |
最低値のログが表示されます
159 |
ランダムな回のログが表示されます
160 |
最大値のログが表示されます
161 |
162 |
163 |
164 |
165 | 166 | 178 | 179 | 191 | 192 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 学马仕竞技场AI模拟器 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
新版本数据均来自解包文件内容,项目改编自原网站
27 |
项目github仓库地址,喜欢这个项目麻烦点个Star谢谢了
有建议和问题可以提issues,或者联系QQ:2750368249,邮箱:kanon511@qq.com
28 |
bata版本:测试版,多测试bata版本,之后功能将逐步迁移到新项目。
29 |
最后更新时间:2024/12/10。场地一道具修复,感性妹妹新AI添加。未更新至最新版本时请刷新网页或重启浏览器。
30 | 31 |
使用穷举组合网站查找最佳卡组
32 |
33 |
34 | 35 |
36 | 39 | 42 |
43 |
44 |
45 | 46 |
47 | 50 | 53 |
54 |
55 |
56 | 57 |
58 | 59 | 60 | 61 | 62 |
63 |
64 |
65 | 66 |
67 |
68 |
69 |
70 |
71 | 72 |
73 |
74 |
75 |
0
76 |
77 |
78 |
79 |
0
80 |
81 |
82 |
83 |
84 | 85 |
86 |
87 |
88 |
89 |
90 | 91 |
92 | 93 |
94 | 99 |
100 |
101 |
102 |
103 |
104 | 运行次数: 105 |
106 | 107 | 108 |
109 | 110 |
111 |
112 |
113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 |
平均值中央值最频值
---
124 |
125 | 126 |
127 |
128 | ログ 129 |
130 | 131 | 132 | 133 | 134 | 135 | 136 |
137 |
138 |
所有模拟中最低值的记录表示
139 |
模拟中随机的一个记录表示
140 |
所有模拟中最高值的记录表示
141 |
142 |
143 |
144 |
145 | 146 | 158 | 159 | 171 | 172 | 189 | 190 | -------------------------------------------------------------------------------- /combination.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 学马仕竞技场最佳卡组模拟器 8 | 9 | 10 | 125 | 126 | 127 | 128 |
129 | 130 |
131 |
132 |
133 | 134 |
135 | 138 | 141 |
142 |
143 |
144 | 145 | 148 |
149 |
150 | 151 | 154 |
155 |
156 | 157 |
158 | 159 | 160 | 161 | 162 |
163 |
164 |
165 | 166 |
167 |
168 |
169 |
170 |
171 | 172 |
173 | 174 |
175 |
176 | 177 |
178 |
使用说明:按照卡组容量大小组合卡组,例如1蓝+4金+卡组和3蓝+2彩+卡组(即4蓝+4金+2彩+卡组)请填写2 4 4 4 4 2 2 2 5 5。
右侧为各个容量你想尝试的卡的可能,运行时,穷举所有可能的组合,并计算出最优的卡组。
179 |
由于使用穷举法,运行时间可能会很长,单次运行请不要放置过多的卡,尽可能通过人工筛选,去掉一些不符合条件的卡,或添加一些符合条件必选的卡。
180 |
项目github仓库地址,项目如果对你有用麻烦点个Star谢谢了
有建议和问题可以提issues,或者联系QQ:2750368249
181 |
182 |
183 | 184 |
185 | 189 |
190 |
191 |
192 |
193 |
194 | 运行次数: 195 |
196 | 197 | 198 |
199 | 200 |
201 |
202 | 203 |
204 | 205 |
206 |
207 |
208 | 211 | 212 | 213 |
214 |
215 |
216 | 219 | 220 | 221 |
222 |
223 |
224 | 227 | 228 | 229 |
230 |
231 |
232 | 235 | 236 | 237 |
238 |
239 |
240 | 243 | 244 | 245 |
246 |
247 |
248 | 251 |
252 | 253 |
254 | 255 |
256 |
257 | 258 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /gvg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 学马仕竞技场AI模拟器 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
把角色卡和主卡角色卡相同,其他留空即可实现未解锁卡组
27 |
新版本数据均来自解包文件内容,项目改编自原网站
28 |
项目github仓库地址,喜欢这个项目麻烦点个Star谢谢了
有建议和问题可以提issues,或者联系QQ:2750368249,邮箱:kanon511@qq.com
29 |
最后更新时间:2024/10/28。B/D场开局属性修复,公会战效果判断条件修复。未更新至最新版本时请刷新网页或重启浏览器。
30 |
使用测试用网站分析AI准确率;使用穷举组合网站查找最佳卡组
31 |
32 |
33 | 34 |
35 | 38 | 41 | 44 |
45 |
46 |
47 | 48 | 51 | 52 |
53 | 56 | 59 |
60 |
61 | 64 | 67 |
68 |
69 |
70 | 71 |
72 | 73 | 74 | 75 | 76 |
77 |
78 |
79 | 80 |
81 |
82 |
83 |
84 |
85 | 86 |
87 |
88 |
89 |
0
90 |
91 |
92 |
93 |
0
94 |
95 |
96 |
97 |
0
98 |
99 |
100 |
101 |
0
102 |
103 |
104 |
105 |
0
106 |
107 |
108 |
109 |
110 | 111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | 120 |
121 | 122 |
123 | 128 |
129 |
130 |
131 |
132 |
133 | 运行次数: 134 |
135 | 136 | 137 |
138 | 139 |
140 |
141 |
142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
平均值中央值最频值
---
153 |
154 | 155 |
156 |
157 | ログ 158 |
159 | 160 | 161 | 162 | 163 | 164 | 165 |
166 |
167 |
所有模拟中最低值的记录表示
168 |
模拟中随机的一个记录表示
169 |
所有模拟中最高值的记录表示
170 |
171 |
172 |
173 |
174 | 175 | 187 | 188 | 200 | 201 | 218 | 219 | -------------------------------------------------------------------------------- /scripts/simulator/data/contestData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * IDの付け方 3 | * 00_00_00 4 | * 年_月_日 5 | * 6 | * 実装: 7 | */ 8 | const contestData = [ 9 | { 10 | id: 240516, 11 | name: '第1期コンテスト', 12 | period: '2024/05/16 - 05/31', 13 | criteria: { 'vocal': 40, 'dance': 27, 'visual': 33 }, 14 | rank: {'vocal': 1, 'dance': 3, 'visual': 2}, 15 | stages: [ 16 | { 17 | name: 'ステージ1', 18 | turn: 12, 19 | stageEffects: [], 20 | stagePItemIds: [4240511], 21 | plan: 'free', 22 | turnTypes: [5, 3, 4], 23 | firstTurn: {'vocal': 100, 'dance': 0, 'visual': 0}, 24 | }, 25 | { 26 | name: 'ステージ2', 27 | turn: 8, 28 | stageEffects: [], 29 | stagePItemIds: [4240512], 30 | plan: 'sense', 31 | turnTypes: [4, 2, 2], 32 | firstTurn: {'vocal': 100, 'dance': 0, 'visual': 0}, 33 | }, 34 | { 35 | name: 'ステージ3', 36 | turn: 8, 37 | stageEffects: [], 38 | stagePItemIds: [4240513], 39 | plan: 'logic', 40 | turnTypes: [4, 2, 2], 41 | firstTurn: {'vocal': 100, 'dance': 0, 'visual': 0}, 42 | }, 43 | ], 44 | }, 45 | { 46 | id: 240602, 47 | name: '第2期コンテスト', 48 | period: '2024/06/02 - 06/16', 49 | criteria: { 'vocal': 33, 'dance': 40, 'visual': 27 }, 50 | rank: {'vocal': 2, 'dance': 1, 'visual': 3}, 51 | stages: [ 52 | { 53 | name: 'ステージ1', 54 | turn: 12, 55 | stageEffects: [], 56 | stagePItemIds: [4240511], 57 | plan: 'free', 58 | turnTypes: [4, 5, 3], 59 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 60 | }, 61 | { 62 | name: 'ステージ2', 63 | turn: 12, 64 | stageEffects: [], 65 | stagePItemIds: [4240612], 66 | plan: 'sense', 67 | turnTypes: [4, 5, 3], 68 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 69 | }, 70 | { 71 | name: 'ステージ3', 72 | turn: 10, 73 | stageEffects: [], 74 | stagePItemIds: [4240613], 75 | plan: 'logic', 76 | turnTypes: [3, 5, 2], 77 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 78 | }, 79 | ], 80 | }, 81 | { 82 | id: 240617, 83 | name: '第3期コンテスト', 84 | period: '2024/06/17 - 07/01', 85 | criteria: { 'vocal': 27, 'dance': 33, 'visual': 40 }, 86 | rank: {'vocal': 3, 'dance': 2, 'visual': 1}, 87 | stages: [ 88 | { 89 | name: 'ステージ1', 90 | turn: 10, 91 | stageEffects: [], 92 | stagePItemIds: [4240621], 93 | plan: 'sense', 94 | turnTypes: [2, 3, 5], 95 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 96 | }, 97 | { 98 | name: 'ステージ2', 99 | turn: 8, 100 | stageEffects: [], 101 | stagePItemIds: [4240622], 102 | plan: 'logic', 103 | turnTypes: [2, 2, 4], 104 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 105 | }, 106 | { 107 | name: 'ステージ3', 108 | turn: 8, 109 | stageEffects: [], 110 | stagePItemIds: [4240623], 111 | plan: 'logic', 112 | turnTypes: [2, 2, 4], 113 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 114 | }, 115 | ], 116 | }, 117 | { 118 | id: 240703, 119 | name: '第4期コンテスト', 120 | period: '2024/07/03 - 07/18', 121 | criteria: { 'vocal': 15, 'dance': 45, 'visual': 40 }, 122 | rank: {'vocal': 3, 'dance': 1, 'visual': 2}, 123 | stages: [ 124 | { 125 | name: 'ステージ1', 126 | turn: 10, 127 | stageEffects: [], 128 | stagePItemIds: [4240711], 129 | plan: 'sense', 130 | turnTypes: [2, 5, 3], 131 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 132 | }, 133 | { 134 | name: 'ステージ2', 135 | turn: 10, 136 | stageEffects: [], 137 | stagePItemIds: [4240612], 138 | plan: 'sense', 139 | turnTypes: [2, 5, 3], 140 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 141 | }, 142 | { 143 | name: 'ステージ3', 144 | turn: 8, 145 | stageEffects: [], 146 | stagePItemIds: [4240713], 147 | plan: 'logic', 148 | turnTypes: [2, 4, 2], 149 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 150 | }, 151 | ], 152 | }, 153 | { 154 | id: 240719, 155 | name: '第5期コンテスト', 156 | period: '2024/07/19 - 08/03', 157 | criteria: { 'vocal': 35, 'dance': 30, 'visual': 35 }, 158 | rank: {'vocal': 1, 'dance': 3, 'visual': 2}, 159 | stages: [ 160 | { 161 | name: 'ステージ1', 162 | turn: 10, 163 | stageEffects: [], 164 | stagePItemIds: [4240711], 165 | plan: 'sense', 166 | turnTypes: [4, 3, 3], 167 | firstTurn: {'vocal': 0, 'dance': 80, 'visual': 20}, 168 | }, 169 | { 170 | name: 'ステージ2', 171 | turn: 12, 172 | stageEffects: [], 173 | stagePItemIds: [4240722], 174 | plan: 'sense', 175 | turnTypes: [5, 3, 4], 176 | firstTurn: {'vocal': 0, 'dance': 80, 'visual': 20}, 177 | }, 178 | { 179 | name: 'ステージ3', 180 | turn: 12, 181 | stageEffects: [], 182 | stagePItemIds: [4240723], 183 | plan: 'logic', 184 | turnTypes: [5, 3, 4], 185 | firstTurn: {'vocal': 0, 'dance': 80, 'visual': 20}, 186 | }, 187 | ], 188 | }, 189 | { 190 | id: 240804, 191 | name: '第6期コンテスト', 192 | period: '2024/08/04 - 08/20', 193 | criteria: { 'vocal': 10, 'dance': 45, 'visual': 45 }, 194 | rank: {'vocal': 3, 'dance': 1, 'visual': 2}, 195 | stages: [ 196 | { 197 | name: 'ステージ1', 198 | turn: 8, 199 | stageEffects: [], 200 | stagePItemIds: [4240811], 201 | plan: 'free', 202 | turnTypes: [1, 4, 3], 203 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 204 | }, 205 | { 206 | name: 'ステージ2', 207 | turn: 10, 208 | stageEffects: [], 209 | stagePItemIds: [4240812], 210 | plan: 'sense', 211 | turnTypes: [2, 5, 3], 212 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 213 | }, 214 | { 215 | name: 'ステージ3', 216 | turn: 12, 217 | stageEffects: [], 218 | stagePItemIds: [4240813], 219 | plan: 'logic', 220 | turnTypes: [2, 6, 4], 221 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 222 | }, 223 | ], 224 | }, 225 | { 226 | id: 240822, 227 | name: '第7期コンテスト', 228 | period: '2024/08/22 - ----', 229 | criteria: { 'vocal': 15, 'dance': 50, 'visual': 35 }, 230 | rank: {'vocal': 3, 'dance': 1, 'visual': 2}, 231 | stages: [ 232 | { 233 | name: 'ステージ1', 234 | turn: 12, 235 | stageEffects: [], 236 | stagePItemIds: [4240821], 237 | plan: 'free', 238 | turnTypes: [2, 6, 4], 239 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 240 | }, 241 | { 242 | name: 'ステージ2', 243 | turn: 12, 244 | stageEffects: [], 245 | stagePItemIds: [4240822], 246 | plan: 'sense', 247 | turnTypes: [2, 6, 4], 248 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 249 | }, 250 | { 251 | name: 'ステージ3', 252 | turn: 12, 253 | stageEffects: [], 254 | stagePItemIds: [4240813], 255 | plan: 'logic', 256 | turnTypes: [2, 6, 4], 257 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 258 | }, 259 | ], 260 | }, 261 | { 262 | id: 240906, 263 | name: '第8期コンテスト', 264 | period: '2024/08/22 - ----', 265 | criteria: { 'vocal': 15, 'dance': 40, 'visual': 45 }, 266 | rank: {'vocal': 3, 'dance': 2, 'visual': 1}, 267 | stages: [ 268 | { 269 | name: 'ステージ1', 270 | turn: 8, 271 | stageEffects: [], 272 | stagePItemIds: [4240911], 273 | plan: 'free', 274 | turnTypes: [2, 2, 4], 275 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 276 | }, 277 | { 278 | name: 'ステージ2', 279 | turn: 12, 280 | stageEffects: [], 281 | stagePItemIds: [4240912], 282 | plan: 'sense', 283 | turnTypes: [2, 4, 6], 284 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 285 | }, 286 | { 287 | name: 'ステージ3', 288 | turn: 8, 289 | stageEffects: [], 290 | stagePItemIds: [4240913], 291 | plan: 'logic', 292 | turnTypes: [2, 2, 4], 293 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 294 | }, 295 | ], 296 | }, 297 | { 298 | id: 240923, 299 | name: '第9期コンテスト', 300 | period: '2024/08/22 - ----', 301 | criteria: { 'vocal': 40, 'dance': 30, 'visual': 30 }, 302 | rank: {'vocal': 1, 'dance': 2, 'visual': 2}, 303 | stages: [ 304 | { 305 | name: 'ステージ1', 306 | turn: 12, 307 | stageEffects: [], 308 | stagePItemIds: [4240921], 309 | plan: 'free', 310 | turnTypes: [5, 3, 4], 311 | firstTurn: {'vocal': 85, 'dance': 15, 'visual': 0}, 312 | }, 313 | { 314 | name: 'ステージ2', 315 | turn: 12, 316 | stageEffects: [], 317 | stagePItemIds: [4240922], 318 | plan: 'logic', 319 | turnTypes: [5, 3, 4], 320 | firstTurn: {'vocal': 85, 'dance': 15, 'visual': 0}, 321 | }, 322 | { 323 | name: 'ステージ3', 324 | turn: 8, 325 | stageEffects: [], 326 | stagePItemIds: [4240923], 327 | plan: 'logic', 328 | turnTypes: [3, 3, 2], 329 | firstTurn: {'vocal': 82, 'dance': 18, 'visual': 0}, 330 | }, 331 | ], 332 | }, 333 | { 334 | id: 241008, 335 | name: '第10期コンテスト', 336 | period: '2024/08/22 - ----', 337 | criteria: { 'vocal': 50, 'dance': 40, 'visual': 10 }, 338 | rank: {'vocal': 1, 'dance': 2, 'visual': 3}, 339 | stages: [ 340 | { 341 | name: 'ステージ1', 342 | turn: 10, 343 | stageEffects: [], 344 | stagePItemIds: [4241011], 345 | plan: 'free', 346 | turnTypes: [5, 3, 2], 347 | firstTurn: {'vocal': 100, 'dance': 0, 'visual': 0}, 348 | }, 349 | { 350 | name: 'ステージ2', 351 | turn: 12, 352 | stageEffects: [], 353 | stagePItemIds: [4241012], 354 | plan: 'sense', 355 | turnTypes: [6, 4, 2], 356 | firstTurn: {'vocal': 100, 'dance': 0, 'visual': 0}, 357 | }, 358 | { 359 | name: 'ステージ3', 360 | turn: 8, 361 | stageEffects: [], 362 | stagePItemIds: [4241013], 363 | plan: 'logic', 364 | turnTypes: [4, 3, 1], 365 | firstTurn: {'vocal': 100, 'dance': 0, 'visual': 0}, 366 | }, 367 | ], 368 | }, 369 | { 370 | id: 241023, 371 | name: '第11期コンテスト', 372 | period: '2024/08/22 - ----', 373 | criteria: { 'vocal': 15, 'dance': 35, 'visual': 50 }, 374 | rank: {'vocal': 3, 'dance': 2, 'visual': 1}, 375 | stages: [ 376 | { 377 | name: 'ステージ1', 378 | turn: 10, 379 | stageEffects: [], 380 | stagePItemIds: [4241021], 381 | plan: 'free', 382 | turnTypes: [2, 3, 5], 383 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 384 | }, 385 | { 386 | name: 'ステージ2', 387 | turn: 10, 388 | stageEffects: [], 389 | stagePItemIds: [4241022], 390 | plan: 'sense', 391 | turnTypes: [2, 3, 5], 392 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 393 | }, 394 | { 395 | name: 'ステージ3', 396 | turn: 12, 397 | stageEffects: [], 398 | stagePItemIds: [4240813], 399 | plan: 'logic', 400 | turnTypes: [2, 4, 6], 401 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 402 | }, 403 | ], 404 | }, 405 | { 406 | id: 241108, 407 | name: '第12期コンテスト', 408 | period: '2024/08/22 - ----', 409 | criteria: { 'vocal': 35, 'dance': 15, 'visual': 50 }, 410 | rank: {'vocal': 2, 'dance': 3, 'visual': 1}, 411 | stages: [ 412 | { 413 | name: 'ステージ1', 414 | turn: 6, 415 | stageEffects: [], 416 | stagePItemIds: [4241111], 417 | plan: 'free', 418 | turnTypes: [2, 1, 3], 419 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 420 | }, 421 | { 422 | name: 'ステージ2', 423 | turn: 8, 424 | stageEffects: [], 425 | stagePItemIds: [4241112], 426 | plan: 'free', 427 | turnTypes: [2, 2, 4], 428 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 429 | }, 430 | { 431 | name: 'ステージ3', 432 | turn: 10, 433 | stageEffects: [], 434 | stagePItemIds: [4241022], 435 | plan: 'sense', 436 | turnTypes: [3, 2, 5], 437 | firstTurn: {'vocal': 0, 'dance': 0, 'visual': 100}, 438 | }, 439 | ], 440 | }, 441 | { 442 | id: 241124, 443 | name: '第13期', 444 | period: '2024/11/24 - ----', 445 | criteria: { 'vocal': 35, 'dance': 35, 'visual': 30 }, 446 | rank: {'vocal': 1, 'dance': 2, 'visual': 3}, 447 | stages: [ 448 | { 449 | name: 'ステージ1', 450 | turn: 6, 451 | stageEffects: [], 452 | stagePItemIds: [4241111], 453 | plan: 'free', 454 | turnTypes: [3, 2, 1], 455 | firstTurn: {'vocal': 100, 'dance': 0, 'visual': 0}, 456 | }, 457 | { 458 | name: 'ステージ2', 459 | turn: 12, 460 | stageEffects: [], 461 | stagePItemIds: [4241122], 462 | plan: 'sense', 463 | turnTypes: [5, 4, 3], 464 | firstTurn: {'vocal': 88, 'dance': 12, 'visual': 0}, 465 | }, 466 | { 467 | name: 'ステージ3', 468 | turn: 12, 469 | stageEffects: [], 470 | stagePItemIds: [4241123], 471 | plan: 'logic', 472 | turnTypes: [5, 4, 3], 473 | firstTurn: {'vocal': 88, 'dance': 12, 'visual': 0}, 474 | }, 475 | ], 476 | }, 477 | { 478 | id: 241210, 479 | name: '第14期', 480 | period: '2024/11/24 - ----', 481 | criteria: { 'vocal': 15, 'dance': 45, 'visual': 40 }, 482 | rank: {'vocal': 3, 'dance': 1, 'visual': 2}, 483 | stages: [ 484 | { 485 | name: 'ステージ1', 486 | turn: 8, 487 | stageEffects: [], 488 | stagePItemIds: [4241211], 489 | plan: 'free', 490 | turnTypes: [2, 4, 2], 491 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 492 | }, 493 | { 494 | name: 'ステージ2', 495 | turn: 12, 496 | stageEffects: [], 497 | stagePItemIds: [4241122], 498 | plan: 'sense', 499 | turnTypes: [2, 6, 4], 500 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 501 | }, 502 | { 503 | name: 'ステージ3', 504 | turn: 12, 505 | stageEffects: [], 506 | stagePItemIds: [4241213], 507 | plan: 'logic', 508 | turnTypes: [2, 6, 4], 509 | firstTurn: {'vocal': 0, 'dance': 100, 'visual': 0}, 510 | }, 511 | ], 512 | }, 513 | ]; 514 | 515 | export class ContestData { 516 | 517 | // property 518 | static #contestData = contestData; 519 | static #index = Object.fromEntries(this.#contestData.map((item, i) => [item.id, i])); 520 | 521 | // method 522 | 523 | /** 524 | * IDと一致するコンテストオブジェクトを返します 525 | * @param {Number} id - コンテストID 526 | * @returns {Object} コンテストオブジェクト 527 | */ 528 | static getById (id) { 529 | if (!(id in this.#index)) { 530 | throw new Error('idと一致するコンテストがありません。'); 531 | } 532 | return this.#contestData[this.#index[id]]; 533 | } 534 | 535 | static has (id) { 536 | return id in this.#index; 537 | } 538 | 539 | /** 540 | * コンテストオブジェクトを返します※非推奨 541 | * @returns {Array} コンテストオブジェクトリスト 542 | */ 543 | static getAll () { 544 | return this.#contestData; 545 | } 546 | 547 | } -------------------------------------------------------------------------------- /scripts/simulator/data/pIdolData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * IDの付け方 3 | * 0_00_00 4 | * レアリティ:R1, SR2, SSR3 5 | * キャラ  :咲季01, 手毬02, ことね03, 麻央04, リーリヤ05, 千奈06, 清夏07, 広08, 莉波09, 佑芽10 6 | * 固有番号 :2桁番号 7 | * 8 | * 実装:7/22まで 9 | */ 10 | 11 | const pIdolData = [ 12 | { 13 | id: 10101, 14 | rarity: 'R', 15 | episode_name: '学園生活', 16 | character_id: 101, 17 | name: '花海咲季', 18 | plan: 'sense', 19 | trend: '好調', 20 | unique_skillCard_id: 2201010, 21 | unique_pItem_id: 2101010, 22 | }, 23 | { 24 | id: 10102, 25 | rarity: 'R', 26 | episode_name: '初声', 27 | character_id: 101, 28 | name: '花海咲季', 29 | plan: 'logic', 30 | trend: '好印象', 31 | unique_skillCard_id: 2201020, 32 | unique_pItem_id: 2101020, 33 | }, 34 | { 35 | id: 20101, 36 | rarity: 'SR', 37 | episode_name: 'わたしが一番!', 38 | character_id: 101, 39 | name: '花海咲季', 40 | plan: 'sense', 41 | trend: '好調', 42 | unique_skillCard_id: 3201010, 43 | unique_pItem_id: 2201010, 44 | }, 45 | { 46 | id: 30101, 47 | rarity: 'SSR', 48 | episode_name: 'Fighting My Way', 49 | character_id: 101, 50 | name: '花海咲季', 51 | plan: 'sense', 52 | trend: '好調', 53 | unique_skillCard_id: 4201010, 54 | unique_pItem_id: 2301010, 55 | }, 56 | { 57 | id: 30102, 58 | rarity: 'SSR', 59 | episode_name: 'Boom Boom Pow', 60 | character_id: 101, 61 | name: '花海咲季', 62 | plan: 'logic', 63 | trend: '好印象', 64 | unique_skillCard_id: 4201020, 65 | unique_pItem_id: 2301020, 66 | }, 67 | { 68 | id: 30103, 69 | rarity: 'SSR', 70 | episode_name: '冠菊', 71 | character_id: 101, 72 | name: '花海咲季', 73 | plan: 'logic', 74 | trend: '好印象', 75 | unique_skillCard_id: 4201030, 76 | unique_pItem_id: 2301030, 77 | }, 78 | { 79 | id: 30104, 80 | rarity: 'SSR', 81 | episode_name: '古今東西ちょちょいのちょい', 82 | character_id: 101, 83 | name: '花海咲季', 84 | plan: 'sense', 85 | trend: '集中', 86 | unique_skillCard_id: 4201040, 87 | unique_pItem_id: 2301040, 88 | }, 89 | { 90 | id: 10201, 91 | rarity: 'R', 92 | episode_name: '学園生活', 93 | character_id: 102, 94 | name: '月村手毬', 95 | plan: 'sense', 96 | trend: '集中', 97 | unique_skillCard_id: 2202010, 98 | unique_pItem_id: 2102010, 99 | }, 100 | { 101 | id: 10202, 102 | rarity: 'R', 103 | episode_name: '初声', 104 | character_id: 102, 105 | name: '月村手毬', 106 | plan: 'logic', 107 | trend: '好印象', 108 | unique_skillCard_id: 2202020, 109 | unique_pItem_id: 2102020, 110 | }, 111 | { 112 | id: 20201, 113 | rarity: 'SR', 114 | episode_name: '一匹狼', 115 | character_id: 102, 116 | name: '月村手毬', 117 | plan: 'sense', 118 | trend: '集中', 119 | unique_skillCard_id: 3202010, 120 | unique_pItem_id: 2202010, 121 | }, 122 | { 123 | id: 30201, 124 | rarity: 'SSR', 125 | episode_name: 'Luna say maybe', 126 | character_id: 102, 127 | name: '月村手毬', 128 | plan: 'sense', 129 | trend: '集中', 130 | unique_skillCard_id: 4202010, 131 | unique_pItem_id: 2302010, 132 | }, 133 | { 134 | id: 30202, 135 | rarity: 'SSR', 136 | episode_name: 'アイヴイ', 137 | character_id: 102, 138 | name: '月村手毬', 139 | plan: 'logic', 140 | trend: '好印象', 141 | unique_skillCard_id: 4202020, 142 | unique_pItem_id: 2302020, 143 | }, 144 | { 145 | id: 30203, 146 | rarity: 'SSR', 147 | episode_name: '仮装狂騒曲', 148 | character_id: 102, 149 | name: '月村手毬', 150 | plan: 'logic', 151 | trend: 'やる気', 152 | unique_skillCard_id: 4202030, 153 | unique_pItem_id: 2302030, 154 | }, 155 | { 156 | id: 10301, 157 | rarity: 'R', 158 | episode_name: '学園生活', 159 | character_id: 103, 160 | name: '藤田ことね', 161 | plan: 'logic', 162 | trend: '好印象', 163 | unique_skillCard_id: 2203010, 164 | unique_pItem_id: 2103010, 165 | }, 166 | { 167 | id: 10302, 168 | rarity: 'R', 169 | episode_name: '初声', 170 | character_id: 103, 171 | name: '藤田ことね', 172 | plan: 'sense', 173 | trend: '好調', 174 | unique_skillCard_id: 2203020, 175 | unique_pItem_id: 2103020, 176 | }, 177 | { 178 | id: 20301, 179 | rarity: 'SR', 180 | episode_name: 'カワイイ♡はじめました', 181 | character_id: 103, 182 | name: '藤田ことね', 183 | plan: 'logic', 184 | trend: '好印象', 185 | unique_skillCard_id: 3203010, 186 | unique_pItem_id: 2203010, 187 | }, 188 | { 189 | id: 30301, 190 | rarity: 'SSR', 191 | episode_name: '世界一可愛い私', 192 | character_id: 103, 193 | name: '藤田ことね', 194 | plan: 'logic', 195 | trend: '好印象', 196 | unique_skillCard_id: 4203010, 197 | unique_pItem_id: 2303010, 198 | }, 199 | { 200 | id: 30302, 201 | rarity: 'SSR', 202 | episode_name: 'Yellow Big Bang!', 203 | character_id: 103, 204 | name: '藤田ことね', 205 | plan: 'sense', 206 | trend: '好調', 207 | unique_skillCard_id: 4203020, 208 | unique_pItem_id: 2303020, 209 | }, 210 | { 211 | id: 30303, 212 | rarity: 'SSR', 213 | episode_name: '冠菊', 214 | character_id: 103, 215 | name: '藤田ことね', 216 | plan: 'logic', 217 | trend: 'やる気', 218 | unique_skillCard_id: 4203030, 219 | unique_pItem_id: 2303030, 220 | }, 221 | { 222 | id: 30304, 223 | rarity: 'SSR', 224 | episode_name: 'White Night! White Wish!', 225 | character_id: 103, 226 | name: '藤田ことね', 227 | plan: 'logic', 228 | trend: '好印象', 229 | unique_skillCard_id: 4203040, 230 | unique_pItem_id: 2303040, 231 | }, 232 | { 233 | id: 10401, 234 | rarity: 'R', 235 | episode_name: '学園生活', 236 | character_id: 104, 237 | name: '有村麻央', 238 | plan: 'sense', 239 | trend: '好調', 240 | unique_skillCard_id: 2204010, 241 | unique_pItem_id: 2104010, 242 | }, 243 | { 244 | id: 10402, 245 | rarity: 'R', 246 | episode_name: '初恋', 247 | character_id: 104, 248 | name: '有村麻央', 249 | plan: 'logic', 250 | trend: '好印象', 251 | unique_skillCard_id: 2204020, 252 | unique_pItem_id: 2104020, 253 | }, 254 | { 255 | id: 20401, 256 | rarity: 'SR', 257 | episode_name: 'はじまりはカッコよく', 258 | character_id: 104, 259 | name: '有村麻央', 260 | plan: 'sense', 261 | trend: '好調', 262 | unique_skillCard_id: 3204010, 263 | unique_pItem_id: 2204010, 264 | }, 265 | { 266 | id: 30401, 267 | rarity: 'SSR', 268 | episode_name: 'Fluorite', 269 | character_id: 104, 270 | name: '有村麻央', 271 | plan: 'sense', 272 | trend: '好調', 273 | unique_skillCard_id: 4204010, 274 | unique_pItem_id: 2304010, 275 | }, 276 | { 277 | id: 30402, 278 | rarity: 'SSR', 279 | episode_name: 'キミとセミブルー', 280 | character_id: 104, 281 | name: '有村麻央', 282 | plan: 'sense', 283 | trend: '集中', 284 | unique_skillCard_id: 4204020, 285 | unique_pItem_id: 2304020, 286 | }, 287 | { 288 | id: 30403, 289 | rarity: 'SSR', 290 | episode_name: 'Feel Jewel Dream', 291 | character_id: 104, 292 | name: '有村麻央', 293 | plan: 'logic', 294 | trend: '好印象', 295 | unique_skillCard_id: 4204030, 296 | unique_pItem_id: 2304030, 297 | }, 298 | { 299 | id: 10501, 300 | rarity: 'R', 301 | episode_name: '学園生活', 302 | character_id: 105, 303 | name: '葛城リーリヤ', 304 | plan: 'logic', 305 | trend: '好印象', 306 | unique_skillCard_id: 2205010, 307 | unique_pItem_id: 2105010, 308 | }, 309 | { 310 | id: 10502, 311 | rarity: 'R', 312 | episode_name: '初心', 313 | character_id: 105, 314 | name: '葛城リーリヤ', 315 | plan: 'sense', 316 | trend: '好調', 317 | unique_skillCard_id: 2205020, 318 | unique_pItem_id: 2105020, 319 | }, 320 | { 321 | id: 20501, 322 | rarity: 'SR', 323 | episode_name: '一つ踏み出した先に', 324 | character_id: 105, 325 | name: '葛城リーリヤ', 326 | plan: 'logic', 327 | trend: '好印象', 328 | unique_skillCard_id: 3205010, 329 | unique_pItem_id: 2205010, 330 | }, 331 | { 332 | id: 30501, 333 | rarity: 'SSR', 334 | episode_name: '白線', 335 | character_id: 105, 336 | name: '葛城リーリヤ', 337 | plan: 'logic', 338 | trend: '好印象', 339 | unique_skillCard_id: 4205010, 340 | unique_pItem_id: 2305010, 341 | }, 342 | { 343 | id: 30502, 344 | rarity: 'SSR', 345 | episode_name: '冠菊', 346 | character_id: 105, 347 | name: '葛城リーリヤ', 348 | plan: 'sense', 349 | trend: '好調', 350 | unique_skillCard_id: 4205020, 351 | unique_pItem_id: 2305020, 352 | }, 353 | { 354 | id: 30503, 355 | rarity: 'SSR', 356 | episode_name: 'White Night! White Wish!', 357 | character_id: 105, 358 | name: '葛城リーリヤ', 359 | plan: 'logic', 360 | trend: 'やる気', 361 | unique_skillCard_id: 4205030, 362 | unique_pItem_id: 2305030, 363 | }, 364 | { 365 | id: 10601, 366 | rarity: 'R', 367 | episode_name: '学園生活', 368 | character_id: 106, 369 | name: '倉本千奈', 370 | plan: 'logic', 371 | trend: 'やる気', 372 | unique_skillCard_id: 2206010, 373 | unique_pItem_id: 2106010, 374 | }, 375 | { 376 | id: 10602, 377 | rarity: 'R', 378 | episode_name: '初心', 379 | character_id: 106, 380 | name: '倉本千奈', 381 | plan: 'sense', 382 | trend: '好調', 383 | unique_skillCard_id: 2206020, 384 | unique_pItem_id: 2106020, 385 | }, 386 | { 387 | id: 20601, 388 | rarity: 'SR', 389 | episode_name: '胸を張って一歩ずつ', 390 | character_id: 106, 391 | name: '倉本千奈', 392 | plan: 'logic', 393 | trend: 'やる気', 394 | unique_skillCard_id: 3206010, 395 | unique_pItem_id: 2206010, 396 | }, 397 | { 398 | id: 30601, 399 | rarity: 'SSR', 400 | episode_name: 'Wonder Scale', 401 | character_id: 106, 402 | name: '倉本千奈', 403 | plan: 'logic', 404 | trend: 'やる気', 405 | unique_skillCard_id: 4206010, 406 | unique_pItem_id: 2306010, 407 | }, 408 | { 409 | id: 30602, 410 | rarity: 'SSR', 411 | episode_name: '日々、発見的ステップ!', 412 | character_id: 106, 413 | name: '倉本千奈', 414 | plan: 'sense', 415 | trend: '好調', 416 | unique_skillCard_id: 4206020, 417 | unique_pItem_id: 2306020, 418 | }, 419 | { 420 | id: 30603, 421 | rarity: 'SSR', 422 | episode_name: 'ようこそ初星温泉', 423 | character_id: 106, 424 | name: '倉本千奈', 425 | plan: 'sense', 426 | trend: '集中', 427 | unique_skillCard_id: 4206030, 428 | unique_pItem_id: 2306030, 429 | }, 430 | { 431 | id: 30604, 432 | rarity: 'SSR', 433 | episode_name: '仮装狂騒曲', 434 | character_id: 106, 435 | name: '倉本千奈', 436 | plan: 'sense', 437 | trend: '集中', 438 | unique_skillCard_id: 4206040, 439 | unique_pItem_id: 2306040, 440 | }, 441 | { 442 | id: 10701, 443 | rarity: 'R', 444 | episode_name: '学園生活', 445 | character_id: 107, 446 | name: '紫雲清夏', 447 | plan: 'sense', 448 | trend: '集中', 449 | unique_skillCard_id: 2207010, 450 | unique_pItem_id: 2107010, 451 | }, 452 | { 453 | id: 10702, 454 | rarity: 'R', 455 | episode_name: '初恋', 456 | character_id: 107, 457 | name: '紫雲清夏', 458 | plan: 'logic', 459 | trend: 'やる気', 460 | unique_skillCard_id: 2207020, 461 | unique_pItem_id: 2107020, 462 | }, 463 | { 464 | id: 20701, 465 | rarity: 'SR', 466 | episode_name: '夢へのリスタート', 467 | character_id: 107, 468 | name: '紫雲清夏', 469 | plan: 'sense', 470 | trend: '集中', 471 | unique_skillCard_id: 3207010, 472 | unique_pItem_id: 2207010, 473 | }, 474 | { 475 | id: 30701, 476 | rarity: 'SSR', 477 | episode_name: 'Tame-Lie-One-Step', 478 | character_id: 107, 479 | name: '紫雲清夏', 480 | plan: 'sense', 481 | trend: '集中', 482 | unique_skillCard_id: 4207010, 483 | unique_pItem_id: 2307010, 484 | }, 485 | { 486 | id: 30702, 487 | rarity: 'SSR', 488 | episode_name: 'キミとセミブルー', 489 | character_id: 107, 490 | name: '紫雲清夏', 491 | plan: 'logic', 492 | trend: 'やる気', 493 | unique_skillCard_id: 4207020, 494 | unique_pItem_id: 2307020, 495 | }, 496 | { 497 | id: 10801, 498 | rarity: 'R', 499 | episode_name: '学園生活', 500 | character_id: 108, 501 | name: '篠澤広', 502 | plan: 'logic', 503 | trend: 'やる気', 504 | unique_skillCard_id: 2208010, 505 | unique_pItem_id: 2108010, 506 | }, 507 | { 508 | id: 10802, 509 | rarity: 'R', 510 | episode_name: '初恋', 511 | character_id: 108, 512 | name: '篠澤広', 513 | plan: 'sense', 514 | trend: '集中', 515 | unique_skillCard_id: 2208020, 516 | unique_pItem_id: 2108020, 517 | }, 518 | { 519 | id: 20801, 520 | rarity: 'SR', 521 | episode_name: '一番向いてないこと', 522 | character_id: 108, 523 | name: '篠澤広', 524 | plan: 'logic', 525 | trend: 'やる気', 526 | unique_skillCard_id: 3208010, 527 | unique_pItem_id: 2208010, 528 | }, 529 | { 530 | id: 30801, 531 | rarity: 'SSR', 532 | episode_name: '光景', 533 | character_id: 108, 534 | name: '篠澤広', 535 | plan: 'logic', 536 | trend: 'やる気', 537 | unique_skillCard_id: 4208010, 538 | unique_pItem_id: 2308010, 539 | }, 540 | { 541 | id: 30802, 542 | rarity: 'SSR', 543 | episode_name: 'コントラスト', 544 | character_id: 108, 545 | name: '篠澤広', 546 | plan: 'sense', 547 | trend: '集中', 548 | unique_skillCard_id: 4208020, 549 | unique_pItem_id: 2308020, 550 | }, 551 | { 552 | id: 30803, 553 | rarity: 'SSR', 554 | episode_name: '仮装狂騒曲', 555 | character_id: 108, 556 | name: '篠澤広', 557 | plan: 'sense', 558 | trend: '好調', 559 | unique_skillCard_id: 4208030, 560 | unique_pItem_id: 2308030, 561 | }, 562 | { 563 | id: 10901, 564 | rarity: 'R', 565 | episode_name: '学園生活', 566 | character_id: 109, 567 | name: '姫崎莉波', 568 | plan: 'sense', 569 | trend: '集中', 570 | unique_skillCard_id: 2209010, 571 | unique_pItem_id: 2109010, 572 | }, 573 | { 574 | id: 10902, 575 | rarity: 'R', 576 | episode_name: '初心', 577 | character_id: 109, 578 | name: '姫崎莉波', 579 | plan: 'logic', 580 | trend: 'やる気', 581 | unique_skillCard_id: 2209020, 582 | unique_pItem_id: 2109020, 583 | }, 584 | { 585 | id: 20901, 586 | rarity: 'SR', 587 | episode_name: '『私らしさ』のはじまり', 588 | character_id: 109, 589 | name: '姫崎莉波', 590 | plan: 'sense', 591 | trend: '集中', 592 | unique_skillCard_id: 3209010, 593 | unique_pItem_id: 2209010, 594 | }, 595 | { 596 | id: 30901, 597 | rarity: 'SSR', 598 | episode_name: 'clumsy trick', 599 | character_id: 109, 600 | name: '姫崎莉波', 601 | plan: 'sense', 602 | trend: '集中', 603 | unique_skillCard_id: 4209010, 604 | unique_pItem_id: 2309010, 605 | }, 606 | { 607 | id: 30902, 608 | rarity: 'SSR', 609 | episode_name: 'キミとセミブルー', 610 | character_id: 109, 611 | name: '姫崎莉波', 612 | plan: 'sense', 613 | trend: '好調', 614 | unique_skillCard_id: 4209020, 615 | unique_pItem_id: 2309020, 616 | }, 617 | { 618 | id: 30903, 619 | rarity: 'SSR', 620 | episode_name: 'ようこそ初星温泉', 621 | character_id: 109, 622 | name: '姫崎莉波', 623 | plan: 'logic', 624 | trend: 'やる気', 625 | unique_skillCard_id: 4209030, 626 | unique_pItem_id: 2309030, 627 | }, 628 | { 629 | id: 30904, 630 | rarity: 'SSR', 631 | episode_name: 'L.U.V', 632 | character_id: 109, 633 | name: '姫崎莉波', 634 | plan: 'logic', 635 | trend: '好印象', 636 | unique_skillCard_id: 4209040, 637 | unique_pItem_id: 2309040, 638 | }, 639 | { 640 | id: 11001, 641 | rarity: 'R', 642 | episode_name: '学園生活', 643 | character_id: 110, 644 | name: '花海佑芽', 645 | plan: 'logic', 646 | trend: 'やる気', 647 | unique_skillCard_id: 2210010, 648 | unique_pItem_id: 2110010, 649 | }, 650 | { 651 | id: 21001, 652 | rarity: 'SR', 653 | episode_name: 'アイドル、はじめっ!', 654 | character_id: 110, 655 | name: '花海佑芽', 656 | plan: 'logic', 657 | trend: 'やる気', 658 | unique_skillCard_id: 3210010, 659 | unique_pItem_id: 2210010, 660 | }, 661 | { 662 | id: 31001, 663 | rarity: 'SSR', 664 | episode_name: 'The Rolling Riceball', 665 | character_id: 110, 666 | name: '花海佑芽', 667 | plan: 'logic', 668 | trend: 'やる気', 669 | unique_skillCard_id: 4210010, 670 | unique_pItem_id: 2310010, 671 | }, 672 | { 673 | id: 31002, 674 | rarity: 'SSR', 675 | episode_name: 'White Night! White Wish!', 676 | character_id: 110, 677 | name: '花海佑芽', 678 | plan: 'sense', 679 | trend: '好調', 680 | unique_skillCard_id: 4210020, 681 | unique_pItem_id: 2310020, 682 | }, 683 | ]; 684 | 685 | export class PIdolData { 686 | 687 | // property 688 | static #pIdolData = pIdolData; 689 | static #index = Object.fromEntries(this.#pIdolData.map((item, i) => [item.id, i])); 690 | static #index_character_id = (()=>{ 691 | const result = {}; 692 | for (let i = 0; i < pIdolData.length; i++) { 693 | if (!(pIdolData[i].character_id in result)) { 694 | result[pIdolData[i].character_id] = []; 695 | } 696 | result[pIdolData[i].character_id].push(pIdolData[i].id); 697 | } 698 | return result; 699 | })(); 700 | 701 | // method 702 | 703 | static getAll () { 704 | return this.#pIdolData; 705 | } 706 | 707 | static getById (id) { 708 | return this.#pIdolData[this.#index[id]]; 709 | } 710 | 711 | static getByCharacterId (character_id) { 712 | return this.#index_character_id[character_id].map(id=>this.getById(id)); 713 | } 714 | 715 | } -------------------------------------------------------------------------------- /scripts/simulator/class/PIdolStatus.js: -------------------------------------------------------------------------------- 1 | import { deep_copy } from "../../../scripts/util/utility.js"; 2 | 3 | const statusList = [ 4 | // センス 5 | { 6 | id: 10001, 7 | name: '好調', 8 | description: '', 9 | value: 0, 10 | type: 'buff', 11 | activate_timing: null, 12 | condition: null, 13 | is_reduce_turnend: true, 14 | }, 15 | { 16 | id: 10002, 17 | name: '集中', 18 | description: '', 19 | value: 0, 20 | type: 'buff', 21 | activate_timing: null, 22 | condition: null, 23 | is_reduce_turnend: false, 24 | }, 25 | { 26 | id: 10003, 27 | name: '好印象', 28 | description: '', 29 | value: 0, 30 | type: 'buff', 31 | activate_timing: null, 32 | condition: null, 33 | is_reduce_turnend: true, 34 | }, 35 | { 36 | id: 10004, 37 | name: 'やる気', 38 | description: '', 39 | value: 0, 40 | type: 'buff', 41 | activate_timing: null, 42 | condition: null, 43 | is_reduce_turnend: false, 44 | }, 45 | { 46 | id: 10005, 47 | name: '消費体力減少', 48 | description: '', 49 | value: 0, 50 | type: 'buff', 51 | activate_timing: null, 52 | condition: null, 53 | is_reduce_turnend: true, 54 | }, 55 | { 56 | id: 10006, 57 | name: '消費体力削減', 58 | description: '', 59 | value: 0, 60 | type: 'buff', 61 | activate_timing: null, 62 | condition: null, 63 | is_reduce_turnend: false, 64 | }, 65 | { 66 | id: 10007, 67 | name: '消費体力追加', 68 | description: '', 69 | value: 0, 70 | type: 'debuff', 71 | activate_timing: null, 72 | condition: null, 73 | is_reduce_turnend: false, 74 | }, 75 | { 76 | id: 100011, 77 | name: '絶好調', 78 | description: '', 79 | value: 0, 80 | type: 'buff', 81 | activate_timing: null, 82 | condition: null, 83 | is_reduce_turnend: true, 84 | }, 85 | { 86 | id: 8, 87 | name: '消費体力増加', 88 | description: '', 89 | value: 0, 90 | type: 'debuff', 91 | activate_timing: null, 92 | condition: null, 93 | is_reduce_turnend: true, 94 | }, 95 | { 96 | id: 9, 97 | name: '元気増加無効', 98 | description: '', 99 | value: 0, 100 | type: 'debuff', 101 | activate_timing: null, 102 | condition: null, 103 | is_reduce_turnend: true, 104 | }, 105 | { 106 | id: 10, 107 | name: '低下状態無効', 108 | description: '', 109 | value: 0, 110 | type: 'buff', 111 | activate_timing: null, 112 | condition: null, 113 | is_reduce_turnend: false, 114 | }, 115 | { 116 | id: 11, 117 | name: 'スキルカード使用数追加', 118 | description: '', 119 | value: 0, 120 | type: 'buff', 121 | activate_timing: null, 122 | condition: null, 123 | is_reduce_turnend: true,//all 124 | }, 125 | { 126 | id: 12, 127 | name: '次に使用するスキルカードの効果を発動', 128 | description: '', 129 | valueStack: [], 130 | type: 'buff', 131 | activate_timing: null, 132 | condition: null, 133 | is_reduce_turnend: false, 134 | }, 135 | 136 | { 137 | id: 14, 138 | name: '次に使用するアクティブスキルカードの効果を発動', 139 | description: '', 140 | valueStack: [], 141 | type: 'buff', 142 | activate_timing: null, 143 | condition: null, 144 | is_reduce_turnend: true, 145 | }, 146 | 147 | { 148 | id: 13, 149 | name: 'パラメータ上昇量増加', 150 | description: '', 151 | valueStack: [], 152 | type: 'buff', 153 | activate_timing: null, 154 | condition: null, 155 | is_reduce_turnend: true, 156 | }, 157 | 158 | { 159 | id: 20001, 160 | name: '不調', 161 | description: '', 162 | value: 0, 163 | type: 'debuff', 164 | activate_timing: null, 165 | condition: null, 166 | is_reduce_turnend: true, 167 | }, 168 | 169 | // { 170 | // id: 100, 171 | // name: '使用したスキルカード数', 172 | // description: '', 173 | // value: 0, 174 | // type: 'buff', 175 | // activate_timing: null, 176 | // condition: null, 177 | // is_reduce_turnend: false, 178 | // }, 179 | 180 | { 181 | id: 501, 182 | name: 'アクティブスキルカード使用時固定元気+2', 183 | description: '', 184 | value: 0, 185 | type: 'buff', 186 | activate_timing: 'before_use_card', 187 | condition: 'cardType==active', 188 | effects: [ 189 | { type: '固定元気', value: 2 }, 190 | ], 191 | is_reduce_turnend: false, 192 | }, 193 | { 194 | id: 502, 195 | name: 'アクティブスキルカード使用時集中+1', 196 | description: '', 197 | value: 0, 198 | type: 'buff', 199 | activate_timing: 'before_use_card', 200 | condition: 'cardType==active', 201 | effects: [ 202 | { type: 'status', target: '集中', value: 1 }, 203 | ], 204 | is_reduce_turnend: false, 205 | }, 206 | { 207 | id: 503, 208 | name: 'メンタルスキルカード使用時好印象+1', 209 | description: '', 210 | value: 0, 211 | type: 'buff', 212 | activate_timing: 'before_use_card', 213 | condition: 'cardType==mental', 214 | effects: [ 215 | { type: 'status', target: '好印象', value: 1 }, 216 | ], 217 | is_reduce_turnend: false, 218 | }, 219 | { 220 | id: 504, 221 | name: 'メンタルスキルカード使用時やる気+1', 222 | description: '', 223 | value: 0, 224 | type: 'buff', 225 | activate_timing: 'before_use_card', 226 | condition: 'cardType==mental', 227 | effects: [ 228 | { type: 'status', target: 'やる気', value: 1 }, 229 | ], 230 | is_reduce_turnend: false, 231 | }, 232 | { 233 | id: 1001, 234 | name: 'ターン終了時、好印象+1', 235 | description: '', 236 | value: 0, 237 | type: 'buff', 238 | activate_timing: 'end_turn', 239 | condition: '', 240 | effects: [ 241 | { type: 'status', target: '好印象', value: 1 }, 242 | ], 243 | is_reduce_turnend: false, 244 | }, 245 | 246 | { 247 | id: 1101, 248 | name: 'ターン終了時、集中が3以上の場合、集中+2', 249 | description: '', 250 | value: 0, 251 | type: 'buff', 252 | activate_timing: 'end_turn', 253 | condition: '集中>=3', 254 | effects: [ 255 | { type: 'status', target: '集中', value: 2 }, 256 | ], 257 | is_reduce_turnend: false, 258 | }, 259 | { 260 | id: 1102, 261 | name: 'ターン終了時、好印象が3以上の場合、好印象+3', 262 | description: '', 263 | value: 0, 264 | type: 'buff', 265 | activate_timing: 'end_turn', 266 | condition: '好印象>=3', 267 | effects: [ 268 | { type: 'status', target: '好印象', value: 3 }, 269 | ], 270 | is_reduce_turnend: false, 271 | }, 272 | 273 | 274 | { 275 | id: 1103, 276 | name: 'アクティブスキルカード使用時、パラメータ+4', 277 | description: '', 278 | value: 0, 279 | type: 'buff', 280 | activate_timing: 'before_use_card', 281 | condition: 'cardType==active', 282 | effects: [ 283 | { type: 'score', value: 4 }, 284 | ], 285 | is_reduce_turnend: false, 286 | }, 287 | { 288 | id: 1104, 289 | name: 'アクティブスキルカード使用時、パラメータ+5', 290 | description: '', 291 | value: 0, 292 | type: 'buff', 293 | activate_timing: 'before_use_card', 294 | condition: 'cardType==active', 295 | effects: [ 296 | { type: 'score', value: 5 }, 297 | ], 298 | is_reduce_turnend: false, 299 | }, 300 | 301 | { 302 | id: 1105, 303 | name: 'スキルカード使用時、好印象の30%分パラメータ', 304 | description: '', 305 | value: 0, 306 | type: 'buff', 307 | activate_timing: 'before_use_card', 308 | condition: '', 309 | effects: [ 310 | { type: 'score', value: null, options: [{ type: '好印象', value: 30 }] }, 311 | ], 312 | is_reduce_turnend: false, 313 | }, 314 | { 315 | id: 1106, 316 | name: 'スキルカード使用時、好印象の50%分パラメータ', 317 | description: '', 318 | value: 0, 319 | type: 'buff', 320 | activate_timing: 'before_use_card', 321 | condition: '', 322 | effects: [ 323 | { type: 'score', value: null, options: [{ type: '好印象', value: 50 }] }, 324 | ], 325 | is_reduce_turnend: false, 326 | }, 327 | { 328 | id: 1107, 329 | name: '元気効果のスキルカード使用後、好印象+1', 330 | description: '', 331 | value: 0, 332 | type: 'buff', 333 | activate_timing: 'after_use_card', 334 | condition: 'cardEffectInclude==block', 335 | effects: [ 336 | { type: 'status', target: '好印象', value: 1 }, 337 | ], 338 | is_reduce_turnend: false, 339 | }, 340 | { 341 | id: 1108, 342 | name: '好印象効果のスキルカード使用後、好印象の30%分のパラメータ', 343 | description: '', 344 | value: 0, 345 | type: 'buff', 346 | activate_timing: 'after_use_card', 347 | condition: 'cardEffectInclude==好印象', 348 | effects: [ 349 | { type: 'score', value: null, options: [{ type: '好印象', value: 30 }] }, 350 | ], 351 | is_reduce_turnend: false, 352 | }, 353 | { 354 | id: 1109, 355 | name: '好印象効果のスキルカード使用後、好印象の50%分のパラメータ', 356 | description: '', 357 | value: 0, 358 | type: 'buff', 359 | activate_timing: 'after_use_card', 360 | condition: 'cardEffectInclude==好印象', 361 | effects: [ 362 | { type: 'score', value: null, options: [{ type: '好印象', value: 50 }] }, 363 | ], 364 | is_reduce_turnend: false, 365 | }, 366 | { 367 | id: 1110, 368 | name: '之后x+1回合,每回合结束时,分数+4', 369 | description: '', 370 | value: 0, 371 | statusList: [], 372 | type: 'buff', 373 | activate_timing: 'end_turn', 374 | condition: '', 375 | effects: [ 376 | { type: 'score', value: 4 }, 377 | ], 378 | is_reduce_turnend: true, 379 | }, 380 | { 381 | id: 1111, 382 | name: 'ターン開始時、好調+2', 383 | description: '', 384 | value: 0, 385 | type: 'buff', 386 | activate_timing: 'start_turn', 387 | condition: '', 388 | effects: [ 389 | { type: 'status', target: '好調', value: 2 }, 390 | ], 391 | is_reduce_turnend: false, 392 | }, 393 | 394 | { 395 | id: 9999, 396 | name: '好印象効果', 397 | description: '', 398 | value: 1, 399 | type: 'buff', 400 | activate_timing: 'end_turn', 401 | condition: '好印象>0', 402 | effects: [ 403 | { type: 'score', value: null, options: [{ type: '好印象', value: 100 }] }, 404 | ], 405 | is_reduce_turnend: false, 406 | }, 407 | 408 | 409 | ]; 410 | 411 | export class _PStatus { 412 | 413 | #status; 414 | #index_name_to_idx; 415 | 416 | constructor (status) { 417 | this.#status = status; 418 | this.#index_name_to_idx = {}; 419 | for (let i = 0; i < this.#status.length; i++) { 420 | this.#index_name_to_idx[this.#status[i].name] = i; 421 | } 422 | } 423 | 424 | #get (name) { 425 | if (!(name in this.#index_name_to_idx)) { 426 | throw new Error(`${name}は存在しないステータスです。`); 427 | } 428 | return this.#status[this.#index_name_to_idx[name]]; 429 | } 430 | 431 | getType (name) { 432 | const status = this.#get(name); 433 | return status.type; 434 | } 435 | 436 | getValue (name) { 437 | const status = this.#get(name); 438 | if (status.valueStack) { 439 | return status.valueStack.reduce((pre, crt) => pre+crt.value, 0); 440 | } 441 | else if (status.statusList) { 442 | return status.statusList.length; 443 | } 444 | else { 445 | return status.value; 446 | } 447 | } 448 | 449 | has (name) { 450 | return this.getValue(name) > 0; 451 | } 452 | 453 | add (name, value, availableFirstAdded, options) { 454 | const status = this.#get(name); 455 | if (status.valueStack) { 456 | const item = { 457 | value: options[0].value, 458 | turn: value 459 | }; 460 | if (availableFirstAdded) { 461 | item.firstAdded = true; 462 | } 463 | status.valueStack.push(item); 464 | } 465 | else if (status.statusList) { 466 | const item = { 467 | turn: value 468 | }; 469 | if (availableFirstAdded) { 470 | item.firstAdded = true; 471 | } 472 | status.statusList.push(item); 473 | } 474 | else { 475 | if (status.value == 0 && availableFirstAdded) { 476 | status.firstAdded = true; 477 | } 478 | status.value += value; 479 | } 480 | } 481 | 482 | reduce (name, value) { 483 | // valueStackはエラー発生するから注意! 484 | const status = this.#get(name); 485 | status.value -= value; 486 | if (status.value < 0) { 487 | status.value = 0; 488 | } 489 | } 490 | 491 | getByTiming (timing) { 492 | const status = this.#status.map((item)=>{ 493 | if (item.statusList){ 494 | item.value = item.statusList.length; 495 | } 496 | return item; 497 | }); 498 | const result = status.filter(item=>item.value>0 && item.activate_timing==timing); 499 | return result; 500 | } 501 | } 502 | 503 | export class PIdolStatus { 504 | 505 | #status; 506 | #index_name_to_idx; 507 | #reduceInTurnendKeys; 508 | 509 | #names_activate_in_useCard; 510 | #delayEffectList = []; 511 | 512 | constructor () { 513 | this.#status = deep_copy(statusList); 514 | 515 | this.#index_name_to_idx = {}; 516 | for (let i = 0; i < this.#status.length; i++) { 517 | this.#index_name_to_idx[this.#status[i].name] = i; 518 | } 519 | 520 | this.#reduceInTurnendKeys = this.#status.filter(item=>item.is_reduce_turnend).map(item=>item.name); 521 | this.#names_activate_in_useCard = this.#status.filter(item=>item.activate_timing=='usecard').map(item=>item.name); 522 | } 523 | 524 | reduceInTurnend () { 525 | for (const key of this.#reduceInTurnendKeys) { 526 | const status = this.#get(key); 527 | if (status.valueStack) { 528 | for (let i = 0; i < status.valueStack.length; i++) { 529 | const item = status.valueStack[i]; 530 | if (item.firstAdded) { 531 | item.firstAdded = false; 532 | continue; 533 | } 534 | item.turn--; 535 | if (item.turn <= 0) { 536 | status.valueStack.splice(i, 1); 537 | i--; 538 | } 539 | } 540 | } 541 | else if (status.statusList) { 542 | for (let i = 0; i < status.statusList.length; i++) { 543 | const item = status.statusList[i]; 544 | if (item.firstAdded) { 545 | item.firstAdded = false; 546 | continue; 547 | } 548 | item.turn--; 549 | if (item.turn <= 0) { 550 | status.statusList.splice(i, 1); 551 | i--; 552 | } 553 | } 554 | } 555 | else { 556 | if (status.value <= 0) continue; 557 | if (status.firstAdded) { 558 | status.firstAdded = false; 559 | continue; 560 | } 561 | this.reduce(key, 1); 562 | } 563 | } 564 | // this.#status[this.#index_name_to_idx['使用したスキルカード数']].value = 0; 565 | } 566 | 567 | #get (name) { 568 | if (!(name in this.#index_name_to_idx)) { 569 | throw new Error(`${name}は存在しないステータスです。`); 570 | } 571 | return this.#status[this.#index_name_to_idx[name]]; 572 | } 573 | 574 | get (name) { 575 | return deep_copy(this.#get(name)); 576 | } 577 | 578 | getType (name) { 579 | const status = this.#get(name); 580 | return status.type; 581 | } 582 | 583 | getValue (name) { 584 | const status = this.#get(name); 585 | if (status.valueStack) { 586 | return status.valueStack.reduce((pre, crt) => pre+crt.value, 0); 587 | } 588 | else if (status.statusList) { 589 | return status.statusList.length; 590 | } 591 | else { 592 | return status.value; 593 | } 594 | } 595 | 596 | has (name) { 597 | return this.getValue(name) > 0; 598 | } 599 | 600 | _deepcopy () { 601 | return deep_copy(this.#status); 602 | } 603 | 604 | 605 | getAll () { 606 | console.log(Object.fromEntries(this.#status.map(item => [item.name, item.value]))); 607 | return Object.fromEntries(this.#status.map(item => [item.name, item.value])) 608 | } 609 | 610 | add (name, value, _availableFirstAdded, options) { 611 | const status = this.#get(name); 612 | const availableFirstAdded = _availableFirstAdded || options?.some(item=>item.type=='immune_decrease'); 613 | if (status.valueStack) { 614 | const item = { 615 | value: options[0].value, 616 | turn: value 617 | }; 618 | if (availableFirstAdded) { 619 | item.firstAdded = true; 620 | } 621 | status.valueStack.push(item); 622 | } 623 | else if (status.statusList) { 624 | const item = { 625 | turn: value 626 | }; 627 | if (availableFirstAdded) { 628 | item.firstAdded = true; 629 | } 630 | status.statusList.push(item); 631 | } 632 | else { 633 | if (status.value == 0 && availableFirstAdded) { 634 | status.firstAdded = true; 635 | } 636 | status.value += value; 637 | } 638 | } 639 | 640 | // addDelayEffect (name, turn, effect) { 641 | // this.#delayEffectList.push({ 642 | // name: name, 643 | // turn: turn, 644 | // effect: effect, 645 | // }); 646 | // } 647 | 648 | addDelayEffect (name, turn, effect) { 649 | this.#delayEffectList.push({ 650 | name: name, 651 | turn: turn, 652 | effect: effect, 653 | }); 654 | // { 655 | // id: 9999, 656 | // name: '好印象効果', 657 | // description: '', 658 | // value: 1, 659 | // type: 'buff', 660 | // activate_timing: 'end_turn', 661 | // condition: '', 662 | // effects: [ 663 | // { type: 'score', value: null, options: [{ type: '好印象', value: 100 }] }, 664 | // ], 665 | // is_reduce_turnend: false, 666 | // }, 667 | } 668 | 669 | getDelayEffectByTurn (turn) { 670 | const result = []; 671 | for (let i = 0; i < this.#delayEffectList.length; i++) { 672 | const delayEffect = this.#delayEffectList[i]; 673 | if (delayEffect.turn == turn) { 674 | result.push(delayEffect); 675 | this.#delayEffectList.splice(i, 1); 676 | i--; 677 | } 678 | } 679 | return result; 680 | } 681 | 682 | reduce (name, value) { 683 | // valueStackは回数を減らします 684 | const status = this.#get(name); 685 | if (status.valueStack) { 686 | //for (let i = 0; i < status.valueStack.length; i++) { 687 | const item = status.valueStack[0]; 688 | item.value--; 689 | if (item.value <= 0) { 690 | status.valueStack.splice(0, 1); 691 | // i--; 692 | } 693 | //} 694 | } 695 | else if (status.statusList) { 696 | //for (let i = 0; i < status.valueStack.length; i++) { 697 | const item = status.statusList[0]; 698 | item.value--; 699 | if (item.value <= 0) { 700 | status.statusList.splice(0, 1); 701 | // i--; 702 | } 703 | //} 704 | } 705 | else { 706 | status.value -= value; 707 | if (status.value < 0) { 708 | status.value = 0; 709 | } 710 | } 711 | } 712 | 713 | getByTiming (timing) { 714 | // const result = []; 715 | // for (const name of this.#names_activate_in_useCard) { 716 | // const status = this.#get(name); 717 | // if (status.value == 0) continue; 718 | // result.push(status); 719 | // } 720 | const status = this.#status.map((item)=>{ 721 | if (item.statusList){ 722 | const _item = deep_copy(item); 723 | _item.value = _item.statusList.length; 724 | } 725 | return item; 726 | }); 727 | const result = status.filter(item=>item.value>0 && item.activate_timing==timing); 728 | return result; 729 | } 730 | 731 | getDelayEffects(turn) { 732 | const result = []; 733 | for (let i = 0; i < this.#delayEffectList.length; i++) { 734 | const delayEffect = this.#delayEffectList[i]; 735 | if (delayEffect.turn == turn) { 736 | result.push(delayEffect); 737 | this.#delayEffectList.splice(i, 1); 738 | i--; 739 | } 740 | } 741 | return result; 742 | } 743 | } -------------------------------------------------------------------------------- /scripts/simulator/class/PIdol.js: -------------------------------------------------------------------------------- 1 | import { Deck } from './Deck.js'; 2 | import { TurnType } from './TurnType.js'; 3 | import { PIdolStatus, _PStatus } from './PIdolStatus.js'; 4 | import { PItemManager } from './PItemManager.js'; 5 | import { SkillCard, SkillCardData } from '../data/skillCardData.js'; 6 | import { PIdolLog } from './PIdolLog.js'; 7 | import { ConditionChecker } from './ConditionChecker.js'; 8 | import { Calculator } from './Calculator.js'; 9 | import { deep_copy } from '../../../scripts/util/utility.js'; 10 | import { AutoEvaluationData } from '../data/ProduceExamAutoEvaluation.js'; 11 | 12 | export class PIdol { 13 | 14 | #parameter; 15 | #status; 16 | #pItemsManager; 17 | deck; 18 | #isAdditional; 19 | #endExecutions; 20 | #log; 21 | turnType; 22 | #trendEvaluationVonusCoef; 23 | #autoId; 24 | 25 | constructor ({ parameter, plan, trend, pItemIds, skillCardIds, autoId}) { 26 | 27 | this.#parameter = { 28 | vocal : parameter.vocal, 29 | dance : parameter.dance, 30 | visual: parameter.visual, 31 | max : Math.max(parameter.vocal, parameter.dance, parameter.visual), 32 | avg : 0, 33 | }; 34 | 35 | this.#status = { 36 | hp: parameter.hp, 37 | maxHp: parameter.hp, 38 | block: 0, 39 | score: 0, 40 | plan: plan, 41 | trend: trend, 42 | turn: 0, 43 | currentTurnType: null, 44 | extraTurn: 0, 45 | remainTurn: null, 46 | turnCount: 0, 47 | lastUsedCard: null, 48 | usedCardCount: 0, 49 | consumedHp: 0, 50 | usedCardTurnCount: 0, 51 | pStatus: new PIdolStatus(), 52 | handCount: null, 53 | turnType: null, 54 | }; 55 | 56 | this.#pItemsManager = new PItemManager(pItemIds); 57 | this.deck = new Deck(skillCardIds); 58 | this.#log = new PIdolLog(); 59 | 60 | this.#trendEvaluationVonusCoef = {} 61 | this.#trendEvaluationVonusCoef[trend] = 1.5; 62 | 63 | this.#autoId = autoId; 64 | } 65 | 66 | init (turnCount, critearia, turnRank, firstTurn, turnTypes) { 67 | this.turnType = new TurnType(turnCount, critearia, turnRank, firstTurn, turnTypes, this.#autoId); 68 | this.#status.turnType = this.turnType; 69 | this.#status.remainTurn = turnCount; 70 | this.#status.turnCount = turnCount; 71 | this.#parameter.avg = 72 | Math.floor(['vocal', 'dance', 'visual'].reduce((acc, curr)=>{ 73 | return acc + this.turnType.getCount(curr) * this.#parameter[curr]; 74 | }, 0) / turnCount); 75 | } 76 | 77 | getResult () { 78 | return { 79 | log: this.#log.getLog(), 80 | finalStatus: { score: this.#status.score, hp: this.#status.hp, block: this.#status.block }, 81 | }; 82 | } 83 | 84 | /** 85 | * ターン開始時の行動 86 | */ 87 | start () { 88 | this.#status.turn++; 89 | this.#status.currentTurnType = this.turnType.getType(this.#status.turn); 90 | this.#log.nextTurn({ score: this.#status.score, hp: this.#status.hp, block: this.#status.block, turnType: this.#status.currentTurnType }); 91 | const defaultActions = [ 92 | { type: 'effect', sourceType: 'pIdol', target: { type: 'draw', value: 3 } }, 93 | ]; 94 | const pItemActions = this.#getPItemAction('start_turn', this.#status); 95 | const pStatusActions = this.#getPStatusAction('start_turn', this.#status); 96 | const pDelayActions = this.#getPDelayAction(this.#status); 97 | const pItemAfterActions = this.#getPItemAction('start_turn_after', this.#status); 98 | // 99 | const actions = []; 100 | defaultActions.forEach(action=>actions.push(action)); 101 | pItemActions.forEach(action=>actions.push(action)); 102 | pStatusActions.forEach(action=>actions.push(action)); 103 | pDelayActions.forEach(action=>actions.push(action)); 104 | pItemAfterActions.forEach(action=>actions.push(action)); 105 | // const actions = defaultActions.concat(pItemActions, pStatusActions, pDelayActions); 106 | // 107 | const executions = this.#simulateActions(actions); 108 | this.#executeActions(executions); 109 | this.#updateHandSkillCards(); 110 | } 111 | 112 | 113 | 114 | useCard (cardNumber) { 115 | const usedCard = this.deck.getHandCardByNumber(cardNumber); 116 | this.#status.lastUsedCard = usedCard; 117 | const executions = usedCard.executions; 118 | this.#endExecutions = usedCard.scheduledExecutions; 119 | this.deck.useCard(cardNumber); 120 | this.#status.handCount--; 121 | this.#executeActions(executions); 122 | if (this.#status.pStatus.has('スキルカード使用数追加')) { 123 | this.#executeActions(this.#simulateActions([ 124 | { type: 'effect', sourceType: 'pIdol', target: { type: 'status', target: 'スキルカード使用数追加', value: -1 } }, 125 | { type: 'effect', sourceType: 'pIdol', target: { type: 'extra_action', value: 1 } }, 126 | ])); 127 | return true; 128 | } else { 129 | return false; 130 | } 131 | } 132 | 133 | rest () { 134 | const status = this.#getStatus(); 135 | const source = { name: '休憩' }; 136 | const executions = this.#simulateActions([ 137 | { type: 'use', sourceType: 'pRest', source: source }, 138 | { type: 'effect', sourceType: 'pRest', target: { type: 'hp', value: 2 } }, 139 | { type: 'end' }, 140 | ], status); 141 | 142 | const scheduledActions = []; 143 | this.#getPItemAction('end_turn', status).forEach(action=>scheduledActions.push(action)); 144 | this.#getPStatusAction('end_turn', status).forEach(action=>scheduledActions.push(action)); 145 | 146 | this.#executeActions(executions); 147 | 148 | this.#endExecutions = this.#simulateActions(scheduledActions, status); 149 | } 150 | 151 | discardAll () { 152 | this.deck.discardAll(); 153 | this.#status.handCount = this.getDeck('handCards').length; 154 | } 155 | 156 | end () { 157 | if (this.#endExecutions) { 158 | this.#executeActions(this.#endExecutions); 159 | this.#endExecutions = null; 160 | } 161 | this.#status.pStatus.reduceInTurnend(); 162 | this.discardAll(); 163 | this.#status.remainTurn--; 164 | this.#status.usedCardTurnCount = 0; 165 | } 166 | 167 | checkAdditionalAction () { 168 | if (this.#isAdditional) { 169 | this.#isAdditional = false; 170 | this.#updateHandSkillCards(); 171 | return true; 172 | } 173 | return false; 174 | } 175 | 176 | checkFinished () { 177 | if (this.#status.remainTurn == 0) return true; 178 | return false; 179 | } 180 | 181 | #updateHandSkillCards () { 182 | const handCards = this.getDeck('handCards'); 183 | this.#log.addExecutionLog([{ type: 'show', sourceType: 'handCard', message: '手札' }]); 184 | handCards.forEach((handCard) => { 185 | this.#setSkillCardAvailale(handCard); 186 | if (handCard.isAvailable()) { 187 | this.#setSkillCardActions(handCard); 188 | } 189 | this.#log.addExecutionLog([{ type: 'card', message: `${handCard.isAvailable()?'○':'×'}${handCard.name}(${handCard.evaluation??0})` }]); 190 | }); 191 | this.#log.addExecutionLog([{ type: 'end' }]); 192 | } 193 | 194 | #getSkillCardActions (skillCard) { 195 | const skillCardActions = []; 196 | skillCard.effects.forEach(effect => { 197 | if (ConditionChecker.check(effect.condition, this.#status)) { 198 | skillCardActions.push({ type: 'effect', sourceType: 'skillCard', name: skillCard.name, target: effect }); 199 | } 200 | }); 201 | const twiceActivateKeys = [ 202 | { name: '次に使用するアクティブスキルカードの効果を発動', condition: 'cardType==active' }, 203 | { name: '次に使用するスキルカードの効果を発動', condition: '' }, 204 | ]; 205 | for (let i = 0; i < twiceActivateKeys.length; i++) { 206 | const item = twiceActivateKeys[i]; 207 | if ( 208 | this.#status.pStatus.has(item.name) && 209 | ConditionChecker.check(item.condition, this.#status) 210 | ) { 211 | const twiceActions = []; 212 | twiceActions.push({ type: 'effect', sourceType: 'pIdol', target: { type: 'status', target: item.name, value: -1 } }); 213 | twiceActions.push({ type: 'use', sourceType: 'pStatus', source: this.#status.pStatus.get(item.name) }); 214 | skillCardActions.forEach(action=>twiceActions.push(action)); 215 | twiceActions.push({ type: 'end' }); 216 | twiceActions.forEach(action=>skillCardActions.push(action)); 217 | break; 218 | } 219 | }; 220 | return skillCardActions; 221 | } 222 | 223 | #setSkillCardActions (skillCard) { 224 | this.#status.lastUsedCard = skillCard; 225 | const preActions = []; 226 | preActions.push({ type: 'use', sourceType: 'skillCard', source: skillCard }); 227 | preActions.push({ type: 'effect', sourceType: 'skillCard', name: skillCard.name, target: skillCard.cost }); 228 | 229 | const status = this.#getStatus(); 230 | const actions = []; 231 | this.#getPItemAction('before_use_card', status).forEach(action=>actions.push(action)); 232 | this.#getPStatusAction('before_use_card', status).forEach(action=>actions.push(action)); 233 | const preExecutions = this.#simulateActions(preActions, status); 234 | this.#getSkillCardActions(skillCard).forEach(action=>actions.push(action)); 235 | 236 | const executions = this.#simulateActions(actions, status); 237 | 238 | const afterActions = []; 239 | this.#getPItemAction('after_use_card', status).forEach(action=>afterActions.push(action)); 240 | this.#getPStatusAction('after_use_card', status).forEach(action=>afterActions.push(action)); 241 | afterActions.push({ type: 'end' }); 242 | const afterExecution = this.#simulateActions(afterActions, status); 243 | 244 | const totalExecution = preExecutions.concat(executions, afterExecution); 245 | 246 | const evaluation = this.#evaluateExecutions(totalExecution, status); 247 | 248 | if (isNaN(evaluation)) { 249 | throw new Error(`evaluation is NaN: ${skillCard.name}`); 250 | } 251 | 252 | skillCard.executions = totalExecution; 253 | skillCard.evaluation = evaluation; 254 | 255 | // // ターンエンド時の処理 256 | const scheduledActions = []; 257 | this.#getPItemAction('end_turn', status).forEach(action=>scheduledActions.push(action)); 258 | this.#getPStatusAction('end_turn', status).forEach(action=>scheduledActions.push(action)); 259 | const scheduledExecutions = this.#simulateActions(scheduledActions, status); 260 | const scheduledEvaluation = this.#evaluateExecutions(scheduledExecutions, status); 261 | skillCard.scheduledExecutions = scheduledExecutions; 262 | 263 | skillCard.evaluation = evaluation + scheduledEvaluation + (this.#autoId >= 5 ? AutoEvaluationData.get_evaluation(status, this.#parameter) : 0); 264 | } 265 | 266 | #evaluateExecutions (executions, status) { 267 | return Math.floor(executions.reduce((acc, curr) => { 268 | let evaluation = Calculator.calcActionEvaluation(curr, this.#status, this.#parameter, this.#trendEvaluationVonusCoef, this.#autoId, this.turnType.getType(this.#status.turn)); 269 | return acc + evaluation; 270 | }, 0)); 271 | } 272 | 273 | #getStatus () { 274 | const status = { 275 | hp: this.#status.hp, 276 | maxHp: this.#status.maxHp, 277 | block: this.#status.block, 278 | score: this.#status.score, 279 | plan: this.#status.plan, 280 | trend: this.#status.trend, 281 | turn: this.#status.turn, 282 | currentTurnType: this.#status.currentTurnType, 283 | extraTurn: this.#status.extraTurn, 284 | remainTurn: this.#status.remainTurn, 285 | turnCount: this.#status.turnCount, 286 | lastUsedCard: this.#status.lastUsedCard, 287 | usedCardCount: this.#status.usedCardCount, 288 | consumedHp: this.#status.consumedHp, 289 | usedCardTurnCount: this.#status.usedCardTurnCount, 290 | pStatus: new _PStatus(this.#status.pStatus._deepcopy()), 291 | handCount: this.getDeck('handCards').length, 292 | turnType: this.#status.turnType, 293 | }; 294 | return status; 295 | } 296 | 297 | #drawSkillCard (number) { 298 | const result = this.deck.draw(number); 299 | this.#status.handCount = this.getDeck('handCards').length; 300 | return result; 301 | } 302 | 303 | getDeck (type) { 304 | return this.deck[type]; 305 | } 306 | 307 | #setSkillCardAvailale (skillCard) { 308 | skillCard.setAvailable( 309 | ConditionChecker.check(skillCard.condition, this.#status) && 310 | this.#checkSkillCardCost(skillCard.cost) 311 | ); 312 | } 313 | 314 | #checkSkillCardCost (cost) { 315 | const actualValue = Calculator.calcActualValue(cost, this.#status, this.#parameter); 316 | if (cost.type == 'hp') return this.#status.hp + this.#status.block >= -actualValue; 317 | if (cost.type == 'direct_hp') return this.#status.hp >= -actualValue; 318 | return this.#status.pStatus.getValue(cost.target) >= -actualValue; 319 | } 320 | 321 | #simulateActions (actions, _status) { 322 | const status = _status ?? this.#getStatus(); 323 | const executions = []; 324 | actions.forEach(action => { 325 | this.#simulateAction(action, status) 326 | .forEach(execution=>executions.push(execution)); 327 | }); 328 | return executions; 329 | } 330 | 331 | #simulateAction (action, status) { 332 | if (action.type == 'use' || action.type == 'end') { 333 | const executes = [action]; 334 | if (action.type == 'use' && action.sourceType == 'skillCard') { 335 | status.usedCardCount++; 336 | status.usedCardTurnCount++; 337 | executes.push({ type: 'used_card_count', args: [1] }); 338 | } 339 | return executes; 340 | } 341 | const { type, target, value, options, delay, condition } = action.target; 342 | const actualValue = Calculator.calcActualValue(action.target, status, this.#parameter); 343 | if (!ConditionChecker.check(condition, this.#status)) return []; 344 | // 345 | const executes = []; 346 | if (delay) { 347 | executes.push({ type: 'delay', args: [actualValue, action.name, status.turn+delay, { 348 | type: type, target: target, value: value, options: options, 349 | }] }); 350 | return executes; 351 | } 352 | if (type == 'score') { 353 | const score = status.score; 354 | status.score = score + actualValue; 355 | executes.push({ type: 'score', args: [actualValue] }); 356 | return executes; 357 | } 358 | if (type == 'hp' || type == 'direct_hp' || type == 'fixed_direct_hp') { 359 | if (actualValue > 0) { 360 | // 回復 361 | const hp = status.hp; 362 | status.hp = Math.min(status.maxHp, hp+actualValue); 363 | executes.push({ type: 'hp', args: [status.hp-hp] }); 364 | } 365 | else if (actualValue < 0) { 366 | // 消費 367 | const block = status.block, hp = status.hp; 368 | if (type == 'direct_hp' || type == 'fixed_direct_hp') { 369 | status.hp = hp + actualValue; 370 | } 371 | else if (block < -actualValue) { 372 | status.hp = hp + (block + actualValue); 373 | status.block = 0; 374 | } 375 | else { 376 | status.hp = hp; 377 | status.block = block + actualValue; 378 | } 379 | if (status.block < block) { 380 | executes.push({ type: 'block', args: [status.block-block] }); 381 | } 382 | status.hp = Math.max(status.hp, 0); 383 | if (status.hp < hp) { 384 | executes.push({ type: 'hp', args: [status.hp-hp] }); 385 | 386 | if (action.sourceType == 'skillCard') { 387 | this.#simulateActions(this.#getPItemAction('consume_hp', status), status) 388 | .forEach(execution=>executes.push(execution)); 389 | } 390 | status.consumedHp += hp-status.hp; 391 | executes.push({ type: 'consumedHp', args: [hp-status.hp] }); 392 | } 393 | } else { 394 | executes.push({ type: 'hp', args: [0] }); 395 | } 396 | return executes; 397 | } 398 | if (type == 'block' || type == '固定元気') { 399 | const block = status.block; 400 | status.block = block + actualValue; 401 | executes.push({ type: 'block', args: [actualValue] }); 402 | return executes; 403 | } 404 | if (type == 'draw') { 405 | executes.push({ type: 'draw', args: [actualValue] }); 406 | return executes; 407 | } 408 | if (type == 'upgrade') { 409 | executes.push({ type: 'upgrade', args: [value] }); 410 | return executes; 411 | } 412 | if (type == 'extra_action') { 413 | executes.push({ type: 'add_action', args: [actualValue] }); 414 | return executes; 415 | } 416 | if (type == 'extra_turn') { 417 | executes.push({ type: 'extra_turn', args: [actualValue] }); 418 | return executes; 419 | } 420 | if (type == 'discard') { 421 | executes.push({ type: 'discard', args: [value] }); 422 | return executes; 423 | } 424 | if (type == 'generate') { 425 | executes.push({ type: 'generate', args: [value] }); 426 | return executes; 427 | } 428 | if (type == 'status'){ // ステータス系 429 | if (actualValue > 0) { // 増加 430 | if (status.pStatus.has('低下状態無効') && status.pStatus.getType(target) == 'debuff') { 431 | status.pStatus.reduce('低下状態無効', 1); 432 | executes.push({ type: 'status', args: [-1, '低下状態無効'] }); 433 | } else { 434 | const availableFirstAdded = action.sourceType == 'skillCard'; 435 | status.pStatus.add(target, actualValue, availableFirstAdded, options); 436 | executes.push({ type: 'status', args: [actualValue, target, availableFirstAdded, options] }); 437 | if (action.sourceType == 'skillCard') { 438 | this.#simulateActions(this.#getPItemAction(`increased_status:${target}`, status), status) 439 | .forEach(execution=>executes.push(execution)); 440 | } 441 | } 442 | } 443 | else { // 消費 444 | status.pStatus.reduce(target, -actualValue); 445 | executes.push({ type: 'status', args: [actualValue, target] }); 446 | } 447 | return executes; 448 | } 449 | throw new Error(`${type}は存在しません -> ${JSON.stringify(action)}`); 450 | } 451 | 452 | #executeActions (actions) { 453 | const executeLog = []; 454 | actions.forEach(action=>{ 455 | if (action.type == 'use' || action.type == 'end') { 456 | executeLog.push(action); 457 | if (action.sourceType == 'pItem') { 458 | action.source.use(); 459 | } 460 | } else { 461 | executeLog.push({ type: 'effect', message: this.#executeAction(action) }); 462 | } 463 | }); 464 | this.#log.addExecutionLog(executeLog); 465 | } 466 | 467 | #executeAction ({ type, args }) { 468 | if (type == 'status') { 469 | const statusType = args[1]; 470 | const statusValue = this.#status.pStatus.getValue(statusType); 471 | if (args[0] > 0) { 472 | this.#status.pStatus.add(statusType, args[0], args[2], args[3]); 473 | } else { 474 | this.#status.pStatus.reduce(statusType, -args[0]); 475 | } 476 | const afterStatusValue = this.#status.pStatus.getValue(statusType); 477 | return `${statusType}:${statusValue}→${afterStatusValue}(${afterStatusValue-statusValue})`; 478 | } 479 | if (type == 'hp') { 480 | const hp = this.#status.hp; 481 | this.#status.hp += args[0]; 482 | return `HP:${hp}→${this.#status.hp}(${this.#status.hp-hp})`; 483 | } 484 | if (type == 'score') { 485 | const score = this.#status.score; 486 | this.#status.score += args[0]; 487 | return `スコア:${score}→${this.#status.score}(${args[0]})`; 488 | } 489 | if (type == 'block') { 490 | const block = this.#status.block; 491 | this.#status.block += args[0]; 492 | return `元気:${block}→${this.#status.block}(${this.#status.block-block})`; 493 | } 494 | if (type == 'add_action') { 495 | this.#isAdditional = true; 496 | return `追加行動`; 497 | } 498 | if (type == 'draw') { 499 | if (this.#status.handCount >= 5) { 500 | return `カードが引けなかった`; 501 | } 502 | const result = this.#drawSkillCard(args[0]); 503 | if (result.response < result.request) { 504 | return `${result.response}枚カードを引いた(${result.request-result.response}枚捨て札に送られた)`; 505 | } 506 | return `${result.response}枚カードを引いた`; 507 | } 508 | if (type == 'delay') { 509 | this.#status.pStatus.addDelayEffect(args[1], args[2], args[3]); 510 | return `予約効果登録:${args[1]}(${args[2]}ターン目)`; 511 | } 512 | if (type == 'upgrade') { 513 | this.deck.upgrade('allhands'); 514 | return `手札を強化した`; 515 | } 516 | if (type == 'discard') { 517 | this.discardAll(); 518 | return `手札を捨てた`; 519 | } 520 | if (type == 'used_card_count') { 521 | this.#status.usedCardCount++; 522 | this.#status.usedCardTurnCount++; 523 | return ``; 524 | } 525 | if (type == 'consumedHp') { 526 | this.#status.consumedHp += args[0]; 527 | return ``; 528 | } 529 | if (type == 'generate') { 530 | let name; 531 | if (args[0] == 'ランダムな強化済みスキルカード') { 532 | const targetCards = SkillCardData.getAll().filter(item=> 533 | (item.plan=='free'||item.plan==this.#status.plan) && // プラン指定 534 | item.id % 10 == 1 && // 強化カード 535 | item.id > 2000000 && // 基本カード削除 536 | String(item.id)[1] != '2' &&// キャラ固有削除) 537 | String(item.id)[1] != '3' // サポ固有削除) 538 | ); 539 | const targetCard = targetCards[Math.floor(Math.random()*targetCards.length)]; 540 | this.deck.addCardInDeck(targetCard.id, 'handCards'); 541 | name = targetCard.name; 542 | } 543 | return `${name}を手札に加えた`; 544 | } 545 | if (type == 'extra_turn') { 546 | const extraTurn = this.#status.extraTurn; 547 | this.#status.extraTurn += args[0]; 548 | this.#status.remainTurn += args[0]; 549 | return `追加ターン:${extraTurn}→${this.#status.extraTurn}(${this.#status.extraTurn-extraTurn})`; 550 | } 551 | throw new Error(`次のアクションタイプは定義されていません -> ${type}`); 552 | } 553 | 554 | /** 555 | * 特定のタイミングで発火するPアイテムのActionを取得する 556 | * @param {*} activateTiming 557 | * @param {*} status 558 | * @returns {Array} 559 | */ 560 | #getPItemAction (activateTiming, status) { 561 | const pItemList = this.#pItemsManager.getByTiming(activateTiming) 562 | .filter(pItem=>ConditionChecker.check(pItem.condition, status)); 563 | const actions = []; 564 | pItemList.forEach(pItem=>{ 565 | actions.push({ type: 'use', sourceType: 'pItem', source: pItem }); 566 | pItem.effects.forEach(effect=> 567 | actions.push({ type: 'effect', sourceType: 'pItem', target: effect })); 568 | actions.push({ type: 'end' }); 569 | }); 570 | return actions; 571 | } 572 | 573 | /** 574 | * 特定のタイミングで発火するPステータスのActionを取得する 575 | * @param {*} activateTiming 576 | * @param {*} status 577 | * @returns {Array} 578 | */ 579 | #getPStatusAction (activateTiming, status) { 580 | // const pStatusList = this.#status.pStatus.getByTiming(activateTiming) 581 | const pStatusList = status.pStatus.getByTiming(activateTiming) 582 | .filter(pStatus=>ConditionChecker.check(pStatus.condition, status)); 583 | const actions = []; 584 | pStatusList.forEach(pStatus=>{ 585 | for (let i = 0; i < pStatus.value; i++) { 586 | actions.push({ type: 'use', sourceType: 'pStatus', source: pStatus }); 587 | pStatus.effects.forEach(effect=> 588 | actions.push({ type: 'effect', sourceType: 'pStatus', target: effect })); 589 | actions.push({ type: 'end' }); 590 | } 591 | }); 592 | return actions; 593 | } 594 | 595 | /** 596 | * 予約効果のActionを取得する 597 | * @param {*} status 598 | * @returns {Array} 599 | */ 600 | #getPDelayAction (status) { 601 | const delayEffectList = this.#status.pStatus.getDelayEffects(status.turn); 602 | const actions = []; 603 | delayEffectList.forEach(delayEffect=>{ 604 | actions.push({ type: 'use', sourceType: 'pDelay', source: delayEffect }); 605 | actions.push({ type: 'effect', sourceType: 'pDelay', target: delayEffect.effect}); 606 | actions.push({ type: 'end' }); 607 | }); 608 | return actions; 609 | } 610 | } --------------------------------------------------------------------------------