refactor(rithmomachia): extract guide sections into separate files

- Extract OverviewSection, PiecesSection, CaptureSection, HarmonySection, and VictorySection into separate files in guide-sections/ directory
- Reduce PlayingGuideModal.tsx from 1628 lines to 519 lines
- Remove unused imports from all refactored files
- Each section is now independently maintainable and testable

This refactoring makes it easier to enhance individual sections (e.g., adding comprehensive content to Harmony section).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-10-31 17:19:31 -05:00
parent b7fac78829
commit 765525dc45
6 changed files with 1132 additions and 1114 deletions

View File

@@ -0,0 +1,306 @@
import { useTranslation } from 'react-i18next'
import { css } from '../../../../../styled-system/css'
import { RithmomachiaBoard, type ExamplePiece } from '../RithmomachiaBoard'
export function CaptureSection({ useNativeAbacusNumbers }: { useNativeAbacusNumbers: boolean }) {
const { t } = useTranslation()
// Example board positions for captures
const equalityExample: ExamplePiece[] = [
{ square: 'G4', type: 'C', color: 'W', value: 25 }, // White's 25
{ square: 'H4', type: 'C', color: 'B', value: 25 }, // Black's 25 (can be captured)
]
const multipleExample: ExamplePiece[] = [
{ square: 'E5', type: 'S', color: 'W', value: 64 }, // White's 64
{ square: 'F5', type: 'T', color: 'B', value: 16 }, // Black's 16 (can be captured: 64÷16=4)
]
const sumExample: ExamplePiece[] = [
{ square: 'F4', type: 'C', color: 'W', value: 9 }, // White's 9 (attacker)
{ square: 'E5', type: 'T', color: 'W', value: 16 }, // White's 16 (helper)
{ square: 'G4', type: 'C', color: 'B', value: 25 }, // Black's 25 (target: 9+16=25)
]
const differenceExample: ExamplePiece[] = [
{ square: 'F4', type: 'T', color: 'W', value: 30 }, // White's 30 (attacker)
{ square: 'E5', type: 'C', color: 'W', value: 10 }, // White's 10 (helper)
{ square: 'G4', type: 'T', color: 'B', value: 20 }, // Black's 20 (target: 30-10=20)
]
const productExample: ExamplePiece[] = [
{ square: 'F4', type: 'C', color: 'W', value: 5 }, // White's 5 (attacker)
{ square: 'E5', type: 'C', color: 'W', value: 5 }, // White's 5 (helper)
{ square: 'G4', type: 'C', color: 'B', value: 25 }, // Black's 25 (target: 5×5=25)
]
return (
<div data-section="capture">
<h3
className={css({
fontSize: { base: '20px', md: '24px' },
fontWeight: 'bold',
color: '#7c2d12',
mb: '16px',
})}
>
{t('guide.capture.title', 'How to Capture')}
</h3>
<p className={css({ fontSize: '15px', lineHeight: '1.6', mb: '24px', color: '#374151' })}>
{t(
'guide.capture.description',
'You can only capture an enemy piece if your piece value has a mathematical relation to theirs:'
)}
</p>
<h4
className={css({
fontSize: '18px',
fontWeight: 'bold',
color: '#111827',
mb: '12px',
mt: '20px',
})}
>
{t('guide.capture.simpleTitle', 'Simple Relations (no helper needed)')}
</h4>
{/* Equality */}
<div
className={css({
mb: '20px',
p: '16px',
bg: '#f9fafb',
borderRadius: '8px',
border: '2px solid #e5e7eb',
})}
>
<p className={css({ fontSize: '14px', fontWeight: 'bold', color: '#111827', mb: '8px' })}>
{t('guide.capture.equality', 'Equal')}
</p>
<p className={css({ fontSize: '13px', color: '#6b7280', mb: '12px' })}>
{t('guide.capture.equalityExample', 'Your 25 captures their 25')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={equalityExample}
scale={0.4}
cropToSquares={['F3', 'I5']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#9ca3af',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.capture.equalityCaption',
'White Circle (25) can capture Black Circle (25) by equality'
)}
</p>
</div>
{/* Multiple/Divisor */}
<div
className={css({
mb: '20px',
p: '16px',
bg: '#f9fafb',
borderRadius: '8px',
border: '2px solid #e5e7eb',
})}
>
<p className={css({ fontSize: '14px', fontWeight: 'bold', color: '#111827', mb: '8px' })}>
{t('guide.capture.multiple', 'Multiple / Divisor')}
</p>
<p className={css({ fontSize: '13px', color: '#6b7280', mb: '12px' })}>
{t('guide.capture.multipleExample', 'Your 64 captures their 16 (64 ÷ 16 = 4)')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={multipleExample}
scale={0.4}
cropToSquares={['D4', 'G6']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#9ca3af',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.capture.multipleCaption',
'White Square (64) can capture Black Triangle (16) because 64 ÷ 16 = 4'
)}
</p>
</div>
<h4
className={css({
fontSize: '18px',
fontWeight: 'bold',
color: '#111827',
mb: '12px',
mt: '24px',
})}
>
{t('guide.capture.advancedTitle', 'Advanced Relations (need one helper piece)')}
</h4>
{/* Sum */}
<div
className={css({
mb: '20px',
p: '16px',
bg: '#f9fafb',
borderRadius: '8px',
border: '2px solid #e5e7eb',
})}
>
<p className={css({ fontSize: '14px', fontWeight: 'bold', color: '#111827', mb: '8px' })}>
{t('guide.capture.sum', 'Sum')}
</p>
<p className={css({ fontSize: '13px', color: '#6b7280', mb: '12px' })}>
{t('guide.capture.sumExample', 'Your 9 + helper 16 = enemy 25')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={sumExample}
scale={0.4}
cropToSquares={['D3', 'H6']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#9ca3af',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.capture.sumCaption',
'White Circle (9) can capture Black Circle (25) using helper Triangle (16): 9 + 16 = 25'
)}
</p>
</div>
{/* Difference */}
<div
className={css({
mb: '20px',
p: '16px',
bg: '#f9fafb',
borderRadius: '8px',
border: '2px solid #e5e7eb',
})}
>
<p className={css({ fontSize: '14px', fontWeight: 'bold', color: '#111827', mb: '8px' })}>
{t('guide.capture.difference', 'Difference')}
</p>
<p className={css({ fontSize: '13px', color: '#6b7280', mb: '12px' })}>
{t('guide.capture.differenceExample', 'Your 30 - helper 10 = enemy 20')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={differenceExample}
scale={0.4}
cropToSquares={['D3', 'H6']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#9ca3af',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.capture.differenceCaption',
'White Triangle (30) can capture Black Triangle (20) using helper Circle (10): 30 - 10 = 20'
)}
</p>
</div>
{/* Product */}
<div
className={css({
mb: '20px',
p: '16px',
bg: '#f9fafb',
borderRadius: '8px',
border: '2px solid #e5e7eb',
})}
>
<p className={css({ fontSize: '14px', fontWeight: 'bold', color: '#111827', mb: '8px' })}>
{t('guide.capture.product', 'Product')}
</p>
<p className={css({ fontSize: '13px', color: '#6b7280', mb: '12px' })}>
{t('guide.capture.productExample', 'Your 5 × helper 5 = enemy 25')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={productExample}
scale={0.4}
cropToSquares={['D3', 'H6']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#9ca3af',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.capture.productCaption',
'White Circle (5) can capture Black Circle (25) using helper Circle (5): 5 × 5 = 25'
)}
</p>
</div>
<div
className={css({
mt: '24px',
p: '16px',
bg: 'rgba(59, 130, 246, 0.1)',
borderLeft: '4px solid #3b82f6',
borderRadius: '4px',
})}
>
<p className={css({ fontSize: '14px', fontWeight: 'bold', color: '#1e40af', mb: '8px' })}>
{t('guide.capture.helpersTitle', '💡 What are helpers?')}
</p>
<p className={css({ fontSize: '14px', color: '#1e3a8a', lineHeight: '1.6' })}>
{t(
'guide.capture.helpersDescription',
'Helpers are your other pieces still on the board. They stay where they are and just provide their value for the math. The game shows you valid captures when you select a piece.'
)}
</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,235 @@
import { useTranslation } from 'react-i18next'
import { css } from '../../../../../styled-system/css'
import { RithmomachiaBoard, type ExamplePiece } from '../RithmomachiaBoard'
export function HarmonySection({ useNativeAbacusNumbers }: { useNativeAbacusNumbers: boolean }) {
const { t } = useTranslation()
// Example board positions for harmonies (White pieces in Black's territory: rows 5-8)
const arithmeticExample: ExamplePiece[] = [
{ square: 'E6', type: 'C', color: 'W', value: 6 }, // White's 6
{ square: 'F6', type: 'C', color: 'W', value: 9 }, // White's 9 (middle)
{ square: 'G6', type: 'T', color: 'W', value: 12 }, // White's 12 (arithmetic: 9 = (6+12)/2)
]
const geometricExample: ExamplePiece[] = [
{ square: 'E6', type: 'C', color: 'W', value: 4 }, // White's 4
{ square: 'F6', type: 'C', color: 'W', value: 8 }, // White's 8 (middle)
{ square: 'G6', type: 'T', color: 'W', value: 16 }, // White's 16 (geometric: 8² = 4×16 = 64)
]
const harmonicExample: ExamplePiece[] = [
{ square: 'E6', type: 'C', color: 'W', value: 6 }, // White's 6
{ square: 'F6', type: 'C', color: 'W', value: 8 }, // White's 8 (middle)
{ square: 'G6', type: 'T', color: 'W', value: 12 }, // White's 12 (harmonic: 2×6×12 = 144 = 8×18)
]
return (
<div data-section="harmony">
<h3
className={css({
fontSize: { base: '20px', md: '24px' },
fontWeight: 'bold',
color: '#7c2d12',
mb: '16px',
})}
>
{t('guide.harmony.title', 'Harmonies (Progressions)')}
</h3>
<p className={css({ fontSize: '15px', lineHeight: '1.6', mb: '24px', color: '#374151' })}>
{t(
'guide.harmony.description',
'Get 3 of your pieces into enemy territory forming one of these progressions:'
)}
</p>
<div className={css({ display: 'flex', flexDirection: 'column', gap: '20px' })}>
{/* Arithmetic Progression */}
<div
className={css({
p: '16px',
bg: '#f0fdf4',
border: '2px solid #86efac',
borderRadius: '8px',
})}
>
<h4
className={css({ fontSize: '16px', fontWeight: 'bold', color: '#15803d', mb: '8px' })}
>
{t('guide.harmony.arithmetic', 'Arithmetic Progression')}
</h4>
<p className={css({ fontSize: '14px', color: '#166534', mb: '8px' })}>
{t('guide.harmony.arithmeticDesc', 'Middle value is the average')}
</p>
<p
className={css({
fontSize: '13px',
color: '#16a34a',
fontFamily: 'monospace',
mb: '12px',
})}
>
{t('guide.harmony.arithmeticExample', 'Example: 6, 9, 12 (because 9 = (6+12)/2)')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={arithmeticExample}
scale={0.4}
cropToSquares={['D5', 'H7']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#166534',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.harmony.arithmeticCaption',
'White pieces 6, 9, 12 in a row in enemy territory form an arithmetic progression'
)}
</p>
</div>
{/* Geometric Progression */}
<div
className={css({
p: '16px',
bg: '#fef3c7',
border: '2px solid #fcd34d',
borderRadius: '8px',
})}
>
<h4
className={css({ fontSize: '16px', fontWeight: 'bold', color: '#92400e', mb: '8px' })}
>
{t('guide.harmony.geometric', 'Geometric Progression')}
</h4>
<p className={css({ fontSize: '14px', color: '#78350f', mb: '8px' })}>
{t('guide.harmony.geometricDesc', 'Middle value is geometric mean')}
</p>
<p
className={css({
fontSize: '13px',
color: '#a16207',
fontFamily: 'monospace',
mb: '12px',
})}
>
{t('guide.harmony.geometricExample', 'Example: 4, 8, 16 (because 8² = 4×16)')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={geometricExample}
scale={0.4}
cropToSquares={['D5', 'H7']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#78350f',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.harmony.geometricCaption',
'White pieces 4, 8, 16 in a row in enemy territory form a geometric progression'
)}
</p>
</div>
{/* Harmonic Progression */}
<div
className={css({
p: '16px',
bg: '#dbeafe',
border: '2px solid #93c5fd',
borderRadius: '8px',
})}
>
<h4
className={css({ fontSize: '16px', fontWeight: 'bold', color: '#1e40af', mb: '8px' })}
>
{t('guide.harmony.harmonic', 'Harmonic Progression')}
</h4>
<p className={css({ fontSize: '14px', color: '#1e3a8a', mb: '8px' })}>
{t('guide.harmony.harmonicDesc', 'Special proportion (formula: 2AB = M(A+B))')}
</p>
<p
className={css({
fontSize: '13px',
color: '#2563eb',
fontFamily: 'monospace',
mb: '12px',
})}
>
{t('guide.harmony.harmonicExample', 'Example: 6, 8, 12 (because 2×6×12 = 8×(6+12))')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={harmonicExample}
scale={0.4}
cropToSquares={['D5', 'H7']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#1e3a8a',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.harmony.harmonicCaption',
'White pieces 6, 8, 12 in a row in enemy territory form a harmonic progression'
)}
</p>
</div>
</div>
<div
className={css({
mt: '24px',
p: '16px',
bg: 'rgba(239, 68, 68, 0.1)',
borderLeft: '4px solid #ef4444',
borderRadius: '4px',
})}
>
<p className={css({ fontSize: '14px', fontWeight: 'bold', color: '#991b1b', mb: '8px' })}>
{t('guide.harmony.rulesTitle', '⚠️ Important Rules')}
</p>
<ul className={css({ fontSize: '14px', color: '#7f1d1d', lineHeight: '1.8', pl: '20px' })}>
<li>
{t(
'guide.harmony.rule1',
'Your 3 pieces must be in a straight line (row, column, or diagonal)'
)}
</li>
<li>{t('guide.harmony.rule2', 'All 3 must be in enemy territory')}</li>
<li>
{t(
'guide.harmony.rule3',
'When you form a harmony, your opponent gets one turn to break it'
)}
</li>
<li>{t('guide.harmony.rule4', 'If it survives, you win!')}</li>
</ul>
</div>
</div>
)
}

View File

@@ -0,0 +1,166 @@
import { useTranslation } from 'react-i18next'
import { css } from '../../../../../styled-system/css'
import { RithmomachiaBoard, type ExamplePiece } from '../RithmomachiaBoard'
export function OverviewSection({ useNativeAbacusNumbers }: { useNativeAbacusNumbers: boolean }) {
const { t } = useTranslation()
// Initial board setup - full starting position
const initialSetup: ExamplePiece[] = [
// BLACK - Column A
{ square: 'A1', type: 'S', color: 'B', value: 28 },
{ square: 'A2', type: 'S', color: 'B', value: 66 },
{ square: 'A7', type: 'S', color: 'B', value: 225 },
{ square: 'A8', type: 'S', color: 'B', value: 361 },
// BLACK - Column B
{ square: 'B1', type: 'S', color: 'B', value: 28 },
{ square: 'B2', type: 'S', color: 'B', value: 66 },
{ square: 'B3', type: 'T', color: 'B', value: 36 },
{ square: 'B4', type: 'T', color: 'B', value: 30 },
{ square: 'B5', type: 'T', color: 'B', value: 56 },
{ square: 'B6', type: 'T', color: 'B', value: 64 },
{ square: 'B7', type: 'S', color: 'B', value: 120 },
{ square: 'B8', type: 'P', color: 'B', value: 36 },
// BLACK - Column C
{ square: 'C1', type: 'T', color: 'B', value: 16 },
{ square: 'C2', type: 'T', color: 'B', value: 12 },
{ square: 'C3', type: 'C', color: 'B', value: 9 },
{ square: 'C4', type: 'C', color: 'B', value: 25 },
{ square: 'C5', type: 'C', color: 'B', value: 49 },
{ square: 'C6', type: 'C', color: 'B', value: 81 },
{ square: 'C7', type: 'T', color: 'B', value: 90 },
{ square: 'C8', type: 'T', color: 'B', value: 100 },
// BLACK - Column D
{ square: 'D3', type: 'C', color: 'B', value: 3 },
{ square: 'D4', type: 'C', color: 'B', value: 5 },
{ square: 'D5', type: 'C', color: 'B', value: 7 },
{ square: 'D6', type: 'C', color: 'B', value: 9 },
// WHITE - Column M
{ square: 'M3', type: 'C', color: 'W', value: 8 },
{ square: 'M4', type: 'C', color: 'W', value: 6 },
{ square: 'M5', type: 'C', color: 'W', value: 4 },
{ square: 'M6', type: 'C', color: 'W', value: 2 },
// WHITE - Column N
{ square: 'N1', type: 'T', color: 'W', value: 81 },
{ square: 'N2', type: 'T', color: 'W', value: 72 },
{ square: 'N3', type: 'C', color: 'W', value: 64 },
{ square: 'N4', type: 'C', color: 'W', value: 16 },
{ square: 'N5', type: 'C', color: 'W', value: 16 },
{ square: 'N6', type: 'C', color: 'W', value: 4 },
{ square: 'N7', type: 'T', color: 'W', value: 6 },
{ square: 'N8', type: 'T', color: 'W', value: 9 },
// WHITE - Column O
{ square: 'O1', type: 'S', color: 'W', value: 153 },
{ square: 'O2', type: 'P', color: 'W', value: 64 },
{ square: 'O3', type: 'T', color: 'W', value: 72 },
{ square: 'O4', type: 'T', color: 'W', value: 20 },
{ square: 'O5', type: 'T', color: 'W', value: 20 },
{ square: 'O6', type: 'T', color: 'W', value: 25 },
{ square: 'O7', type: 'S', color: 'W', value: 45 },
{ square: 'O8', type: 'S', color: 'W', value: 15 },
// WHITE - Column P
{ square: 'P1', type: 'S', color: 'W', value: 289 },
{ square: 'P2', type: 'S', color: 'W', value: 169 },
{ square: 'P7', type: 'S', color: 'W', value: 81 },
{ square: 'P8', type: 'S', color: 'W', value: 25 },
]
return (
<div data-section="overview">
<h3
className={css({
fontSize: { base: '20px', md: '24px' },
fontWeight: 'bold',
color: '#7c2d12',
mb: '16px',
})}
>
{t('guide.overview.goalTitle', 'Goal of the Game')}
</h3>
<p className={css({ fontSize: '16px', lineHeight: '1.6', mb: '20px', color: '#374151' })}>
{t(
'guide.overview.goal',
'Arrange 3 of your pieces in enemy territory to form a mathematical progression, survive one opponent turn, and win.'
)}
</p>
<h3
className={css({
fontSize: { base: '18px', md: '20px' },
fontWeight: 'bold',
color: '#7c2d12',
mb: '12px',
mt: '24px',
})}
>
{t('guide.overview.boardTitle', 'The Board')}
</h3>
<div className={css({ mb: '20px' })}>
<RithmomachiaBoard
pieces={initialSetup}
scale={0.6}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p className={css({ fontSize: '14px', color: '#6b7280', mb: '20px', fontStyle: 'italic' })}>
{t(
'guide.overview.boardCaption',
'The starting position - Black on the left, White on the right'
)}
</p>
<ul
className={css({
fontSize: '15px',
lineHeight: '1.8',
pl: '20px',
mb: '20px',
color: '#374151',
})}
>
<li>{t('guide.overview.boardSize', '8 rows × 16 columns (columns A-P, rows 1-8)')}</li>
<li>
{t(
'guide.overview.territory',
'Your half: Black controls rows 5-8, White controls rows 1-4'
)}
</li>
<li>
{t(
'guide.overview.enemyTerritory',
'Enemy territory: Where you need to build your winning progression'
)}
</li>
</ul>
<h3
className={css({
fontSize: { base: '18px', md: '20px' },
fontWeight: 'bold',
color: '#7c2d12',
mb: '12px',
mt: '24px',
})}
>
{t('guide.overview.howToPlayTitle', 'How to Play')}
</h3>
<ol
className={css({
fontSize: '15px',
lineHeight: '1.8',
pl: '20px',
color: '#374151',
})}
>
<li>{t('guide.overview.step1', 'Move your pieces toward the center')}</li>
<li>{t('guide.overview.step2', 'Look for ways to capture using math')}</li>
<li>{t('guide.overview.step3', 'Push into enemy territory')}</li>
<li>{t('guide.overview.step4', 'Watch for chances to make a progression')}</li>
<li>{t('guide.overview.step5', 'Win by forming a progression that survives one turn!')}</li>
</ol>
</div>
)
}

View File

@@ -0,0 +1,231 @@
import { useTranslation } from 'react-i18next'
import { css } from '../../../../../styled-system/css'
import { PieceRenderer } from '../PieceRenderer'
import type { PieceType } from '../../types'
export function PiecesSection({ useNativeAbacusNumbers }: { useNativeAbacusNumbers: boolean }) {
const { t } = useTranslation()
const pieces: {
type: PieceType
name: string
movement: string
count: number
exampleValues: number[]
}[] = [
{
type: 'C',
name: t('guide.pieces.circle', 'Circle'),
movement: t('guide.pieces.circleMove', 'Moves diagonally'),
count: 12,
exampleValues: [3, 5, 7, 9],
},
{
type: 'T',
name: t('guide.pieces.triangle', 'Triangle'),
movement: t('guide.pieces.triangleMove', 'Moves in straight lines'),
count: 6,
exampleValues: [12, 16, 20, 30],
},
{
type: 'S',
name: t('guide.pieces.square', 'Square'),
movement: t('guide.pieces.squareMove', 'Moves in any direction'),
count: 6,
exampleValues: [25, 28, 45, 66],
},
]
return (
<div data-section="pieces">
<h3
className={css({
fontSize: { base: '20px', md: '24px' },
fontWeight: 'bold',
color: '#7c2d12',
mb: '16px',
})}
>
{t('guide.pieces.title', 'Your Pieces (25 total)')}
</h3>
<p className={css({ fontSize: '15px', mb: '24px', color: '#374151' })}>
{t(
'guide.pieces.description',
'Each side has 25 pieces with different movement patterns. The shape tells you how it moves:'
)}
</p>
<div className={css({ display: 'flex', flexDirection: 'column', gap: '24px' })}>
{pieces.map((piece) => (
<div
key={piece.type}
className={css({
p: '16px',
bg: '#f9fafb',
borderRadius: '8px',
border: '2px solid #e5e7eb',
})}
>
<div
className={css({ display: 'flex', alignItems: 'center', gap: '12px', mb: '12px' })}
>
<div
className={css({
width: '60px',
height: '60px',
flexShrink: 0,
})}
>
<PieceRenderer
type={piece.type}
color="W"
value={piece.exampleValues[0]}
size={60}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<div className={css({ flex: 1 })}>
<h4
className={css({
fontSize: '18px',
fontWeight: 'bold',
color: '#111827',
mb: '4px',
})}
>
{piece.name} ({piece.count} per side)
</h4>
<p className={css({ fontSize: '14px', color: '#6b7280' })}>{piece.movement}</p>
</div>
</div>
{/* Example values */}
<div className={css({ mt: '12px' })}>
<p
className={css({
fontSize: '13px',
color: '#9ca3af',
mb: '8px',
fontStyle: 'italic',
})}
>
{t('guide.pieces.exampleValues', 'Example values')}:
</p>
<div className={css({ display: 'flex', gap: '12px', flexWrap: 'wrap' })}>
{piece.exampleValues.map((value) => (
<div
key={value}
className={css({
width: '48px',
height: '48px',
})}
>
<PieceRenderer
type={piece.type}
color="W"
value={value}
size={48}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
))}
</div>
</div>
</div>
))}
</div>
{/* Pyramid section */}
<div
className={css({
mt: '32px',
p: '20px',
bg: 'rgba(251, 191, 36, 0.1)',
borderLeft: '4px solid #f59e0b',
borderRadius: '4px',
})}
>
<h4 className={css({ fontSize: '18px', fontWeight: 'bold', color: '#92400e', mb: '12px' })}>
{t('guide.pieces.pyramidTitle', '⭐ Pyramids are special')}
</h4>
<p className={css({ fontSize: '14px', color: '#78350f', lineHeight: '1.6', mb: '16px' })}>
{t(
'guide.pieces.pyramidDescription',
'Each side has 1 Pyramid. Pyramids have 4 face values - when capturing, you choose which value to use. They move one step in any direction.'
)}
</p>
<div className={css({ display: 'flex', gap: '32px', flexWrap: 'wrap', mt: '16px' })}>
{/* Black Pyramid */}
<div>
<p
className={css({
fontSize: '13px',
fontWeight: 'bold',
color: '#92400e',
mb: '8px',
textAlign: 'center',
})}
>
{t('guide.pieces.blackPyramid', 'Black Pyramid')}
</p>
<div className={css({ width: '80px', height: '80px' })}>
<PieceRenderer
type="P"
color="B"
value="P"
size={80}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#78350f',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t('guide.pieces.pyramidValues', 'Values')}: 36, 25, 16, 4
</p>
</div>
{/* White Pyramid */}
<div>
<p
className={css({
fontSize: '13px',
fontWeight: 'bold',
color: '#92400e',
mb: '8px',
textAlign: 'center',
})}
>
{t('guide.pieces.whitePyramid', 'White Pyramid')}
</p>
<div className={css({ width: '80px', height: '80px' })}>
<PieceRenderer
type="P"
color="W"
value="P"
size={80}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#78350f',
mt: '8px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t('guide.pieces.pyramidValues', 'Values')}: 64, 49, 36, 25
</p>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,189 @@
import { useTranslation } from 'react-i18next'
import { css } from '../../../../../styled-system/css'
import { RithmomachiaBoard, type ExamplePiece } from '../RithmomachiaBoard'
export function VictorySection({ useNativeAbacusNumbers }: { useNativeAbacusNumbers: boolean }) {
const { t } = useTranslation()
// Example winning position: White has formed a geometric progression in Black's territory
const winningExample: ExamplePiece[] = [
// White's winning progression in enemy territory (rows 5-8)
{ square: 'E6', type: 'C', color: 'W', value: 4 },
{ square: 'F6', type: 'C', color: 'W', value: 8 },
{ square: 'G6', type: 'T', color: 'W', value: 16 },
// Some Black pieces remaining (unable to break the harmony)
{ square: 'C7', type: 'S', color: 'B', value: 49 },
{ square: 'J6', type: 'T', color: 'B', value: 30 },
]
return (
<div data-section="victory">
<h3
className={css({
fontSize: { base: '20px', md: '24px' },
fontWeight: 'bold',
color: '#7c2d12',
mb: '16px',
})}
>
{t('guide.victory.title', 'How to Win')}
</h3>
<div className={css({ display: 'flex', flexDirection: 'column', gap: '24px' })}>
<div>
<h4
className={css({
fontSize: '18px',
fontWeight: 'bold',
color: '#111827',
mb: '12px',
display: 'flex',
alignItems: 'center',
gap: '8px',
})}
>
<span>👑</span>
<span>{t('guide.victory.harmony', 'Victory #1: Harmony (Progression)')}</span>
</h4>
<p className={css({ fontSize: '15px', lineHeight: '1.6', color: '#374151', mb: '12px' })}>
{t(
'guide.victory.harmonyDesc',
"Form a mathematical progression with 3 pieces in enemy territory. If it survives your opponent's next turn, you win!"
)}
</p>
{/* Visual example of winning harmony */}
<div
className={css({
mb: '16px',
p: '16px',
bg: '#f9fafb',
borderRadius: '8px',
border: '2px solid #86efac',
})}
>
<p
className={css({
fontSize: '14px',
fontWeight: 'bold',
color: '#15803d',
mb: '12px',
textAlign: 'center',
})}
>
{t('guide.victory.exampleTitle', 'Example: White Wins!')}
</p>
<div className={css({ display: 'flex', justifyContent: 'center' })}>
<RithmomachiaBoard
pieces={winningExample}
scale={0.4}
cropToSquares={['B5', 'K8']}
showLabels={true}
useNativeAbacusNumbers={useNativeAbacusNumbers}
/>
</div>
<p
className={css({
fontSize: '12px',
color: '#166534',
mt: '12px',
textAlign: 'center',
fontStyle: 'italic',
})}
>
{t(
'guide.victory.exampleCaption',
'White pieces 4, 8, 16 form a geometric progression in enemy territory. Black cannot break it - White wins!'
)}
</p>
</div>
<div
className={css({
p: '12px',
bg: '#f0fdf4',
borderRadius: '6px',
border: '1px solid #86efac',
})}
>
<p className={css({ fontSize: '13px', color: '#15803d' })}>
{t(
'guide.victory.harmonyNote',
'This is the primary victory condition in Rithmomachia'
)}
</p>
</div>
</div>
<div>
<h4
className={css({
fontSize: '18px',
fontWeight: 'bold',
color: '#111827',
mb: '12px',
display: 'flex',
alignItems: 'center',
gap: '8px',
})}
>
<span>🚫</span>
<span>{t('guide.victory.exhaustion', 'Victory #2: Exhaustion')}</span>
</h4>
<p className={css({ fontSize: '15px', lineHeight: '1.6', color: '#374151' })}>
{t(
'guide.victory.exhaustionDesc',
'If your opponent has no legal moves at the start of their turn, they lose.'
)}
</p>
</div>
</div>
<h3
className={css({
fontSize: { base: '18px', md: '20px' },
fontWeight: 'bold',
color: '#7c2d12',
mb: '12px',
mt: '32px',
})}
>
{t('guide.victory.strategyTitle', 'Quick Strategy Tips')}
</h3>
<ul
className={css({
fontSize: '14px',
lineHeight: '1.8',
pl: '20px',
color: '#374151',
})}
>
<li>{t('guide.victory.tip1', 'Control the center — easier to invade enemy territory')}</li>
<li>
{t(
'guide.victory.tip2',
'Small pieces are fast — circles (3, 5, 7, 9) can slip into enemy half quickly'
)}
</li>
<li>
{t(
'guide.victory.tip3',
'Large pieces are powerful — harder to capture due to their size'
)}
</li>
<li>
{t(
'guide.victory.tip4',
"Watch for harmony threats — don't let opponent get 3 pieces deep in your territory"
)}
</li>
<li>
{t(
'guide.victory.tip5',
'Pyramids are flexible — choose the right face value for each situation'
)}
</li>
</ul>
</div>
)
}