In this article, I would like to give an example that combines ATS code with JavaScript (JS) code. This is also an occasion for me to advocate refinement-based programming.
// extern fun drawAnim(): void extern fun drawAnim_init(): void extern fun drawAnim_loop(): void extern fun drawFrame (): void // implement drawAnim() = { val () = drawAnim_init () val () = drawAnim_loop () } implement drawAnim_loop() = { val () = drawFrame () val () = sleep (1(*sec*)) // HX: this needs to be fixed val () = drawAnim_loop () } //
The code is self-explanatory: [drawAnim] is the main function to be called for doing animation; it calls [drawAnim_init] to do some form of initialization and then [drawAnim_loop] to start a loop that repeatedly draws a frame (by calling [drawFrame]) and sleeps (by calling [sleep]).
However, the call to [sleep] inside the body of [drawAnim_loop] is problematic. As we want to run the animation inside a browser, calling [sleep], if supported, means to stop the browzer entirely, which is probably unacceptable. Instead, [drawAnim_loop] can be implemented as follows:
// implement drawAnim_loop() = { val () = drawFrame () val () = $extfcall (void, "setTimeout", drawAnim_loop, 1000(*ms*)) } //
Essentially, the call to the external function [setTimeout] in JS requests that the browser schedule a call to [drawAnim_loop] 1000 milliseconds (that is, 1 second) after the time when the call is made.
I present as follows a simple implementation of [drawFrame] in JS directly so as to allow the reader to obtain a concrete feel for the above animation setup:
%{$ // var canvas = document.getElementById ("Patsoptaas-Evaluate-canvas"); // var ctx2d = canvas.getContext( '2d' ); // var theToggle = 0 // function drawFrame() { var w = canvas.width; var h = canvas.height; if (theToggle) ctx2d.fillStyle = "#ffff00"; // yellow if (!theToggle) ctx2d.fillStyle = "#0000ff"; // blue theToggle = 1 - theToggle; ctx2d.rect(0, 0, w, h); ctx2d.fill(); } // jQuery(document).ready(function(){drawAnim();}); // %} // end of [%{$] //
I do not plan to go over the JS code in detail. Essentially, it locates a canvas identified by the string "Patsoptaas-Evaluate-canvas". The implementation of [drawFrame] paints the entire canvas with blue and yellow, alternately. The entirety of the code presented so far is stored in Sierpinski-3angle-part.dats, which can be readily tested on-line.
The reader can take a look at an animated drawing of Sierpinski triangles on-line.
Given a natural number, a Sierpinski triangle at level n can be defined inductively on n. A regular triangle is regarded as a Sierpinski triangle at level 0. For a positive number n, a triangle ABC is a Sierpinski triangle at level n if the three triangles APR, PBQ, and RQC are Sierpinski triangles at level n-1, where P, Q, and R are the midpoints of the three sides AB, BC and CA, respectively.
Let us introduce an abstract type [color] for colors and use [BLUE] and [YELLOW] to refer to two colors defined in JS of the same names:
// abstype color // macdef BLUE = $extval(color, "BLUE") macdef YELLOW = $extval(color, "YELLOW") //
Following is the interface of a function [drawTriangle], which is called to draw a triangle ABC filled with a given color:
// extern fun drawTriangle ( c: color // color for filling , Ax: double, Ay: double // x-y-coordinates for A , Bx: double, By: double // x-y-coordinates for B , Cx: double, Cy: double // x-y-coordinates for C ) : void = "mac#" //
Let us introduce another function [drawSTriangle] for drawing a Sierpinski triangle filled with a given color and then implement it based on the function [drawTriangle]:
// extern fun drawSTriangle ( c: color , Ax: double, Ay: double // x-y-coordinates for A , Bx: double, By: double // x-y-coordinates for B , Cx: double, Cy: double // x-y-coordinates for C , level: int ) : void = "mac#" // implement drawSTriangle ( c, Ax, Ay, Bx, By, Cx, Cy, level ) = ( // if level > 0 then let val Px = (Ax + Bx) / 2 and Py = (Ay + By) / 2 val Qx = (Bx + Cx) / 2 and Qy = (By + Cy) / 2 val Rx = (Cx + Ax) / 2 and Ry = (Cy + Ay) / 2 // val () = drawTriangle (c, Px, Py, Qx, Qy, Rx, Ry) // val level1 = level - 1 val () = drawSTriangle (c, Ax, Ay, Px, Py, Rx, Ry, level1) val () = drawSTriangle (c, Px, Py, Bx, By, Qx, Qy, level1) val () = drawSTriangle (c, Rx, Ry, Qx, Qy, Cx, Cy, level1) in // nothing end // end of [then] else () // end of [else] // ) (* end of [drawSTriangle] *) //
The function [drawFrame] can be implemented as follows:
// local // extern fun theLevel_getinc(): int = "mac#" // in // implement drawFrame () = { // val Ax = theAx_get() // x-coordinate of A val Ay = theAy_get() // y-coordinate of A val Bx = theBx_get() // x-coordinate of B val By = theBy_get() // y-coordinate of B val Cx = theCx_get() // x-coordinate of C val Cy = theCy_get() // y-coordinate of C // val level = theLevel_getinc () val ((*void*)) = drawTriangle (BLUE, Ax, Ay, Bx, By, Cx, Cy) val ((*void*)) = drawSTriangle (YELLOW, Ax, Ay, Bx, By, Cx, Cy, level) // } (* end of [drawFrame] *) // end // end of [local] //
After the canvas for drawing is located, three points A, B and C are choosen on the canvas to be the vertices of a Sierpinski triangle. The x-y-coordinates of A can be obtained by calling [theAx_get] and [theAy_get], which are implemented in JS. The x-y-coordinates of B and C can be obtained similarly. The function [theLevel_getinc] is called to yield the level of the Sierpinski triangle to be drawn. Please find all the details in the following JS code, which also includes an implementation of [drawTriangle]:
// %{$ // var canvas = document.getElementById ("Patsoptaas-Evaluate-canvas"); var ctx2d = canvas.getContext( '2d' ); // function theAx_get() { return 0; } function theAy_get() { return canvas.height; } function theBx_get() { return canvas.width/2; } function theBy_get() { return 0; } function theCx_get() { return canvas.width; } function theCy_get() { return canvas.height; } // var theLevel = 0; function theLevel_getinc() { var level = theLevel; theLevel = (level+1)%7; return level; } // function drawTriangle ( color, Ax, Ay, Bx, By, Cx, Cy ) { ctx2d.beginPath(); ctx2d.moveTo(Ax, Ay); ctx2d.lineTo(Bx, By); ctx2d.lineTo(Cx, Cy); ctx2d.closePath(); ctx2d.fillStyle = color; ctx2d.fill(); return; } // jQuery(document).ready(function(){drawAnim();}); // %} // end of [%{$] //
The entirety of the code for this implementation of an animated drawing of Sierpinski triangles is stored in Sierpinski-3angle-final.dats, which can be readily tested on-line. Of course, it is also possible to use patsopt to compile Sierpinski-3angle-final.dats into some C code and then use atscc2js to compile the C code into some JS code. Please find the related details in the provided Makefile. There is a file Sierpinski-3angle.html available for testing the generated JS code.
This article is written by Hongwei Xi.