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>
相关推荐
陈天伟教授2 小时前
人工智能应用- 人机对战:03.玩转 ATARI 游戏
人工智能·神经网络·游戏·语言模型·自然语言处理·机器翻译
暮志未晚Webgl4 小时前
UE5实现游戏中英文切换的本地化功能
游戏·ue5
2501_946490385 小时前
xR虚拟制片技术在游戏IP展演中的高密度拍摄落地实践——基于hecoos xR+UE4的技术实现
tcp/ip·游戏·xr
wanhengidc15 小时前
私有云具体是指什么
服务器·网络·游戏·智能手机·云计算
dalong101 天前
A24:圈住小猫游戏
笔记·算法·游戏·aardio
斯内科1 天前
C#德州扑克梭哈游戏(2):牌型与点数比较
游戏·c#·梭哈
Pure_White_Sword1 天前
bugku-reverse题目-游戏过关
游戏·网络安全·ctf·reverse·逆向工程
斯内科1 天前
C#德州扑克梭哈游戏(1):简介与初步枚举定义
游戏·德州扑克·梭哈