Skip to content

Commit 4b27e79

Browse files
authored
Standardize using card orders
Also fixed various bugs introduced in the last commit.
1 parent a37963d commit 4b27e79

30 files changed

+240
-277
lines changed

src/action-handler.js

+7-11
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ export function handle_action(action) {
2525
if (action.type === 'clue' && action.giver === state.ourPlayerIndex)
2626
this.handHistory[state.turn_count] = Utils.objClone(state.ourHand);
2727

28-
const update_func = (suitIndex, rank) => (draft) => {
29-
draft.suitIndex = suitIndex;
30-
draft.rank = rank;
31-
};
32-
3328
switch(action.type) {
3429
case 'clue': {
3530
// {type: 'clue', clue: { type: 1, value: 1 }, giver: 0, list: [ 8, 9 ], target: 1, turn: 0}
@@ -61,8 +56,8 @@ export function handle_action(action) {
6156
const card = state.deck[order];
6257

6358
if (card.identity() === undefined)
64-
state.deck = state.deck.with(order, produce(card, update_func(suitIndex, rank)));
65-
this.players[playerIndex].updateThoughts(order, update_func(suitIndex, rank));
59+
state.deck = state.deck.with(order, produce(card, Utils.assignId({ suitIndex, rank })));
60+
this.players[playerIndex].updateThoughts(order, Utils.assignId({ suitIndex, rank }));
6661

6762
logger.highlight('yellowb', `Turn ${state.turn_count}: ${logAction(action)}`);
6863

@@ -138,8 +133,8 @@ export function handle_action(action) {
138133
const card = state.deck[order];
139134

140135
if (card.identity() === undefined)
141-
state.deck = state.deck.with(order, produce(card, update_func(suitIndex, rank)));
142-
this.players[playerIndex].updateThoughts(order, update_func(suitIndex, rank));
136+
state.deck = state.deck.with(order, produce(card, Utils.assignId({ suitIndex, rank })));
137+
this.players[playerIndex].updateThoughts(order, Utils.assignId({ suitIndex, rank }));
143138

144139
logger.highlight('yellowb', `Turn ${state.turn_count}: ${logAction(action)}`);
145140

@@ -174,8 +169,9 @@ export function handle_action(action) {
174169
});
175170

176171
if (!infer && identities.length === 1) {
177-
this.me.updateThoughts(order, update_func(identities[0].suitIndex, identities[0].rank));
178-
state.deck = state.deck.with(order, produce(state.deck[order], update_func(identities[0].suitIndex, identities[0].rank)));
172+
const { suitIndex, rank } = identities[0];
173+
this.me.updateThoughts(order, Utils.assignId({ suitIndex, rank }));
174+
state.deck = state.deck.with(order, produce(state.deck[order], Utils.assignId({ suitIndex, rank })));
179175
}
180176
team_elim(this);
181177
break;

src/basics.js

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ActualCard, Card } from './basics/Card.js';
22
import { cardCount, find_possibilities } from './variants.js';
3+
import * as Utils from './tools/util.js';
34
import { produce } from './StateProxy.js';
45

56
/**
@@ -78,17 +79,15 @@ export function onDiscard(game, action) {
7879

7980
if (suitIndex !== -1 && rank !== -1) {
8081
state.discard_stacks[suitIndex][rank - 1]++;
81-
state.deck[order] = produce(state.deck[order], (draft) => {
82-
draft.suitIndex = suitIndex;
83-
draft.rank = rank;
84-
});
82+
state.deck[order] = produce(state.deck[order], Utils.assignId({ suitIndex, rank }));
8583

8684
for (const player of game.allPlayers) {
85+
const { possible, inferred } = player.thoughts[order];
8786
player.updateThoughts(order, (draft) => {
8887
draft.suitIndex = suitIndex;
8988
draft.rank = rank;
90-
draft.possible = player.thoughts[order].possible.intersect(identity);
91-
draft.inferred = player.thoughts[order].inferred.intersect(identity);
89+
draft.possible = possible.intersect(identity);
90+
draft.inferred = inferred.intersect(identity);
9291
});
9392

9493
player.card_elim(state);
@@ -158,10 +157,7 @@ export function onPlay(game, action) {
158157

159158
if (suitIndex !== undefined && rank !== undefined) {
160159
state.play_stacks[suitIndex] = rank;
161-
state.deck[order] = produce(state.deck[order], (draft) => {
162-
draft.suitIndex = suitIndex;
163-
draft.rank = rank;
164-
});
160+
state.deck[order] = produce(state.deck[order], Utils.assignId({ suitIndex, rank }));
165161

166162
for (const player of game.allPlayers) {
167163
const { possible, inferred } = player.thoughts[order];

src/basics/Player.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,13 @@ export class Player {
157157
card.possibilities.some(p => state.isPlayable(p)) && // Exclude empty case
158158
((options?.assume ?? true) || !this.waiting_connections.some((wc, i1) =>
159159
// Unplayable target of possible waiting connection
160-
(wc.focused_card.order === o && !state.isPlayable(wc.inference) && card.possible.has(wc.inference)) ||
160+
(wc.focus === o && !state.isPlayable(wc.inference) && card.possible.has(wc.inference)) ||
161161
wc.connections.some((conn, ci) => ci >= wc.conn_index && conn.order === o && (
162162
// Unplayable connecting card
163163
conn.identities.some(i => !state.isPlayable(i) && card.possible.has(i)) ||
164164
// A different connection on the same focus doesn't use this connecting card
165165
this.waiting_connections.some((wc2, i2) =>
166-
i1 !== i2 && wc2.focused_card.order === wc.focused_card.order && wc2.connections.every(conn2 => conn2.order !== o))))
166+
i1 !== i2 && wc2.focus === wc.focus && wc2.connections.every(conn2 => conn2.order !== o))))
167167
)) &&
168168
state.hasConsistentInferences(card);
169169
});
@@ -305,7 +305,7 @@ export class Player {
305305
continue;
306306

307307
const fake_wcs = this.waiting_connections.filter(wc =>
308-
wc.focused_card.order === order && !state.deck[wc.focused_card.order].matches(wc.inference, { assume: true }));
308+
wc.focus === order && !state.deck[wc.focus].matches(wc.inference, { assume: true }));
309309

310310
// Ignore all waiting connections that will be proven wrong
311311
const diff = produce(card, (draft) => { draft.inferred = card.inferred.subtract(fake_wcs.flatMap(wc => wc.inference)); });
@@ -358,7 +358,7 @@ export class Player {
358358

359359
if (rank !== hypo_stacks[suitIndex] + 1) {
360360
// e.g. a duplicated 1 before any 1s have played will have all bad possibilities eliminated by good touch
361-
logger.warn(`tried to add new playable card ${logCard(id)} ${card.order}, hypo stacks at ${hypo_stacks[suitIndex]}`);
361+
logger.warn(`tried to add new playable card ${logCard(id)} ${order}, hypo stacks at ${hypo_stacks[suitIndex]}`);
362362
continue;
363363
}
364364

src/basics/clue-result.js

+11-16
Original file line numberDiff line numberDiff line change
@@ -51,28 +51,23 @@ export function bad_touch_result(game, hypo_game, hypo_player, giver, target) {
5151
const dupe_scores = game.players.map((player, pi) => {
5252
if (pi == target)
5353
return Infinity;
54-
let possible_dupe = 0;
54+
5555
// Check if the giver may have a touched duplicate card.
56-
// TODO: Should we consider chop moved cards?
57-
for (const order of state.hands[target]) {
56+
return state.hands[target].reduce((acc, order) => {
5857
const card = state.deck[order];
59-
// Ignore cards that aren't newly clued
6058
if (!card.newly_clued)
61-
continue;
59+
return acc;
6260

6361
const identity = card.identity();
6462
// TODO: Should we cluing cards where receiver knows they are duplicated?
6563
if (!identity || hypo_game.state.isBasicTrash(identity))
66-
continue;
67-
for (const giverOrder of state.hands[pi]) {
68-
// Allow known duplication since we can discard to resolve it.
69-
if (state.deck[giverOrder].clued &&
70-
player.thoughts[giverOrder].inferred.length > 1 &&
71-
player.thoughts[giverOrder].inferred.has(identity))
72-
possible_dupe++;
73-
}
74-
}
75-
return possible_dupe;
64+
return acc;
65+
66+
// Allow known duplication since we can discard to resolve it.
67+
acc += state.hands[pi].filter(o => ((c = player.thoughts[o]) =>
68+
c.clued && c.inferred.length > 1 && c.inferred.has(identity))()).length;
69+
return acc;
70+
}, 0);
7671
});
7772

7873
const min_dupe = Math.min(...dupe_scores);
@@ -102,7 +97,7 @@ export function bad_touch_result(game, hypo_game, hypo_player, giver, target) {
10297
const thoughts = me.thoughts[o];
10398

10499
// We need to check old thoughts, since the clue may cause good touch elim that removes earlier notes
105-
return old_thoughts.matches(card, { infer: true }) && (old_thoughts.touched || thoughts.touched) && o !== order;
100+
return o !== order && old_thoughts.matches(card, { infer: true }) && (old_thoughts.touched || thoughts.touched);
106101
}));
107102

108103
if (duplicates.length > 0 && !(duplicates.every(o => state.deck[o].newly_clued) && order < Math.min(...duplicates)))

src/basics/helper.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function checkFix(game, oldThoughts, clueAction) {
8787
// There is a waiting connection that depends on this card
8888
if (reset_order !== undefined) {
8989
const reset_card = common.thoughts[reset_order];
90-
const new_game = game.rewind(reset_card.drawn_index, [{ type: 'identify', order: reset_card.order, playerIndex: target, identities: [reset_card.possible.array[0].raw()] }]);
90+
const new_game = game.rewind(reset_card.drawn_index, [{ type: 'identify', order: reset_order, playerIndex: target, identities: [reset_card.possible.array[0].raw()] }]);
9191
Object.assign(game, new_game);
9292
return { rewinded: true };
9393
}

src/constants.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const MAX_H_LEVEL = 11;
2-
export const BOT_VERSION = '1.6.8';
2+
export const BOT_VERSION = '1.6.9';
33

44
export const ACTION = /** @type {const} */ ({
55
PLAY: 0,

src/conventions/h-group/action-helper.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function order_1s(state, player, orders, options = { no_filter: false })
6565
const unknown_1s = options.no_filter ? orders : orders.filter(o => ((card = state.deck[o]) =>
6666
card.clues.length > 0 &&
6767
card.clues.every(clue => clue.type === CLUE.RANK && clue.value === 1) &&
68-
player.thoughts[card.order].possible.every(p => p.rank === 1))());
68+
player.thoughts[o].possible.every(p => p.rank === 1))());
6969

7070
return unknown_1s.sort((order1, order2) => {
7171
const [c1_start, c2_start] = [order1, order2].map(o => state.inStartingHand(o));
@@ -81,7 +81,7 @@ export function order_1s(state, player, orders, options = { no_filter: false })
8181
return 1;
8282

8383
if (c1.chop_when_first_clued && c2.chop_when_first_clued)
84-
return c2.order - c1.order;
84+
return order2 - order1;
8585

8686
// c1 is chop focus
8787
if (c1.chop_when_first_clued)

src/conventions/h-group/clue-finder/clue-finder.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function save_clue_value(game, hypo_game, save_clue, all_clues) {
3939

4040
if (chop_moved.length === 0) {
4141
// Chop can be clued later
42-
if (state.hands.some(hand => hand.some(o => state.deck[o].matches(old_chop_card) && o !== old_chop)))
42+
if (state.hands.some(hand => hand.some(o => o !== old_chop && state.deck[o].matches(old_chop_card))))
4343
return -10;
4444

4545
return Math.max(find_clue_value(result), state.isCritical(old_chop_card) ? 0.1 : -Infinity);
@@ -129,8 +129,8 @@ export function find_clues(game, options = {}) {
129129
const focused_card = state.deck[focus];
130130

131131
const in_finesse = common.waiting_connections.some(w_conn => {
132-
const { focused_card: wc_focus, inference } = w_conn;
133-
const matches = player.thoughts[wc_focus.order].matches(inference, { assume: true });
132+
const { focus: wc_focus, inference } = w_conn;
133+
const matches = player.thoughts[wc_focus].matches(inference, { assume: true });
134134

135135
return matches && focused_card.playedBefore(inference, { equal: true });
136136
});
@@ -152,8 +152,8 @@ export function find_clues(game, options = {}) {
152152
continue;
153153

154154
const stomped_finesse = common.waiting_connections.some(w_conn => {
155-
const { focused_card: wc_focus, connections, conn_index, inference } = w_conn;
156-
const matches = player.thoughts[wc_focus.order].matches(inference, { assume: true });
155+
const { focus: wc_focus, connections, conn_index, inference } = w_conn;
156+
const matches = player.thoughts[wc_focus].matches(inference, { assume: true });
157157

158158
return matches && list.some(o => {
159159
const card = hypo_game.common.thoughts[o];
@@ -218,7 +218,7 @@ export function find_clues(game, options = {}) {
218218
break;
219219

220220
case CLUE_INTERP.CM_TEMPO: {
221-
const { tempo, valuable } = valuable_tempo_clue(game, clue, clue.result.playables, focused_card);
221+
const { tempo, valuable } = valuable_tempo_clue(game, clue, clue.result.playables, focus);
222222

223223
if (!safe) {
224224
logger.highlight('yellow', 'unsafe!');

src/conventions/h-group/clue-finder/determine-clue.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export function evaluate_clue(game, action, clue, target, target_card) {
7676
// The focused card must not have been reset and must match inferences
7777
if (order === target_card.order) {
7878
if (card.reset && !game.common.thoughts[order].reset) {
79-
reason = `card ${logCard(state.deck[card.order])} ${card.order} lost all inferences and was reset`;
79+
reason = `card ${logCard(state.deck[order])} ${order} lost all inferences and was reset`;
8080
break;
8181
}
8282

@@ -103,7 +103,7 @@ export function evaluate_clue(game, action, clue, target, target_card) {
103103

104104
// For non-focused cards:
105105
if (card.reset) {
106-
reason = `card ${logCard(state.deck[card.order])} ${card.order} lost all inferences and was reset`;
106+
reason = `card ${logCard(state.deck[order])} ${order} lost all inferences and was reset`;
107107
break;
108108
}
109109

@@ -117,7 +117,7 @@ export function evaluate_clue(game, action, clue, target, target_card) {
117117
card.inferred.every(i => i.rank <= hypo_game.common.hypo_stacks[i.suitIndex] + 1);
118118

119119
if (looks_playable && !card.inferred.has(visible_card)) {
120-
reason = `card ${logCard(visible_card)} ${visible_card.order} looks incorrectly playable with inferences [${card.inferred.map(logCard).join(',')}]`;
120+
reason = `card ${logCard(visible_card)} ${order} looks incorrectly playable with inferences [${card.inferred.map(logCard).join(',')}]`;
121121
break;
122122
}
123123
}

src/conventions/h-group/clue-interpretation/connecting-cards.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function find_known_connecting(game, giver, identity, ignoreOrders = [],
6363
inferences = old_card.inferred.subtract(old_card.inferred.filter(inf => inf.playedBefore(identity)));
6464

6565
// If a waiting connection will reveal this card, assume it will be known in time.
66-
const connection = common.waiting_connections.find(conn => !conn.symmetric && conn.focused_card.order == order && conn.target !== state.ourPlayerIndex);
66+
const connection = common.waiting_connections.find(conn => !conn.symmetric && conn.focus == order && conn.target !== state.ourPlayerIndex);
6767
if (connection !== undefined)
6868
inferences = inferences.intersect(connection.inference);
6969
}
@@ -186,7 +186,7 @@ function find_unknown_connecting(game, action, reacting, identity, connected = [
186186

187187
return card.touched && !card.newly_clued &&
188188
(state.deck[order].identity() !== undefined || common.dependentConnections(order).every(wc =>
189-
!wc.symmetric && wc.focused_card.matches(wc.inference, { assume: true })));
189+
!wc.symmetric && state.deck[wc.focus].matches(wc.inference, { assume: true })));
190190
};
191191

192192
if (state.hands.some((hand, index) => index !== giver && hand.some(o => order_touched(o) && state.deck[o].matches(finesse_card)))) {

src/conventions/h-group/clue-interpretation/connection-helper.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ export function valid_bluff(game, action, identity, reacting, connected) {
7373
* @param {State} state
7474
* @param {SymFocusPossibility[]} sym_possibilities
7575
* @param {FocusPossibility[]} existing_connections
76-
* @param {ActualCard} focused_card
76+
* @param {number} focus
7777
* @param {number} giver
7878
* @param {number} target
7979
* @returns {WaitingConnection[]}
8080
*/
81-
export function generate_symmetric_connections(state, sym_possibilities, existing_connections, focused_card, giver, target) {
81+
export function generate_symmetric_connections(state, sym_possibilities, existing_connections, focus, giver, target) {
8282
const symmetric_connections = [];
8383

8484
for (const sym of sym_possibilities) {
@@ -95,7 +95,7 @@ export function generate_symmetric_connections(state, sym_possibilities, existin
9595
symmetric_connections.push({
9696
connections,
9797
conn_index: 0,
98-
focused_card,
98+
focus,
9999
inference: { suitIndex, rank },
100100
giver,
101101
target,

src/conventions/h-group/clue-interpretation/interpret-clue.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ function resolve_clue(game, old_game, action, inf_possibilities, focused_card) {
123123
common.waiting_connections.push({
124124
connections,
125125
conn_index: 0,
126-
focused_card,
126+
focus,
127127
inference,
128128
giver,
129129
target,
@@ -143,7 +143,7 @@ function resolve_clue(game, old_game, action, inf_possibilities, focused_card) {
143143
));
144144
const ownBlindPlays = correct_match?.connections.filter(conn => conn.type === 'finesse' && conn.reacting === state.ourPlayerIndex).length || 0;
145145
const symmetric_fps = find_symmetric_connections(game, old_game, action, inf_possibilities, selfRanks, ownBlindPlays);
146-
const symmetric_connections = generate_symmetric_connections(state, symmetric_fps, inf_possibilities, focused_card, giver, target);
146+
const symmetric_connections = generate_symmetric_connections(state, symmetric_fps, inf_possibilities, focus, giver, target);
147147

148148
if (correct_match?.connections[0]?.bluff) {
149149
const { reacting } = correct_match.connections[0];
@@ -363,7 +363,7 @@ export function interpret_clue(game, action) {
363363
const to_remove = new Set();
364364

365365
for (const [i, waiting_connection] of Object.entries(common.waiting_connections)) {
366-
const { connections, conn_index, action_index, focused_card: wc_focus, inference, target: wc_target } = waiting_connection;
366+
const { connections, conn_index, action_index, focus: wc_focus, inference, target: wc_target } = waiting_connection;
367367
const focus_id = state.deck[focus].identity();
368368

369369
if (focus_id !== undefined && list.length === 1) {
@@ -387,17 +387,17 @@ export function interpret_clue(game, action) {
387387
if (impossible_conn !== undefined)
388388
logger.warn(`connection [${connections.map(logConnection)}] depends on revealed card having identities ${impossible_conn.identities.map(logCard)}`);
389389

390-
else if (!common.thoughts[wc_focus.order].possible.has(inference))
390+
else if (!common.thoughts[wc_focus].possible.has(inference))
391391
logger.warn(`connection [${connections.map(logConnection)}] depends on focused card having identity ${logCard(inference)}`);
392392

393393
else
394394
continue;
395395

396-
const rewind_card = state.deck[impossible_conn?.order] ?? wc_focus;
397-
const rewind_identity = common.thoughts[rewind_card.order]?.identity();
396+
const rewind_order = impossible_conn?.order ?? wc_focus;
397+
const rewind_identity = common.thoughts[rewind_order]?.identity();
398398

399-
if (rewind_identity !== undefined && !common.thoughts[rewind_card.order].rewinded && wc_target === state.ourPlayerIndex && state.ourHand.includes(rewind_card.order)) {
400-
const new_game = game.rewind(rewind_card.drawn_index, [{ type: 'identify', order: rewind_card.order, playerIndex: state.ourPlayerIndex, identities: [rewind_identity.raw()] }]);
399+
if (rewind_identity !== undefined && !common.thoughts[rewind_order].rewinded && wc_target === state.ourPlayerIndex && state.ourHand.includes(rewind_order)) {
400+
const new_game = game.rewind(state.deck[rewind_order].drawn_index, [{ type: 'identify', order: rewind_order, playerIndex: state.ourPlayerIndex, identities: [rewind_identity.raw()] }]);
401401
if (new_game) {
402402
Object.assign(game, new_game);
403403
return;
@@ -680,7 +680,7 @@ export function interpret_clue(game, action) {
680680
}
681681

682682
try {
683-
logger.debug('hand state after clue', logHand(state.hands[target].map(o => state.deck[o])));
683+
logger.debug('hand state after clue', logHand(state.hands[target]));
684684
}
685685
catch (err) {
686686
logger.info('Failed to debug hand state', err, state.hands[target], Utils.globals.game.common.thoughts.map(c => c.order));

src/conventions/h-group/clue-interpretation/interpret-cm.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function interpret_tcm(game, target, focus_order) {
4040
for (let i = oldest_trash_index + 1; i < state.hands[target].length; i++) {
4141
const order = state.hands[target][i];
4242

43-
if (!state.deck[state.hands[target][i]].clued && !common.thoughts[order].chop_moved) {
43+
if (!state.deck[order].clued && !common.thoughts[order].chop_moved) {
4444
const { inferred } = common.thoughts[order];
4545
common.updateThoughts(order, (draft) => {
4646
// Remove all commonly trash identities

0 commit comments

Comments
 (0)