The goal was for the program to remain below 30 lines (a bit meaningless) or 3000 characters (more significant). I applied the rules strictly then I saw in the comments of the above article that I could discart the import states from the count. Doh, I could have made more!
The theme is rather broad, any animation, for example, could qualify, somehow... Curiously, being so broad, it was hard to find an idea to code. Then I ran JGears for something unrelated, and I had a revelation: I should make a clock with visible gears!
I looked at the source of JGears to find out how they make such good looking gears, and adapted the idea to JavaFX, using some shortcuts to keep size low. I also found there how to compute relative speed of gears, based on ratio of number of teeth.
I made a generic gear class, reproducing original configuration. I added the possibility to add an axis, and to have triangular teeth, better when there are lot of them.
I tested that once "compressed", the code was still of reasonable size.
After that, work was mostly a question of design. When I was a kid, I had a broken mechanical alarm clock I dismantled. I found out that these gears with their axis made wonderful, stable whirligigs (peg tops). I still have some of them. I remembered that we could have two or more gears on the same axis. I used that fact in my design.
I didn't tried to make a real clock, marking accurately hours, minutes and seconds. First, it would be boring, the hour and minute hands moving too slow. Second, it would make design difficult, having high speed ratio between the parts, needing either lot of gears or gears with lot of teeth, hard to make nice on a computer screen.
So I just chose to move the handles at different speeds, but fast, without trying to make them synchronized or somthing.
To keep things interesting, I made "crystal" gears, with transparency, so that all parts are visible. And the resulting look is nice.
Funnily, I didn't followed the advices of Josh: I didn't used binding at all, nor recursion... I thought of adding a spiral spring made with recursion, had an initial implementation, but dropped it because of the size. Likewise, I made more baroque hands but I would have gone beyond the 3k limit. But hey, no regret, it is nice already, I think.
I used SVGPath to draw the clock hands to make them more interesting. I sticked to a simple design (4 anchors), resulting in a shorter code... I drew the hands in Inkscape, at a great scale, but putting the axis at y = 0. I named each shape to find their ID. After saving the SVG file, I opened it and took the 'd' parameter of the 'path' tag. I can use it as is as the 'content' parameter of the SVGPath class, except it was oversized and needed to be translated on the X axis. Plus each coordinate has a gazillion of decimal digits, which is costly in this challenge.
So I dusted off my old little Lua scripts I wrote to simplify SVG paths in my editor SciTE. I wrote them years ago, when I was studying the then emerging SVG technology... I actually rewrote them to make them more flexible: I can find the min and max coordinates, scale, translate, mirror, and reduce the number of decimal digits. I can share these scripts if you are interested.
One handy thing with SVGPath is that it will naturally rotate around the zeroes of the path, so when properly aligned, it doesn't need much code to move them.
The method for compacting the code is quite simple: obviously, the first step is to remove all indentation, and unneeded spaces, ie. those not in strings and not between two alphabetic characters. I also removed soft braces around unique lines: I tend to put them (almost) systematically in real code. Next step is to reduce these long, explicit identifier names: I could have used aa, ab, etc. but I chose instead kind of abbreviations of the names, giving 1 to 3 character names.
As I wrote above, I removed unused code ("no axis" option), choice of color (it is the same for all gears), inverted default for one parameter (to the most used one), put a test inside a loop instead of the test choosing the loop kind, used variables to position the gears, as it is shorter than specifying 'translateX/Y' each time... That kind of little tricks.
So here is the compacted code, as exported by SciTE (I use the Java lexer with the JavaFX set of keywords), hard-wrapped to look nicer in this blog:
import javafx.stage.*;import javafx.scene.*;import javafx.util.*;import javafx.scene.image.*;
import javafx.scene.paint.*;import javafx.scene.shape.*;
import javafx.scene.text.*;import javafx.scene.transform.*;import javafx.scene.input.*;
import javafx.animation.*;import javafx.geometry.*;import javafx.fxd.*;import javafx.lang.*;
class G extends CustomNode{
public-init var tx:Number;public-init var ty:Number;public-init var ird:Number;
public-init var tk:String='T';public-init var ord:Number;public-init var tn:Integer;
public-init var td:Number;public-init var goa:G[];public-init var got:G[];
public-init var hr:Rotate;var a:Number;var da:Number;
public function mg(s:Number):Void{rotate+=s;for(g in goa)g.mg(s);
for(g in got)g.mg(-s*(tn as Number)/g.tn);if(hr!=null)hr.angle+=s;}
override protected function create():Node{def r1=ord-td/2.0;def r2=ord+td/2.0;a=2*Math.PI/tn;
if(tk=='T')da=a/2 else da=a/4;def sa=if(tk=='T')da else 3*da;
def gear=Path{fill:Color.web('#C0C0C0',0.5),stroke:Color.BLACK,
elements:[MoveTo{x:r1*Math.cos(sa),y:r1*Math.sin(sa)}
for(i in[1..tn])[if(tk=='S')[gl(r1),gl(r2)]else[],gl(r2),gl(r1)]]}
Group{translateX:tx,translateY:ty,content:[gear,Circle{radius:ird,
fill:Color.web('#EE9670'),stroke:Color.BLACK}]}}
function gl(r:Number):LineTo{def lt=LineTo{x:r*Math.cos(a),y:r*Math.sin(a)}a+=da;return lt;}}
def rhs=Rotate{};def rhm=Rotate{};def rhh=Rotate{};def cs=Color.BLUE;def cf=Color.PURPLE;
def hds=SVGPath{stroke:cs;fill:cf;transforms:rhs,translateX:400,translateY:400,
content:"M1,-350C-3,-258-1,-86-5,-34C-6,-10-15,7 1,7C16,6 9,-9 7,-34C2,-86 5,-259 1,-350z"}
def hdm=SVGPath{stroke:cs;fill:cf;transforms:rhm,translateX:400,translateY:400,
content:"M1,-250C-4,-162-1,-81-6,-34C-7,-18-18,11 1,11C19,11 9,-16 8,-34C3,-82 7,-162 1,-250z"}
def hdh=SVGPath{stroke:cs;fill:cf;transforms:rhh,translateX:400,translateY:400,
content:"M0,-150C-5,-109-4,-66-8,-34C-9,-24-22,15 0,15C23,15 10,-22 9,-33C4,-66 7,-109 0,-150z"}
def gs=G{tk:'S',ird:20,ord:95,tn:24,td:20,tx:400,ty:400,hr:rhs}
def gm=G{tk:'S',ird:30,ord:150,tn:60,td:12,tx:400,ty:400,hr:rhm}
def ghmu=G{tk:'S',ird:10,ord:105,tn:42,td:12,tx:600,ty:238,rotate:6.9,got:gm}
def ghml=G{ird:10,ord:35,tn:24,td:12,tx:600,ty:238,goa:ghmu}
def ghsu=G{tk:'S',ird:10,ord:183,tn:51,td:20,tx:600,ty:600,rotate:4.5,got:gs}
def ghsl=G{ird:10,ord:60,tn:36,td:12,tx:600,ty:600,goa:ghsu}
def gh=G{ird:40,ord:220,tn:120,td:12,tx:400,ty:400,got:[ghml,ghsl],hr:rhh}
def ghu=G{ird:10,ord:93,tn:54,td:12,tx:120,ty:548,got:gh}
def ghl=G{ird:10,ord:60,tn:60,td:10,tx:120,ty:548,rotate:4.6,goa:ghu}
def gi=G{ird:10,ord:60,tn:60,td:10,tx:70,ty:435,rotate:4.2,got:ghl}
def gsp=G{ird:24,ord:200,tn:200,td:10,tx:220,ty:220,got:gi}
Stage{title:"Crystal Clock",scene:Scene{width:800,height:800,fill:Color.web('#CCFFEE')
content:[gsp,gi,ghl,ghu,gh,ghml,ghsl,gm,ghmu,gs,ghsu,hdh,hdm,hds]}}
Timeline{repeatCount:Timeline.INDEFINITE,keyFrames:KeyFrame{time:25ms,
action:function(){gsp.mg(-0.05);}}}.play();
You can find a more readable version in my Launchpad repository of JavaFX code. Look at Gear.fx, the definition of the Gear class, and CrystalClock.fx, the clock itself (Gears.fx is the first try of the Gear class). You can look at the history of these files if you are curious... I spent quite some time on this program, I hope you will like it.
Note the generic version of Gear.fx has some facilities for "debugging", like showing the ID of a gear, allowing to drag them to position them precisely, indicating which is the last clicked gear (to allow to rotate it with a slider), etc.
Here is the applet version, my first opportunity to try and include a JavaFX applet in this blog!
J'ai écrit cet article directement en anglais, je l'ai traduit (OK, vais le traduire...) dans JavaFX : 3000 chars pour une horloge de cristal