Skip to content

Commit

Permalink
Adds part-merging feature.
Browse files Browse the repository at this point in the history
  • Loading branch information
mntone committed Dec 26, 2023
1 parent 4b4ca98 commit 305c9bc
Show file tree
Hide file tree
Showing 10 changed files with 400 additions and 283 deletions.
374 changes: 204 additions & 170 deletions src/App/Localizable.xcstrings

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions src/App/ViewModels/SettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ final class SettingsViewModel: ObservableObject {
}
}

@Published
var mergeParts: Bool {
didSet {
settings.mergeParts = mergeParts
}
}

init(rootViewModel: HomeViewModel,
storage: Storage) {
guard let app = MAApp.resolver.resolve(MonsterAnalyzerCore.App.self) else {
Expand All @@ -34,8 +41,10 @@ final class SettingsViewModel: ObservableObject {
self.settings = app.settings
self.storage = storage
self.elementDisplay = app.settings.elementDisplay
self.mergeParts = app.settings.mergeParts

settings.$elementDisplay.dropFirst().receive(on: DispatchQueue.main).assign(to: &$elementDisplay)
settings.$mergeParts.dropFirst().receive(on: DispatchQueue.main).assign(to: &$mergeParts)
}

convenience init(rootViewModel: HomeViewModel) {
Expand Down
8 changes: 2 additions & 6 deletions src/App/Views/MonsterView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct MonsterView: View {
StateView(state: viewModel.state, background: background) { data in
Form {
if viewModel.elementDisplay != .none {
Section {
Section("Weakness") {
let requireHeader = data.weakness.sections.count > 1
ForEach(data.weakness.sections) { section in
if requireHeader {
Expand All @@ -29,12 +29,10 @@ struct MonsterView: View {
viewModel: section)
}
}
} header: {
Text("header.weakness")
}
}

Section {
Section("Physiology") {
let requireHeader = data.physiologies.sections.count > 1
ForEach(data.physiologies.sections) { section in
if requireHeader {
Expand All @@ -58,8 +56,6 @@ struct MonsterView: View {
#endif
}
}
} header: {
Text("header.physiology")
}
}
.block {
Expand Down
31 changes: 22 additions & 9 deletions src/App/Views/Settings/DisplaySettingsPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,32 @@ struct DisplaySettingsPane: View {

var body: some View {
Form {
Section("Weakness") {
#if os(watchOS)
Toggle("Element Attack", isOn: Binding {
viewModel.elementDisplay != .none
} set: { value in
viewModel.elementDisplay = value ? .sign : .none
})
Toggle("Element Attack", isOn: Binding {
viewModel.elementDisplay != .none
} set: { value in
viewModel.elementDisplay = value ? .sign : .none
})
#else
PreferredPicker("Element Attack",
data: WeaknessDisplayMode.allCases,
selection: $viewModel.elementDisplay) { mode in
Text(mode.localizedKey)
PreferredPicker("Element Attack",
data: WeaknessDisplayMode.allCases,
selection: $viewModel.elementDisplay) { mode in
Text(mode.localizedKey)
}
#endif
}

Section {
Toggle("Merge parts with the same status", isOn: $viewModel.mergeParts)
} header: {
Text("Physiology")
} footer: {
Text("The settings will take effect when you restart the app.")
#if os(macOS)
.foregroundStyle(.secondary)
#endif
}
}
.navigationTitle("Display")
.modifier(SharedSettingsPaneModifier())
Expand Down
4 changes: 2 additions & 2 deletions src/Core/DataSources/MockDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ public struct MockDataSource {
])

public static var physiology1: Physiologies {
PhysiologyMapper.map(json: monster1,
languageService: PassthroughtLanguageService())
let mapper = PhysiologyMapper(languageService: PassthroughtLanguageService())
return mapper.map(json: monster1, options: PhysiologyMapperOptions(mergeParts: false))
}

public init() {
Expand Down
238 changes: 148 additions & 90 deletions src/Core/Mappers/PhysiologyMapper.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import Foundation

enum PhysiologyMapper {
private static let removingState: Set<String> = ["default", "broken"]
struct PhysiologyMapperOptions {
let mergeParts: Bool
}

private static func getStateInfo(_ states: [String]) -> PhysiologyStateInfo {
if states.contains("broken") {
return .broken
} else if states.contains("default") {
return .default
} else {
return .other
}
struct PhysiologyMapper {
private let _languageService: LanguageService

init(languageService: LanguageService) {
self._languageService = languageService
}

private static func map(_ src: MHMonsterPhysiologyValue,
states: [String]? = nil,
languageService: LanguageService) -> Physiology {
private func map(_ src: MHMonsterPhysiologyValue,
states: [String]? = nil) -> Physiology {
let baseStates = states ?? src.states
let statesLabel = languageService.getLocalizedJoinedString(of: baseStates, for: .state)
return Physiology(stateInfo: getStateInfo(baseStates),
let statesLabel = _languageService.getLocalizedJoinedString(of: baseStates, for: .state)
return Physiology(stateInfo: Self.getStateInfo(baseStates),
label: statesLabel,
value: PhysiologyValue(slash: src.slash,
strike: src.strike,
Expand All @@ -31,54 +28,7 @@ enum PhysiologyMapper {
stun: src.stun)
}

private static func getAverage(_ data: [PhysiologyGroup], of attack: Attack) -> Float {
let sum = data.map { group in
group.parts.count * group.items.map { physiology in
Int(physiology.value.value(of: attack))
}.reduce(into: 0) { cur, next in
cur += next
}
}.reduce(into: 0) { cur, next in
cur += next
}

let count = data.map { group in
group.parts.count * group.items.count
}.reduce(into: 0) { cur, next in
cur += next
}
return Float(sum) / Float(count)
}

private static func getAverages(_ data: [PhysiologyGroup]) -> PhysiologyValue<Float> {
PhysiologyValue(slash: Self.getAverage(data, of: .slash),
strike: Self.getAverage(data, of: .strike),
shell: Self.getAverage(data, of: .shell),
fire: Self.getAverage(data, of: .fire),
water: Self.getAverage(data, of: .water),
thunder: Self.getAverage(data, of: .thunder),
ice: Self.getAverage(data, of: .ice),
dragon: Self.getAverage(data, of: .dragon))
}

private static func filter<C>(_ states: C, by physiologies: [MHMonsterPhysiology], in frequency10e6: Int) -> [String] where C: Collection, C.Element == String {
return states.filter { state in
let pickedCount = physiologies.reduce(into: 0) { count, physiology in
let hasState = physiology.values.contains { value in
value.states.contains(state)
}
if hasState {
count += 1
}
}
let output = 1000000 * pickedCount / physiologies.count >= frequency10e6
return output
}
}

private static func getGroup(of targetState: String,
from values: [MHMonsterPhysiologyValue],
languageService: LanguageService) -> [Physiology] {
private func getGroup(of targetState: String, from values: [MHMonsterPhysiologyValue]) -> [Physiology] {
values.compactMap { value -> Physiology? in
guard value.states.contains(targetState) else {
return nil
Expand All @@ -89,11 +39,79 @@ enum PhysiologyMapper {
if filteredState.isEmpty {
filteredState.append("default")
}
return map(value, states: filteredState, languageService: languageService)
return map(value, states: filteredState)
}
}

private func map(_ targetState: String, of physiologies: [MHMonsterPhysiology], merge: Bool) -> [PhysiologyGroup] {
if merge {
var result: [PhysiologyGroup] = []
for physiology in physiologies {
let abnormalItems = getGroup(of: targetState, from: physiology.values)
guard !abnormalItems.isEmpty else {
let partsLabel = _languageService.getLocalizedJoinedString(of: physiology.parts, for: .part)
let defaultItems = getGroup(of: "default", from: physiology.values)
let defaultGroup = PhysiologyGroup(parts: physiology.parts,
label: partsLabel,
items: defaultItems,
isReference: true)
result.append(defaultGroup)
continue
}

if let matchedIndex = result.firstIndex(where: { group in
group.items == abnormalItems
}) {
let targetGroup = result[matchedIndex]
let mergedParts = targetGroup.parts + physiology.parts
let mergedPartsLabel = _languageService.getLocalizedJoinedString(of: mergedParts, for: .part)
let mergedGroup = PhysiologyGroup(parts: mergedParts,
label: mergedPartsLabel,
items: targetGroup.items,
isReference: false)
result[matchedIndex] = mergedGroup
continue
}

let partsLabel = _languageService.getLocalizedJoinedString(of: physiology.parts, for: .part)
let abnormalGroup = PhysiologyGroup(parts: physiology.parts,
label: partsLabel,
items: abnormalItems,
isReference: false)
result.append(abnormalGroup)
}

// Replace the label with "all" if the number of items is 1.
if result.count == 1 {
let allLabel = _languageService.getLocalizedJoinedString(of: ["all"], for: .part)
let allGroup = result[0]
result[0] = PhysiologyGroup(parts: allGroup.parts,
label: allLabel,
items: allGroup.items,
isReference: allGroup.isReference)
}
return result
} else {
let result = physiologies.map { physiology in
let abnormalItems = getGroup(of: targetState, from: physiology.values)
let partsLabel = _languageService.getLocalizedJoinedString(of: physiology.parts, for: .part)
guard !abnormalItems.isEmpty else {
let defaultItems = getGroup(of: "default", from: physiology.values)
return PhysiologyGroup(parts: physiology.parts,
label: partsLabel,
items: defaultItems,
isReference: true)
}
return PhysiologyGroup(parts: physiology.parts,
label: partsLabel,
items: abnormalItems,
isReference: false)
}
return result
}
}

static func map(json src: MHMonster, languageService: LanguageService) -> Physiologies {
func map(json src: MHMonster, options: PhysiologyMapperOptions) -> Physiologies {
let allAbnormalStates = Set(src.physiologies.flatMap { physiologies in
Set(physiologies.values.flatMap(\.states))
}).subtracting(Self.removingState)
Expand All @@ -104,45 +122,85 @@ enum PhysiologyMapper {
guard !physiologyValue.states.contains(where: { s in filteredAllAbnormalStates.contains(s) }) else {
return nil
}
return map(physiologyValue, languageService: languageService)
return map(physiologyValue)
}

let partsLabel = languageService.getLocalizedJoinedString(of: physiology.parts, for: .part)
let partsLabel = _languageService.getLocalizedJoinedString(of: physiology.parts, for: .part)
return PhysiologyGroup(parts: physiology.parts,
label: partsLabel,
items: items,
isReference: false)
}
let defaultSection = PhysiologySection(label: languageService.getLocalizedString(of: "default", for: .state),
let defaultSection = PhysiologySection(label: _languageService.getLocalizedString(of: "default", for: .state),
groups: defaultSectionData,
average: getAverages(defaultSectionData))
average: Self.getAverages(defaultSectionData))

var abnormalSections = filteredAllAbnormalStates.map { targetState in
let sectionData = src.physiologies.map { physiology in
let abnormalItems = getGroup(of: targetState,
from: physiology.values,
languageService: languageService)
let partsLabel = languageService.getLocalizedJoinedString(of: physiology.parts, for: .part)
guard !abnormalItems.isEmpty else {
let defaultItems = getGroup(of: "default",
from: physiology.values,
languageService: languageService)
return PhysiologyGroup(parts: physiology.parts,
label: partsLabel,
items: defaultItems,
isReference: true)
}
return PhysiologyGroup(parts: physiology.parts,
label: partsLabel,
items: abnormalItems,
isReference: false)
}
return PhysiologySection(label: languageService.getLocalizedString(of: targetState, for: .state),
let sectionData = map(targetState, of: src.physiologies, merge: options.mergeParts)
return PhysiologySection(label: _languageService.getLocalizedString(of: targetState, for: .state),
groups: sectionData,
average: getAverages(sectionData))
average: Self.getAverages(sectionData))
}
abnormalSections.insert(defaultSection, at: 0)

return Physiologies(id: src.id, sections: abnormalSections)
}


private static let removingState: Set<String> = ["default", "broken"]

private static func getStateInfo(_ states: [String]) -> PhysiologyStateInfo {
if states.contains("broken") {
return .broken
} else if states.contains("default") {
return .default
} else {
return .other
}
}

private static func filter<C>(_ states: C, by physiologies: [MHMonsterPhysiology], in frequency10e6: Int) -> [String] where C: Collection, C.Element == String {
return states.filter { state in
let pickedCount = physiologies.reduce(into: 0) { count, physiology in
let hasState = physiology.values.contains { value in
value.states.contains(state)
}
if hasState {
count += 1
}
}
let output = 1000000 * pickedCount / physiologies.count >= frequency10e6
return output
}
}

private static func getAverage(_ data: [PhysiologyGroup], of attack: Attack) -> Float {
let sum = data.map { group in
group.parts.count * group.items.map { physiology in
Int(physiology.value.value(of: attack))
}.reduce(into: 0) { cur, next in
cur += next
}
}.reduce(into: 0) { cur, next in
cur += next
}

let count = data.map { group in
group.parts.count * group.items.count
}.reduce(into: 0) { cur, next in
cur += next
}
return Float(sum) / Float(count)
}

private static func getAverages(_ data: [PhysiologyGroup]) -> PhysiologyValue<Float> {
PhysiologyValue(slash: Self.getAverage(data, of: .slash),
strike: Self.getAverage(data, of: .strike),
shell: Self.getAverage(data, of: .shell),
fire: Self.getAverage(data, of: .fire),
water: Self.getAverage(data, of: .water),
thunder: Self.getAverage(data, of: .thunder),
ice: Self.getAverage(data, of: .ice),
dragon: Self.getAverage(data, of: .dragon))
}
}
Loading

0 comments on commit 305c9bc

Please sign in to comment.