Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7de43ece9e |
@ -1,302 +1,316 @@
|
||||
:root {
|
||||
--bg-dark: #0f172a;
|
||||
--bg-mesh: #020617;
|
||||
--card-bg: rgba(30, 41, 59, 0.7);
|
||||
--accent: #38bdf8;
|
||||
--accent-hover: #7dd3fc;
|
||||
--text-main: #f8fafc;
|
||||
--text-muted: #94a3b8;
|
||||
--snake-head: #4ade80;
|
||||
--snake-body: #166534;
|
||||
--apple: #ef4444;
|
||||
--wall: #475569;
|
||||
--font-main: 'Inter', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
--shadow-sm: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient 15s ease infinite;
|
||||
color: #212529;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: var(--font-main);
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--text-main);
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
.background-mesh {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background-color: var(--bg-mesh);
|
||||
background-image:
|
||||
radial-gradient(at 0% 0%, rgba(56, 189, 248, 0.15) 0px, transparent 50%),
|
||||
radial-gradient(at 100% 0%, rgba(139, 92, 246, 0.15) 0px, transparent 50%),
|
||||
radial-gradient(at 100% 100%, rgba(236, 72, 153, 0.1) 0px, transparent 50%),
|
||||
radial-gradient(at 0% 100%, rgba(56, 189, 248, 0.1) 0px, transparent 50%);
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 85vh;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
|
||||
backdrop-filter: blur(15px);
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
overflow: hidden;
|
||||
.main-header {
|
||||
padding: 1.5rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.main-header h1 {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.025em;
|
||||
margin-bottom: 0.25rem;
|
||||
background: linear-gradient(to right, var(--accent), #818cf8);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
.badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.2rem 0.6rem;
|
||||
background: var(--accent);
|
||||
border-radius: 9999px;
|
||||
-webkit-text-fill-color: var(--bg-dark);
|
||||
vertical-align: middle;
|
||||
margin-left: 0.4rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
.subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
.main-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
@media (min-width: 968px) {
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 340px;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1.25rem;
|
||||
padding: 1.25rem;
|
||||
box-shadow: var(--shadow-xl);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 85%;
|
||||
padding: 0.85rem 1.1rem;
|
||||
border-radius: 16px;
|
||||
line-height: 1.5;
|
||||
font-size: 0.95rem;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
|
||||
animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
.card h3 {
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px) scale(0.95); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
.canvas-wrapper {
|
||||
position: relative;
|
||||
background: #000;
|
||||
border-radius: 1.25rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
aspect-ratio: 1 / 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.message.visitor {
|
||||
align-self: flex-end;
|
||||
background: linear-gradient(135deg, #212529 0%, #343a40 100%);
|
||||
color: #fff;
|
||||
border-bottom-right-radius: 4px;
|
||||
#gameCanvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.message.bot {
|
||||
align-self: flex-start;
|
||||
background: #ffffff;
|
||||
color: #212529;
|
||||
border-bottom-left-radius: 4px;
|
||||
.overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
transition: opacity 0.3s ease;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.chat-input-area {
|
||||
padding: 1.25rem;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
.overlay.hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-input-area form {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
.overlay-content h2 {
|
||||
font-size: 2.5rem;
|
||||
color: var(--snake-head);
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 0 0 15px rgba(74, 222, 128, 0.3);
|
||||
}
|
||||
|
||||
.chat-input-area input {
|
||||
flex: 1;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
outline: none;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
transition: all 0.3s ease;
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.chat-input-area input:focus {
|
||||
border-color: #23a6d5;
|
||||
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
|
||||
.stat-item {
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
padding: 0.75rem;
|
||||
border-radius: 1rem;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.chat-input-area button {
|
||||
background: #212529;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
.stat-item:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(56, 189, 248, 0.2);
|
||||
}
|
||||
|
||||
.chat-input-area button:hover {
|
||||
background: #000;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
.stat-item .label {
|
||||
display: block;
|
||||
font-size: 0.65rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.2rem;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Background Animations */
|
||||
.bg-animations {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
.stat-item .value {
|
||||
display: block;
|
||||
font-size: 1.25rem;
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 700;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.blob {
|
||||
position: absolute;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
|
||||
.control-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.blob-1 {
|
||||
top: -10%;
|
||||
left: -10%;
|
||||
background: rgba(238, 119, 82, 0.4);
|
||||
.control-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.blob-2 {
|
||||
bottom: -10%;
|
||||
right: -10%;
|
||||
background: rgba(35, 166, 213, 0.4);
|
||||
animation-delay: -7s;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
input[type="range"] {
|
||||
width: 100%;
|
||||
accent-color: var(--accent);
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.blob-3 {
|
||||
top: 40%;
|
||||
left: 30%;
|
||||
background: rgba(231, 60, 126, 0.3);
|
||||
animation-delay: -14s;
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
.range-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% { transform: translate(0, 0) rotate(0deg) scale(1); }
|
||||
33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
|
||||
66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
|
||||
100% { transform: translate(0, 0) rotate(360deg) scale(1); }
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.admin-link {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.85rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
.admin-link:hover {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
text-decoration: none;
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--accent), #6366f1);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(56, 189, 248, 0.3);
|
||||
}
|
||||
|
||||
/* Admin Styles */
|
||||
.admin-container {
|
||||
max-width: 900px;
|
||||
margin: 3rem auto;
|
||||
padding: 2.5rem;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 20px 50px rgba(0,0,0,0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(56, 189, 248, 0.4);
|
||||
}
|
||||
|
||||
.admin-container h1 {
|
||||
margin-top: 0;
|
||||
color: #212529;
|
||||
font-weight: 800;
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-main);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 8px;
|
||||
margin-top: 1.5rem;
|
||||
.btn-secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.table th {
|
||||
background: transparent;
|
||||
border: none;
|
||||
.info-card p {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-card strong {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.main-footer {
|
||||
margin-top: auto;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.main-header h1 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.canvas-wrapper {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem;
|
||||
color: #6c757d;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.overlay-content h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.table td {
|
||||
background: #fff;
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.table tr td:first-child { border-radius: 12px 0 0 12px; }
|
||||
.table tr td:last-child { border-radius: 0 12px 12px 0; }
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
transition: all 0.3s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #23a6d5;
|
||||
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
|
||||
@media (max-height: 700px) and (orientation: landscape) {
|
||||
.main-layout {
|
||||
flex-direction: row;
|
||||
}
|
||||
.canvas-wrapper {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@ -1,39 +1,599 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const chatForm = document.getElementById('chat-form');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
/**
|
||||
* Auto-Snake: AI-Powered Snake Game
|
||||
* Implements Advanced Pathfinding (BFS + Longest Path Survival)
|
||||
* to ensure the snake fills the grid and avoids infinite loops.
|
||||
*/
|
||||
|
||||
const appendMessage = (text, sender) => {
|
||||
const msgDiv = document.createElement('div');
|
||||
msgDiv.classList.add('message', sender);
|
||||
msgDiv.textContent = text;
|
||||
chatMessages.appendChild(msgDiv);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
};
|
||||
const CONFIG = {
|
||||
GRID_SIZE: 20,
|
||||
TILE_SIZE: 0,
|
||||
COLORS: {
|
||||
BG: '#020617',
|
||||
SNAKE_HEAD: '#4ade80',
|
||||
SNAKE_BODY: '#22c55e',
|
||||
SNAKE_BODY_GRADIENT: '#166534',
|
||||
APPLE: '#ef4444',
|
||||
APPLE_SHINE: '#fca5a5',
|
||||
WALL: '#475569',
|
||||
GRID: 'rgba(56, 189, 248, 0.05)'
|
||||
}
|
||||
};
|
||||
|
||||
chatForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const message = chatInput.value.trim();
|
||||
if (!message) return;
|
||||
class Game {
|
||||
constructor() {
|
||||
this.canvas = document.getElementById('gameCanvas');
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
|
||||
this.winCount = parseInt(localStorage.getItem('winCount') || '0');
|
||||
this.bestScore = parseInt(localStorage.getItem('bestScore') || '0');
|
||||
this.lastWinTime = parseInt(localStorage.getItem('lastWinTime') || Date.now());
|
||||
|
||||
this.speedRange = document.getElementById('speedRange');
|
||||
this.winDisplay = document.getElementById('winCount');
|
||||
this.timerDisplay = document.getElementById('timer');
|
||||
this.scoreDisplay = document.getElementById('currentScore');
|
||||
this.bestScoreDisplay = document.getElementById('bestScore');
|
||||
this.resetBtn = document.getElementById('resetBtn');
|
||||
this.toggleMazeBtn = document.getElementById('toggleMazeBtn');
|
||||
this.overlay = document.getElementById('gameOverlay');
|
||||
|
||||
this.snake = [];
|
||||
this.apple = { x: 0, y: 0 };
|
||||
this.mazeEnabled = false;
|
||||
this.walls = [];
|
||||
this.isGameOver = false;
|
||||
this.isWin = false;
|
||||
this.frameCount = 0;
|
||||
|
||||
this.lastFrameTime = 0;
|
||||
|
||||
this.init();
|
||||
this.setupEventListeners();
|
||||
this.reset();
|
||||
this.gameLoop(0);
|
||||
}
|
||||
|
||||
appendMessage(message, 'visitor');
|
||||
chatInput.value = '';
|
||||
init() {
|
||||
const resize = () => {
|
||||
const container = this.canvas.parentElement;
|
||||
const size = Math.min(container.clientWidth, 600);
|
||||
this.canvas.width = size;
|
||||
this.canvas.height = size;
|
||||
CONFIG.TILE_SIZE = size / CONFIG.GRID_SIZE;
|
||||
};
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
this.winDisplay.textContent = this.winCount;
|
||||
this.bestScoreDisplay.textContent = this.bestScore;
|
||||
this.updateTimer();
|
||||
setInterval(() => this.updateTimer(), 1000);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('api/chat.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
// Artificial delay for realism
|
||||
setTimeout(() => {
|
||||
appendMessage(data.reply, 'bot');
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
|
||||
setupEventListeners() {
|
||||
this.resetBtn.addEventListener('click', () => {
|
||||
this.winCount = 0;
|
||||
this.bestScore = 0;
|
||||
localStorage.setItem('winCount', '0');
|
||||
localStorage.setItem('bestScore', '0');
|
||||
this.winDisplay.textContent = '0';
|
||||
this.bestScoreDisplay.textContent = '0';
|
||||
this.reset();
|
||||
});
|
||||
|
||||
this.toggleMazeBtn.addEventListener('click', () => {
|
||||
this.mazeEnabled = !this.mazeEnabled;
|
||||
this.reset();
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
const mid = Math.floor(CONFIG.GRID_SIZE / 2);
|
||||
this.snake = [
|
||||
{ x: mid, y: mid },
|
||||
{ x: mid, y: mid + 1 },
|
||||
{ x: mid, y: mid + 2 }
|
||||
];
|
||||
this.isGameOver = false;
|
||||
this.isWin = false;
|
||||
this.overlay.classList.add('hidden');
|
||||
|
||||
this.generateMaze();
|
||||
this.spawnApple();
|
||||
this.scoreDisplay.textContent = '0';
|
||||
}
|
||||
|
||||
generateMaze() {
|
||||
this.walls = [];
|
||||
if (!this.mazeEnabled) return;
|
||||
|
||||
const padding = 4;
|
||||
const length = 8;
|
||||
for (let i = padding; i < padding + length; i++) {
|
||||
this.walls.push({ x: i, y: padding });
|
||||
this.walls.push({ x: CONFIG.GRID_SIZE - 1 - i, y: CONFIG.GRID_SIZE - 1 - padding });
|
||||
this.walls.push({ x: padding, y: CONFIG.GRID_SIZE - 1 - i });
|
||||
this.walls.push({ x: CONFIG.GRID_SIZE - 1 - padding, y: i });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
spawnApple() {
|
||||
let possible = [];
|
||||
for (let y = 0; y < CONFIG.GRID_SIZE; y++) {
|
||||
for (let x = 0; x < CONFIG.GRID_SIZE; x++) {
|
||||
if (!this.isOccupied(x, y)) possible.push({ x, y });
|
||||
}
|
||||
}
|
||||
|
||||
if (possible.length === 0) {
|
||||
this.handleWin();
|
||||
return;
|
||||
}
|
||||
this.apple = possible[Math.floor(Math.random() * possible.length)];
|
||||
}
|
||||
|
||||
isOccupied(x, y, ignoreTail = false) {
|
||||
if (x < 0 || x >= CONFIG.GRID_SIZE || y < 0 || y >= CONFIG.GRID_SIZE) return true;
|
||||
const snakeToCheck = ignoreTail ? this.snake.slice(0, -1) : this.snake;
|
||||
if (snakeToCheck.some(s => s.x === x && s.y === y)) return true;
|
||||
if (this.walls.some(w => w.x === x && w.y === y)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
updateTimer() {
|
||||
const diff = Math.floor((Date.now() - this.lastWinTime) / 1000);
|
||||
const mins = Math.floor(diff / 60).toString().padStart(2, '0');
|
||||
const secs = (diff % 60).toString().padStart(2, '0');
|
||||
this.timerDisplay.textContent = `${mins}:${secs}`;
|
||||
}
|
||||
|
||||
handleWin() {
|
||||
this.isWin = true;
|
||||
this.winCount++;
|
||||
this.lastWinTime = Date.now();
|
||||
localStorage.setItem('winCount', this.winCount);
|
||||
localStorage.setItem('lastWinTime', this.lastWinTime);
|
||||
this.winDisplay.textContent = this.winCount;
|
||||
this.overlay.classList.remove('hidden');
|
||||
document.getElementById('overlayTitle').textContent = 'VICTORY!';
|
||||
document.getElementById('overlayMessage').textContent = 'Perfect cycle complete. Restarting...';
|
||||
setTimeout(() => this.reset(), 5000);
|
||||
}
|
||||
|
||||
handleGameOver() {
|
||||
this.isGameOver = true;
|
||||
this.overlay.classList.remove('hidden');
|
||||
document.getElementById('overlayTitle').textContent = 'RECALCULATING';
|
||||
document.getElementById('overlayMessage').textContent = 'Wait, adjusting strategy...';
|
||||
setTimeout(() => this.reset(), 1000);
|
||||
}
|
||||
|
||||
bfs(start, target, ignoreTail = false) {
|
||||
const queue = [[start]];
|
||||
const visited = new Set();
|
||||
visited.add(`${start.x},${start.y}`);
|
||||
|
||||
while (queue.length > 0) {
|
||||
const path = queue.shift();
|
||||
const curr = path[path.length - 1];
|
||||
if (curr.x === target.x && curr.y === target.y) return path;
|
||||
|
||||
const neighbors = [
|
||||
{ x: curr.x, y: curr.y - 1 }, { x: curr.x, y: curr.y + 1 },
|
||||
{ x: curr.x - 1, y: curr.y }, { x: curr.x + 1, y: curr.y }
|
||||
];
|
||||
|
||||
for (const n of neighbors) {
|
||||
if (!this.isOccupied(n.x, n.y, ignoreTail) && !visited.has(`${n.x},${n.y}`)) {
|
||||
visited.add(`${n.x},${n.y}`);
|
||||
queue.push([...path, n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getLongestPath(start, target) {
|
||||
let path = this.bfs(start, target, true);
|
||||
if (!path) return null;
|
||||
|
||||
let extended = true;
|
||||
while (extended) {
|
||||
extended = false;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const p1 = path[i];
|
||||
const p2 = path[i+1];
|
||||
|
||||
const dirs = [
|
||||
{ x: 0, y: -1 }, { x: 0, y: 1 },
|
||||
{ x: -1, y: 0 }, { x: 1, y: 0 }
|
||||
];
|
||||
|
||||
for (const d of dirs) {
|
||||
const n1 = { x: p1.x + d.x, y: p1.y + d.y };
|
||||
const n2 = { x: p2.x + d.x, y: p2.y + d.y };
|
||||
|
||||
if (!this.isOccupied(n1.x, n1.y, true) && !this.isOccupied(n2.x, n2.y, true) &&
|
||||
!path.some(p => p.x === n1.x && p.y === n1.y) &&
|
||||
!path.some(p => p.x === n2.x && p.y === n2.y)) {
|
||||
|
||||
path.splice(i + 1, 0, n1, n2);
|
||||
extended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (extended) break;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
canReachTail(virtualSnake) {
|
||||
if (virtualSnake.length < 2) return true;
|
||||
const head = virtualSnake[0];
|
||||
const tail = virtualSnake[virtualSnake.length - 1];
|
||||
|
||||
const originalSnake = this.snake;
|
||||
this.snake = virtualSnake;
|
||||
const path = this.bfs(head, tail, true);
|
||||
this.snake = originalSnake;
|
||||
return !!path;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.isGameOver || this.isWin) return;
|
||||
|
||||
const head = this.snake[0];
|
||||
const tail = this.snake[this.snake.length - 1];
|
||||
|
||||
// 1. Path to apple
|
||||
let path = this.bfs(head, this.apple, true);
|
||||
let nextMove = null;
|
||||
|
||||
if (path && path.length > 1) {
|
||||
const virtualSnake = [path[1], ...this.snake];
|
||||
if (!(path[1].x === this.apple.x && path[1].y === this.apple.y)) {
|
||||
virtualSnake.pop();
|
||||
}
|
||||
|
||||
if (this.canReachTail(virtualSnake)) {
|
||||
nextMove = path[1];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback: Longest path to tail
|
||||
if (!nextMove) {
|
||||
const pathToTail = this.getLongestPath(head, tail);
|
||||
if (pathToTail && pathToTail.length > 1) {
|
||||
nextMove = pathToTail[1];
|
||||
} else {
|
||||
nextMove = this.getSurvivalMove();
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextMove) {
|
||||
this.handleGameOver();
|
||||
return;
|
||||
}
|
||||
|
||||
this.snake.unshift(nextMove);
|
||||
if (nextMove.x === this.apple.x && nextMove.y === this.apple.y) {
|
||||
const score = this.snake.length - 3;
|
||||
this.scoreDisplay.textContent = score;
|
||||
if (score > this.bestScore) {
|
||||
this.bestScore = score;
|
||||
this.bestScoreDisplay.textContent = score;
|
||||
localStorage.setItem('bestScore', score);
|
||||
}
|
||||
this.spawnApple();
|
||||
} else {
|
||||
this.snake.pop();
|
||||
}
|
||||
this.frameCount++;
|
||||
}
|
||||
|
||||
getSurvivalMove() {
|
||||
const head = this.snake[0];
|
||||
const neighbors = [
|
||||
{ x: head.x, y: head.y - 1 }, { x: head.x, y: head.y + 1 },
|
||||
{ x: head.x - 1, y: head.y }, { x: head.x + 1, y: head.y }
|
||||
].filter(n => !this.isOccupied(n.x, n.y));
|
||||
|
||||
if (neighbors.length === 0) return null;
|
||||
return neighbors.sort((a, b) => this.getReachableArea(b) - this.getReachableArea(a))[0];
|
||||
}
|
||||
|
||||
getReachableArea(pos) {
|
||||
const visited = new Set();
|
||||
const stack = [pos];
|
||||
visited.add(`${pos.x},${pos.y}`);
|
||||
let count = 0;
|
||||
|
||||
while (stack.length > 0) {
|
||||
const curr = stack.pop();
|
||||
count++;
|
||||
|
||||
const neighbors = [
|
||||
{ x: curr.x, y: curr.y - 1 }, { x: curr.x, y: curr.y + 1 },
|
||||
{ x: curr.x - 1, y: curr.y }, { x: curr.x + 1, y: curr.y }
|
||||
];
|
||||
|
||||
for (const n of neighbors) {
|
||||
if (!this.isOccupied(n.x, n.y) && !visited.has(`${n.x},${n.y}`)) {
|
||||
visited.add(`${n.x},${n.y}`);
|
||||
stack.push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
draw() {
|
||||
const { ctx, canvas } = this;
|
||||
const ts = CONFIG.TILE_SIZE;
|
||||
|
||||
ctx.fillStyle = CONFIG.COLORS.BG;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Grid
|
||||
ctx.strokeStyle = CONFIG.COLORS.GRID;
|
||||
ctx.lineWidth = 0.5;
|
||||
for (let i = 0; i <= CONFIG.GRID_SIZE; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(i * ts, 0); ctx.lineTo(i * ts, canvas.height); ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, i * ts); ctx.lineTo(canvas.width, i * ts); ctx.stroke();
|
||||
}
|
||||
|
||||
// Walls
|
||||
this.walls.forEach(w => {
|
||||
const x = w.x * ts, y = w.y * ts;
|
||||
ctx.fillStyle = CONFIG.COLORS.WALL;
|
||||
this.drawTexturedRect(ctx, x, y, ts, ts, 'wall');
|
||||
});
|
||||
|
||||
// Apple
|
||||
const ax = this.apple.x * ts + ts/2, ay = this.apple.y * ts + ts/2;
|
||||
this.drawApple(ctx, ax, ay, ts);
|
||||
|
||||
// Snake
|
||||
for (let i = this.snake.length - 1; i >= 0; i--) {
|
||||
const s = this.snake[i];
|
||||
const x = s.x * ts, y = s.y * ts;
|
||||
if (i === 0) {
|
||||
this.drawSnakeHead(ctx, x, y, ts);
|
||||
} else if (i === this.snake.length - 1) {
|
||||
this.drawSnakeTail(ctx, x, y, ts, i);
|
||||
} else {
|
||||
this.drawSnakeBody(ctx, x, y, ts, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawApple(ctx, x, y, ts) {
|
||||
const radius = ts / 2 - 2;
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.3)';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x, y + ts/4, radius, radius/2, 0, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
|
||||
const grad = ctx.createRadialGradient(x - ts/6, y - ts/6, ts/10, x, y, ts/2);
|
||||
grad.addColorStop(0, '#ff4d4d');
|
||||
grad.addColorStop(1, '#8b0000');
|
||||
ctx.fillStyle = grad;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.4)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(x - ts/5, y - ts/5, ts/8, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#22c55e';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x + 2, y - ts/2.5, ts/4, ts/8, Math.PI/4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
drawSnakeHead(ctx, x, y, ts) {
|
||||
const next = this.snake[1];
|
||||
let rotation = 0;
|
||||
if (next) {
|
||||
if (next.x < this.snake[0].x) rotation = Math.PI / 2;
|
||||
else if (next.x > this.snake[0].x) rotation = -Math.PI / 2;
|
||||
else if (next.y < this.snake[0].y) rotation = Math.PI;
|
||||
else if (next.y > this.snake[0].y) rotation = 0;
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(x + ts/2, y + ts/2);
|
||||
ctx.rotate(rotation);
|
||||
|
||||
const grad = ctx.createRadialGradient(0, 0, ts/4, 0, 0, ts/2);
|
||||
grad.addColorStop(0, CONFIG.COLORS.SNAKE_HEAD);
|
||||
grad.addColorStop(1, CONFIG.COLORS.SNAKE_BODY);
|
||||
ctx.fillStyle = grad;
|
||||
|
||||
this.drawRoundedRect(ctx, -ts/2 + 1, -ts/2 + 1, ts - 2, ts + 2, 12);
|
||||
|
||||
// Apple tracking eyes
|
||||
const dx = this.apple.x - this.snake[0].x;
|
||||
const dy = this.apple.y - this.snake[0].y;
|
||||
const angle = Math.atan2(dy, dx) - rotation + Math.PI/2;
|
||||
const eyeOffset = ts * 0.05;
|
||||
const ex = Math.cos(angle) * eyeOffset;
|
||||
const ey = Math.sin(angle) * eyeOffset;
|
||||
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-ts*0.22, -ts*0.2, ts*0.14, 0, Math.PI*2);
|
||||
ctx.arc(ts*0.22, -ts*0.2, ts*0.14, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-ts*0.22 + ex, -ts*0.2 + ey, ts*0.07, 0, Math.PI*2);
|
||||
ctx.arc(ts*0.22 + ex, -ts*0.2 + ey, ts*0.07, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
|
||||
// Nostrils
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.3)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-ts*0.1, -ts*0.4, 2, 0, Math.PI*2);
|
||||
ctx.arc(ts*0.1, -ts*0.4, 2, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
|
||||
if (this.frameCount % 20 < 10) {
|
||||
ctx.strokeStyle = '#ef4444';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, -ts/2);
|
||||
ctx.lineTo(0, -ts/2 - 10);
|
||||
ctx.lineTo(-4, -ts/2 - 14);
|
||||
ctx.moveTo(0, -ts/2 - 10);
|
||||
ctx.lineTo(4, -ts/2 - 14);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
drawSnakeBody(ctx, x, y, ts, index) {
|
||||
const curr = this.snake[index];
|
||||
const next = this.snake[index - 1];
|
||||
const prev = this.snake[index + 1];
|
||||
|
||||
if (!next || !prev) return;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(x + ts/2, y + ts/2);
|
||||
|
||||
const grad = ctx.createLinearGradient(-ts/2, -ts/2, ts/2, ts/2);
|
||||
grad.addColorStop(0, CONFIG.COLORS.SNAKE_BODY);
|
||||
grad.addColorStop(1, CONFIG.COLORS.SNAKE_BODY_GRADIENT);
|
||||
ctx.fillStyle = grad;
|
||||
|
||||
const dirNext = { x: next.x - curr.x, y: next.y - curr.y };
|
||||
const dirPrev = { x: prev.x - curr.x, y: prev.y - curr.y };
|
||||
|
||||
const isHorizontal = next.y === prev.y;
|
||||
const isVertical = next.x === prev.x;
|
||||
|
||||
if (isHorizontal) {
|
||||
this.drawRoundedRect(ctx, -ts/2, -ts/2 + 2, ts, ts - 4, 4);
|
||||
} else if (isVertical) {
|
||||
this.drawRoundedRect(ctx, -ts/2 + 2, -ts/2, ts - 4, ts, 4);
|
||||
} else {
|
||||
// Corner - smooth arc connection
|
||||
ctx.beginPath();
|
||||
if ((dirNext.x === 1 && dirPrev.y === 1) || (dirNext.y === 1 && dirPrev.x === 1)) {
|
||||
// Bottom-right
|
||||
ctx.arc(ts/2, ts/2, ts - 2, Math.PI, Math.PI * 1.5);
|
||||
ctx.lineTo(ts/2, ts/2);
|
||||
} else if ((dirNext.x === -1 && dirPrev.y === 1) || (dirNext.y === 1 && dirPrev.x === -1)) {
|
||||
// Bottom-left
|
||||
ctx.arc(-ts/2, ts/2, ts - 2, Math.PI * 1.5, 0);
|
||||
ctx.lineTo(-ts/2, ts/2);
|
||||
} else if ((dirNext.x === 1 && dirPrev.y === -1) || (dirNext.y === -1 && dirPrev.x === 1)) {
|
||||
// Top-right
|
||||
ctx.arc(ts/2, -ts/2, ts - 2, Math.PI * 0.5, Math.PI);
|
||||
ctx.lineTo(ts/2, -ts/2);
|
||||
} else {
|
||||
// Top-left
|
||||
ctx.arc(-ts/2, -ts/2, ts - 2, 0, Math.PI * 0.5);
|
||||
ctx.lineTo(-ts/2, -ts/2);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Animated scales
|
||||
if ((index + Math.floor(this.frameCount/2)) % 6 === 0) {
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.12)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, ts/5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
drawSnakeTail(ctx, x, y, ts, index) {
|
||||
const curr = this.snake[index];
|
||||
const prev = this.snake[index - 1];
|
||||
if (!prev) return;
|
||||
|
||||
let rotation = 0;
|
||||
if (prev.x < curr.x) rotation = Math.PI / 2;
|
||||
else if (prev.x > curr.x) rotation = -Math.PI / 2;
|
||||
else if (prev.y < curr.y) rotation = Math.PI;
|
||||
else if (prev.y > curr.y) rotation = 0;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(x + ts/2, y + ts/2);
|
||||
ctx.rotate(rotation);
|
||||
|
||||
const grad = ctx.createLinearGradient(-ts/2, -ts/2, ts/2, ts/2);
|
||||
grad.addColorStop(0, CONFIG.COLORS.SNAKE_BODY);
|
||||
grad.addColorStop(1, CONFIG.COLORS.SNAKE_BODY_GRADIENT);
|
||||
ctx.fillStyle = grad;
|
||||
|
||||
// Tapered tail
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-ts/2 + 4, -ts/2);
|
||||
ctx.lineTo(ts/2 - 4, -ts/2);
|
||||
ctx.quadraticCurveTo(0, ts/2, 0, ts/2 + 6);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
drawTexturedRect(ctx, x, y, w, h, type) {
|
||||
if (type === 'wall') {
|
||||
const grad = ctx.createLinearGradient(x, y, x + w, y + h);
|
||||
grad.addColorStop(0, '#475569');
|
||||
grad.addColorStop(1, '#1e293b');
|
||||
ctx.fillStyle = grad;
|
||||
this.drawRoundedRect(ctx, x + 2, y + 2, w - 4, h - 4, 6);
|
||||
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
|
||||
ctx.strokeRect(x + 6, y + 6, w - 12, h - 12);
|
||||
}
|
||||
}
|
||||
|
||||
drawRoundedRect(ctx, x, y, w, h, r) {
|
||||
ctx.beginPath();
|
||||
if (ctx.roundRect) {
|
||||
ctx.roundRect(x, y, w, h, r);
|
||||
} else {
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.lineTo(x + w - r, y);
|
||||
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
||||
ctx.lineTo(x + w, y + h - r);
|
||||
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
||||
ctx.lineTo(x + r, y + h);
|
||||
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
||||
ctx.lineTo(x, y + r);
|
||||
ctx.quadraticCurveTo(x, y, x + r, y);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
gameLoop(timestamp) {
|
||||
const speed = parseInt(this.speedRange.value);
|
||||
const interval = 1000 / speed;
|
||||
|
||||
if (timestamp - this.lastFrameTime >= interval) {
|
||||
this.update();
|
||||
this.draw();
|
||||
this.lastFrameTime = timestamp;
|
||||
}
|
||||
|
||||
requestAnimationFrame((t) => this.gameLoop(t));
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => new Game());
|
||||
BIN
assets/pasted-20260303-004347-83d8b12e.jpg
Normal file
BIN
assets/pasted-20260303-004347-83d8b12e.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 371 KiB |
BIN
assets/pasted-20260303-004846-1f6b05b6.jpg
Normal file
BIN
assets/pasted-20260303-004846-1f6b05b6.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 347 KiB |
BIN
assets/pasted-20260303-005216-02bf6ec0.jpg
Normal file
BIN
assets/pasted-20260303-005216-02bf6ec0.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 349 KiB |
BIN
assets/pasted-20260303-005641-21839e8c.jpg
Normal file
BIN
assets/pasted-20260303-005641-21839e8c.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 417 KiB |
BIN
assets/pasted-20260303-011135-6f2f6c05.jpg
Normal file
BIN
assets/pasted-20260303-011135-6f2f6c05.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 764 KiB |
231
index.php
231
index.php
@ -6,145 +6,118 @@ declare(strict_types=1);
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
// SEO Meta Tags
|
||||
$title = "Auto-Snake: The Infinite Loop";
|
||||
$description = "Watch an AI-powered snake navigate mazes and eat apples in an infinite, perfect loop. A mesmerizing demonstration of pathfinding.";
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
<title><?= htmlspecialchars($title) ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($description) ?>" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="<?= htmlspecialchars($title) ?>" />
|
||||
<meta property="og:description" content="<?= htmlspecialchars($description) ?>" />
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:title" content="<?= htmlspecialchars($title) ?>" />
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($description) ?>" />
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
<div class="background-mesh"></div>
|
||||
|
||||
<header class="main-header">
|
||||
<div class="container">
|
||||
<h1>Auto-Snake <span class="badge">AI Bot</span></h1>
|
||||
<p class="subtitle">Smart, smooth, and never-ending.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container main-layout">
|
||||
<div class="game-column">
|
||||
<div class="canvas-wrapper">
|
||||
<canvas id="gameCanvas"></canvas>
|
||||
<div id="gameOverlay" class="overlay hidden">
|
||||
<div class="overlay-content">
|
||||
<h2 id="overlayTitle">VICTORY!</h2>
|
||||
<p id="overlayMessage">The grid is full. Restarting...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="sidebar">
|
||||
<div class="stats-card card">
|
||||
<h3>Live Stats</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<span class="label">Wins</span>
|
||||
<span id="winCount" class="value">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="label">Time Since Win</span>
|
||||
<span id="timer" class="value">00:00</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="label">Score</span>
|
||||
<span id="currentScore" class="value">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="label">Best Score</span>
|
||||
<span id="bestScore" class="value">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls-card card">
|
||||
<h3>Simulation Controls</h3>
|
||||
<div class="control-group">
|
||||
<label for="speedRange">Simulation Speed</label>
|
||||
<input type="range" id="speedRange" min="1" max="60" value="45">
|
||||
<div class="range-labels">
|
||||
<span>Slow</span>
|
||||
<span>Fast</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="resetBtn" class="btn btn-secondary">Reset Data</button>
|
||||
<button id="toggleMazeBtn" class="btn btn-primary">Toggle Maze</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card card">
|
||||
<h3>How it works</h3>
|
||||
<p>This AI uses <strong>Breadth-First Search (BFS)</strong> for pathfinding. To avoid loops, it employs a <strong>Longest Path approximation</strong> when following its tail, ensuring it consumes the most space possible to wait for a safe opening to the apple.</p>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
|
||||
<footer class="main-footer">
|
||||
<div class="container">
|
||||
<p>© <?= date('Y') ?> Auto-Snake AI. Built with PHP <?= $phpVersion ?>.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user