Logical part of solver version 5.2 |
|
In version 5.2, the algorithms for bridge and preferans solvers were changed. The bridge solver was rewritten from zero. In the old versions, it was impossible to solve several problems in one process, because there were many static class members and one had to create many processes to solve them. This caused some difficulties, especially in synchronization. Now, the program is made in such a way that you can solve several problems in one process in parallel. It is rewritten by analogy with the changes in the preferans solver version 5.1. Each object of the Bridge class, whose hash table is allocated 272mb, can solve its own task. If the computer has at least 6 cores, then six objects will require 1.6gb of RAM. Technically, a 32 bit application cannot use more than 4gb of RAM, in fact windows does not give more than 1.5-1.8gb. Therefore, the application became a 64 bit application, where there are no such limitations. In the process of rewriting it was noticed that some options of the bridge algorithm were not used in the preferans algorithm version 5.1, now everything that speeds up the preferans solver algorithm is added to it. Previously, there were several processes for the option for solving all players and trumps in bridge, and there was a separate bridgeConsole project for them. Now it is no longer needed and has been removed. Since the bridge algorithm has been rewritten, it is now possible to describe all its features. The description of the algorithms is divided into two parts: the first describes what was in the bridge solver algorithm before version 5.2. The second describes what appeared in version 5.2. Final score calculation on the last move, penultimate charge
Algorithm features that were in the bridge problem solver before version 5.2
New in version 5.2 algorithm
Card storing
Bridge
In version 5.2, the card storage scheme has changed. Now a bit code is created for each suit and all operations are performed with these codes. The lowest four bits contain the number of remaining cards in the suit, bits 5-6 indicate the player who has the highest remaining card, bits 7-8 indicate the player who has the second highest card, and so on. The suit code can contain a maximum of 30 bits, 4 bits per length and two bits for each of the remaining cards, which can be up to 13. North has a two-bit code of 00, east has code 01, south has code 10, east has code 11. Consider the problem
|
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The code of the clubs suit in binary form is 10000011010001101000. For convenience, let's write this code with separators 10_00_00_11_01_00_01_01_10.1000 - the last 4 bits contain the number 8 - the number of remaining cards in the suit. Bits 5-6 have code 10 - the highest ace card in the south. Bits 7-8 have code 01 - second highest card is king in the east. The third highest card Jack is in the north, so bits 9-10 contain 00, and so on. The suit codes are counted only once when the position is loaded and then the card is deleted and inserted back when all the moves are searched.
Note. Before version 5.2, doubly linked lists were used to store cards in suits. The calculations involved deleting, restoring cards in these lists. Also in parallel for each suit was a bitmask of 13 bits. When searching for a position in the table of permutations hash codes were taken from the preliminary correspondence of bit masks and suit bit codes, as they are made now. Now there are no lists - there are only suit codes.
Preferans
Doubly linked lists were used to store cards in suits until version 5.1, starting from version 5.1 the suit bit codes are used. See the description of logical version 5.1 (in russian only) for more details.
Hashing, macros for reading/writing hash
Bridge
Two structures HashItem, which contains 16 bytes and Hash, which contains 4*16+4=68 bytes are used to store the position in the hash table. The parameter HASH_ITEMS=4 and it is assumed that structure alignment=4.
struct HashItem {
int32_t code[3];
int16_t code3;
int8_t f;
int8_t v;
};
struct Hash{
HashItem i[HASH_ITEMS];
int32_t next;
};
One of the suit codes is stored only half as the other half is included in the table index. The same hash code corresponds to HASH_ITEMS=4 elements. The number of elements of the hash table is determined by HASH_BITS=22. The size of the table is HASH_SIZE=2HASH_BITS, which is 68*2HASH_BITS-20=68*4=272mb. Writing to the hash table is done in a loop, the next field is used for this purpose. At first it is 0, the first write goes to field i[0] and the next field becomes 1. Then in the field i[1] and next becomes 2, and so on. When all four elements of the hash table are written, the next field will be zeroed and the next entry will occur in field i[0], then the entry will occur in field i[1] and so on.
Two macros are used to write and read the hash table. First, there is a hash check that such a position already exists. Then, if there is no information in the hash table, it will be written. The index of both macros is calculated the same way, so when you use them, it is calculated only once.
Preferans
Big hash table needed only for solving all opponents deals. For all other search options (finding the best move, evaluating all moves, solving for all players, and trumps), increasing the size of the hash table only slows down the calculation, so a small hash table is used for them.
Preferans. Version 5.1
The table size of 64mb was used to solve all opponents deals. A 2mb table was used for all other searches. In all cases there was only one element per hash code
struct Hash {
int16_t code[3];
int8_t f;
int8_t v;
};
For the same reason as in the bridge, one of the suit codes does not need to be stored - it is included in the index.
Preferans. Version 5.2
Since version 5.2, the hash table structure is similar to the bridge.
struct HashItem {
int16_t code[3];
int8_t f;
int8_t v;
};
struct Hash{
HashItem i[HASH_ITEMS];
int32_t next;
};
The size of the HashItem structure is 8 bytes. HASH_ITEMS=4, so the size of the Hash structure is 8*HASH_ITEMS+4=36 bytes. The number of elements in the hash table is determined by the HASH_BITS parameter. The size of the table is HASH_SIZE=2HASH_BITS, which is 36*2HASH_BITS-20. The replacement of elements in the table is done in the same way as for the bridge. For solving all opponents HASH_BITS=23, the size of the hash table is 288mb. For all other options HASH_BITS=19, hash table size is 18mb.
Zero-window search
Bridge
The evaluation of the position e=t-f is equal to the difference of tricks of the current side t and the opposite side f. If the players have n cards left in their hands, the minimum possible score is -n, the maximum n. The score will always have the same parity as n. The score e=t-f, with t+f=n, that is, f=n-t, hence e=2*t-n, that is, the parity of e coincides with the parity of n. It is possible to do a single search, using alpha-beta cutoff, with interval (-n, n). Or you can do several searches, but with a minimum interval. Suppose that n is even, then we can first do a search in the interval (0, 2) and then, depending on the result, narrow the initial interval either to (-n, 0) or (2, n). Then, doing the next zero-window search, again split the resulting interval in half, and so on, until the final estimate is obtained. The using of zero-window search speeds up the bridge algorithm.
Note. Since the parity of the estimate always coincides with the parity of the number n, even though the search is performed as if in an interval of length 2, in fact the length is 1.
Preferans
The search with a zero window is not used because it slows down the algorithm.
One function per trick
Bridge
You can use a separate function for each move within the trick (four functions total), but it is better to make one function for all four moves in the trick, which avoids the passing of many parameters.
Preferans
Preferans version 5.1 used three separate functions for the first, second and third moves in the trick. In this case it was necessary to pass a lot of parameters to the functions for the second and third moves. Since version 5.2 one function is used for all three moves in the trick, as in bridge.
Comparison of two cards via a macro and a precomputed comparison table
To compare two cards, you can use a function, or create a pre-calculated table for each possible trump and no-trump game. One variable sc, consisting of two bytes, is used to store the card. The high byte contains the suit from 0 to 3, the low byte the rank from 0 to 12. So the maximum value of sc is 3*256+12=780, the number of different values is 781. The maximum sc will take 10 bits, that is, to compare two variables sc1 and sc2, it is enough to have a table containing 781 * 1024 + 781 = 800,525 bytes. Before version 5.2 there were five tables, one for each suit and one for no-trump games. Since version 5.2 in the internal representation for trump games the trump suit always equals zero and thus only two comparison tables are required: one for games with a trump and one for nt games. Altogether for two tables it will require 2 * 800 525/1024/1024 = 1.5 mb. The same table is suitable both for bridge and preferans.
Bridge
The bridge algorithm, like previous versions, uses a pre-calculated table and macro to compare the two cards.
Preferans
Preferans 5.1 used a separate function for comparison. Now a macro and a pre-calculated table is used, as in bridge.
Leading player array
Bridge
When you run through the variants, the input of the evaluation function is the parameter ww, which means the number of the player who makes the first move. We also need to know the numbers of other players, so for the bridge we create an array const int w[] = { ww, (ww + 1) % 4, (ww + 2) % 4, (ww + 3) % 4 }. Note that the array elements satisfy the condition w[i+1]=(w[i]+1)%4. The array w needs to be computed in each trick, so to avoid this we create a static array m_w, such that m_w[i]=i%4. Obviously, m_w[i+1]=(m_w[i]+1)%4. That is, the elements of this array satisfy the same condition as the elements of the array w. Now the evaluation function can pass not the ww parameter, but the pw pointer to int. If we know the number of the player who took the trick t, we should pass pw+t, where pw is the parameter obtained by the current evaluation function. For the very first evaluation function you should calculate the parameter through m_w array. Now the calculation of array w[] is not needed.
Let us estimate the length of the array m_w. Consider the first trick, the first move in it can do a maximum of the third player (requires 4 elements). Maximum who can take a trick is the third player in the trick. Thus for one trick the length of the array is 3+4=7. In each following trick the maximum shift will be 3, and such tricks will be maximum 12, i.e. the length of the array m_w is 7+3*12=43.
Preferans
Before version 5.2, the parameter ww was used and the array w was calculated. Version 5.2 uses the same idea as the bridge. The array, unlike the bridge, is initialized by the rule m_w[i]=i%3. Let us estimate the length of the array m_w. Consider the first trick, the first move in it can be taken by the second player maximum (requires 3 elements). Maximum who can take a trick is the second player in the trick. Thus for one trick the length of the array is 3+2=5. In each following trick the maximum shift will be 2, and such tricks will be maximum 9, i.e. the length of array m_w is 5+2*9=23.
Static moves ordering
The order of moves is important to speed up the algorithm, and it is preferable to consider the best moves first. In version 5.2 the number of options for finding the possible orders of moves has been expanded. In some cases, you have to search for moves in the one suit
- moves 2-4 move in bridge and the player has a leading suit.
- moves 2-3 in preference and the player has a leading suit
- moves 2-3 in preference and you don't have a leading suit but you have a trump for games with trump
- when the first move in bridge and preference is played.
- moves 2-4 in bridge when the player doesn't have a leading suit.
- moves 2-3 in preference and the player doesn't have a leading suit and a trump for games with trump
The orders of moves in the one suit.
Let's denote the number of variants O1 = MOVES_ONE_SUIT_OPTIONS = 6.
- high⇨low - all cards from high to low
- low, high⇨low - the lowest card comes first, then the rest from the highest to the lowest
- high, low, high⇨low - the high card, then the low card, then the rest from high to low
- low⇨high - all cards from low to high
- high, low⇨high - the highest card goes first, then the rest from the lowest to the highest
- low, high, low⇨high - a low card, then a high card, then the rest from low to high
The suit is divided into three parts high, low and all remaining cards (high⇨low or low⇨high), the first two parts high or low can present or absent, but the last (all remaining cards) must occur, and it must be the last. Also, some move orders are useless because they repeat other orders. For example, the order high, high⇨low means the same as the order high⇨low, so high⇨low can go only after low, and low⇨high only after high.
The orders of moves in several suits for no-trump and preferans misere games.
Denote the number of options OM_NT = MOVES_MANY_SUIT_OPTIONS_NT = 10. There will be more orders here. Denote
- LOW = low0, low1... - all lowest cards in the suits
- HIGH = high0, high1... - all high cards in the suits
- HIGHLOW = high0⇨low0, high1⇨low1... - all remaining cards in suits, from the high to the low
- LOWHIGH = low0⇨high0, low1⇨high1... - all remaining cards in suits, from low to high
As well as in the case of one suit, the parts of LOW and HIGH can present or absent, but one and only one of HIGHLOW or LOWHIGH must always be, and it must be the last. However, since there are now several suits, HIGHLOW may come not only after LOW but also after HIGH. The same is true for LOWHIGH. We get the following orders
- HIGHLOW
- LOW, HIGHLOW
- HIGH, HIGHLOW
- low0, high0, low1, high1..., HIGHLOW
- high0, low0, high1, low1..., HIGHLOW
The second part is the same, but at the end instead of HIGHLOW it will be LOWHIGH
- LOWHIGH
- LOW, LOWHIGH
- HIGH, LOWHIGH
- low0, high0, low1, high1..., LOWHIGH
- high0, low0, high1, low1..., LOWHIGH
The orders of moves in several suits for games with trump.
The move orders specified above for several suits of no-trump games will also determine some orders for trump games as well. Since version 5.2 the trump suit, if there is a trump, is always zero in the inner representation. That is, for example, for a given order of low0, high0, low1, high1..., HIGHLOW, moves from the trump suit will be tried first. For trump games, the number of move orders will be the same as for no-trump games. Denote the number of options as OM = MOVES_MANY_SUIT_OPTIONS = 10.
Grouping tasks.
All tasks are divided into five groups. For bridge tasks are divided into two groups: trump and no-trump tasks. In preference, problems are divided into three groups: trump, no-trump, and miseres. For each of the five groups, quick tasks are eliminated, and for the others, different orders of moves are found. More detailed analysis of the different types of problems speeds up the program.
| bridge | preferans | ||||
|---|---|---|---|---|---|
| trump games | no trump games | trump games | no trump games | misere games | |
| first move | several suits in the game with trump, OM options | several suits in the game without trump, OM_NT options | several suits in the game with trump, OM options | several suits in the game without trump, OM_NT options | several suits in the game without trump, OM_NT options |
| moves 2-4 bridge or moves 2-3 preferans | one suit, options O1 | one suit, options O1 | one suit, options O1 one suit, options O1 | one suit, options O1 | one suit, options O1 |
|
several suits in the game with trump, OM options | several suits in the game without trump, OM_NT options | several suits in the game with trump, OM options | several suits in the game without trump, OM_NT options | several suits in the game without trump, OM_NT options | |
| total number of options | O1 × OM2 | O1 × OM_NT2 | O12 × OM2 | O1 × OM_NT2 | O1 × OM_NT2 |
For each of five possible types, it is needed to find its optimal parameters. For preferans games where there is a trump such parameters will be 4, for all other types of games there will be 3 parameters.
Bridge
For games with trump it is necessary to look through O1 × OM2 = 600 variants of the move orders. For no-trump games it is necessary to look through O1 × OM_NT2 = 600 variants. To search for parameters, the tasks were taken from the files old.bts, Competition.bts, HughDarwen.bts, GeorgeCoffin.bts. They are in the folder of the problems library bridge/problems. The file old.bts is a union of all the tasks from the bridge/problems/bts directory on which the program has been tested historically. Only tasks that solve for longer than one second were taken for the search. After that, tasks were split into two groups: tasks with trump and tasks without trump. For games with trump it took 17 hours on 6 cores. For games without trump it took 17.5 hours.
Preferans
For games with trump it is necessary to try O12 × OM2 = 3600 variants. For games without trump and misere games it is necessary to look over O1 × OM_NT2 = 600 variants. In all cases, the search is performed for the option solve all deals where it is needed to solve 184,756 deals. For games with trump, the search took 25.5 hours on 6 cores. For games without trump it took 3.5 hours, for misere games it took 20 minutes.
Sure tricks
Bridge
Used until version 5.2.
Preferans
Not used.
Quick tricks
Bridge
Used until version 5.2.
Preferans
Not used.
New in version 5.2 algorithm
Dividing algorithms by game type
Before version 5.2, bridge and preferance used the same algorithm for all game types. Starting from version 5.2 the algorithms are divided by game types. For bridge there are two types of games: with and without trump. For preferans there are three: with trump, non-misere and non-trump games, and miseres. The separation of games by type allows you not to check whether the game is a trump or no-trump game when calculating, and also for preferans, whether it is a misere game or not, which speeds up the algorithm. It also made it possible to find parameters for static ordering of moves for each type, which is better than one general order for all types. To avoid writing separate code for each type of game, the separation is implemented through a system of macros and the #include directive. Thus, we have to write one code for all game types.
Preliminary moves calculation
When searching for possible moves for a player, we have to search through the bits of the suit code, first determining the length from the code, then looking through each two bits of the code for a non-zero length. In this search, we have to search through all bits, including the bits of the players that we do not need. In particular, if the suit does not contain the cards of the player we want, we will not get a card at all. The idea of the preliminary calculation is to pre-calculate the moves for each code and for each player. The move table will be universal for all tasks. In this case, only the moves we need will be found. Also, in this case, we don't need to search for cards for several players in parallel, which simplifies the logic of the program. A preliminary calculation of moves greatly speeds up the algorithm.
Bridge
For each suit length n we store the moves in separate tables. Let the suit contain exactly n cards, then the number of codes is 4n. We need to store moves for four players. Without sequences, the number of moves per player can contain at most n/2 cards if n is even, and (n+1)/2 cards if n is odd, plus one byte containing the number of cards. Both formulas can be combined into one (n+n%2)/2+1.
Note. It is possible to store moves in an alternative form, first set all the moves, then make the closing byte, for example equal to -1. But, when you order the moves, it is necessary to go through the moves not only from the highest card to the lowest, but also vice versa. And if you want to go through the cards in reverse order, with the closing byte you have to first make a pass to the closing byte, and then make a pass in reverse order, so you have to make a double pass. This is why it is done through the byte containing the number of cards. This avoids the double pass.
For all the suit codes of length n you need 4n*4*((n+n%2)/2+1) = 22n+2*((n+n%2)/2+1) bytes or 22n-18*((n+n%2)/2+1) mb. The table shows the amount of memory to store codes of suits of length n and, after a slash, how much memory is needed for all suits of length ≤ n.
| n | requires memory mb |
|---|---|
| ... | .. |
| 10 | 24 / 31.6 |
| 11 | 112 / 143.6 |
| 12 | 448 / 591.6 |
| 13 | 2,048 / 2,639.6 |
For now, the program is designed to store moves up to and including length 11. In the future, when computers have more RAM, it will be possible to store moves for all lengths. The following algorithm is used to calculate moves in a suit of length 13. The suit is split into two of 12 and 1 cards, and the moves for those lengths already exist. And then the moves from the suit of length 1 must be added to the moves of the suit of length 12. Keep in mind that if the last move of the length 12 suit and the move of the length 1 suit are in the sequences, the move of the length 1 suit doesn't need to be added. Consider the length 13 suit code in binary form. The last 4 bits of 1101 are lengths. Let us find cards for the third player (binary code 11). 00_00_11_01_01_11_11_00_00_10_10_11_11.1101. In the first part of the code we get ranks 2, 5, 11 (rank 6 is excluded, because it is in the sequence with rank 5). The move from the second part of the code is not added, since it is in the sequence with rank 11.
The algorithm for enumerating cards in suits is made in such a way that the moves must be in the same array. To do this, you need to add 1 byte for suits of length 12, so at n=12 you need 224-18*(12/2+2) = 29 = 512 mbs. When searching moves, we need to calculate (n+n%2)/2+1 each time, which would slow down the algorithm. To avoid this, we will use a total of 8 bytes for all lengths. In the future, when all suit lengths up to and including 13 will be stored, 8 bytes will also be enough for n=13. This means that a suit of length n would require 22n-18*8 = 22n-15 mb. The table of required memory will now be as follows.
| n | requires memory mb |
|---|---|
| ... | .. |
| 10 | 32 / 42.7 |
| 11 | 128 / 170.7 |
| 12 | 512 / 682.7 |
| 13 | 2,048 / 2,730.7 |
Preferans
Preferans is much simpler than bridge; it is possible to store all the moves and do it in one table. The suit code has a maximum of 18 bits with two bits (00, 01 or 10) for each of the eight cards plus two closing bits 11. We obtain that the maximum value of the code is 0b111010101010101010 = 240,298. That is, you need an array of length 240,299. For each code you need to store cards for three players. The maximum number of cards (not including sequences) per player is 4 plus 1 byte per length. We obtain that the required memory size for all moves is 240,299*3*5/1024/1024 = 3.4 mb.
Final score calculation in the last move of the next-to-last trick
Bridge
Before version 5.2 the final estimateat once was calculated in the first move of the last trick, the four remaining cards were calculated at once, and the score of the last trick was based on them. Now after the last move of the next to last trick, there is no recursion call, the four remaining cards are found at once, and the final position score is obtained.
Preferans
By analogy with bridge before version 5.2 the position was finally calculated in the first move of the last trick, now after the last move of the penultimate trick.
Deleting a card using a previously saved array
When solving problems in the search process, you need to remove cards from the suit code. You can do this through a function/macros, or you can create a table with pre-calculated codes after removing one of the cards in a suit.
Bridge
For bridge, each suit code consists of a maximum of 30 bits - 4 bits for the number of cards and two bits for each of 13 possible cards. The table must contain the suit code after deletion, that is, each element of the table contains 4 bytes. So you need 230*13*4 bytes = 52 gb. The table requires a lot of space, so this improvement is only used in preference. It is possible in principle not to store lengths, but to make a separate table for each length. In this case only for suits of length 13 you would need 226*13*4 bytes = 3.25 gb, which is also very much.
Preferans
The suit code consists of a maximum of 18 bits, two bits for each of the eight cards in the suit (00, 01, or 10) and two closing bits 11. The maximum value of the suit code is 0x3aaaa=240,298, that is 240,299 variants in total. Plus a card with a rank from 0 to 7 can be deleted for a total of 8 options. Each element of the table contains 4 bytes. Thus, we get that to store the whole table, we need 240,299*8*4/1024/1024=7.33 mb.
Restoring a card using a previously saved code
When solving problems, in the process of enumeration, when the program returns to a higher level, it is necessary to restore the deleted cards. You can do this using a function/macro, or you can save a card's code into a separate variable before deleting it. Then, when you need to restore the card, you just need to take the saved code.
Bridge
The bridge option has been used since version 5.2. Before version 5.2 suit codes were not used at all - the remaining cards were stored in two-linked lists, so there was no such option.Preferans
In preferans the option is used since version 5.2, before that the function was used.Solution of all opponent's deals
The option of solving all opponents' deals is now available for bridge as well. If one of the players has \(c_1\) and the other has \(c_2\) unfixed cards (the cards on the table are fixed), then the total number of positions equals the binomial coefficient \(C^{c_1}_{c_1+c_2}\). The option is active as long as one of the players with unknown cards has at least one card left.
Formula for the number of positions.
It has been noticed that when the number of cards is increased by one, the number of variants increases by about two times. We can derive an approximate formula for the number of positions without using the binomial coefficient. Denote as \(n=c_1+c_2\) the total number of cards, then \(c_1\) is approximately equal to \(n/2\). That is, the total number of positions is \(C^{n/2}_n=\frac{n!}{(n/2)!^2}\), we estimate it using the Stirling formula \(n!\approx\sqrt{2\pi n}\left(\frac{n}{e}\right)^n\). From it we get that $$ (n/2)!^2\approx \left(\sqrt{\frac{2\pi n}{2}}\left(\frac{n}{2e}\right)^{n/2}\right)^2=\pi n \left( \frac{n}{2e}\right)^n$$ Now let's find an approximate value of the binomial coefficient. $$ C^{\frac{n}{2}}_n=\frac{n!}{(n/2)!^2}\approx\sqrt{2\pi n}\left(\frac{n}{e}\right)^n \frac{1}{\pi n} \left( \frac{2e}{n}\right)^n = \sqrt{\frac{2}{\pi n}}\cdot 2^n$$ That is, roughly speaking, increasing the number of unknown cards by one really doubles the number of positions. The formula gives a good approximation. The maximum number of positions for bridge is \(C^{13}_{26}=10,400,600\), the approximation formula gives \(\sqrt{\frac{2}{26\pi}}\cdot 2^{26}\approx 10,501,063 \), For preferans, \(C^{10}_{20}=184,756\), the approximate formula gives a value of \(\sqrt{\frac{2}{20\pi}}\cdot 2^{20}\approx 187,079 \)
Bridge
The option appeared in version 5.2.
Note. Since problems take a very long time to calculate in the bridge, in order to get a more accurate estimate while the problem is not calculated to the end, before solving all variants are divided into chunks of 40 elements and then these chunks are randomly shuffled. In this case, if you wait until the end of the calculation - there will be a complete enumeration of all the deals.
Preferans
The option of solving all opponents' deals has now been extended. Previously it was active, either when the first move was not made at all, or the only move was made and that move was made by the one who declared the contract. Now it is possible to make a calculation in any position, if the whisting players / misere catchers have at least one card left in their hands.
Estimation function for all moves
Bridge
The function of evaluating all moves, after finding the best move, is now multitasking. If the option for evaluating all moves is set, it runs even longer than the search for the best move. Since version 5.2, multiple threads are started for evaluation if three conditions are satisfied at the same time:
- number of cores must be greater than one
- the number of cards to evaluate, excluding sequences and the best move already evaluated, must be greater than 1
- the searching for the best move took more than one second
Also, since the score of the best move is known, the score for the other moves will not be greater than for the best, which reduces the search interval.
Above before evaluating all moves the best move was found, there is also a second way. If the user has chosen the option to evaluate all moves, then first estimate all moves at full intervals using many threads, and then, the best move will be the move with the maximum estimate. On a dual-core processor, the first way works faster for some cases and the second way for another. At the moment the first method is used.
Preferans
Everything counts too fast for preferans, so multi-threading and search interval reduction are not used.
Function for finding the optimal line of moves
In version 5.2 there was added a panel of the best moves. The program makes the best move t1, then finds the best move t2 after move t1, then the best move t3, after moves t1 and t2 and so on. Thus the program reaches the end of the game and gets one of the best move sequences.
Bridge
The first move of the sequence t1 has already been obtained in the search for the best move; moreover, its score e1 is known. If t2 is the second, third or fourth move in the trick, its score will be -e1. That is, the score is known, we only need to find move t2. This is done by a zero-window search (-e1-2, -e1). If t2 is the first move in a trick, then we need to look at who took the previous trick. Consider an example. Suppose the last move in the previous trick was taken by north and north/south in the optimal game take 6 tricks, and west/east take 2. Then the score e1 is 6-2=4. If the previous trick is taken by north or south, then the next best move t2 must be evaluated by north/south, since they took a trick and make the next move. North/South now take 5 tricks, not 6, since they've already taken one. And thus, the north/south pair now has a score of e2=e1-1=5-2=3, that is, the search should be in the range (e1-3, e1-1). If the previous trick is taken by East or West, then the next move should be evaluated in view of the West/East pair, they take not two, but one trick (because they already took one), and now their evaluation is e2=1-6=-5=-e1-1, that is, the search must be in the range (-e1-3, -e1-1). Thus, you can always search in zero interval. The function of searching for a sequence of best moves works very fast.
Preferans
The search with zero interval and evaluation of the best move is not used - the moves can be found too fast as it is.



ru
A K Q 3
-
J 6 5