From a0a3db96c0c5641cd4910dec805c1aa0b54312ac Mon Sep 17 00:00:00 2001 From: michael Date: Thu, 18 Jul 2019 16:26:01 +0000 Subject: [PATCH] * Initial version --- demo/pacman/audio/die.mp3 | Bin 0 -> 8556 bytes demo/pacman/audio/eatghost.mp3 | Bin 0 -> 3808 bytes demo/pacman/audio/eating.mp3 | Bin 0 -> 5704 bytes demo/pacman/audio/start.mp3 | Bin 0 -> 40159 bytes demo/pacman/cherry.png | Bin 0 -> 428 bytes demo/pacman/ghost-scared.png | Bin 0 -> 407 bytes demo/pacman/ghost1.png | Bin 0 -> 431 bytes demo/pacman/ghost2.png | Bin 0 -> 446 bytes demo/pacman/ghost3.png | Bin 0 -> 426 bytes demo/pacman/ghost4.png | Bin 0 -> 439 bytes demo/pacman/pacman.css | 189 ++++ demo/pacman/pacman.html | 94 ++ demo/pacman/pacman.lpi | 96 +++ demo/pacman/pacman.lpr | 30 + demo/pacman/upacman.pp | 1465 ++++++++++++++++++++++++++++++++ 15 files changed, 1874 insertions(+) create mode 100644 demo/pacman/audio/die.mp3 create mode 100644 demo/pacman/audio/eatghost.mp3 create mode 100644 demo/pacman/audio/eating.mp3 create mode 100644 demo/pacman/audio/start.mp3 create mode 100644 demo/pacman/cherry.png create mode 100644 demo/pacman/ghost-scared.png create mode 100644 demo/pacman/ghost1.png create mode 100644 demo/pacman/ghost2.png create mode 100644 demo/pacman/ghost3.png create mode 100644 demo/pacman/ghost4.png create mode 100644 demo/pacman/pacman.css create mode 100644 demo/pacman/pacman.html create mode 100644 demo/pacman/pacman.lpi create mode 100644 demo/pacman/pacman.lpr create mode 100644 demo/pacman/upacman.pp diff --git a/demo/pacman/audio/die.mp3 b/demo/pacman/audio/die.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..57e32c9af9eaa41870f79faf843fac61459e6548 GIT binary patch literal 8556 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 GH~kOr1o315 literal 0 HcmV?d00001 diff --git a/demo/pacman/audio/eatghost.mp3 b/demo/pacman/audio/eatghost.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0bccf3b1365b2333a1427a2ea0bd276c11b3e20e GIT binary patch literal 3808 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 Jik1I|{{hQrDQf@# literal 0 HcmV?d00001 diff --git a/demo/pacman/audio/eating.mp3 b/demo/pacman/audio/eating.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7630181e4ea979c96e76dbe0e52bb40c0a723e68 GIT binary patch literal 5704 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 literal 0 HcmV?d00001 diff --git a/demo/pacman/ghost-scared.png b/demo/pacman/ghost-scared.png new file mode 100644 index 0000000000000000000000000000000000000000..861f5577c6d532d6b15322ae935a1b5b3439558c GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*t3?v(r4=o2$Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCjKou5Hn!&&zUNC1@pbbz$D8MJg6-Wb>FfjaYXlOWa z;K2X?|0fmf+6xq8O!9VjVf~T+@;H#gUgGKN%6^NPorj-A=jAsepiq;ii(`ny<*64g z@*Y;;VF|d*c|f?OS)M8&}t;1Ql-*@D~$vSi*Vmvg@IZ&kclo_Xz&G z@n@^N*PKZc?M|$gO?%RqZhmNohUx!}3TIt4CudD|z4^J~`n=hj9<+u8oO5@*d9t}D z?%nZ-iu5VwXN#!lvI6-E$sR$z z3=CCjKou5Hn!&&zUNC1@pbbz$G{7gs6-YBQfB?h){|62n z`2YVu1H=D~o@@<3amFNXcNf+l`7e(HIqW5#zOL-InAv%Zcsy>I?E?x;_jGX#k+{6{ zLM-1Q1p$_X9Q}Z|f>QSzH^wo1WVrU0wdc-%4kPbOQ?-2-oYHo6LSo7LiWwe!4`nu4 zx8mp|zsS&S<=T#B^K;BU745&MwfGL>L{G~(E-F1cMT*Q^*G&xI>aI>+vuA1Y1WV6= zB){_pahF-+D*gDybYec6*Z$L<{ky2uto%;l7q7bhzP2;^(l-2(kI%l}!>~Nk>iEp2 zR~vy&R4s9hC`m~yNwrEYN(E93Mh1qKx(4RDhQ=X=2398KRwkC(21Zr}1}>8y-$K!l vo1c=IRteHzple_Z)DvQ8WMyh#WoV>rUgTe~DWM4fZp(~G literal 0 HcmV?d00001 diff --git a/demo/pacman/ghost2.png b/demo/pacman/ghost2.png new file mode 100644 index 0000000000000000000000000000000000000000..74402635ddbc23bcbeb3947df3d12283c6571487 GIT binary patch literal 446 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*t3?v(r4=o2$Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCjKou5Hn!&&zUNC1@pbbz$BEToa6{G-&8W{fn2crK6 z4jf=$_}|dbP`S#L4=Bf&5^q_h(wl8~feZOlrgJ zWr9M(mLKsU1I` z-8EcZBtE@{{oGGY7QUZcZ$3M(TJl`|UAfcabpHPrgOr?WFP<{Fvhk52hiK@gxIkXp z$zB(8b?g(^f?ip?ntJUkFjyE=OI#yLQW8s2t&)pUffR$0fuW_Yfw``safqRTm5I5P ziKVuIk(Ggg%jCzmP&DM`r(~v8f;1TD8W`ysnTHq|TN#*HnOJBW7y>nLd@Fkc)WG2B L>gTe~DWM4f;7gO5 literal 0 HcmV?d00001 diff --git a/demo/pacman/ghost3.png b/demo/pacman/ghost3.png new file mode 100644 index 0000000000000000000000000000000000000000..e0948894d11a09d02a38e3c7d0c11b7bd19831b1 GIT binary patch literal 426 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*t3?v(r4=o2$Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCjKou5Hn!&&zUNC1@pbbz$D8MJg6{G+N7#I#5IPm}f ze+GvCb}NOB07V&-yxm<``ZI0@0y*p@p1!W^x0s~G*%_`hnyLbYrh2+KhDcnVdm)hT zkOB`|LV~zNa<%zQ);ad`8sZN4ysHrDy3T zuGvMoN0zQ%d$qmbZM64iab>|0VLpXLA=})#{#VYQ=^e6e!5;M`e|(I@_@~O(c-|}3 zd3H#$DC14L-N6rP`!kF8usuC{;&7$q(~Kt;4#CMk7ymr}cFD{1Jq%kUftDnm{r-UW|aQ%-- literal 0 HcmV?d00001 diff --git a/demo/pacman/ghost4.png b/demo/pacman/ghost4.png new file mode 100644 index 0000000000000000000000000000000000000000..d24576a9800b63a58a64508822e907c99475b910 GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*t3?v(r4=o2$Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCjKou5Hn!&&zUNC1@pbbz$BEToa6{LWH;XebzfddEr z|Nr060OB<;H1ODLSp$?~O!9VjVd>Af83^RCmw5WRvfpBo7U#4`Vpw1b6q@7d;us=v zdFn+^zC#8)tQWSZb67I|OT7^u$nU_*&TvF8y-$K!lo1c=IRteHzple{HYh)f`WNc+%X=Q4rZD0u0keU&;2&jR<)78&qol`;+ E0809e(*OVf literal 0 HcmV?d00001 diff --git a/demo/pacman/pacman.css b/demo/pacman/pacman.css new file mode 100644 index 0000000..ad6c149 --- /dev/null +++ b/demo/pacman/pacman.css @@ -0,0 +1,189 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: 'Roboto', serif; +} + +.progress { + border: 2px solid #B0B0B0; + border-radius: 5px; +} + +.progress-inner { + height: 20px; + margin: 2px; +} + +#pacman { + display: flex; + width: 560px; + margin: 3em auto; +} + +#my-canvas { + width: 482px; + height: 528px; +} + +#info { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 470px; + width: 240px; +} + +#info > #logo { + flex-basis: auto; + margin-top: 7px; + padding: 5px 0; + display: flex; + align-items: center; + justify-content: center; + background-color: #b8caf4; +} + +#info > #score-envelope { + flex-basis: 8%; + border: 2px solid #B0B0B0; + border-radius: 5px; + display: flex; + align-items: center; + font-weight: bold; +} + +#info > #highscore-envelope { + flex-basis: 8%; + border: 2px solid #B0B0B0; + border-radius: 5px; + display: flex; + align-items: center; + font-weight: bold; +} + +#info > #ghosts-envelope { + flex-basis: 8%; + border: 2px solid #B0B0B0; + border-radius: 5px; + display: flex; + align-items: center; + font-weight: bold; +} + + +#info > #bonus-envelope { + flex-basis: 8%; + border: 2px solid #B0B0B0; + border-radius: 5px; + display: flex; + align-items: center; + font-weight: bold; +} + +#info > #lives-envelope { + flex-basis: 8%; + border: 2px solid #B0B0B0; + border-radius: 5px; + display: flex; + align-items: center; + font-weight: bold; +} + +#info > #status-envelope { + flex-basis: 8%; + border: 2px solid #B0B0B0; + border-radius: 5px; + display: flex; + align-items: center; + font-weight: bold; +} + +#info > #controls-envelope { + flex-basis: auto; + border: 2px solid #B0B0B0; + border-radius: 5px; + padding: 0.5em 0; + display: flex; + flex-direction: column; + justify-content: center; +} + +#info > #btn-reset { + flex-basis: auto; +} + +.button { + margin: 0.4em; + padding: 1em; + text-align: center; + cursor: pointer; + outline: none; + color: #fff; + min-width: 100px; + font-weight: bold; + background-color: #537fe4; + border: none; + border-radius: 15px; + box-shadow: 1px 2px #aaa; + } + +.button:hover { + background-color: #2B60DE; +} + +.button:active { + background-color: #2B60DE; + box-shadow: 0 3px #888; + transform: translateY(2px); +} + +#controls > .one-btn { + display: flex; + justify-content: center; +} + +#controls > .two-btn { + display: flex; + justify-content: space-between; +} + +.label { + text-transform: uppercase; + font-weight: bold; + margin-right: 0.5em; + padding-left: 0.5em; +} + +.source { + display: flex; + width: 540px; + margin: -40px auto; + font-size: 12px; +} + +#btn-reset{ + background: #2B60DE; + color: #fff; + font-weight: bold; + border: 0; + padding: 12px 0; + width: 100%; + border-radius: 5px; + text-transform: uppercase; + font-size: 14px; + cursor: pointer; + outline: none; +} + +#btn-reset:hover { + background-color: #1e4fc2; +} + +#btn-reset:active { + background-color: #1e4fc2; + transform: translateY(2px); +} diff --git a/demo/pacman/pacman.html b/demo/pacman/pacman.html new file mode 100644 index 0000000..692b5ab --- /dev/null +++ b/demo/pacman/pacman.html @@ -0,0 +1,94 @@ + + + + + + + Pacman using pas2js + + + + + + + + + +
+
+ +
+
+ +
+
+
+
+
+
+
+
High Score:
+
0
+
+
+
Score:
+
0
+
+
+
Lives:
+
3
+
+
+
Game status:
+
Playing
+
+
+
+
0
+
+
+
+
0
+
+
+
Controls:
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
Sound:
+
+
+
+
+
+ Created using   pas2js.   Sources:   Program   unit. +
+
+ + + + +
+ + + diff --git a/demo/pacman/pacman.lpi b/demo/pacman/pacman.lpi new file mode 100644 index 0000000..84c51ef --- /dev/null +++ b/demo/pacman/pacman.lpi @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + <UseAppBundle Value="False"/> + <ResourceType Value="res"/> + </General> + <CustomData Count="5"> + <Item0 Name="MaintainHTML" Value="1"/> + <Item1 Name="PasJSHTMLFile" Value="project1.html"/> + <Item2 Name="PasJSPort" Value="0"/> + <Item3 Name="PasJSWebBrowserProject" Value="1"/> + <Item4 Name="RunAtReady" Value="1"/> + </CustomData> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + <Modes Count="0"/> + </RunParams> + <Units> + <Unit> + <Filename Value="pacman.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="pacman.html"/> + <IsPartOfProject Value="True"/> + <CustomData Count="1"> + <Item0 Name="PasJSIsProjectHTMLFile" Value="1"/> + </CustomData> + </Unit> + <Unit> + <Filename Value="upacman.pp"/> + <IsPartOfProject Value="True"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target FileExt=".js"> + <Filename Value="pacman"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="js"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <AllowLabel Value="False"/> + <CPPInline Value="False"/> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> + <CodeGeneration> + <TargetOS Value="browser"/> + </CodeGeneration> + <Linking> + <Debugging> + <GenerateDebugInfo Value="False"/> + <UseLineInfoUnit Value="False"/> + </Debugging> + </Linking> + <Other> + <CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/> + <CompilerPath Value="$(pas2js)"/> + </Other> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/demo/pacman/pacman.lpr b/demo/pacman/pacman.lpr new file mode 100644 index 0000000..3ad3ee6 --- /dev/null +++ b/demo/pacman/pacman.lpr @@ -0,0 +1,30 @@ +program pacman; + +{$mode objfpc} + +uses + browserapp, JS, Classes, SysUtils, Web, upacman; + +type + TMyApplication = class(TBrowserApplication) + FPacMan : TPacMan; + procedure doRun; override; + end; + +procedure TMyApplication.doRun; + +begin + FPacMan:=TPacMan.Create(Self); + // Your code here + Terminate; +end; + +var + Application : TMyApplication; + +begin + Application:=TMyApplication.Create(nil); + Application.Initialize; + Application.Run; +// Application.Free; +end. diff --git a/demo/pacman/upacman.pp b/demo/pacman/upacman.pp new file mode 100644 index 0000000..4b6062b --- /dev/null +++ b/demo/pacman/upacman.pp @@ -0,0 +1,1465 @@ +unit upacman; + +interface + +uses + sysutils, classes, types, web, js; + +const + TimerInterval = 20; + GridXSize = 30; + GridYSize = 33; + DrawGrid = False; + + ControlCount = 5; + ControlNames : Array[1..ControlCount] of string = ('left','right','down','up','pause'); + + +type + TAudio = (aStart,aDie,aEatGhost,aEatPill); + + TStr4 = String; // set of N,E,S,W + + TSprite=record + SpImg : TJSHTMLImageElement; // Image of a ghost + XY : TPoint; // Grid x and y + Sx,Sy : double; // smooth x and y between 0 en 1 + Dir : char; // N,E,S,W + Spd : double; + StartPos : TPoint; + end; + + TCell=record + WallType :(wtNone,wtEW,wtNS,wtNE,wtNW,wtSW,wtSE,wtNoGo); + PillType :(ptNone,ptPill,ptSuperPill); + I :integer; // used for searching the maze + Dirty : Boolean; + end; + + TField = array[0..GridYSize-1] of String; + + { TPacman } + TProcedure = Procedure Of Object; + + { TPacmanAudio } + + TPacmanAudio = Class + private + FOnLoaded: TNotifyEvent; + procedure AudioLoaded; + function CheckEnd(Event: TEventListenerEvent): boolean; + function CheckplayOK(Event: TEventListenerEvent): boolean; + published + files : Array [TAudio] of TJSHTMLAudioElement; + filesOK : Array [TAudio] of Boolean; + Playing : Array [TAudio] of Boolean; + Procedure LoadAudio; + Procedure play(aAudio : Taudio); + Procedure DisableSound; + Procedure Pause; + Procedure Resume; + Property OnLoaded : TNotifyEvent Read FOnLoaded Write FonLoaded; + end; + + TPacman = class(TComponent) + Private + // Html image elements + // 0 = pacman, virtual + // 1..4 : Ghost + // 5 = scared + ImgGhost : Array[0..5] of TJSHTMLImageElement; + ImgBonus: TJSHTMLImageElement; + SpriteTimer: NativeInt; + pnBonusBarOuter: TJSHTMLElement; + pnBonusBarInner: TJSHTMLElement; + pnScareBarOuter: TJSHTMLElement; + pnScareBarInner: TJSHTMLElement; + lbBonusCnt: TJSHTMLElement; + lbLives: TJSHTMLElement; + lbScore: TJSHTMLElement; + lbHiscore: TJSHTMLElement; + lbGhostCnt: TJSHTMLElement; + FCanvasEl:TJSHTMLCanvasElement; + FCanvas:TJSCanvasRenderingContext2D; + FCBXSound:TJSHTMLInputElement; + FBtnReset : TJSHTMLButtonElement; + FAudio : TPacmanAudio; + function CheckSound(Event: TEventListenerEvent): boolean; + procedure DoAudioLoaded(Sender: TObject); + function DoResetClick(aEvent: TJSMouseEvent): boolean; + procedure InitAudio; + procedure MarkCellsDirty; + private + FAudioDisabled: Boolean; + FCanvasID: String; + FResetID: String; + Pause:boolean; + LivesLeft:integer; + BonusCnt :integer; + GhostCnt :integer; + BonusTimer:integer; + ScareTimer:integer; + PacMouthOpen:integer; + PacMouthOpenDir:integer; + PillsLeft:integer; + PacmanDir:char; + score,HiScore:integer; + // 0: Packman. + // 1..4 : ghost + // 5: Bonus + Sprite:array[0..5] of TSprite; + Cells:array[0..GridXSize-1,0..GridYSize] of TCell; + FDying : Boolean; +// Maze solving code + function SolveMaze (P1,P2: TPoint): boolean; + function SolveMazeStep1(P1,P2: TPoint): boolean; + function SolveMazeStep2(P1,P2: TPoint): boolean; + function SolveMazeStep3(P1,P2: TPoint): boolean; +// Display code + procedure line(x1, y1, x2, y2: integer); + procedure DrawCells(DirtyOnly : Boolean = False); + procedure DrawPacman(); + procedure CheckGameOver; + procedure StartTimer; + procedure ShowText(aText: string; OnDone : TProcedure); + procedure UpdateScore(); +// Initializing code + procedure InitSprite(var aSprite: TSprite; aImg: TJSHTMLImageElement; aSpd: Double); + procedure InitSprites(); + procedure InitVars(aField: TField); + procedure InitCells(aField: TField); + procedure SetGhostScared(aScared: boolean); +// Business code: TestAndGet + function GetGhostDir(aXY:TPoint; aOldDir: char): char; + function GetBestDir(aXY:TPoint): char; + function GetPossibleDir(aXY:TPoint): TStr4; + function GetPacmanDir(aXY:TPoint; aOldDir: char): char; + procedure GetRandomCellAndDir(var aXY:TPoint; var aDir: char); +// Business code: Actions + procedure StopTimer; + Function DoRestartClick(aEvent: TJSMouseEvent): boolean; + procedure EatPill(aXY: TPoint); + procedure EatSuperPill(aXY: TPoint); + procedure EatBonus(); + procedure EatGhost(var aGhost: TSprite); + procedure ClearCell(aXY: TPoint); + procedure MoveSprite(aSpriteInx:integer); + function DoBonusTimer(): boolean; + procedure DoScareTimer(); + Procedure DrawScene; +// Business code: Decisions + procedure CollisionDetect(var aXY:TPoint); + procedure RestartGame(); + procedure RestartLevel(); + procedure PacmanDies(); + procedure NextLevel(); + procedure GameOver(); + // Debug & Test + // procedure DbgShow(); + // Business code: Actions + Procedure playsound(aAudio : TAudio); + procedure DoSpriteTimer; + // User response code + function HandleKeyPress(k : TJSKeyBoardEvent) : Boolean; + function DoMouseClick(aEvent: TJSMouseEvent): boolean; + Public + // Initializing code + Constructor Create(aOwner : TComponent); override; + Procedure SetupPacman; + Procedure Start; + Property CanvasID : String Read FCanvasID Write FCanvasID; + Property ResetID : String Read FResetID Write FResetID; + Property AudioDisabled : Boolean Read FAudioDisabled Write FAudioDisabled; + end; + +implementation + + +//============================================================================== +// Generic constants +//============================================================================== +// These constants define the look and feel of the game. +// They set speeds and timeouts, and the define a playing field +// To make the definition of a different playing field easier it is defined as +// an array of strings, in which each character defines specific cell-properties +// The initialization code reads this and uses it to build an array of type TCell[]. +// +// The const Level1field defines a playing field. +// These are the characters used to define the habitat of the ghosts and pacman +// 'x' : a NoGo area. It shows up empty on the screen, but ghosts, pacman +// and bonusses cannot go there. +// '-','|' : a horizontal or verical wall +// '/','\' : a cornerwall, which one depends on surrounding cells +// '1'..'4' : starting position of ghost 1 to 4 +// 'P' : starting position of Pacman +// ' ' : empty space, Pacman, ghosts and bonusses can go there +// '.' : simple pill, Pacman, ghosts and bonusses can go there +// 'o' : super pill, Pacman, ghosts and bonusses can go there. +// This also sets the "ScareTheGhosts" timer +//============================================================================== + +const + CellSize = 16; // do not change... + GhostSpeedScared = 0.10; // Speed of ghosts when scared + GhostSpeedNormal = 0.20; // Speed of ghosts when not scared. + PacmanSpeed = 0.25; // Speed of Pacman + BonusSpeed = 0.04; // speed of cherries + BonusTimeOut1 = 500; // time for cherries not visible + BonusTimeOut2 = 300; // time for cherries visible + ScareTimeOut = 300; // time that the ghosts stay scared + HuntFactor = 0.5; // 0.0:ghosts move random, 1.0=ghosts really hunt + + AudioNames : Array[TAudio] of string = ('start','die','eatghost','eatpill'); + +const + Level1Field : TField = + ('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + 'x/------------\/------------\x', + 'x|............||............|x', + 'x|./--\./---\.||./---\./--\.|x', + 'x|o|xx|.|xxx|.||.|xxx|.|xx|o|x', + 'x|.\--/.\---/.\/.\---/.\--/.|x', + 'x|..........................|x', + 'x|./--\./\./------\./\./--\.|x', + 'x|.\--/.||.\--\/--/.||.\--/.|x', + 'x|......||....||....||......|x', + 'x\----\.|\--\ || /--/|./----/x', + 'xxxxxx|.|/--/ \/ \--\|.|xxxxxx', + 'xxxxxx|.|| ||.|xxxxxx', + 'xxxxxx|.|| /-- --\ ||.|xxxxxx', + '------/.\/ | 1 3 | \/.\------', + ' . | 2 4 | . ', + '------\./\ | | /\./------', + 'xxxxxx|.|| \------/ ||.|xxxxxx', + 'xxxxxx|.|| ||.|xxxxxx', + 'xxxxxx|.|| /------\ ||.|xxxxxx', + 'x/----/.\/ \--\/--/ \/.\----\x', + 'x|............||............|x', + 'x|./--\./---\.||./---\./--\.|x', + 'x|.\-\|.\---/.\/.\---/.|/-/.|x', + 'x|o..||.......P........||..o|x', + 'x\-\.||./\./------\./\.||./-/x', + 'x/-/.\/.||.\--\/--/.||.\/.\-\x', + 'x|......||....||....||......|x', + 'x|./----/\--\.||./--/\----\.|x', + 'x|.\--------/.\/.\--------/.|x', + 'x|..........................|x', + 'x\--------------------------/x', + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'); + +const + WallSet = ['-','|','\','/']; + + clBlack = 'black'; + clWhite = 'white'; + clRed = 'red'; + clYellow = '#FFFF00'; + clBlue = 'blue'; + clLime = 'lime'; + +{ TPacman } + + +constructor TPacman.Create(aOwner: TComponent); +begin + inherited; + FaudioDisabled:=True; + FAudio:=TPacmanAudio.Create; + Faudio.OnLoaded:=@DoAudioLoaded; + SetupPacman; +end; + +//============================================================================== +// Display code +//============================================================================== +// This code is responsible for showing pacman, ghosts, bonuses, scores on the +// screen It uses global variables and the Cells[] array to know where and what +// ShowText() this code shows a flashing text (how surprising) in the +// middle of the playing field for about 3 seconds +// Line() draws a line on img.canvas (should be a standard function!!!) +// DrawCells() clears and draws the complete playingfield according to the +// cell properties in the Cell[] array. Does not draw Pacman, +// ghosts or flying bonusses. +// DrawPacman() Draws an image of Pacman in sprite[0] depending on direction +// UpdateScore() Updates the labels for lives, score, hiscore etc. + +Type + + { TFlashText } + + TFlashText = Class(TObject) + FPacMan : TPacMan; + FText : String; + FFlashInterval : NativeInt; + FCount : Integer; + FonDone : TProcedure; + Procedure DoFlash; + Constructor Create(aPacMan : TPacMan; aText : String; aOnDone : TProcedure); + end; + +{ TFlashText } + +procedure TFlashText.DoFlash; + +var + n,x,y:integer; + FS : TJSTextMetrics; + +begin +// FPacMan.FCanvas.fillStyle:=clBlack; + if FCount mod 2=0 then + FPacMan.FCanvas.FillStyle:=clRed //textbackground is black + else + FPacMan.FCanvas.FillStyle:=clYellow; //textbackground is black + FPacMan.FCanvas.Font:='40px Roboto'; //make text really big + // position text in the middle of the field + FS:=FPacMan.FCanvas.measureText(FText); + x:=FPacMan.FCanvasEl.Width div 2-Round(FS.width) div 2; + y:=FPacMan.FCanvasEl.Height div 2- 20 { Round(FS.actualBoundingBoxAscent) div 2}; + FPacMan.FCanvas.FillText(FText,x,y); + Inc(FCount); + if FCount>=10 then + begin + window.ClearInterval(FFlashInterval); + FPacMan.DrawScene; + if Assigned(FonDone) then + FonDone(); + Free; + end; +end; + +constructor TFlashText.Create(aPacMan : TPacMan; aText: String; aOnDone : TProcedure); +begin + FPacMan:=aPacMan; + FText:=aText; + FOnDone:=aOnDone; + DoFlash; + FFlashInterval:=window.setInterval(@DoFlash,150); +end; + +procedure TPacman.ShowText(aText: string; OnDone : TProcedure); + +begin + TFlashText.Create(Self,aText,OnDone); +end; + +procedure TPacman.line(x1, y1, x2, y2: integer); +begin // should be a standard method of a canvas... + FCanvas.BeginPath; + FCanvas.MoveTo(x1,y1); + FCanvas.LineTo(x2,y2); + FCanvas.stroke(); +end; + + +procedure TPacman.DrawCells(DirtyOnly : Boolean = False); + +const + Sze=CellSize; + HSze=CellSize div 2; + + Procedure DoArc(x,y,r,a1,a2 : Double; anti : boolean = false); + + begin + FCanvas.BeginPath; + FCanvas.Arc(x,y,r,a1,a2,anti); + FCanvas.Stroke; + end; + +var + x,y,sx,sy,r:integer; + +begin + // Clear where necessary + with FCanvas do + if DirtyOnly then + begin + // Only selected cells + StrokeStyle:=clBlack; + FillStyle:=clBlack; + for x:=0 to GridXSize-1 do + for y:=0 to GridYSize-1 do + if Cells[x,y].Dirty or not DirtyOnly then + begin + sx:=x*Sze; + sy:=y*Sze; //calculate pixel position on screen + FillRect(sx,sy,sze,sze); + end; + end + else + begin + // clear screen to black + FillStyle:='black'; + FillRect(0,0, FCanvasEl.Width,FCanvasEl.Height); + // Draw supportGrid (helpfull during development, not needed) + if DrawGrid then + begin + lineWidth:=2; // Pen.width:=1; + StrokeStyle:='#202020'; + for x:=0 to GridXSize do + line(x*Sze,0,x*Sze,Sze*(GridYSize)); + for y:=0 to GridYSize do + line(0,y*Sze,Sze*(GridXSize),y*Sze); + end; + end; + // Draw pills + With FCanvas do + begin + // Draw Pills + StrokeStyle:=clWhite; + FillStyle:=clWhite; + for x:=0 to GridXSize-1 do + for y:=0 to GridYSize-1 do + if Cells[x,y].Dirty or not DirtyOnly then + begin + sx:=x*Sze+HSze; + sy:=y*Sze+HSze; + r:=0; + case Cells[x,y].PillType of + ptPill : r:=2; + ptSuperPill : r:=6; + end; + if r>0 then + begin + BeginPath; + Arc(sx,sy,r,0,2*Pi); + Fill; + end; + end; + end; + // Draw Walls per cell + With FCanvas do + begin + StrokeStyle:=clBlue; + FillStyle:=clBlue; + LineWidth:=sze div 4; + for x:=0 to GridXSize-1 do + for y:=0 to GridYSize-1 do + if Cells[x,y].Dirty or not DirtyOnly then + begin + sx:=x*Sze; + sy:=y*Sze; //calculate pixel position on screen + case Cells[x,y].WallType of + wtEW: line(sx,sy+hsze,sx+sze,sy+hsze); // left to right + wtNS: line(sx+hsze,sy,sx+hsze,sy+sze); // top to bottom + wtSW: DoArc(sx , sy+Sze, Sze / 2,0 ,(3*Pi/2),true); // bottom to left + wtNE: DoArc(sx+Sze, sy , Sze / 2,Pi/2,Pi); // top to right + wtSE: DoArc(sx+Sze, sy+Sze, Sze / 2,Pi ,Pi*3/2); // bottom to right + wtNW: DoArc(sx , sy , Sze / 2,0 ,Pi/2); // top to left + end; + Cells[x,y].Dirty:=False; + end; + end; +end; + +procedure TPacman.DrawPacman(); + +Const + Radius = 12; + Offset = CellSize; + EyeY = CellSize * 2/3; + LeftEyeX = CellSize * 2/3; + RightEyeX = CellSize * 4/3; + MouthRadius = CellSize * 1/3; + EyeRadius = 1.5; + +Var + X,Y : Double; + + Procedure Pie(aAngle : double); + + Var + aStart,aEnd : Double; + + begin + if PacMouthOpen=0 then + begin + aStart:=0; + aEnd:=2*pi + end + else + begin + aStart:=aAngle + (PacMouthOpen/90)*(Pi/2); + if aStart>2*Pi then + aStart:=aStart-2*pi; + aEnd :=aAngle - (PacMouthOpen/90)*(Pi/2); + { + // Draw this to clear first + FCtx.fillStyle:=clBlack; + FCtx.StrokeStyle:=clBlack; + FCtx.Arc(X+15,Y+15,Radius,0,2*pi,True); + FCtx.Fill; + } + end; + With FCanvas do + begin + BeginPath; + MoveTo(X+OffSet,Y+Offset); + Arc(X+Offset,Y+Offset,Radius,aStart,aEnd); + LineTo(X+Offset,Y+Offset); + Fill; + end; + end; + +begin + X:=Sprite[0].XY.x*CellSize-CellSize/2; + Y:=Sprite[0].XY.y*CellSize-CellSize/2; + if PacMouthOpen>40 then + PacMouthOpenDir:=-10 // if maxopen then start closing + else if PacMouthOpen<2 then + PacMouthOpenDir:= 10; // if minopen then start opening + inc(PacMouthOpen,PacMouthOpenDir); // adjust mouth opening + with FCanvas do + begin + FillStyle:=clYellow; // set face color to yellow + StrokeStyle:=clYellow; // pen too + case Sprite[0].Dir of // draw face depending on direction (opposite to what you'd expect) + 'E': Pie(Pi); // to the right + 'W': Pie(0); // to the left + 'N': Pie(3*Pi/2); // to the top + 'S': Pie(Pi/2); // to the bottom + else + beginPath; + Arc(X+OffSet,y+OffSet,Radius,0,2*pi); // whole face area + Fill(); + FillStyle:=clBlack; // + StrokeStyle:=clBlack; // + beginPath; + Arc(X+LeftEyeX,Y+EyeY,EyeRadius,0,2*pi); // left eye + Stroke; + beginPath; + Arc(X+RightEyeX,Y+EyeY,Eyeradius,0,2*pi); // right eye + Stroke; + LineWidth:=3; // + beginPath; + arc(X+offSet,Y+OffSet,MouthRadius,0,Pi);//mouth + Stroke; + end; + end; +end; + +procedure TPacman.UpdateScore(); +begin + if Score>HiScore then + HiScore:=Score; + lbScore.InnerText := inttostr(Score); + lbHiScore.InnerText := inttostr(HiScore); + lbLives.InnerText := inttostr(LivesLeft); + lbBonusCnt.InnerText:= inttostr(BonusCnt); + lbGhostCnt.InnerText:= inttostr(GhostCnt); +end; + +//============================================================================== +// Initialization code +//============================================================================== +// There are several moments in the game something needs to be put in the +// beginstate. +// InitSprite() Called by InitSprites on Create(), creates images and presets +// sprite variables +// InitSprites() This code first creates and initializes all objects and +// variables sets their beginstate values. Called only once !! +// InitVars() This gets some sprite properties from a TField constant +// and resets counters prior to a new game +// InitCells() This copies the cell-properties from a TField constant +// SetGhostScared() sets images and speeds of the 4 ghosts depending on param. + +procedure TPacman.InitSprite(var aSprite: TSprite; aImg: TJSHTMLImageElement; aSpd: Double); +begin + aSprite.spImg := aImg; // get an image instance, owned + aSprite.SpImg.Width:=28; // make the black pixels transparent + aSprite.SpImg.Height:=28; // make the black pixels transparent + aSprite.dir := '-'; // no direction + aSprite.Spd := aSpd; // default speed + aSprite.XY := point(1,1); // Just a non error generating value + aSprite.Sx := 0; // partial X in the middle of a cell + aSprite.Sy := 0; // partial Y in the middle of a cell + aSprite.StartPos:=point(2,2); // Just a non error generating value +end; + +procedure TPacman.InitSprites(); + +var + I : integer; + +begin + Sprite[0].SpImg:=Nil; + For I:=1 to 4 do + InitSprite(Sprite[I],ImgGhost[i],GhostSpeedNormal); + Sprite[0].Spd:=PacmanSpeed; // the image is overwritten later + InitSprite(Sprite[5],ImgBonus ,BonusSpeed); +end; + + +procedure TPacman.InitVars(aField: TField); + +// Uses a TField definition to set the global variable PillCount and the initial +// positions of Pacman and the Ghosts, Also (pre)sets timers and pacman's mouth. + +var x,y,n:integer; + +begin + PillsLeft:=0; + Score :=0; + LivesLeft:=3; + BonusCnt :=0; + GhostCnt :=0; + Pause :=false; + pacMouthopen:=0; + pacMouthopenDir:=10; //startvalues for open mouth of pacman + for x:=0 to GridXSize-1 do + for y:=0 to GridYSize-1 do + begin + case aField[y][x+1] of + '.','o': inc(PillsLeft); // normal and superpills + 'P' : sprite[0].StartPos:=point(x,y); // starting position of PacMan + '1' : sprite[1].StartPos:=point(x,y); // starting position of Ghost #1 + '2' : sprite[2].StartPos:=point(x,y); // starting position of Ghost #2 + '3' : sprite[3].StartPos:=point(x,y); // starting position of Ghost #3 + '4' : sprite[4].StartPos:=point(x,y); // starting position of Ghost #4 + end; + end; + for n:=0 to 4 do + sprite[n].XY:=sprite[n].StartPos; + ScareTimer:=0; + BonusTimer:=0; +end; + +procedure TPacman.InitCells(aField: TField); +// Uses a TField definition to set properties of all cells in the Cell[] array +const + wsH=['-','\','/']; // set of wall chars used in SW-NE detection + wsV=['|','\','/']; // set of wall chars used in SE-NW detection +var + x,y:integer; + c : char; +begin + for y:=0 to GridYSize-1 do + for x:=0 to GridXSize-1 do + begin + // Set values for WallType from string-field definition + c:=aField[y][x+1]; + case c of + '|': Cells[x,y].WallType:=wtNS; // top to bottom + '-': Cells[x,y].WallType:=wtEW; // left to right + '\': if (aField[y][x] in wsH) and (aField[y+1][x+1] in wsV) + then Cells[x,y].WallType:=wtSW // bottom to left + else Cells[x,y].WallType:=wtNE; // top to right + '/': if (aField[y][x+2] in wsH) and (aField[y+1][x+1] in wsV) + then Cells[x,y].WallType:=wtSE // bottom to right + else Cells[x,y].WallType:=wtNW; // top to left + 'x': Cells[x,y].Walltype:=wtNoGo; // no visible wall, but still occupied + else + Cells[x,y].WallType:=wtNone; // no obstacle to pacman and ghosts + end; + // set values for PillType from string-field definition + case c of + '.': Cells[x,y].PillType := ptPill; // this cell contains a Pill + 'o': Cells[x,y].PillType := ptSuperPill; // this cell a SuperPill + else Cells[x,y].PillType := ptNone; // walls and empty space, no points + end; + end; +end; + +procedure TPacman.SetGhostScared(aScared: boolean); + + Procedure DoImg(Idx: Integer;aImg : TJSHTMLImageElement; aSpeed : Double); + + begin + Sprite[Idx].spImg:=aImg; + Sprite[Idx].Spd:=aSpeed; + end; + +var + i : Integer; + +begin + if aScared then + begin // assign "scared" images and set speed to scared + for I:=1 to 4 do + DoImg(i,ImgGhost[5],GhostSpeedScared); + end + else + begin // assign normal ghost images and set speed to normal + For i:=1 to 4 do + DoImg(I,ImgGhost[i],GhostSpeedNormal); + end; +end; + +//============================================================================== +// User input code +//============================================================================== +// This is a very simple piece of code, the only function is FormKeyDown (which +// is an eventproperty of the form) which sets the direction Pacman should go. +// for now only 4 keys are valid, arrow up,down,left,right. + +function TPacman.HandleKeyPress(k: TJSKeyBoardEvent): Boolean; + +begin + Result:=True; + if FDying then exit; + case k.Code of + // For some reason, it is opposite of what you'd expect... + TJSKeyNames.ArrowRight : PacManDir:='W'; + TJSKeyNames.ArrowUp : PacManDir:='N'; + TJSKeyNames.ArrowLeft : PacManDir:='E'; + TJSKeyNames.ArrowDown : PacManDir:='S'; + 'KeyP' : Pause:=not Pause; + end; + k.preventDefault; +end; + +function TPacman.DoResetClick(aEvent: TJSMouseEvent): boolean; + +begin + Result:=True; + FDying:=True; + StopTimer; + RestartGame(); +end; + +function TPacman.CheckSound(Event: TEventListenerEvent): boolean; + +begin + Result:=True; + AudioDisabled:=Not FCBXSound.checked; + if AudioDisabled then + FAudio.DisableSound; +end; + +procedure TPacman.DoAudioLoaded(Sender: TObject); +begin + Start; +end; + +function TPacman.DoMouseClick(aEvent: TJSMouseEvent): boolean; + +Const + SControl = 'control-'; + +Var + S : String; +begin + Result:=true; + S:=aEvent.currentTarget.ID; + aEvent.preventDefault; + if Copy(S,1,Length(SControl))=SControl then + begin + Delete(S,1,Length(sControl)); + Case S of + 'left' : PacManDir:='W'; + 'right' : PacManDir:='E'; + 'down' : PacManDir:='S'; + 'up' : PacManDir:='N'; + 'pause' : Pause:=Not Pause; + end; + end; +end; + +//============================================================================== +// Business logic, rules of the game. +//============================================================================== +// The ghosts are aware of the position of pacman. Depending on their fear for +// him they try to get to him (Fear=-1) or to get away from him (Fear=1) or anything in +// between. +// +// Every once in a while a bonuscherry starts moving around for a some time. +// When Pacman eats the cherry the score is incremented and the cherry disappears. +// Whenever Pacman eats a small pill the score is incremented and the pill disappears +// Whenever Pacman eats a large pill the score is incremented, the pill diappears, +// and a timer is started that keeps the ghosts to a Fearlavel of 1 al long as the +// timer runs. after that the ghosts wil gradually return to fear=-1; +// When pacman eats a scared ghost the score is incremented and the ghost is sent +// back to his cave... +// When pacman eats a not so scared ghost he dies... +// In this case all ghosts are sent home, and if there are stil lives left the +// game continues with one life less... +// When Pacman runs out of lives the game is ended and a new game is started. +// If all pills are eaten the game is also ended and a new game is started. + +//============================================================================== +// Business code: TestAndGet +//============================================================================== +// GetPossibleDir() +// GetGhostDir() +// GetPacmanDir() +// GetRandomCellAndDir() + +function TPacman.GetPossibleDir(aXY: TPoint): TStr4; +begin + result:=''; // Start with an empty string + if Cells[aXY.X,aXY.Y-1].WallType=wtNone then result:=result+'N'; // up is possible + if Cells[aXY.X-1,aXY.Y].WallType=wtNone then result:=result+'E'; // left is possible + if Cells[aXY.X,aXY.Y+1].WallType=wtNone then result:=result+'S'; // down is possible + if Cells[aXY.X+1,aXY.Y].WallType=wtNone then result:=result+'W'; // right is possible +end; + +function TPacman.GetBestDir(aXY: TPoint): char; +begin + result:='-'; + if SolveMaze(aXY,sprite[0].XY) then begin // fill the SearchIndexes cell[x,y].i + if Cells[aXY.X,aXY.Y-1].I<-10 then result:='N'; // up is best + if Cells[aXY.X-1,aXY.Y].I<-10 then result:='E'; // left is best + if Cells[aXY.X,aXY.Y+1].I<-10 then result:='S'; // down is best + if Cells[aXY.X+1,aXY.Y].I<-10 then result:='W'; // right is best + end; +end; + +function TPacman.GetGhostDir(aXY: TPoint; aOldDir: char): char; +var BestDir:char; D:Char;s:TStr4; +begin + result:='-'; + s:=GetPossibleDir(aXY); + case aOldDir of // get the direction opposite of the current direction + 'W':D:='E'; 'E':D:='W'; 'S':D:='N'; 'N':D:='S'; else D:='-'; + end; + if (length(s)>1) then begin // more than one direction: make a choice + BestDir:=GetBestDir(aXY); + if (scaretimer=0) and (BestDir<>'-') then begin// + if random < Huntfactor then s:=BestDir; // hunt depends on factor + end else begin + delete(s,pos(BestDir,s),1); // fleeing does not + end; + end; + // if other than the reverse direction are possible then remove the reverse direction + if (length(s)>1) and (pos(d,s)<>0) then delete(s,pos(d,s),1); + if (length(s)=1) then result:=s[1]; // only one direction possible: Go + if (length(s)>1) then result:=s[1+random(length(s))]; // choose at random +end; + +function TPacman.GetPacmanDir(aXY: TPoint; aOldDir: char): char; +var s:TStr4; +begin + s:=GetPossibleDir(aXY); + if pos(PacmanDir,s)>0 then s:=pacmandir else + if pos(aOldDir,s)>0 then s:=aOldDir else s:='-'; + result:=s[1]; +end; + +procedure TPacman.GetRandomCellAndDir(var aXY: TPoint; var aDir: char); +begin + repeat + aXY:=point(1+random(GridXSize-3),random(GridYSize-3)); + until (Cells[aXY.x,aXY.y].WallType=wtnone); + aDir:=GetGhostDir(aXY,'-'); +end; + +procedure TPacman.StopTimer; +begin + Window.clearInterval(SpriteTimer); +end; + +procedure TPacman.MarkCellsDirty; + +Var + n,maxn,x,y,i,j : Integer; + +begin + maxn:=4; + if BonusTimer>0 then + inc(maxn); + for n:=0 to 4 do + begin + X:=Sprite[n].XY.x; + Y:=Sprite[n].XY.Y; + for i:=-1 to 1 do + for j:=-1 to 1 do + Cells[X+i,Y+j].Dirty:=True; + end; +end; + +procedure TPacman.DoSpriteTimer; + +var n:integer; + +begin + if Pause=false then + begin + MarkCellsDirty; + DrawCells(True); + for n:=0 to 4 do + MoveSprite(n); // for 'Pacman' and each 'Ghost' + if DoBonusTimer() then + MoveSprite(5); // update bonustimer plus cherry + DoScareTimer(); // update the timer that controls scaring of the ghosts + DrawPacman(); // the images have moved, update the pacmanface + end; +end; + +//============================================================================== +// Business code: Actions +//============================================================================== +// OnRestartMessage() +// EatPill() +// EatSuperPill() +// EatBonus() +// EatGhost() +// ClearCell() +// MoveSprite() +// DoBonusTimer() +// DoScareTimer() +// OnSpriteTimer() + +function TPacman.DoRestartClick(aEvent: TJSMouseEvent): boolean; +begin + RestartGame(); // start game after VCL is ready drawing the screen +end; + +procedure TPacman.EatPill(aXY: TPoint); +begin + inc(Score, 1); + ClearCell(aXY); + dec(PillsLeft); + UpdateScore(); + playsound(aEatPill); + if PillsLeft=0 then NextLevel(); +end; + +procedure TPacman.EatSuperPill(aXY: TPoint); +begin + ClearCell(aXY); + ScareTimer:=ScareTimeOut; // Make 'm scared for a while... + inc(Score,10); + playsound(aEatPill); + UpdateScore(); + dec(PillsLeft); if PillsLeft=0 then NextLevel(); +end; + +procedure TPacman.EatBonus(); +begin + BonusTimer:=0; // remove cherry + inc(Score,50); + inc(BonusCnt); + UpdateScore(); // write scores to screen +end; + +procedure TPacman.EatGhost(var aGhost: TSprite); +begin + playsound(aEatGhost); + aGhost.XY:=aGhost.StartPos; // send ghost home + inc(Score,20); + inc(GhostCnt); + UpdateScore(); // write scores to screen +end; + +procedure TPacman.ClearCell(aXY: TPoint); +var sx,sy:integer; +begin + cells[aXY.X,aXY.Y].PillType:=ptNone; // clear cell in Cell[] array + Fcanvas.FillStyle:=clBlack; // also clear this part of the canvas + sx:=aXY.x*CellSize; + sy:=aXY.y*CellSize; + FCanvas.fillrect(sx,sy,cellsize,cellsize); +end; + +procedure TPacman.MoveSprite(aSpriteInx: integer); +var oXY:TPoint; +begin + with Sprite[aSpriteInx] do begin + // change position depending on direction + oXY:=XY; + case Dir of + 'N': begin Sy:=Sy-Spd; if Sy<=-1 then begin dec(XY.y); Sy:=Sy+1; end; end; + 'E': begin Sx:=Sx-Spd; if Sx<=-1 then begin dec(XY.x); Sx:=Sx+1; end; end; + 'S': begin Sy:=Sy+Spd; if Sy>= 1 then begin inc(XY.y); Sy:=Sy-1; end; end; + 'W': begin Sx:=Sx+Spd; if Sx>= 1 then begin inc(XY.x); Sx:=Sx-1; end; end; + else + begin + oXY:=point(0,0); + Sx:=0;Sy:=0; + end; + end; + //if cell changed then choose new direction depending on wall limitations + if (XY.x<>oXY.x) or (XY.y<>oXY.y) then + begin + if aSpriteInx=0 then + dir:=GetPacmanDir(XY,dir) + else + dir:=GetGhostDir (XY,dir); + if dir in ['E','W'] then //correct partial displacements + sy:=0 + else + sx:=0; + if aSpriteInx=0 then + CollisionDetect(XY); //only for The Man himself... + end; + // if position goes offgrid then reenter on the other side of the screen + if XY.x>GridXSize-3 then XY.x:=2; if XY.x<2 then XY.x:=GridXSize-3; + if XY.y>GridYSize-3 then XY.y:=2; if XY.y<2 then XY.y:=GridYSize-3; + // set sprite image position according to new Cx:Sx,Cy,Sy + // Pacman is drawn separately + if aSpriteInx<>0 then + FCanvas.drawImage(spimg,(XY.x+Sx+0.5)*CellSize-SpImg.Width/2, + (XY.y+Sy+0.5)*CellSize-SpImg.Height/2); + // SpImg.Left := round(); + // SPImg.Top := round((XY.y+Sy+0.5)*CellSize-SpImg.picture.Height/2); + end; +end; + +function TPacman.DoBonusTimer(): boolean; + +Var + S : String; + w : Integer; + +begin + if BonusTimer>=0 then begin // bonustimer is positive: cherry is onscreen + dec(BonusTimer); + if BonusTimer<=0 then begin // if decrement makes it negative then + // sprite[5].SpImg.visible:=false; // remove cherry from screen, and + BonusTimer:=-BonusTimeOut1-random(BonusTimeOut1); // set a negative timeout + end; + end else begin // if bonus timer is negative then cherry is not onscreen + inc(BonusTimer); + if BonusTimer>=0 then begin // when increment makes it positive then + // sprite[5].SpImg.visible:=true; // make cherry visible, + // sprite[5].Sx:=0; sprite[5].Sy:=0;// set partial position to zero, and + GetRandomCellAndDir(Sprite[5].XY,Sprite[5].Dir);// choose a random position + BonusTimer:=+BonusTimeOut2+random(BonusTimeOut2); // Set a positive timeout + end; + end; + // update a custom made progressbar on the screen + S:='background-color: '; + W:=bonustimer*Round(pnBonusBarOuter.clientWidth) div (2*BonusTimeOut2); + if BonusTimer>0 then + S:=S+clLime+'; width: '+IntToStr(W)+'px;' + else + S:=S+clRed+'; width: 0px;'; + pnbonusbarInner['style']:=S; + result:=BonusTimer>0; +end; + +procedure TPacman.DoScareTimer(); + +Var + S: String; + w : integer; +begin + // just after superpill is eaten the caretimer is set to ScareTimeOut + if scaretimer>=ScareTimeOut then SetGhostScared(true); //frighten them !! + if ScareTimer>0 then begin + dec(ScareTimer); + // if scaretimer becomes zero then scare time is over: return to normal + if scaretimer=0 then SetGhostScared(false); // fun is over... + // update a custom made progressbar on the screen + if ScareTimer>ScareTimeOut div 5 then + S:='background-color: '+clLime + else + S:='background-color: '+clRed; // make bar red for last 20% of the time + W:=ScareTimer*pnScareBarOuter.Clientwidth div ScareTimeOut; + S:=S+'; width: '+IntToStr(w)+'px;'; + pnScareBarInner.Attrs['style']:=S; + end; +end; + +procedure TPacman.DrawScene; + +Var + I : Integer; + +begin + DrawCells(); + for I:=0 to 4 do + MoveSprite(I); // For 'Pacman' and each 'Ghost' + DrawPacMan; +end; + + +procedure TPacman.SetupPacman; + +Var + I : integer; + El : TJSElement; + +begin + if FCanvasID='' then + FCanvasID:='my-canvas'; + if FResetID='' then + FResetID:='btn-reset'; + FCanvasEl:=TJSHTMLCanvasElement(Document.getElementById(FCanvasID)); + FCanvas:=TJSCanvasRenderingContext2D(FCanvasEl.getContext('2d')); + FBtnReset:=TJSHTMLButtonElement(Document.getElementById(FResetID)); + FCBXSound:=TJSHTMLInputElement(Document.getElementById('cbx-sound')); + FCBXSound.onchange:=@CheckSound; + if Assigned(FBtnReset) then + FBtnReset.OnClick:=@DoResetClick; + FCanvasEl.width := Round(FCanvasEl.OffsetWidth); + FCanvasEl.height := Round(FCanvasEl.OffsetHeight); + for I:=1 to 4 do + ImgGhost[i]:=TJSHTMLImageElement(Document.getElementById('ghost'+IntToStr(i))) ; + ImgGhost[5]:=TJSHTMLImageElement(Document.getElementById('ghost-scared')); + ImgBonus:=TJSHTMLImageElement(Document.getElementById('cherry')); + pnBonusBarOuter:=TJSHTMLElement(Document.getElementById('bonus-outer')); + pnBonusBarInner:= TJSHTMLElement(Document.getElementById('bonus-inner')); + pnScareBarOuter:=TJSHTMLElement(Document.getElementById('scare-outer')); + pnScareBarInner:=TJSHTMLElement(Document.getElementById('scare-inner')); + for I:=1 to ControlCount do + begin + El:=Document.GetElementById('control-'+ControlNames[i]); + if Assigned(El) then + TJSHTMLElement(El).onClick:=@DoMouseClick; + end; + lbScore:=TJSHTMLCanvasElement(Document.getElementById('score')); + lbHiscore:=TJSHTMLCanvasElement(Document.getElementById('highscore')); + lbLives:=TJSHTMLCanvasElement(Document.getElementById('lives')); + lbBonusCnt:=TJSHTMLCanvasElement(Document.getElementById('bonus')); + lbGhostCnt:=TJSHTMLCanvasElement(Document.getElementById('ghosts')); + // Sprites need the images, so this can only be done in this part + InitSprites(); + document.onkeydown:=@HandleKeyPress; + InitAudio(); +end; + +procedure TPacman.InitAudio; + +begin + Faudio.LoadAudio; +end; + +procedure TPacman.StartTimer; + +begin + FDying:=False; + SpriteTimer:=window.setInterval(@DoSpriteTimer,TimerInterval); +end; + +procedure TPacman.Start; +begin + RestartGame; +end; + + +//============================================================================== +// Business code: Decisions +//============================================================================== +// CollisionDetect() +// RestartGame() +// RestartLevel() +// PacmanDies() +// NextLevel() +// GameOver() + +procedure TPacman.CollisionDetect(var aXY: TPoint); +var n,ix,dX,dY:integer; +begin + case cells[aXY.X,aXY.Y].PillType of + ptPill :EatPill(aXY); + ptSuperPill :EatSuperPill(aXY); + end; + ix:=0; for n:=1 to 5 do begin + dX:=sprite[n].XY.x-aXY.x; + dY:=sprite[n].XY.y-aXY.y; + if (abs(dX)<=1) and (abs(dY)<=1) then ix:=n; + end; + if (ix=5) and (BonusTimer>0) then EatBonus(); + if ix in [1..4] then begin + if ScareTimer>0 then EatGhost(sprite[ix]) else PacmanDies(); + end; +end; + +procedure TPacman.RestartGame(); +begin + InitVars(Level1Field); + InitCells(Level1Field); + RestartLevel(); +end; + +procedure TPacman.RestartLevel(); +var n:integer; +begin + for n:=0 to 4 do + Sprite[n].XY:=Sprite[n].StartPos; + UpdateScore(); + SetGhostScared(false); + DrawScene; + PacmanDir:='-'; + DrawPacman(); // the images have moved, set the pacmanface + if not AudioDisabled then + FAudio.Play(aStart); + ShowText('GET READY !!!',@StartTimer); + PacmanDir:='-'; +end; + +procedure TPacman.CheckGameOver; + +begin + if LivesLeft<=0 then + GameOver() + else + ReStartLevel(); +end; + +procedure TPacman.PacmanDies(); +begin +//exit; + if FDying then + exit; + FDying:=True; + StopTimer; + playsound(aDie); + dec(LivesLeft); + UpdateScore(); + PacmanDir:='-'; + ShowText('YOU DIE !!!',@CheckGameOver); +end; + +procedure TPacman.NextLevel(); +begin + StopTimer; + ShowText('YOU WIN !!!',@RestartGame); +end; + +procedure TPacman.GameOver(); +begin + ShowText('YOU LOOSE !!!',@RestartGame); +end; + +procedure TPacman.playsound(aAudio: TAudio); +begin + if not AudioDisabled then + FAudio.play(aAudio); +end; + +//============================================================================== +// Maze solving +//============================================================================== +// Solving a maze is implemented here as a 3 step process. +// Step 1: +// All accessible maze cells get an searchindex of 0, all blocked cells +// (f.i. Walls) get an index of -1. +// Step 2: +// Two arrays are used to keep track of a set of cells that are tested +// This step begins with adding the first point to the primary array. +// This now contains exactly one cell. Then a loop starts: for each cell in +// the primary array the 4 surrounding cells are tested (left,right,up down) +// If the index of such a cell is 0 then the cell is free and it is added to +// a secondary array of cell coordinates. The searchindex of the cell is set +// to a value that is one higher than the searchindex of original cell. +// If the neighbour cells of all cells in the primary array are tested then +// the secondary array is copied to the primary array and the secondary array +// is cleared. +// There are 2 reasons to end this loop: +// 1: The cell that was searched for is found +// 2: There are no more cells with a searchindex of 0, secondary array is empty +// When this is all done the cells have a search index that increments as the +// cell is further away from the originating point. Not all cells are tested. +// When the loop finds the target in say 10 steps the testing stops, so no cell +// will get an index higher than 10. +// Imagine an octopus with growing tentacles that stops when the prey is found +// Step 3: +// Now that the target is found we have to find "the tentacle that leads back +// to the octopus", the shortest way back to the originating point. +// This is done by starting at the endpoint, and looking in the surrounding +// cells for a valid searchindex that is smaller than the cells own searchindex. +// Move the cellpointer to the adjacing cell with a smaller index and eventually +// you get back to the source. +// Imagine a river valley in which a lot of streams go down to the middle. Just +// follow gravity and you will end up in the center. +// On the way back the cells are marked, and that way you will have a set of +// cells that give you the shortest route form A to B. +// +// For debugging the searchindexes are set to 10 and higher for the tested cells +// on routes without result, and -10 and lower for the tested cells that are part +// of the shortest route. SearchIndex = 10 or -10 is the startingpoint. +// Blocked cells are -1, Untested cells are 0. +// Cells with an index of -10 or less are the solution. +// +// For this game we are only interested in the first direction decision of a +// Ghost, so after step 1 to 3 we only look which cell in the adjacent cells of +// a Ghost is in the path, and send the Ghost that way (or opposite when it is +// scared). + +function TPacman.SolveMaze(P1, P2: TPoint): boolean; +begin // 3 step maze solving algorithm + result := SolveMazeStep1(P1,P2); // step1 + if result then result := SolveMazeStep2(P1,P2); // step2 + if result then result := SolveMazeStep3(P1,P2); // step3 +end; + +function TPacman.SolveMazeStep1(P1, P2: TPoint): boolean; +var x,y:integer; +begin + for x:=0 to GridXSize-1 do for y:=0 to GridYSize-1 do begin + if Cells[x,y].WallType=wtNone + then Cells[x,y].I:=0 // these cells can be part of a route + else Cells[x,y].I:=-1; // these cells can not... + end; + // no search is usefull if P1 or P1 is not a valid cell... + result:= (cells[P1.x ,P1.y].I=0) and (cells[P2.x,P2.y].I=0) +end; + +// In the procedure below a fixed size is used for SArr1 and SArr2. +// Of course it is much better to use a dynamic array that is never too small +// I tested the maximum number of alternative routes in this maze is 17, and the +// maximum number of searchloops is 54. +// To keep code as simple as possible the arraysizes are set to 64 (17 needed). +function TPacman.SolveMazeStep2(P1, P2: TPoint): boolean; +var SArr1,SArr2:array[0..63] of tpoint; + SArr1Cnt,SArr2Cnt:integer; + SI:integer; n:integer; + procedure AddLS2(x,y:integer); + begin + if (x<0) or (x>=GridXSize) then exit; // index offgrid: do nothing + if (y<0) or (y>=GridYSize) then exit; // index offgrid: do nothing + if cells[x,y].i<>0 then exit; // cell is blocked: do nothing + cells[x,y].i:=SI; // cell is usable: give index + SArr2[SArr2Cnt]:=point(x,y); inc(SArr2Cnt); // add cell to SArr2 for next run + if (x=P2.x) and (y=P2.y) then Result:=true; // if endpoint is found then stop + end; +begin + SI:=10; Result:=false; // start at 10 to have some special numbers to spare + cells[p1.x,p1.y].i:=SI; // for debugging, set the searchindex of first cell + SArr1Cnt:=1; SArr1[0]:=P1;// prepare primary array with one (the first) cell + repeat // now start searching for PacMan !! + inc(SI); // increment search index + SArr2Cnt:=0; // clear secondary array + for n:=0 to SArr1Cnt-1 do begin // for all points in primary array do + AddLS2(SArr1[n].x+1,SArr1[n].y );// Test and maybe add cell to the right + AddLS2(SArr1[n].x ,SArr1[n].y+1);// Test and maybe add cell below + AddLS2(SArr1[n].x-1,SArr1[n].y );// Test and maybe add cell to the left + AddLS2(SArr1[n].x ,SArr1[n].y-1);// Test and maybe add cell above + end; + //now copy alle new searchpoints in SArr2 to sArr1, and set the number of points + for n:=0 to SArr2Cnt-1 do SArr1[n]:=SArr2[n]; SArr1Cnt:=SArr2Cnt; + until Result or (SArr2Cnt=0); // repeat until pacman is found or all cells tested +end; + +function TPacman.SolveMazeStep3(P1, P2: TPoint): boolean; +var Rdy:boolean; dP:TPoint; I:integer; + procedure Check(x,y:integer); + var It:integer; + begin + if (x<0) or (x>=GridXSize) then exit; // index offgrid: do nothing + if (y<0) or (y>=GridYSize) then exit; // index offgrid: do nothing + It:=cells[x,y].I; // make a long name short... + if (It>0) and (It<I) then begin // if index is smaller than the last but >0 + I:=It; // then make I the smaller index + dP:=point(x,y); // and make the next cell the tested cell + end; + end; +begin + repeat + I:=cells[P2.x,P2.y].i; // inx of current cell (P) + dP:=P2; // make next p equal to current cell + Check(P2.x+1,P2.y ); // test right + Check(P2.x-1,P2.y ); // test left + Check(P2.x ,P2.y+1); // test bottom + Check(P2.x ,P2.y-1); // test top + Rdy:=(dP.x=P2.x)and(dP.y=P2.y); // if dP still equal to P than search is over + cells[p2.x,p2.y].i := -cells[p2.x,p2.y].i;// mark this cell as returnpath + P2:=dP; // move current cell to the next one + until Rdy; + result:=(P2.x=P1.x)and(P2.y=P1.y);// what can possibly go wrong??? +end; + + +Procedure TPacmanAudio.AudioLoaded; + +Var + AllLoaded : Boolean; + A : TAudio; + +begin + allLoaded:=True; + For a in TAudio do + AllLoaded:=AllLoaded and FilesOK[a]; + if allLoaded and Assigned(FonLoaded) then + begin + Writeln('All Loaded'); + FOnLoaded(Self); + end; +end; + +function TPacmanAudio.CheckEnd(Event: TEventListenerEvent): boolean; + +var + a : TAudio; + +begin + For a in TAudio do + if (Files[a]=Event.target) then + Playing[a]:=False; +end; + +function TPacmanAudio.CheckplayOK (Event: TEventListenerEvent): boolean; + +var + a : TAudio; + +begin + For a in TAudio do + if (Files[a]=Event.target) then + begin + Files[a].oncanplaythrough:=nil; + FilesOK[a]:=True; + AudioLoaded; + end; +end; + +procedure TPacmanAudio.LoadAudio; + +var + F : TJSHTMLAudioElement; + A : TAudio; + +begin + for a in TAudio do + begin + F:=TJSHTMLAudioElement(document.getElementbyID('audio-'+audionames[a])); + Files[a]:=F; + FilesOK[a]:=F.readyState>=3; + if not FilesOK[a] then + F.oncanplaythrough:=@CheckPlayOK; + end; +end; + +procedure TPacmanAudio.DisableSound; + +var + a : TAudio; + +begin + For a in TAudio do + if Playing[a] then + begin + files[a].pause(); + files[a].currentTime := 0; + end; +end; + + +procedure TPacmanAudio.play(aAudio: Taudio); + +begin + Writeln('Attempting to play:',AudioNames[aAudio]); + + if FilesOK[aAudio] then + begin + Playing[aAudio]:=true; + Files[aAudio].play; + Files[aAudio].onended:=@CheckEnd; + end; +end; + +Procedure TPacmanAudio.Pause; + +var + a : TAudio; + +begin + For a in TAudio do + if Playing[a] and not Files[a].paused then + files[a].pause(); +end; + +procedure TPacmanAudio.Resume; + +var + a : TAudio; + +begin + For a in TAudio do + if Playing[a] and Files[a].paused then + files[a].play(); +end; + +end.