@@ -15,6 +15,7 @@ import { logCard } from '../../tools/log.js';
15
15
* @typedef {import('../../types.js').Action } Action
16
16
* @typedef {import('../../types.js').PerformAction } PerformAction
17
17
* @typedef {Omit<PerformAction, 'tableID'> & {playerIndex: number} } ModPerformAction
18
+ * @typedef {{ id: Identity, missing: number, all: boolean }[] } RemainingSet
18
19
*/
19
20
20
21
export const simpler_cache = new Map ( ) ;
@@ -42,6 +43,7 @@ function find_must_plays(state, hand) {
42
43
if ( id . suitIndex === - 1 || state . isBasicTrash ( id ) )
43
44
return acc ;
44
45
46
+ // All remaining copies of this identity are in the hand
45
47
if ( cardCount ( state . variant , id ) - state . baseCount ( id ) === group . length )
46
48
acc . push ( id ) ;
47
49
@@ -201,70 +203,106 @@ function get_playables(state, playerTurn) {
201
203
*
202
204
* @param {State } state
203
205
* @param {number } playerTurn
206
+ * @param {RemainingSet } remaining_ids
207
+ * @param {number } depth
204
208
* @returns {boolean }
205
209
*/
206
- export function winnable_simpler ( state , playerTurn ) {
207
- if ( state . score === state . maxScore )
210
+ export function winnable_simpler ( state , playerTurn , remaining_ids , depth = 0 ) {
211
+ if ( state . score === state . maxScore ) {
212
+ // logger.info(`${Array.from({ length: depth }, _ => ' ').join('')}won!!`);
208
213
return true ;
214
+ }
209
215
210
- if ( unwinnable_state ( state , playerTurn ) )
216
+ if ( unwinnable_state ( state , playerTurn ) ) {
217
+ // logger.info(`${Array.from({ length: depth }, _ => ' ').join('')}unwinnable state`);
211
218
return false ;
219
+ }
212
220
213
- const cached_result = simpler_cache . get ( hash_state ( state ) + `,${ playerTurn } ` ) ;
221
+ const hash = `${ hash_state ( state ) } ,${ playerTurn } ,${ JSON . stringify ( remaining_ids . filter ( r => logCard ( r . id ) !== 'xx' ) ) } ` ;
222
+
223
+ const cached_result = simpler_cache . get ( hash ) ;
214
224
if ( cached_result !== undefined )
215
225
return cached_result ;
216
226
217
- for ( const order of get_playables ( state , playerTurn ) ) {
218
- const action = { type : ACTION . PLAY , target : order , playerIndex : playerTurn } ;
227
+ /** @type { ModPerformAction[] } */
228
+ const possible_actions = [ ] ;
219
229
220
- if ( predict_winnable2 ( state , playerTurn , action ) )
221
- return true ;
222
- }
230
+ for ( const order of get_playables ( state , playerTurn ) )
231
+ possible_actions . push ( { type : ACTION . PLAY , target : order , playerIndex : playerTurn } ) ;
223
232
224
- if ( state . clue_tokens > 0 ) {
225
- const action = { type : ACTION . RANK , target : - 1 , value : - 1 , playerIndex : playerTurn } ;
226
-
227
- if ( predict_winnable2 ( state , playerTurn , action ) )
228
- return true ;
229
- }
233
+ if ( state . clue_tokens > 0 )
234
+ possible_actions . push ( { type : ACTION . RANK , target : - 1 , value : - 1 , playerIndex : playerTurn } ) ;
230
235
231
236
const discardable = state . hands [ playerTurn ] . find ( o => ( ( c = state . deck [ o ] ) => c . identity ( ) === undefined || state . isBasicTrash ( c ) ) ( ) ) ;
232
237
233
- if ( state . pace >= 0 && discardable !== undefined ) {
234
- const action = { type : ACTION . DISCARD , target : discardable , playerIndex : playerTurn } ;
238
+ if ( state . pace >= 0 && discardable !== undefined )
239
+ possible_actions . push ( { type : ACTION . DISCARD , target : discardable , playerIndex : playerTurn } ) ;
235
240
236
- if ( predict_winnable2 ( state , playerTurn , action ) )
237
- return true ;
238
- }
241
+ const winnable = possible_actions . some ( action => winnable_if ( state , playerTurn , action , remaining_ids , depth ) . winnable ) ;
242
+ simpler_cache . set ( hash , winnable ) ;
239
243
240
- simpler_cache . set ( hash_state ( state ) + `,${ playerTurn } ` , false ) ;
241
- return false ;
244
+ return winnable ;
242
245
}
243
246
244
247
/**
245
- * @param {State } _state
246
- * @param {number } _playerTurn
247
- * @param {ModPerformAction } _action
248
+ * @param {RemainingSet } remaining
249
+ * @param {Identity } id
248
250
*/
249
- export function predict_winnable ( _state , _playerTurn , _action ) {
250
- return true ;
251
- // return winnable_simpler(advance_state(state, action), state.nextPlayerIndex(playerTurn));
251
+ export function remove_remaining ( remaining , id ) {
252
+ const index = remaining . findIndex ( r => r . id . suitIndex === id . suitIndex && r . id . rank === id . rank ) ;
253
+ const { missing, all } = remaining [ index ] ;
254
+
255
+ if ( missing === 1 )
256
+ return remaining . toSpliced ( index , 1 ) ;
257
+ else
258
+ return remaining . with ( index , { id, missing : missing - 1 , all } ) ;
252
259
}
253
260
254
261
/**
255
262
* @param {State } state
256
263
* @param {number } playerTurn
257
264
* @param {ModPerformAction } action
265
+ * @param {RemainingSet } remaining_ids
266
+ * @param {number } [depth]
258
267
*/
259
- export function predict_winnable2 ( state , playerTurn , action ) {
260
- return winnable_simpler ( advance_state ( state , action ) , state . nextPlayerIndex ( playerTurn ) ) ;
268
+ export function winnable_if ( state , playerTurn , action , remaining_ids , depth = 0 ) {
269
+ if ( action . type === ACTION . RANK || action . type === ACTION . COLOUR || state . cardsLeft === 0 ) {
270
+ const newState = advance_state ( state , action , undefined ) ;
271
+ // logger.info(`${Array.from({ length: depth }, _ => ' ').join('')}checking if winnable after ${logObjectiveAction(state, action)} {`);
272
+ const winnable = winnable_simpler ( newState , state . nextPlayerIndex ( playerTurn ) , remaining_ids , depth + 1 ) ;
273
+
274
+ // logger.info(`${Array.from({ length: depth }, _ => ' ').join('')} } ${winnable}`);
275
+ return { winnable } ;
276
+ }
277
+
278
+ /** @type {Identity[] } */
279
+ const winnable_draws = [ ] ;
280
+
281
+ // logger.info(`${Array.from({ length: depth }, _ => ' ').join('')}remaining ids ${JSON.stringify(remaining_ids.map(r => ({...r, id: logCard(r.id) })))}`);
282
+
283
+ for ( const { id } of remaining_ids ) {
284
+ const draw = Object . freeze ( new ActualCard ( id . suitIndex , id . rank , state . cardOrder + 1 , state . turn_count ) ) ;
285
+ const newState = advance_state ( state , action , draw ) ;
286
+ const new_remaining = remove_remaining ( remaining_ids , id ) ;
287
+
288
+ // logger.info(`${Array.from({ length: depth }, _ => ' ').join('')}checking if winnable after ${logObjectiveAction(state, action)} drawing ${logCard(id)} {`);
289
+ const winnable = winnable_simpler ( newState , state . nextPlayerIndex ( playerTurn ) , new_remaining , depth + 1 ) ;
290
+
291
+ if ( winnable )
292
+ winnable_draws . push ( id ) ;
293
+
294
+ // logger.info(`${Array.from({ length: depth }, _ => ' ').join('')} } ${winnable}`);
295
+ }
296
+
297
+ return { winnable : winnable_draws . length > 0 , winnable_draws } ;
261
298
}
262
299
263
300
/**
264
301
* @param {State } state
265
302
* @param {ModPerformAction } action
303
+ * @param {ActualCard } draw
266
304
*/
267
- function advance_state ( state , action ) {
305
+ function advance_state ( state , action , draw ) {
268
306
const new_state = state . shallowCopy ( ) ;
269
307
new_state . hands = state . hands . slice ( ) ;
270
308
new_state . turn_count ++ ;
@@ -293,7 +331,7 @@ function advance_state(state, action) {
293
331
294
332
if ( state . deck [ newCardOrder ] === undefined ) {
295
333
new_state . deck = new_state . deck . slice ( ) ;
296
- new_state . deck [ newCardOrder ] = Object . freeze ( new ActualCard ( - 1 , - 1 , newCardOrder , state . turn_count ) ) ;
334
+ new_state . deck [ newCardOrder ] = draw ?? Object . freeze ( new ActualCard ( - 1 , - 1 , newCardOrder , state . turn_count ) ) ;
297
335
}
298
336
} ;
299
337
0 commit comments