-- | Basic operations on 2D vectors represented in an efficient,-- but not unique, way.moduleGame.LambdaHack.Vector(Vector,toVector,shift,shiftBounded,moves,movesWidth,isUnit,euclidDistSq,diagonal,neg,towards,displacement,displacePath,shiftPath)whereimportData.BinaryimportGame.LambdaHack.PointXYimportGame.LambdaHack.VectorXYimportGame.LambdaHack.AreaimportGame.LambdaHack.PointimportGame.LambdaHack.Utils.Assert-- | 2D vectors represented as offsets in the linear framebuffer-- indexed by 'Point'.---- A newtype is used to prevent mixing up the type with @Point@ itself.-- Note that the offset representations of a vector is usually not unique.-- E.g., for vectors of length 1 in the chessboard metric, used to denote-- geographical directions, the representations are pairwise distinct-- if and only if the level width and height are at least 3.newtypeVector=VectorIntderiving(Show,Eq)instanceBinaryVectorwhereput(Vectordir)=putdirget=fmapVectorget-- | Converts a vector in cartesian representation into @Vector@.toVector::X->VectorXY->VectortoVectorlxsize(VectorXY(x,y))=Vector$x+y*lxsizeisUnitXY::VectorXY->BoolisUnitXYv=chessDistXYv==1-- | Tells if a vector has length 1 in the chessboard metric.isUnit::X->Vector->BoolisUnitlxsize=isUnitXY.fromDirlxsize-- | Converts a unit vector in cartesian representation into @Vector@.toDir::X->VectorXY->VectortoDirlxsizev@(VectorXY(x,y))=assert(lxsize>=3&&isUnitXYv`blame`(lxsize,v))$Vector$x+y*lxsize-- | Converts a unit vector in the offset representation-- into the cartesian representation. Arbitrary vectors can't be-- converted uniquely.fromDir::X->Vector->VectorXYfromDirlxsize(Vectordir)=assert(lxsize>=3&&isUnitXYres&&fstlen1+sndlen1*lxsize==dir`blame`(lxsize,dir,res))$reswhere(x,y)=(dir`mod`lxsize,dir`div`lxsize)-- Pick the vector's canonical form of length 1:len1=ifx>1then(x-lxsize,y+1)else(x,y)res=VectorXYlen1-- | Translate a point by a vector.---- Particularly simple and fast implementation in the linear representation.shift::Point->Vector->Pointshiftloc(Vectordir)=loc+dir-- | Translate a point by a vector, but only if the result fits in an area.shiftBounded::X->Area->Point->Vector->PointshiftBoundedlxsizearealocdir=letres=shiftlocdirinifinsidelxsizeresareathenreselseloc-- | Vectors of all unit moves, clockwise, starting north-west.moves::X->[Vector]moveslxsize=map(toDirlxsize)movesXY-- | Vectors of all unit moves, clockwise, starting north-west,-- parameterized by level width.movesWidth::[X->Vector]movesWidth=map(fliptoDir)movesXY-- | Squared euclidean distance between two unit vectors.euclidDistSq::X->Vector->Vector->InteuclidDistSqlxsizedir0dir1|VectorXY(x0,y0)<-fromDirlxsizedir0,VectorXY(x1,y1)<-fromDirlxsizedir1=euclidDistSqXY$VectorXY(x1-x0,y1-y0)-- | Checks whether a unit vector is a diagonal direction,-- as opposed to cardinal.diagonal::X->Vector->Booldiagonallxsizedir|VectorXY(x,y)<-fromDirlxsizedir=x*y/=0-- | Reverse an arbirary vector.neg::Vector->Vectorneg(Vectordir)=Vector(-dir)-- TODO: use bla for that-- | Given a vector of arbitrary non-zero length, produce a unit vector-- that points in the same direction (in the chessboard metric).-- Of several equally good directions it picks one of those that visually-- (in the euclidean metric) maximally align with the original vector.normalize::X->VectorXY->Vectornormalizelxsizev@(VectorXY(dx,dy))=assert(dx/=0||dy/=0`blame`(dx,dy))$letangle::Doubleangle=atan(fromIntegraldy/fromIntegraldx)/(pi/2)dxy|angle<=-0.75=(0,-1)|angle<=-0.25=(1,-1)|angle<=0.25=(1,0)|angle<=0.75=(1,1)|angle<=1.25=(0,1)|otherwise=assert`failure`(lxsize,dx,dy,angle)rxy=ifdx>=0thenVectorXYdxyelsenegXY$VectorXYdxyinassert((ifisUnitXYvthenv==rxyelseTrue)`blame`(v,rxy))$toDirlxsizerxy-- TODO: Perhaps produce all acceptable directions and let AI choose.-- That would also eliminate the Doubles. Or only directions from bla?-- Smart monster could really use all dirs to be less predictable,-- but it wouldn't look as natural as bla, so for less smart bla is better.-- | Given two distinct locations, determine the direction (a unit vector)-- in which one should move from the first in order to get closer-- to the second. Ignores obstacles. Of several equally good directions-- (in the chessboard metric) it picks one of those that visually-- (in the euclidean metric) maximally align with the vector between-- the two points..towards::X->Point->Point->Vectortowardslxsizeloc0loc1=assert(loc0/=loc1`blame`(loc0,loc1))$letv=displacementXYZlxsizeloc0loc1innormalizelxsizev-- | A vector from a point to another. We have---- > shift loc1 (displacement loc1 loc2) == loc2---- Particularly simple and fast implementation in the linear representation.displacement::Point->Point->Vectordisplacementloc1loc2=Vector$loc2-loc1-- | A list of vectors between a list of points.displacePath::[Point]->[Vector]displacePath[]=[]displacePathlp1@(_:lp2)=map(uncurrydisplacement)$ziplp1lp2-- | A list of points that a list of vectors leads to.shiftPath::Point->[Vector]->[Point]shiftPath_[]=[]shiftPathstart(v:vs)=letnext=shiftstartvinnext:shiftPathnextvs