From 9df5286b596cf95e25e43bdd995d51f4894a5142 Mon Sep 17 00:00:00 2001 From: alanwatsonforster <68709385+alanwatsonforster@users.noreply.github.com> Date: Sat, 23 Sep 2023 13:06:39 -0600 Subject: [PATCH] Move coordinate manipulation into the hex module. --- airpower/aircraft.py | 218 ++++++++++++++++--------------------------- airpower/hex.py | 139 ++++++++++++++++++++++++--- airpower/hexcode.py | 2 +- 3 files changed, 207 insertions(+), 152 deletions(-) diff --git a/airpower/aircraft.py b/airpower/aircraft.py index 2bad36f9..0860be0b 100644 --- a/airpower/aircraft.py +++ b/airpower/aircraft.py @@ -118,85 +118,16 @@ def _reportstatus(self, when): # Elements def _H(self): - dx = { - 0: +1.00, - 30: +1.00, - 60: +0.50, - 90: +0.00, - 120: -0.50, - 150: -1.00, - 180: -1.00, - 210: -1.00, - 240: -0.50, - 270: -0.00, - 300: +0.50, - 330: +1.00 - } - dy = { - 0: +0.00, - 30: +0.50, - 60: +0.75, - 90: +1.00, - 120: +0.75, - 150: +0.50, - 180: +0.00, - 210: -0.50, - 240: -0.75, - 270: -1.00, - 300: -0.75, - 330: -0.50 - } - self._x += dx[self._facing] - self._y += dy[self._facing] - - def _onedge(self): - if self._x % 1 != 0: - return True - elif self._x % 2 == 0 and self._y % 1 == 0.5: - return True - elif self._x % 2 == 1 and self._y % 1 == 0.0: - return True - else: - return False + self._x, self._y = aphex.nextposition(self._x, self._y, self._facing) def _R(self, facingchange): - if self._onedge(): - if self._facing == 0: - self._y -= 0.5 - elif self._facing == 60: - self._x += 0.50 - self._y -= 0.25 - elif self._facing == 120: - self._x += 0.50 - self._y += 0.25 - elif self._facing == 180: - self._y += 0.5 - elif self._facing == 240: - self._x -= 0.50 - self._y += 0.25 - elif self._facing == 300: - self._x -= 0.50 - self._y -= 0.25 + if aphex.isedgeposition(self._x, self._y): + self._x, self._y = aphex.centertoright(self._x, self._y, self._facing) self._facing = (self._facing - facingchange) % 360 def _L(self, facingchange): - if self._onedge(): - if self._facing == 0: - self._y += 0.5 - elif self._facing == 60: - self._x -= 0.50 - self._y += 0.25 - elif self._facing == 120: - self._x -= 0.50 - self._y -= 0.25 - elif self._facing == 180: - self._y -= 0.5 - elif self._facing == 240: - self._x += 0.50 - self._y -= 0.25 - elif self._facing == 300: - self._x += 0.50 - self._y += 0.25 + if aphex.isedgeposition(self._x, self._y): + self._x, self._y = aphex.centertoleft(self._x, self._y, self._facing) self._facing = (self._facing + facingchange) % 360 def _D(self, altitudechange): @@ -221,70 +152,9 @@ def _K(self): def _A(self, what): self._reportevent("attack with %s." % what) - ############################################################################## - - def checkforterraincollision(self): - altitudeofterrain = apaltitude.terrainaltitude() - if self._altitude <= altitudeofterrain: - self._altitude = altitudeofterrain - self._altitudecarry = 0 - self._reportevent("aircraft has collided with terrain at altitude %d." % altitudeofterrain) - self._destroyed = True - - def checkforleavingmap(self): - if not apmap.iswithinmap(self._x, self._y): - self._reportevent("aircraft has left the map.") - self._leftmap = True - - ############################################################################## - - # Turn management + def _getelementdispatchlist(self): - def _restore(self, i): - self._x, self._y, self._facing, self._altitude, self._altitudecarry, self._destroyed, self._leftmap = self._saved[i] - - def _save(self, i): - if len(self._saved) == i: - self._saved.append(None) - self._saved[i] = (self._x, self._y, self._facing, self._altitude, self._altitudecarry, self._destroyed, self._leftmap) - - def _maxprevturn(self): - return len(self._saved) - 1 - - def start(self, turn, nfp, actions): - - if turn > self._maxprevturn() + 1: - raise ValueError("turn %d is out of sequence." % turn) - - self._turn = turn - self._nfp = nfp - self._ifp = 0 - self._ihfp = 0 - self._ivfp = 0 - self._sfp = 0 - self._restore(turn - 1) - - self._reportstartofturn() - if self._destroyed: - self._report("aircraft has been destroyed.") - self._reportendofturn() - self._save(self._turn) - return - - if self._leftmap: - self._report("aircraft has left the map.") - self._reportendofturn() - self._save(self._turn) - return - - self._reportstatus("start") - - if actions != "": - self.next(actions) - - def next(self, actions): - - elements = [ + return [ # This table is searched in order, so put longer elements before shorter # ones that are prefixes (e.g., put C2 before C and D3/4 before D3). @@ -350,8 +220,13 @@ def next(self, actions): ["R" , lambda : self._R(30) , lambda: None], ["S1/2", lambda: self._S(1/2) , lambda: None], + ["S3/2", lambda: self._S(3/2) , lambda: None], ["S½" , lambda: self._S(1/2) , lambda: None], + ["S1½" , lambda: self._S(3/2) , lambda: None], ["S1" , lambda: self._S(1) , lambda: None], + ["S2" , lambda: self._S(2) , lambda: None], + ["SSSS", lambda: self._S(2) , lambda: None], + ["SSS" , lambda: self._S(3/2) , lambda: None], ["SS" , lambda: self._S(1) , lambda: None], ["S" , lambda: self._S(1/2) , lambda: None], @@ -366,6 +241,69 @@ def next(self, actions): ] + ############################################################################## + + def checkforterraincollision(self): + altitudeofterrain = apaltitude.terrainaltitude() + if self._altitude <= altitudeofterrain: + self._altitude = altitudeofterrain + self._altitudecarry = 0 + self._reportevent("aircraft has collided with terrain at altitude %d." % altitudeofterrain) + self._destroyed = True + + def checkforleavingmap(self): + if not apmap.iswithinmap(self._x, self._y): + self._reportevent("aircraft has left the map.") + self._leftmap = True + + ############################################################################## + + # Turn management + + def _restore(self, i): + self._x, self._y, self._facing, self._altitude, self._altitudecarry, self._destroyed, self._leftmap = self._saved[i] + + def _save(self, i): + if len(self._saved) == i: + self._saved.append(None) + self._saved[i] = (self._x, self._y, self._facing, self._altitude, self._altitudecarry, self._destroyed, self._leftmap) + + def _maxprevturn(self): + return len(self._saved) - 1 + + def start(self, turn, nfp, actions): + + if turn > self._maxprevturn() + 1: + raise ValueError("turn %d is out of sequence." % turn) + + self._turn = turn + self._nfp = nfp + self._ifp = 0 + self._ihfp = 0 + self._ivfp = 0 + self._sfp = 0 + self._restore(turn - 1) + + self._reportstartofturn() + if self._destroyed: + self._report("aircraft has been destroyed.") + self._reportendofturn() + self._save(self._turn) + return + + if self._leftmap: + self._report("aircraft has left the map.") + self._reportendofturn() + self._save(self._turn) + return + + self._reportstatus("start") + + if actions != "": + self.next(actions) + + def next(self, actions): + if self._destroyed or self._leftmap: return @@ -386,10 +324,12 @@ def next(self, actions): lastx = self._x lasty = self._y + elementdispatchlist = self._getelementdispatchlist() + # Execute movement elements. a = action while a != "": - for element in elements: + for element in elementdispatchlist: if element[0] == a[:len(element[0])]: element[1]() a = a[len(element[0]):] @@ -408,7 +348,7 @@ def next(self, actions): # Execute other elements. a = action while a != "": - for element in elements: + for element in elementdispatchlist: if element[0] == a[:len(element[0])]: element[2]() a = a[len(element[0]):] diff --git a/airpower/hex.py b/airpower/hex.py index ed37f845..83cda17a 100644 --- a/airpower/hex.py +++ b/airpower/hex.py @@ -1,4 +1,7 @@ -def iscenter(x, y): +from os import X_OK + + +def iscenterposition(x, y): """ Return True if the point (x,y) in hex coordinates corresponds to the center @@ -12,7 +15,7 @@ def iscenter(x, y): else: return False -def isedge(x, y): +def isedgeposition(x, y): """ Return True if the point (x,y) in hex coordinates corresponds to (the center @@ -37,16 +40,19 @@ def isvalidposition(x, y): of a hex or to (the center of) the edge of a hex. Otherwise, return False. """ - return iscenter(x, y) or isedge(x, y) + return iscenterposition(x, y) or isedgeposition(x, y) def areadjacent(x0, y0, x1, y1): """ - Return True if the points (x0,y0) and (x1,y1) correspond the the centers of - adjacent hexes. Otherwise, return False. + Return True if the positions (x0,y0) and (x1,y1) correspond the the centers + of adjacent hexes. Otherwise, return False. """ - if not iscenter(x0, y0) or not isoncenter(x1, y1): + assert isvalidposition(x0, y0) + assert isvalidposition(x1, y1) + + if not iscenterposition(x0, y0) or not isoncenter(x1, y1): return False if abs(x1 - x0) == 1.0 and abs(y1 - y0) == 0.5: return True @@ -68,23 +74,23 @@ def checkisvalidposition(x, y): def isvalidfacing(x, y, facing): """ - Return True if facing is a valid facing at the point (x, y) in hex + Return True if facing is a valid facing at the position (x, y) in hex coordinates, which must correspond to to the center of a hex or to (the center of) the edge of a hex. """ - checkisvalidposition(x, y) + assert isvalidposition(x, y) - if iscenter(x, y): + if iscenterposition(x, y): return facing % 30 == 0 if x % 2 == 0.5 and y % 1.0 == 0.25: return facing % 180 == 120 elif x % 2 == 0.5 and y % 1.0 == 0.75: return facing % 180 == 60 - elif x % 2 == 1.0 and y % 1 == 0.25: + elif x % 2 == 1.5 and y % 1 == 0.25: return facing % 180 == 60 - elif x % 2 == 1.0 and y % 1.0 == 0.75: + elif x % 2 == 1.5 and y % 1.0 == 0.75: return facing % 180 == 120 else: return facing % 180 == 0 @@ -92,10 +98,119 @@ def isvalidfacing(x, y, facing): def checkisvalidfacing(x, y, facing): """ - Raise a ValueError exception if facing is not a valid facing at the point + Raise a ValueError exception if facing is not a valid facing at the position (x, y) in hex coordinates, which must correspond to to the center of a hex or to (the center of) the edge of a hex. """ + assert isvalidposition(x, y) + if not isvalidfacing(x, y, facing): raise ValueError("(%s,%s) is not the center or edge of a hex." % (x,y)) + +def nextposition(x, y, facing): + + """ + Return the coordinates of the next valid position after the point (x, y) with + respect to the facing. + """ + + assert isvalidposition(x, y) + assert isvalidfacing(x, y, facing) + + if facing == 0: + x += +1.00 + y += +0.00 + elif facing == 30: + x += +1.00 + y += +0.50 + elif facing == 60: + x += +0.50 + y += +0.75 + elif facing == 90: + x += +0.00 + y += +1.00 + elif facing == 120: + x += -0.50 + y += +0.75 + elif facing == 150: + x += -1.00 + y += +0.50 + elif facing == 180: + x += -1.00 + y += +0.00 + elif facing == 210: + x += -1.00 + y += -0.50 + elif facing == 240: + x += -0.50 + y += -0.75 + elif facing == 270: + x += -0.00 + y += -1.00 + elif facing == 300: + x += +0.50 + y += -0.75 + elif facing == 330: + x += +1.00 + y += -0.50 + + return x, y + +def centertoleft(x, y, facing): + + """ + Return the coordinates of the hex center to the left of the edge + position (x, y) and where left is defined with respect to the facing. + """ + + assert isedgeposition(x, y) + assert isvalidfacing(x, y, facing) + + if facing == 0: + y += 0.5 + elif facing == 60: + x -= 0.50 + y += 0.25 + elif facing == 120: + x -= 0.50 + y -= 0.25 + elif facing == 180: + y -= 0.5 + elif facing == 240: + x += 0.50 + y -= 0.25 + elif facing == 300: + x += 0.50 + y += 0.25 + + return x, y + +def centertoright(x, y, facing): + + """ + Return the coordinates of the hex center to the left of the edge + position (x, y) and where right is defined with respect to the facing. + """ + assert isedgeposition(x, y) + assert isvalidfacing(x, y, facing) + + if facing == 0: + y -= 0.5 + elif facing == 60: + x += 0.50 + y -= 0.25 + elif facing == 120: + x += 0.50 + y += 0.25 + elif facing == 180: + y += 0.5 + elif facing == 240: + x -= 0.50 + y += 0.25 + elif facing == 300: + x -= 0.50 + y -= 0.25 + + return x, y + diff --git a/airpower/hexcode.py b/airpower/hexcode.py index 3241c0eb..c3d89544 100644 --- a/airpower/hexcode.py +++ b/airpower/hexcode.py @@ -121,7 +121,7 @@ def fromxy(x, y): aphex.checkisvalidposition(x, y) - if aphex.iscenter(x, y): + if aphex.iscenterposition(x, y): sheet = apmap.tosheet(x, y) x0, y0 = apmap.sheetorigin(sheet)