HTML | 简易水排序游戏求解器 第二版

第二版在第一版的基础上,将颜色选择功能移至点击杯子底部的方块,调整页面更为紧凑。

颜色选择界面:

求解步骤:

最后贴上源码:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>水排序游戏求解器</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        background: linear-gradient(135deg, #1a2a6c 0%, #2c3e50 100%);
        color: #333;
        line-height: 1.5;
        padding: 15px;
        min-height: 100vh;
        font-size: 14px;
      }

      .container {
        max-width: 1200px;
        margin: 0 auto;
        background: rgba(255, 255, 255, 0.92);
        border-radius: 12px;
        box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
        padding: 4px 20px;
        position: relative;
        overflow: hidden;
      }

      .container::before {
        content: "";
        position: absolute;
        top: -50%;
        left: -50%;
        width: 200%;
        height: 200%;
        background: radial-gradient(circle, rgba(102, 126, 234, 0.15) 0%, rgba(255, 255, 255, 0) 70%);
        z-index: -1;
      }

      h1 {
        text-align: center;
        color: #2c3e50;
        margin-bottom: 8px;
        font-size: 1.5em;
        text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        position: relative;
      }

      h1::after {
        content: "";
        display: block;
        width: 70px;
        height: 3px;
        background: linear-gradient(90deg, #3498db, #667eea);
        margin: 10px auto 15px;
        border-radius: 2px;
      }

      .controls {
        background: linear-gradient(120deg, #f8f9ff, #eef2f7);
        padding: 0 18px;
        border-radius: 12px;
        margin-bottom: 10px;
        border: 1px solid #e0e6ef;
        box-shadow: 0 3px 12px rgba(0, 0, 0, 0.08);
      }

      .control-group {
        margin-bottom: 15px;
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        gap: 8px;
        padding: 4px 0;
        border-bottom: 1px dashed #dce4ec;
      }

      .control-group:last-child {
        border-bottom: none;
        margin-bottom: 0;
      }

      .control-group label {
        font-weight: 600;
        min-width: 110px;
        color: #2c3e50;
        font-size: 0.95em;
      }

      .control-group input,
      .control-group select,
      .control-group button {
        padding: 10px 15px;
        border: 2px solid #dce4ec;
        border-radius: 8px;
        font-size: 14px;
        transition: all 0.3s ease;
        background: white;
        font-weight: 500;
      }

      .control-group input:focus,
      .control-group select:focus,
      .control-group button:focus {
        outline: none;
        border-color: #667eea;
        box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.25);
      }

      .control-group button {
        background: linear-gradient(120deg, #667eea, #764ba2);
        color: white;
        border: none;
        cursor: pointer;
        font-weight: bold;
        min-width: 140px;
        box-shadow: 0 3px 8px rgba(102, 126, 234, 0.3);
        position: relative;
        overflow: hidden;
      }

      .control-group button::after {
        content: "";
        position: absolute;
        top: -50%;
        left: -50%;
        width: 200%;
        height: 200%;
        background: linear-gradient(120deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));
        transform: rotate(30deg);
        transition: all 0.6s;
        opacity: 0;
      }

      .control-group button:hover::after {
        opacity: 1;
        left: 100%;
      }

      .control-group button:hover {
        box-shadow: 0 5px 12px rgba(102, 126, 234, 0.4);
      }

      .control-group button:disabled {
        background: linear-gradient(120deg, #bdc3c7, #95a5a6);
        cursor: not-allowed;
        transform: none;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      }

      .control-group button:disabled:hover::after {
        opacity: 0;
      }

      .color-picker {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        margin-top: 12px;
        padding: 12px;
        background: white;
        border-radius: 10px;
        border: 1px solid #eaeff5;
        box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
      }

      .color-option {
        display: flex;
        align-items: center;
        gap: 8px;
        cursor: pointer;
        padding: 4px 12px;
        border-radius: 8px;
        transition: all 0.25s ease;
        background: #f8fafc;
        border: 2px solid transparent;
      }

      .color-option:hover {
        background: #edf2f7;
      }

      .color-option input[type="radio"]:checked+.color-box {
        box-shadow: 0 0 0 2px #667eea, 0 0 0 4px rgba(102, 126, 234, 0.3);
      }

      .color-option input[type="radio"] {
        margin: 0;
        width: 16px;
        height: 16px;
      }

      .color-box {
        width: 28px;
        height: 28px;
        border-radius: 50%;
        border: 2px solid #e0e6ef;
        transition: all 0.3s ease;
        flex-shrink: 0;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      }

      .color-box.empty {
        border-style: dashed;
        background: #f8fafc;
      }

      .cup-editor {
        background: #f9fbfd;
        border-radius: 12px;
        padding: 5px 20px;
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
      }

      .cup-editor h2 {
        color: #2c3e50;
        margin-bottom: 5px;
        text-align: center;
        font-size: 1.2em;
        position: relative;
        padding-bottom: 2px;
      }

      .cup-editor h2::after {
        content: "";
        position: absolute;
        bottom: 0;
        left: 50%;
        transform: translateX(-50%);
        width: 50px;
        height: 2px;
        background: linear-gradient(90deg, #3498db, #667eea);
        border-radius: 2px;
      }

      .cup-grid {
        display: grid;
        grid-template-columns: repeat(7, 1fr);
        gap: 18px;
        margin-top: 15px;
        padding: 8px;
      }

      .cup-container {
        display: flex;
        flex-direction: column;
        align-items: center;
        transition: transform 0.3s ease;
      }

      .cup-label {
        font-weight: 700;
        margin-bottom: 10px;
        color: #2c3e50;
        font-size: 1.1em;
        background: linear-gradient(90deg, #3498db, #667eea);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
      }

      .cup {
        width: 63px;
        height: 154px;
        background: linear-gradient(145deg, #f0f4f8, #e2e8f0);
        border: 2px solid #cbd5e0;
        border-radius: 10px 10px 8px 8px;
        position: relative;
        overflow: hidden;
        box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
        transition: all 0.4s ease;
      }

      .cup::before {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        height: 10px;
        background: linear-gradient(0deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%);
        border-radius: 8px 8px 0 0;
        z-index: 10;
      }

      .cup::after {
        content: "";
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        height: 6px;
        background: rgba(0, 0, 0, 0.08);
        border-radius: 0 0 6px 6px;
      }

      .water-layer {
        width: 100%;
        height: 25%;
        position: absolute;
        bottom: 0;
        transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 8px;
        color: white;
        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
        font-weight: 600;
        padding: 0 3px;
        box-sizing: border-box;
        background-clip: padding-box;
        border-top: 1px solid rgba(255, 255, 255, 0.2);
      }

      .water-layer:first-child {
        border-top: none;
      }

      .water-layer.empty {
        background: transparent;
        border-top: none;
      }

      .water-layer::after {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        height: 50%;
        background: linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0) 100%);
      }

      .water-selector {
        margin-top: 12px;
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 4px;
        width: 63px;
      }

      .water-selector button {
        width: 100%;
        height: 22px;
        border: 2px solid #cbd5e0;
        border-radius: 5px;
        cursor: pointer;
        transition: all 0.25s ease;
        padding: 0;
        font-weight: 500;
        font-size: 8px;
        color: #4a5568;
        background: white;
        box-shadow: 0 2px 3px rgba(0, 0, 0, 0.05);
      }

      .water-selector button:hover {
        border-color: #667eea;
        z-index: 2;
        box-shadow: 0 3px 6px rgba(102, 126, 234, 0.3);
      }

      .water-selector button.selected {
        border-color: #f39c12;
        box-shadow: 0 0 0 3px rgba(243, 156, 18, 0.4), 0 3px 6px rgba(243, 156, 18, 0.3);
        background: linear-gradient(135deg, #fef9e7, #fdebd0);
        color: #b7950b;
        font-weight: 700;
      }

      .water-selector button.empty {
        background: #f8fafc;
        border-style: dashed;
        color: #718096;
      }

      .color-modal {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.7);
        z-index: 1000;
        justify-content: center;
        align-items: center;
      }

      .color-modal.active {
        display: flex;
        animation: modalFadeIn 0.3s ease-out;
      }

      @keyframes modalFadeIn {
        from {
          opacity: 0;
          transform: scale(0.9);
        }

        to {
          opacity: 1;
          transform: scale(1);
        }
      }

      .color-modal-content {
        background: white;
        border-radius: 16px;
        padding: 25px;
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
        max-width: 90%;
        max-height: 90%;
        overflow-y: auto;
      }

      .color-modal h3 {
        text-align: center;
        color: #2c3e50;
        margin-bottom: 20px;
        font-size: 1.4em;
      }

      .modal-color-grid {
        display: grid;
        grid-template-columns: repeat(6, 1fr);
        /* 每排6个颜色 */
        gap: 15px;
        padding: 10px;
      }

      .modal-color-option {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 8px;
        cursor: pointer;
        padding: 12px;
        border-radius: 10px;
        transition: all 0.25s ease;
        background: #f8fafc;
        border: 2px solid transparent;
      }

      .modal-color-option:hover {
        background: #edf2f7;
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
      }

      .modal-color-option.selected {
        border-color: #667eea;
        box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
        background: #e3f2fd;
      }

      .modal-color-box {
        width: 50px;
        height: 50px;
        border-radius: 50%;
        border: 2px solid #e0e6ef;
        transition: all 0.3s ease;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
      }

      .modal-color-box.empty {
        border-style: dashed;
        background: linear-gradient(135deg, #f8f9ff, #e9ecef);
      }

      .modal-color-name {
        font-size: 0.85em;
        color: #2c3e50;
        font-weight: 600;
        text-align: center;
      }

      .solution-section {
        margin-top: 25px;
        display: none;
        animation: fadeIn 0.6s ease-out;
      }

      @keyframes fadeIn {
        from {
          opacity: 0;
        }

        to {
          opacity: 1;
        }
      }

      .solution-section h2 {
        color: #2c3e50;
        margin-bottom: 5px;
        text-align: center;
        font-size: 1.2em;
        position: relative;
        padding-bottom: 10px;
      }

      .solution-section h2::after {
        content: "";
        position: absolute;
        bottom: 0;
        left: 50%;
        transform: translateX(-50%);
        width: 60px;
        height: 3px;
        background: linear-gradient(90deg, #27ae60, #2ecc71);
        border-radius: 2px;
      }

      .solution-steps {
        max-height: 200px;
        overflow-y: auto;
        border: 2px solid #e2e8f0;
        border-radius: 12px;
        padding: 16px;
        background: linear-gradient(145deg, #f8fafc, #edf2f7);
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
        margin-top: 12px;
      }

      .solution-steps::-webkit-scrollbar {
        width: 7px;
      }

      .solution-steps::-webkit-scrollbar-track {
        background: #e2e8f0;
        border-radius: 8px;
      }

      .solution-steps::-webkit-scrollbar-thumb {
        background: #667eea;
        border-radius: 8px;
      }

      .step {
        padding: 4px 16px;
        margin-bottom: 10px;
        background: white;
        border-radius: 10px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
        transition: all 0.25s ease;
        cursor: pointer;
        position: relative;
        overflow: hidden;
      }

      .step:hover {
        box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
        background: #f8fafc;
      }

      .step::before {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        width: 3px;
        height: 100%;
        background: linear-gradient(0deg, #3498db, #667eea);
      }

      .step.active {
        background: linear-gradient(135deg, #e3f2fd, #bbdefb);
        border-left-color: #2196f3;
        box-shadow: 0 3px 12px rgba(33, 150, 243, 0.25);
      }

      .step.active::before {
        background: linear-gradient(0deg, #1976d2, #2196f3);
      }

      .step-number {
        font-weight: 800;
        color: #2c3e50;
        min-width: 28px;
        height: 28px;
        border-radius: 50%;
        background: linear-gradient(135deg, #667eea, #764ba2);
        color: white;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 1em;
        box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4);
      }

      .step-action {
        flex-grow: 1;
        font-size: 1em;
        margin: 0 12px;
        font-weight: 500;
        color: #2c3e50;
      }

      .step-arrow {
        color: #7f8c8d;
        font-size: 1.6em;
        margin: 0 6px;
        font-weight: 300;
      }

      .step-cup {
        font-weight: 700;
        padding: 3px 10px;
        border-radius: 18px;
        font-size: 0.95em;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
      }

      .cup-from {
        color: #e74c3c;
        background: linear-gradient(135deg, #fadbd8, #f5b7b1);
        border: 1px solid #f1948a;
      }

      .cup-to {
        color: #27ae60;
        background: linear-gradient(135deg, #d5f5e3, #abebc6);
        border: 1px solid #7dcea0;
      }

      .visualization {
        margin-top: 25px;
        text-align: center;
        background: linear-gradient(145deg, #f8fafc, #edf2f7);
        border-radius: 16px;
        padding: 20px;
        box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12);
        margin-bottom: 20px;
      }

      .visualization h3 {
        color: #2c3e50;
        margin-bottom: 15px;
        font-size: 1.5em;
      }

      .visualization-controls {
        margin: 16px 0;
        display: flex;
        justify-content: center;
        gap: 12px;
        flex-wrap: wrap;
      }

      .visualization-controls button {
        padding: 10px 20px;
        background: linear-gradient(135deg, #3498db, #2980b9);
        color: white;
        border: none;
        border-radius: 40px;
        cursor: pointer;
        font-weight: 600;
        font-size: 1em;
        box-shadow: 0 3px 12px rgba(52, 152, 219, 0.4);
        transition: all 0.3s ease;
        min-width: 120px;
        position: relative;
        overflow: hidden;
      }

      .visualization-controls button::after {
        content: "";
        position: absolute;
        top: -50%;
        left: -50%;
        width: 200%;
        height: 200%;
        background: linear-gradient(120deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));
        transform: rotate(30deg);
        transition: all 0.6s;
        opacity: 0;
      }

      .visualization-controls button:hover::after {
        opacity: 1;
        left: 100%;
      }

      .visualization-controls button:hover {
        box-shadow: 0 5px 16px rgba(52, 152, 219, 0.55);
      }

      .visualization-controls button:active {
        box-shadow: 0 2px 6px rgba(52, 152, 219, 0.4);
      }

      .visualization-controls button:disabled {
        background: linear-gradient(135deg, #bdc3c7, #95a5a6);
        cursor: not-allowed;
        transform: none;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      }

      .visualization-controls button:disabled:hover::after {
        opacity: 0;
      }

      .visualization-controls button.stop {
        background: linear-gradient(135deg, #e74c3c, #c0392b);
        box-shadow: 0 3px 12px rgba(231, 76, 60, 0.4);
      }

      .visualization-controls button.stop:hover {
        box-shadow: 0 5px 16px rgba(231, 76, 60, 0.55);
      }

      #autoPlayBtn.playing {
        background: linear-gradient(135deg, #e74c3c, #c0392b);
        animation: pulse 1.5s infinite;
      }

      @keyframes pulse {
        0% {
          box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.4);
        }

        70% {
          box-shadow: 0 0 0 10px rgba(231, 76, 60, 0);
        }

        100% {
          box-shadow: 0 0 0 0 rgba(231, 76, 60, 0);
        }
      }

      .status-message {
        padding: 15px 20px;
        margin: 20px 0;
        border-radius: 12px;
        text-align: center;
        font-weight: 600;
        font-size: 1em;
        display: none;
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
        position: fixed;
        top: 0;
        right: 0;
        overflow: hidden;
        z-index: 10;
      }

      .status-message::before {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 3px;
        z-index: -1;
      }

      .status-success {
        background: linear-gradient(135deg, #d4edda, #c3e6cb);
        color: #155724;
        border: 1px solid #c3e6cb;
      }

      .status-success::before {
        background: linear-gradient(90deg, #28a745, #20c997);
      }

      .status-error {
        background: linear-gradient(135deg, #f8d7da, #f5c6cb);
        color: #721c24;
        border: 1px solid #f5c6cb;
      }

      .status-error::before {
        background: linear-gradient(90deg, #dc3545, #e83e8c);
      }

      .status-info {
        background: linear-gradient(135deg, #d1ecf1, #bee5eb);
        color: #0c5460;
        border: 1px solid #bee5eb;
      }

      .status-info::before {
        background: linear-gradient(90deg, #17a2b8, #1abc9c);
      }

      .status-warning {
        background: linear-gradient(135deg, #fff3cd, #ffeaa7);
        color: #856404;
        border: 1px solid #ffeaa7;
      }

      .status-warning::before {
        background: linear-gradient(90deg, #ffc107, #fd7e14);
      }

      .loading {
        display: inline-block;
        width: 24px;
        height: 24px;
        border: 3px solid rgba(102, 126, 234, 0.3);
        border-radius: 50%;
        border-top-color: #667eea;
        animation: spin 0.8s linear infinite;
        margin-right: 12px;
        vertical-align: middle;
      }

      @keyframes spin {
        to {
          transform: rotate(360deg);
        }
      }

      .progress-container {
        width: 100%;
        background: #e9ecef;
        border-radius: 8px;
        margin: 12px 0;
        overflow: hidden;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      }

      .progress-bar {
        height: 10px;
        background: linear-gradient(90deg, #667eea, #764ba2);
        border-radius: 8px;
        width: 0%;
        transition: width 0.4s ease;
        position: relative;
      }

      .progress-bar::after {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0.2) 100%);
        animation: shine 2s infinite;
      }

      @keyframes shine {
        0% {
          transform: translateX(-100%);
        }

        100% {
          transform: translateX(100%);
        }
      }

      .progress-text {
        text-align: center;
        font-weight: 600;
        color: #2c3e50;
        margin-top: 6px;
        font-size: 1em;
      }

      .stats-container {
        display: flex;
        justify-content: space-around;
        margin-top: 16px;
        flex-wrap: wrap;
        gap: 12px;
      }

      .stat-box {
        background: white;
        border-radius: 12px;
        padding: 0;
        text-align: center;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
        min-width: 110px;
        transition: all 0.3s ease;
      }

      .stat-box:hover {
        box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
      }

      .stat-value {
        font-size: 1.9em;
        font-weight: 800;
        color: #667eea;
        margin: 4px 0;
      }

      .stat-label {
        color: #7f8c8d;
        font-weight: 500;
      }

      @media (max-width: 768px) {
        .container {
          padding: 12px;
          margin: 8px;
        }

        .cup-grid {
          grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
          gap: 12px;
        }

        .cup {
          width: 55px;
          height: 135px;
        }

        .water-selector {
          width: 55px;
        }

        .control-group {
          flex-direction: column;
          align-items: flex-start;
        }

        .control-group label {
          min-width: auto;
          margin-bottom: 6px;
        }

        h1 {
          font-size: 1.5em;
        }

        .visualization-controls button {
          min-width: 100px;
          padding: 8px 12px;
          font-size: 0.9em;
        }

        .stats-container {
          flex-direction: column;
          align-items: center;
        }
      }

      @media (max-width: 480px) {
        .cup-grid {
          grid-template-columns: repeat(auto-fill, minmax(95px, 1fr));
        }

        .cup {
          width: 48px;
          height: 120px;
        }

        .water-selector {
          width: 48px;
          gap: 2px;
        }

        .water-selector button {
          height: 18px;
          font-size: 7px;
        }

        .cup-label {
          font-size: 0.95em;
        }

        .control-group input,
        .control-group select,
        .control-group button {
          width: 100%;
          max-width: 280px;
        }
      }
    </style>
  </head>

  <body>
    <div class="container">
      <h1>💧 水排序游戏求解器</h1>
      <div class="controls">
        <div class="control-group">
          <label for="numCups">杯子数量:</label>
          <input type="number" id="numCups" min="2" max="20" value="14">
          <button id="applyCups">应用设置</button>
        </div>

        <!-- <div class="control-group">
          <label>选择颜色:</label>
          <div class="color-picker" id="colorPicker">
          </div>
        </div> -->

        <div class="control-group">
          <label>预设关卡:</label>
          <select id="presetSelector">
            <option value="">自定义配置</option>
            <option value="level1">预设1</option>
          </select>
          <button id="loadPreset">加载关卡</button>

          <button id="solveBtn" disabled>🔍 计算最优解</button>
          <button id="resetBtn">↺ 重置配置</button>
        </div>
      </div>

      <div class="cup-editor">
        <h2>杯子配置编辑器</h2>
        <div class="cup-grid" id="cupGrid">
          <!-- 杯子将通过JS动态生成 -->
        </div>
      </div>

      <!-- 颜色选择弹窗 -->
      <div class="color-modal" id="colorModal">
        <div class="color-modal-content">
          <h3>🎨 选择颜色</h3>
          <div class="modal-color-grid" id="modalColorGrid">
            <!-- 颜色选项将通过JS动态生成 -->
          </div>
        </div>
      </div>

      <div class="status-message" id="statusMessage"></div>

      <div class="progress-container" id="progressContainer" style="display: none;">
        <div class="progress-bar" id="progressBar"></div>
        <div class="progress-text" id="progressText">准备开始计算...</div>
      </div>

      <div class="solution-section" id="solutionSection">
        <h2>解决方案</h2>
        <div class="stats-container">
          <div class="stat-box">
            <div class="stat-value" id="totalSteps">0</div>
            <div class="stat-label">总步数</div>
          </div>
          <div class="stat-box">
            <div class="stat-value" id="nodesSearched">0</div>
            <div class="stat-label">搜索节点</div>
          </div>
          <div class="stat-box">
            <div class="stat-value" id="solveTime">0.0</div>
            <div class="stat-label">计算耗时 (秒)</div>
          </div>
          <div class="stat-box">
            <div class="stat-value" id="emptyCups">2</div>
            <div class="stat-label">空杯数量</div>
          </div>
        </div>

        <div class="visualization">
          <h3>步骤可视化</h3>
          <div class="visualization-controls">
            <button id="prevStepBtn" disabled>⏮️ 首步</button>
            <button id="prevStepBtnSingle" disabled>◀️ 上一步</button>
            <span id="currentStep">步骤: 0/0</span>
            <button id="nextStepBtnSingle" disabled>下一步 ▶️</button>
            <button id="nextStepBtn" disabled>末步 ⏭️</button>
            <button id="autoPlayBtn" disabled>⏯️ 自动播放</button>
          </div>
          <div class="cup-grid" id="visualizationGrid">
            <!-- 可视化杯子将通过JS动态生成 -->
          </div>
        </div>

        <h3 style="margin: 25px 0 15px; color: #2c3e50; text-align: center;">详细步骤</h3>
        <div class="solution-steps" id="solutionSteps">
          <!-- 解决方案步骤将通过JS动态生成 -->
        </div>
      </div>
    </div>

    <script>
      // 颜色定义 - 优化了颜色选择,确保对比度和可访问性
      const COLORS = [
        { name: '空', value: 'empty', color: '#f5f5f5', displayName: '空' },
        { name: '黄色', value: 'yellow', color: '#fce65d', displayName: '黄色' },
        { name: '褐色', value: 'brown', color: '#9d714c', displayName: '褐色' },
        { name: '灰色', value: 'gray', color: '#d2d2d2', displayName: '灰色' },
        { name: '红色', value: 'red', color: '#eb564f', displayName: '红色' },
        { name: '淡粉色', value: 'light_pink', color: '#ee7bbc', displayName: '淡粉色' },
        { name: '深粉色', value: 'dark_pink', color: '#d24594', displayName: '深粉色' },
        { name: '淡绿色', value: 'light_green', color: '#aeed94', displayName: '淡绿色' },
        { name: '深绿色', value: 'dark_green', color: '#57a748', displayName: '深绿色' },
        { name: '深紫色', value: 'dark_purple', color: '#925fde', displayName: '深紫色' },
        { name: '淡紫色', value: 'light_purple', color: '#ac91f8', displayName: '淡紫色' },
        { name: '深蓝色', value: 'dark_blue', color: '#3568f5', displayName: '深蓝色' },
        { name: '淡蓝色', value: 'light_blue', color: '#73e0dd', displayName: '淡蓝色' },
      ];

      // 预设关卡 - 修复了level1的数据并添加了新关卡
      const PRESETS = {
        level1: {
          numCups: 14,
          cups: [
            ['empty', 'empty', 'empty', 'empty'],
            ['empty', 'empty', 'empty', 'empty'],
            ['yellow', 'dark_pink', 'light_pink', 'brown'],
            ['dark_purple', 'light_green', 'yellow', 'dark_pink'],
            ['gray', 'dark_blue', 'gray', 'dark_green'],
            ['gray', 'brown', 'dark_blue', 'light_green'],
            ['red', 'light_purple', 'light_blue', 'light_pink'],
            ['dark_pink', 'light_blue', 'yellow', 'light_green'],
            ['light_purple', 'dark_purple', 'light_green', 'light_blue'],
            ['light_purple', 'brown', 'brown', 'light_purple'],
            ['dark_purple', 'dark_pink', 'dark_green', 'gray'],
            ['red', 'red', 'dark_blue', 'red'],
            ['yellow', 'light_pink', 'dark_blue', 'dark_green'],
            ['light_pink', 'dark_green', 'light_blue', 'dark_purple']
          ]
        }
      };

      // 全局变量
      let numCups = 14;
      let cups = [];
      let solution = null;
      let currentStepIndex = 0;
      let autoPlayInterval = null;
      let isSolving = false;
      let solveStartTime = 0;

      // 颜色选择弹窗相关变量
      let pendingSelection = null; // 存储待填充的位置 {cupIndex, layerIndex}
      let selectedModalColor = 'empty'; // 弹窗中选中的颜色

      // DOM元素
      const numCupsInput = document.getElementById('numCups');
      const applyCupsBtn = document.getElementById('applyCups');
      const cupGrid = document.getElementById('cupGrid');
      const solveBtn = document.getElementById('solveBtn');
      const resetBtn = document.getElementById('resetBtn');
      const presetSelector = document.getElementById('presetSelector');
      const loadPresetBtn = document.getElementById('loadPreset');
      const solutionSection = document.getElementById('solutionSection');
      const solutionSteps = document.getElementById('solutionSteps');
      const statusMessage = document.getElementById('statusMessage');
      const prevStepBtn = document.getElementById('prevStepBtn');
      const prevStepBtnSingle = document.getElementById('prevStepBtnSingle');
      const nextStepBtnSingle = document.getElementById('nextStepBtnSingle');
      const nextStepBtn = document.getElementById('nextStepBtn');
      const autoPlayBtn = document.getElementById('autoPlayBtn');
      const currentStepDisplay = document.getElementById('currentStep');
      const visualizationGrid = document.getElementById('visualizationGrid');
      const progressContainer = document.getElementById('progressContainer');
      const progressBar = document.getElementById('progressBar');
      const progressText = document.getElementById('progressText');
      const totalStepsEl = document.getElementById('totalSteps');
      const nodesSearchedEl = document.getElementById('nodesSearched');
      const solveTimeEl = document.getElementById('solveTime');
      const emptyCupsEl = document.getElementById('emptyCups');

      // 颜色弹窗 DOM 元素
      const colorModal = document.getElementById('colorModal');
      const modalColorGrid = document.getElementById('modalColorGrid');

      // 初始化
      function init () {

        // 生成弹窗颜色选择器
        generateModalColorPicker();

        // 应用初始杯子数量
        applyCupCount();

        // 加载事件监听器
        setupEventListeners();

        // 点击弹窗外部关闭
        colorModal.addEventListener('click', (e) => {
          if (e.target === colorModal) {
            closeColorModal();
          }
        });

        // 按ESC键关闭弹窗
        document.addEventListener('keydown', (e) => {
          if (e.key === 'Escape') {
            closeColorModal();
          }
        });

        // 弹窗中按Enter键确认选择
        document.addEventListener('keydown', (e) => {
          if (colorModal.classList.contains('active') && e.key === 'Enter') {
            applyColorSelection();
          }
        });

        // 加载默认预设
        loadPresetLevel('level1');

        // 更新空杯数量显示
        updateEmptyCupsDisplay();
      }

      // 生成弹窗颜色选择器
      function generateModalColorPicker () {
        modalColorGrid.innerHTML = '';
        COLORS.forEach(color => {
          const div = document.createElement('div');
          div.className = 'modal-color-option';
          if (color.value === 'empty') {
            div.classList.add('selected');
          }
          const isSpecial = color.value === 'empty' ? 'empty' : '';
          div.innerHTML = `
<div class="modal-color-box ${isSpecial}" style="background-color: ${color.color};"></div>
<div class="modal-color-name">${color.displayName}</div>
`;
          div.dataset.color = color.value;
          div.addEventListener('click', () => {
            // 更新选中状态
            modalColorGrid.querySelectorAll('.modal-color-option').forEach(opt => {
              opt.classList.remove('selected');
            });
            div.classList.add('selected');
            selectedModalColor = color.value;

            // 立即应用选择并关闭弹窗
            applyColorSelection();
          });
          modalColorGrid.appendChild(div);
        });
      }

      // 打开颜色选择弹窗
      function openColorModal (cupIndex, layerIndex) {
        pendingSelection = { cupIndex, layerIndex };
        selectedModalColor = 'empty'; // 默认选中空

        // 重置选中状态
        modalColorGrid.querySelectorAll('.modal-color-option').forEach(opt => {
          opt.classList.toggle('selected', opt.dataset.color === 'empty');
        });

        colorModal.classList.add('active');
      }

      // 关闭颜色选择弹窗
      function closeColorModal () {
        colorModal.classList.remove('active');
        pendingSelection = null;
        selectedModalColor = null;
      }

      // 应用颜色选择
      function applyColorSelection () {
        if (!pendingSelection) return;

        const { cupIndex, layerIndex } = pendingSelection;
        cups[cupIndex][layerIndex] = selectedModalColor;

        renderCups();
        updateSolveButton();
        updateEmptyCupsDisplay();

        closeColorModal();
      }

      // 应用杯子数量
      function applyCupCount () {
        numCups = parseInt(numCupsInput.value);
        if (numCups < 2) numCups = 2;
        if (numCups > 20) numCups = 20;
        numCupsInput.value = numCups;
        // 初始化杯子
        cups = [];
        for (let i = 0; i < numCups; i++) {
          cups.push(['empty', 'empty', 'empty', 'empty']);
        }
        renderCups();
        updateSolveButton();
        updateEmptyCupsDisplay();
      }

      // 渲染杯子编辑器
      function renderCups () {
        cupGrid.innerHTML = '';
        for (let i = 0; i < numCups; i++) {
          const cupContainer = document.createElement('div');
          cupContainer.className = 'cup-container';
          const cupLabel = document.createElement('div');
          cupLabel.className = 'cup-label';
          cupLabel.textContent = `杯${i + 1}`;
          cupContainer.appendChild(cupLabel);
          const cup = document.createElement('div');
          cup.className = 'cup';
          cup.id = `cup-${i}`;

          // 添加水层 - 从底部到顶部
          for (let j = 0; j < 4; j++) {
            const waterLayer = document.createElement('div');
            waterLayer.className = 'water-layer';
            const colorObj = COLORS.find(c => c.value === cups[i][j]);
            if (cups[i][j] === 'empty') {
              waterLayer.className += ' empty';
            } else {
              waterLayer.style.backgroundColor = colorObj.color;
              waterLayer.textContent = colorObj.displayName;
            }
            waterLayer.style.bottom = `${j * 25}%`;
            cup.appendChild(waterLayer);
          }
          cupContainer.appendChild(cup);

          // 添加水选择器
          const waterSelector = document.createElement('div');
          waterSelector.className = 'water-selector';
          for (let j = 0; j < 4; j++) {
            const btn = document.createElement('button');
            btn.dataset.cup = i;
            btn.dataset.layer = j;
            const colorObj = COLORS.find(c => c.value === cups[i][j]);
            btn.style.backgroundColor = colorObj.color;
            btn.className = cups[i][j] === 'empty' ? 'empty' : '';
            if (cups[i][j] === 'empty') {
              btn.textContent = '空';
            } else {
              btn.title = colorObj.displayName;
            }

            // 修改点击事件:打开颜色选择弹窗
            btn.addEventListener('click', () => {
              // 移除同杯子其他层的选中状态
              waterSelector.querySelectorAll('button').forEach(b => {
                b.classList.remove('selected');
              });
              // 高亮当前选中的层
              btn.classList.add('selected');
              // 打开颜色选择弹窗
              openColorModal(i, j);
            });

            waterSelector.appendChild(btn);
          }
          cupContainer.appendChild(waterSelector);
          cupGrid.appendChild(cupContainer);
        }
      }

      // 更新求解按钮状态
      function updateSolveButton () {
        // 检查是否有足够的数据来求解
        const hasNonEmptyCups = cups.some(cup =>
          cup.some(layer => layer !== 'empty')
        );
        solveBtn.disabled = !hasNonEmptyCups || isSolving;
      }

      // 更新空杯数量显示
      function updateEmptyCupsDisplay () {
        let emptyCount = 0;
        for (const cup of cups) {
          if (cup[0] === 'empty' && cup[1] === 'empty' && cup[2] === 'empty' && cup[3] === 'empty') {
            emptyCount++;
          }
        }
        emptyCupsEl.textContent = emptyCount;
      }

      // 设置事件监听器
      function setupEventListeners () {
        applyCupsBtn.addEventListener('click', applyCupCount);
        solveBtn.addEventListener('click', solvePuzzle);
        resetBtn.addEventListener('click', resetPuzzle);
        loadPresetBtn.addEventListener('click', () => {
          if (presetSelector.value) {
            loadPresetLevel(presetSelector.value);
          } else {
            showStatus('请选择一个预设关卡', 'warning');
          }
        });

        // 步骤导航
        prevStepBtn.addEventListener('click', () => {
          currentStepIndex = 0;
          updateVisualization();
        });
        prevStepBtnSingle.addEventListener('click', () => {
          if (currentStepIndex > 0) {
            currentStepIndex--;
            updateVisualization();
          }
        });
        nextStepBtnSingle.addEventListener('click', () => {
          if (currentStepIndex < solution.steps.length) {
            currentStepIndex++;
            updateVisualization();
          }
        });
        nextStepBtn.addEventListener('click', () => {
          currentStepIndex = solution.steps.length;
          updateVisualization();
        });
        autoPlayBtn.addEventListener('click', toggleAutoPlay);
      }

      // 加载预设关卡
      function loadPresetLevel (levelName) {
        if (!levelName || !PRESETS[levelName]) {
          showStatus('未找到指定的预设关卡', 'error');
          return;
        }
        const preset = PRESETS[levelName];
        numCups = preset.numCups;
        numCupsInput.value = numCups;
        cups = JSON.parse(JSON.stringify(preset.cups));
        // 如果预设的杯子数量与当前不同,重新渲染
        renderCups();
        updateSolveButton();
        updateEmptyCupsDisplay();
        // 更新下拉框选择
        presetSelector.value = levelName;
        showStatus(`已加载关卡`, 'success');
      }

      // 求解谜题
      function solvePuzzle () {
        if (isSolving) return;
        isSolving = true;
        solveStartTime = performance.now();
        showStatus('🔍 正在计算最优解决方案,请稍候...', 'info');
        solveBtn.disabled = true;
        resetBtn.disabled = true;
        loadPresetBtn.disabled = true;

        // 显示进度条
        progressContainer.style.display = 'block';
        progressBar.style.width = '5%';
        progressText.textContent = '初始化搜索...';

        // 使用Web Worker或分片处理避免UI冻结
        setTimeout(() => {
          try {
            // 更新进度
            progressBar.style.width = '15%';
            progressText.textContent = '分析初始状态...';

            // 检查初始状态是否有效
            if (!isValidState(cups)) {
              throw new Error('无效的杯子配置:每种颜色必须恰好有4份水');
            }

            // 更新进度
            progressBar.style.width = '25%';
            progressText.textContent = '启动A*搜索算法...';

            // 执行A*搜索
            solution = aStarSolve(cups);

            // 更新进度
            progressBar.style.width = '85%';
            progressText.textContent = '生成可视化步骤...';

            if (solution && solution.steps.length > 0) {
              displaySolution(solution);
              const solveTime = ((performance.now() - solveStartTime) / 1000).toFixed(2);
              showStatus(`✅ 找到最优解决方案!共 ${solution.steps.length} 步,耗时 ${solveTime} 秒`, 'success');
              solveTimeEl.textContent = solveTime;
            } else if (solution && solution.steps.length === 0) {
              showStatus('🎉 恭喜!当前配置已经是完成状态', 'success');
              solutionSection.style.display = 'block';
              // 隐藏可视化部分
              document.querySelector('.visualization').style.display = 'none';
            } else {
              showStatus('❌ 无法找到解决方案。请检查杯子配置是否有效,或尝试增加空杯数量', 'error');
            }
          } catch (error) {
            console.error('求解出错:', error);
            showStatus('❌ 求解过程中出错: ' + error.message, 'error');
          } finally {
            isSolving = false;
            solveBtn.disabled = false;
            resetBtn.disabled = false;
            loadPresetBtn.disabled = false;
            // 隐藏进度条
            setTimeout(() => {
              progressContainer.style.display = 'none';
            }, 500);
          }
        }, 50);
      }

      // 检查状态是否有效
      function isValidState (state) {
        const colorCount = {};
        for (const cup of state) {
          for (const layer of cup) {
            if (layer !== 'empty') {
              colorCount[layer] = (colorCount[layer] || 0) + 1;
            }
          }
        }
        // 检查每种颜色是否恰好4份
        for (const [color, count] of Object.entries(colorCount)) {
          if (count !== 4) {
            console.warn(`颜色 ${color} 有 ${count} 份,不是4份`);
            return false;
          }
        }
        return true;
      }

      // 显示解决方案
      function displaySolution (solution) {
        solutionSection.style.display = 'block';
        solutionSteps.innerHTML = '';

        // 更新统计信息
        totalStepsEl.textContent = solution.steps.length;
        nodesSearchedEl.textContent = solution.nodesVisited.toLocaleString();

        // 显示步骤
        solution.steps.forEach((step, index) => {
          const stepDiv = document.createElement('div');
          stepDiv.className = 'step';
          stepDiv.innerHTML = `
<div class="step-number">${index + 1}</div>
<div class="step-action">
<span class="step-cup cup-from">杯${step.from + 1}</span>
<span class="step-arrow">→</span>
<span class="step-cup cup-to">杯${step.to + 1}</span>
<span style="margin-left: 12px; color: #4a5568; font-weight: 500;">(倒 ${step.amount} 份${COLORS.find(c => c.value === step.color)?.displayName || ''})</span>
</div>
`;
          stepDiv.addEventListener('click', () => {
            currentStepIndex = index + 1;
            updateVisualization();
          });
          solutionSteps.appendChild(stepDiv);
        });

        // 初始化可视化
        currentStepIndex = 0;
        renderVisualizationGrid();
        updateVisualization();

        // 启用导航按钮
        prevStepBtn.disabled = true;
        prevStepBtnSingle.disabled = true;
        nextStepBtnSingle.disabled = false;
        nextStepBtn.disabled = false;
        autoPlayBtn.disabled = false;

        // 滚动到解决方案部分
        solutionSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }

      // 渲染可视化网格
      function renderVisualizationGrid () {
        visualizationGrid.innerHTML = '';
        for (let i = 0; i < numCups; i++) {
          const cupContainer = document.createElement('div');
          cupContainer.className = 'cup-container';
          const cupLabel = document.createElement('div');
          cupLabel.className = 'cup-label';
          cupLabel.textContent = `杯${i + 1}`;
          cupContainer.appendChild(cupLabel);
          const cup = document.createElement('div');
          cup.className = 'cup';
          cup.id = `vis-cup-${i}`;

          // 添加占位水层
          for (let j = 0; j < 4; j++) {
            const waterLayer = document.createElement('div');
            waterLayer.className = 'water-layer empty';
            waterLayer.style.bottom = `${j * 25}%`;
            cup.appendChild(waterLayer);
          }
          cupContainer.appendChild(cup);
          visualizationGrid.appendChild(cupContainer);
        }
      }

      // 更新可视化
      function updateVisualization () {
        // 更新步骤显示
        currentStepDisplay.textContent = `步骤: ${currentStepIndex}/${solution.steps.length}`;

        // 更新步骤高亮
        document.querySelectorAll('.step').forEach((step, index) => {
          step.classList.toggle('active', index === currentStepIndex - 1);
        });

        // 更新按钮状态
        prevStepBtn.disabled = currentStepIndex === 0;
        prevStepBtnSingle.disabled = currentStepIndex === 0;
        nextStepBtnSingle.disabled = currentStepIndex === solution.steps.length;
        nextStepBtn.disabled = currentStepIndex === solution.steps.length;

        // 获取当前状态
        const currentState = solution.states[currentStepIndex];

        // 更新杯子显示
        for (let i = 0; i < numCups; i++) {
          const cupElement = document.getElementById(`vis-cup-${i}`);
          const layers = cupElement.querySelectorAll('.water-layer');
          for (let j = 0; j < 4; j++) {
            const layer = layers[j];
            const colorValue = currentState[i][j];
            const colorObj = COLORS.find(c => c.value === colorValue);
            if (colorValue === 'empty') {
              layer.className = 'water-layer empty';
              layer.style.backgroundColor = '';
              layer.textContent = '';
            } else {
              layer.className = 'water-layer';
              layer.style.backgroundColor = colorObj.color;
              layer.textContent = colorObj.displayName;
            }
          }
        }

        // 滚动到当前步骤
        if (currentStepIndex > 0) {
          const activeStep = document.querySelector('.step.active');
          if (activeStep) {
            activeStep.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
          }
        }
      }

      // 切换自动播放
      function toggleAutoPlay () {
        if (autoPlayInterval) {
          clearInterval(autoPlayInterval);
          autoPlayInterval = null;
          autoPlayBtn.textContent = '⏯️ 自动播放';
          autoPlayBtn.classList.remove('playing');
        } else {
          autoPlayBtn.textContent = '⏹️ 停止播放';
          autoPlayBtn.classList.add('playing');
          autoPlayInterval = setInterval(() => {
            if (currentStepIndex < solution.steps.length) {
              currentStepIndex++;
              updateVisualization();
            } else {
              clearInterval(autoPlayInterval);
              autoPlayInterval = null;
              autoPlayBtn.textContent = '⏯️ 自动播放';
              autoPlayBtn.classList.remove('playing');
            }
          }, 1000);
        }
      }

      // 重置谜题
      function resetPuzzle () {
        cups = [];
        for (let i = 0; i < numCups; i++) {
          cups.push(['empty', 'empty', 'empty', 'empty']);
        }
        renderCups();
        solutionSection.style.display = 'none';
        showStatus('🔄 已重置谜题配置', 'success');
        updateSolveButton();
        updateEmptyCupsDisplay();

        // 重置统计信息
        totalStepsEl.textContent = '0';
        nodesSearchedEl.textContent = '0';
        solveTimeEl.textContent = '0.0';
      }

      // 数组洗牌函数(Fisher-Yates算法)
      function shuffleArray (array) {
        for (let i = array.length - 1; i > 0; i--) {
          const j = Math.floor(Math.random() * (i + 1));
          [array[i], array[j]] = [array[j], array[i]];
        }
        return array;
      }

      // 显示状态消息
      function showStatus (message, type) {
        statusMessage.textContent = message;
        statusMessage.className = 'status-message';
        statusMessage.classList.add(`status-${type}`);
        statusMessage.style.display = 'block';

        // 5秒后自动隐藏(错误消息显示更久)
        const duration = type === 'error' ? 8000 : 5000;
        setTimeout(() => {
          statusMessage.style.display = 'none';
        }, duration);
      }

      // ==================== 优化后的 A* 搜索算法 ====================

      // 判断状态是否为目标状态
      function isGoalState (state) {
        return state.every(cup => {
          // 空杯子
          if (cup[0] === 'empty') return true;
          // 检查是否全为同一种颜色
          const firstColor = cup[0];
          for (let i = 1; i < 4; i++) {
            if (cup[i] !== 'empty' && cup[i] !== firstColor) {
              return false;
            }
            // 如果遇到空层,后续必须全为空
            if (cup[i] === 'empty') {
              for (let j = i + 1; j < 4; j++) {
                if (cup[j] !== 'empty') return false;
              }
              break;
            }
          }
          return true;
        });
      }

      // 获取所有可能的移动 - 优化版
      function getPossibleMoves (state) {
        const moves = [];
        const numCups = state.length;

        for (let from = 0; from < numCups; from++) {
          // 找到from杯的顶部非空水块:从顶部(索引3)向下找第一个非空
          let topIndex = -1;
          let topColor = null;
          for (let i = 3; i >= 0; i--) {
            if (state[from][i] !== 'empty') {
              topIndex = i;
              topColor = state[from][i];
              break;
            }
          }
          if (topIndex === -1) continue; // 杯子为空

          // 计算from杯顶部连续同色数量:从topIndex开始向下(索引减小)连续同色
          let topCount = 1;
          for (let i = topIndex - 1; i >= 0; i--) {
            if (state[from][i] === topColor) {
              topCount++;
            } else {
              break;
            }
          }

          for (let to = 0; to < numCups; to++) {
            if (from === to) continue;

            // 检查to杯的空余空间:从顶部(索引3)向下找连续的空层数量
            let emptySpaces = 0;
            for (let i = 3; i >= 0; i--) {
              if (state[to][i] === 'empty') {
                emptySpaces++;
              } else {
                break;
              }
            }
            if (emptySpaces === 0) continue; // to杯已满

            // 获取to杯的顶部颜色(如果非空)
            let toTopColor = null;
            if (emptySpaces < 4) {
              // 顶部非空层的索引 = 3 - emptySpaces
              toTopColor = state[to][3 - emptySpaces];
            }

            // 规则:to杯为空,或者to杯顶部颜色与from杯顶部颜色相同
            if (toTopColor === null || toTopColor === topColor) {
              const amount = Math.min(topCount, emptySpaces);
              // 避免无意义的移动(倒0份或倒满整个杯子但颜色相同)
              if (amount > 0) {
                moves.push({ from, to, amount, color: topColor });
              }
            }
          }
        }
        return moves;
      }

      // 执行移动 - 优化版
      function applyMove (state, move) {
        const newState = state.map(cup => [...cup]);

        // 从from杯移除水:从顶部非空层开始,向下移除连续的move.amount个move.color
        // 找到from杯的顶部非空索引
        let fromTopIndex = -1;
        for (let i = 3; i >= 0; i--) {
          if (newState[move.from][i] !== 'empty') {
            fromTopIndex = i;
            break;
          }
        }
        if (fromTopIndex === -1) {
          // 杯子为空,不应该发生
          return newState;
        }

        // 从fromTopIndex开始,向下(索引减小)移除连续的move.color,移除move.amount个
        let removed = 0;
        for (let i = fromTopIndex; i >= 0 && removed < move.amount; i--) {
          if (newState[move.from][i] === move.color) {
            newState[move.from][i] = 'empty';
            removed++;
          } else {
            // 遇到不同颜色,停止(理论上不会,因为move是合法的)
            break;
          }
        }

        // 向to杯添加水:找到to杯中第一个空层的索引(从底部开始,即索引0开始)
        let firstEmptyIndex = 0;
        while (firstEmptyIndex < 4 && newState[move.to][firstEmptyIndex] !== 'empty') {
          firstEmptyIndex++;
        }

        // 从firstEmptyIndex开始,填充move.amount个move.color
        for (let i = 0; i < move.amount; i++) {
          if (firstEmptyIndex + i < 4) {
            newState[move.to][firstEmptyIndex + i] = move.color;
          } else {
            // 不应该发生,因为emptySpaces>=amount
            break;
          }
        }

        return newState;
      }

      // 优化后的启发函数
      function heuristic (state) {
        let totalSegments = 0;
        let emptyCups = 0;
        const colorPositions = new Map(); // 记录每种颜色出现的杯子

        for (let cupIndex = 0; cupIndex < state.length; cupIndex++) {
          const cup = state[cupIndex];

          // 检查空杯
          if (cup[0] === 'empty') {
            emptyCups++;
            continue;
          }

          // 计算杯子中的段数
          let segments = 1;
          let currentColor = cup[0];

          // 记录颜色出现的杯子
          if (!colorPositions.has(currentColor)) {
            colorPositions.set(currentColor, new Set());
          }
          colorPositions.get(currentColor).add(cupIndex);

          // 从索引1到3,如果当前层非空且与前一层不同,则段数+1
          for (let i = 1; i < 4; i++) {
            // 如果当前层为空,则后面都是空,停止
            if (cup[i] === 'empty') {
              break;
            }

            // 记录颜色出现的杯子
            if (!colorPositions.has(cup[i])) {
              colorPositions.set(cup[i], new Set());
            }
            colorPositions.get(cup[i]).add(cupIndex);

            if (cup[i] !== cup[i - 1]) {
              segments++;
            }
          }
          totalSegments += segments;
        }

        // 计算颜色分散度惩罚
        let dispersionPenalty = 0;
        for (const [color, cups] of colorPositions.entries()) {
          // 每种颜色理想情况下应在1个杯子中,每多一个杯子增加惩罚
          dispersionPenalty += (cups.size - 1) * 2;
        }

        // 启发值 = 总段数 + 分散度惩罚 - 空杯子的奖励
        // 空杯子可以帮助减少段数,所以是负惩罚
        return totalSegments + dispersionPenalty - emptyCups * 1.5;
      }

      // 优化后的A*搜索算法
      function aStarSolve (initialState) {
        // 检查初始状态是否已经是目标状态
        if (isGoalState(initialState)) {
          return {
            steps: [],
            states: [initialState],
            nodesVisited: 0
          };
        }

        // 优先队列(最小堆)
        const openSet = [];

        // 闭集:使用Map存储已访问状态,键为状态的规范表示
        const closedSet = new Map();

        // 状态规范化函数(用于高效比较)
        function getStateKey (state) {
          return state.map(cup =>
            cup.join(',')
          ).join('|');
        }

        // 初始节点
        const startNode = {
          state: initialState,
          gScore: 0,
          fScore: heuristic(initialState),
          parent: null,
          move: null
        };

        openSet.push(startNode);
        let nodesVisited = 0;
        const maxNodes = 100000; // 最大搜索节点数限制

        // 最小堆辅助函数
        function pushToHeap (heap, node) {
          heap.push(node);
          // 简单排序(对于小规模搜索足够高效)
          heap.sort((a, b) => a.fScore - b.fScore);
        }

        function popFromHeap (heap) {
          return heap.shift();
        }

        while (openSet.length > 0) {
          nodesVisited++;

          // 更新进度
          if (nodesVisited % 500 === 0) {
            const progress = Math.min(80, 25 + (nodesVisited / maxNodes) * 55);
            progressBar.style.width = `${progress}%`;
            progressText.textContent = `搜索中... 已访问 ${nodesVisited.toLocaleString()} 个节点`;
          }

          // 检查是否超过最大节点数
          if (nodesVisited > maxNodes) {
            throw new Error(`搜索过于复杂(已访问 ${nodesVisited.toLocaleString()} 个节点),可能无解或需要更多空杯。尝试增加空杯数量或简化配置。`);
          }

          // 从优先队列中取出fScore最小的节点
          const current = popFromHeap(openSet);
          const currentStateKey = getStateKey(current.state);

          // 检查是否为目标状态
          if (isGoalState(current.state)) {
            // 重建路径
            const steps = [];
            const states = [initialState];
            let node = current;
            while (node.parent !== null) {
              steps.unshift(node.move);
              states.unshift(node.state);
              node = node.parent;
            }
            // 添加最终状态
            states.unshift(current.state);

            return {
              steps,
              states,
              nodesVisited
            };
          }

          // 将当前状态加入闭集
          closedSet.set(currentStateKey, current);

          // 获取所有可能的移动
          const moves = getPossibleMoves(current.state);

          for (const move of moves) {
            const newState = applyMove(current.state, move);
            const newStateKey = getStateKey(newState);

            // 跳过已在闭集中的状态
            if (closedSet.has(newStateKey)) {
              continue;
            }

            const gScore = current.gScore + 1;
            const hScore = heuristic(newState);
            const fScore = gScore + hScore;

            // 检查是否已在openSet中
            let existingNode = null;
            for (let i = 0; i < openSet.length; i++) {
              if (getStateKey(openSet[i].state) === newStateKey) {
                existingNode = openSet[i];
                break;
              }
            }

            if (existingNode) {
              if (gScore < existingNode.gScore) {
                // 更新节点
                existingNode.gScore = gScore;
                existingNode.fScore = fScore;
                existingNode.parent = current;
                existingNode.move = move;
                // 重新排序
                openSet.sort((a, b) => a.fScore - b.fScore);
              }
            } else {
              // 新节点
              pushToHeap(openSet, {
                state: newState,
                gScore,
                fScore,
                parent: current,
                move
              });
            }
          }
        }

        return null; // 未找到解决方案
      }

      // 初始化应用
      window.addEventListener('DOMContentLoaded', init);
    </script>
  </body>

</html>
相关推荐
魔士于安1 天前
unity urp材质球大全
游戏·unity·游戏引擎·材质·贴图·模型
Swift社区1 天前
鸿蒙游戏 UI 怎么设计才不乱?
游戏·ui·harmonyos
Swift社区2 天前
鸿蒙游戏中的“智能 NPC”架构设计
游戏·华为·harmonyos
王杨游戏养站系统2 天前
3分钟!玩转游戏下载站系统!蜘蛛池seo功能完善部署!
游戏·游戏下载站养站系统·游戏养站系统
魔士于安2 天前
unity 低多边形 无人小村 木质建筑 晾衣架 盆子手推车,桌子椅子,罐子,水井
游戏·unity·游戏引擎·贴图·模型
魔士于安2 天前
unity 骷髅人 连招 武器 刀光 扭曲空气
游戏·unity·游戏引擎·贴图·模型
前端不太难2 天前
为什么 AI 游戏更适合鸿蒙?
人工智能·游戏·harmonyos
沙振宇2 天前
【Web】使用Vue3+PlayCanvas开发3D游戏(十一)渲染3D高斯泼溅效果
前端·游戏·3d
wanhengidc2 天前
服务器 数据科技发展
运维·服务器·爬虫·科技·游戏·智能手机
Clank的游戏栈2 天前
AI实战:如何更好的使用AI开发游戏项目
人工智能·游戏