fortel-ziweidoushu
Version:
紫微斗數排盤 Zi Wei Dou Shu
569 lines (502 loc) • 22.3 kB
text/typescript
import { mod } from './../utils'
import { Cell, BorrowCell } from './cell'
import { ConfigType, DestinyConfig, Gender } from './destinyConfig'
import { Ground } from './ground'
import { LifeStage } from './lifeStage'
import { MajorStar } from './majorStar'
import { MiniStar } from './miniStar'
import { MinorStar } from './minorStar'
import { Element, ShadowLight, Direction } from './miscEnums'
import { Sky } from './sky'
import { StarDerivative } from './starDerivative'
import { Temple } from './temple'
import util from 'util'
import { Star } from './star'
import type { Calendar } from './../calendar/calender'
import { defaultCalendar, Runtime } from '../main'
class DestinyBoard {
config: DestinyConfig
element: Element
#destinyMaster?: MajorStar | MinorStar
#bodyMaster?: MajorStar | MinorStar
#startControl?: Ground
#cells: Cell[] = []
#destinyTempleCellGround?: Ground
// cellGroundZeroIndex?: number
#bornStarDerivativeMap = new Map<StarDerivative, MajorStar | MinorStar>()
constructor(destinyConfig: DestinyConfig) {
this.config = destinyConfig
this.element = Element.FIRE
this.#setupStartControl()
this.#setupDestinyBodyMaster()
this.#setupBasicTempleCells()
this.#setupTemples()
this.#setupElements()
this.#setupLifeStage()
this.#setupAgeRange()
this.#setupMajorStars()
this.#setupMinorStars()
this.#setupMiniStars()
this.#setupBornStarDerivative()
this.#setupBorrowCells()
Object.freeze(this)
}
get destinyMaster() {
if (this.#destinyMaster !== undefined) {
return this.#destinyMaster
}
throw new Error('destinyMaster not defined.')
}
get bodyMaster() {
if (this.#bodyMaster !== undefined) {
return this.#bodyMaster
}
throw new Error('bodyMaster not defined.')
}
get startControl() {
if (this.#startControl !== undefined) {
return this.#startControl
}
throw new Error('startControl not defined.')
}
get destinyTempleCellGround() {
if (this.#destinyTempleCellGround !== undefined) {
return this.#destinyTempleCellGround
}
throw new Error('destinyTempleCellGround not defined.')
}
get cells() {
return this.#cells
}
get bornStarDerivativeMap() {
return this.#bornStarDerivativeMap
}
#setupStartControl(): void {
this.#startControl = this.config.bornTimeGround.ground.shift(-this.config.getLogicalMonth() + 1)
}
#setupDestinyBodyMaster(): void {
this.#destinyMaster = [
MajorStar.MAJOR_STAR_GREED,
MajorStar.MAJOR_STAR_ARGUMENT,
MinorStar.MINOR_STAR_EARN,
MinorStar.MINOR_STAR_SKILL,
MajorStar.MAJOR_STAR_FIRE,
MajorStar.MAJOR_STAR_GOLD,
MajorStar.MAJOR_STAR_PIONEER,
MajorStar.MAJOR_STAR_GOLD,
MajorStar.MAJOR_STAR_FIRE,
MinorStar.MINOR_STAR_SKILL,
MinorStar.MINOR_STAR_EARN,
MajorStar.MAJOR_STAR_ARGUMENT,
][this.config.yearGround.index]
this.#bodyMaster = [
MinorStar.MINOR_STAR_BURNING,
MajorStar.MAJOR_STAR_SUPPORT,
MajorStar.MAJOR_STAR_RULE,
MajorStar.MAJOR_STAR_ENJOYMENT,
MinorStar.MINOR_STAR_CLEVER,
MajorStar.MAJOR_STAR_CHANGE,
MinorStar.MINOR_STAR_BURNING,
MajorStar.MAJOR_STAR_SUPPORT,
MajorStar.MAJOR_STAR_RULE,
MajorStar.MAJOR_STAR_ENJOYMENT,
MinorStar.MINOR_STAR_CLEVER,
MajorStar.MAJOR_STAR_CHANGE,
][this.config.yearGround.index]
}
#setupBasicTempleCells(): void {
const cells: Cell[] = []
const tempCells: Cell[] = []
// build cells
for (let i = 0; i < 12; i++) {
const cell: Cell = new Cell(Sky.get((this.config.yearSky.index % 5) * 2 + 2 + i), Ground.get(i + 2))
tempCells.push(cell)
}
for (let i = -2; i < -2 + 12; i++) {
const cell = tempCells[mod(i, 12)]
cell.cellIndex = cells.length
//cell.cells = cells
cell.prevCell = tempCells[mod(i - 1, 12)]
cell.nextCell = tempCells[mod(i + 1, 12)]
cells.push(tempCells[mod(i, 12)])
}
this.#cells = cells //assign cells
// store cellGroundZeroIndex
// for (let i = 0; i < cells.length; i++) {
// if (cells[i].ground === Ground.get(0)) {
// this.cellGroundZeroIndex = i
// break
// }
// }
}
#setupTemples(): void {
// 安身
const bodyTempleGround = this.config.bornTimeGround.ground.shift(2 + this.config.getLogicalMonth() - 1)
let destinyTempleCellGround: Ground
// 安命
switch (this.config.configType) {
case ConfigType.GROUND:
destinyTempleCellGround = bodyTempleGround
break
case ConfigType.HUMAN:
destinyTempleCellGround = Ground.get(-this.config.bornTimeGround.ground.index + 2 + this.config.getLogicalMonth() - 1 + 2)
break
case ConfigType.SKY:
default:
destinyTempleCellGround = Ground.get(-this.config.bornTimeGround.ground.index + 2 + this.config.getLogicalMonth() - 1)
}
this.#destinyTempleCellGround = destinyTempleCellGround
this.getCellByGround(destinyTempleCellGround).temples.push(Temple.TEMPLE_DESTINY)
this.getCellByGround(bodyTempleGround).temples.push(Temple.TEMPLE_BODY)
for (let i = Temple.LOOP_TEMPLES.indexOf(Temple.TEMPLE_BROTHER); i < Temple.LOOP_TEMPLES.length; i++) {
const templeGround = Ground.get(destinyTempleCellGround.index - i)
this.getCellByGround(templeGround).temples.push(Temple.LOOP_TEMPLES[i])
}
}
#setupBorrowCells(): void {
for (const cell of this.#cells) {
if (cell.majorStars.length == 0) {
if (cell.getNextICell(4).majorStars.length == 0) {
cell.borrowCells.push(
Object.freeze(new BorrowCell(cell.getNextICell(6), 54.26)),
Object.freeze(new BorrowCell(cell.getPrevICell(4), 32.45)),
Object.freeze(new BorrowCell(cell.getPrevICell(2), 13.29)),
)
} else if (cell.getPrevICell(4).majorStars.length == 0) {
cell.borrowCells.push(
Object.freeze(new BorrowCell(cell.getNextICell(6), 54.26)),
Object.freeze(new BorrowCell(cell.getNextICell(4), 32.45)),
Object.freeze(new BorrowCell(cell.getNextICell(2), 13.29)),
)
} else {
cell.borrowCells.push(
Object.freeze(new BorrowCell(cell.getNextICell(6), 51)),
Object.freeze(new BorrowCell(cell.getNextICell(4), 24.5)),
Object.freeze(new BorrowCell(cell.getPrevICell(4), 24.5)),
)
}
}
}
}
#setupElements(): void {
const cell = this.getCellByTemple(Temple.TEMPLE_DESTINY)
const skyGroup = Math.round(Math.floor(cell.sky.index / 2))
const groundGroup = Math.round(Math.floor(cell.ground.index / 2))
if (skyGroup >= 0 && skyGroup < 10 / 2 && groundGroup >= 0 && groundGroup < 12 / 2) {
const mapping = [
[Element.GOLD, Element.WATER, Element.FIRE, Element.GOLD, Element.WATER, Element.FIRE],
[Element.WATER, Element.FIRE, Element.EARTH, Element.WATER, Element.FIRE, Element.EARTH],
[Element.FIRE, Element.EARTH, Element.WOOD, Element.FIRE, Element.EARTH, Element.WOOD],
[Element.EARTH, Element.WOOD, Element.GOLD, Element.EARTH, Element.WOOD, Element.GOLD],
[Element.WOOD, Element.GOLD, Element.WATER, Element.WOOD, Element.GOLD, Element.WATER],
]
this.element = mapping[skyGroup][groundGroup]
}
}
#setupLifeStage(): void {
let ground: Ground
if (this.element == Element.WATER) {
ground = Ground.get(8)
} else if (this.element == Element.WOOD) {
ground = Ground.get(11)
} else if (this.element == Element.GOLD) {
ground = Ground.get(5)
} else if (this.element == Element.EARTH) {
ground = Ground.get(8)
} else {
ground = Ground.get(2)
}
const direction = this.configDirection
const beginCell = this.getCellByGround(ground)
for (let i = 0; i < this.#cells.length; i++) {
beginCell.getNextICell(i * direction.direction).lifeStage = LifeStage.values[i]
}
}
#setupAgeRange(): void {
const direction = this.configDirection
const ageStart = this.element.patternNumber
const destinyCell = this.getCellByTemple(Temple.TEMPLE_DESTINY)
for (let i = 0; i < this.#cells.length; i++) {
destinyCell.getNextICell(i * direction.direction).ageStart = ageStart + 10 * i
}
}
#setupMajorStars(): void {
for (const [majorStar, majorStarPlacer] of MajorStar.majorStarPlacers.entries()) {
const ground = majorStarPlacer.evalGround(this)
const cell = this.getCellByGround(ground)
cell.majorStars.push(majorStar)
}
}
#setupMinorStars(): void {
for (const [minorStar, minorStarPlacer] of MinorStar.minorStarPlacers.entries()) {
const ground = minorStarPlacer.evalGround(this)
const cell = this.getCellByGround(ground)
cell.minorStars.push(minorStar)
}
}
#setupMiniStars(): void {
for (const miniStar of MiniStar.stars) {
const miniStarPlacer = MiniStar.miniStarPlacers.get(miniStar)
if (!miniStarPlacer) {
throw new Error('miniStarPlacer not found')
}
const ground = miniStarPlacer.evalGround(this)
const cell = this.getCellByGround(ground)
cell.miniStars.push(miniStar)
}
for (const miniStar of MiniStar.twelveScholarStars) {
const miniStarPlacer = MiniStar.miniStarPlacers.get(miniStar)
if (!miniStarPlacer) {
throw new Error('miniStarPlacer not found')
}
const ground = miniStarPlacer.evalGround(this)
const cell = this.getCellByGround(ground)
cell.scholarStar = miniStar
}
for (const miniStar of MiniStar.twelveYearGodStars) {
const miniStarPlacer = MiniStar.miniStarPlacers.get(miniStar)
if (!miniStarPlacer) {
throw new Error('miniStarPlacer not found')
}
const ground = miniStarPlacer.evalGround(this)
const cell = this.getCellByGround(ground)
cell.yearGodStar = miniStar
}
for (const miniStar of MiniStar.twelveLeaderStars) {
const miniStarPlacer = MiniStar.miniStarPlacers.get(miniStar)
if (!miniStarPlacer) {
throw new Error('miniStarPlacer not found')
}
const ground = miniStarPlacer.evalGround(this)
const cell = this.getCellByGround(ground)
cell.leaderStar = miniStar
}
}
#setupBornStarDerivative(): void {
this.#bornStarDerivativeMap.set(StarDerivative.WEALTHINESS, StarDerivative.getWealthinessStar(this.config.yearSky))
this.#bornStarDerivativeMap.set(StarDerivative.POWER, StarDerivative.getPowerStar(this.config.yearSky))
this.#bornStarDerivativeMap.set(StarDerivative.FAME, StarDerivative.getFameStar(this.config.yearSky))
this.#bornStarDerivativeMap.set(StarDerivative.PROBLEM, StarDerivative.getProblemStar(this.config.yearSky))
Object.freeze(this.#bornStarDerivativeMap)
}
getCellByGround(ground: Ground): Cell {
return this.#cells[ground.index]
}
getCellByTemple(temple: Temple): Cell {
const targetCell = this.#cells.find((cell) => {
return cell.temples.map((temple) => temple.key).includes(temple.key)
})
if (targetCell) {
return targetCell
} else {
throw new Error('Cannot find cell')
}
}
getCellByStar(star: Star): Cell {
const targetCell = this.cells.find((cell) => {
return cell.allStars.includes(star)
})
if (!targetCell) {
throw new Error('Cannot find cell')
}
return targetCell
}
getTenYearSky(age: number): Sky {
const direction = this.configDirection.direction
const destinyCell = this.getCellByTemple(Temple.TEMPLE_DESTINY)
const cycleAgeEnd = destinyCell.getNextICell(-1 * direction).ageEnd
const logicalAge = mod(age, cycleAgeEnd)
let targetCell: Cell | undefined
for (let i = 0; i < this.#cells.length; i++) {
const cell = destinyCell.getNextICell(i * direction)
if (logicalAge <= cell.ageEnd) {
targetCell = cell
break
}
}
if (targetCell) {
return targetCell.sky
} else {
throw new Error('Cannot find ten year sky for unknown reason.')
}
}
getTenYearCellGround(age: number): Ground {
const direction = this.configDirection.direction
const destinyCell = this.getCellByTemple(Temple.TEMPLE_DESTINY)
const cycleAgeEnd = destinyCell.getNextICell(-1 * direction).ageEnd
const logicalAge = mod(age, cycleAgeEnd)
let targetCell: Cell | undefined
for (let i = 0; i < this.#cells.length; i++) {
const cell = destinyCell.getNextICell(i * direction)
if (logicalAge <= cell.ageEnd) {
targetCell = cell
break
}
}
if (targetCell) {
return targetCell.ground
} else {
throw new Error('Cannot find ten year ground for unknown reason.')
}
}
getRuntimContext({
lunarYear,
lunarMonth,
lunarDay,
leap,
calendar,
}: {
lunarYear: number
lunarMonth: number
lunarDay: number
leap: boolean
calendar?: Calendar
}): RuntimeContext {
calendar = calendar ?? defaultCalendar
const age = lunarYear - this.config.year + 1
const tenYearGround = this.getTenYearCellGround(age)
const runtimeDateContext = {
age,
effectiveMonth: lunarMonth + (leap && lunarDay > 15 ? 1 : 0),
tenYearGround,
tenYearSky: this.getCellByGround(tenYearGround).sky,
...calendar.lunarSkyGround(lunarYear, lunarMonth, lunarDay, leap),
}
runtimeDateContext.monthSky = Sky.get((((runtimeDateContext.yearSky.index % 5) + 1) * 2 + (runtimeDateContext.effectiveMonth - 1)) % 10)
runtimeDateContext.monthGround = Ground.get(runtimeDateContext.effectiveMonth + 1)
const runtimeBoardState: {
tenYear: {
cellGround: Ground | null
groundStars: Map<Ground, MinorStar[]> | null
starDerivativeMap: Map<MajorStar | MinorStar, StarDerivative> | null
}
year: {
cellGround: Ground | null
groundStars: Map<Ground, MinorStar[]> | null
starDerivativeMap: Map<MajorStar | MinorStar, StarDerivative> | null
}
month: {
cellGround: Ground | null
groundStars: Map<Ground, MinorStar[]> | null
starDerivativeMap: Map<MajorStar | MinorStar, StarDerivative> | null
}
day: {
cellGround: Ground | null
groundStars: Map<Ground, MinorStar[]> | null
starDerivativeMap: Map<MajorStar | MinorStar, StarDerivative> | null
}
} = {
tenYear: { cellGround: null, groundStars: null, starDerivativeMap: null },
year: { cellGround: null, groundStars: null, starDerivativeMap: null },
month: { cellGround: null, groundStars: null, starDerivativeMap: null },
day: { cellGround: null, groundStars: null, starDerivativeMap: null },
}
runtimeBoardState.tenYear.cellGround = runtimeDateContext.tenYearGround
runtimeBoardState.tenYear.groundStars = Runtime.getRuntimeLocationStars(runtimeDateContext.tenYearSky)
runtimeBoardState.tenYear.starDerivativeMap = Runtime.getStarToDerivativeMapOf(runtimeDateContext.tenYearSky)
runtimeBoardState.year.cellGround = runtimeDateContext.yearGround
runtimeBoardState.year.groundStars = Runtime.getRuntimeLocationStars(runtimeDateContext.yearSky)
runtimeBoardState.year.starDerivativeMap = Runtime.getStarToDerivativeMapOf(runtimeDateContext.yearSky)
runtimeBoardState.month.cellGround = this.startControl.shift(runtimeDateContext.yearGround.index).shift(lunarMonth - 1)
runtimeBoardState.month.groundStars = Runtime.getRuntimeLocationStars(runtimeDateContext.monthSky)
runtimeBoardState.month.starDerivativeMap = Runtime.getStarToDerivativeMapOf(runtimeDateContext.monthSky)
runtimeBoardState.day.cellGround = runtimeBoardState.month.cellGround.shift(lunarDay - 1)
if (leap) {
runtimeBoardState.day.cellGround = runtimeBoardState.day.cellGround.shift(calendar.lunarMonthDays(lunarYear, lunarMonth, false))
}
runtimeBoardState.day.groundStars = Runtime.getRuntimeLocationStars(runtimeDateContext.daySky)
runtimeBoardState.day.starDerivativeMap = Runtime.getStarToDerivativeMapOf(runtimeDateContext.daySky)
return {
...runtimeDateContext,
...runtimeBoardState,
}
}
get shadowLight(): ShadowLight {
return this.config.yearSky.index % 2 == 0 ? ShadowLight.LIGHT : ShadowLight.SHADOW
}
get configDirection(): Direction {
const direction = this.shadowLight === ShadowLight.LIGHT ? Direction.CLOCKWISE : Direction.ANTI_CLOCKWISE
return direction.add(this.config.gender == Gender.M ? Direction.CLOCKWISE : Direction.ANTI_CLOCKWISE)
}
getMajorStarEnergyLevel(majorStar: MajorStar) {
return MajorStar.majorStarPlacers.get(majorStar)!.evalEnergyLevel(MajorStar.majorStarPlacers.get(majorStar)!.evalGround(this))
}
getMajorStarDerivative(star: MajorStar | MinorStar) {
if (this.#bornStarDerivativeMap.get(StarDerivative.WEALTHINESS)?.equals(star)) {
return StarDerivative.WEALTHINESS
} else if (this.#bornStarDerivativeMap.get(StarDerivative.POWER)?.equals(star)) {
return StarDerivative.POWER
} else if (this.#bornStarDerivativeMap.get(StarDerivative.FAME)?.equals(star)) {
return StarDerivative.FAME
} else if (this.#bornStarDerivativeMap.get(StarDerivative.PROBLEM)?.equals(star)) {
return StarDerivative.PROBLEM
} else {
return null
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
[util.inspect.custom](depth: number, opts: any): string {
return util.inspect(this.toJSON(), opts)
}
toJSON() {
return {
config: this.config,
element: this.element,
destinyMaster: this.#destinyMaster,
bodyMaster: this.#bodyMaster,
startControl: this.#startControl,
cells: this.#cells,
bornStarDerivativeMap: Object.fromEntries(this.#bornStarDerivativeMap),
}
}
// prettier-ignore
toString(): string {
const cellsString = this.#cells.map((cell) => ' ' + cell.toString()).join(',\n')
return (
`DestinyBoard {\n`+
` config: ${this.config},\n`+
` element: ${this.element},\n`+
` destinyMaster: ${this.#destinyMaster},\n`+
` bodyMaster: ${this.#bodyMaster},\n`+
` startControl: ${this.#startControl},\n`+
` cells: [\n`+
cellsString+'\n'+
` ],\n`+
` #bornStarDerivativeMap: ${JSON.stringify(Object.fromEntries(this.#bornStarDerivativeMap))}\n`+
`}`
)
}
}
export type RuntimeContext = {
tenYear: {
cellGround: Ground | null
groundStars: Map<Ground, MinorStar[]> | null
starDerivativeMap: Map<MajorStar | MinorStar, StarDerivative> | null
}
year: {
cellGround: Ground | null
groundStars: Map<Ground, MinorStar[]> | null
starDerivativeMap: Map<MajorStar | MinorStar, StarDerivative> | null
}
month: {
cellGround: Ground | null
groundStars: Map<Ground, MinorStar[]> | null
starDerivativeMap: Map<MajorStar | MinorStar, StarDerivative> | null
}
day: {
cellGround: Ground | null
groundStars: Map<Ground, MinorStar[]> | null
starDerivativeMap: Map<MajorStar | MinorStar, StarDerivative> | null
}
yearSky: Sky
yearGround: Ground
monthSky: Sky
monthGround: Ground
daySky: Sky
dayGround: Ground
age: number
effectiveMonth: number
tenYearGround: Ground
tenYearSky: Sky
}
export { DestinyBoard }