You can solve that in A* if you can express "smooth" as a path expansion limitation.
A* works by extending path by one position within the limits of your entity that moves.
Normally, the only limits of the entity are not crossing obstacles on the map. If your entity has no limitations in movement (the usual case), you can simplify A* by only looking at the map to extend paths. However, you can add more limitations to the movement of your entity, reducing the number of path extensions that can be made each step, without a problem.
If you define "smooth" as driving at least 3 steps in a single direction, and turning at most 45 degrees after 3 or more steps, you not only need a position of the entity, but also the current direction and how many steps to take before it can turn again. A "position" is thus not just position p, but also direction d (0..7), and number of remaining steps s (3..0) before you can turn again.
A "position" in A* thus becomes a triplet (p, d, s). It becomes possible that several entities in the open or closed list are at the same p, but with different d or s (different directions of movement or different number of remaining steps).
This is the disadvantage of the approach, the number of points in an open or closed list becomes larger (here, theoretically, maximum by a factor 3*7 = 21).
Path expansion in the usual form within A* is a function that takes a position p, and constructs a list successor points. In the above case it takes a triplet (p, d, s) and produces a list new triplets (p', d', s').
In code:
DELTA_XY = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
# Usual expansion, ignoring obstacles on the map.
def expand(pos):
x, y = pos # 'pos' is a x,y pair
newposlist = []
for dx, dy in DELTA_DXY:
newposlist.append( (x+dx, y+dy) )
return newposlist
# Limited expansion, ignoring obstacles on the map.
def expand(pos, dir, steps):
x, y = pos
if steps > 0: # Can't turn, continue moving in direction 'dir'
dx, dy = DELTA_XY[dir]
return [ ((x+dx, y+dy), dir, steps - 1) ]
else: # Can turn 45 degrees
newposlist = []
for dd in [7, 0, 1]:
newdir = (dir + dd) % 8 # Turn, but limit to 0..7 again
dx, dy = DELTA_XY[newdir]
if dd == 0:
newsteps = 0
else:
newsteps = 3
newposlist.append( ((x+dx, y+dy), newdir, newsteps) )
return newposlist