Enhance PlantSimulator with yield and yield rate tracking
- Added yield and yield rate properties to the PlantState interface. - Implemented computeYieldRate function to calculate yield based on environmental factors and plant growth. - Updated GrowthChart component to visualize yield and yield rate alongside height and leaf count. - Modified PlantSimulator state management to include yield and yield rate history. - Enhanced UI to display current yield and yield rate in the simulator.
This commit is contained in:
@@ -31,6 +31,8 @@ interface PlantState {
|
||||
height: number // current stem height px (0 → MAX_HEIGHT)
|
||||
leaves: Leaf[]
|
||||
tick: number
|
||||
yield: number // accumulated yield in grams (0 → MAX_YIELD)
|
||||
yieldRate: number // current yield production rate g/s
|
||||
}
|
||||
|
||||
interface EnvironmentSettings {
|
||||
@@ -41,6 +43,7 @@ interface EnvironmentSettings {
|
||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
const MAX_HEIGHT = 260
|
||||
const MAX_YIELD = 500 // max yield in grams
|
||||
const SVG_WIDTH = 200
|
||||
const SVG_HEIGHT = 320
|
||||
const STEM_X = SVG_WIDTH / 2
|
||||
@@ -56,6 +59,16 @@ function growthRate(env: EnvironmentSettings, speed: number): number {
|
||||
return speed * lightFactor * waterFactor
|
||||
}
|
||||
|
||||
// Yield rate in g/s: leaves and height progress amplify yield production
|
||||
function computeYieldRate(env: EnvironmentSettings, leafCount: number, heightProgress: number): number {
|
||||
const lightFactor = 0.2 + (env.light / 100) * 0.8
|
||||
const waterFactor = 0.2 + (env.water / 100) * 0.8
|
||||
const leafFactor = leafCount / MAX_LEAVES
|
||||
// yield only starts after 20% growth and accelerates with more leaves
|
||||
const maturityFactor = Math.max(0, (heightProgress - 0.2) / 0.8)
|
||||
return parseFloat((MAX_YIELD * 0.012 * lightFactor * waterFactor * leafFactor * maturityFactor).toFixed(3))
|
||||
}
|
||||
|
||||
// ─── Plant SVG Component ──────────────────────────────────────────────────────
|
||||
|
||||
function PlantSVG({ plant, tick }: { plant: PlantState; tick: number }) {
|
||||
@@ -125,10 +138,14 @@ function PlantSVG({ plant, tick }: { plant: PlantState; tick: number }) {
|
||||
|
||||
function GrowthChart({
|
||||
heightHistory,
|
||||
leafHistory
|
||||
leafHistory,
|
||||
yieldHistory,
|
||||
yieldRateHistory
|
||||
}: {
|
||||
heightHistory: number[]
|
||||
leafHistory: number[]
|
||||
yieldHistory: number[]
|
||||
yieldRateHistory: number[]
|
||||
}) {
|
||||
const labels = heightHistory.map((_, i) => `${i}s`)
|
||||
|
||||
@@ -139,7 +156,7 @@ function GrowthChart({
|
||||
label: 'ارتفاع (px)',
|
||||
data: heightHistory,
|
||||
borderColor: '#4a7c59',
|
||||
backgroundColor: 'rgba(74,124,89,0.15)',
|
||||
backgroundColor: 'rgba(74,124,89,0.10)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
@@ -149,11 +166,32 @@ function GrowthChart({
|
||||
label: 'تعداد برگ',
|
||||
data: leafHistory,
|
||||
borderColor: '#f9c74f',
|
||||
backgroundColor: 'rgba(249,199,79,0.15)',
|
||||
backgroundColor: 'rgba(249,199,79,0.10)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
yAxisID: 'yLeaf'
|
||||
},
|
||||
{
|
||||
label: 'محصول (g)',
|
||||
data: yieldHistory,
|
||||
borderColor: '#f97316',
|
||||
backgroundColor: 'rgba(249,115,22,0.10)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
yAxisID: 'yYield'
|
||||
},
|
||||
{
|
||||
label: 'سرعت محصول (g/s)',
|
||||
data: yieldRateHistory,
|
||||
borderColor: '#a78bfa',
|
||||
backgroundColor: 'rgba(167,139,250,0.10)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
borderDash: [4, 3],
|
||||
yAxisID: 'yYieldRate'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -162,7 +200,7 @@ function GrowthChart({
|
||||
responsive: true,
|
||||
animation: { duration: 0 },
|
||||
plugins: {
|
||||
legend: { labels: { color: '#e2e8f0', font: { size: 12 } } },
|
||||
legend: { labels: { color: '#e2e8f0', font: { size: 11 } } },
|
||||
title: {
|
||||
display: true,
|
||||
text: 'نمودار رشد گیاه',
|
||||
@@ -192,6 +230,21 @@ function GrowthChart({
|
||||
ticks: { color: '#f9c74f' },
|
||||
grid: { display: false },
|
||||
title: { display: true, text: 'برگ', color: '#f9c74f' }
|
||||
},
|
||||
yYield: {
|
||||
type: 'linear' as const,
|
||||
position: 'left' as const,
|
||||
min: 0,
|
||||
max: MAX_YIELD,
|
||||
display: false,
|
||||
grid: { display: false }
|
||||
},
|
||||
yYieldRate: {
|
||||
type: 'linear' as const,
|
||||
position: 'right' as const,
|
||||
min: 0,
|
||||
display: false,
|
||||
grid: { display: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,18 +259,22 @@ export default function PlantSimulator() {
|
||||
const [speed, setSpeed] = useState(1.5) // px per tick base
|
||||
const [env, setEnv] = useState<EnvironmentSettings>({ light: 75, water: 65 })
|
||||
|
||||
const [plant, setPlant] = useState<PlantState>({ height: 0, leaves: [], tick: 0 })
|
||||
const [plant, setPlant] = useState<PlantState>({ height: 0, leaves: [], tick: 0, yield: 0, yieldRate: 0 })
|
||||
const [heightHistory, setHeightHistory] = useState<number[]>([0])
|
||||
const [leafHistory, setLeafHistory] = useState<number[]>([0])
|
||||
const [yieldHistory, setYieldHistory] = useState<number[]>([0])
|
||||
const [yieldRateHistory, setYieldRateHistory] = useState<number[]>([0])
|
||||
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const tickRef = useRef(0)
|
||||
const historyIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setPlant({ height: 0, leaves: [], tick: 0 })
|
||||
setPlant({ height: 0, leaves: [], tick: 0, yield: 0, yieldRate: 0 })
|
||||
setHeightHistory([0])
|
||||
setLeafHistory([0])
|
||||
setYieldHistory([0])
|
||||
setYieldRateHistory([0])
|
||||
tickRef.current = 0
|
||||
}, [])
|
||||
|
||||
@@ -258,7 +315,12 @@ export default function PlantSimulator() {
|
||||
scale: Math.min(l.scale + 0.025, 1)
|
||||
}))
|
||||
|
||||
return { height: newHeight, leaves: grownLeaves, tick: t }
|
||||
const heightProgress = newHeight / MAX_HEIGHT
|
||||
const currentYieldRate = computeYieldRate(env, grownLeaves.length, heightProgress)
|
||||
// accumulate yield each tick (33ms ≈ 0.033s)
|
||||
const newYield = Math.min(prev.yield + currentYieldRate * 0.033, MAX_YIELD)
|
||||
|
||||
return { height: newHeight, leaves: grownLeaves, tick: t, yield: newYield, yieldRate: currentYieldRate }
|
||||
})
|
||||
}, 33)
|
||||
|
||||
@@ -278,6 +340,8 @@ export default function PlantSimulator() {
|
||||
setPlant(prev => {
|
||||
setHeightHistory(h => [...h.slice(-59), Math.round(prev.height)])
|
||||
setLeafHistory(l => [...l.slice(-59), prev.leaves.length])
|
||||
setYieldHistory(y => [...y.slice(-59), parseFloat(prev.yield.toFixed(1))])
|
||||
setYieldRateHistory(r => [...r.slice(-59), prev.yieldRate])
|
||||
return prev
|
||||
})
|
||||
}, 1000)
|
||||
@@ -312,6 +376,14 @@ export default function PlantSimulator() {
|
||||
<div className='text-yellow-400 font-semibold text-lg'>{plant.leaves.length}</div>
|
||||
<div className='text-slate-400'>تعداد برگ</div>
|
||||
</div>
|
||||
<div className='bg-orange-900/40 border border-orange-700/40 rounded-xl p-3 text-center'>
|
||||
<div className='text-orange-400 font-semibold text-lg'>{plant.yield.toFixed(1)}</div>
|
||||
<div className='text-slate-400'>محصول (g)</div>
|
||||
</div>
|
||||
<div className='bg-violet-900/40 border border-violet-700/40 rounded-xl p-3 text-center'>
|
||||
<div className='text-violet-400 font-semibold text-lg'>{plant.yieldRate.toFixed(3)}</div>
|
||||
<div className='text-slate-400'>سرعت محصول (g/s)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isFinished && (
|
||||
@@ -400,10 +472,15 @@ export default function PlantSimulator() {
|
||||
|
||||
{/* ── Right: Chart ── */}
|
||||
<div className='lg:col-span-2 bg-slate-800 border border-slate-700 rounded-2xl p-6 shadow-xl'>
|
||||
<GrowthChart heightHistory={heightHistory} leafHistory={leafHistory} />
|
||||
<GrowthChart
|
||||
heightHistory={heightHistory}
|
||||
leafHistory={leafHistory}
|
||||
yieldHistory={yieldHistory}
|
||||
yieldRateHistory={yieldRateHistory}
|
||||
/>
|
||||
|
||||
{/* Info cards */}
|
||||
<div className='mt-6 grid grid-cols-3 gap-4 text-sm'>
|
||||
<div className='mt-6 grid grid-cols-2 lg:grid-cols-4 gap-4 text-sm'>
|
||||
<div className='bg-slate-700/60 rounded-xl p-4 border border-slate-600'>
|
||||
<div className='text-slate-400 mb-1'>پیشرفت رشد</div>
|
||||
<div className='w-full bg-slate-600 rounded-full h-2 mb-1'>
|
||||
@@ -438,6 +515,20 @@ export default function PlantSimulator() {
|
||||
</div>
|
||||
<div className='text-blue-400 font-semibold'>{env.water}%</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-slate-700/60 rounded-xl p-4 border border-slate-600'>
|
||||
<div className='text-slate-400 mb-1'>محصول دهی</div>
|
||||
<div className='w-full bg-slate-600 rounded-full h-2 mb-1'>
|
||||
<div
|
||||
className='bg-orange-400 h-2 rounded-full transition-all'
|
||||
style={{ width: `${(plant.yield / MAX_YIELD) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-between items-center'>
|
||||
<span className='text-orange-400 font-semibold'>{plant.yield.toFixed(1)}g</span>
|
||||
<span className='text-violet-400 text-xs'>{plant.yieldRate.toFixed(3)} g/s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
@@ -445,8 +536,9 @@ export default function PlantSimulator() {
|
||||
<p>
|
||||
این شبیهساز رشد گیاه را بر اساس سرعت پایه، میزان نور خورشید و آب دریافتی
|
||||
محاسبه میکند. هر برگ به صورت تدریجی روی ساقه ظاهر شده و با حرکت طبیعی
|
||||
در باد نمایش داده میشود. نمودار تغییرات ارتفاع و تعداد برگها را در طول
|
||||
زمان ثبت میکند.
|
||||
در باد نمایش داده میشود. <strong className='text-slate-300'>محصولدهی (g)</strong> پس از ۲۰٪ رشد شروع شده
|
||||
و با تعداد برگ، نور و آب شتاب میگیرد. <strong className='text-slate-300'>سرعت محصول (g/s)</strong> نشاندهنده
|
||||
نرخ لحظهای تولید است. نمودار تغییرات همه شاخصها را در طول زمان ثبت میکند.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user