Messages AND phase summaries working with last results format

This commit is contained in:
AlxAI 2025-02-09 22:05:11 -08:00
parent 16e3b45264
commit 3af8527c62
23 changed files with 165 additions and 2896 deletions

View file

@ -1,49 +1,70 @@
import React from "react";
import PropTypes from "prop-types";
import { PhaseSummaryView } from "./phase_summary_view"; // Import named export
/**
* A simple bottom sheet that slides up from the bottom of the screen,
* showing the current phase summary.
*/
export function PhaseSummaryBottomSheet({ phase, summaryText, onClose }) {
export class PhaseSummaryBottomSheet extends React.Component {
componentDidUpdate(prevProps) {
if (prevProps.phase !== this.props.phase) {
console.log(
"[PhaseSummaryBottomSheet Debug] Phase prop changed from",
prevProps.phase,
"to",
this.props.phase
);
}
}
return (
<div style={{
position: 'fixed',
bottom: 0,
left: 0,
width: '100%',
maxHeight: '40%',
backgroundColor: '#fff',
boxShadow: '0 -2px 8px rgba(0,0,0,0.25)',
zIndex: 9999,
overflowY: 'auto',
transition: 'transform 0.3s ease-in-out',
transform: 'translateY(0%)'
}}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.5rem',
background: '#f2f2f2',
borderBottom: '1px solid #ccc'
}}>
<h5 className="mb-0">Summary for {phase}:</h5>
<button className="btn btn-sm btn-danger" onClick={onClose}>Close</button>
</div>
<div style={{
padding: '1rem',
overflowY: 'auto'
}}>
{summaryText || "No phase summary available."}
</div>
</div>
);
render() {
const { phase, summaryText, visible, onClose } = this.props;
if (!visible) {
return null;
}
const bottomSheetStyle = {
position: 'fixed',
bottom: 0,
left: 0,
width: '100%',
backgroundColor: 'white',
borderTop: '1px solid #ccc',
padding: '20px',
boxShadow: '0px -2px 5px rgba(0, 0, 0, 0.1)',
zIndex: 1000,
maxHeight: '40vh',
overflowY: 'auto'
};
return (
<div style={bottomSheetStyle}>
{/* <div style={{ fontStyle: 'italic', color: 'gray' }}>
Debug: Currently showing summary for phase: {phase}
</div> */}
<PhaseSummaryView phase={phase} summaryText={summaryText} />
<button
onClick={onClose}
style={{
position: 'absolute',
top: '10px',
right: '10px',
padding: '5px 10px'
}}
>
Close
</button>
</div>
);
}
}
PhaseSummaryBottomSheet.propTypes = {
phase: PropTypes.string,
summaryText: PropTypes.string,
onClose: PropTypes.func.isRequired
phase: PropTypes.string.isRequired,
summaryText: PropTypes.string,
visible: PropTypes.bool,
onClose: PropTypes.func.isRequired
};
PhaseSummaryBottomSheet.defaultProps = {
visible: false,
summaryText: ""
};

View file

@ -21,7 +21,6 @@ export class MessageView extends React.Component {
// message
render() {
const message = this.props.message;
console.log("DEBUG: In <MessageView>, message prop =>", message);
const owner = this.props.owner;
const id = this.props.id ? {id: this.props.id} : {};
const messagesLines = message.message.replace('\r\n', '\n')

View file

@ -4,6 +4,7 @@ import PropTypes from "prop-types";
export class PhaseSummaryView extends React.Component {
render() {
const { phase, summaryText } = this.props;
console.log("PhaseSummaryView props", this.props)
if (!summaryText) {
return null; // or some fallback message
}
@ -24,4 +25,4 @@ export class PhaseSummaryView extends React.Component {
PhaseSummaryView.propTypes = {
phase: PropTypes.string.isRequired,
summaryText: PropTypes.string
};
};

View file

@ -921,6 +921,7 @@ export class ContentGame extends React.Component {
renderCurrentMessages(engine, role) {
const messageChannels = engine.getMessageChannels(role, true);
console.log('messageChannels =>', messageChannels);
const tabNames = [];
for (let powerName of Object.keys(engine.powers)) if (powerName !== role)
tabNames.push(powerName);
@ -1081,13 +1082,17 @@ export class ContentGame extends React.Component {
}
renderTabResults(toDisplay, initialEngine) {
const {engine, pastPhases, phaseIndex} = this.__get_engine_to_display(initialEngine);
const {
engine: displayedEngine,
pastPhases,
phaseIndex
} = this.__get_engine_to_display(initialEngine);
let orders = {};
let orderResult = null;
if (engine.order_history.contains(engine.phase))
orders = engine.order_history.get(engine.phase);
if (engine.result_history.contains(engine.phase))
orderResult = engine.result_history.get(engine.phase);
if (displayedEngine.order_history.contains(displayedEngine.phase))
orders = displayedEngine.order_history.get(displayedEngine.phase);
if (displayedEngine.result_history.contains(displayedEngine.phase))
orderResult = displayedEngine.result_history.get(displayedEngine.phase);
let countOrders = 0;
for (let powerOrders of Object.values(orders)) {
if (powerOrders)
@ -1142,7 +1147,7 @@ export class ContentGame extends React.Component {
{this.state.historyCurrentOrders && (
<div className={'history-current-orders'}>{this.state.historyCurrentOrders.join(', ')}</div>
)}
{this.renderMapForResults(engine, this.state.historyShowOrders)}
{this.renderMapForResults(displayedEngine, this.state.historyShowOrders)}
</div>
<div className={'col-xl'}>{orderView}</div>
</Row>
@ -1155,27 +1160,21 @@ export class ContentGame extends React.Component {
}
renderTabMessages(toDisplay, initialEngine, currentPowerName) {
const {engine, pastPhases, phaseIndex} = this.__get_engine_to_display(initialEngine);
let allMessages = [];
const history = engine.message_history || {};
for (const [key, val] of Object.entries(history)) {
if (val && typeof val === 'object') {
if (typeof val.values === 'function') {
const arr = Array.from(val.values());
allMessages.push(...arr);
} else {
allMessages.push(...Object.values(val));
}
} else {
console.log(`[renderTabMessages] Skipping key="${key}" =>`, val);
}
const {
engine: displayedEngine,
pastPhases,
phaseIndex
} = this.__get_engine_to_display(initialEngine);
const messageChannels = displayedEngine.getMessageChannels(currentPowerName, true);
const tabNames = [];
for (let powerName of Object.keys(displayedEngine.powers)) {
if (powerName !== currentPowerName) tabNames.push(powerName);
}
console.log("DEBUG: combined allMessages =>", allMessages);
if (!toDisplay) return null;
tabNames.sort();
tabNames.push('GLOBAL');
const titles = tabNames.map(tabName => (tabName === 'GLOBAL' ? tabName : tabName.substr(0, 3)));
const currentTabId = this.state.tabPastMessages || tabNames[0];
return (
<Tab id={'tab-phase-history'} display={toDisplay}>
<Row>
@ -1183,15 +1182,15 @@ export class ContentGame extends React.Component {
{this.state.historyCurrentOrders && (
<div className={'history-current-orders'}>{this.state.historyCurrentOrders.join(', ')}</div>
)}
{this.renderMapForMessages(engine, this.state.historyShowOrders)}
{this.renderMapForMessages(displayedEngine, this.state.historyShowOrders)}
</div>
<div className={'col-xl'}>
{this.__form_phases(pastPhases, phaseIndex)}
{pastPhases[phaseIndex] === initialEngine.phase ? (
this.renderCurrentMessages(initialEngine, currentPowerName)
) : (
this.renderPastMessages(engine, currentPowerName)
)}
{pastPhases[phaseIndex] === initialEngine.phase ? (
this.renderCurrentMessages(displayedEngine, currentPowerName)
) : (
this.renderPastMessages(displayedEngine, currentPowerName)
)}
</div>
</Row>
{toDisplay && <HotKey keys={['arrowleft']} onKeysCoincide={this.onDecrementPastPhase}/>}
@ -1371,21 +1370,22 @@ export class ContentGame extends React.Component {
</div>
);
// Guard referencing engine.phase_summaries
const phaseSummaries = engine.phase_summaries || {};
const currentPhase = engine.phase || "(unknown phase)";
let summaryText = "";
// Attempt to get a prior phase or fallback to current if prior is not available
const priorPhase = engine.phasePrior;
if (engine.phase_summaries) {
if (priorPhase && engine.phase_summaries.hasOwnProperty(priorPhase)) {
summaryText = engine.phase_summaries[priorPhase];
} else if (engine.phase && engine.phase_summaries.hasOwnProperty(engine.phase)) {
summaryText = engine.phase_summaries[engine.phase];
}
}
const summaryForCurrentPhase =
phaseSummaries.hasOwnProperty(currentPhase) ? phaseSummaries[currentPhase] : "";
// 1) Compute which engine & phase to display
const {
engine: displayedEngine,
pastPhases,
phaseIndex
} = this.__get_engine_to_display(engine);
// 2) Identify the actual current phase in the user's selection
const currentPhase = pastPhases[phaseIndex];
const summaryText =
(engine.phase_summaries && engine.phase_summaries[currentPhase])
? engine.phase_summaries[currentPhase]
: ""; // fallback if no summary
console.log("[DEBUG content_game] Chosen phase:", currentPhase,
"Summary length:", summaryText.length);
// Example: Flatten messages from all known phases
let allMessages = [];
@ -1429,7 +1429,7 @@ export class ContentGame extends React.Component {
)) || ''}
</Tabs>
{/* Debug log before rendering the bottom sheet */}
{console.log("Rendering PhaseSummaryBottomSheet with phase:", currentPhase, "summary length:", summaryForCurrentPhase.length)}
{console.log("Rendering PhaseSummaryBottomSheet with phase:", currentPhase, "summary length:", summaryText.length)}
<PhaseSummaryBottomSheet
visible={this.state.summaryVisible}

View file

@ -16,7 +16,9 @@
// ==============================================================================
import $ from "jquery";
import {STRINGS} from "../../diplomacy/utils/strings";
import {Game} from "../../diplomacy/engine/game";
import {Game, comparablePhase} from "../../diplomacy/engine/game";
import {SortedDict} from "../../diplomacy/utils/sorted_dict";
import { Message } from "../../diplomacy/engine/message";
export function loadGameFromDisk() {
return new Promise((onLoad, onError) => {
@ -40,12 +42,14 @@ export function loadGameFromDisk() {
return;
}
console.log("[loadGameFromDisk] Parsed savedData:", savedData);
const gameObject = {
game_id: `(local) ${savedData.id}`,
map_name: savedData.map,
rules: savedData.rules,
state_history: {},
message_history: {},
message_history: {}, // Initialize as empty
order_history: {},
result_history: {},
phase_summaries: {}
@ -57,36 +61,21 @@ export function loadGameFromDisk() {
const gameState = savedPhase.state;
const phaseOrders = savedPhase.orders || {};
const phaseResults = savedPhase.results || {};
// 1) Fix or parse messages if they're a string
let phaseMessages = savedPhase.messages;
if (typeof phaseMessages === 'string') {
console.warn('[loadGameFromDisk] Phase', savedPhase.name,
'has messages as string. Attempting fallback parse...');
// If it starts with "SortedDict", we can't trivially parse.
// Minimal fallback: set to empty object or parse if you have a custom parser.
if (phaseMessages.startsWith('SortedDict{')) {
phaseMessages = {};
const phaseMessages = {};
// Convert messages array to time_sent-indexed object
if (savedPhase.messages) {
for (let message of savedPhase.messages) {
phaseMessages[message.time_sent] = message;
}
} else if (!phaseMessages) {
phaseMessages = {};
} else {
// Convert array -> object keyed by time_sent
const obj = {};
for (const msg of phaseMessages) {
if (msg && msg.time_sent !== undefined) {
obj[msg.time_sent] = msg;
}
}
phaseMessages = obj;
}
if (!gameState.name) gameState.name = savedPhase.name;
gameObject.state_history[gameState.name] = gameState;
gameObject.message_history[gameState.name] = phaseMessages;
gameObject.order_history[gameState.name] = phaseOrders;
gameObject.result_history[gameState.name] = phaseResults;
gameObject.message_history[gameState.name] = phaseMessages;
// Summaries
if (savedPhase.summary) {
@ -102,22 +91,13 @@ export function loadGameFromDisk() {
const latestGameState = latestPhase.state;
const latestPhaseOrders = latestPhase.orders || {};
const latestPhaseResults = latestPhase.results || {};
let latestPhaseMessages = latestPhase.messages;
if (typeof latestPhaseMessages === 'string') {
console.warn('[loadGameFromDisk] Latest phase has messages as string. Fallback parse...');
if (latestPhaseMessages.startsWith('SortedDict{')) {
latestPhaseMessages = {};
const latestPhaseMessages = {};
// Convert latest phase messages array to time_sent-indexed object
if (latestPhase.messages) {
for (let message of latestPhase.messages) {
latestPhaseMessages[message.time_sent] = message;
}
} else if (!latestPhaseMessages) {
latestPhaseMessages = {};
} else {
const obj = {};
for (const msg of latestPhaseMessages) {
if (msg && msg.time_sent !== undefined) {
obj[msg.time_sent] = msg;
}
}
latestPhaseMessages = obj;
}
if (!latestGameState.name)
@ -132,7 +112,7 @@ export function loadGameFromDisk() {
}
// Final game metadata
gameObject.messages = [];
gameObject.messages = []; // Initialize as an empty array
gameObject.role = STRINGS.OBSERVER_TYPE;
gameObject.status = STRINGS.COMPLETED;
gameObject.timestamp_created = 0;
@ -142,7 +122,7 @@ export function loadGameFromDisk() {
const game = new Game(gameObject);
// Set the current phase to the latest
// Set the current phase to the latest. MUST be done AFTER creating the Game object.
game.setPhaseData({
name: latestGameState.name,
state: latestGameState,
@ -150,6 +130,10 @@ export function loadGameFromDisk() {
messages: latestPhaseMessages
});
// ADDED FOR DEBUGGING
console.log("DEBUG game.messages (after loading):", game.messages);
console.log("DEBUG game.message_history (after loading):", game.message_history);
onLoad(game);
};
reader.readAsText(file);