refactor(rithmomachia): extract constants and coordinate utilities (Phase 1)
Extract duplicated code into reusable modules to improve maintainability and reduce code size in RithmomachiaGame.tsx. **Constants extracted:** - constants/captureRelations.ts: Relation colors, operators, tooltips - RELATION_COLORS map (was duplicated 3x) - RELATION_OPERATORS map (was duplicated 3x) - Helper functions: getRelationColor(), getRelationOperator() - constants/board.ts: Board layout constants - Board dimensions, column/row labels - Default cell size, gap, padding values **Utilities extracted:** - utils/boardCoordinates.ts: Board position calculations - parseSquare(): Convert "A1" notation to file/rank indices - getSquarePosition(): Calculate pixel position from square notation - Replaces ~20 lines of duplicated coordinate calculation code **Changes to RithmomachiaGame.tsx:** - Removed 3 duplicate color/operator map definitions (~50 lines) - Replaced inline coordinate calculations with getSquarePosition() - Net reduction: ~65 lines of duplicated code This is Phase 1 of the rithmomachia refactoring plan. Remaining phases: - Phase 2: Extract capture UI components (~1,500 lines) - Phase 3: Refactor board components (container/presentation split) - Phase 4: Extract phase components (Setup, Playing, Results) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@ import { useViewerId } from '@/hooks/useViewerId'
|
||||
import { useAbacusSettings } from '@/hooks/useAbacusSettings'
|
||||
import type { RosterWarning } from '@/components/nav/GameContextNav'
|
||||
import { css } from '../../../../styled-system/css'
|
||||
import { getRelationColor, getRelationOperator } from '../constants/captureRelations'
|
||||
import { getSquarePosition } from '../utils/boardCoordinates'
|
||||
import { useRithmomachia } from '../Provider'
|
||||
import type { Piece, RelationKind, RithmomachiaConfig } from '../types'
|
||||
import { validateMove } from '../utils/pathValidator'
|
||||
@@ -1412,29 +1414,8 @@ function HelperSelectionOptions({
|
||||
}
|
||||
}
|
||||
|
||||
// Color scheme based on relation type
|
||||
const colorMap: Record<RelationKind, string> = {
|
||||
SUM: '#ef4444', // red
|
||||
DIFF: '#f97316', // orange
|
||||
PRODUCT: '#8b5cf6', // purple
|
||||
RATIO: '#3b82f6', // blue
|
||||
EQUAL: '#10b981', // green
|
||||
MULTIPLE: '#eab308', // yellow
|
||||
DIVISOR: '#06b6d4', // cyan
|
||||
}
|
||||
const color = colorMap[relation] || '#6b7280'
|
||||
|
||||
// Operator symbols
|
||||
const operatorMap: Record<RelationKind, string> = {
|
||||
SUM: '+',
|
||||
DIFF: '−',
|
||||
PRODUCT: '×',
|
||||
RATIO: '÷',
|
||||
EQUAL: '=',
|
||||
MULTIPLE: '×',
|
||||
DIVISOR: '÷',
|
||||
}
|
||||
const operator = operatorMap[relation] || '?'
|
||||
const color = getRelationColor(relation)
|
||||
const operator = getRelationOperator(relation)
|
||||
|
||||
return (
|
||||
<g>
|
||||
@@ -1594,38 +1575,11 @@ function NumberBondVisualization({
|
||||
return () => clearTimeout(timer)
|
||||
}, [autoAnimate])
|
||||
|
||||
// Color scheme based on relation type
|
||||
const colorMap: Record<RelationKind, string> = {
|
||||
SUM: '#ef4444', // red
|
||||
DIFF: '#f97316', // orange
|
||||
PRODUCT: '#8b5cf6', // purple
|
||||
RATIO: '#3b82f6', // blue
|
||||
EQUAL: '#10b981', // green
|
||||
MULTIPLE: '#eab308', // yellow
|
||||
DIVISOR: '#06b6d4', // cyan
|
||||
}
|
||||
const color = colorMap[relation] || '#6b7280'
|
||||
|
||||
// Operation symbol based on relation
|
||||
const operatorMap: Record<RelationKind, string> = {
|
||||
SUM: '+',
|
||||
DIFF: '−',
|
||||
PRODUCT: '×',
|
||||
RATIO: '÷',
|
||||
EQUAL: '=',
|
||||
MULTIPLE: '×',
|
||||
DIVISOR: '÷',
|
||||
}
|
||||
const operator = operatorMap[relation]
|
||||
const color = getRelationColor(relation)
|
||||
const operator = getRelationOperator(relation)
|
||||
|
||||
// Calculate actual board position for target
|
||||
const targetFile = targetPiece.square.charCodeAt(0) - 65
|
||||
const targetRank = Number.parseInt(targetPiece.square.slice(1), 10)
|
||||
const targetRow = 8 - targetRank
|
||||
const targetBoardPos = {
|
||||
x: padding + targetFile * (cellSize + gap) + cellSize / 2,
|
||||
y: padding + targetRow * (cellSize + gap) + cellSize / 2,
|
||||
}
|
||||
const targetBoardPos = getSquarePosition(targetPiece.square, { cellSize, gap, padding })
|
||||
|
||||
// Animation: Rotate and collapse from actual positions to target
|
||||
const captureAnimation = useSpring({
|
||||
@@ -2088,56 +2042,14 @@ function CaptureRelationOptions({
|
||||
// Show only the current helper
|
||||
const currentHelper = validHelpers[currentHelperIndex]
|
||||
|
||||
// Color scheme based on relation type
|
||||
const colorMap: Record<RelationKind, string> = {
|
||||
SUM: '#ef4444', // red
|
||||
DIFF: '#f97316', // orange
|
||||
PRODUCT: '#8b5cf6', // purple
|
||||
RATIO: '#3b82f6', // blue
|
||||
EQUAL: '#10b981', // green
|
||||
MULTIPLE: '#eab308', // yellow
|
||||
DIVISOR: '#06b6d4', // cyan
|
||||
}
|
||||
const color = colorMap[hoveredRelation] || '#6b7280'
|
||||
const color = getRelationColor(hoveredRelation)
|
||||
const operator = getRelationOperator(hoveredRelation)
|
||||
|
||||
// Operator symbols
|
||||
const operatorMap: Record<RelationKind, string> = {
|
||||
SUM: '+',
|
||||
DIFF: '−',
|
||||
PRODUCT: '×',
|
||||
RATIO: '÷',
|
||||
EQUAL: '=',
|
||||
MULTIPLE: '×',
|
||||
DIVISOR: '÷',
|
||||
}
|
||||
const operator = operatorMap[hoveredRelation] || '?'
|
||||
|
||||
// Calculate mover position on board
|
||||
const moverFile = moverPiece.square.charCodeAt(0) - 65
|
||||
const moverRank = Number.parseInt(moverPiece.square.slice(1), 10)
|
||||
const moverRow = 8 - moverRank
|
||||
const moverPos = {
|
||||
x: padding + moverFile * (cellSize + gap) + cellSize / 2,
|
||||
y: padding + moverRow * (cellSize + gap) + cellSize / 2,
|
||||
}
|
||||
|
||||
// Calculate target position on board
|
||||
const targetFile = targetPiece.square.charCodeAt(0) - 65
|
||||
const targetRank = Number.parseInt(targetPiece.square.slice(1), 10)
|
||||
const targetRow = 8 - targetRank
|
||||
const targetBoardPos = {
|
||||
x: padding + targetFile * (cellSize + gap) + cellSize / 2,
|
||||
y: padding + targetRow * (cellSize + gap) + cellSize / 2,
|
||||
}
|
||||
|
||||
// Calculate current helper position on board
|
||||
const helperFile = currentHelper.square.charCodeAt(0) - 65
|
||||
const helperRank = Number.parseInt(currentHelper.square.slice(1), 10)
|
||||
const helperRow = 8 - helperRank
|
||||
const helperPos = {
|
||||
x: padding + helperFile * (cellSize + gap) + cellSize / 2,
|
||||
y: padding + helperRow * (cellSize + gap) + cellSize / 2,
|
||||
}
|
||||
// Calculate piece positions on board
|
||||
const layout = { cellSize, gap, padding }
|
||||
const moverPos = getSquarePosition(moverPiece.square, layout)
|
||||
const targetBoardPos = getSquarePosition(targetPiece.square, layout)
|
||||
const helperPos = getSquarePosition(currentHelper.square, layout)
|
||||
|
||||
return (
|
||||
<g key={currentHelper.id}>
|
||||
|
||||
23
apps/web/src/arcade-games/rithmomachia/constants/board.ts
Normal file
23
apps/web/src/arcade-games/rithmomachia/constants/board.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Board layout constants
|
||||
*/
|
||||
|
||||
export const BOARD_ROWS = 8
|
||||
export const BOARD_COLUMNS = 8
|
||||
|
||||
/**
|
||||
* Column labels (A-H)
|
||||
*/
|
||||
export const COLUMN_LABELS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] as const
|
||||
|
||||
/**
|
||||
* Row labels (1-8)
|
||||
*/
|
||||
export const ROW_LABELS = [1, 2, 3, 4, 5, 6, 7, 8] as const
|
||||
|
||||
/**
|
||||
* Default board dimensions (can be overridden)
|
||||
*/
|
||||
export const DEFAULT_CELL_SIZE = 64
|
||||
export const DEFAULT_GAP = 4
|
||||
export const DEFAULT_PADDING = 32
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { RelationKind } from '../types'
|
||||
|
||||
/**
|
||||
* Color scheme for each capture relation type
|
||||
*/
|
||||
export const RELATION_COLORS: Record<RelationKind, string> = {
|
||||
SUM: '#ef4444', // red
|
||||
DIFF: '#f97316', // orange
|
||||
PRODUCT: '#8b5cf6', // purple
|
||||
RATIO: '#3b82f6', // blue
|
||||
EQUAL: '#10b981', // green
|
||||
MULTIPLE: '#eab308', // yellow
|
||||
DIVISOR: '#06b6d4', // cyan
|
||||
}
|
||||
|
||||
/**
|
||||
* Mathematical operator symbols for each relation
|
||||
*/
|
||||
export const RELATION_OPERATORS: Record<RelationKind, string> = {
|
||||
SUM: '+',
|
||||
DIFF: '−',
|
||||
PRODUCT: '×',
|
||||
RATIO: '÷',
|
||||
EQUAL: '=',
|
||||
MULTIPLE: '×',
|
||||
DIVISOR: '÷',
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-readable descriptions for relation tooltips
|
||||
*/
|
||||
export const RELATION_TOOLTIPS: Record<RelationKind, string> = {
|
||||
EQUAL: 'Equality: a = b',
|
||||
MULTIPLE: 'Multiple: b is a multiple of a',
|
||||
DIVISOR: 'Divisor: a divides evenly into b',
|
||||
SUM: 'Arithmetic Sum: a + c = b',
|
||||
DIFF: 'Arithmetic Difference: b − a = c',
|
||||
PRODUCT: 'Geometric Product: a × c = b',
|
||||
RATIO: 'Geometric Ratio: b ÷ a = c',
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relation color with fallback
|
||||
*/
|
||||
export function getRelationColor(relation: RelationKind | null): string {
|
||||
if (!relation) return '#6b7280' // gray fallback
|
||||
return RELATION_COLORS[relation] || '#6b7280'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relation operator symbol with fallback
|
||||
*/
|
||||
export function getRelationOperator(relation: RelationKind | null): string {
|
||||
if (!relation) return '?'
|
||||
return RELATION_OPERATORS[relation] || '?'
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Board coordinate calculation utilities
|
||||
*/
|
||||
|
||||
export interface BoardPosition {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface BoardLayout {
|
||||
cellSize: number
|
||||
gap: number
|
||||
padding: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse square notation (e.g., "A1", "H8") into file and rank indices
|
||||
* @param square - Square notation (e.g., "A1")
|
||||
* @returns Object with file (0-7) and rank (1-8)
|
||||
*/
|
||||
export function parseSquare(square: string): { file: number; rank: number } {
|
||||
const file = square.charCodeAt(0) - 65 // 'A' = 0, 'B' = 1, etc.
|
||||
const rank = Number.parseInt(square.slice(1), 10)
|
||||
return { file, rank }
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert file and rank to row index (0-7, top to bottom)
|
||||
* @param rank - Rank number (1-8)
|
||||
* @returns Row index (0-7)
|
||||
*/
|
||||
export function rankToRow(rank: number): number {
|
||||
return 8 - rank
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the center position of a square on the board
|
||||
* @param square - Square notation (e.g., "A1")
|
||||
* @param layout - Board layout configuration
|
||||
* @returns Center position {x, y} in pixels
|
||||
*/
|
||||
export function getSquarePosition(square: string, layout: BoardLayout): BoardPosition {
|
||||
const { file, rank } = parseSquare(square)
|
||||
const row = rankToRow(rank)
|
||||
|
||||
return {
|
||||
x: layout.padding + file * (layout.cellSize + layout.gap) + layout.cellSize / 2,
|
||||
y: layout.padding + row * (layout.cellSize + layout.gap) + layout.cellSize / 2,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the top-left corner position of a square on the board
|
||||
* @param square - Square notation (e.g., "A1")
|
||||
* @param layout - Board layout configuration
|
||||
* @returns Top-left position {x, y} in pixels
|
||||
*/
|
||||
export function getSquareCorner(square: string, layout: BoardLayout): BoardPosition {
|
||||
const { file, rank } = parseSquare(square)
|
||||
const row = rankToRow(rank)
|
||||
|
||||
return {
|
||||
x: layout.padding + file * (layout.cellSize + layout.gap),
|
||||
y: layout.padding + row * (layout.cellSize + layout.gap),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total board dimensions
|
||||
* @param layout - Board layout configuration
|
||||
* @returns Total width and height in pixels
|
||||
*/
|
||||
export function getBoardDimensions(layout: BoardLayout): { width: number; height: number } {
|
||||
const boardSize = 8 * layout.cellSize + 7 * layout.gap
|
||||
return {
|
||||
width: boardSize + 2 * layout.padding,
|
||||
height: boardSize + 2 * layout.padding,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user