Distance of Robot from Starting Point after a Series of Movements - python

I am trying to write a program which takes a list of directions and magnitudes and outputs the distance of the robot from its starting position.
I get an error when executing the following code but I cannot identify why I get the error.
import math
position = [0,0]
direction = ['+Y','-X','-Y','+X','-X','-Y','+X']
magnitude = [9,7,4,8,3,6,2]
i = 0
while i < len(direction):
if direction[i] == '+Y': position[0] += magnitude[i]
elif direction[i] == '-Y': position[0] -= magnitude[i]
elif direction[i] == '+X': position[1] += magnitude[i]
elif direction[i] == '-X': position[1] -= magnitude[i]
else: pass
i += 1
print float(math.sqrt(position[1]**2+position[0]**2))
Edit:
I get this error:
IndentationError: unindent does not match any outer indentation level

Most probably you have mixed up your spaces and tabs. In this instance, it might be easier to put the sign within the magnitude and filter with x and y like so:
In [15]: mDr = [ (int(d[0]+m), d[1]) for (d, m) in zip(direction, map(str, magnitude))]
In [16]: mDr
Out[16]: [(9, 'Y'), (-7, 'X'), (-4, 'Y'), (8, 'X'), (-3, 'X'), (-6, 'Y'), (2, 'X')]
In this case, you can get to total x and y distances pretty easily. For example, the y distances:
In [17]: [md[0] for md in mDr if md[1] =='Y']
Out[17]: [9, -4, -6]
And the total y distance in the particular direction:
In [18]: sum( [md[0] for md in mDr if md[1] =='Y'] )
Out[18]: -1
You can do the same for x and then calculate the distance that way.

Here comes my offtopic reaction (your problem was mixing tabs and spaces, my answer is simple rewrite).
import math
xymoves = {"+X": (1, 0), "-X": (-1, 0), "+Y": (0, 1), "-Y": (0, -1)}
position = [0, 0]
directions = ['+Y', '-X', '-Y', '+X', '-X', '-Y', '+X']
assert all(xymove in xymoves for xymove in directions)
magnitudes = [9, 7, 4, 8, 3, 6, 2]
for direction, magnitude in zip(directions, magnitudes):
xmove, ymove = xymoves[direction]
position[0] += magnitude * xmove
position[1] += magnitude * ymove
print math.sqrt(position[1]**2+position[0]**2)
Changes:
looping using for and not while with incrementing index.
the logic "where to move" moved from if elif elif into dictionary xymoves
rejecting to process direction, which is not expected
math.sqrt always returns float, so conversion to float removed
Note, that the dictionary with xymoves could be extended with other directions, e.g. using "N" for North, "NE" for North-East etc.

Related

how to select some lines created by points based on their distances in python

I have some lines created by connecting points of a regular grid and want to pair the correct lines to create surfces. This is coordinates of my point array:
coord=np.array([[0.,0.,2.], [0.,1.,3.], [0.,2.,2.], [1.,0.,1.], [1.,1.,3.],\
[1.,2.,1.], [2.,0.,1.], [2.,1.,1.], [3.,0.,1.], [4.,0.,1.]])
Then, I created lines by connecting points. My points are from a regular grid. So, I have two perpendicular sets of lines. I called them blue (vertical) and red (horizontal) lines. To do so:
blue_line=[]
for ind, i in enumerate (range (len(coord)-1)):
if coord[i][0]==coord[i+1][0]:
line=[ind, ind+1]
# line=[x+1 for x in line]
blue_line.append(line)
threshold_x = 1.5
threshold_y = 1.5
i, j = np.where((coord[:, 1] == coord[:, np.newaxis, 1]) &
(abs(coord[:, 0] - coord[:, np.newaxis, 0]) < 1.2 * threshold_y))
# Restrict to where i is before j
i, j = i[i < j], j[i < j]
# Combine and print the indices
red_line=np.vstack([i, j]).T
blue_line=np.array(blue_line)
red_line=np.array(red_line)
all_line=np.concatenate((blue_line, red_line), axis=0)
To find the correct lines for creating surfaces, I check the center of each line with the adjacent ones. I start from the first blue line and try if there are other three adjacent lines or not. If I find any line which its center is less than threshold_x and also its x coordinate is different from that line, I will keep it as a pair. Then I continue searching for adjacent lines with this rule. My fig clearly shows it. First blue line is connected by an arrow to the blue line numbered 3 and also red lines numbered 6 and 7. It is not paired with blue line numbered 2 because they have the same x coordinate. I tried the following but it was not successful to do all the things and I coulnot solve it:
ave_x=[]
ave_y=[]
ave_z=[]
for ind, line in enumerate (all_line):
x = (coord[line][0][0]+coord[line][1][0])/2
ave_x.append (x)
y = (coord[line][0][1]+coord[line][1][1])/2
ave_y.append (y)
z = (coord[line][0][2]+coord[line][1][2])/2
ave_z.append (z)
avs=np.concatenate((ave_x, ave_y, ave_z), axis=0)
avs=avs.reshape(-1,len (ave_x))
avs_f=avs.T
blue_red=[len (blue_line), len (red_line)]
avs_split=np.split(avs_f,np.cumsum(blue_red))[:-1] # first array is center of
# blue lines and second is center of red lines
dists=[]
for data in avs_split:
for ind, val in enumerate (data):
if ind < len(data):
for ind in range (len(data)-1):
squared_dist = np.sum((data[ind]-data[ind+1])**2, axis=0)
dists.append (squared_dist)
In fact I expect my code to give me the resulting list as the pairs of the lines the create three surfaces:
[(1, 6, 3, 7), (2, 7, 4, 8), (3, 9, 5, 10)]
At the end, I want to find the number of lines which are not used in creating the surfaces or are used but are closer than a limit the the dashed line in my fig. I have the coordinate of the two points creating that dashed line:
coord_dash=np.array([[2., 2., 2.], [5., 0., 1.]])
adjacency_threshold=2
These line numbers are also:
[4, 10, 5, 11, 12]
In advance I do appreciate any help.
I'm not sure my answer is what you are looking for because your question is a bit unclear. To start off I create the blue and red lines as dictionaries, where the keys are the line numbers and the values are tuples with the star and end point numbers. I also create a dictionary all_mid where the key is the line number and the value is a pandas Series with the coordinates of the mid point.
import numpy as np
import pandas as pd
coord = np.array([[0.,0.,2.], [0.,1.,3.], [0.,2.,2.], [1.,0.,1.], [1.,1.,3.],
[1.,2.,1.], [2.,0.,1.], [2.,1.,1.], [3.,0.,1.], [4.,0.,1.]])
df = pd.DataFrame(
data=sorted(coord, key=lambda item: (item[0], item[1], item[2])),
columns=['x', 'y', 'z'],
index=range(1, len(coord) + 1))
count = 1
blue_line = {}
for start, end in zip(df.index[:-1], df.index[1:]):
if df.loc[start, 'x'] == df.loc[end, 'x']:
blue_line[count] = (start, end)
count += 1
red_line = []
index = df.sort_values('y').index
for start, end in zip(index[:-1], index[1:]):
if df.loc[start, 'y'] == df.loc[end, 'y']:
red_line.append((start, end))
red_line = {i + count: (start, end)
for i, (start, end) in enumerate(sorted(red_line))}
all_line = {**blue_line, **red_line}
all_mid = {i: (df.loc[start] + df.loc[end])/2
for i, (start, end) in all_line.items()}
The lines look like this:
In [875]: blue_line
Out[875]: {1: (1, 2), 2: (2, 3), 3: (4, 5), 4: (5, 6), 5: (7, 8)}
In [876]: red_line
Out[876]:
{6: (1, 4),
7: (2, 5),
8: (3, 6),
9: (4, 7),
10: (5, 8),
11: (7, 9),
12: (9, 10)}
Then I define some utility functions:
adjacent returns True if the input points are adjacent.
left_to_right returns True if the x coordinate of the first point is less than the x coordinate of the second point.
connections returns a dictionary in which the key is a line number and the value is a list with the line numbers connected to it.
def adjacent(p, q, threshold=1):
dx = abs(p['x'] - q['x'])
dy = abs(p['y'] - q['y'])
dxy = np.sqrt(dx**2 + dy**2)
return np.max([dx, dy, dxy]) <= threshold
def left_to_right(p, q):
return p['x'] < q['x']
def connections(midpoints, it):
mapping = {}
for start, end in it:
if adjacent(midpoints[start], midpoints[end]):
if left_to_right(midpoints[start], midpoints[end]):
if start in mapping:
if end not in mapping[start]:
mapping[start].append(end)
else:
mapping[start] = [end]
return mapping
We are now ready to create a list of lists, in which each sublist has the line numbers that make up a surface:
from itertools import product, combinations
blues = blue_line.keys()
reds = red_line.keys()
blue_to_red = connections(all_mid, product(blues, reds))
blue_to_blue = connections(all_mid, combinations(blues, r=2))
surfaces = []
for start in blue_line:
red_ends = blue_to_red.get(start, [])
blue_ends = blue_to_blue.get(start, [])
if len(red_ends) == 2 and len(blue_ends) == 1:
surfaces.append(sorted([start] + red_ends + blue_ends))
This is what you get:
In [879]: surfaces
Out[879]: [[1, 3, 6, 7], [2, 4, 7, 8], [3, 5, 9, 10]]

How to model Bishop movement on a chessboard

I have a board, and I want to model a bishop's possible moves on it. I attempted this code:
for c1, c2 in [(1, -1), (1, 1), (-1, -1), (-1, 1)]:
for x, y in [range(x+c1, board_size), range(y+c2, board_size)]:
moves.append(x, y)
But it doesn't work to find all the moves. Yet, I don't understand why. Doesn't it check all four directions?
Your logic is sound, but your execution is not.
Half of your calculations must go from x or y to 0 (the other half go from x or y to board_size
Ranges don't work from larger to smaller values with the default step, so you'll need to introduce a step of -1 to count from x or y to 0
You should use zip() to create an iterable collection of tuples.
This will work:
right_up = zip(range(x + 1, board_size), range(y - 1, -1, -1))
right_down = zip(range(x + 1, board_size), range(y + 1, board_size))
left_up = zip(range(x - 1, -1, -1), range(y - 1, -1, -1))
left_down = zip(range(x - 1, -1, -1), range(y + 1, board_size))
for r in (right_up, right_down, left_up, left_down):
for new_x, new_y in r: # add coordinates to move list

How to generate random numbers to satisfy a specific mean and median in python?

I would like to generate n random numbers e.g., n=200, where the range of possible values is between 2 and 40 with a mean of 12 and median is 6.5.
I searched everywhere and i could not find a solution for this. I tried the following script by it works for small numbers such as 20, for big numbers it takes ages and result is returned.
n=200
x = np.random.randint(0,1,size=n) # initalisation only
while True:
if x.mean() == 12 and np.median(x) == 6.5:
break
else:
x=np.random.randint(2,40,size=n)
Could anyone help me by improving this to get a quick result even when n=5000 or so?
One way to get a result really close to what you want is to generate two separate random ranges with length 100 that satisfies your median constraints and includes all the desire range of numbers. Then by concatenating the arrays the mean will be around 12 but not quite equal to 12. But since it's just mean that you're dealing with you can simply generate your expected result by tweaking one of these arrays.
In [162]: arr1 = np.random.randint(2, 7, 100)
In [163]: arr2 = np.random.randint(7, 40, 100)
In [164]: np.mean(np.concatenate((arr1, arr2)))
Out[164]: 12.22
In [166]: np.median(np.concatenate((arr1, arr2)))
Out[166]: 6.5
Following is a vectorized and very much optimized solution against any other solution that uses for loops or python-level code by constraining the random sequence creation:
import numpy as np
import math
def gen_random():
arr1 = np.random.randint(2, 7, 99)
arr2 = np.random.randint(7, 40, 99)
mid = [6, 7]
i = ((np.sum(arr1 + arr2) + 13) - (12 * 200)) / 40
decm, intg = math.modf(i)
args = np.argsort(arr2)
arr2[args[-41:-1]] -= int(intg)
arr2[args[-1]] -= int(np.round(decm * 40))
return np.concatenate((arr1, mid, arr2))
Demo:
arr = gen_random()
print(np.median(arr))
print(arr.mean())
6.5
12.0
The logic behind the function:
In order for us to have a random array with that criteria we can concatenate 3 arrays together arr1, mid and arr2. arr1 and arr2 each hold 99 items and the mid holds 2 items 6 and 7 so that make the final result to give as 6.5 as the median. Now we an create two random arrays each with length 99. All we need to do to make the result to have a 12 mean is to find the difference between the current sum and 12 * 200 and subtract the result from our N largest numbers which in this case we can choose them from arr2 and use N=50.
Edit:
If it's not a problem to have float numbers in your result you can actually shorten the function as following:
import numpy as np
import math
def gen_random():
arr1 = np.random.randint(2, 7, 99).astype(np.float)
arr2 = np.random.randint(7, 40, 99).astype(np.float)
mid = [6, 7]
i = ((np.sum(arr1 + arr2) + 13) - (12 * 200)) / 40
args = np.argsort(arr2)
arr2[args[-40:]] -= i
return np.concatenate((arr1, mid, arr2))
Here, you want a median value lesser than the mean value. That means that a uniform distribution is not appropriate: you want many little values and fewer great ones.
Specifically, you want as many value lesser or equal to 6 as the number of values greater or equal to 7.
A simple way to ensure that the median will be 6.5 is to have the same number of values in the range [ 2 - 6 ] as in [ 7 - 40 ]. If you choosed uniform distributions in both ranges, you would have a theorical mean of 13.75, which is not that far from the required 12.
A slight variation on the weights can make the theorical mean even closer: if we use [ 5, 4, 3, 2, 1, 1, ..., 1 ] for the relative weights of the random.choices of the [ 7, 8, ..., 40 ] range, we find a theorical mean of 19.98 for that range, which is close enough to the expected 20.
Example code:
>>> pop1 = list(range(2, 7))
>>> pop2 = list(range(7, 41))
>>> w2 = [ 5, 4, 3, 2 ] + ( [1] * 30)
>>> r1 = random.choices(pop1, k=2500)
>>> r2 = random.choices(pop2, w2, k=2500)
>>> r = r1 + r2
>>> random.shuffle(r)
>>> statistics.mean(r)
12.0358
>>> statistics.median(r)
6.5
>>>
So we now have a 5000 values distribution that has a median of exactly 6.5 and a mean value of 12.0358 (this one is random, and another test will give a slightly different value). If we want an exact mean of 12, we just have to tweak some values. Here sum(r) is 60179 when it should be 60000, so we have to decrease 175 values which were neither 2 (would go out of range) not 7 (would change the median).
In the end, a possible generator function could be:
def gendistrib(n):
if n % 2 != 0 :
raise ValueError("gendistrib needs an even parameter")
n2 = n//2 # n / 2 in Python 2
pop1 = list(range(2, 7)) # lower range
pop2 = list(range(7, 41)) # upper range
w2 = [ 5, 4, 3, 2 ] + ( [1] * 30) # weights for upper range
r1 = random.choices(pop1, k=n2) # lower part of the distrib.
r2 = random.choices(pop2, w2, k=n2) # upper part
r = r1 + r2
random.shuffle(r) # randomize order
# time to force an exact mean
tot = sum(r)
expected = 12 * n
if tot > expected: # too high: decrease some values
for i, val in enumerate(r):
if val != 2 and val != 7:
r[i] = val - 1
tot -= 1
if tot == expected:
random.shuffle(r) # shuffle again the decreased values
break
elif tot < expected: # too low: increase some values
for i, val in enumerate(r):
if val != 6 and val != 40:
r[i] = val + 1
tot += 1
if tot == expected:
random.shuffle(r) # shuffle again the increased values
break
return r
It is really fast: I could timeit gendistrib(10000) at less than 0.02 seconds. But it should not be used for small distributions (less than 1000)
Ok, you're looking at the distribution which has no less than 4 parameters - two of those defining range and two responsible for required mean and median.
I could think about two possibilities from the top of my head:
Truncated normal distribution, look here for details. You have already range defined, and have to recover μ and σ from mean and median. It will require solving couple of nonlinear equation, but quite doable in python. Sampling could be done using https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.truncnorm.html
4-parameters Beta distribution, see here for details. Again, recovering α and β in Beta distribution from mean and median will require solving couple of non-linear equations. Knowing them sampling would be easy via https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.beta.html
UPDATE
Here how you could do it for truncated normal going from mean to mu: Truncated normal with a given mean
If you have a bunch of smaller arrays with the right median and mean, you can combine them to produce a larger array.
So... you can pre-generate smaller arrays as you are currently doing, and then combine them randomly for larger n. Of course, this will result in a biased random sample, but it sounds like you just want something that's approximately random.
Here's working (py3) code that generates a sample of size 5000 with your desired properties, which it build from smaller samples of size 4, 6, 8, 10, ..., 18.
Note, that I changed how the smaller random samples are built: half of the numbers must be <= 6 and half >= 7 if the median is to be 6.5, so we generate those halves independently. This speeds things up massively.
import collections
import numpy as np
import random
rs = collections.defaultdict(list)
for i in range(50):
n = random.randrange(4, 20, 2)
while True:
x=np.append(np.random.randint(2, 7, size=n//2), np.random.randint(7, 41, size=n//2))
if x.mean() == 12 and np.median(x) == 6.5:
break
rs[len(x)].append(x)
def random_range(n):
if n % 2:
raise AssertionError("%d must be even" % n)
r = []
while n:
i = random.randrange(4, min(20, n+1), 2)
# Don't be left with only 2 slots left.
if n - i == 2: continue
xs = random.choice(rs[i])
r.extend(xs)
n -= i
random.shuffle(r)
return r
xs = np.array(random_range(5000))
print([(i, list(xs).count(i)) for i in range(2, 41)])
print(len(xs))
print(xs.mean())
print(np.median(xs))
Output:
[(2, 620), (3, 525), (4, 440), (5, 512), (6, 403), (7, 345), (8, 126), (9, 111), (10, 78), (11, 25), (12, 48), (13, 61), (14, 117), (15, 61), (16, 62), (17, 116), (18, 49), (19, 73), (20, 88), (21, 48), (22, 68), (23, 46), (24, 75), (25, 77), (26, 49), (27, 83), (28, 61), (29, 28), (30, 59), (31, 73), (32, 51), (33, 113), (34, 72), (35, 33), (36, 51), (37, 44), (38, 25), (39, 38), (40, 46)]
5000
12.0
6.5
The first line of the output shows that there's 620 2's, 52 3's, 440 4's etc. in the final array.
While this post already has an accepted answer, I'd like to contribute a general non integer approach. It does not need loops or testing. The idea is to take a PDF with compact support. Taking the idea of the accepted answer of Kasrâmvd, make two distributions in the left and right interval. Chose shape parameters such that the mean falls to the given value. The interesting opportunity here is that one can create a continuous PDF, i.e. without jumps where the intervals join.
As an example I have chosen the beta distribution. To have finite non-zero values at the border I've chosen beta =1 for the left and alpha = 1 for the right.
Looking at the definition of the PDF and the requirement of the mean the continuity gives two equations:
4.5 / alpha = 33.5 / beta
2 + 6.5 * alpha / ( alpha + 1 ) + 6.5 + 33.5 * 1 / ( 1 + beta ) = 24
This is a quadratic equation rather easy to solve. The just using scipy.stat.beta like
from scipy.stats import beta
import matplotlib.pyplot as plt
import numpy as np
x1 = np.linspace(2, 6.5, 200 )
x2 = np.linspace(6.5, 40, 200 )
# i use s and t not alpha and beta
s = 1./737 *(np.sqrt(294118) - 418 )
t = 1./99 *(np.sqrt(294118) - 418 )
data1 = beta.rvs(s, 1, loc=2, scale=4.5, size=20000)
data2 = beta.rvs(1, t, loc=6.5, scale=33.5, size=20000)
data = np.concatenate( ( data1, data2 ) )
print np.mean( data1 ), 2 + 4.5 * s/(1.+s)
print np.mean( data2 ), 6.5 + 33.5/(1.+t)
print np.mean( data )
print np.median( data )
fig = plt.figure()
ax = fig.add_subplot( 1, 1, 1 )
ax.hist(data1, bins=13, density=True )
ax.hist(data2, bins=67, density=True )
ax.plot( x1, beta.pdf( x1, s, 1, loc=2, scale=4.5 ) )
ax.plot( x2, beta.pdf( x2, 1, t, loc=6.5, scale=33.5 ) )
ax.set_yscale( 'log' )
plt.show()
provides
>> 2.661366939244768 2.6495436216856976
>> 21.297348804473618 21.3504563783143
>> 11.979357871859191
>> 6.5006779033245135
so results are as required and it looks like:

Cartesian product of two 2d arrays

Suppose I have a 2d image, with associated coordinates (x,y) at every point.
I want to find the inner product of the position vector at every point $i$ with every other point $j$. Essentially, the Cartesian product of two 2d arrays.
What would be the fastest way to accomplish this, in Python?
My current implementation looks something like this:
def cartesian_product(arrays):
broadcastable = np.ix_(*arrays)
broadcasted = np.broadcast_arrays(*broadcastable)
rows, cols = reduce(np.multiply, broadcasted[0].shape), len(broadcasted)
out = np.empty(rows * cols, dtype=broadcasted[0].dtype)
start, end = 0, rows
for a in broadcasted:
out[start:end] = a.reshape(-1)
start, end = end, end + rows
return out.reshape(cols, rows).T
def inner_product():
x, y = np.meshgrid(np.arange(4),np.arange(4))
cart_x = cartesian_product([x.flatten(),x.flatten()])
cart_y = cartesian_product([y.flatten(),y.flatten()])
Nx = x.shape[0]
xx = (cart_x[:,0]*cart_x[:,1]).reshape((Nx**2,Nx,Nx))
yy = (cart_y[:,0]*cart_y[:,1]).reshape((Nx**2,Nx,Nx))
inner_products = xx+yy
return inner_products
(Credit where credit is due: cartesian_product is taken from Using numpy to build an array of all combinations of two arrays)
But this doesn't work. For larger arrays (say, 256x256), this gives me a memory error.
You're probably storing the generated cartesian product.
You're taking product of 2 dimensional arrays. Product of mxm and nxn matrices would produce (mmn*n) values.
For 256*256 matrices it's going to generate 2^32=4,294,967,296 elements.
If you don't need all the values at the same time, you could try storing a few and processing them and disposing them off before generating next values.
Simpler way of taking cartesian product, would be like :
import itertools
xMax = 2
yMax = 2
m1 = [ [ (x + y*xMax) for x in range(xMax)] for y in range(yMax)]
print("m1=" + `m1`)
m2 = [ [ chr(ord('a') + (x + y*xMax)) for x in range(xMax)] for y in range(yMax)]
print("m2=" + `m2`)
for x in m1 :
for y in m2:
for e in itertools.product(x,y): #generating xMax *xMax at at time, process one by one or in batch
print e
Above code will generate following output
m1=[[0, 1], [2, 3]]
m2=[['a', 'b'], ['c', 'd']]
(0, 'a')
(0, 'b')
(1, 'a')
(1, 'b')
(0, 'c')
(0, 'd')
(1, 'c')
(1, 'd')
(2, 'b')
(2, 'a')
(3, 'a')
(3, 'b')
(2, 'c')
(2, 'd')
(3, 'c')
(3, 'd')

How do I generate all of a knight's moves?

I am writing a Chess program in Python that needs to generate all the moves of a knight. For those not familiar with chess, a knight moves in an L shape.
So, given a position of (2, 4) a knight could move to (0, 3), (0, 5), (1, 2), (3, 2), etc. for a total of (at most) eight different moves.
I want to write a function called knight_moves that generates these tuples in a list. What is the easiest way to do this in Python?
def knight_moves(position):
''' Returns a list of new positions given a knight's current position. '''
pass
Ok, so thanks to Niall Byrne, I came up with this:
from itertools import product
def knight_moves(position):
x, y = position
moves = list(product([x-1, x+1],[y-2, y+2])) + list(product([x-2,x+2],[y-1,y+1]))
moves = [(x,y) for x,y in moves if x >= 0 and y >= 0 and x < 8 and y < 8]
return moves
Why not store the relative pairs it can move in ? So take your starting point, and add a set of possible moves away from it, you then would just need a sanity check to make sure they are still in bounds, or not on another piece.
ie given your (2, 4) starting point, the options are (-2,-1), (-2,+1), (-1,+2), (+2,+1)
The relative positions would thus always be the same.
Not familiar with chess...
deltas = [(-2, -1), (-2, +1), (+2, -1), (+2, +1), (-1, -2), (-1, +2), (+1, -2), (+1, +2)]
def knight_moves(position):
valid_position = lambda (x, y): x >= 0 and y >= 0 and ???
return filter(valid_position, map(lambda (x, y): (position[0] + x, position[1] + y), deltas))
Instead of using an array, I would suggest you use bitboards. Not only are they very easy to manipulate, they will also reduce the need for boundary checking. With as few as 12 bitboards, you could probably encode the information you need for the whole game.
https://www.chessprogramming.org/Bitboards
The basic idea of bitboards is to use a 64 bit integer and set 1 if a piece is present on the bit. For example, if you had a 64 bit integer to represent white knights, you would set the 2nd and 6th bits at the starting of the game as they are the positions where the white knights are located. Using this notation, it becomes easy to calculate the knight's moves. It will be easy to calculate other pieces' moves too.
With this representation, you could take a look at this link to the chess engine for a ready made algorithm to implement knight's moves.
http://www.mayothi.com/nagaskakichess6.html
This might sound as an overkill if you're not familiar with analytical geometry (or complex numbers geometry) but I came up with a very elegant mathematical solution when
I was implementing a validation for the movement of pieces.
The knight's moves are lying on a circle which can be defined as
(x-x_0)^2+(y-y_0)^2=5 where x_0 and y_0 are the Knight's current coordinates. If you switch to polar coordinates, you can get all possible coordinates with this simple code:
import math
def knight_moves(x,y):
new_positions=[]
r=math.sqrt(5) #radius of the circle
for phi in [math.atan(2),math.atan(1/2)]: #angles in radians
for quadrant in range(4):
angle=phi+quadrant*math.pi/2 # add 0, 90, 180, 270 degrees in radians
new_x=round(x+r*math.cos(angle))
new_y=round(y+r*math.sin(angle))
if max(new_x,new_y,7-new_x,7-new_y)<=7: #validation whether the move is in grid
new_positions.append([new_x,new_y])
return(new_positions)
def validate_knight_move(x,y,x_0,y_0):
return((x-x_0)**2+(y-y_0)**2==5)
x_0=2
y_0=4
moves=knight_moves(x_0,y_0)
print(moves)
validation=[validate_knight_move(move[0],move[1],x_0,y_0) for move in moves]
print(validation)
[[3, 6], [0, 5], [1, 2], [4, 3], [4, 5], [1, 6], [0, 3], [3, 2]]
[True, True, True, True, True, True, True, True]
It's good to point here, that it is much simpler to validate the position than to construct it directly. Therefore, it might be a good idea to just try whether all possible moves lie on the circle or not:
def knight_moves2(x,y):
new_positions=[]
for dx in [-2,-1,1,2]:
for dy in [-2,-1,1,2]:
if(validate_knight_move(x+dx,y+dy,x,y)): #is knight move?
if max(x+dx,y+dy,7-(x+dx),7-(y+dy))<=7: #validation whether the move is in grid
new_positions.append([x+dx,y+dy])
return(new_positions)
new_positions=knight_moves2(x_0,y_0)
print(new_positions)
[[0, 3], [0, 5], [1, 2], [1, 6], [3, 2], [3, 6], [4, 3], [4, 5]]
Here's an easy implementation:
def knights_moves():
a = []
b = (1, 2)
while 1:
a.append(b)
b = (-b[0], b[1])
a.append(b)
b = (b[1], b[0])
if b in a:
return a
[(1, 2), (-1, 2), (2, -1), (-2, -1), (-1, -2), (1, -2), (-2, 1), (2, 1)]
From there you can just simply add the current position to every member of this list, and then double check for validity.
Completing xiaowl's answer,
possible_places = [(-2, -1), (-2, +1), (+2, -1), (+2, +1), (-1, -2), (-1, +2), (+1, -2), (+1, +2)]
def knight_moves(cur_pos):
onboard = lambda (x, y): x >= 0 and y >= 0 and x<8 and y<8
eval_move = lambda(x,y): (cur_pos[0] + x, cur_pos[1] + y)
return filter(onboard, map(eval_move, possible_places))
For the knights moves:
def getAllValidMoves(x0, y0):
deltas = [(-2, -1), (-2, +1), (+2, -1), (+2, +1), (-1, -2), (-1, +2), (+1, -2), (+1, +2)]
validPositions = []
for (x, y) in deltas:
xCandidate = x0 + x
yCandidate = y0 + y
if 0 < xCandidate < 8 and 0 < yCandidate < 8:
validPositions.append([xCandidate, yCandidate])
return validPositions
print getAllValidMoves(3,3)
I just stored all the possible deltas, applied each one of them to the "initial position" and saved the ones that were inside the chessboard
from itertools import product
def moves():
""" The available (relative) moves"""
a = list(product( (1, -1), (2,-2)))
return a + [tuple(reversed(m)) for m in a]
def neighbors(a,b):
# true if x,y belongs in a chess table
in_table = lambda (x, y): all((x < 8, y < 8, x >= 0, y >= 0))
# returns the possible moving positions
return filter(in_table, [(a+x, b+y) for x, y in moves()])
"neighbors" are the available positions that a knight can go from a,b
The below method is implemented in python. It accepts the board (which can be of any m*n & has values 0(available) and 1(occupied) and current position of knight)
def get_knight_moves(board, position):
KNIGHT_STEPS = ((1, 2), (-1, 2), (1, -2), (-1, -2), (2, 1), (-2, 1), (2, -1), (-2, -1))
knight_moves = []
for (i, j) in KNIGHT_STEPS:
try:
x, y = position[0] + i, position[1] + j
if board[x][y] == 0:
knight_moves.append((x, y))
except IndexError:
pass
print(knight_moves)

Categories

Resources