Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
AITradeIngress.cpp
Go to the documentation of this file.
1#include "AITradeIngress.hpp"
2
4{
5 namespace
6 {
7 [[nodiscard]] bool decodePlayer(
8 std::int64_t value,
9 rules::PlayerNumber& player) noexcept
10 {
11 if (value >= 0 && value < rules::MaxPlayers)
12 {
13 player = static_cast<rules::PlayerNumber>(value);
14 return true;
15 }
17 {
18 player = rules::NobodyPlayer;
19 return true;
20 }
21 return false;
22 }
23
24 void finishTrade(TradeIngressState& state) noexcept
25 {
26 state.currentTrade = {};
27 state.immunities = {};
28 state.counterRuntime = {};
29 state.proposedPlayer = rules::NobodyPlayer;
30 state.lastEditor = rules::NobodyPlayer;
31 state.tradeStarted = false;
32 state.tradeOfferedForAcceptance = false;
33 state.tradeJustRejectedCountered = false;
34 state.playerJustRejectedCountered = rules::NobodyPlayer;
35 state.pendingTradeAcceptPlayers = 0;
36 state.deferredAcceptancePlayers = 0;
37 for (auto& session : state.counterSessions)
38 session.timesCounteredTrade = 0;
39 }
40 }
41
43 {
44 state = {};
45 }
46
49 const actions::Message& message,
50 TradeIngressState& state) noexcept
51 {
52 switch (message.action)
53 {
55 {
57 if (!decodePlayer(message.numberA, proposer) ||
59 return false;
60
61 state.currentTrade = {};
62 state.immunities = {};
63 // RULE resynchronizes a counter using the original proposer,
64 // even when a different player has just received editing permission.
65 const bool pendingEditorRequest = state.counterRuntime.hasPendingProposal &&
66 (state.counterRuntime.player == proposer ||
67 state.counterRuntime.proposedPlayer == proposer) &&
68 (state.counterRuntime.sending.state == SendingTradeState::AskingTradeEdit ||
69 state.counterRuntime.sending.state == SendingTradeState::TradeItems);
71 state.counterRuntime = {};
72 if (!state.tradeStarted)
73 {
74 state.proposedPlayer = proposer;
75 state.lastEditor = proposer;
76 state.tradeOfferedForAcceptance = false;
77 state.tradeJustRejectedCountered = false;
78 state.playerJustRejectedCountered = rules::NobodyPlayer;
79 state.pendingTradeAcceptPlayers = 0;
80 state.deferredAcceptancePlayers = 0;
81 }
82 state.tradeStarted = true;
83 return true;
84 }
85
87 state.proposedPlayer = state.lastEditor;
88 state.tradeOfferedForAcceptance = true;
89 return true;
90
92 {
95 if (!decodePlayer(message.numberA, from) ||
96 !decodePlayer(message.numberB, to) ||
97 message.numberC < 0 ||
98 message.numberC > static_cast<std::int64_t>(
100 return false;
101
102 const auto kind = static_cast<rules::TradeItemKind>(message.numberC);
103 const auto propertySet = static_cast<rules::board::PropertySet>(
104 message.numberE);
105 state.tradeStarted = true;
106 return addTradeItem(
107 state.currentTrade,
108 state.immunities,
109 kind,
110 message.numberD,
111 from,
112 to,
114 }
115
117 {
118 if (state.tradeOfferedForAcceptance)
119 state.tradeOfferedForAcceptance = false;
120
122 if (!decodePlayer(message.numberA, editor) ||
124 return false;
125
126 if (state.lastEditor != editor)
127 {
128 state.proposedPlayer = state.lastEditor;
129 state.lastEditor = editor;
130 }
131 state.tradeStarted = true;
132
133 if (state.counterRuntime.hasPendingProposal &&
134 state.counterRuntime.player == editor &&
135 state.counterRuntime.sending.state == SendingTradeState::TradeItems)
136 {
138 gameState, state.counterRuntime);
139 }
140 return true;
141 }
142
144 {
145 if (message.numberC < 0 || message.numberC >= rules::MaxPlayers)
146 return false;
147
148 const auto player = static_cast<rules::PlayerNumber>(message.numberC);
149 const auto actionValue = message.numberA;
150 if (actionValue == static_cast<std::int64_t>(actions::Type::TradeAccept))
151 {
152 state.pendingTradeAcceptPlayers &= ~(1u << player);
153 if (state.tradeJustRejectedCountered &&
154 state.playerJustRejectedCountered == player)
155 {
156 state.tradeJustRejectedCountered = false;
157 state.playerJustRejectedCountered = rules::NobodyPlayer;
158 }
159 return true;
160 }
161
162 if (actionValue != static_cast<std::int64_t>(actions::Type::StartTradeEditing) &&
163 actionValue != static_cast<std::int64_t>(actions::Type::TradeEditingDone))
164 return false;
165 if (player != state.counterRuntime.player)
166 return false;
167
169 state.counterRuntime, static_cast<actions::Type>(actionValue),
170 message.numberB != 0);
171 if (actionValue == static_cast<std::int64_t>(actions::Type::StartTradeEditing) &&
172 state.tradeJustRejectedCountered &&
173 state.playerJustRejectedCountered == player)
174 {
175 state.tradeJustRejectedCountered = false;
176 state.playerJustRejectedCountered = rules::NobodyPlayer;
177 }
178 return true;
179 }
180
183 return true;
184
185 default:
186 return false;
187 }
188 }
189
192 rules::PlayerNumber player,
193 const TradeProposalList& proposal,
195 TradeIngressState& state) noexcept
196 {
197 if (gameState.numberOfPlayers > rules::MaxPlayers ||
198 player >= gameState.numberOfPlayers ||
199 state.counterRuntime.sending.state != SendingTradeState::Nothing ||
200 !playerInvolvedInTrade(proposal[player]) ||
201 !tradeIsProper(gameState, proposal))
202 return false;
203 const int spot = findFreeTradeSpot(
204 state.turnState[player].timeLastTrade, strategy.maxTrades);
205 if (spot < 0)
206 return false;
208 next.player = player;
209 next.proposedPlayer = player;
210 next.pendingProposal = proposal;
211 next.hasPendingProposal = true;
212 next.sending.sendTradeOffer = true;
213 if (!sendProposedTrade(gameState, player, proposal, false, player, next.sending))
214 return false;
215 state.counterRuntime = next;
216 // Retail records the slot once the send starts, including an edit
217 // request that RULE may subsequently deny.
218 state.turnState[player].timeLastTrade[static_cast<std::size_t>(spot)] =
219 strategy.timeForgetTrade;
220 return true;
221 }
222
224 std::uint8_t numberOfPlayers,
226 const std::array<bool, rules::MaxPlayers>& localAIPlayers,
227 TradeIngressState& state) noexcept
228 {
229 if (numberOfPlayers > rules::MaxPlayers)
230 return;
231 for (rules::PlayerNumber player = 0; player < numberOfPlayers; ++player)
232 {
233 if (!localAIPlayers[player])
234 continue;
235 auto& turn = state.turnState[player];
236 auto& strategy = profiles[player];
237 ++turn.turnsAfterForgettingLast;
238 if (turn.turnsAfterForgettingLast >= strategy.turnsToForgetPropertyTrade)
239 {
240 forgetTradedProperties(state.counterSessions[player].propertyMemory);
241 turn.turnsAfterForgettingLast = 0;
242 }
243 for (auto& timer : turn.timeLastTrade)
244 if (timer > 0)
245 --timer;
246 for (auto& attitude : strategy.playerAttitude)
247 {
248 if (attitude > strategy.neutralAttitude)
249 attitude -= strategy.attitudeTickChange;
250 else
251 attitude += strategy.attitudeTickChange;
252 }
253 }
254 }
255
256 std::array<double, rules::MaxPlayers> tradeResponseAttitudeChanges(
260 bool accepted,
263 const profile::ConfigContext& context) noexcept
264 {
265 std::array<double, rules::MaxPlayers> changes{};
266 if (gameState.numberOfPlayers > rules::MaxPlayers ||
268 respondingPlayer >= gameState.numberOfPlayers)
269 return changes;
270
272 {
274 observer < gameState.numberOfPlayers; ++observer)
275 {
276 if (!context.localAIPlayer[observer])
277 continue;
278 const auto& strategy = profiles[observer];
279 double evaluation = decision::evaluateTrade(
282 evaluation *= strategy.attitudeChangeTradeObserving;
283 if (evaluation < 0)
284 {
285 if (evaluation < -strategy.attitudeLostForRejectedTrade)
286 evaluation = -strategy.attitudeLostForRejectedTrade;
287 }
288 else if (evaluation > strategy.attitudeLostForRejectedTrade)
289 evaluation = strategy.attitudeLostForRejectedTrade;
290
291 if ((accepted && evaluation < 0) ||
292 (!accepted && evaluation > 0))
293 changes[observer] -= evaluation;
294 }
295 }
296 // Retail applies this separately, even with futures/immunities.
297 // It clamps the observed delta, not the resulting player attitude.
298 if (context.localAIPlayer[proposer])
300 ? profiles[proposer].attitudeLostForRejectedTrade
301 : -profiles[proposer].attitudeLostForRejectedTrade;
302 return changes;
303 }
304
307 rules::PlayerNumber player,
309 const TradeIngressState& state) noexcept
310 {
311 TradeAcceptanceResult result{};
312 if (gameState.numberOfPlayers == 0 ||
313 gameState.numberOfPlayers > rules::MaxPlayers ||
314 player >= gameState.numberOfPlayers ||
315 state.proposedPlayer >= gameState.numberOfPlayers)
316 return result;
317
318 if (state.tradeJustRejectedCountered)
319 {
321 return result;
322 }
323
324 const auto playerBit = 1u << player;
325 if ((state.pendingTradeAcceptPlayers & playerBit) != 0 ||
326 (state.counterRuntime.player == player &&
327 state.counterRuntime.sending.state != SendingTradeState::Nothing))
328 {
329 result.status = TradeAcceptanceStatus::Busy;
330 return result;
331 }
332
333 if (!playerInvolvedInTrade(state.currentTrade[player]))
334 {
336 return result;
337 }
338
339 if (playerHasFutureOrImmunity(player, state.immunities))
340 {
342 return result;
343 }
344
345 result.fedUp = propertiesAlreadyTraded(
346 state.counterSessions[player].propertyMemory,
347 state.proposedPlayer, state.currentTrade[player],
348 config.numberTimesAllowPropertyTrade) != 0;
349 result.evaluation = decision::evaluateTrade(
350 gameState, player, player, state.currentTrade, config.evaluation);
351 const double threshold = result.fedUp ?
352 config.minEvaluationIfFedUp : config.minEvaluationThreshold;
353 if (result.evaluation >= threshold)
354 {
355 result.status = TradeAcceptanceStatus::Accept;
356 return result;
357 }
358
359 result.status = result.fedUp ?
362 return result;
363 }
364
367 rules::PlayerNumber player,
368 bool tradeAccept,
369 double counterRoll,
370 int pendingActions,
371 bool auctionOn,
374 TradeIngressState& state) noexcept
375 {
376 CounterTradeRunResult result{};
377 if (player >= gameState.numberOfPlayers ||
378 state.proposedPlayer >= gameState.numberOfPlayers)
379 return result;
380
382 inputs.proposedPlayer = state.proposedPlayer;
383 inputs.pendingActions = pendingActions;
384 inputs.tradeAccept = tradeAccept;
385 inputs.counterRoll = counterRoll;
386 inputs.auctionOn = auctionOn;
387 inputs.playerSendingTrade =
388 state.counterRuntime.sending.state != SendingTradeState::Nothing;
389
390 return counterProposeTrade(
391 gameState,
392 player,
393 state.currentTrade,
394 inputs,
397 state.counterSessions[player],
398 state.counterRuntime,
399 state.immunities);
400 }
401}
unsigned state
unsigned index
Kind kind
std::uint16_t value
Definition Timers.cpp:16
double evaluateTrade(const rules::GameState &state, rules::PlayerNumber player, rules::PlayerNumber strategyPlayer, const ai::trade::TradeProposalList &proposals, const TradeEvaluationConfig &config) noexcept
std::array< Profile, rules::MaxPlayers > ProfileSet
Definition AIProfile.hpp:78
decision::TradeEvaluationConfig makeTradeEvaluationConfig(const ProfileSet &profiles, rules::PlayerNumber strategyPlayer, const ConfigContext &context) noexcept
int findFreeTradeSpot(std::span< const std::int64_t > timeLastTrade, std::size_t maxTrades) noexcept
void forgetTradedProperties(PropertyTradeMemory &memory) noexcept
bool sendProposedTrade(const rules::GameState &state, rules::PlayerNumber player, const TradeProposalList &proposals, bool tradeAccept, rules::PlayerNumber proposedPlayer, TradeSendRuntimeState &runtime) noexcept
bool tradeIsProper(const rules::GameState &state, const TradeProposalList &proposals, std::span< const FutureImmunityRecord > immunities) noexcept
rules::board::PropertySet propertiesAlreadyTraded(const PropertyTradeMemory &memory, rules::PlayerNumber proposedPlayer, const TradeProposalRecord &proposal, int dangerLevel) noexcept
void resetTradeIngress(TradeIngressState &state) noexcept
bool playerInvolvedInTrade(const TradeProposalRecord &proposal) noexcept
std::array< double, rules::MaxPlayers > tradeResponseAttitudeChanges(const rules::GameState &gameState, rules::PlayerNumber proposer, rules::PlayerNumber respondingPlayer, bool accepted, const TradeIngressState &state, const profile::ProfileSet &profiles, const profile::ConfigContext &context) noexcept
bool addTradeItem(TradeProposalList &proposals, FutureImmunityList &immunities, rules::TradeItemKind kind, std::int64_t amount, rules::PlayerNumber fromPlayer, rules::PlayerNumber toPlayer, rules::board::PropertySet propertySet) noexcept
void processCounterProposalActionCompleted(CounterTradeRuntimeState &runtime, actions::Type action, bool success) noexcept
bool continueCounterProposalTradeSend(const rules::GameState &state, CounterTradeRuntimeState &runtime) noexcept
bool sendProactiveTrade(const rules::GameState &gameState, rules::PlayerNumber player, const TradeProposalList &proposal, const profile::Profile &strategy, TradeIngressState &state) noexcept
void advanceTradeTurn(std::uint8_t numberOfPlayers, profile::ProfileSet &profiles, const std::array< bool, rules::MaxPlayers > &localAIPlayers, TradeIngressState &state) noexcept
CounterTradeRunResult counterProposeCurrentTrade(const rules::GameState &gameState, rules::PlayerNumber player, bool tradeAccept, double counterRoll, int pendingActions, bool auctionOn, const decision::CounterProposalPreflightConfig &preflightConfig, const decision::CounterProposalBalanceConfig &balanceConfig, TradeIngressState &state) noexcept
bool playerHasFutureOrImmunity(rules::PlayerNumber player, std::span< const FutureImmunityRecord > immunities) noexcept
std::array< TradeProposalRecord, rules::MaxPlayers > TradeProposalList
TradeAcceptanceResult evaluateCurrentTradeAcceptance(const rules::GameState &gameState, rules::PlayerNumber player, const TradeAcceptanceConfig &config, const TradeIngressState &state) noexcept
CounterTradeRunResult counterProposeTrade(const rules::GameState &state, rules::PlayerNumber player, const TradeProposalList &currentTrade, const decision::CounterProposalPreflightInputs &inputs, const decision::CounterProposalPreflightConfig &preflightConfig, const decision::CounterProposalBalanceConfig &balanceConfig, decision::CounterProposalSession &session, CounterTradeRuntimeState &runtime, std::span< const FutureImmunityRecord > immunities) noexcept
bool processTradeRuleMessage(const rules::GameState &gameState, const actions::Message &message, TradeIngressState &state) noexcept
std::uint32_t PropertySet
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber NobodyPlayer
Definition RuleTypes.hpp:16