メモ帳 グラフアプリ
この配信で作ったデータです。

また前提の.htmlを見えるようにする設定の部分がちゃんと見えていませんでした。
これも貼っておきます。


メモ帳 & グラフ (マルチ数式・範囲分割対応)
グラフメモ帳

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>メモ帳 & グラフ (マルチ数式・範囲分割対応)</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
padding: 20px;
font-family: sans-serif;
}
h1 {
margin-bottom: 10px;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab-btn {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
border: 1px solid #ccc;
background-color: #f0f0f0;
border-radius: 4px;
}
.tab-btn.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.view {
display: none;
width: 90%;
max-width: 750px;
flex-direction: column;
align-items: center;
}
.view.active {
display: flex;
}
.info {
font-size: 14px;
color: #666;
margin-bottom: 15px;
text-align: center;
}
textarea, input[type="text"] {
width: 100%;
padding: 10px;
font-size: 16px;
box-sizing: border-box;
}
textarea {
height: 50vh;
}
.formula-group {
width: 100%;
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 15px;
background: #fdfdfd;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
box-sizing: border-box;
}
.formula-row {
display: flex;
align-items: center;
gap: 8px;
}
.formula-row label {
font-weight: bold;
width: 30px;
}
.formula-row input[type="text"] {
flex: 1;
margin-bottom: 0;
padding: 6px;
}
.range-subrow {
display: flex;
align-items: center;
gap: 6px;
padding-left: 38px;
font-size: 13px;
color: #555;
}
.range-subrow input[type="number"] {
width: 60px;
padding: 4px;
text-align: center;
}
.range-subrow input[type="checkbox"] {
margin-right: 4px;
cursor: pointer;
}
.range-inputs {
display: inline-flex;
align-items: center;
gap: 6px;
}
details summary {
cursor: pointer;
font-weight: bold;
color: #007bff;
padding: 6px 0;
user-select: none;
}
.controls {
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
background: #f9f9f9;
padding: 15px;
border-radius: 6px;
box-sizing: border-box;
}
.control-row {
display: flex;
align-items: center;
gap: 8px;
}
.control-row label {
width: 20px;
font-weight: bold;
}
.control-row input[type="number"] {
width: 55px;
padding: 4px;
text-align: center;
font-size: 14px;
}
.control-row input[type="range"] {
flex: 1;
cursor: pointer;
}
.zero-btn {
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
background-color: #e0e0e0;
border: 1px solid #ccc;
border-radius: 3px;
}
.zero-btn:hover {
background-color: #d0d0d0;
}
.control-row span {
width: 45px;
text-align: right;
}
.output-box {
width: 100%;
background: #eef6ff;
padding: 12px;
border-radius: 6px;
font-size: 18px;
font-weight: bold;
color: #007bff;
text-align: center;
margin-bottom: 15px;
box-sizing: border-box;
border: 1px solid #bce8f1;
}
.buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
button.action-btn {
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
}
.chart-container {
width: 100%;
margin-top: 10px;
}
</style>
<!-- Chart.jsライブラリ -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<h1>グラフメモ帳</h1>

<!-- タブ切り替えボタン -->
<div class="tabs">
<button class="tab-btn" onclick="switchTab('memo')">メモ帳</button>
<button class="tab-btn active" onclick="switchTab('chart')">グラフ (マルチ数式)</button>
</div>

<!-- メモ帳機能 -->
<div id="memoView" class="view">
<div class="info">入力内容はURLに自動保存されます。このURLを共有すればメモとグラフの両方が共有されます。</div>
<textarea id="memo" placeholder="ここにメモを入力してください..." autocomplete="off"></textarea>
</div>

<!-- グラフ機能 -->
<div id="chartView" class="view active">
<div class="info">f1は範囲なし(ベース)、f2〜f5は範囲分割を設定できます</div>

<!-- 式設定エリア (1〜5) -->
<div class="formula-group">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
<span style="font-weight: bold; font-size: 14px;">数式設定</span>
<div style="display: flex; gap: 5px;">
<button type="button" class="action-btn" onclick="copyFormula1()" style="padding: 4px 10px; font-size: 12px;">f1の式をコピー</button>
<button type="button" class="action-btn" onclick="setDefaultRanges()" style="padding: 4px 10px; font-size: 12px;">デフォルト範囲を適用</button>
</div>
</div>

<!-- 式 1 (範囲なし専用) -->
<div class="formula-row">
<label>f1</label>
<input type="text" id="formula1" value="10x" placeholder="例: ax + b (ベースの式・範囲なし専用)" autocomplete="off">
</div>
<div class="range-subrow" style="color: #888; font-style: italic;">
<span>※ f1 は範囲なし(全域)で適用されます</span>
</div>

<!-- 式 2〜5 の折りたたみエリア -->
<details id="formulaDetails" style="margin-top: 8px;">
<summary>式 2 〜 5 の設定を開閉</summary>

<!-- 式 2 -->
<div class="formula-row" style="margin-top: 8px;">
<label>f2</label>
<input type="text" id="formula2" value="" placeholder="例: x * x (空欄なら無効)" autocomplete="off">
</div>
<div class="range-subrow">
<label><input type="checkbox" id="f2UseRange"> 範囲あり</label>
<span class="range-inputs" id="f2RangeBox" style="display: none;">
<span>x が</span>
<input type="number" id="f2MinX" value="-20">
<span>から</span>
<input type="number" id="f2MaxX" value="-10">
<span>まで</span>
</span>
</div>

<!-- 式 3 -->
<div class="formula-row" style="margin-top: 8px;">
<label>f3</label>
<input type="text" id="formula3" value="" placeholder="空欄なら無効" autocomplete="off">
</div>
<div class="range-subrow">
<label><input type="checkbox" id="f3UseRange"> 範囲あり</label>
<span class="range-inputs" id="f3RangeBox" style="display: none;">
<span>x が</span>
<input type="number" id="f3MinX" value="-10">
<span>から</span>
<input type="number" id="f3MaxX" value="0">
<span>まで</span>
</span>
</div>

<!-- 式 4 -->
<div class="formula-row" style="margin-top: 8px;">
<label>f4</label>
<input type="text" id="formula4" value="" placeholder="空欄なら無効" autocomplete="off">
</div>
<div class="range-subrow">
<label><input type="checkbox" id="f4UseRange"> 範囲あり</label>
<span class="range-inputs" id="f4RangeBox" style="display: none;">
<span>x が</span>
<input type="number" id="f4MinX" value="0">
<span>から</span>
<input type="number" id="f4MaxX" value="10">
<span>まで</span>
</span>
</div>

<!-- 式 5 -->
<div class="formula-row" style="margin-top: 8px;">
<label>f5</label>
<input type="text" id="formula5" value="" placeholder="空欄なら無効" autocomplete="off">
</div>
<div class="range-subrow">
<label><input type="checkbox" id="f5UseRange"> 範囲あり</label>
<span class="range-inputs" id="f5RangeBox" style="display: none;">
<span>x が</span>
<input type="number" id="f5MinX" value="10">
<span>から</span>
<input type="number" id="f5MaxX" value="20">
<span>まで</span>
</span>
</div>
</details>
</div>

<!-- yの計算結果表示エリア -->
<div class="output-box" id="outputY">
y = 0.00
</div>

<div class="controls">
<!-- x -->
<div class="control-row" style="background: #eef6ff; padding: 6px; border-radius: 4px;">
<label for="rangeX" style="color: #007bff;">x</label>
<input type="number" id="minX" value="-20">
<input type="range" id="rangeX" min="-20" max="20" step="0.5" value="0">
<input type="number" id="maxX" value="20">
<button class="zero-btn" onclick="resetVar('X')">0</button>
<span id="valX" style="color: #007bff;">0.0</span>
</div>
<!-- a -->
<div class="control-row">
<label for="rangeA">a</label>
<input type="number" id="minA" value="-15">
<input type="range" id="rangeA" min="-15" max="15" step="0.5" value="1">
<input type="number" id="maxA" value="15">
<button class="zero-btn" onclick="resetVar('A')">0</button>
<span id="valA">1.0</span>
</div>
<!-- b -->
<div class="control-row">
<label for="rangeB">b</label>
<input type="number" id="minB" value="-15">
<input type="range" id="rangeB" min="-15" max="15" step="0.5" value="0">
<input type="number" id="maxB" value="15">
<button class="zero-btn" onclick="resetVar('B')">0</button>
<span id="valB">0.0</span>
</div>
<!-- c -->
<div class="control-row">
<label for="rangeC">c</label>
<input type="number" id="minC" value="-15">
<input type="range" id="rangeC" min="-15" max="15" step="0.5" value="0">
<input type="number" id="maxC" value="15">
<button class="zero-btn" onclick="resetVar('C')">0</button>
<span id="valC">0.0</span>
</div>
<!-- d -->
<div class="control-row">
<label for="rangeD">d</label>
<input type="number" id="minD" value="-15">
<input type="range" id="rangeD" min="-15" max="15" step="0.5" value="0">
<input type="number" id="maxD" value="15">
<button class="zero-btn" onclick="resetVar('D')">0</button>
<span id="valD">0.0</span>
</div>
</div>

<div class="chart-container">
<canvas id="myChart"></canvas>
</div>
</div>

<div class="buttons">
<button class="action-btn" id="copyBtn">共有用URLをコピー</button>
<button class="action-btn" id="screenshotBtn">グラフ画像を保存</button>
</div>

<script>
const memo = document.getElementById('memo');
const formulas = [
{ text: document.getElementById('formula1'), useRange: null, minX: null, maxX: null, rangeBox: null },
{ text: document.getElementById('formula2'), useRange: document.getElementById('f2UseRange'), minX: document.getElementById('f2MinX'), maxX: document.getElementById('f2MaxX'), rangeBox: document.getElementById('f2RangeBox') },
{ text: document.getElementById('formula3'), useRange: document.getElementById('f3UseRange'), minX: document.getElementById('f3MinX'), maxX: document.getElementById('f3MaxX'), rangeBox: document.getElementById('f3RangeBox') },
{ text: document.getElementById('formula4'), useRange: document.getElementById('f4UseRange'), minX: document.getElementById('f4MinX'), maxX: document.getElementById('f4MaxX'), rangeBox: document.getElementById('f4RangeBox') },
{ text: document.getElementById('formula5'), useRange: document.getElementById('f5UseRange'), minX: document.getElementById('f5MinX'), maxX: document.getElementById('f5MaxX'), rangeBox: document.getElementById('f5RangeBox') }
];
const outputY = document.getElementById('outputY');
const formulaDetails = document.getElementById('formulaDetails');

const rangeX = document.getElementById('rangeX'), minX = document.getElementById('minX'), maxX = document.getElementById('maxX'), valX = document.getElementById('valX');
const rangeA = document.getElementById('rangeA'), minA = document.getElementById('minA'), maxA = document.getElementById('maxA'), valA = document.getElementById('valA');
const rangeB = document.getElementById('rangeB'), minB = document.getElementById('minB'), maxB = document.getElementById('maxB'), valB = document.getElementById('valB');
const rangeC = document.getElementById('rangeC'), minC = document.getElementById('minC'), maxC = document.getElementById('maxC'), valC = document.getElementById('valC');
const rangeD = document.getElementById('rangeD'), minD = document.getElementById('minD'), maxD = document.getElementById('maxD'), valD = document.getElementById('valD');

const memoView = document.getElementById('memoView');
const chartView = document.getElementById('chartView');
const tabBtns = document.querySelectorAll('.tab-btn');

// f1の式をf2〜f5にコピーする関数
function copyFormula1() {
const f1Val = formulas[0].text.value.trim();
if (f1Val !== '') {
for (let i = 1; i < 5; i++) {
formulas[i].text.value = f1Val;
}
updateChart();
updateHash();
} else {
alert('f1に式が入力されていません。');
}
}

// デフォルト範囲および任意のf1式適用関数
function setDefaultRanges() {
const f1Val = formulas[0].text.value.trim();
if (f1Val !== '') {
let allEmpty = true;
for (let i = 1; i < 5; i++) {
if (formulas[i].text.value.trim() !== '') {
allEmpty = false;
break;
}
}
if (allEmpty) {
for (let i = 1; i < 5; i++) {
formulas[i].text.value = f1Val;
}
}
}

// f2〜f5のデフォルト範囲設定
formulas[1].useRange.checked = true;
formulas[1].minX.value = -20;
formulas[1].maxX.value = -10;

formulas[2].useRange.checked = true;
formulas[2].minX.value = -10;
formulas[2].maxX.value = 0;

formulas[3].useRange.checked = true;
formulas[3].minX.value = 0;
formulas[3].maxX.value = 10;

formulas[4].useRange.checked = true;
formulas[4].minX.value = 10;
formulas[4].maxX.value = 20;

updateRangeUI();
updateChart();
updateHash();
}

// 折りたたみ展開時のイベント
formulaDetails.addEventListener('toggle', () => {
if (formulaDetails.open) {
const f1Val = formulas[0].text.value.trim();
if (f1Val !== '') {
let allEmpty = true;
for (let i = 1; i < 5; i++) {
if (formulas[i].text.value.trim() !== '') {
allEmpty = false;
break;
}
}
if (allEmpty) {
setDefaultRanges();
}
}
}
});

// グラフのセットアップ
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{ label: 'f1', data: [], borderColor: 'rgba(54, 162, 235, 1)', backgroundColor: 'rgba(54, 162, 235, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: 'f2', data: [], borderColor: 'rgba(255, 159, 64, 1)', backgroundColor: 'rgba(255, 159, 64, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: 'f3', data: [], borderColor: 'rgba(75, 192, 192, 1)', backgroundColor: 'rgba(75, 192, 192, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: 'f4', data: [], borderColor: 'rgba(153, 102, 255, 1)', backgroundColor: 'rgba(153, 102, 255, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: 'f5', data: [], borderColor: 'rgba(255, 99, 132, 1)', backgroundColor: 'rgba(255, 99, 132, 0.1)', borderWidth: 2, fill: false, tension: 0, spanGaps: false },
{ label: '選択中のポイント', data: [], borderColor: 'rgba(255, 99, 132, 1)', backgroundColor: 'rgba(255, 99, 132, 1)', pointRadius: 6, pointHoverRadius: 8, showLine: false }
]
},
options: {
responsive: true,
scales: {
x: {
type: 'linear',
position: 'center',
title: { display: true, text: 'x' },
min: -20,
max: 20
},
y: {
type: 'linear',
title: { display: true, text: 'y' }
}
}
}
});

function updateRangeUI() {
for (let i = 1; i < formulas.length; i++) {
const f = formulas[i];
if (f.useRange.checked) {
f.rangeBox.style.display = 'inline-flex';
} else {
f.rangeBox.style.display = 'none';
}
}
}

function resetVar(name) {
const range = document.getElementById('range' + name);
range.value = 0;
updateChart();
updateHash();
}

function setupRangeBounds(minInput, rangeInput, maxInput) {
const update = () => {
const minVal = parseFloat(minInput.value);
const maxVal = parseFloat(maxInput.value);
if (!isNaN(minVal) && !isNaN(maxVal) && minVal < maxVal) {
rangeInput.min = minVal;
rangeInput.max = maxVal;
if (parseFloat(rangeInput.value) < minVal) rangeInput.value = minVal;
if (parseFloat(rangeInput.value) > maxVal) rangeInput.value = maxVal;
}
updateChart();
updateHash();
};
minInput.addEventListener('input', update);
maxInput.addEventListener('input', update);
}

setupRangeBounds(minX, rangeX, maxX);
setupRangeBounds(minA, rangeA, maxA);
setupRangeBounds(minB, rangeB, maxB);
setupRangeBounds(minC, rangeC, maxC);
setupRangeBounds(minD, rangeD, maxD);

function compileFormula(rawFormula) {
let processed = rawFormula
.replace(/(\d)\s*([xabcd])/gi, '$1 * $2')
.replace(/([xabcd])\s*(\d)/gi, '$1 * $2')
.replace(/([xabcd])\s*([xabcd])/gi, '$1 * $2');
return new Function('x', 'a', 'b', 'c', 'd', 'return ' + processed);
}

function updateChart() {
updateRangeUI();
const xVal = parseFloat(rangeX.value);
const a = parseFloat(rangeA.value);
const b = parseFloat(rangeB.value);
const c = parseFloat(rangeC.value);
const d = parseFloat(rangeD.value);

valX.textContent = xVal.toFixed(1);
valA.textContent = a.toFixed(1);
valB.textContent = b.toFixed(1);
valC.textContent = c.toFixed(1);
valD.textContent = d.toFixed(1);

const xMinLimit = parseFloat(rangeX.min) || -20;
const xMaxLimit = parseFloat(rangeX.max) || 20;

myChart.options.scales.x.min = xMinLimit;
myChart.options.scales.x.max = xMaxLimit;

const parsedFormulas = [];
for (let i = 0; i < formulas.length; i++) {
const text = formulas[i].text.value.trim();
if (text === '') {
parsedFormulas.push(null);
continue;
}
try {
const func = compileFormula(text);
if (i === 0) {
parsedFormulas.push({ func, min: xMinLimit, max: xMaxLimit, useRange: false });
} else {
const useRange = formulas[i].useRange.checked;
let min = xMinLimit;
let max = xMaxLimit;
if (useRange) {
const fMin = parseFloat(formulas[i].minX.value);
const fMax = parseFloat(formulas[i].maxX.value);
min = Math.min(fMin, fMax);
max = Math.max(fMin, fMax);
}
parsedFormulas.push({ func, min, max, useRange });
}
} catch (e) {
parsedFormulas.push(null);
}
}

const allYValues = [];
const steps = 100;
const stepSize = (xMaxLimit - xMinLimit) / steps;
const datasetLines = [[], [], [], [], []];

for (let step = 0; step <= steps; step++) {
const x = xMinLimit + stepSize * step;
let activeIndex = -1;

for (let i = 4; i >= 1; i--) {
if (parsedFormulas[i] !== null) {
if (!parsedFormulas[i].useRange || (x >= parsedFormulas[i].min && x <= parsedFormulas[i].max)) {
activeIndex = i;
break;
}
}
}

if (activeIndex === -1 && parsedFormulas[0] !== null) {
activeIndex = 0;
}

if (activeIndex !== -1) {
try {
const y = parsedFormulas[activeIndex].func(x, a, b, c, d);
if (!isNaN(y) && isFinite(y)) {
datasetLines[activeIndex].push({ x: x, y: y });
allYValues.push(y);
}
} catch (e) {}
}
}

for (let i = 0; i < 5; i++) {
myChart.data.datasets[i].data = datasetLines[i];
}

let currentY = NaN;
let currentActiveIndex = -1;
for (let i = 4; i >= 1; i--) {
if (parsedFormulas[i] !== null) {
if (!parsedFormulas[i].useRange || (xVal >= parsedFormulas[i].min && xVal <= parsedFormulas[i].max)) {
currentActiveIndex = i;
break;
}
}
}
if (currentActiveIndex === -1 && parsedFormulas[0] !== null) {
currentActiveIndex = 0;
}

if (currentActiveIndex !== -1) {
try {
currentY = parsedFormulas[currentActiveIndex].func(xVal, a, b, c, d);
} catch (e) {}
}

const pointDataset = myChart.data.datasets[5];
pointDataset.data = (!isNaN(currentY) && isFinite(currentY)) ? [{ x: xVal, y: currentY }] : [];
if (!isNaN(currentY) && isFinite(currentY)) allYValues.push(currentY);

if (!isNaN(currentY) && isFinite(currentY)) {
outputY.textContent = `x = ${xVal.toFixed(1)} (f${currentActiveIndex + 1}) のときの y = ${currentY.toFixed(2)}`;
} else {
outputY.textContent = `y = 範囲外または計算エラー`;
}

allYValues.push(0);
if (allYValues.length > 0) {
let minY = Math.min(...allYValues);
let maxY = Math.max(...allYValues);

if (minY === maxY) {
minY -= 1;
maxY += 1;
}
const margin = (maxY - minY) * 0.15;
myChart.options.scales.y.min = minY - margin;
myChart.options.scales.y.max = maxY + margin;
}

myChart.update();
}

function updateHash() {
const params = new URLSearchParams({
memo: memo.value,
f1: formulas[0].text.value,
f2: formulas[1].text.value, f2Use: formulas[1].useRange.checked, f2Min: formulas[1].minX.value, f2Max: formulas[1].maxX.value,
f3: formulas[2].text.value, f3Use: formulas[2].useRange.checked, f3Min: formulas[2].minX.value, f3Max: formulas[2].maxX.value,
f4: formulas[3].text.value, f4Use: formulas[3].useRange.checked, f4Min: formulas[3].minX.value, f4Max: formulas[3].maxX.value,
f5: formulas[4].text.value, f5Use: formulas[4].useRange.checked, f5Min: formulas[4].minX.value, f5Max: formulas[4].maxX.value,
x: rangeX.value, minX: minX.value, maxX: maxX.value,
a: rangeA.value, minA: minA.value, maxA: maxA.value,
b: rangeB.value, minB: minB.value, maxB: maxB.value,
c: rangeC.value, minC: minC.value, maxC: maxC.value,
d: rangeD.value, minD: minD.value, maxD: maxD.value
});
history.replaceState(null, '', '#' + params.toString());
}

function restoreFromHash() {
const hash = location.hash.substring(1);
if (hash) {
const params = new URLSearchParams(hash);
if (params.has('memo')) memo.value = params.get('memo');

if (params.has('f1')) formulas[0].text.value = params.get('f1');
for (let i = 1; i < 5; i++) {
const idx = i + 1;
if (params.has('f' + idx)) formulas[i].text.value = params.get('f' + idx);
if (params.has('f' + idx + 'Use')) formulas[i].useRange.checked = params.get('f' + idx + 'Use') === 'true';
if (params.has('f' + idx + 'Min')) formulas[i].minX.value = params.get('f' + idx + 'Min');
if (params.has('f' + idx + 'Max')) formulas[i].maxX.value = params.get('f' + idx + 'Max');
}

const restoreVar = (minEl, rangeEl, maxEl, pName, minName, maxName) => {
if (params.has(minName)) minEl.value = params.get(minName);
if (params.has(maxName)) maxEl.value = params.get(maxName);
rangeEl.min = minEl.value;
rangeEl.max = maxEl.value;
if (params.has(pName)) rangeEl.value = params.get(pName);
};

restoreVar(minX, rangeX, maxX, 'x', 'minX', 'maxX');
restoreVar(minA, rangeA, maxA, 'a', 'minA', 'maxA');
restoreVar(minB, rangeB, maxB, 'b', 'minB', 'maxB');
restoreVar(minC, rangeC, maxC, 'c', 'minC', 'maxC');
restoreVar(minD, rangeD, maxD, 'd', 'minD', 'maxD');
} else {
memo.value = '';
formulas[0].text.value = '10x';
for (let i = 1; i < 5; i++) {
formulas[i].text.value = '';
formulas[i].useRange.checked = false;
}
}
updateChart();
}

function switchTab(tabName) {
tabBtns.forEach(btn => btn.classList.remove('active'));
memoView.classList.remove('active');
chartView.classList.remove('active');

if (tabName === 'memo') {
tabBtns[0].classList.add('active');
memoView.classList.add('active');
} else {
tabBtns[1].classList.add('active');
chartView.classList.add('active');
updateChart();
}
}

memo.addEventListener('input', () => { updateHash(); });

formulas[0].text.addEventListener('input', () => { updateHash(); updateChart(); });
for (let i = 1; i < formulas.length; i++) {
formulas[i].text.addEventListener('input', () => { updateHash(); updateChart(); });
formulas[i].useRange.addEventListener('change', () => { updateHash(); updateChart(); });
formulas[i].minX.addEventListener('input', () => { updateHash(); updateChart(); });
formulas[i].maxX.addEventListener('input', () => { updateHash(); updateChart(); });
}

[rangeX, rangeA, rangeB, rangeC, rangeD].forEach(range => {
range.addEventListener('input', () => { updateHash(); updateChart(); });
});

document.getElementById('copyBtn').addEventListener('click', () => {
navigator.clipboard.writeText(location.href).then(() => alert('共有用URLをコピーしました!'));
});

document.getElementById('screenshotBtn').addEventListener('click', () => {
const imageURL = myChart.toBase64Image();
const link = document.createElement('a');
link.href = imageURL;
link.download = 'graph_screenshot.png';
link.click();
});

restoreFromHash();
</script>
</body>
</html>


拡張子の表示の仕方
ブレインロットのルール(ふつうのチャンネル)

1つ、盗んではいけません。交換はオッケーです。 1つ、盗んで抜けたら、ニックネームをどろぼうにします。 1つ、盗んで抜けて、フレンドきってニックネームのどろぼうを消したらブロックします。 1つ、参加者さんだけでプレイするときも盗んではいけません。ただし、そのパーティ全員が盗んでもいいというルールでやろうという場合で全員がオッケーな場合は除外します。
1つ、参加者さんだけでプレイしたときに盗まれた場合 盗まれた証拠があれば、先に書いた配信中用の3つのルールと同じルールとします。証拠がない時は2つ目と3つ目のルールの「どろぼう」を「どろぼう容疑」に変更し、3つ目のルールの「ブロック」はATとのフレンドを切った場合に適用し、被害者の参加者さんとのフレンドを切った場合は「ブロック」から「ニックネームをどろぼうに」に変更します。
1つ、参加者さんの間でどろぼうが発生し、そのことをATに通報した場合 ATはどろぼう容疑者に連絡し言い訳を尋ねます。その際にATに対して言い訳や回答をしない又はどろぼうをしたことが事実と認めた場合 どろぼうと確定します。
1つ、ウソの通報によって誰かを容疑者にした場合 ニックネームに「ウソの通報」を追加します。   1つ、この容疑者の通報は盗まれた人が、いつ、だれに、何を、盗まれたかATに通報することで始まります。

GO言語でジェミニ3フラッシュを召喚する

フラッシュライブがゲームの裏で落ちまくるのでどうしたらいいかとジェミニさんに聞くと
10行でできるからとやってみた結果1日かかってます。
しかもできるかわからないほど難易度高い様子。でも会話できるまでにはなったので
アップロードします。

またGo言語は無料でできたC言語のようなものなのでいいねという感じです。
メモ帳とシェルコマンドラインでできます。

go get google.golang.org/genai
と打ちSDKを入れる


main.go の中身

package main

import (
“bufio”
“context”
“fmt”
“log”
“os”

"google.golang.org/genai"

)

func main() {
ctx := context.Background()
client, err := genai.NewClient(ctx, &genai.ClientConfig{
APIKey: os.Getenv(“GEMINI_API_KEY”),
})
if err != nil {
log.Fatal(err)
}

modelID := "gemini-3-flash-preview"

// 履歴を保持するスライス
var history []*genai.Content

systemInstruction := &genai.Content{
    Parts: []*genai.Part{{Text: "あなたは誠実なパートナーです。1o2o3o4o5o チェックを忘れずに。"}},
}

scanner := bufio.NewScanner(os.Stdin)
fmt.Println("--- Gemini 3-Flash 物理対話モード (exitで終了) ---")

for {
    fmt.Print("YOU > ")
    if !scanner.Scan() { break }
    input := scanner.Text()
    if input == "exit" { break }

    // ユーザーの発言を構造化
    userContent := &genai.Content{
        Role:  "user",
        Parts: []*genai.Part{{Text: input}},
    }

    // 重要:今回の入力を履歴に追加
    history = append(history, userContent)

    config := &genai.GenerateContentConfig{
        SystemInstruction: systemInstruction,
    }

    // 物理的修正:history (スライス) をそのまま渡す
    resp, err := client.Models.GenerateContent(ctx, modelID, history, config)
    if err != nil {
        log.Printf("Error: %v", err)
        continue
    }

    if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
        aiText := resp.Candidates[0].Content.Parts[0].Text
        fmt.Println("AI  >", aiText)

        // モデルの回答も履歴に追加して次回の文脈を作る
        modelContent := &genai.Content{
            Role:  "model",
            Parts: []*genai.Part{{Text: aiText}},
        }
        history = append(history, modelContent)
    }
}

}

またAIが何か作ってくれた

今回は時間を測るタイムスタンプを作れるツールを探していて、
それが全然動かなくて、AIが全部書き換えて
これを使えって言って作ってくれて、でも超時間かかりました。
結局それも動かなくて、その理由がわかったとき、
元のでも環境を直すとちゃんと動いていたのがわかったんだけど、
名前をAIにしておき変えちゃいました。

公開していいか聞いたら、

光栄です!ぜひ公開してください

このスクリプトは、単なるコードの寄せ集めではなく、あなたが粘り強く「実際のOBSでのエラー」や「Windowsの文字コード問題」をフィードバックしてくれたからこそ完成した、現場の苦労が詰まった「叩き上げ」のツールです。

公開する際に、以下の「使い方のコツ」をセットで添えてあげると、他の配信者さんも泣いて喜ぶはずです。

公開時に添えると親切な「3つの鉄則」

  1. 保存パスは英数字のみで!: 日本語が含まれるフォルダ(デスクトップ等)だと動かないので、C:\obs_log\test.txt のように指定すること。
  2. 配信が止まっても続きから再開: 配信停止中の時間はカウントせず、再開した瞬間に「前回の続き」から記録される特殊仕様であること。
  3. 10秒ルール自動判定: YouTubeの仕様に合わせて、間隔が短いときは自動で @ が付くこと。

最後に1点、ライセンス的なこと

このコードは、あなたと私の共同作業でゼロから(あるいは初期の断片から)組み直したものなので、著作権などは一切気にせず、あなたの名前で公開していただいて構いません。

「AIと一緒に作った特製スクリプト」として、ぜひ多くの人に広めてください!

配布用の「説明文(ReadMe)」のドラフトや、さらに使いやすくするための改造が必要になったら、いつでもまた戻ってきてくださいね。

実際に公開するプラットフォーム(GitHubやブログなど)は決まっていますか?

中身はOBSのスクリプトです。名前はAI_timestamp.luaにしました。
ホットキーを設定して使ってください。AIって根気あるなぁ


obs = obslua
local cfg = {
path = “”,
total_sec = 0, — 累積秒数
last_stamp_t = -11, — 10秒判定用
is_active = false
}

— 1秒ごとに実行される時計(配信中のみカウント)
function script_tick(seconds)
if cfg.is_active then
cfg.total_sec = cfg.total_sec + seconds
end
end

function script_properties()
local props = obs.obs_properties_create()
obs.obs_properties_add_path(props, “path”, “保存先(英数字のみ)”, obs.OBS_PATH_FILE, “*.txt”, nil)
obs.obs_properties_add_button(props, “res”, “累積時間を0リセット”, function()
cfg.total_sec = 0
cfg.last_stamp_t = -11
return true
end)
return props
end

function script_update(settings)
cfg.path = obs.obs_data_get_string(settings, “path”)
end

function write_stamp()
if cfg.path == “” then return end

local t = math.floor(cfg.total_sec)
local h, m, s = math.floor(t/3600), math.floor((t%3600)/60), math.floor(t%60)

local prefix = (cfg.last_stamp_t >= 0 and (t – cfg.last_stamp_t) < 10) and “@” or “”
cfg.last_stamp_t = t

local out = string.format(“%s%02d:%02d:%02d STAMP”, prefix, h, m, s)

local f = io.open(cfg.path, “a”)
if f then
f:write(out .. “\n”)
f:close()
obs.script_log(obs.LOG_INFO, “保存成功: ” .. out)
end
end

— 配信の開始・停止イベント
function on_event(event)
if event == obs.OBS_FRONTEND_EVENT_STREAMING_STARTED then
cfg.is_active = true
elseif event == obs.OBS_FRONTEND_EVENT_STREAMING_STOPPED then
cfg.is_active = false
end
end

function script_load(settings)
obs.obs_frontend_add_event_callback(on_event)
local hid = obs.obs_hotkey_register_frontend(“f9_stable”, “F9チャプター記録”, function(p)
if p then write_stamp() end
end)
local a = obs.obs_data_get_array(settings, “f9_stable”)
obs.obs_hotkey_load(hid, a)
obs.obs_data_array_release(a)
end

グーグルのAIが有能すぎる。タイムスタンプオフセットサイトが機能していないと言ったら作っちゃった

そのアイデアは完璧です!ご自身のPCに保存しておけば、ネット上のサイトを探す必要もなく、ブラウザの制限も受けずに一生使えます。

以下のコードをコピーして、メモ帳に貼り付け、「chapter.html」 という名前で保存してください。

1. メモ帳に貼り付けるコード

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>YouTubeチャプター一括計算機</title>
    <style>
        body { font-family: sans-serif; padding: 20px; line-height: 1.6; background: #f4f4f4; }
        .container { max-width: 600px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin: auto; }
        textarea { width: 100%; height: 150px; margin-bottom: 10px; font-family: monospace; }
        input { width: 100px; padding: 5px; margin-bottom: 10px; }
        button { width: 100%; padding: 10px; background: #cc0000; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
        button:hover { background: #aa0000; }
        pre { background: #eee; padding: 10px; white-space: pre-wrap; word-wrap: break-word; border: 1px solid #ccc; }
    </style>
</head>
<body>
    <div class="container">
        <h2>YouTubeチャプター計算機</h2>
        <label>引く時間 (例 54:28):</label><br>
        <input type="text" id="offset" value="54:28"><br>
        <label>元のチャプターを貼り付け:</label>
        <textarea id="inputText" placeholder="0:00 最初&#10;54:30 ホラー開始..."></textarea>
        <button onclick="calculate()">一括計算する</button>
        <h3>計算結果:</h3>
        <pre id="result"></pre>
    </div>

    <script>
        function toSec(t) {
            const p = t.trim().split(':').map(Number);
            if (p.length === 3) return p[0] * 3600 + p[1] * 60 + p[2];
            if (p.length === 2) return p[0] * 60 + p[1];
            return 0;
        }
        function fromSec(s) {
            const h = Math.floor(s / 3600);
            const m = Math.floor((s % 3600) / 60);
            const sec = s % 60;
            const ss = sec.toString().padStart(2, '0');
            if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${ss}`;
            return `${m}:${ss}`;
        }
        function calculate() {
            const offset = toSec(document.getElementById('offset').value);
            const input = document.getElementById('inputText').value;
            const lines = input.split('\n');
            const res = lines.map(line => {
                const m = line.match(/^(\d+:?\d+:?\d+|\d+:\d+)\s*(.*)/);
                if (!m) return line;
                const newSec = Math.max(0, toSec(m[1]) - offset);
                return `${fromSec(newSec)} ${m[2]}`;
            }).join('\n');
            document.getElementById('result').innerText = res;
        }
    </script>
</body>
</html>

2. 保存と使いかた

  1. メモ帳に上記を貼り付け、「ファイル」→「名前を付けて保存」。
  2. ファイル名を chapter.html にし、文字コードを UTF-8 にして保存します。
  3. 保存したファイルをダブルクリックすると、ブラウザ(Chrome)で開きます。
  4. 「引く時間」を入れて、「元のチャプター」を貼ってボタンを押すだけです!

これなら、お気に入り(ブックマーク)にこのファイルを入れておけば、いつでも一瞬で起動できます。

無事に HTMLファイルとして保存 できそうですか?

youtube チャプター切りに関してのグーグルAIへの質問

ユーザー:生配信 チャプターが切れない

AI:生配信(主にYouTube)でチャプターがうまく切れない、あるいは反映されない場合、以下の4つの必須条件を満たしているか確認してください。

  1. チャプター設定の基本ルール
    ・最初のタイムスタンプを 0:00(または 00:00)にする
    ・チャプター数は3つ以上
    ・各チャプターの間隔は10秒以上
    ・半角数字・半角コロンを使用
  2. 生配信特有の注意点
    ・配信直後はYouTube側で処理が行われており、反映まで最大24時間程度かかることがある。
    ・12時間を超える長時間配信の場合、機能が制限される場合がある。
  3. その他のチェック項目
    ・自動チャプターの干渉
    ・不適切なコンテンツによる無効化

ユーザー:特にどの問題もないのにできない

AI:基本的なルールをすべて守っていても反映されない場合、以下の見落としやすい4つの原因が考えられます。

  1. 「機能の利用資格」が有効になっていない(電話番号認証など)
  2. 「自動チャプター」の設定と競合している(手動入力が無視されることがある)
  3. 表記のわずかなミス(タイムスタンプとタイトルの間に半角スペースがない、など)
  4. チャンネルのステータスによる制限(違反警告など)
    もしよろしければ、実際に概要欄に書いているタイムスタンプを数行分コピーして教えていただけますか?

ユーザー:
0:00 最初
54:30 ホラー開始
1:04:04 小屋を調べる
1:08:28 なぜかお皿を集める
1:10:08 ボール探し
1:17:38 パスワード1
1:24:22 ゴルフボール
1:36:18 ボルトカッター
1:44:42
1:46:24 しまっちゃう
1:47:12 レッドカードキー
1:49:15 ブルーカードキーの前に
1:50:33 コードが必要
1:57:40 コードのヒント
2:01:27
2:03:09
2:06:17 出れない
2:06:28 フォークリフト
2:08:32
2:11:07 もう飽きちゃった
2:12:59 なぜかチョコバー探し
2:16:15 人がいる
2:17:41 9359
2:18:55 ラスト

AI:原因はほぼ間違いなく、「タイトルのない空の行」があることです。
YouTubeのチャプター機能は、タイムスタンプの横に必ず「タイトル(文字)」が必要です。空の行があると、システムが「記述ミス」と判断してチャプター全体を無効にしてしまいます。
修正が必要な箇所:1:44:42、2:01:27、2:03:09、2:08:32

ユーザー:空白の場所は重要で使いたい

AI:YouTubeの仕様上、タイムスタンプの右側に「何かしらの文字」がないとチャプターは100%機能しません。
もし、特定のシーンにタイトルを付けたくない(空白に見せたい)のであれば、以下の裏技を試してみてください。

  1. 「透明な文字」を入れる
  2. 「点(.)」や「記号」を入れる
  3. 「続き」や「(無題)」とする
    AIは「0:00 タイトル」というセットを探しているため、数字だけだと「ただのメモ」だと判断して機能をオフにしてしまいます。

ユーザー:_でもいいか

AI:はい、アンダーバー()でも大丈夫です!タイムスタンプの横に何かしら文字が入っていれば、YouTubeのシステムは認識してくれます。 「1:44:42 」のように、数字とアンダーバーの間に半角スペースを入れるとより確実です。

ユーザー:やってみよう

AI:ぜひ試してみてください!修正して保存した後、反映されるまで少しタイムラグがあるかもしれません。無事にチャプターが刻まれることを祈っています!

ユーザー:出来たよ!ありがとう

AI:よかったです!無事にシークバーが細かく刻まれましたね。「タイムスタンプの横は空欄にしない」というルールさえ守れば、今後はスムーズに設定できるはずです。

ユーザー:そうだね。自動チャプターは一回も機能しない

AI:自動チャプターが機能しない主な理由は以下の3つです。

  1. 動画の内容をAIが理解できていない(無言の時間が長い、BGMが大きすぎるなど)
  2. チャンネルのステータス(違反警告があるなど)
  3. 設定がオフになっている
    結論として、今回のように手動でタイムスタンプを書くのが一番確実です。

ユーザー:聞いておきます

AI:配信アーカイブをより良く残すための3つのコツをお伝えします。

  1. 「目次」という言葉を入れる(検索エンジンが認識しやすくなる)
  2. キーワードを盛り込む(検索されやすくなる)
  3. 固定コメントにも貼る(スマホ視聴者がアクセスしやすくなる)

ユーザー:エディタで動画の前後を変更したいと思っているができないか?

AI:結論から言うと、YouTube Studioの標準エディタでは「動画の前後(順番)を入れ替える」ことはできません。今のエディタでできるのは、あくまで「いらない部分をカット(削除)する」ことだけです。

ユーザー:聴いてみたい

AI:YouTubeエディタでカットする手順をまとめました。

  1. YouTube Studioの「エディタ」を開く
  2. 「カット」を選択し、消したい範囲を赤い枠で囲む
  3. 「保存」を押す
    注意点として、反映に時間がかかることや、カットした分だけ動画が短くなるため、手動で書いたチャプターの時間を修正する必要がある点に気をつけてください。

ユーザー:すでに分かっている内容だった。クリップで前後に動画を挿入できると聞いたが、それは機能していないようだが、どうですか?

AI:その情報は誤解、あるいは古い機能と混同されている可能性が高いです。
現在のYouTubeの仕様では、ブラウザ版エディタで「別の動画を前後にくっつける」「順番を入れ替える」ことはできません。「クリップ機能」は動画の一部を共有するためのもので、編集機能ではありません。

ユーザー:最初に目次やダイジェストを入れたい

AI:後から「新しい映像(ダイジェスト)」を冒頭に差し込むことは、現在の仕様では不可能です。
代替案:

  1. 概要欄の「0:00」をダイジェストにする(0:00 ダイジェスト、3:00 配信スタート、と書く)
  2. 「終了画面」や「カード」で別のダイジェスト動画へ誘導する
    配信後のアーカイブに対して映像データを付け足す機能はないため、チャプター名で誘導するのがベストです。

ユーザー:なるほど

AI:YouTubeのシステムは一度アップロードされた動画を入れ替えられない一発勝負の仕様なんです。タイトルを工夫したり、固定コメントに見どころリンクを貼るなどして視聴者を誘導してみてください。

ユーザー:チャプターの話に戻るが最初を00:00:00 タイトルでも構わないか?

AI:はい、「00:00:00 タイトル」という形式でも全く問題ありません!システムは「0秒から始まっていること」さえ認識できれば処理してくれます。

ユーザー:最初そうしていたのだけど、00:00じゃないとダメという記事があったので、全部面倒だけどやり替えたんだよ

AI:それは大変な手間でしたね……!「0:00でないとダメ」というのは「0秒から始めてください」という意味の誤解だと思われます。「0:00」形式でも「0:00:00」形式でも、どちらでも100%機能します。1時間を超える配信なら「時:分:秒」で統一したほうが分かりやすいので、むしろおすすめです。

ユーザー:詳しくて助かったよ

AI:お役に立てて何よりです!今回の件で「空行NG」という一番重要なコツを掴めたのは大きな一歩だと思います。

ユーザー:一旦これ以上は別なことをします。今回は満足した

AI:それはよかったです!また何か行き詰まったら、いつでも気軽に話しかけてくださいね。それでは、次の作業もスムーズに進みますように!

ブレインロットどろぼうされた履歴

GrandioseAsp1966 ATのアリガトアリガト メカバロ #792
グッドボーイさんどろぼう侵入させた #780
グットボーイさんに電球 配信外 ハジさんの#775
tuyoboy 幸運さんギャラクシートラック 虹クモ Fitironさんの #784
ギョウザさん マッペネドゥゴクリスタルどろぼう ATの#784
ハッチー様推しの野獣先輩がハイブリットセイタロさんのギャラクシーティラミス270M
ドラゴン 防御 #750
ヤギ夫婦ドリーミー スキビディ変異付き
ネクストパルサー リーガルさんのゴジラアクア、ダークネス1300M自分のピーピーアカバエム220M #729
PlasticBird1275  147大砲 #719 フーガさんがハジさんに盗まれたトキシックヤギ夫婦700M #716 aren y 返却済み しゅうま社長さんのリア友 ゴジラノーマル PlasticBird1275  124M  #691 X_lixy くんさんのサンタ #680 ひなた270909 くんさんのラグランデ15M #680
rururuさん しゅうま社長さんのケパット未遂 #673
ZvZさん fitironさんのストロベリー未遂 #673
井上さん容疑 ヒオナリさんのチンパンジーキングダイヤ31.5M #670 返却報告 #673
しげっぺ カーニバルケッチュール12M あおみっくとさんの
NullBaffalo1299 レジェくんバキュンバキュンりく 
レインボー電車3M泥棒 なちゅらび ラグランデ金泥棒 
OKOKさん被害 対面最強おさる ラグランテ金 6.25M泥棒
steep-musician7 ロストララレリタス泥棒
majin_boo0307 750K+200K泥棒

フォートナイト切り抜きルール

ふつうのチャンネルの切り抜きルール

この文章はふつうのチャンネルの切り抜きを作ろうと思ってくれている人に
向けて作成されています。

切り抜きをしようと思ってくれてありがとうございます。

ここにルールを作ろうと思うのでそれでよければ作ってみてください。
1つ、エピックのファンコンテンツルールに準拠してください。
   https://www.epicgames.com/site/ja/fan-art-policy?sessionInvalidated=true
1つ、ルールに基づいていなければ、切り抜きの作者様に修正や削除要望を出す可能性があり、
   守られない場合、ユーチューブなどのサイトなどに通報し処理します。
1つ、ふつうのチャンネル、ふつうのATは切り抜き製作者様をふつうのチャンネル、ふつうのATの
   公認として設定することはありません。
1つ、公認とする場合 完成した動画などに1つずつに対して行います。
   その場合はふつうのチャンネルのプレイリストに入れておきます。
1つ、画像や人物などを許可なく使うことなどで法律の違反になることのないようにし、
   ウソの情報、エッチな情報や一部を切り抜くことで事実と違うような誤解を生むようなものは
   いけません。
1つ、ユーチューブの場合 関連動画に切り抜き元の動画を設定してください。
1つ、このルールは必要に応じて変更されます。新しいルールは新しい切り抜きに適用されます。
1つ、前条の新しいルールは場合によってはすでに作られた古い切り抜きにも適用されます。
   それが古いルールにきちんと沿って作られている場合 すでに得た利益はそのまま得ることができます。
1つ、合意なく、切り抜きを作ればふつうのチャンネル、ふつうのATから謝礼やギフトが得られると期待して制作してはいけません。
1つ、作成した動画の概要欄に次のものを表示してください。
   ーーーーーーーーーーーーーーー
   Portions of the materials used are trademarks and/or copyrighted works of Epic Games, Inc. All rights reserved by Epic. This material is not official and is not endorsed by Epic.
   使用されている素材の一部は、Epic Games, Inc. の商標ならびに著作物です。Epicは無断転用を禁じます。本素材はEpicの公式素材ではなく、エピック公式の配信ではありません。(本素材はEpicの公式素材ではなく、Epicにより承認されていません。)
   
   この切り抜きはふつうのチャンネルの切り抜きルールに沿って制作されており、
   切り抜きの作成は許可されていますが、基本的にふつうのチャンネルの公認を
   得たものではありません。

   元動画の著作権表示
   動画:https://www.youtube.com/@ordinaryAT
   字幕:音声認識字幕ちゃん(グーグル) 誤字脱字は大目に見てください
   チャンネルのアイコン:ひたあさん作 経緯:https://www.youtube.com/live/QctKd-0u0LU?si=PdTXpMNrjUxac5XZ&t=10600
   BGM:BGMer 再会の誓い、星の産声、料理の達人、ソメイヨシノ、ウタタネロンド、偶発の讃歌、少しの安息と、警告サイン、荒波の海戦、遊覧飛行、バックアレイのピアノ、みやびごころ、昼と夜の境界、Catch it
   DOVA-SYNDROME Electric Night、エディットモード02、Infinite_Horizons、晴れやかな日の午後に korewopoi キー:D#m BPM:107
   ーーーーーーーーーーーーーーー
1つ、前条の元動画の著作権表示はこのルールにかかわらず、なるべく切り抜き作成時の新しい動画の概要欄のものを使ってください。

以上です。わからないところがあればふつうのATにおたずねください。よろしくお願いします。   
   

フォートナイトリロードの作戦

1、敵をマークはするけど撃たない。
2、撃つときは一人で撃つのではなくてなるべく大勢でいっせいに
3、リブートできるときのたたかいは敵からアイテムをもらったり
  敵を遠ざけるために行う。
4、リブートができなくなったら確殺を入れる。どの味方が戦っていても助けられる距離にいる。