horiyouta commited on
Commit
861a629
·
1 Parent(s): d8c161f

2508181817

Browse files
Files changed (2) hide show
  1. web/index.html +9 -1
  2. web/script.js +117 -10
web/index.html CHANGED
@@ -100,12 +100,20 @@
100
  <div id="item-choices" class="flex gap-8"></div>
101
  <div id="choice-info-area" class="mt-8 w-3/4 h-64 glass-pane flex gap-4 p-4">
102
  <div id="choice-description" class="w-1/2 text-lg text-gray-300">
103
- <p>アイテムにカーソルを合わせると、ここに説明が表示されます。</p>
104
  </div>
105
  <div id="choice-preview-area" class="w-1/2 bg-black/30 rounded-lg relative overflow-hidden">
106
  <div class="idle-particles"></div>
107
  </div>
108
  </div>
 
 
 
 
 
 
 
 
109
  </div>
110
  <div id="animation-modal" class="fixed inset-0 bg-gray-900 bg-opacity-90 backdrop-blur-sm flex justify-center items-center z-50 hidden">
111
  <div id="visualization-area" class="w-[95%] h-[90%] glass-pane relative p-4 overflow-hidden">
 
100
  <div id="item-choices" class="flex gap-8"></div>
101
  <div id="choice-info-area" class="mt-8 w-3/4 h-64 glass-pane flex gap-4 p-4">
102
  <div id="choice-description" class="w-1/2 text-lg text-gray-300">
103
+ <p>アイテムにカーソルを合わせるかタップすると、ここに説明が表示されます。</p>
104
  </div>
105
  <div id="choice-preview-area" class="w-1/2 bg-black/30 rounded-lg relative overflow-hidden">
106
  <div class="idle-particles"></div>
107
  </div>
108
  </div>
109
+ <!-- ★★★ ここから追加 ★★★ -->
110
+ <div id="choice-selection-footer" class="mt-4 text-center opacity-0 transition-opacity duration-300">
111
+ <button id="confirm-choice-btn" class="btn btn-primary text-xl px-10 py-3" disabled>
112
+ <i class="fas fa-check-circle mr-2"></i> このアイテムにする
113
+ </button>
114
+ <!-- 選択されたアイテムのIDを保持するための非表示要素 -->
115
+ <input type="hidden" id="selected-choice-id">
116
+ </div>
117
  </div>
118
  <div id="animation-modal" class="fixed inset-0 bg-gray-900 bg-opacity-90 backdrop-blur-sm flex justify-center items-center z-50 hidden">
119
  <div id="visualization-area" class="w-[95%] h-[90%] glass-pane relative p-4 overflow-hidden">
web/script.js CHANGED
@@ -30,12 +30,14 @@ let playerLayers = []; // 現在モデルに配置されているレイヤー
30
  let playerHP = 100;
31
  let enemyHP = 100;
32
  let currentStage = 1;
33
- let isBattleInProgress = false; // ★★★ バトルループ中のフラグを追加
34
- let draggedItem = null; // { type, layer, index }
35
  let dragOverIndex = null; // 並び替え先のインデックス
36
  let wasDroppedSuccessfully = false; // ★★★ このフラグを追加
37
  let currentEnemy = { image_b64: null, label: null }; // ★★★ クライアント側で敵の状態を保持
38
  let ENEMY_MAX_HP = 100;
 
 
39
  const PLAYER_MAX_HP = 100;
40
 
41
  const allAvailableLayers = [
@@ -184,30 +186,53 @@ function updatePlayerModelUI() {
184
  }
185
  });
186
 
 
 
 
 
 
 
 
 
 
187
  if (playerLayers.length === 0) {
188
  const p = document.createElement('p');
189
  p.className = 'text-gray-400 text-center p-4';
190
- p.textContent = 'インベントリからレイヤー(層)をここにドラッグ&ドロップしてください。';
191
  modelArea.appendChild(p);
192
  } else {
 
193
  playerLayers.forEach((layer, index) => {
194
  const div = document.createElement('div');
195
  div.className = `player-layer ${layer.rarity === 'gold' ? 'gold-rare' : ''}`;
196
  div.draggable = true;
197
  div.dataset.index = index;
 
 
 
 
 
 
198
  div.innerHTML = `<span><i class="fas ${layer.icon} mr-2"></i>${layer.name}</span><button class="text-red-400 hover:text-red-200 text-lg">&times;</button>`;
199
- div.querySelector('button').onclick = () => removeLayer(index);
 
 
 
200
 
 
201
  div.addEventListener('dragstart', (e) => handleDragStart(e, index, layer));
202
  div.addEventListener('dragend', handleDragEnd);
203
  div.addEventListener('dragenter', (e) => handleDragEnterModelItem(e, index));
204
  div.addEventListener('dragleave', (e) => e.currentTarget.classList.remove('is-dragged-over'));
205
 
 
 
 
206
  modelArea.appendChild(div);
 
207
  });
208
  }
209
  battleBtn.disabled = playerLayers.length === 0;
210
- // UI更新後に接続線を描画
211
  requestAnimationFrame(drawLayerConnections);
212
  }
213
 
@@ -231,8 +256,11 @@ function updatePlayerInventoryUI() {
231
 
232
  // イベントリスナー
233
  item.addEventListener('click', () => {
 
234
  showDescription(layer, document.getElementById('layer-description'));
235
  runPreviewAnimation(layer, previewAnimationArea);
 
 
236
  });
237
  item.addEventListener('dragstart', (e) => handleInventoryDragStart(e, layer));
238
  item.addEventListener('dragend', handleDragEnd);
@@ -288,7 +316,7 @@ function updateHpBars() {
288
  async function fetchNewEnemy() {
289
  const response = await fetch('/api/get_enemy');
290
  const enemyData = await response.json();
291
-
292
  // ★★★ グローバル変数に保存
293
  currentEnemy = {
294
  image_b64: enemyData.image_b64,
@@ -376,7 +404,7 @@ function dropOnModelArea(ev) {
376
  } else {
377
  logMessage(`レイヤー追加: ${draggedItem.layer.name}`, 'action');
378
  }
379
- } else {
380
  logMessage('モデルの順序を変更しました。', 'info');
381
  }
382
 
@@ -550,6 +578,13 @@ function showItemSelection() {
550
  itemSelectionModal.classList.add('flex');
551
  itemChoices.innerHTML = '';
552
 
 
 
 
 
 
 
 
553
  // ★★★ ステージに応じたゴールドレア出現確率を計算
554
  // ステージ1: 10%, ステージ2: 20%, ステージ3: 30%, ステージ4: 40%, ステージ5: 50%
555
  const goldChance = Math.min(0.1 * currentStage, 0.5);
@@ -595,9 +630,9 @@ function showItemSelection() {
595
  // 選択肢のUIを生成
596
  finalChoices.forEach(layer => {
597
  const item = document.createElement('div');
598
- // ★★★ レアリティクラスを追加
599
  item.className = `layer-item w-48 h-48 flex flex-col justify-center ${layer.rarity === 'gold' ? 'gold-rare' : ''}`;
600
  item.innerHTML = `<i class="fas ${layer.icon} layer-icon text-5xl"></i><p class="text-lg mt-2">${layer.name}</p>`;
 
601
 
602
  // ★★★ ゴールドレアの場合、キラキラエフェクトを追加
603
  if (layer.rarity === 'gold') {
@@ -614,11 +649,25 @@ function showItemSelection() {
614
  item.appendChild(sparkleContainer);
615
  }
616
 
617
- item.onclick = () => selectItem(layer);
618
- item.addEventListener('mouseenter', () => {
 
 
 
 
 
 
 
 
619
  showDescription(layer, choiceDescription);
620
  runPreviewAnimation(layer, choicePreviewArea);
 
 
 
 
 
621
  });
 
622
  itemChoices.appendChild(item);
623
  });
624
  }
@@ -788,6 +837,54 @@ async function runPreviewAnimation(layer, previewArea) {
788
  }
789
  }
790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
791
  // --- Main Game Logic ---
792
  function addInventoryItem(layer) {
793
  playerInventory[layer.id] = (playerInventory[layer.id] || 0) + 1;
@@ -1580,4 +1677,14 @@ document.addEventListener('drop', (e) => {
1580
  document.addEventListener('DOMContentLoaded', () => {
1581
  gameContainer.style.opacity = 0;
1582
  gameContainer.style.transition = 'opacity 0.5s ease-in-out';
 
 
 
 
 
 
 
 
 
 
1583
  });
 
30
  let playerHP = 100;
31
  let enemyHP = 100;
32
  let currentStage = 1;
33
+ let isBattleInProgress = false;
34
+ let draggedItem = null;
35
  let dragOverIndex = null; // 並び替え先のインデックス
36
  let wasDroppedSuccessfully = false; // ★★★ このフラグを追加
37
  let currentEnemy = { image_b64: null, label: null }; // ★★★ クライアント側で敵の状態を保持
38
  let ENEMY_MAX_HP = 100;
39
+ let selectedLayerForMove = null; // タップで移動対象として選択されたレイヤーのインデックス
40
+
41
  const PLAYER_MAX_HP = 100;
42
 
43
  const allAvailableLayers = [
 
186
  }
187
  });
188
 
189
+ const createDropZone = (index) => {
190
+ const zone = document.createElement('div');
191
+ zone.className = 'player-layer-drop-zone h-4'; // スマホで見えるように少し高さを付ける
192
+ zone.dataset.index = index;
193
+ zone.addEventListener('click', () => handleLayerMove(index));
194
+ modelArea.appendChild(zone);
195
+ };
196
+
197
+
198
  if (playerLayers.length === 0) {
199
  const p = document.createElement('p');
200
  p.className = 'text-gray-400 text-center p-4';
201
+ p.textContent = 'インベントリからレイヤー(層)をここにドラッグ&ドロップ or タップしてください。';
202
  modelArea.appendChild(p);
203
  } else {
204
+ createDropZone(0); // 最初のレイヤーの上にドロップゾーンを作成
205
  playerLayers.forEach((layer, index) => {
206
  const div = document.createElement('div');
207
  div.className = `player-layer ${layer.rarity === 'gold' ? 'gold-rare' : ''}`;
208
  div.draggable = true;
209
  div.dataset.index = index;
210
+
211
+ // ★★★ 選択状態のスタイルを適用 ★★★
212
+ if (selectedLayerForMove === index) {
213
+ div.classList.add('border-cyan-400', 'scale-105');
214
+ }
215
+
216
  div.innerHTML = `<span><i class="fas ${layer.icon} mr-2"></i>${layer.name}</span><button class="text-red-400 hover:text-red-200 text-lg">&times;</button>`;
217
+ div.querySelector('button').onclick = (e) => {
218
+ e.stopPropagation(); // 親要素のクリックイベントを発火させない
219
+ removeLayer(index);
220
+ };
221
 
222
+ // ドラッグ&ドロップイベント (PC用)
223
  div.addEventListener('dragstart', (e) => handleDragStart(e, index, layer));
224
  div.addEventListener('dragend', handleDragEnd);
225
  div.addEventListener('dragenter', (e) => handleDragEnterModelItem(e, index));
226
  div.addEventListener('dragleave', (e) => e.currentTarget.classList.remove('is-dragged-over'));
227
 
228
+ // ★★★ タップイベント (スマホ用) ★★★
229
+ div.addEventListener('click', () => handleLayerSelectForMove(index));
230
+
231
  modelArea.appendChild(div);
232
+ createDropZone(index + 1); // 各レイヤーの下にドロップゾーンを作成
233
  });
234
  }
235
  battleBtn.disabled = playerLayers.length === 0;
 
236
  requestAnimationFrame(drawLayerConnections);
237
  }
238
 
 
256
 
257
  // イベントリスナー
258
  item.addEventListener('click', () => {
259
+ // PCでの説明表示機能は維持
260
  showDescription(layer, document.getElementById('layer-description'));
261
  runPreviewAnimation(layer, previewAnimationArea);
262
+ // スマホ用の追加機能
263
+ addLayerFromInventory(layer);
264
  });
265
  item.addEventListener('dragstart', (e) => handleInventoryDragStart(e, layer));
266
  item.addEventListener('dragend', handleDragEnd);
 
316
  async function fetchNewEnemy() {
317
  const response = await fetch('/api/get_enemy');
318
  const enemyData = await response.json();
319
+
320
  // ★★★ グローバル変数に保存
321
  currentEnemy = {
322
  image_b64: enemyData.image_b64,
 
404
  } else {
405
  logMessage(`レイヤー追加: ${draggedItem.layer.name}`, 'action');
406
  }
407
+ } else {
408
  logMessage('モデルの順序を変更しました。', 'info');
409
  }
410
 
 
578
  itemSelectionModal.classList.add('flex');
579
  itemChoices.innerHTML = '';
580
 
581
+ const confirmBtn = document.getElementById('confirm-choice-btn');
582
+ const choiceFooter = document.getElementById('choice-selection-footer');
583
+ const selectedChoiceInput = document.getElementById('selected-choice-id');
584
+ confirmBtn.disabled = true;
585
+ choiceFooter.classList.add('opacity-0');
586
+ selectedChoiceInput.value = '';
587
+
588
  // ★★★ ステージに応じたゴールドレア出現確率を計算
589
  // ステージ1: 10%, ステージ2: 20%, ステージ3: 30%, ステージ4: 40%, ステージ5: 50%
590
  const goldChance = Math.min(0.1 * currentStage, 0.5);
 
630
  // 選択肢のUIを生成
631
  finalChoices.forEach(layer => {
632
  const item = document.createElement('div');
 
633
  item.className = `layer-item w-48 h-48 flex flex-col justify-center ${layer.rarity === 'gold' ? 'gold-rare' : ''}`;
634
  item.innerHTML = `<i class="fas ${layer.icon} layer-icon text-5xl"></i><p class="text-lg mt-2">${layer.name}</p>`;
635
+ item.dataset.id = layer.id; // ★★★ IDをデータ属性として保持
636
 
637
  // ★★★ ゴールドレアの場合、キラキラエフェクトを追加
638
  if (layer.rarity === 'gold') {
 
649
  item.appendChild(sparkleContainer);
650
  }
651
 
652
+ // ★★★ クリック/タップ時の動作を変更 ★★★
653
+ item.addEventListener('click', () => {
654
+ // 他のアイテムの選択状態を解除
655
+ document.querySelectorAll('#item-choices .layer-item').forEach(el => {
656
+ el.classList.remove('border-cyan-400', 'scale-105');
657
+ });
658
+ // このアイテムを選択状態にする
659
+ item.classList.add('border-cyan-400', 'scale-105');
660
+
661
+ // 説明とプレビューを表示
662
  showDescription(layer, choiceDescription);
663
  runPreviewAnimation(layer, choicePreviewArea);
664
+
665
+ // 決定ボタンを有効化し、選択したIDを保持
666
+ selectedChoiceInput.value = layer.id;
667
+ confirmBtn.disabled = false;
668
+ choiceFooter.classList.remove('opacity-0');
669
  });
670
+
671
  itemChoices.appendChild(item);
672
  });
673
  }
 
837
  }
838
  }
839
 
840
+ // ★★★ インベントリからのタップでレイヤーを追加する関数 ★★★
841
+ function addLayerFromInventory(layer) {
842
+ // 新しい一意なインスタンスを作成して追加
843
+ const layerInstance = {
844
+ ...layer,
845
+ instanceId: `inst_${Date.now()}_${Math.random()}`
846
+ };
847
+ if (useInventoryItem(layerInstance)) {
848
+ playerLayers.push(layerInstance);
849
+ logMessage(`レイヤー追加: ${layerInstance.name}`, 'action');
850
+ updatePlayerModelUI();
851
+ updatePlayerInventoryUI();
852
+ } else {
853
+ logMessage(`インベントリに ${layer.name} がありません`, 'error');
854
+ }
855
+ }
856
+
857
+ // ★★★ モデル内のレイヤーを移動のために「選択」する関数 ★★★
858
+ function handleLayerSelectForMove(index) {
859
+ if (selectedLayerForMove === index) {
860
+ // 同じレイヤーを再度タップしたら選択解除
861
+ selectedLayerForMove = null;
862
+ logMessage('レイヤーの移動をキャンセルしました。', 'info');
863
+ } else {
864
+ selectedLayerForMove = index;
865
+ logMessage(`レイヤー '${playerLayers[index].name}' を選択しました。移動先の青いエリアをタップしてください。`, 'action');
866
+ }
867
+ updatePlayerModelUI(); // UIを再描画して選択状態を反映
868
+ }
869
+
870
+ // ★★★ 選択したレイヤーをドロップゾーンに「移動」する関数 ★★★
871
+ function handleLayerMove(targetIndex) {
872
+ if (selectedLayerForMove === null) return; // 何も選択されていなければ何もしない
873
+
874
+ // 移動するレイヤーを取得
875
+ const [movedLayer] = playerLayers.splice(selectedLayerForMove, 1);
876
+
877
+ // 削除によってインデックスがずれるのを補正
878
+ const adjustedTargetIndex = selectedLayerForMove < targetIndex ? targetIndex - 1 : targetIndex;
879
+
880
+ // 新しい場所にレイヤーを挿入
881
+ playerLayers.splice(adjustedTargetIndex, 0, movedLayer);
882
+
883
+ logMessage('レイヤーを移動しました。', 'success');
884
+ selectedLayerForMove = null; // 移動が終わったら選択状態を解除
885
+ updatePlayerModelUI();
886
+ }
887
+
888
  // --- Main Game Logic ---
889
  function addInventoryItem(layer) {
890
  playerInventory[layer.id] = (playerInventory[layer.id] || 0) + 1;
 
1677
  document.addEventListener('DOMContentLoaded', () => {
1678
  gameContainer.style.opacity = 0;
1679
  gameContainer.style.transition = 'opacity 0.5s ease-in-out';
1680
+ });
1681
+
1682
+ document.getElementById('confirm-choice-btn').addEventListener('click', () => {
1683
+ const selectedId = document.getElementById('selected-choice-id').value;
1684
+ if (selectedId) {
1685
+ const selectedLayer = allAvailableLayers.find(l => l.id == selectedId);
1686
+ if (selectedLayer) {
1687
+ selectItem(selectedLayer);
1688
+ }
1689
+ }
1690
  });