Skip to content

Commit

Permalink
+++
Browse files Browse the repository at this point in the history
  • Loading branch information
Offirmo committed Sep 16, 2024
1 parent 2ff37e6 commit 394c421
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { type Immutable} from '@offirmo-private/ts-types'
import { Enum } from 'typescript-string-enums'

import { generate_uuid } from '@offirmo-private/uuid'
import { getꓽrandom, RNGEngine } from '@offirmo/random'

import {
CharacterAttribute,
CharacterClass,
State as CharacterState,
} from '@tbrpg/state--character'
import * as InventoryState from '@tbrpg/state--inventory'
import * as WalletState from '@tbrpg/state--wallet'
import {
create as create_weapon,
is_at_max_enhancement as is_weapon_at_max_enhancement,
} from '@tbrpg/logic--weapons'
import {
create as create_armor,
} from '@tbrpg/logic--armors'
import {
create as create_monster,
} from '@tbrpg/logic--monsters'
import {
OutcomeArchetype,
AdventureType,
AdventureArchetype,
generate_random_coin_gain_or_loss,
} from '@tbrpg/logic--adventures'

import {
ResolvedAdventure,
} from '../types.js'

import { LIB } from '../consts.js'

/////////////////////////////////////////////////

type AttributesArray = CharacterAttribute[]

const ALL_ATTRIBUTES_X_LVL: AttributesArray = [ 'health', 'mana', 'strength', 'agility', 'charisma', 'wisdom', 'luck' ]

const WARRIOR_LIKE_PRIMARY_ATTRIBUTES: AttributesArray = ['strength']
const ROGUE_LIKE_PRIMARY_ATTRIBUTES: AttributesArray = ['agility']
const MAGE_LIKE_PRIMARY_ATTRIBUTES: AttributesArray = ['mana']
const HYBRID_PALADIN_LIKE_PRIMARY_ATTRIBUTES: AttributesArray = ['strength', 'mana']

const PRIMARY_STATS_BY_CLASS: { [k: string]: AttributesArray } = {
[CharacterClass.novice]: ALL_ATTRIBUTES_X_LVL,

[CharacterClass.barbarian]: WARRIOR_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.druid]: ['wisdom', 'mana'],
[CharacterClass.warrior]: WARRIOR_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.paladin]: HYBRID_PALADIN_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.rogue]: ROGUE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.sorcerer]: MAGE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.warlock]: MAGE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.wizard]: MAGE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass['darkness hunter']]: HYBRID_PALADIN_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.hunter]: ['agility'],
[CharacterClass.priest]: ['charisma', 'mana'],
[CharacterClass['death knight']]: HYBRID_PALADIN_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.mage]: MAGE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.engineer]: ['wisdom'],
[CharacterClass.thief]: ROGUE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.assassin]: ROGUE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.illusionist]: ['charisma', 'agility'],
[CharacterClass.knight]: WARRIOR_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.pirate]: ['luck'],
[CharacterClass.ninja]: ROGUE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.corsair]: ROGUE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.necromancer]: MAGE_LIKE_PRIMARY_ATTRIBUTES,
[CharacterClass.sculptor]: ['agility'],
[CharacterClass.summoner]: MAGE_LIKE_PRIMARY_ATTRIBUTES,
}
if (Object.keys(PRIMARY_STATS_BY_CLASS).length !== Enum.keys(CharacterClass).length)
throw new Error(`${LIB}: PRIMARY_STATS_BY_CLASS is out of date!`)


const WARRIOR_LIKE_SECONDARY_ATTRIBUTES: AttributesArray = ['health']
const ROGUE_LIKE_SECONDARY_ATTRIBUTES: AttributesArray = ['luck']
const MAGE_LIKE_SECONDARY_ATTRIBUTES: AttributesArray = ['wisdom']
const HYBRID_PALADIN_LIKE_SECONDARY_ATTRIBUTES: AttributesArray = ['health']


const SECONDARY_STATS_BY_CLASS: { [k: string]: AttributesArray } = {
[CharacterClass.novice]: ALL_ATTRIBUTES_X_LVL,

[CharacterClass.barbarian]: WARRIOR_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.druid]: ['strength', 'agility'],
[CharacterClass.warrior]: WARRIOR_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.paladin]: HYBRID_PALADIN_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.rogue]: ROGUE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.sorcerer]: MAGE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.warlock]: MAGE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.wizard]: MAGE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass['darkness hunter']]: HYBRID_PALADIN_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.hunter]: ['strength'],
[CharacterClass.priest]: ['wisdom'],
[CharacterClass['death knight']]: HYBRID_PALADIN_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.mage]: MAGE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.engineer]: ['agility', 'luck'],
[CharacterClass.thief]: ROGUE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.assassin]: ROGUE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.illusionist]: ['luck'],
[CharacterClass.knight]: WARRIOR_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.pirate]: ['charisma', 'agility'],
[CharacterClass.ninja]: ROGUE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.corsair]: ['charisma', 'luck'],
[CharacterClass.necromancer]: MAGE_LIKE_SECONDARY_ATTRIBUTES,
[CharacterClass.sculptor]: ['wisdom', 'charisma'],
[CharacterClass.summoner]: MAGE_LIKE_SECONDARY_ATTRIBUTES,
}
if (Object.keys(SECONDARY_STATS_BY_CLASS).length !== Enum.keys(CharacterClass).length)
throw new Error(`${LIB}: SECONDARY_STATS_BY_CLASS is out of date!`)

/////////////////////////////////////////////////

function create(
rng: RNGEngine,
aa: Immutable<AdventureArchetype>,
character: Immutable<CharacterState>,
inventory: Immutable<InventoryState.State>,
wallet: Immutable<WalletState.State>,
): ResolvedAdventure {
const { hid, good, type, outcome } = aa

const should_gain: OutcomeArchetype = {
...outcome,
}

// instantiate the special gains
if (should_gain.random_attribute) {
const stat: keyof OutcomeArchetype = getꓽrandom.picker.of(ALL_ATTRIBUTES_X_LVL)(rng) as keyof OutcomeArchetype
(should_gain as any)[stat] = true
}
if (should_gain.lowest_attribute) {
const lowest_stat: keyof OutcomeArchetype = ALL_ATTRIBUTES_X_LVL.reduce((acc, val) => {
return (character.attributes as any)[acc] < (character.attributes as any)[val] ? acc : val
}, 'health') as keyof OutcomeArchetype
(should_gain as any)[lowest_stat] = true
}
if (should_gain.class_primary_attribute) {
const stat: keyof OutcomeArchetype = getꓽrandom.picker.of(PRIMARY_STATS_BY_CLASS[character.klass]!)(rng) as keyof OutcomeArchetype
(should_gain as any)[stat] = true
}
if (should_gain.class_secondary_attribute) {
const stat: keyof OutcomeArchetype = getꓽrandom.picker.of(SECONDARY_STATS_BY_CLASS[character.klass]!)(rng) as keyof OutcomeArchetype
(should_gain as any)[stat] = true
}

if (should_gain.armor_or_weapon) {
// TODO take into account the existing inventory?
if (getꓽrandom.generator_of.bool()(rng))
should_gain.armor = true
else
should_gain.weapon = true
}
if (should_gain.improvementⵧarmor_or_weapon) {
if (is_weapon_at_max_enhancement(InventoryState.get_slotted_weapon(inventory)!)) // most likely to happen
should_gain.improvementⵧarmor = true
else if (getꓽrandom.generator_of.bool()(rng))
should_gain.improvementⵧarmor = true
else
should_gain.improvementⵧweapon = true
}

// intermediate data
const new_player_level = character.attributes.level + (should_gain.level ? 1 : 0)

// TODO check multiple charac gain (should not happen)
return {
uuid: generate_uuid(),
hid,
good,
encounter: type === AdventureType.fight ? create_monster(rng, {level: character.attributes.level}) : null,
gains: {
level: should_gain.level ? 1 : 0,
health: should_gain.health ? 1 : 0,
mana: should_gain.mana ? 1 : 0,
strength: should_gain.strength ? 1 : 0,
agility: should_gain.agility ? 1 : 0,
charisma: should_gain.charisma ? 1 : 0,
wisdom: should_gain.wisdom ? 1 : 0,
luck: should_gain.luck ? 1 : 0,
coin: generate_random_coin_gain_or_loss(rng, {
range: should_gain.coin,
player_level: new_player_level,
current_wallet_amount: wallet.coin_count,
}),
token: should_gain.token ? 1 : 0,
armor: should_gain.armor ? create_armor(rng) : null,
weapon: should_gain.weapon ? create_weapon(rng) : null,
improvementⵧarmor: should_gain.improvementⵧarmor,
improvementⵧweapon: should_gain.improvementⵧweapon,
},
}
}

/////////////////////////////////////////////////

export {
create,
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ function create(SXC?: TBRSoftExecutionContext, seed?: PRNGState.Seed): Immutable
wallet: WalletState.add_amount(WalletState.create(), WalletState.Currency.coin, 1), // don't start empty so that a loss can happen
prng: PRNGState.create(seed),
energy: u_state_energy,
engagement: EngagementState.create(SXC),
codes: CodesState.create(SXC),
engagement: EngagementState.create(SXC as any),
codes: CodesState.create(SXC as any),
progress: AchievementsState.create(SXC),
meta: MetaState.create(),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@
"@offirmo-private/rich-text-format": "^0",
"@oh-my-rpg/state--meta": "^0",
"@tbrpg/definitions": "^0",
"@tbrpg/logic--shop": "^0",
"@tbrpg/logic--adventure--resolved": "*",
"@tbrpg/logic--adventures": "^0",
"@tbrpg/logic--armors": "^0",
"@tbrpg/logic--monsters": "^0",
"@tbrpg/logic--shop": "^0",
"@tbrpg/logic--weapons": "^0",
"@tbrpg/state--achievements": "^0",
"@tbrpg/state--character": "^0",
"@tbrpg/state--inventory": "^0",
"@tbrpg/state--achievements": "^0",
"@tbrpg/state--wallet": "^0",
"tiny-invariant": "^1",
"typescript-string-enums": "^1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { type Immutable } from '@offirmo-private/ts-types'
import { InventorySlot, ITEM_SLOTS } from '@tbrpg/definitions'
import { CHARACTER_ATTRIBUTES, CharacterAttribute } from '@tbrpg/state--character'
import { type InventorySlot, ITEM_SLOTS } from '@tbrpg/definitions'
import { CHARACTER_ATTRIBUTES, type CharacterAttribute } from '@tbrpg/state--character'
import { i18n_messages as I18N_ADVENTURES } from '@tbrpg/logic--adventures'
import { Adventure } from '@tbrpg/state'
import { ALL_CURRENCIES, Currency, get_currency_amount } from '@tbrpg/state--wallet'
import { type ResolvedAdventure } from '@tbrpg/logic--adventure--resolved'
import { ALL_CURRENCIES, type Currency } from '@tbrpg/state--wallet'

import * as RichText from '@offirmo-private/rich-text-format'

Expand All @@ -14,7 +14,7 @@ import { RenderItemOptions } from './types.js'
import { DEFAULT_RENDER_ITEM_OPTIONS } from './consts.js'


function render_adventure(a: Immutable<Adventure>, options: Immutable<RenderItemOptions> = DEFAULT_RENDER_ITEM_OPTIONS): RichText.Document {
function renderꓽresolved_adventure(a: Immutable<ResolvedAdventure>, options: Immutable<RenderItemOptions> = DEFAULT_RENDER_ITEM_OPTIONS): RichText.Document {
const gains: any = a.gains // alias for typing

// in this special function, we'll be:
Expand Down Expand Up @@ -132,9 +132,9 @@ function render_adventure(a: Immutable<Adventure>, options: Immutable<RenderItem
const active_adventure_outcomes = Object.keys(gains).filter(prop => !!gains[prop])
const unhandled_adventure_outcomes = active_adventure_outcomes.filter(prop => !handled_adventure_outcomes_so_far.has(prop))
if (unhandled_adventure_outcomes.length) {
console.error(`render_adventure(): *UN*handled outcome properties: "${unhandled_adventure_outcomes}"!`)
console.info(`render_adventure(): handled outcome properties: "${Array.from(handled_adventure_outcomes_so_far.values())}"`)
throw new Error('render_adventure(): unhandled outcome properties!')
console.error(`renderꓽresolved_adventure(): *UN*handled outcome properties: "${unhandled_adventure_outcomes}"!`)
console.info(`renderꓽresolved_adventure(): handled outcome properties: "${Array.from(handled_adventure_outcomes_so_far.values())}"`)
throw new Error('renderꓽresolved_adventure(): unhandled outcome properties!')
}

/////// Final wrap-up //////
Expand All @@ -159,5 +159,5 @@ function render_adventure(a: Immutable<Adventure>, options: Immutable<RenderItem


export {
render_adventure,
renderꓽresolved_adventure,
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { expect } from 'chai'
import strip_terminal_escape_codes from 'strip-ansi'

import rich_text_to_terminal from '@offirmo-private/rich-text-format--to-terminal'
import { xxx_internal_reset_prng_cache } from '@oh-my-rpg/state--prng'
import { ALL_GOOD_ADVENTURE_ARCHETYPES, ALL_BAD_ADVENTURE_ARCHETYPES } from '@tbrpg/logic--adventures'
import {
create,
play,
} from '@tbrpg/state'
import {
DEMO_ADVENTURE_01,
DEMO_ADVENTURE_02,
DEMO_ADVENTURE_03,
DEMO_ADVENTURE_04,
} from '@tbrpg/state'
} from '@tbrpg/logic--adventure--resolved'

import rich_text_to_terminal from '@offirmo-private/rich-text-format--to-terminal'

import { render_adventure } from './index.js'
import { renderꓽresolved_adventure } from './index.js'


describe('🔠 view to @offirmo-private/rich-text-format - adventure', function() {

it('should render properly - with gain of skills', () => {
const $doc = render_adventure(DEMO_ADVENTURE_01)
const $doc = renderꓽresolved_adventure(DEMO_ADVENTURE_01)
//console.log(prettifyꓽjson($doc))

const str = strip_terminal_escape_codes(rich_text_to_terminal($doc))
Expand All @@ -34,7 +35,7 @@ describe('🔠 view to @offirmo-private/rich-text-format - adventure', function
})

it('should render properly - with gain of coins', () => {
const $doc = render_adventure(DEMO_ADVENTURE_02)
const $doc = renderꓽresolved_adventure(DEMO_ADVENTURE_02)
//console.log(prettifyꓽjson($doc))

const str = strip_terminal_escape_codes(rich_text_to_terminal($doc))
Expand All @@ -46,7 +47,7 @@ describe('🔠 view to @offirmo-private/rich-text-format - adventure', function
})

it('should render properly - with gain of item(s)', () => {
const $doc = render_adventure(DEMO_ADVENTURE_03)
const $doc = renderꓽresolved_adventure(DEMO_ADVENTURE_03)
//console.log(prettifyꓽjson($doc))

const str = strip_terminal_escape_codes(rich_text_to_terminal($doc))
Expand All @@ -57,7 +58,7 @@ describe('🔠 view to @offirmo-private/rich-text-format - adventure', function
})

it('should render properly - with gain of item improvement', () => {
const $doc = render_adventure(DEMO_ADVENTURE_04)
const $doc = renderꓽresolved_adventure(DEMO_ADVENTURE_04)
//console.log(prettifyꓽjson($doc))

const str = strip_terminal_escape_codes(rich_text_to_terminal($doc))
Expand All @@ -74,11 +75,11 @@ describe('🔠 view to @offirmo-private/rich-text-format - adventure', function
ALL_GOOD_ADVENTURE_ARCHETYPES
.forEach(({hid, good}, index) => {
describe(`✅ adventure #${index} "${hid}"`, function() {
it('should be playable', () => {
it('should be render-able', () => {
let state = create()
state = play(state, undefined, hid)

const $doc = render_adventure(state.u_state.last_adventure!)
const $doc = renderꓽresolved_adventure(state.u_state.last_adventure!)
//console.log(prettifyꓽjson($doc))

// should just not throw
Expand All @@ -91,7 +92,7 @@ describe('🔠 view to @offirmo-private/rich-text-format - adventure', function
ALL_BAD_ADVENTURE_ARCHETYPES
.forEach(({hid, good}, index) => {
describe(`❎ adventure #${index} "${hid}"`, function() {
it('should be playable', () => {
it('should be render-able', () => {
let state = create()

state = play(state)
Expand All @@ -104,11 +105,12 @@ describe('🔠 view to @offirmo-private/rich-text-format - adventure', function

state = play(state, undefined, hid)

const $doc = render_adventure(state.u_state.last_adventure!)
const $doc = renderꓽresolved_adventure(state.u_state.last_adventure!)
//console.log(prettifyꓽjson($doc))

// should just not throw
const str = rich_text_to_terminal($doc)
//console.log(str)
// should just not throw
})
})
})
Expand Down

0 comments on commit 394c421

Please sign in to comment.