<style>
/* --------------------------------------------------------------------------------------------------------------------------------
بخش CSS: استایلدهی شبکه و کنترلها
-------------------------------------------------------------------------------------------------------------------------------- */
#schelling-container {
font-family: Tahoma, sans-serif;
text-align: center;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
max-width: 500px; /* برای محدود کردن عرض در صفحه */
direction: rtl; /* برای نمایش صحیح فارسی */
}
h2 {
color: #333;
font-size: 24px;
margin-bottom: 20px;
}
#schelling-grid {
display: grid;
/* اندازه شبکه 10x10 */
grid-template-columns: repeat(10, 30px);
grid-template-rows: repeat(10, 30px);
border: 2px solid #555;
width: 300px;
height: 300px;
margin: 20px auto; /* وسطچین کردن شبکه */
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
}
.grid-cell {
width: 30px;
height: 30px;
box-sizing: border-box;
border: 1px solid #ccc;
text-align: center;
line-height: 30px;
font-weight: bold;
font-size: 16px;
}
/* رنگبندی عوامل (همانند نمودارهای matplotlib) */
.agent-x {
background-color: #1f77b4; /* آبی */
color: white;
}
.agent-o {
background-color: #d62728; /* قرمز */
color: white;
}
.agent-e {
background-color: #f0f0f0; /* خاکستری روشن (خالی) */
}
#controls {
margin-bottom: 20px;
}
#controls button {
padding: 10px 15px;
font-size: 16px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
transition: background-color 0.3s;
}
#controls button:hover {
background-color: #45a049;
}
#controls label {
font-size: 16px;
margin: 0 10px;
}
#step-counter {
margin-top: 10px;
padding: 5px;
font-size: 18px;
font-weight: bold;
color: #333;
min-height: 25px;
}
</style>
<div id="schelling-container">
<h2>شبیهساز تعاملی مدل شلینگ</h2>
<div id="controls">
<label for="threshold-slider">آستانه رضایت (t): </label>
<input type="range" id="threshold-slider" min="1" max="8" value="3" oninput="updateThresholdLabel(this.value)">
<span id="threshold-label">3</span>
<button id="run-button" onclick="startSimulation()">اجرا / شروع مجدد</button>
</div>
<div id="step-counter">مرحله: 0 - شبکه آماده</div>
<div id="schelling-grid"></div>
</div>
<script>
// --------------------------------------------------------------------------------------------------------------------------------
// بخش JavaScript: پیادهسازی منطق مدل شلینگ
// --------------------------------------------------------------------------------------------------------------------------------
const GRID_SIZE = 10;
const NUM_X = 25;
const NUM_O = 25;
let grid = [];
let threshold = 3;
let simulationRunning = false;
let stepCount = 0;
const gridContainer = document.getElementById('schelling-grid');
const stepCounterEl = document.getElementById('step-counter');
const runButton = document.getElementById('run-button');
let animationFrameId = null; // برای مدیریت حلقه requestAnimationFrame
// --- توابع اصلی شبیهسازی ---
function initializeGrid() {
// ۱. ایجاد لیست عوامل (25X, 25O, 50E)
let agents = [];
for (let i = 0; i < NUM_X; i++) agents.push('X');
for (let i = 0; i < NUM_O; i++) agents.push('O');
for (let i = 0; i < (GRID_SIZE * GRID_SIZE - NUM_X - NUM_O); i++) agents.push('E');
// ۲. بر زدن تصادفی
for (let i = agents.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[agents[i], agents[j]] = [agents[j], agents[i]];
}
// ۳. تبدیل به شبکه 2D
grid = [];
for (let r = 0; r < GRID_SIZE; r++) {
grid.push(agents.slice(r * GRID_SIZE, (r + 1) * GRID_SIZE));
}
}
function isSatisfied(r, c) {
const agentType = grid[r][c];
if (agentType === 'E') return true;
let similarNeighbors = 0;
// بررسی ۸ همسایه (Moore neighborhood)
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
const nr = r + dr;
const nc = c + dc;
// بررسی مرزها
if (nr >= 0 && nr < GRID_SIZE && nc >= 0 && nc < GRID_SIZE) {
if (grid[nr][nc] === agentType) {
similarNeighbors++;
}
}
}
}
// شرط رضایت: تعداد همنوع >= آستانه
return similarNeighbors >= threshold;
}
function runSimulationStep() {
// توقف اجرای قبلی در صورت وجود
if (!simulationRunning) return;
if (animationFrameId) cancelAnimationFrame(animationFrameId);
stepCount++;
// ۱. پیدا کردن تمام عوامل ناراضی
let unsatisfiedAgents = [];
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] !== 'E' && !isSatisfied(r, c)) {
unsatisfiedAgents.push({r, c});
}
}
}
// ۲. بررسی وضعیت پایداری
if (unsatisfiedAgents.length === 0) {
stepCounterEl.style.color = 'green';
stepCounterEl.textContent = `✅ پایداری در مرحله ${stepCount - 1} حاصل شد.`;
simulationRunning = false;
runButton.textContent = "شروع مجدد";
return;
}
// ۳. جابجایی تصادفی یک عامل ناراضی
const agentToMove = unsatisfiedAgents[Math.floor(Math.random() * unsatisfiedAgents.length)];
const agentType = grid[agentToMove.r][agentToMove.c];
let emptySpots = [];
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (grid[r][c] === 'E') {
emptySpots.push({r, c});
}
}
}
if (emptySpots.length === 0) {
stepCounterEl.textContent = `❌ خطا: خانهی خالی وجود ندارد.`;
simulationRunning = false;
return;
}
const targetSpot = emptySpots[Math.floor(Math.random() * emptySpots.length)];
// جابجایی
grid[targetSpot.r][targetSpot.c] = agentType;
grid[agentToMove.r][agentToMove.c] = 'E';
// ۴. بهروزرسانی نمایش
stepCounterEl.style.color = '#333';
stepCounterEl.textContent = `مرحله: ${stepCount} - (${unsatisfiedAgents.length} ناراضی باقیمانده)`;
renderGrid();
// ۵. اجرای مرحله بعدی (استفاده از setTimeout برای کنترل سرعت)
animationFrameId = setTimeout(() => requestAnimationFrame(runSimulationStep), 100); // 100ms تأخیر
}
// --- توابع کمکی و رابط کاربری ---
function renderGrid() {
gridContainer.innerHTML = '';
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
const cell = document.createElement('div');
cell.classList.add('grid-cell');
const agentType = grid[r][c];
// افزودن کلاسهای رنگی
if (agentType === 'X') {
cell.classList.add('agent-x');
cell.textContent = 'X';
} else if (agentType === 'O') {
cell.classList.add('agent-o');
cell.textContent = 'O';
} else {
cell.classList.add('agent-e');
}
gridContainer.appendChild(cell);
}
}
}
function updateThresholdLabel(value) {
threshold = parseInt(value, 10);
document.getElementById('threshold-label').textContent = value;
// اگر در حال اجرا نبود، آستانه جدید را نمایش دهد.
if (!simulationRunning) {
stepCounterEl.textContent = `مرحله: 0 - آستانه جدید: ${threshold}`;
}
}
function startSimulation() {
// توقف حلقه قبلی در صورت فعال بودن
if (animationFrameId) cancelAnimationFrame(animationFrameId);
threshold = parseInt(document.getElementById('threshold-slider').value, 10);
stepCount = 0;
// شروع دوباره
initializeGrid();
renderGrid();
simulationRunning = true;
runButton.textContent = "در حال اجرا... (توقف خودکار)";
stepCounterEl.style.color = '#333';
stepCounterEl.textContent = "مرحله: 0 - در حال اجرا...";
// شروع اولین گام
animationFrameId = setTimeout(() => requestAnimationFrame(runSimulationStep), 100);
}
// اجرای اولیه برای نمایش شبکه تصادفی در بارگذاری اولیه صفحه
initializeGrid();
renderGrid();
</script>