Option 1:
The most basic thing I can suggest is that if you have a mapping from one thing to another, and if that mapping is fixed, you should be using a mapping data type.
The mapping data type in python is the dict
, which can be written in-line like so:
uci_bit = {
'a': 2**7, 'b': 2**6, 'c': 2**5, 'd': 2**4,
'e': 2**3, 'f': 2**2, 'g': 2**1, 'h': 2**0,
}
Alternatively, the dict()
function returns a dictionary, and takes keyword arguments:
uci_bit = dict(a=2**7, b=2**6, c=2**5, d=2**4,
e=2**3, f=2**2, g=2**1, h=2**0)
Same result either way. You could then access the dict using the standard square-bracket notation:
ch = uci[0]
mask1 = uci_bit[ch]
Or as a single expression:
mask1 = uci_bit[uci[0]]
Option 2:
However, there's another approach that has some potential. Looking things up in a dictionary has a cost. It's technically \$O(n)\$ but maybe there's a constant or two in there. So ...
Because the two components of the uci location are fixed-width ('a' and '2') you might be able to look them up in a string using the str.index
method and get better performance.
Note: I wrote "might be". The way to know for sure is to write both bits of code and run a timing test on the alternatives. Look up the timeit
module in the standard distribution.
Something like:
UCI_CHARS = 'hgfedcba'
mask1 = 2 ** UCI_CHARS.index(uci[0])
mask2 = 2 ** UCI_CHARS.index(uci[2])
You might also want to check the result of using dict
versus str.index
versus the int()
function on the single-digit part of uci decoding.
Option 3:
It's not clear how many of these operations you are performing. If you are doing a single game, and there might be two dozen uci to bit operations, then it probably doesn't matter. But if you're doing a lot of games at the same time, it might behoove you to generate a dictionary containing all the UCI letter/number combinations, and do the lookup as a single operation. Something like:
for ch in 'abcdefgh':
for digit in '12345678':
uci = ch + digit
bit = # whatever computation
uci_bit[uci] = bit
This would let you decode 'a2' -> bits in a single step at the cost of the up-front loop.
Other considerations
I asked the Duck about bitboard, and was immediately presented with the fact that there is no single standard representation. Instead, there are different representations for different purposes and different CPU architectures.
A result of this is that you should definitely be doing this inside a class or function, and there should definitely be a "name" given to the kind of bitboard you want to compute, so that readers will understand which of the several options you are choosing. Rank-major versus File-major and big- versus little- endian representations are all valid, so make sure to include this information either in the class or function name, or in the commentary.
def uci_to_bitboard_lsf(uci: str) -> int:
""" Convert UCI notation to bitboard(LSF, little-endian) """
...
Also, I suspect that using *
may be a bug, and you should be using +
instead. (It really should be a bitwise or, the |
operator, but +
will work.)