mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-30 17:40:47 +00:00
Messages AND phase summaries working with last results format
This commit is contained in:
parent
16e3b45264
commit
3af8527c62
23 changed files with 165 additions and 2896 deletions
|
|
@ -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: ""
|
||||
};
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue