フロントエンドプレイアブル
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
2026-02-15 14:57:17 +09:00
commit f25fd6f802
198 changed files with 10342 additions and 0 deletions

View File

@@ -0,0 +1,211 @@
<%= form_with(model: game, class: "space-y-6") do |form| %>
<% if game.errors.any? %>
<div class="rounded-md bg-red-50 p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">
<%= pluralize(game.errors.count, "error") %> prohibited this game from being saved:
</h3>
<div class="mt-2 text-sm text-red-700">
<ul class="list-disc pl-5 space-y-1">
<% game.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
</div>
</div>
</div>
<% end %>
<div>
<%= form.label :title, "ゲームタイトル", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.text_field :title, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md", placeholder: "例: 定例ディプロマシー会 #1" %>
</div>
</div>
<% unless game.persisted? %>
<% if current_user&.admin? %>
<div>
<%= form.label :game_mode, "ゲームモード", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.select :game_mode,
options_for_select([
['ソロモード (単独プレイ)', 'admin_mode'],
['マルチプレイヤーモード (複数プレイ)', 'player_mode']
], game.is_solo_mode ? 'admin_mode' : 'player_mode'),
{},
{ id: 'game-mode-select', class: 'shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md' } %>
</div>
</div>
<% end %>
<div id="participants-count-field" style="<%= game.solo_mode? ? 'display: none;' : '' %>">
<%= form.label :participants_count, "参加人数", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.number_field :participants_count, min: 1, max: 7, step: 1, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %>
</div>
<p class="mt-2 text-sm text-gray-500">1人から7人まで設定可能です。</p>
</div>
<div id="password-field" style="<%= game.solo_mode? ? 'display: none;' : '' %>">
<%= form.label :password, "パスワード (任意)", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.password_field :password, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %>
</div>
<p class="mt-2 text-sm text-gray-500">知人同士でプレイする場合など、アクセス制限をかけたい場合に設定してください。</p>
</div>
<% end %>
<div id="auto-fill-mode-field" style="<%= (game.solo_mode? && !game.persisted?) ? 'display: none;' : '' %>">
<%= form.label :auto_order_mode, "自動処理モード", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.select :auto_order_mode,
options_for_select([
['HOLD (待機)', 'hold'],
['ランダム動作', 'random']
], game.auto_order_mode || 'hold'),
{},
{ class: 'shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md' } %>
</div>
<p class="mt-2 text-sm text-gray-500">注文未入力の国や、プレイヤー不在の国の動作を設定します。</p>
</div>
<div id="turn-schedule-field" style="<%= (game.solo_mode? && !game.persisted?) ? 'display: none;' : '' %>">
<%= form.label :turn_schedule, "ターン進行方式", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<% current_schedule = game.turn_schedule.presence %>
<% preset_value = case current_schedule
when nil then "manual"
when "0" then "daily_0"
else "custom"
end %>
<select id="turn_schedule_preset"
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
onchange="handleSchedulePresetChange(this)">
<option value="manual" <%= 'selected' if preset_value == 'manual' %>>手動(管理者が処理)</option>
<option value="daily_0" <%= 'selected' if preset_value == 'daily_0' %>>毎日1回0時</option>
<option value="custom" <%= 'selected' if preset_value == 'custom' %>>カスタム...</option>
</select>
</div>
<div id="custom-schedule-field" style="<%= preset_value == 'custom' ? '' : 'display: none;' %>" class="mt-2">
<input type="text" id="custom_schedule_input"
value="<%= current_schedule if preset_value == 'custom' %>"
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
placeholder="例: 0,12,18毎日0時・12時・18時"
oninput="document.getElementById('turn_schedule_value').value = this.value">
</div>
<%= form.hidden_field :turn_schedule, id: "turn_schedule_value", value: current_schedule %>
<p class="mt-2 text-sm text-gray-500">
自動の場合、締切時刻を過ぎると未入力国はAutoOrderで処理され、ターンが自動進行します。
</p>
</div>
<script>
function handleSchedulePresetChange(select) {
const customField = document.getElementById('custom-schedule-field');
const hiddenField = document.getElementById('turn_schedule_value');
const customInput = document.getElementById('custom_schedule_input');
switch (select.value) {
case 'manual':
customField.style.display = 'none';
hiddenField.value = '';
break;
case 'daily_0':
customField.style.display = 'none';
hiddenField.value = '0';
break;
case 'custom':
customField.style.display = 'block';
hiddenField.value = customInput.value;
break;
}
}
</script>
<div>
<%= form.label :memo, "メモ", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.textarea :memo, rows: 3, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %>
</div>
<p class="mt-2 text-sm text-gray-500">ゲームに関するメモや注意事項があれば記載してください。</p>
</div>
<!-- ハウスルール設定 -->
<div class="border-t border-gray-200 pt-6 mt-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">ハウスルール</h3>
<div class="space-y-4">
<div>
<%= form.label :year_limit, "年数制限", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.number_field :year_limit, min: 1901, max: 1999, step: 1, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md", placeholder: "空欄 = 無制限" %>
</div>
<p class="mt-2 text-sm text-gray-500">指定した年を超えるとゲームが自動終了します(例: 1910。空欄で無制限。</p>
</div>
<div>
<%= form.label :victory_sc_count, "目標SC数", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.number_field :victory_sc_count, min: 1, max: 34, step: 1, value: game.victory_sc_count || 18, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %>
</div>
<p class="mt-2 text-sm text-gray-500">ソロ勝利に必要なSC数デフォルト: 18。</p>
</div>
<div>
<%= form.label :scoring_system, "スコアリング方式", class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1">
<%= form.select :scoring_system,
options_for_select([
['なし', 'none'],
['SC数獲得SC数がスコア', 'sc_count'],
['SC比率全SC中の割合%', 'sc_ratio'],
['DSS生存国で均等分割', 'dss'],
['SoS二乗和比率', 'sos']
], game.scoring_system || 'none'),
{},
{ class: 'shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md' } %>
</div>
<p class="mt-2 text-sm text-gray-500">ゲーム終了時のスコア計算方式を選択します。</p>
</div>
</div>
</div>
<div class="pt-5">
<div class="flex justify-end">
<%= form.submit "保存する", class: "ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const gameModeSelect = document.getElementById('game-mode-select');
const participantsField = document.getElementById('participants-count-field');
const passwordField = document.getElementById('password-field');
const autoFillField = document.getElementById('auto-fill-mode-field');
if (gameModeSelect) {
gameModeSelect.addEventListener('change', function() {
const isAdminMode = this.value === 'admin_mode';
if (isAdminMode) {
if(participantsField) participantsField.style.display = 'none';
if(passwordField) passwordField.style.display = 'none';
if(autoFillField) autoFillField.style.display = 'none';
} else {
if(participantsField) participantsField.style.display = 'block';
if(passwordField) passwordField.style.display = 'block';
if(autoFillField) autoFillField.style.display = 'block';
}
});
}
});
</script>
<% end %>