`;
39 | // }
40 | else if (log.sourceType == 'pRest') {
41 | 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 |
58 |
59 |
60 |
63 |
64 |
65 |
66 |
76 |
77 |
84 |
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 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
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 |
33 |
34 |
35 |
36 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
50 |
53 |
54 |
55 |
64 |
70 |
83 |
90 |
91 |
92 |
93 |
94 |
99 |
100 |
101 |
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 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
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 |
164 |
165 |
166 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
182 |
183 |
184 |
185 |
189 |
190 |
191 |
201 |
202 |
203 |
204 |
205 |
208 |
211 |
212 |
213 |
216 |
219 |
220 |
221 |
224 |
227 |
228 |
229 |
232 |
235 |
236 |
237 |
240 |
243 |
244 |
245 |
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 |
32 |
33 |
34 |
35 |
38 |
41 |
44 |
45 |
46 |
47 |
48 |
51 |
52 |
53 |
56 |
59 |
60 |
61 |
64 |
67 |
68 |
69 |
78 |
84 |
85 |
86 |
87 |
91 |
95 |
99 |
103 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
128 |
129 |
130 |
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 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
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