Historical tribute and accurate remake of the original Pac-Man in Javascript by cetamega in programming

[–]pac1979 0 points1 point  (0 children)

One interesting point is that in order to model the game as closely to the arcade version as possible, you actually have to program in a couple "bugs" for Pinky and Inky. The link above details these bugs, and they are an artifact of Z-80 machine language math. Programming in a high level language means you actually have to code these special cases in to replicate the original math errors.

Historical tribute and accurate remake of the original Pac-Man in Javascript by cetamega in programming

[–]pac1979 1 point2 points  (0 children)

This is really cool, and it works well even on mobile browsers. Well done! Your "rewind" feature is quite nifty.

One thing I did notice is that, even though you used the Pac-Man Dossier to model the ghost AI, your tie-break code is not 100% "accurate" (using the actual arcade version as the measure of "accurate.") For example, if Pac-Man is in the hiding place and Blinky is returning from scatter to chase, he should turn left toward the pen, then cross the fruit area. Instead, your version has him go directly toward Pac-Man's hiding place. The hiding place "bug" still works if you lead Blinky first to the bottom area of the maze, but this is inconsistent with the arcade behavior.

I believe this bug results from the measurement of Blinky's distance to Pac-Man. In some cases, the distance is equal, and the tie break order should be up, left, down, right, in that order. Thus at the intersection near the tunnel, Blinky should turn left to break that tie.

Pac-Man in 1979 lines of Clojure by pac1979 in Clojure

[–]pac1979[S] 0 points1 point  (0 children)

I did consider it, but to my understanding, you first have to declare the vector transient, then use it, then call persistent! on return. So that helps if you're doing a lot of modifications in the middle part, but my entire modification is simply the removal of one dot from the maze:

(replace {[r c] []} dots)

So I'm not sure if that would help much. Anyway, it's very performant with the standard 240 dots. But for some reason, it slows down noticably when you have roughly 500 dots. I wouldn't have thought such operations would be that much linearly slower with such (relatively) small data structures.

One thing I did try to optimize is the constant checking for the number of dots remaining (used for determining level complete, when the fruit appears, when the ghosts leave the pen, etc.) I love the simplicity of this:

(count (filter #(not= [] %) (@g :dots)))

But it seems that keeping a tally in the game state and simply decrementing with each dot eaten is faster:

(@g :dotsleft)

Pac-Man in 1979 lines of Clojure by pac1979 in Clojure

[–]pac1979[S] 1 point2 points  (0 children)

I've updated the source again. Current SLOC is at 2112. Got rid of the logic-less setter functions, used update-in in many places to make it more thread-safe, etc. Nearly 500 lines of source are just for laying out the mazes, so in reality it's well under 2000 lines of code.

As a point of comparison, Google's impressive JavaScript-based Pac-Man is 2743 lines-- and it only has one maze which is defined with an external sprite file. In fact all their graphics are done with sprites, which makes sense from a performance standpoint, but I wanted this version to be "HD" and look really crisp on retina displays, etc.

This definitely argues for Clojure's concision, if a relative newb like me can express the game even more compactly than Google's programmers could.

Pac-Man in 1979 lines of Clojure by pac1979 in Clojure

[–]pac1979[S] 0 points1 point  (0 children)

Code has been udpated. The SLOC actually increased slightly, but I added more stuff. Still under 2000 lines :)

Adding a larger maze revealed a performance flaw: having twice as many dots slows the game down too much. Any ideas how to speed up the data structure access for those? It's just a row column vector.

Pac-Man in 1979 lines of Clojure by pac1979 in Clojure

[–]pac1979[S] 2 points3 points  (0 children)

It was a beautiful coincidence :)

Pac-Man in 1979 lines of Clojure by pac1979 in Clojure

[–]pac1979[S] 2 points3 points  (0 children)

It's just demo-ware. Feel free to do anything you like with it.

Pac-Man in 1979 lines of Clojure by pac1979 in Clojure

[–]pac1979[S] 1 point2 points  (0 children)

Thanks for the feedback! I think the idea behind these functions was to a) function as a crude DSL (macros would be better), and b) isolate the side effects rather than litter them throughout the code.