<* M2EXTENSIONS + *> <* STORAGE + *> <* WOFF + *> IMPLEMENTATION MODULE JJSimplePl; FROM IO IMPORT WrStr, WrChar, WrLn, WrCard, WrInt, WrReal, WrBool; FROM SYSTEM IMPORT ADDRESS; FROM StandardLib IMPORT RandomCard; FROM PACSInterface IMPORT StringAr, ObjectEn, ElEn, PlTypeEn, DirEn, CampEn, PosRc, PropRc; FROM PACSInterface IMPORT WrObjectEn, WrDirEn, WrCampEn, WrPosRc, WrPropRc, WrPl; FROM PACSInterface IMPORT Move, MoveIfInArena, IsInArena, INCDirEn, DECDirEn, CalculateDistance, OppositeDir, IsOppositeHome, ToOppositeHome, IsHome, OppositeCamp; FROM PACSInterface IMPORT MoveRcPt, MoveRc, ViewRcPt, ViewRc, PlBehaviourPr, GameConfRc, GetGameConf, GetPoints; FROM PACSInterface IMPORT IsWall, IsInView, GetNbrPl, GetNbrEl, GiveComment, PutMine; (* MEMORY *) VAR (* we remember is our last move, so we will not stupidly go back from where we came *) gPrevDir_Eater, gPrevDir_Hunter, gPrevDir_Explorer, gPrevDir_Ghost: DirEn; (* position where we want to put the next mine *) gNextMinePos: PosRc; (* we will not forget our opponants *) gOpponantTeam: StringAr; VAR (* handy global variables *) gProp: PropRc; (* this are the properties of the current player *) gDebugLevel: [0..10]; (* 0 = show nothing, 10 = show everything see bottom of module for detailed explanation *) (*************************) (* A. General procedures *) (*************************) PROCEDURE IsThereObjectOnPos(view: ViewRcPt; pos: PosRc; object: ObjectEn): BOOLEAN; (* Checks the view if the object is on that position *) VAR view_now: ViewRcPt; BEGIN view_now := view; WHILE (view_now # NIL) DO IF (pos.x = view_now^.pos.x) AND (pos.y = view_now^.pos.y) AND (view_now^.what = object) THEN RETURN TRUE; END; view_now := view_now^.next; END; RETURN FALSE; END IsThereObjectOnPos; PROCEDURE Is_Pl_In_View(pos: PosRc; dir: DirEn; view: ViewRcPt; prop: PropRc): BOOLEAN; (* Checks the view if there is another player in the position he wants to move to (dir), a player that he cannot catch *) VAR toReturn: BOOLEAN; view_now: ViewRcPt; BEGIN Move(pos, dir); (* move imaginary to that position *) toReturn := FALSE; view_now := view; WHILE (view_now # NIL) DO IF (pos.x = view_now^.pos.x) AND (pos.y = view_now^.pos.y) AND NOT IsHome(pos) (* home cell: another player is no problem... *) AND (view_now^.what >= EATER) AND (view_now^.what <= HUNTER) AND NOT((view_now^.camp = OppositeCamp(prop.camp)) (* pl you can catch *) AND ( ((prop.plType = HUNTER) AND (view_now^.what = GHOST)) OR ((prop.plType = GHOST) AND ((view_now^.what = EATER) OR (view_now^.what = EXPLORER))))) THEN toReturn := TRUE; END; view_now := view_now^.next; END; RETURN toReturn; END Is_Pl_In_View; PROCEDURE CanIGoInThatDir(dir, prevDir: DirEn; view: ViewRcPt; inPrevDir: BOOLEAN): BOOLEAN; (* Checks whether it is good to go in that direction: - that there is no wall, no other player and not in opponant home cell - if inPrevDir = FALSE: not back in prev dir *) VAR ok: BOOLEAN; BEGIN IF NOT inPrevDir AND (OppositeDir(dir) = prevDir) THEN ok := FALSE; ELSE IF IsWall(gProp.pos, dir) OR ToOppositeHome(gProp.camp, gProp.pos, dir) OR Is_Pl_In_View(gProp.pos, dir, view, gProp) THEN ok := FALSE; ELSE ok := TRUE; END; END; RETURN ok; END CanIGoInThatDir; PROCEDURE calc_nearest_home(camp: CampEn; pos:PosRc): PosRc; VAR home_left, home_right: PosRc; gameConf: GameConfRc; BEGIN gameConf := GetGameConf(); home_left.y := 1; home_right.y := gameConf.arenaWidth; IF camp = WHITE THEN home_left.x := 1; home_right.x := 1; ELSE home_left.x := gameConf.arenaHeight; home_right.x := gameConf.arenaHeight; END; IF CalculateDistance(pos, home_left) < CalculateDistance(pos, home_right) THEN RETURN home_left; ELSE RETURN home_right; END; END calc_nearest_home; PROCEDURE AddMoveToLL(dir: DirEn; putWall: BOOLEAN; putWallDir: DirEn; VAR movesLL: MoveRcPt); VAR new_move, last_move: MoveRcPt; BEGIN NEW(new_move); new_move^.dir := dir; new_move^.putWall := putWall; new_move^.putWallDir := putWallDir; new_move^.next := NIL; (* append at end of LL *) IF movesLL = NIL THEN movesLL := new_move; ELSE last_move := movesLL; WHILE last_move^.next # NIL DO last_move := last_move^.next; END; last_move^.next := new_move; END; END AddMoveToLL; PROCEDURE Comment(ourCamp: CampEn); VAR theirCamp: CampEn; ourPoints, theirPoints: CARDINAL; BEGIN theirCamp := OppositeCamp(ourCamp); ourPoints := GetPoints(ourCamp); theirPoints := GetPoints(theirCamp); IF ourPoints > 50 THEN IF (ourPoints / 2) > theirPoints THEN GiveComment("This is incredible..."); ELSIF (ourPoints > 100) AND (theirPoints < 70) THEN GiveComment("Looser! Is this all you can do??"); ELSIF ourPoints > theirPoints - 20 THEN GiveComment("Yeah, we are winning..."); END; END; END Comment; PROCEDURE DisposeView(view: ViewRcPt); BEGIN IF view # NIL THEN DisposeView(view^.next); DISPOSE(view); END; END DisposeView; (****************************) (* B. The Action procedures *) (****************************) (* B1 Check View *) PROCEDURE IsThereInRadius(view: ViewRcPt; object: ObjectEn; myPos: PosRc; radius: CARDINAL; VAR objectPos: PosRc): BOOLEAN; (* Check if there is an object in the view in radius distance Return TRUE if found and return the object position If there are more than one, return shortest! *) VAR found: BOOLEAN; distance: CARDINAL; isElement: BOOLEAN; BEGIN IF (object <= MAX(ElEn)) THEN isElement := TRUE; ELSE isElement := FALSE; END; found := FALSE; WHILE view # NIL DO IF (view^.what = object) AND (CalculateDistance(myPos, view^.pos) < radius) AND (isElement OR (gProp.camp # view^.camp)) THEN IF found = FALSE THEN found := TRUE; objectPos := view^.pos; distance := CalculateDistance(myPos, view^.pos); ELSIF CalculateDistance(myPos, view^.pos) < distance THEN objectPos := view^.pos; distance := CalculateDistance(myPos, view^.pos); END; END; view := view^.next; END; IF (gDebugLevel >= 4) AND found THEN WrPosRc(gProp.pos);WrPl(gProp); WrStr("sees "); WrObjectEn(object);WrStr("at ");WrPosRc(objectPos); END; RETURN found; END IsThereInRadius; (* B2. Put Mines *) PROCEDURE CalculateNextMine(); (* we put a mine field between 1/4 arenaHeight & 3/4 arenaHeight, over all the width we put them next to each other, row by row, column by column*) VAR gameConf: GameConfRc; BEGIN gameConf := GetGameConf(); INC(gNextMinePos.x); IF gNextMinePos.x > gameConf.arenaHeight*3/4 THEN gNextMinePos.x := gameConf.arenaHeight/4; IF gNextMinePos.y = gameConf.arenaWidth THEN gNextMinePos.y := 1; ELSE INC(gNextMinePos.y); END; END; END CalculateNextMine; PROCEDURE CheckIfBonusAndPutMine(view: ViewRcPt); (* for explorer: if I catch a bonus, I put a mine on a strategic position... *) BEGIN IF (gProp.plType = EXPLORER) AND IsThereObjectOnPos(view, gProp.pos, BONUS) THEN PutMine(gNextMinePos); CalculateNextMine(); GiveComment("I put a strategical mine!"); END; END CheckIfBonusAndPutMine; (* B3. Random walking *) PROCEDURE FindRandomDir(prevDir: DirEn; view: ViewRcPt; VAR randomDir: DirEn): BOOLEAN; (* pick a random direction, only check that you don't bump into a wall or a player, go into an opponent home cell or go back to the previous direction return FALSE if no dir found *) VAR i, random_nbr: CARDINAL; BEGIN (* find a random direction *) i := 0; REPEAT INC(i); IF i = 20 THEN (* no way to go (after 20 tries) *) RETURN FALSE; END; random_nbr := RandomCard(0, 3); randomDir := VAL(DirEn, random_nbr); UNTIL CanIGoInThatDir(randomDir, prevDir, view, FALSE); RETURN TRUE; END FindRandomDir; PROCEDURE DoRandomMoves(view: ViewRcPt; VAR prevDir: DirEn): MoveRcPt; (* do random moves *) VAR dir: DirEn; i: CARDINAL; moves: MoveRcPt; BEGIN IF gDebugLevel >= 4 THEN WrPosRc(gProp.pos);WrPl(gProp); WrStr("sees nothing: "); WrStr("I just walk around...");WrLn; END; moves := NIL; FOR i := 1 TO gProp.nbrMoves DO IF NOT FindRandomDir(prevDir, view, dir) THEN RETURN moves; END; AddMoveToLL(dir, FALSE, MIN(DirEn), moves); Move(gProp.pos, dir); (* execute the move virtually *) CheckIfBonusAndPutMine(view); prevDir := dir; END; RETURN moves; END DoRandomMoves; (* B3. Goto or runaway *) PROCEDURE Calc_dir(pos1, pos2: PosRc; oppositeDir: BOOLEAN; VAR secondDir: DirEn): DirEn; (* Calculate best direction to go from pos1 to pos 2 second direction in secondDir, if there is no second dir, secondDir = first dir if oppositeDir = TRUE: take the opposite directions (to run away from pos2) *) VAR first_dir: DirEn; x_dist, y_dist: INTEGER; BEGIN x_dist := VAL(INTEGER, pos1.x) - VAL(INTEGER, pos2.x); y_dist := VAL(INTEGER, pos1.y) - VAL(INTEGER, pos2.y); IF ABS(x_dist) > ABS(y_dist) THEN IF x_dist < 0 THEN first_dir := DOWN; ELSE first_dir := UP; END; IF y_dist = 0 THEN secondDir := first_dir; ELSIF y_dist < 0 THEN secondDir := RIGHT; ELSE secondDir := LEFT; END; ELSE (* |y_dist| is the biggest *) IF y_dist < 0 THEN first_dir := RIGHT; ELSE first_dir := LEFT; END; IF x_dist = 0 THEN secondDir := first_dir; ELSIF x_dist < 0 THEN secondDir := DOWN; ELSE secondDir := UP; END; END; IF oppositeDir THEN first_dir := OppositeDir(first_dir); secondDir := OppositeDir(secondDir); END; RETURN first_dir; END Calc_dir; PROCEDURE GoToOrRunAwayFrom(otherPos: PosRc; goto: BOOLEAN; view: ViewRcPt; VAR prevDir: DirEn): MoveRcPt; (* if goto = TRUE: tries to go to the destination, but not very clever if goto = FALSE: Go as far as possible from enemy position: in opposite direction *) VAR moves: MoveRcPt; putWall: BOOLEAN; firstDir, secondDir, dir, wallDir: DirEn; i : CARDINAL; BEGIN IF (gDebugLevel >= 4) THEN IF goto THEN WrStr(": I go to it "); WrLn; ELSE WrStr(": I run away from it "); WrLn; END; END; moves := NIL; FOR i := 1 TO gProp.nbrMoves DO putWall := FALSE; wallDir := MIN(DirEn); (* calculate direction of target or opposite direction *) firstDir := Calc_dir(gProp.pos, otherPos, NOT goto, secondDir); IF (gDebugLevel = 6) THEN WrCard(i, 2); WrStr(": 1st=");WrDirEn(firstDir); WrStr(" 2nd="); WrDirEn(secondDir); END; (* try first *) IF CanIGoInThatDir(firstDir, prevDir, view, TRUE) THEN dir := firstDir; IF (gDebugLevel = 6) THEN WrStr(" 1st ok");WrLn; END; IF (goto = FALSE) AND (gProp.nbrWalls > 0) THEN (* put wall in dir of enemy! *) putWall := TRUE; wallDir := OppositeDir(firstDir); DEC(gProp.nbrWalls); GiveComment("I put a wall against enemy!"); END; ELSIF (firstDir # secondDir) AND CanIGoInThatDir(secondDir, prevDir, view, TRUE) THEN dir := secondDir; IF (gDebugLevel = 6) THEN WrStr(" 2nd ok");WrLn;END; ELSE (* I put firstDir as prevDir, so he will NOT go in the direction of the enemy *) IF NOT goto AND (i = 1) THEN prevDir := firstDir; END; IF NOT FindRandomDir(prevDir, view, dir) THEN IF (gDebugLevel = 6) THEN WrStr(" nok & no random => stop");WrLn; END; RETURN moves; END; IF (gDebugLevel = 6) THEN WrStr(" nok random="); WrDirEn(dir);WrLn;END; END; AddMoveToLL(dir, putWall, wallDir, moves); Move(gProp.pos, dir); (* execute the move virtually *) CheckIfBonusAndPutMine(view); prevDir := dir; END; RETURN moves; END GoToOrRunAwayFrom; (********************) (* C. The behaviour *) (********************) PROCEDURE JJSimpleEater(prop: PropRc; view: ViewRcPt): MoveRcPt; VAR radius: CARDINAL; ghostPos, applePos, nearestHomePos: PosRc; moves: MoveRcPt; BEGIN gProp := prop; radius := prop.nbrMoves + 1; IF IsThereInRadius(view, GHOST, prop.pos, radius, ghostPos) THEN moves := GoToOrRunAwayFrom(ghostPos, FALSE, view, gPrevDir_Eater); ELSIF IsThereInRadius(view, APPLE, prop.pos, radius, applePos) THEN moves := GoToOrRunAwayFrom(applePos, TRUE, view, gPrevDir_Eater); ELSIF prop.nbrApples > 0 THEN nearestHomePos := calc_nearest_home(prop.camp, prop.pos); IF (gDebugLevel >= 4) THEN WrPosRc(gProp.pos);WrPl(gProp);WrStr("sees nothing, carry apples home to ");WrPosRc(nearestHomePos); END; moves := GoToOrRunAwayFrom(nearestHomePos, TRUE, view, gPrevDir_Eater); ELSE moves := DoRandomMoves(view, gPrevDir_Eater); END; Comment(prop.camp); DisposeView(view); (* The dispose MUST happen in your code *) RETURN moves; END JJSimpleEater; PROCEDURE JJSimpleGhost(prop: PropRc; view: ViewRcPt): MoveRcPt; VAR radius: CARDINAL; hunterPos, eaterPos, explorerPos: PosRc; moves: MoveRcPt; BEGIN gProp := prop; radius := prop.nbrMoves + 1; IF IsThereInRadius(view, HUNTER, prop.pos, radius, hunterPos) THEN moves := GoToOrRunAwayFrom(hunterPos, FALSE, view, gPrevDir_Hunter); ELSIF IsThereInRadius(view, EATER, prop.pos, radius, eaterPos) THEN moves := GoToOrRunAwayFrom(eaterPos, TRUE, view, gPrevDir_Ghost); ELSIF IsThereInRadius(view, EXPLORER, prop.pos, radius, explorerPos) THEN moves := GoToOrRunAwayFrom(explorerPos, TRUE, view, gPrevDir_Ghost); ELSE moves := DoRandomMoves(view, gPrevDir_Ghost); END; DisposeView(view); (* The dispose MUST happen in your code *) RETURN moves; END JJSimpleGhost; PROCEDURE JJSimpleHunter(prop: PropRc; view: ViewRcPt): MoveRcPt; VAR radius: CARDINAL; ghostPos: PosRc; moves: MoveRcPt; BEGIN gProp := prop; radius := prop.nbrMoves + 1; IF IsThereInRadius(view, GHOST, prop.pos, radius, ghostPos) THEN moves := GoToOrRunAwayFrom(ghostPos, TRUE, view, gPrevDir_Hunter); ELSE moves := DoRandomMoves(view, gPrevDir_Hunter); END; DisposeView(view); (* The dispose MUST happen in your code *) RETURN moves; END JJSimpleHunter; PROCEDURE JJSimpleExplorer(prop: PropRc; view: ViewRcPt): MoveRcPt; VAR radius: CARDINAL; ghostPos, bonusPos: PosRc; moves: MoveRcPt; BEGIN gProp := prop; radius := prop.nbrMoves + 1; IF IsThereInRadius(view, GHOST, prop.pos, radius, ghostPos) THEN moves := GoToOrRunAwayFrom(ghostPos, FALSE, view, gPrevDir_Explorer); ELSIF IsThereInRadius(view, BONUS, prop.pos, radius, bonusPos) THEN moves := GoToOrRunAwayFrom(bonusPos, TRUE, view, gPrevDir_Explorer); ELSE moves := DoRandomMoves(view, gPrevDir_Explorer); END; DisposeView(view); (* The dispose MUST happen in your code *) RETURN moves; END JJSimpleExplorer; (**********************) (* D. Init & End Team *) (**********************) PROCEDURE JJInitSimples(myCamp: CampEn; opponantTeam: StringAr); (* Initialise memory *) VAR gameConf: GameConfRc; BEGIN IF myCamp = WHITE THEN gPrevDir_Eater := UP; gPrevDir_Hunter := UP; gPrevDir_Explorer := UP; gPrevDir_Ghost := UP; ELSE gPrevDir_Eater := DOWN; gPrevDir_Hunter := DOWN; gPrevDir_Explorer := DOWN; gPrevDir_Ghost := DOWN; END; gameConf := GetGameConf(); (* put first mine on that pos *) gNextMinePos.x := RandomCard(gameConf.arenaHeight/4, gameConf.arenaHeight*3/4); gNextMinePos.y := RandomCard(1, gameConf.arenaWidth); gOpponantTeam := opponantTeam; END JJInitSimples; PROCEDURE JJEndSimples(weWin: BOOLEAN); (* Dispose allocated memory *) BEGIN IF weWin THEN GiveComment("Joepie, we won..."); ELSE GiveComment("Merde, we lost..."); END; END JJEndSimples; BEGIN (* global variable gDebug is used to debug the code & show what the behaviour does set debuglevel: (higher => more details) = 0 show nothing >= 2 only show special things >= 4 show where he goes to or run from = 6 show decisions on directions = 8 show details *) gDebugLevel := 0; (* configuring the team *) JJSimples.name := "The JJ Simples"; JJSimples.players[EATER] := JJSimpleEater; JJSimples.players[EXPLORER] := JJSimpleExplorer; JJSimples.players[HUNTER] := JJSimpleHunter; JJSimples.players[GHOST] := JJSimpleGhost; JJSimples.initTeam := JJInitSimples; JJSimples.endTeam := JJEndSimples; END JJSimplePl.