Enable Javascript in your browser and then refresh this page, for a much enhanced experience.
Fortress Cannons solution in Clear category for Fortress Cannons by JimmyCarlos
import itertools
class Position:
def __init__(self,C,R):
self.C = C
self.R = int(R)
self.isUpColumn = self.C in "ACEGIK"
def __repr__(self):
return "{}{}".format(self.C,self.R)
def __eq__(self,other):
return (self.R,self.C) == (other.R,other.C)
def __hash__(self): # Allows instances of this class to be put into a set.
return 0
prev_letter=lambda self,c: chr(ord(c)-1)
next_letter=lambda self,c: chr(ord(c)+1)
def move_N(self):
if self.R != 1: return Position(self.C,self.R-1)
def move_NE(self):
if self.isUpColumn and self.C != "L" and self.R != 1: return Position(self.next_letter(self.C),self.R-1)
elif not self.isUpColumn and self.C != "L": return Position(self.next_letter(self.C),self.R)
def move_SE(self):
if self.isUpColumn and self.C != "L": return Position(self.next_letter(self.C),self.R)
elif not self.isUpColumn and self.C != "L" and self.R != 9: return Position(self.next_letter(self.C),self.R+1)
def move_S(self):
if self.R != 9: return Position(self.C,self.R+1)
def move_SW(self):
if self.isUpColumn and self.C != "A": return Position(self.prev_letter(self.C),self.R)
elif not self.isUpColumn and self.C != "A" and self.R != 9: return Position(self.prev_letter(self.C),self.R+1)
def move_NW(self):
if self.isUpColumn and self.C != "A" and self.R != 1: return Position(self.prev_letter(self.C),self.R-1)
elif not self.isUpColumn and self.C != "A": return Position(self.prev_letter(self.C),self.R)
def is_reachable_enemy(startPosition,targetPosition,startDirection,cannon):
# Turn the strings to Position Objects
startPosition,targetPosition = Position(*startPosition),Position(*targetPosition)
firing_arc,min_range,max_range = cannon
# Try to work out which way is forward, left, right and backward.
DIRECTIONS = ["N","NE","SE","S","SW","NW"]
di = DIRECTIONS.index(startDirection)
adj_directions = [DIRECTIONS[(di-1)%6],DIRECTIONS[(di+1)%6]]
# Use a BFS to attempt to find the target.
nodesChecked,nodesToCheck,nodesDiscovered = set(),{startPosition},set()
for distance_away in range(1,max_range+1):
for node in nodesToCheck:
directionsAllowed = [startDirection]
if firing_arc == 120 or (firing_arc == 60 and distance_away % 2 == 0): directionsAllowed += adj_directions
for direction in directionsAllowed:
newNode = eval("node.move_{}()".format(direction))
if newNode is None: continue # Tile is at edge of board.
if all(newNode not in S for S in (nodesChecked,nodesToCheck,nodesDiscovered)):
nodesDiscovered.add(newNode) # A different tile was created, explore that one.
if distance_away >= min_range and targetPosition in nodesDiscovered: return True
nodesChecked,nodesToCheck,nodesDiscovered = nodesChecked|nodesToCheck,nodesDiscovered,set()
else:
return False
def fortress_cannons(fort, cannons, enemies):
directions = ["N","NE","SE","S","SW","NW"]
for cannon_directions in itertools.product(directions,repeat=len(cannons)):
if all(any(is_reachable_enemy(fort,enemy,d,c) for d,c in zip(cannon_directions,cannons)) for enemy in enemies):
return list(cannon_directions)
Aug. 13, 2019