Change UI
This commit is contained in:
@@ -13,7 +13,7 @@ export default function BestSnakeDisplay({ network, gridSize, fitness }: BestSna
|
||||
return (
|
||||
<div className="best-snake-panel">
|
||||
<div className="best-header">
|
||||
<h3>👑 All-Time Best</h3>
|
||||
<h3>All-Time Best</h3>
|
||||
<div className="best-stats">
|
||||
<span className="label">Fitness Record:</span>
|
||||
<span className="value">{Math.round(fitness)}</span>
|
||||
|
||||
@@ -29,10 +29,10 @@ export default function Controls({
|
||||
className={`btn ${isRunning ? 'btn-pause' : 'btn-play'}`}
|
||||
onClick={onToggleRunning}
|
||||
>
|
||||
{isRunning ? '⏸ Pause' : '▶ Play'}
|
||||
{isRunning ? 'Pause' : 'Play'}
|
||||
</button>
|
||||
<button className="btn btn-reset" onClick={onReset}>
|
||||
🔄 Reset
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@ export default function Controls({
|
||||
id="speed-slider"
|
||||
type="range"
|
||||
min="1"
|
||||
max="20"
|
||||
max="100"
|
||||
step="1"
|
||||
value={speed}
|
||||
onChange={(e) => onSpeedChange(Number(e.target.value))}
|
||||
|
||||
@@ -1,11 +1,502 @@
|
||||
.snake-ai-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 2rem;
|
||||
gap: 1.5rem;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
font-family: 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
background-color: #050505;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Canvas */
|
||||
.snake-canvas-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Snake Grid */
|
||||
.snake-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
/* Tighter gap */
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.snake-grid-cell {
|
||||
position: relative;
|
||||
background: #000;
|
||||
border: 1px solid #333;
|
||||
padding: 4px;
|
||||
transition: all 0.2s steps(2);
|
||||
}
|
||||
|
||||
.snake-grid-cell:hover {
|
||||
border-color: #0f0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 0 1px #0f0;
|
||||
}
|
||||
|
||||
.snake-grid-cell.best {
|
||||
border-color: #0f0;
|
||||
background: #001100;
|
||||
box-shadow: 0 0 0 1px #0f0;
|
||||
}
|
||||
|
||||
.snake-grid-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.snake-grid-cell.best .snake-grid-label {
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.fitness-badge {
|
||||
background: #111;
|
||||
padding: 2px 6px;
|
||||
border: 1px solid #333;
|
||||
font-size: 0.7rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.snake-grid-cell.best .fitness-badge {
|
||||
background: #002200;
|
||||
border-color: #0f0;
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.snake-grid-cell canvas {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
/* Remove inline-block gap */
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
|
||||
.canvas-info {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: #000;
|
||||
border: 1px solid #333;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.info-item .value {
|
||||
color: #0f0;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.controls-panel {
|
||||
background: #000;
|
||||
padding: 1rem;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.controls-panel h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.control-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #333;
|
||||
background: #111;
|
||||
color: #888;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.1s;
|
||||
font-family: inherit;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #222;
|
||||
color: #fff;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
.btn-play {
|
||||
border-color: #060;
|
||||
color: #0a0;
|
||||
}
|
||||
|
||||
.btn-play:hover {
|
||||
background: #002200;
|
||||
color: #0f0;
|
||||
border-color: #0f0;
|
||||
}
|
||||
|
||||
.btn-pause {
|
||||
border-color: #640;
|
||||
color: #a80;
|
||||
}
|
||||
|
||||
.btn-pause:hover {
|
||||
background: #221100;
|
||||
color: #fc0;
|
||||
border-color: #fc0;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
border-color: #600;
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
.btn-reset:hover {
|
||||
background: #220000;
|
||||
color: #f00;
|
||||
border-color: #f00;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.control-group label strong {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
border-radius: 0;
|
||||
background: #222;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 0;
|
||||
background: #0f0;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.slider-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.7rem;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.control-info {
|
||||
background: #080808;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #222;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #0f0;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.stats-panel {
|
||||
background: #000;
|
||||
padding: 1rem;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.stats-panel h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: #080808;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #222;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-item.highlight {
|
||||
border-color: #0f0;
|
||||
background: #001100;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 0.75rem;
|
||||
margin-bottom: 0.25rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: #0f0;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.stat-item.highlight .stat-value {
|
||||
color: #0f0;
|
||||
text-shadow: 0 0 5px rgba(0, 255, 0, 0.4);
|
||||
}
|
||||
|
||||
.progress-indicator {
|
||||
background: #080808;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #222;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: #222;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: #0f0;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
/* Tips */
|
||||
.tips-panel {
|
||||
background: #000;
|
||||
padding: 1rem;
|
||||
border: 1px solid #333;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tips-panel h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tip-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tip-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tip-section h4 {
|
||||
color: #fff;
|
||||
font-size: 0.9rem;
|
||||
margin: 0 0 0.5rem 0;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #333;
|
||||
}
|
||||
|
||||
.tip-section p {
|
||||
color: #888;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.tip-section ul,
|
||||
.tip-section ol {
|
||||
color: #888;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.tip-section li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.tip-section strong {
|
||||
color: #0f0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Scrollbar styling for tips panel */
|
||||
.tips-panel::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.tips-panel::-webkit-scrollbar-track {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.tips-panel::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.tips-panel::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1200px) {
|
||||
.snake-ai-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Best Snake Display */
|
||||
.best-snake-panel {
|
||||
background: #000;
|
||||
padding: 1rem;
|
||||
border: 1px solid #0f0;
|
||||
box-shadow: 0 0 0 1px #0f0 inset, 0 0 10px rgba(0, 255, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.best-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.best-header h3 {
|
||||
margin: 0;
|
||||
color: #0f0;
|
||||
font-size: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.best-stats {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: baseline;
|
||||
background: #001100;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #060;
|
||||
}
|
||||
|
||||
.best-stats .label {
|
||||
color: #686;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.best-stats .value {
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.best-canvas-wrapper {
|
||||
padding: 4px;
|
||||
background: #000;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
display: flex;
|
||||
|
||||
@@ -75,23 +75,36 @@ export default function SnakeAI() {
|
||||
const animate = (timestamp: number) => {
|
||||
const elapsed = timestamp - lastUpdateRef.current;
|
||||
|
||||
// Non-linear speed scaling:
|
||||
// Speed 1: ~5000ms (5s) per generation - Observation mode
|
||||
// Speed 5: ~1000ms (1s)
|
||||
// Speed 20: ~50ms - Turbo training
|
||||
// Formula: Base delay divided by speed, with a visual observation bias
|
||||
// Speed handling logic:
|
||||
// 1-20: Visual speeds (delay between generations)
|
||||
// 21-100: Turbo speeds (multiple generations per frame)
|
||||
|
||||
let updateInterval;
|
||||
if (speed <= 5) {
|
||||
// Speeds 1-5: 10s down to 2s
|
||||
updateInterval = 12000 / speed;
|
||||
if (speed <= 20) {
|
||||
// Standard visual mode
|
||||
let updateInterval;
|
||||
if (speed <= 5) {
|
||||
// Speeds 1-5: Very slow observation
|
||||
updateInterval = 12000 / speed;
|
||||
} else {
|
||||
// Speeds 6-20: 62.5ms to 1000ms
|
||||
// speed 20 -> 1000/16 = 62.5ms
|
||||
// speed 6 -> 1000/2 = 500ms
|
||||
updateInterval = 1000 / (speed - 4);
|
||||
}
|
||||
|
||||
if (elapsed >= updateInterval) {
|
||||
runGeneration();
|
||||
lastUpdateRef.current = timestamp;
|
||||
}
|
||||
} else {
|
||||
// Speeds 6-20: Linear fast
|
||||
updateInterval = 1000 / (speed - 4);
|
||||
}
|
||||
// Turbo mode: Run multiple generations per frame
|
||||
// Speed 21 -> 1 gen per frame (~60 eps)
|
||||
// Speed 100 -> 10 gens per frame (~600 eps)
|
||||
const gensPerFrame = Math.floor((speed - 10) / 10);
|
||||
|
||||
if (elapsed >= updateInterval) {
|
||||
runGeneration();
|
||||
for (let i = 0; i < gensPerFrame; i++) {
|
||||
runGeneration();
|
||||
}
|
||||
lastUpdateRef.current = timestamp;
|
||||
}
|
||||
|
||||
@@ -118,7 +131,7 @@ export default function SnakeAI() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AppContainer title="Neural Network Snake Evolution">
|
||||
<AppContainer title="Snake Evolution">
|
||||
<div className="snake-ai-layout">
|
||||
<div className="left-panel">
|
||||
<BestSnakeDisplay
|
||||
|
||||
@@ -96,12 +96,12 @@ export default function SnakeCanvas({ network, gridSize, showGrid = true, size =
|
||||
const canvasSize = gridSize * CELL_SIZE + CANVAS_PADDING * 2;
|
||||
|
||||
// Clear canvas
|
||||
ctx.fillStyle = '#1a1a2e';
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(0, 0, canvasSize, canvasSize);
|
||||
|
||||
// Draw grid
|
||||
if (showGrid) {
|
||||
ctx.strokeStyle = '#2a2a3e';
|
||||
ctx.strokeStyle = '#1a1a1a';
|
||||
ctx.lineWidth = 1;
|
||||
for (let i = 0; i <= currentGame.gridSize; i++) {
|
||||
const pos = i * CELL_SIZE + CANVAS_PADDING;
|
||||
@@ -155,7 +155,7 @@ export default function SnakeCanvas({ network, gridSize, showGrid = true, size =
|
||||
ctx.fillRect(0, 0, canvasSize, canvasSize);
|
||||
|
||||
ctx.fillStyle = '#e74c3c';
|
||||
ctx.font = `bold ${size === 'small' ? '16' : '24'}px Inter, sans-serif`;
|
||||
ctx.font = `bold ${size === 'small' ? '16' : '24'}px 'Courier New', monospace`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('DEAD', canvasSize / 2, canvasSize / 2);
|
||||
@@ -174,9 +174,8 @@ export default function SnakeCanvas({ network, gridSize, showGrid = true, size =
|
||||
style={{
|
||||
width: `${canvasSize}px`,
|
||||
height: `${canvasSize}px`,
|
||||
border: '2px solid #3a3a4e',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: '#1a1a2e',
|
||||
border: '1px solid #333',
|
||||
backgroundColor: '#000000',
|
||||
}}
|
||||
/>
|
||||
{currentGame && (size !== 'small' || showStats) && (
|
||||
|
||||
@@ -4,7 +4,7 @@ export default function Tips() {
|
||||
<h3>How It Works</h3>
|
||||
|
||||
<div className="tip-section">
|
||||
<h4>🧠 Neural Network</h4>
|
||||
<h4>Neural Network</h4>
|
||||
<p>
|
||||
Each snake is controlled by a neural network with <strong>8 inputs</strong>:
|
||||
</p>
|
||||
@@ -20,7 +20,7 @@ export default function Tips() {
|
||||
</div>
|
||||
|
||||
<div className="tip-section">
|
||||
<h4>🧬 Evolution Process</h4>
|
||||
<h4>Evolution Process</h4>
|
||||
<ol>
|
||||
<li><strong>Play:</strong> All snakes play until they die</li>
|
||||
<li><strong>Evaluate:</strong> Calculate fitness score</li>
|
||||
@@ -31,7 +31,7 @@ export default function Tips() {
|
||||
</div>
|
||||
|
||||
<div className="tip-section">
|
||||
<h4>📊 Fitness Function</h4>
|
||||
<h4>Fitness Function</h4>
|
||||
<p>Snakes are scored based on:</p>
|
||||
<ul>
|
||||
<li><strong>Food collected</strong> × 100 (primary goal)</li>
|
||||
@@ -41,7 +41,7 @@ export default function Tips() {
|
||||
</div>
|
||||
|
||||
<div className="tip-section">
|
||||
<h4>💡 Tips for Best Results</h4>
|
||||
<h4>Optimization Tips</h4>
|
||||
<ul>
|
||||
<li>Higher mutation rate = more exploration</li>
|
||||
<li>Lower mutation rate = refine existing strategies</li>
|
||||
|
||||
Reference in New Issue
Block a user