tag:blogger.com,1999:blog-118857572018-05-27T21:45:31.400-04:00Medicine for the SkyCurtis Autery's ramblings.Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.comBlogger205125tag:blogger.com,1999:blog-11885757.post-22825110365189635152013-11-01T12:39:00.001-04:002013-11-01T12:41:38.449-04:00New Home<p>I've been actively developing a new blogging engine titled <a href="https://github.com/ceautery/dinghy">Dinghy</a>, designed to work with Google App Engine. I'm using it to host my new Blog, <a href="http://curtisautery.appspot.com">Stories for Outcasts</a>, which is also included on the "feeds worth following" widget here. </p><p>This blog will remain up, as it still generates plenty of traffic, particularly from some oddball search topics such as Roman numerals and mainframe arcana, and I will add major announcements here occasionally, but "Medicine for the Sky" will be largely inactive. </p><p>Oh, speaking of major announcements, about 7 weeks ago, my daughter <a href="http://curtisautery.appspot.com/5785905063264256">Emrys</a> was born, bringing the total in our brood to 4 girls!</p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-856015285917306182013-08-25T08:57:00.000-04:002013-08-25T09:02:58.565-04:00Translucent text on a Bootstrap 3 cover image<p>I'm close to debuting a lightweight blogging engine using Go, App Engine, and Bootstrap 3, and I wanted to share a quick solution I found to what seems to be a common problem: putting text on top of a cover image without contrast problems, and without having to explicitly line up a translucent background color with the parent div's corners. </p><p>In this case, I'm using a simple Bootstrap 3 jumbotron with a background image declared with the css "background-image: url()" syntax. The jumbotron div has a child H1 element with a sample blog name ("Stories for Outcasts", which I thought was pretty funny, but [probably] won't be the final blog name I choose). </p><p>The background image is of my Tom Baker Dr. Who action figure peering at some API or other I was toying with on my laptop. It's mainly a dark image, so I chose white for the superimposed text. Here's the HTML stub: </p><pre><br /><!DOCTYPE html><br /><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><br /> <meta charset="utf-8"><br /> <link href="Blog_files/bootstrap.min.css" rel="stylesheet"><br /><br /> <style type="text/css"><br /> #cover {<br /> background-size: 100% auto;<br /> background-image: url(static/cover.jpg);<br /> margin-top: -20px;<br /> }<br /><br /> #cover h1 {<br /> color: #fff;<br /> }<br /> </style><br /></head><br /><body><br /> <div class="container col-md-8"><br /> <div id="cover" class="jumbotron"><br /> <h1 class="text-center">Stories for Outcasts</h1><br /> </div><br /> </div><br /></body></html><br /></pre><p>...and what the result looks like: </p><p><img src="https://lh4.googleusercontent.com/-y8zu_fRvgLs/Uhn993VwtjI/AAAAAAAAELs/3f6ZsGW8m-M/s800/contrast_bad.png" /></p><a name='more'></a><p>As you can see, this mainly works, except for the edge of the laptop screen, where the white border of the displayed editor window contrasts badly with the superimposed text. I debated editing the background image to add a translucent alpha channel, but what I really wanted was a generic solution for when I swap out the cover image in the future, and which other users of the blog engine could use without needing to post-process their cover images. </p><p>I started with adding an RGBA background element to the H1 element using this CSS: </p><pre><br />#cover h1 {<br /> color: #fff;<br /> background: rgba(0, 0, 0, 0.4);<br />}<br /></pre><p>This is declaring a background color of a 40% opaque black. The new jumbotron looked like this: </p><p><img src="https://lh4.googleusercontent.com/-rKvMRNzV41s/Uhn9-GGEzGI/AAAAAAAAEL4/qBNUXw50RAA/s800/contrast_border.png" /></p><p>A step in the right direction, but the border of the H1 element was even more distracting than the original bad contrast. I played around for a little while with trying to blow up the H1's borders to match the parent div, while still inheriting the jumbotron's rounded border. The closest I came was setting the div to relative positioning, the H1 to absolute, and explicitly setting borders. The code was ugly, and I wasn't confident it would work in all cases (e.g., Bootstrap does some voodoo with layout for mobile devices with media queries). More importantly, it was counter to my idea of having a lightweight blog engine with simple code. </p><p>It turned out that I could switch the jumbotron class to the H1 instead of the parent div. Since this was counter to Bootstrap's expectations, there were naturally some side effects, specifically it removed the font-size and line-height formatting of the H1, and the rounded corners of the parent div, which I added back in explicitly. The final code: </p><pre><br /><!DOCTYPE html><br /><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><br /> <meta charset="utf-8"><br /> <link href="Blog_files/bootstrap.min.css" rel="stylesheet"><br /><br /> <style type="text/css"><br /> #cover {<br /> background-size: 100% auto;<br /> background-image: url(static/cover.jpg);<br /> border-radius: 6px;<br /> margin-top: -20px;<br /> }<br /><br /> #cover h1 {<br /> color: #fff;<br /> background: rgba(0, 0, 0, 0.4);<br /> font-size: 63px;<br /> line-height: 1.5em;<br /> }<br /> </style><br /></head><br /><body><br /> <div class="container col-md-8"><br /> <div id="cover"><br /> <h1 class="text-center jumbotron">Stories for Outcasts</h1><br /> </div><br /> </div><br /></body></html><br /></pre><p>This still fell into my intuitive range of "simple", and resulted in the formatting and contrast I was aiming for: </p><p><img src="https://lh5.googleusercontent.com/-UTQM79s95jk/Uhn9-ORl72I/AAAAAAAAEL0/rpxv1Wx04fM/s800/contrast_fixed.png" /></p><p>So in short, to fix your contrast issues with superimposed text, leave the background image as-is on the parent div, and move the div's formatting down to the child text element, including the RGBA background definition. </p><p>Enjoy! </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-86355735078956869482013-07-19T15:19:00.000-04:002013-08-25T19:48:46.122-04:00Minimum wage isn't a living wage<p>I stopped making minimum wage in 1991, when I was 20 years old. I was working for Donatos pizza at $4.25 per hour as "inside help", a position typically reserved for part-time highschool workers. My car was donated to me by my grandmother, as I couldn't afford a car payment, and she wanted to help me out. I shared an apartment with a friend from a previous job, Steve, and the two of us scraped by, kept our $435/month rent more or less paid on time, ate a lot of pizza at work to curb the grocery bill, and played a lot of Zelda in lieu of entertainment requiring cash. </p><p>I look back on that time fondly, but I was close to poverty, and wouldn't have been able to manage without the free car, living in a time where filling a car's gas tank cost just $10, and having a co-conspirator to share expenses with. If I'd have gotten sick, it would have been very problematic for both of us; I had no health insurance at all, and I couldn't afford to miss any work. If my car had broken down, I would have been running back to family for help to get back on my feet. </p><p>I was lucky, though, in that the pizza store noticed my quickly improving skills and work ethic, and offered me a position in management. A couple months after that, I leveraged the extra cash that position brought into my own apartment, (Steve ended up moving back to his hometown of Youngstown, settling affairs after his ex-wife died, and trying to build a good life for his son - that story deserves a better treatment, and provided Steve doesn't object, I'd like to tell it from my point of view some day) where I slowly accumulated furniture, a stereo, a set of books I'd been meaning to get to some day, and a modest cash reserve for car repairs, doctor bills and whatnot. The management job came with a 45 hour work week (minimum), a week of vacation, insurance, and a small monthly bonus if the store hit it's "numbers". Most of that is laughable by the standards of a software developer, but at the time I felt like I was living it large. </p><a name='more'></a><p>The hours and work took its toll on me, but the measly $25k-ish I was making at the time felt like luxurious living compared to where I had been just a few months before. My body was young and didn't object too much to >45 hours in a kitchen each week, but I was eventually put in a position where I was negotiating things that affected people's lives, which I was wholly unprepared for. I fired someone who was unreliable, and faced his desperate pleading for a second chance. Two different sets of people with petty conflicts with each other tried to recruit me to side with them over their perceived goatee Spock doppleganger. A 15 year old Muirfieldite kid got stupid high before coming to work to impress his friends, and I looked into his eyes and failed to hide the hate I felt for him and his kind - sons of blue-blood lineage working only because their parents wanted them to experience manual labor before shipping them off to Ivy League colleges. He played it cool, and I didn't fire him on the spot or make a scene, but no matter how justified I felt, I looked into a little kid's eyes like I thought he was a piece of shit, and I couldn't take it back. He didn't come back to work again after that, and most of his friends quit soon thereafter. The worst, though, was when I moved one of the Sri Lankan immigrants to another store against his will, which deserves a little sidebar. </p><p>In the 90s, Columbus had a sizeable Sri Lankan population, people who had fled their country's civil war (which lasted 25 years before the Tamil Tigers were finally defeated). The Sri Lankan Americans I knew all worked multiple working class jobs (Donatos and KFC being two common places they worked), and lived together in a single apartment. They were hard workers, but due to having to negotiate multiple jobs, had an inflexible schedule and could only work the morning shift at my store. They spoke English well enough for me and other non-bigots to understand them clearly, but not well enough to answer phones or interact with customers much at the front counter. The main problem was that our store couldn't afford all of them for day shifts, as we didn't have very busy lunches, and needed to have at least one good English speaker in addition to the manager to work the counter. One of them had to move to the night shift or leave the store. I chose the newest arrival, Siva. </p><p>Actually his given name was Thambamuthu, and I'm still unclear as to why his fellow expatriates called him Siva. Thinking I was being helpful by letting him keep the same hours, I negotiated with another store nearby who needed inside lunch help for Siva's transfer, and broke the news to him the same day I posted the following week's schedule, which he wasn't on. In reality, I was an ignorant 21 year old and handled the situation very indelicately. Siva was a judge in his home country, and the other Sri Lankans were very respectful to him - in fact he often barked orders to them during shifts where the usual reply was a submissive "Yes sir!" and hurrying to do whatever the task was. And I just stepped on his authority because he happened to be hired last. What an idiot I was. </p><p>Everything worked out well enough, though. Siva accepted the transfer, I believe he took his teenaged son with him, who was a nighttime delivery driver for the store, so he managed to save face somewhat by appearing to be bending over backwards to help a struggling store. But I realized what I had done, and it weighed on me. Soon afterwards I used an excuse of a bad store visit from an area supervisor to sabotage my position (the details aren't important to me any more - suffice to say I was asking for it), and accepted a demotion to driver at another store... where, surprisingly, I made about the same money for much less work, and much less that I could do wrong to affect people's lives. All I had to do was keep my car between the lines. </p><p>I did so well driving that my first desk job at the failing CompuServe online service, for $9.60/hr, more than double the minimum wage, was a substantial pay cut for me, and had I not previously taken a roommate, I would have been struggling again, as my life had taken on a car payment, the habit of eating out periodically, going to movies, buying books, and losing a chunk of my paycheck to insurance costs. </p><p>Why the trip down memory lane? McDonalds recently took a lot of flak for a <a href="http://www.practicalmoneyskills.com/mcdonalds/documents/McD_Journal2.pdf">sample budget</a> purported to show how easy it is to manage a budget and succeed in life if you make minimum wage at McDonalds. The budget is unrealistic for a number or reasons. In case the document gets taken down, here is the gist of it: </p><pre>Monthly Net Income<br />Income (1st job) $ 1,105<br />Income (<b>2nd job</b>) $ 955<br />Other Income $ 0<br />Monthly Net Income Total $ 2,060<br /><br />Monthly Expenses<br />Savings $ 100<br />Mortgage/Rent $ 600<br />Car Payment $ 150<br />Car/Home Insurance $ 100<br />Health Insurance $ <b>20</b><br />Heating $ 50<br />Cable/Phone $ 100<br />Electric $ 90<br />Other $ 100<br />Monthly Expenses Total $ 1,310<br />Monthly Spending Money $ 750<br />Daily Spending Money Goal $ 25</pre><p>The intent of this seems to be showing that working minimum wage can take care of all your expenses, plus give you $25 mad money every day. Except it doesn't mention groceries. Except it implies you can find adequate private insurance for $20 a month. Except the utopia they describe requires a second minimum wage job for 35 hours a week. </p><p>In reality, this shows quite simply that if you make double minimum wage, which I did as a probationary tech support guy at CompuServe, that you'll still be struggling to feed yourself and find insurance, which I would have been had I not advanced quickly, or didn't have a reliable roommate. </p><p>Let's take a more realistic look at what budget numbers would be like. In my hypothetical example, we have a newly married young couple, both currently only capable of making minimum wage for whatever reason (maybe they're fresh off the boat from Sri Lanka). And to their delight, they have a new baby in their lives. What sort of life would they have here in the land of opportunity? Can they live what most of us consider a "normal" life on the wages we set as the minimum needed to stay above poverty, or can they not exist without special help in the form of food stamps, public housing assistance, reduced electric rates, medicaid and the like? </p><p>First, they have to choose whether or not mom has to give the baby to a daycare so she can also work minimum wage, or if she gets to stay home and care for the baby herself. </p><p>Dad gets a job at McDonalds in my home city of Columbus Ohio, making the current minimum wage, $7.25 per hour. We'll start with him trying to be the sole breadwinner. Since he'll be the only one working, he doesn't think they can afford a car, so he'll take the bus to work. He needs good medical, dental, and vision insurance for the whole family, so I've used the monthly family rates for the most common plans used at my job: </p><pre>Medical: Anthem Lumenos HRA Plan $ 308.32 <br /> Dental: Aetna Dental PPO $ 30.49 <br /> Vision: EyeMed Vision Plan $ 19.93 </pre><p>I'm only going to require him to work 40 hours per week, the work week length fought for so hard by labor unions, and which most of us take for granted. Here's what his gross monthly pay will look like: </p> <pre>Minimum wage<br />Per hour $ 7.25<br />Per 40 hour week $ 290.00<br />Per 52 week year $ 15,080.00<br /><b>Monthly $ 1,256.67</b></pre><p>Here's what his monthly paycheck will look like: </p><pre> Monthly Paycheck Formula<br />Gross $ 1,256.67 <br />Medical $ 308.32 <br />Dental $ 30.49 <br />Vision $ 19.93 <br />Taxable $ 897.93 Gross - sum of insurances<br />Medicare $ 13.02 1.45% of taxable<br />Social Security $ 55.67 6.2 % of taxable<br />Fed withholding $ 0.00 Exemptions* exceed salary, so no federal withholding<br />Ohio withholding $ 6.73 Taxable - exemptions = 735.42, so 1.276% bracket.**<br /> Tax is $2.66 + 1.276% of ($735.42 - $416.67)<br />Columbus tax $ 22.45 2.5% of Taxable<br />Net pay $ 800.06 Taxable - taxes (or Gross - insurances - taxes)</pre><p>* Exemptions </p><p>There are 3 people in the family, so we'll say 3 exemptions to make things simple. Here are the 2013 exemption rates: </p><pre> Annual Monthly Monthly x 3<br />Federal $ 3,900 $ 325 $ 975<br />State of Ohio $ 650 $ 54.17 $ 162.51</pre><p>** 2013 Monthly payroll withholding tables </p><pre>Federal (married filing jointly) <br />Salary Minimum Base withholding Percent of excess<br />$ 0.00 $ 0.00 0<br />$ 692.00 $ 0.00 10<br />$ 2,179.00 $ 148.70 15<br />$ 6,733.00 $ 831.80 25<br />$ 12,892.00 $ 2,371.55 28<br />$ 19,279.00 $ 4,159.91 33<br />$ 33,888.00 $ 8,980.88 35<br />$ 38,192.00 $ 10,487.28 39.6<br /> <br /><br />State of Ohio<br />Salary Minimum Base withholding % of excess<br />$ 0.00 $ 0.00 0.638<br />$ 416.67 $ 2.66 1.276<br />$ 833.33 $ 7.98 2.552<br />$ 1,250.00 $ 18.61 3.190<br />$ 1,666.67 $ 31.90 3.828<br />$ 3,333.33 $ 95.70 4.466<br />$ 6,666.67 $ 244.57 5.103<br />$ 8,333.33 $ 329.62 6.379</pre><p>So, what can our family do with their $800.06 per month? Let's look at a budget: </p><pre>Monthly budget, mom stays home with infant<br />Item Amount Running total<br />Net pay $ 800.06 $ 800.06 <br />Rent $ 695.00<sub>1</sub> $ 105.06 <br />Bus fare $ 62.00<sub>2</sub> $ 43.06 <br />Utilities $ 52.16<sub>3</sub> -$ 9.10</pre><ol><li />Average Columbus 1 bedroom apartment rate. Source: <a href="http://www.apartmentratings.com/rate?a=MSAAvgRentalPrice&msa=1840">apartmentratings.com</a><li />According to <a href="http://www.cota.com/General-Fares.aspx">cota.com</a>, the best public transport option seems to be the 31 day pass for $62, which will let dad run to the grocery store on the weekends, or do other solo errands. <li />Average Columbus utility bill for 2 people. Source: <a href="http://publicutilities.columbus.gov/WorkArea/linkit.aspx?LinkIdentifier=id&ItemID=56219">columbus.gov</a></ol><p>Before they've bought any food they're already $9 in the hole. So mom doesn't get to stay home with the baby. She has to get a job, which also means finding a day care. Since the family must be able to pick the baby up from daycare at a moment's notice, and coordinating 2 people getting to and from work is logistically harder, they'll need to procure a car instead of relying on public transport. </p><p>Mom's paycheck is a little better than dad's, since she can skip the insurance: </p><pre> Mom's Paycheck <br />Gross $ 1,256.67 <br />Insurances $ 0.00 <br />Taxable $ 1,256.67 <br />Medicare $ 18.22 <br />Social Security $ 77.91 <br />Fed withholding $ 0.00 <br />Ohio withholding $ 13.64 <br />Columbus tax $ 31.42 <br />Net pay $ 1,115.48</pre><p>And here is their new budget: </p><pre>Budget, both parents work, baby in daycare <br />Item Amount Running total<br />Combined Net pay $ 1,915.54 $ 1,915.54 <br />Rent $ 695.00 $ 1,220.54 <br />Gas $ 42.00<sub>1</sub> $ 1,178.54 <br />Utilities $ 52.16 $ 1,126.38 <br />Daycare $ 650.00<sub>2</sub> $ 476.38 <br />Car payment $ 141.10<sub>3</sub> $ 335.28 <br />Car insurance $ 54.00<sub>4</sub> $ 281.28 <br />Groceries $ 215.00<sub>5</sub> $ 66.28 <br />Everything else $ 66.28</pre><ol><li />The best small car averages $500 annual fuel cost. Source: <a href="http://www.fueleconomy.gov/feg/Find.do?action=sbs&id=33307">fueleconomy.gov</a> (Of course, the car is a 2013 Scion iQ EV, which would be out of their price range, but let's use the number anyway, and say they're very conservative in their amount of driving.) <li />Average monthly cost of infant daycare in Ohio source: <a href="http://naccrrapps.naccrra.org/map/publications/2012/ohio_sfs_2012_preliminary_3_20_12.pdf">naccrra.org</a><li />Cheapest local car on Carmax: 2006 Chevy Aveo, $7998. Let's assume no money down, amoritized over 6 years at 4%, giving $141.10 per month. <li />Average Ohio car insurance cost (2011). Source: <a href="http://www.columbusinsurancemarket.com/blog/average_ohio_auto_insurance_cost.aspx">columbusinsurancemarket.com</a><li />I'm taking a SWAG here, and saying $50 per week, x 52 weeks / 12 months = $215 per month </ol>Now the happy couple is doing a lot better. Mom's paycheck is slightly higher than the costs incurred by daycare and buying a car. They have money left over to feed themselves, and have a spare $66.28 per month. Here is a small list of a few things, off the top of my head, that will eat up $66: <ul><li />A babysitter, dinner, and a movie one night per month <li />A doctor's visit plus medicine <li />A day off work to care for a sick baby <li />A birthday present <li />One concert ticket <li />A mobile phone bill, or a month of Internet, or cable TV. <li />Toddler clothes when baby gets bigger </ul><p>My point is simple: Minimum wage is not enough. My hypothetical couple would live in cheap housing, would probably skip insurance altogether, would use food stamps and other public assistance, and at least one of them would take a second job. A number of counter arguments are obvious: You should have to work hard to build a good life. Quality workers quickly advance to make more money. This is what you get for not being educated. Entertainment and luxury purchases aren't a right, they're a privilege. </p><p>To hell with all that. I don't want an honest, hardworking, smart man like Siva to have to live with 5 of his fellows and work two jobs in order to build the lifestyle I take for granted. I want us to pay him enough at whatever job he gets to live like I do. He deserves it as a fellow human being. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-20765829370971202442013-05-28T20:47:00.002-04:002013-05-28T20:47:39.706-04:00First impressions of WordPressFor reasons that may become significant, I began my first install of WordPress yesterday evening. My install was under CygWin, and most of the challenge was getting WP's dependencies met, PHP being the most frustrating. Eventually I found apt-cyg and cygwinports.org, both from Victor Miti's excellent primer <a href="http://victormiti.umusebo.com/blog/installing-and-configuring-apache-php-mysql-on-cygwin">Installing and Configuring Apache, PHP & MySQL on Cygwin</a>, which had most of what I needed.<div><br /></div><div>After getting Apache2, MySQL, and PHP working, the WP install itself was a breeze, and the content management features seemed straightforward and mature out of the box. On day 2, I've stumbled onto the WordPress Console plugin, and the P2 theme, both being fairly snazzy:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-8kl7ppP1q8U/UaVNgYe2sBI/AAAAAAAAEH4/vfsdzfOuzAk/s1600/wp2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-8kl7ppP1q8U/UaVNgYe2sBI/AAAAAAAAEH4/vfsdzfOuzAk/s800/wp2.png" /></a></div><div><br /></div><div>I can see from this brief exposure, and from an interview with a VideoPress developer, why they have such a thriving community. Good stuff all around, folks... whether or not you end up hiring me.</div>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-38534880354109754662013-05-14T16:18:00.000-04:002013-05-27T20:09:35.797-04:00Ignoring the tyranny of official widgets<p>...and a small explanation of why that's important to me. </p><p>In 2009 I became immersed in the world of check image files, often referred to as "Check 21" files, in reference to the <a href="http://en.wikipedia.org/wiki/Check_21_Act">Check Clearing for the 21st Century Act</a>, which gives a scanned image of a check legal credibility as a financial instrument. In our modern world of online bill pay, web 2.0, and pizza boys with credit card scanners that plug into their mobile phones, paying utility bills by mailing a check is slowly becoming anachronistic... or so you might think. </p><p>In reality, over 300,000 physical checks per month get mailed to our Canton remittance center, many from businesses with multi-thousand dollar electric bills - businesses large enough to employ an IT department to do ACH transactions, wire payments, what have you. But the physical checks keep being printed and mailed, for much the same reason that companies that have ever used a mainframe continue to use them despite the cool kids shouting "dinosaur!" or "RESTful!" or "node.js!!" at them: It's well understood and works reliably. </p><a name='more'></a><p>Our Canton center has hardware to create scanned images of these checks and create from them an X9.37 file, the older of the two ANSI Check 21 standards, which contains TIFF image data surrounded by text records in the EBCDIC character set. On a slow day, these files weigh in at 250 megs, and I have seen them as large as 750. For years, my code has delivered this file daily to the bank using FTPS, of all things, where each send contains a little bit of holding your breath. Because of the particulars of the FTPS protocol, it's possible for a "control" connection to time out while a data transfer is still going on. With files this size, if there is a network slowdown anywhere, a timeout can happen. Then we wait. If we get the return acknowledgement file from the bank with control totals in it, all is well. If not, it's a phone call to try to identity if the file is still processing or never made it. </p><p>Somewhere in this file is a check from a little old lady who recently got a cutoff notice for being 3 months late on her bill (I actually have no idea how our cutoff policy works, but if we cut off a little old lady's electricity for being less than 2 months behind, then we're a bunch of assholes, so I assume it's 3 or more, hopefully with trying to get them into a local assistance program like Virginia and Michigan use during the winter months). Anyway, if her check is processed, then she gets to have a working refrigerator the next month. And if something goes wrong with my process that I can't fix, then she's in real trouble. </p><p>I know of no actual little old lady on the brink like this, but the point is there are real people paying for our power who can be adversely affected if I can't get their money to the bank. The economy being what it is, likely some of these people are late on their bills and just scraping by. And I'm going to do every god damned thing I can to help them. And over the years, I've done exactly that, and both the bank IT folks and the users in Canton have grown to rely on my troubleshooting, where whatever management-imposed official "incident ticket" system is currently in favor is unceremoniously ignored in favor of just calling me directly. I encourgage this behavior in my customers, and it has adversely affected my work in the form of project delays for new work, or me putting off administrative work like timesheet entry, mandatory HR training (how not to let people tailgate you into the building, how not to fall down the stairs, etc.), and I suspect my matter-of-fact bluntness about operating this way cost me a promotion last year. Join me for a brew sometime if you want to hear me complain about this, otherwise... on with the story. </p><p>When this solution was first deployed, I worked closely with our network group, the bank's techs, had them capture network traffic and send me sniffer logs, had them pool over firewall rules looking for ways to shore up the connection, check for anywhere a timeout setting could be adjusted, dug for a way in my application infrastructure (webMethods Integration Server) to play with low-level network settings. And for the most part everything works famously now. But about twice a year something catastrophic happens. The bank's ISP wires something backwards, or the bank upgrades their software, or one of our routers overheats, or there's a cross-connect to production during a DR test. In a big IT system with error-prone humans poking at it, things go bad sometimes; it's inevitable. </p><p>The thing I pushed for the hardest kept receiving the most resistance: switching the transfer protocol to an HTTPS post. The bank could only do that via the AS/2 protocol, which would require some additional coding and testing, as our company had never used AS/2 before. I was all for it, loving new things and not being afraid to break new ground, but management, as is the nature of management, was conservative and change-averse, not wanting to dedicate funds to it, especially since [the mortgage meltdown|lower cost of shale gas|The Reorg] was going on. </p><p>Eventually, though, we got approval and funding. Unfortunately the timeline for the project would have had me finishing right before this year's "Lean Tranformation" would be affecting enterprise IT, and I didn't know if I'd have a job once the layoffs started. My worst-case scenario would be to implement something that I would not be around to monitor and patch, and go to bed everynight with the thought of the little old lady and her refrigerator hanging over my head like the sword of Damocles. Was my code healthy enough? Will the documentation I left behind be enough to help whoever supports this in the future? </p><p>So I made other work a priority and pushed the project schedule well into the other side of the upcoming reorg. As it turned out, my work kept me around yet again (this is reorg 4 since I was hired a decade ago), so I began work in earnest recently, learning the basics of how the webMethods products "Integration Server" and "Trading Networks" (TN) interact to perform AS/2. The long and short of it is they expect many small messages, and are very loose about how data structures, and even the raw data itself, is copied around in the file system and TN database. Nothing in the specs explicitly prevented sending very large files this way, but it turned out that this would cause a few problems, requiring me to ignore the tyranny of the official widget, and roll my own AS/2 delivery service that was more streamlined. </p><p>AS/2 via webMethods the vendor-defined way is pretty straight-forward: define some partner information in the Trading Networks space including SSL keys, server address and credentials, and IDs for the AS/2 envelope, and post a data stream to the service wm.EDIINT:send, which takes care of all the MIME and HTTPS legwork. </p><p>Early testing with this method worked famously; our end-users were able to push files to the service, and the bank received them in the proper format. Except, that is, for the largest test file, a 400 meg behemoth in the normal world, and average sized production file in my world. And so I dove down a bizarre troubleshooting rabbit hole trying to untangle what went wrong, and how to fix it. </p><p>The file in question failed with this error message: </p><pre><br />Unrecognized input object - input [EDIINT.000001.000005]<br /></pre><p>This was essentially TN saying "where's that object I'm supposed to stream to the bank? All I see is NULL". Our smaller files all had the following three objects in the TN transactions log: </p><p><img src="https://lh4.googleusercontent.com/-BIBSSk6kOmY/UZKVqmVk-tI/AAAAAAAAEFo/9aaIUB2gono/s640/tn1.png" /></p><p>...where the larger file was missing the largest obect, and the remaining variables looked different: </p><p><img src="https://lh5.googleusercontent.com/-lb--E9olp-U/UZKVqoB1DBI/AAAAAAAAEFw/kZlurMGVb6I/s640/tn2.png" /></p><p>The mention of "tspace" in the bad transaction was interesting. This is where webMethods creates temporary files representing generic "stream" objects. Integration Server is essentially a java application server, working in much the same way as WebLogic, except that it makes an overt attempt to hide low-level details of the underlying java objects; buffered input streams, file readers, XML node iterators, and the like are all just generic "objects" in the development tool, where inputs can be dragged to built-in services in a visio-like design tool, and said services go through the trouble of figuring out what type of data they've been handed, and how to finagle it into what they really need. </p><p>This is pretty nice if you use amateur developers to code fungible widgets, and only expect the services to receive small messages. But if you have millions of dollars in a daily file that is always guaranteed to be larger than your JVM memory allocation, then it's not so nice. </p><p>So with tspace being my first lead, I decided to watch what happened in the tspace mount while the service was running. I wrote a quick perl one-liner to output the current time, a list of temp files, and free space on the mount, repeating every second. The last thing I saw before the error was this: </p><pre><br />Wed May 8 11:47:34 2013<br />-rw-r----- 1 admin admin 431082539 May 8 11:47 18DocRes.dat<br />-rw-r----- 1 admin admin 431082539 May 8 11:47 19DocRes.dat<br />-rw-r----- 1 admin admin 124847371 May 8 11:47 20DocRes.dat<br />Filesystem 1024-blocks Free %Used Iused %Iused Mounted on<br />/dev/tspacemnt 4194304 1966468 54% 10 1% /opt/tspace<br /></pre><p>First, crap! It was duplicating the 400 meg file not once, but twice! I duplicated this on a smaller file and saw that the file was indeed being triplicated, with the third file being slightly larger, as it contained the AS/2 envelope and signature. If this had run to completion with the large file, it would have used over a gig of temp space. As it was, the error happened when we were pretty close to exactly a gig. Hurm... The next refresh of the one-liner gave this: </p><pre><br />Wed May 8 11:47:35 2013<br />-rw-r----- 1 admin admin 431082539 May 8 11:47 18DocRes.dat<br />-rw-r----- 1 admin admin 431082539 May 8 11:47 19DocRes.dat<br />Filesystem 1024-blocks Free %Used Iused %Iused Mounted on<br />/dev/tspacemnt 4194304 2088436 51% 9 1% /opt/tspace<br /></pre><p>The third file disappeared, leaving the other two waiting for the service to timeout before being removed. I was curious about exactly how much space was being used before the error, so later in the day I ran the service again, and as the third file was being generated, a took out the one second refresh and just had it refresh constantly until the file disappeard. This was the last refresh before the third file disappeared: </p><pre><br />-rw-r----- 1 admin admin 137757736 May 8 15:29 10DocRes.dat<br />-rw-r----- 1 admin admin 431082539 May 8 15:28 8DocRes.dat<br />-rw-r----- 1 admin admin 431082539 May 8 15:28 9DocRes.dat<br /></pre><p>Those file sizes total 999,922,814 bytes. Looking closer, every refresh had roughly a 100k filesize difference, and we were within 100k of 1 billion bytes. As it turned out, there was a setting in the integration server config declaring exactly this: </p><pre><br />$ pwd<br />/IntegrationServer/config<br />$ grep tspace.[a-z]*= server.cnf<br />watt.server.tspace.location=/opt/tspace<br />watt.server.tspace.max=1000000000<br />$<br /></pre><p>So that explained what happened, if not why the hell the design would be to silently delete the most current file in tspace once the max was exceeded, without even throwing an error to the service writing to the file. On top of that, the tspace mount looked like this after all the temp files got removed: </p><pre><br />$ cd /opt/tspace<br />$ ls -l<br />total 0<br />drwxr-xr-x 2 root system 256 Dec 16 2011 lost+found<br />$ df -k .<br />Filesystem 1024-blocks Free %Used Iused %Iused Mounted on<br />/dev/tspacemnt 4194304 3772356 11% 5 1% /opt/tspace<br />$<br /></pre><p>Whoa! No files, but 400 megs are sapped from the filesystem. The file being creamed must have an adverse effect on garbage collection, preventing it from freeing the space from one of the file handles. Bouncing the server and grabbing a thread dump confirmed my suspicion: </p><pre><br />"util.mime.getMimeInputStream" Id=105756 in TIMED_WAITING (running in native)<br /> at java.lang.Object.wait(Native Method)<br /> - waiting on <0x45b345b3> (a java.io.PipedInputStream)<br /></pre><p>Many of the webMethods built-in services are Java only, with the source not being included in the packages. If they don't work correctly, you're hosed unless you can write the service yourself. The AS/2 enveloping and file transfer services are not examples of this, fortunately; instead, they are coded with "Flow" code - the drag and drop visual tool I described above. This means I can poke around in the code to see what makes it tick with relative ease. </p><p>After examining the code for EDIINT:send, the source of the problem was apparent. First, "send" calls "createMime": </p><p><img src="https://lh3.googleusercontent.com/-_io8XB42OAY/UZKVpgx8WhI/AAAAAAAAEFY/JS8FKovs-aA/s640/send1.png" /></p><p>createMime, in turn, calls "streamToBytesOrReservation". This is one of the services I alluded to which takes a random object and turns it into something the service can use. In this case, it wants to take any sort of reader and turn it into either a byte array or a temp file (reservation, the "Res" in "DocRes" files above refers to reservation). In our case, however, the source object was already a reservation, so this was unnecessary. </p><p><img src="https://lh3.googleusercontent.com/-l8-aFQK7w8w/UZKVpdpPG2I/AAAAAAAAEFU/Nt8--hcQnEc/s640/createMime.png" /></p><p>Further down in "send", a mapping step has a transformer that again invokes "streamToBytesOrReservation", dropping the output into the "ediintdata" field that shows up in the "Transaction Details" TN screen from above... if everything worked right, that is. </p><p><img src="https://lh3.googleusercontent.com/-c7IZLHggxfQ/UZKVp7ihf9I/AAAAAAAAEFc/NhCC_sifDkw/s640/send2.png" /></p><p>So now I knew what needed to be changed. My next step was to find the AS/2 syntax used in the actual HTTPS post, so that I could duplicate it without invoking the built-in AS/2 services. I ran a small dummy file through the old code and captured the final MIME envelope. I then used the most fantastic, flexible, and overlooked Unix tool of all time, commandline openssl, to emulate a session and see the server reponse: </p><pre><br />$ openssl s_client -connect [server]:[port] -ssl3<br />CONNECTED(00000003)<br />depth=1 /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 Secure Server CA - G3<br />verify error:num=20:unable to get local issuer certificate<br />verify return:0<br />---<br />Certificate chain<br /> 0 s:/C=US/ST=...[rest of certificate chain]<br />---<br />No client certificate CA names sent<br />---<br />SSL handshake has read 3585 bytes and written 270 bytes<br />---<br />New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA<br />Server public key is 2048 bit<br />Secure Renegotiation IS NOT supported<br />Compression: NONE<br />Expansion: NONE<br />SSL-Session:<br /> Protocol : SSLv3<br /> Cipher : DHE-RSA-AES256-SHA<br /> Session-ID: 518D0D911844B53DAED1DD24333FA90961FABF21876FA49BA07F741293A18A9B<br /> Session-ID-ctx:<br /> Master-Key: 8728B62C10CF721C0FCB82CB4A15F9A55151F74D896B18104686F5BA120884E2493753359C76E19926EBF394F641F2FF<br /> Key-Arg : None<br /> Start Time: 1368198681<br /> Timeout : 7200 (sec)<br /> Verify return code: 20 (unable to get local issuer certificate)<br />---<br />POST /as2 HTTP/1.1<br />Host: [server]<br />User-Agent: CERN-LineMode/2.15 libwww/2.17b3<br />Authorization: Basic [encoded user:pass]<br />AS2-From: [AEP identifier]<br />AS2-To: [Bank identifier]<br />AS2-Version: 1.1<br />Message-ID: <1659527914.01368194059987.JavaMail.admin@[local server]><br />Content-Type: multipart/signed; boundary="----=_Part_6_1615028291.1368194059968"; protocol="application/pkcs7-signature"; micalg=sha1<br />Content-Length: 1137<br /><br />------=_Part_6_1615028291.1368194059968<br />Content-Type: text/plain; charset=ISO-8859-1<br />Content-Transfer-Encoding: binary<br /><br />line 1<br />line 2<br /><br />------=_Part_6_1615028291.1368194059968<br />Content-Type: application/pkcs7-signature; name=smime.p7s<br />Content-Transfer-Encoding: base64<br />Content-Disposition: attachment; filename=smime.p7s<br /><br />MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAMYICAjCCAf4C<br />AQEwgacwgZkxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRPaGlvMREwDwYDVQQHEwhDb2x1bWJ1czEg<br />MB4GA1UEChMXQW1lcmljYW4gRWxlY3RyaWMgUG93ZXIxDDAKBgNVBAsTA0IyQjEcMBoGA1UEAxMT<br />cm9laWFzZDMxLmFlcHNjLmNvbTEaMBgGCSqGSIb3DQEJARYLZWRpQGFlcC5jb20CCQCwrdolXfCc<br />8zAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X<br />DTEzMDUxMDEzNTQxOVowIwYJKoZIhvcNAQkEMRYEFAJEm9GL1ntHw6HGwzD8biAEwAYGMFIGCSqG<br />SIb3DQEJDzFFMEMwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMA0G<br />CCqGSIb3DQMCAgEoMAcGBSsOAwIHMA0GCSqGSIb3DQEBAQUABIGAP1VtnSKeh5c4cUpkNlNj1dkG<br />cBxSwotOKzIVuKHqQui0hfHrWtljMyeguqrVNR5oHhV/p9uKROKmhIxRB1kHR2JjSMa2lesJmMO1<br />JvvIYN8mvpiViT6SJ9mHy1LQm6+ydaHG0il3VLASoIszY6oAABnCWigs8oJ7P4NFpzMIqFUAAAAA<br />AAA=<br />------=_Part_6_1615028291.1368194059968--<br />HTTP/1.1 200 OK<br />Server: Apache-Coyote/1.1<br />Date: Fri, 10 May 2013 15:09:18 GMT<br />Connection: close<br />Content-Type: text/plain<br />Content-Length: 131<br /><br />The message with msg id [<1659527914.01368194059987.JavaMail.admin@[local server]>] was received and has been handed off for processing.<br /></pre><p>The very last message is the ideal response I was looking for. Now I had everything I needed to create an equivalent service, "sendAS2", in my local package, that did everything "send" and "createMIME" does, minus the file duplication: </p><p><img src="https://lh6.googleusercontent.com/-gf1ZjO59MT4/UZKVquf4O7I/AAAAAAAAEFs/phcxVyOEqkc/s640/sendAS2.png" /></p><p>The proof is in the pudding, as they say, so after some small file tests to verify this was working, I threw the 400 meg file at the new service, and watched tspace to see what happened. As with the old service, at first it took about 5 minutes to transfer the file down from the Canton share drive: </p><pre><br />$ perl -le 'while(1){print scalar localtime; print `ls -l *.dat; df -k .`; sleep(15)}'<br />Sat May 11 16:12:03 2013<br />*.dat not found<br />Filesystem 1024-blocks Free %Used Iused %Iused Mounted on<br />/dev/tspacemnt 4194304 4193320 1% 4 1% /opt/tspace<br /><br />Sat May 11 16:12:18 2013<br />-rw-r----- 1 admin admin 11547296 May 11 16:12 1DocRes.dat<br />Filesystem 1024-blocks Free %Used Iused %Iused Mounted on<br />/dev/tspacemnt 4194304 4182040 1% 5 1% /opt/tspace<br /><br />...<br /><br />Sat May 11 16:20:50 2013<br />-rw-r----- 1 admin admin 431082539 May 11 16:20 1DocRes.dat<br />Filesystem 1024-blocks Free %Used Iused %Iused Mounted on<br />/dev/tspacemnt 4194304 3772340 11% 5 1% /opt/tspace<br /></pre><p>And then no other new temp file was created: </p><pre><br />Sat May 11 16:21:05 2013<br />-rw-r----- 1 admin admin 431082539 May 11 16:20 1DocRes.dat<br />Filesystem 1024-blocks Free %Used Iused %Iused Mounted on<br />/dev/tspacemnt 4194304 3772340 11% 5 1% /opt/tspace<br /></pre><p>Finally, after the HTTPS post completes, the temp file is removed, and the filesystem space is freed when the filehandle is closed: </p><pre><br />Sat May 11 16:21:20 2013<br />*.dat not found<br />Filesystem 1024-blocks Free %Used Iused %Iused Mounted on<br />/dev/tspacemnt 4194304 4193320 1% 4 1% /opt/tspace<br /></pre><p>So between the new code managing temp files better, and adjusting the tspace.max config value to match the actual size of the tspace mount, we're in much better shape. To me, this is yet another example of the need to understand bits on the wire. The development framework that we've had forced on us for most of the last decade gives management the illusion that development can be drag and drop and pretty pictures, and that it's not necessary to have competent engineers with deep knowledge of the systems in the enterprise. That is simply not realistic. </p><p>Sometimes I reflect on why I have so much passion about this particular type of file. I take my work seriously, always have, and my customers get my full attention and all the effort I can give them to solve a problem, but this is a special case. As a child, my family lived pretty hand to mouth. My stepfather, when he lived with us, was often in trouble with the law or with drugs, and my mother worked two kitchen jobs trying to keep us afloat. Late notices on the kitchen counter were a common site, and mom was occasionally on the phone with various companies making payment plan arrangements. </p><p>A particularly stressful day for me as a young boy was when I was home alone, and there was a knock on the door from an electric company rep. </p><p>"Um, hi. I'm here to disconnect your power. Here's how you can make payment arrangements to get it turned back on." </p><p>Having no other intelligent reply to give, I said simply "ok. Thanks." </p><p>Mom got off work a few hours later and came home to a dark house, and was near tears. "I paid that bill." </p><p>Maybe there's not a little old lady who'll be cold tomorrow if my work isn't true. But there damn well won't be a little boy who feels helpless watching his mom beg for one more day because of an administrative fuck up. Not if I have anything to say about it. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-89563412548587331772013-04-30T21:09:00.002-04:002013-04-30T21:33:45.920-04:00Base Eighteen on a Chinese Abacus<p>For the sake of simplicity, my use here of number words (ten, hundred) instead of digits (10, 100) will refer to decimal values. "Ten" = <img src="https://lh3.googleusercontent.com/-ssRwdtHKff8/UONELPMj9PI/AAAAAAAADyw/zDCBu7HuD6U/s800/Dec10.PNG" /></p><p>I've been interested in arithmetic on the abacus for a while now, although I'm still quite the amateur. Last year I wrote a (not yet officially adopted) <a href="http://sandcastle.khanacademy.org/media/castles/ceautery:soroban/exercises/soroban_basic_addition.html">plugin to the Khan Academy Exercises engine</a> to allow practice exercises on a 4x1 soroban, which helped me improve my speed and accuracy with basic addition, and I've acquired these two real-world abaci: </p><p><img src="https://lh4.googleusercontent.com/-L8M2_7wp9vg/UONERxwBmCI/AAAAAAAADzc/v5PIGz9hrTo/s800/abaci.jpg" /></p><p>The top abacus is a cheap Chinese 5x2 Suanpan I bought in Chicago's Chinatown a few years back when my wife and I vacationed there, and the bottom abacus is a better quality Japanese 4x1 Soroban that Liberty ordered for me as a Christmas present last year (along with a book on the Haskell programming language I've been wanting - best wife ever!). "Suanpan" and "Soroban" are language variants on the same word, which roughly translates to English as "counting tray", and neither name implies a specific number of beads. </p><a name='more'></a><p>On each abacus in the picture above, one column is set to the number nine, and the others to zero. If you are unfamiliar with how numbers work on an Asian abacus, it's very simple: The bottom beads (Earth) are worth 1 each, the top beads (Heaven) are worth 5. When a bead is moved toward the dividing bar, it counts toward the final number. On the 4x1 Soroban, numbers from 0 to 4 can be represented with earth beads, and either 0 or 5 with the heaven bead, so each column can represent the digit 0 through 9. </p><p>On the 5x2 Suanpan, however, earth beads can represent 0 through 5, and the heaven beads can have a value of 0, 5, or 10. In modern practice, the extra top and bottom beads are used to cache carry operations, however the <a href="http://en.wikipedia.org/wiki/Suanpan">Wikipedia entry on Suanpan</a> makes a reference to this also being used historically for base sixteen (hexadecimal) fractions, which merchants took advantage of back before measurements or money (it's unclear which - as is the validity of the assertion) became standardized to base ten. </p><p>After thinking that through, I saw that the 5x2 abacus could instead represent base eighteen (octodecimal) if the heaven beads were worth 6 instead of 5, which better aligns with the eighteen possible positions each column has. With that method, this enumeration shows the values for each position: </p><p><img src="https://lh5.googleusercontent.com/-wylGW0Ak3CU/UONEL9FOFCI/AAAAAAAADy8/iZKQUTHj4rA/s800/b18Abacus.png" /></p><p>Wacky, and according to a handful of Google searches, seemingly undiscovered by anyone before me. I hope I'm wrong on that point, as it seems like a straightforward deduction (please shoot me a reference if you've heard of this before). So what exactly can one do in the octodecimal world, and more importantly, what makes it interesting enough to investigate? </p><p class="section">The visual </p><p>For starters, I can "see" 10 in my head only with moderate effort (I picture two dice with 5 facing up), but I can't look at a group of approximately fourteen random objects and tell you whether the number of them is closer to ten or eighteen without counting. Representing 10 in octodecimal as: </p><p><img src="https://lh4.googleusercontent.com/-I9sc5q-PMbQ/UONELG-E_CI/AAAAAAAADys/i_OrhZWZq0I/s800/B18_10.PNG" /></p><p>seems as visually "right" as if 10 is represented the normal decimal way: </p><p><img src="https://lh3.googleusercontent.com/-ssRwdtHKff8/UONELPMj9PI/AAAAAAAADyw/zDCBu7HuD6U/s800/Dec10.PNG" /></p><p>Similarly, 100 represented octodecimally... </p><p><img src="https://lh3.googleusercontent.com/-vU6Pz7fx3GY/UONELNO_v4I/AAAAAAAADy0/2mKxFd62gBo/s800/B18_100.PNG" /></p><p>...could pass visually for my idea of what 100 should look like as if it were represented decimally: </p><p><img src="https://lh3.googleusercontent.com/-2ZqbmvUSlsI/UONELaI27xI/AAAAAAAADy4/bfi0cU5BBOw/s800/Dec100.PNG" /></p><p>When you think of higher powers of 10 visually - a thousand, or ten thousand - what do you see? I see a whole lot, and a huge, unidentifiable mass, and if I came across collections of that size unexpectedly, I doubt I could estimate them within a factor of two. </p><p class="section">Multiplication table </p><p>The closest match to octodemical in popular use now is hexadecimal (base sixteen, or "hex" as the cool kids say), commonly used in software development. Hexadecimal values less than ten are represented as 0 through 9, and values ten through fifteen are represented by the letters "a" through "f" so that those values can still be shown as single "digits". We can easily expand that another two characters, using "g" for sixteen, and "h" for seventeen. Using that framework, an octodecimal multiplication table looks like this: </p><pre><br />2 * 2 = 4<br />2 * 3 = 6 3 * 3 = 9<br />2 * 4 = 8 3 * 4 = c 4 * 4 = g<br />2 * 5 = a 3 * 5 = f 4 * 5 = 12 5 * 5 = 17<br />2 * 6 = c 3 * 6 = 10 4 * 6 = 16 5 * 6 = 1c 6 * 6 = 20<br />2 * 7 = e 3 * 7 = 13 4 * 7 = 1a 5 * 7 = 1h 6 * 7 = 26 7 * 7 = 2d<br />2 * 8 = g 3 * 8 = 16 4 * 8 = 1e 5 * 8 = 24 6 * 8 = 2c 7 * 8 = 32<br />2 * 9 = 10 3 * 9 = 19 4 * 9 = 20 5 * 9 = 29 6 * 9 = 30 7 * 9 = 39<br />2 * a = 12 3 * a = 1c 4 * a = 24 5 * a = 2e 6 * a = 36 7 * a = 3g<br />2 * b = 14 3 * b = 1f 4 * b = 28 5 * b = 31 6 * b = 3c 7 * b = 45<br />2 * c = 16 3 * c = 20 4 * c = 2c 5 * c = 36 6 * c = 40 7 * c = 4c<br />2 * d = 18 3 * d = 23 4 * d = 2g 5 * d = 3b 6 * d = 46 7 * d = 51<br />2 * e = 1a 3 * e = 26 4 * e = 32 5 * e = 3g 6 * e = 4c 7 * e = 58<br />2 * f = 1c 3 * f = 29 4 * f = 36 5 * f = 43 6 * f = 50 7 * f = 5f<br />2 * g = 1e 3 * g = 2c 4 * g = 3a 5 * g = 48 6 * g = 56 7 * g = 64<br />2 * h = 1g 3 * h = 2f 4 * h = 3e 5 * h = 4d 6 * h = 5c 7 * h = 6b<br /><br /><br />8 * 8 = 3a<br />8 * 9 = 40 9 * 9 = 49<br />8 * a = 48 9 * a = 50 a * a = 5a<br />8 * b = 4g 9 * b = 59 a * b = 62 b * b = 6d<br />8 * c = 56 9 * c = 60 a * c = 6c b * c = 76 c * c = 80<br />8 * d = 5e 9 * d = 69 a * d = 74 b * d = 7h c * d = 8c d * d = 97<br />8 * e = 64 9 * e = 70 a * e = 7e b * e = 8a c * e = 96 d * e = a2<br />8 * f = 6c 9 * f = 79 a * f = 86 b * f = 93 c * f = a0 d * f = af<br />8 * g = 72 9 * g = 80 a * g = 8g b * g = 9e c * g = ac d * g = ba<br />8 * h = 7a 9 * h = 89 a * h = 98 b * h = a7 c * h = b6 d * h = c5<br /><br /><br />e * e = ag<br />e * f = bc f * f = c9<br />e * g = c8 f * g = d6 g * g = e4<br />e * h = d4 f * h = e3 g * h = f2 h * h = g1<br /></pre><p>A few things jump out at me from this table. First, there aren't a lot of products that end with "h" (seventeen). In fact, there are only two. Here they are in octodecimal, and converted to base ten: </p><pre><br />Octodecimal Decimal<br />5 * 7 = 1h 5 * 7 = (1 * 18) + 17 = 35<br />b * d = 7h 11 * 13 = (7 * 18) + 17 = 126 + 17 = 143<br /></pre><p>I imagine this is due in part to seventeen being the largest prime less than the radix. Similarly, in a base ten multiplication table constructed the same way, the largest prime less than the radix is 7, which ends a product only once, 3 * 9 = 27. In both the "h" and 7 examples, the multiplicand and multiplier are coprime to the radix. Maybe that's significant, or maybe it doesn't apply to other bases the same way; couldn't say, but I find it interesting. </p><p>Another thing that is neat is that all the "h" equations have a product whose digits sum to "h" (h * 2 = 1g, 1 + g = h; g * h = f2, f + 2 = h), similar to base 10 where the products of the 9 equations all have digits that sum to 9 (3 * 9 = 27, 2 + 7 = 9; 7 * 9 = 63, 6 + 3 = 9). <a href="http://www.dozenal.org/articles/DSA-Mult.pdf">This page</a> (pdf) from the Dozenal Society shows that the same principal applies to all other bases as well (at least to all the ones I can read, they use a custom character set for large bases). </p><p>The last fun thing that stands out is the products ending with 0: </p><pre><br />6 * 3 = 10 <br />6 * 6 = 20 9 * 2 = 10<br />6 * 9 = 30 9 * 4 = 20<br />6 * c = 40 9 * 6 = 30<br />6 * f = 50 9 * 8 = 40<br /> 9 * a = 50<br />c * 3 = 20 9 * c = 60<br />c * 6 = 40 9 * e = 70<br />c * 9 = 60 9 * g = 80<br />c * c = 80 <br />c * f = a0 <br /></pre><p>The numbers 6, 9, and c (twelve) all share two factors with 10 (eighteen), and the number they are multiplied with contains the third factor, producing a multiple of 10. Neat. </p><p class="section">Relearning multiplication tables at 40 </p><p>In short, it's a bear. I wrote a quick HTML5 flashcard app below to time how quickly I could convert octodecimal digits to decimal numbers, as well as multiply by 2 in octodecimal. After I recorded the video below I kept practicing and got a little better, and then added the other tables up to h... but I couldn't do it. Not yet. I got about 90% with the 3's multiplication table, and barely made a dent in 4's. I think I'd have to spend a couple hours a day at this for weeks (and avoid decimal numbers in everyday life) before I memorized up to the H's table. Anyway, here's me plugging away at basic decimal conversion and the 2's table: </p><p><iframe width="420" height="315" src="http://www.youtube.com/embed/mvkXNrU5Nj0" frameborder="0" allowfullscreen></iframe></p><p>In closing, our concept of "10" is arbitrary, like a few things in mathematics that I once assumed were natural laws. It's a cultural decision, like order of operations and multiplying matrices. Scaling 10 up to contain eighteen things is only as jarring to my internal estimator as software development's hexadecimal or New Math's octal, and much less so than hardware engineering's binary, or the Sumerian base sixty for angle calculations. Why base eighteen? Because there exists a popular tool which naturally lends itself to calculating in that base. I don't think there is a current need in the world for octodemical, but it's a fun exercise, and fun is enough. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-21701326957205838402013-04-08T14:44:00.000-04:002013-04-08T19:32:24.553-04:00Stripping a UTF-8 byte order mark with Go<p>Recently I've been watching some videos on the Go programming language, and I was impressed enough to go install it and start tinkering. Right out of the gate, I ran into trouble getting the code on <a href="http://tour.golang.org/#1">tour.golang.org's first page</a> working locally. The code is simply: </p><p><img src="https://lh6.googleusercontent.com/-1PHUTuYItDw/UWMNuPRTH9I/AAAAAAAAEAI/RLxMQql27Sw/s517/notepad.png" /></p><p>Google's goal in this case seems to be illustrating to seasoned coders on their first visit that the language has UTF-8 support built-in. Notepad on Windows 7 does display CJK characters correctly out of the box, without needing to play around with language options in the control panel. It also lets you save files in a few unicode-friendly formats, one of which is UTF-8. Unfortunately, Notepad always writes a byte-order mark (BOM) at the beginning of files saved in UTF-8 format, and this can't be disabled: </p><p><img src="https://lh6.googleusercontent.com/-yp-ZAWc3aDU/UWMNuC_zgQI/AAAAAAAAEAM/feVECbiugy0/s678/hexdump.png" /></p><p>The <a href="http://tools.ietf.org/html/rfc3629#page-6">UTF-8 standard</a> is ambiguous about whether the BOM should be accepted in all cases, but regardless of how you read the standard, the Go compiler does not support them, and throws this error when I attempt to compile the same program: </p><a name='more'></a><p><img src="https://lh3.googleusercontent.com/-c90fVnn5gE4/UWMNtnMuI-I/AAAAAAAAEAc/Nj5e4s0xi2Y/s669/cmd1.png" /></p><p>So if you're on Windows on want to code an app that uses Unicode characters, you're going to need another editing tool, or a program to strip the BOM before you compile. After a quick search, I found the top answer on <a href="http://stackoverflow.com/questions/8898294/convert-utf-8-with-bom-to-utf-8-with-no-bom-in-python">this page</a> to be an adequate python solution, which allowed me to run the sample program, bringing me to my next problem: </p><p><img src="https://lh5.googleusercontent.com/-NDaprE9kDIk/UWMNtmhYzTI/AAAAAAAAEAY/4HdfbZBieBk/s669/cmd2.png" /></p><p>Apparently, in the Western build of Windows 7, there is no double byte character set (DBCS) support in the cmd.exe console. The "chcp 65001" business was switching to the UTF-8 codepage, which did cause the output to attempt rendering the Chinese ideograms instead of the 6 ASCII bytes. The font had no support for them, as you can see, hence the blocks. I spent a while reading through other people's attempted solutions, but as far as I can tell no one truly licked it. </p><p>The current build of Cygwin uses mintty as its terminal, which has UTF-8 support out of the box, so switching to Cygwin to run the program produced better results: </p><p><img src="https://lh5.googleusercontent.com/-EXanlCoZW5M/UWMNtgRc7BI/AAAAAAAAEAU/hsQcz4j7Kz4/s595/cyg.png" /></p><p>So with all those problems licked, I decided to write a BOM stripper in Go to use in the future instead of the Python one. Writing this simple program turned out to be a better tutorial in the language than the tour or the many Go videos on youtube. Once again, just tinkering with a tool is a better teacher... for me, anyway. Without further ado, here's the BOM-stripper, my first Go program: </p><pre><br />package main<br /><br />import (<br /> "fmt"<br /> "io/ioutil"<br /> "os"<br /> "bytes"<br />)<br /><br />func main() {<br /> bom := []byte{0xef, 0xbb, 0xbf} // UTF-8<br /><br /> if len(os.Args) < 2 {<br /> fmt.Println("Include file name to parse on command-line")<br /> return<br /> }<br /> fileName := os.Args[1]<br /> contents, err := ioutil.ReadFile(fileName)<br /> if err != nil {<br /> fmt.Println("Error reading file")<br /> fmt.Println(err)<br /> return<br /> }<br /><br /> if !bytes.Equal(contents[:3], bom) {<br /> fmt.Println("No byte-order mark found")<br /> return<br /> }<br /><br /> err = os.Rename(fileName, fileName + ".bak")<br /> if err != nil {<br /> fmt.Println("Error renaming file")<br /> fmt.Println(err)<br /> return <br /> }<br /><br /> err = ioutil.WriteFile(fileName, contents[3:], 0644)<br /> if err != nil {<br /> fmt.Println("Error re-writing file")<br /> fmt.Println(err)<br /> return <br /> }<br />}<br /></pre><p>Here it is in action. I've taken the original program, stripped out the comments, and saved it with Notepad, re-introducing the BOM. This shows the original "illegal character" error, the change in byte size after bom.go is run, and it not making a second change to the file if run again. </p><p><img src="https://lh6.googleusercontent.com/-KvfTo3UbX08/UWMNtyLYD8I/AAAAAAAAEAQ/excbR3bZJFk/s595/cyg2.png" /></p><p>I think I'm going to enjoy programming in Go. It seems to be a good combination of expressive, self-documenting, and low-level. I like how the common idiom "err = <i>function()</i>" makes you think about error handling at each stage. I'm kicking myself that I didn't start using Go earlier. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-22014071877206945192013-03-23T12:55:00.000-04:002013-03-23T12:55:50.595-04:00Screen-scraping a .NET site with App Engine/Java<p>My impetus for developing this was the desire to populate a hidden page on my blog with live data from a pair of funds, namely an ebay merchant account used as a rollover fund for donations, and a 529 account from an Ohio provider. </p><p>There is an excellent API for ebay merchant accounts that I was able to integrate quickly with App Engine. The blog page performs a poor man's AJAX call to an appspot.com servlet, which in turn queries the ebay API for a current balance, and in the end the dollar amount is returned to the blogspot XMLHttpRequest, where a simple DOM .innerHTML replace populates the browser window with the balance. Here's a quick rundown of the code: </p><p class="section">Blogger hooks </p><pre><br />Rollover account balance: <span id="pcfBalance">??</span><br /><br /><script><br />var xmlhttp = new XMLHttpRequest();<br />xmlhttp.onreadystatechange = function() {<br /> if(xmlhttp.readyState == 4){<br /> document.getElementById('pcfBalance').innerHTML = xmlhttp.responseText;<br /> }<br />};<br />xmlhttp.open("GET","http://[my application].appspot.com/pcfbal",true);<br />xmlhttp.send(null);<br /></script><br /></pre><p class="section">App Engine servlet </p><a name='more'></a><pre><br />public void doGet(HttpServletRequest req, HttpServletResponse resp)<br /> throws IOException {<br /><br /> HTTPRequest hreq = new HTTPRequest(new URL("https://api-3t.paypal.com/nvp"), HTTPMethod.POST);<br /> hreq.setPayload("METHOD=GetBalance&USER=[my user id]&PWD=[my password]&VERSION=94.0".getBytes());<br /><br /> URLFetchService f = URLFetchServiceFactory.getURLFetchService();<br /> HTTPResponse hresp = f.fetch(hreq);<br /> <br /> String response = URLDecoder.decode(new String(hresp.getContent()), "UTF-8");<br /> Matcher m = Pattern.compile("L_AMT0=(\\d+\\.\\d+)").matcher(response);<br /> String output = null;<br /> <br /> if (m.find()) {<br /> output = "$".concat(m.group(1));<br /> } else {<br /> output = "Error getting balance.";<br /> }<br /> <br /> resp.setContentType("text/html; charset=UTF-8");<br /> resp.addHeader("Access-Control-Allow-Origin", "http://cautery.blogspot.com");<br /> resp.getWriter().println(output);<br />}<br /></pre><p>So, easy enough for ebay (except for the hardcoded credentials in the code, which is insanity if Google's code download prevention ever goes on the blink). The 529 account, on the other hand, is with College Advantage, which doesn't provide a developer API, so the integration with my balance page had to resort to screen-scraping. </p><p>Their customer website is written in .NET, so along the way I needed to decode some of what's going on under the hood. On the login page, this form is presented: </p><p><img src="https://lh6.googleusercontent.com/-DMxF4zyMxr0/UU2tZTsbRZI/AAAAAAAAD_U/7nSdJYYg82s/s525/login.PNG" /></p><p>When you enter your credentials and submit the form, the next page contains the account balance: </p><p><img src="https://lh5.googleusercontent.com/-cWtw9piJlpY/UU2tYtEt2JI/AAAAAAAAD_U/rBv1lTaCGoY/s640/account-summary.PNG" /></p><p>Based on that, I assumed that I would need my servlet to use URLFetch to GET the login page, grab a session cookie, post my login credentials, and scan the return page for a dollar amount. I quickly found out that it was going to be a bit more tricky. </p><p>In the HTML of the login page, the form is plainly visible: </p><pre><br /><form name="aspnetForm" method="post" action="login.aspx" onsubmit="javascript:return WebForm_OnSubmit();" id="aspnetForm"><br /></pre><p>A simple post back to the same ASP page, with presumably a javascript validation step. The inputs were spread out in the code, but were easily enumerated with Chrome's devloper console: </p><pre><br />> var inputs = document.forms[0].getElementsByTagName('input')<br /><- undefined<br />> for (var i in inputs) console.log(inputs[i])<br /> <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value><br /> <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value><br /> <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTk2NDcyNzc3N ... fMckMweSG6gqi57QQ0="><br /> <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWBwKZz/jaCALlu9TcCQ ... hwKmiI2E1Qp5JtZ5s"><br /> <input type="text" class="text-box"><br /> <input name="ctl00$cphContent$Login1$UserName" type="text" value="(username)" maxlength="50" id="ctl00_cphContent_Login1_UserName" tabindex="1" class="watermark"><br /> <input id="password-fake" class="watermark" type="text" value="(password)" tabindex="2" style="display: inline;"><br /> <input name="ctl00$cphContent$Login1$Password" type="password" id="ctl00_cphContent_Login1_Password" tabindex="3"><br /> <input id="ctl00_cphContent_Login1_RememberMe" type="checkbox" name="ctl00$cphContent$Login1$RememberMe"><br /> <input type="image" name="ctl00$cphContent$Login1$LoginImageButton" id="ctl00_cphContent_Login1_LoginImageButton" src="images/redesign/sign-in-btn.png" alt="Click here to login" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("ctl00$cphContent$Login1$LoginImageButton", "", true, "Login1", "", false, false))" style="color:#284775;border-width:0px;"><br /> 10<br /> function item() { [native code] }<br /><- undefined<br /></pre><p>This was when I first had my "aw crap" moment. The ASP page was littered with things I don't particularly care for, not the least of which is having 8 more form inputs than is necessary. It had the __VIEWSTATE and __EVENTVALIDATION objects, used by ASP.NET to manage state (since posts happen to the same URL) and add some extra security to prevent the client adding extra form fields. It had the long .NET ct100 input names, and also two layers of inputs for the password, one confusingly titled "password-fake". And lastly, the postback call, omnipresent in ASP applications. </p><p>After trying different combinations of inputs, I found that I could not programmatically submit the form as-is and get to the next step. The same login page was always returned to me. Puzzled, I decided to see what my browser actually submitted, and use Chrome's net-internals packet capturing console to catch raw HTTP requests. When I did this, I saw some more things which were irritants, but ultimately found what I was missing: copious javascript manipulation of form elements - both deleting and adding them. </p><p>The following is a somewhat truncated series of raw requests and responses showing the entire path to getting the sought-after account balance. </p><p>Step 1 - Chrome sends a GET request for the login page </p><pre><br />GET /cas/login.aspx HTTP/1.1<br />Host: www.collegeadvantage.com<br />Connection: keep-alive<br />Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<br />User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17<br />Accept-Encoding: gzip,deflate,sdch<br />Accept-Language: en-US,en;q=0.8<br />Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3<br /></pre><p>Step 2 - Server sends the login form, plus a session cookie </p><pre><br />HTTP/1.1 200 OK<br />Cache-Control: no-cache, no-store<br />Pragma: no-cache<br />Content-Length: 28017<br />Content-Type: text/html; charset=utf-8<br />Expires: -1<br />Server: Microsoft-IIS/7.5<br />X-AspNet-Version: 2.0.50727<br />Set-Cookie: ASP.NET_SessionId=dhevgljw5210rfyuczyq0p3o; path=/; HttpOnly<br />X-Powered-By: ASP.NET<br />Date: Thu, 21 Mar 2013 00:56:16 GMT<br /></p><p><br /><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br /><html xmlns="http://www.w3.org/1999/xhtml"><br /><head id="ctl00_headEnrollmentMater"><title><br />...rest of login page<br /></pre><p>Step 3 - After I enter my credentials and submit the form, Chrome posts this: </p><pre><br />POST /cas/login.aspx HTTP/1.1<br />Host: www.collegeadvantage.com<br />Connection: keep-alive<br />Content-Length: 2598<br />Cache-Control: max-age=0<br />Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<br />Origin: https://www.collegeadvantage.com<br />User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17<br />Content-Type: application/x-www-form-urlencoded<br />Referer: https://www.collegeadvantage.com/cas/login.aspx<br />Accept-Encoding: gzip,deflate,sdch<br />Accept-Language: en-US,en;q=0.8<br />Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3<br />Cookie: ASP.NET_SessionId=dhevgljw5210rfyuczyq0p3o; __utma=229128...etc + other Google Analytics cookies<br /><br />__EVENTTARGET=<br />&__EVENTARGUMENT=<br />&__VIEWSTATE=%2FwEPDwUKLTk2NDcyNzc3N...etc<br />&__EVENTVALIDATION=%2FwEWBwKZz%2FjaC...etc<br />&ctl00%24cphContent%24Login1%24UserName=[user name]<br />&ctl00%24cphContent%24Login1%24Password=[password]<br />&ctl00%24cphContent%24Login1%24LoginImageButton.x=0<br />&ctl00%24cphContent%24Login1%24LoginImageButton.y=0<br /></pre><p>The obvious things that stick out here are the last two form elements. In the form, there was a LoginImageButton element, but here that is removed, and replaced with the button object's .x and .y fields. Javascript from either an onload event or the validation function must have done this, and I couldn't imagine it was intentional. Astonishingly, both of these form elements are necessary to continue. Leaving either (or both) of them out results in the login silently failing, and returning you to the login form again. </p><p>Step 4 - Server sends redirect to account details page </p><pre><br />HTTP/1.1 302 Found<br />Cache-Control: no-cache, no-store<br />Pragma: no-cache<br />Content-Length: 28352<br />Content-Type: text/html; charset=utf-8<br />Expires: -1<br />Location: /cas/AccountDetails.aspx<br />Server: Microsoft-IIS/7.5<br />X-AspNet-Version: 2.0.50727<br />Set-Cookie: UserInfo=userName=; expires=Thu, 21-Mar-2013 00:56:24 GMT; path=/<br />Set-Cookie: OTTA_AUTHX=864D0BB4752D1...etc<br />X-Powered-By: ASP.NET<br />Date: Thu, 21 Mar 2013 00:56:23 GMT<br /><br /><html><head><title>Object moved</title></head><body><br /><h2>Object moved to <a href="%2fcas%2fAccountDetails.aspx">here</a>.</h2><br /></body></html><br /><br /><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br /><html xmlns="http://www.w3.org/1999/xhtml"><br /><head id="ctl00_headEnrollmentMater"><title>...<br /></pre><p>This was another "what the hell?" moment. First, why is there a doctype tag and an additional HTML page under the default IIS "Object moved" page? If your browser doesn't accept the redirect in the HTTP header, what's it going to do with the mess that follows? </p><p>Second, why on earth would the server be setting additional cookies on the redirect? After comparing that with another .NET site I frequent (my bank, Huntington) I saw that it was essentially doing the same thing. A little research showed that setting a secondary authorization cookie is a common security measure against session hijacking using the "session fixation" attack. </p><p>Since App Engine's URLFetch service isn't a full-fledged browser, this last bit actually caused me some problems. When I got to this step in my code, the URLFetch Service kept throwing convertApplicationException errors (in fact it was when these started showing up that I realized I had finally found the right combination of headers and form elements). </p><p>The problem was the session cookie in the redirect. URLFetch defaults to following redirects, but cookie management (in my version of the Java toolkit, anyway) is left to the programmer by means of setHeader or addHeader calls. So, if my hypothesis of what was happening is accurate, URLFetch followed the redirect, but did not have the OTTA_AUTHX session cookie, which led to the exception by virtue of a redirect loop. </p><p>The fix was pretty simple, just adding the doNotFollowRedirects option to the HTTPRequest object, allowing a chance to parse the headers and add the new cookie to the followup GET request. </p><p>Step 5 - Chrome sends a GET request for the account details page, including both the session and secondary authorization cookies. </p><pre><br />GET /cas/AccountDetails.aspx HTTP/1.1<br />Host: www.collegeadvantage.com<br />Connection: keep-alive<br />Cache-Control: max-age=0<br />Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<br />User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17<br />Referer: https://www.collegeadvantage.com/cas/login.aspx<br />Accept-Encoding: gzip,deflate,sdch<br />Accept-Language: en-US,en;q=0.8<br />Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3<br />Cookie: ASP.NET_SessionId=dhevgljw5210rfyuczyq0p3o; __utma=2291283...etc; UserInfo=userName=; OTTA_AUTHX=864D0BB4752D1CBE7E55843B...etc<br /></pre><p>Step 6 - Account details page is returned, containing the account balance in a simple HTML table. Since that's the only thing we care about at this point, I've omitted the headers and the rest of the document. </p><pre><br /><td align="center">$25.07</td><br /></pre><p>The total can be easily parsed by a regular expression, as it's the only thing on the page in $##.## format. </p><p>Initially I intended to have this servlet function the same way as the one for ebay - pulling live data with each call. However, since I'm screen-scraping a page that can change at any time, and the calling Google app server may have different IP addresses (which could look like fraud or unauthorized access to College Advantage), that's two good reasons to minimize the number of calls I make to the site. </p><p>I opted instead to split this into two parts: One that was invoked by cron daily, logging on once, pulling the balance, and putting it in a database, and another that the blogspot page would invoke, which simply queried the database for the most recent balance. </p><p>Here is the first servlet of the pair, invoked daily by cron: </p><pre><br />public class CADatabaseWriter extends HttpServlet {<br /> private static final Logger log = Logger.getLogger(CADatabaseWriter.class.getName());<br /> <br /> public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {<br /> // 1 - Download login form, grab ASP session ID cookie and hidden form fields<br /> HTTPRequest hreq = new HTTPRequest(new URL("https://www.collegeadvantage.com/cas/login.aspx"), HTTPMethod.GET);<br /> URLFetchService f = URLFetchServiceFactory.getURLFetchService();<br /> HTTPResponse hresp = f.fetch(hreq);<br /><br /> List<HTTPHeader> headers = hresp.getHeaders();<br /> String cookie = "";<br /> for (int i = 0; i < headers.size(); i++) {<br /> if (headers.get(i).getName().equals("Set-Cookie")) {<br /> cookie = getCookie(headers.get(i).getValue(), "ASP.NET_SessionId");<br /> break;<br /> }<br /> }<br /><br /> String[] lines = new String(hresp.getContent()).split("\n");<br /> Pattern p = Pattern.compile("input type=\"hidden\" name=\"(.+?)\".+value=\"(.+?)\"");<br /> StringBuffer formdata = new StringBuffer();<br /> for (int i = 0; i < lines.length; i++) {<br /> Matcher m = p.matcher(lines[i]);<br /> if (m.find()) {<br /> cat(formdata, m.group(1), m.group(2));<br /> }<br /> }<br /> cat(formdata, "ctl00$cphContent$Login1$UserName", "(hardcoded user id)");<br /> cat(formdata, "ctl00$cphContent$Login1$Password", "(hardcoded password");<br /> // Yes, I know, brazenly insecure<br /> cat(formdata, "ctl00$cphContent$Login1$LoginImageButton.x", "67");<br /> cat(formdata, "ctl00$cphContent$Login1$LoginImageButton.y", "14");<br /><br /><br /> // 2 - POST login request using data scraped from GET request<br /> hreq = new HTTPRequest(new URL("https://www.collegeadvantage.com/cas/login.aspx"), HTTPMethod.POST, doNotFollowRedirects());<br /> hreq.setHeader(new HTTPHeader("Origin", "https://www.collegeadvantage.com"));<br /> hreq.setHeader(new HTTPHeader("Content-Type", "application/x-www-form-urlencoded"));<br /> hreq.setHeader(new HTTPHeader("Referer", "https://www.collegeadvantage.com/cas/login.aspx"));<br /> hreq.setHeader(new HTTPHeader("Cookie", cookie));<br /><br /> hreq.setPayload(formdata.toString().getBytes());<br /> hresp = f.fetch(hreq);<br /> headers = hresp.getHeaders();<br /><br /><br /> // 3 - Pull additional OTTA_AUTHX cookie from redirect page,<br /> // add to final GET request for account data<br /> for (int i = 0; i < headers.size(); i++) {<br /> if (headers.get(i).getName().equals("Set-Cookie")) {<br /> String cookieStr = headers.get(i).getValue(); <br /> cookie = cookie + "; " +<br /> getCookie(cookieStr, "OTTA_AUTHX");<br /> break;<br /> }<br /> }<br /><br /> hreq = new HTTPRequest(new URL("https://www.collegeadvantage.com/cas/AccountDetails.aspx"), HTTPMethod.GET);<br /> hreq.setHeader(new HTTPHeader("Referer", "https://www.collegeadvantage.com/cas/login.aspx"));<br /> hreq.setHeader(new HTTPHeader("Cookie", cookie));<br /><br /> hresp = f.fetch(hreq);<br /><br /> // 4 - Parse current balance<br /> String content = new String(hresp.getContent());<br /> Matcher m = Pattern.compile(">(\\$\\d+\\.\\d+)<").matcher(content);<br /> <br /> if (m.find()) {<br /> Entity balance = new Entity("balance");<br /> balance.setProperty("balance", m.group(1));<br /> balance.setProperty("date", new Date());<br /> DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();<br /> datastore.put(balance);<br /> log.warning("Success");<br /> } else {<br /> log.severe("Error retrieving balance");<br /> }<br /> }<br /><br /> private String getCookie(String cookieStr, String key) {<br /> String value = key + "=";<br /> Pattern p = Pattern.compile(key + "=(.+?);");<br /> Matcher m = p.matcher(cookieStr);<br /> if (m.find()) value = value.concat(m.group(1));<br /> return value;<br /> }<br /> <br /> private void cat(StringBuffer sb, String key, String value)<br /> throws UnsupportedEncodingException {<br /> if (sb.length() > 0) sb.append(('&'));<br /> String k = URLEncoder.encode(key, "UTF-8");<br /> String v = URLEncoder.encode(value, "UTF-8");<br /> sb.append(k).append('=').append(v);<br /> }<br />}<br /></pre><p>This is mapped as /cad in the web.xml file, along with this security constraint, which allows only admins (and cron) to call the servlet: </p><pre><br /><security-constraint><br /> <web-resource-collection><br /> <url-pattern>/cad</url-pattern><br /> </web-resource-collection><br /> <auth-constraint><br /> <role-name>admin</role-name><br /> </auth-constraint><br /></security-constraint><br /></pre><p>I also needed to add a cron.xml file to WEB-INF to specify the schedule: </p><pre><br /><?xml version="1.0" encoding="UTF-8"?><br /><cronentries><br /> <cron><br /> <url>/cad</url><br /> <description>Get daily balance from College Advantage</description><br /> <schedule>every day 06:00</schedule><br /> <timezone>America/New_York</timezone><br /> </cron><br /></cronentries><br /></pre><p>That was parsed by App Engine and showed up as this item in "Cron Jobs" in the App Engine UI: </p><p><img src="https://lh4.googleusercontent.com/--YZ2vLUzu98/UU2tYkO-yEI/AAAAAAAAD_U/FNrou_sKtOU/s640/cron.PNG" /></p><p>In a serendipitous coincidence, the next day at 6am the collegeadvatage.com login servlet was down, looking like this to a browser: </p><p><img src="https://lh4.googleusercontent.com/-0UgESOupszo/UU2tY43F-sI/AAAAAAAAD_U/lk01F64djr8/s600/error_ca.PNG" /></p><p>...and this to the App Engine log: </p><p><img src="https://lh4.googleusercontent.com/-2LIcaENlP9c/UU2tZLlyCRI/AAAAAAAAD_U/TMLq1YygnGE/s594/error_log.PNG" /></p><p>The top entry showing a success is one I ran manually after the site was repaired, so I was able to see that my error branching was placed fairly well. It's easy to spot as the manually invoked one because it is immediately preceded by a 302 redirect, meaning the security restraint was being provided, and Google was checking to make sure my account was both logged in and an administrator. On the following morning, the 6am job ran successfully, which correlates to the 10am balance (UTC) shown in the entity table here. </p><p><img src="https://lh4.googleusercontent.com/-XwTSk0Bouyc/UU2tYnngcpI/AAAAAAAAD_U/dBa2BSgWzS8/s510/entities.PNG" /></p><p>What's that? Yes, a deposit did just happen to clear before that last datastore entry was added. </p><p>The servlet that displays the current balance back to blogspot is much simpler, just querying the datastore for the most recent entity, and returning it's "balance" field: </p><pre><br />public class CABalServlet extends HttpServlet{<br /> public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {<br /> DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();<br /> Query q = new Query("balance").addSort("date", Query.SortDirection.DESCENDING);<br /> Entity e = datastore.prepare(q).asList(FetchOptions.Builder.withLimit(1)).get(0);<br /> resp.setContentType("text/html; charset=UTF-8");<br /> resp.addHeader("Access-Control-Allow-Origin", "http://cautery.blogspot.com");<br /> resp.getWriter().println(e.getProperty("balance"));<br /> }<br />}<br /></pre><p>This debugging process was a good reminder to me why being versed in the underlying protocols and bits on the wire is so important. In the ASP.NET world, a lot of what's going on under the hood is abstracted and obfuscated with runat=server includes, giving the programmer no indication of the madness going on in the HTML. Of course, if I'm going to complain about that, I should have a lot to say about GWT, which I have avoided lately out of a desire to code UI pieces in raw HTML. (What GWT loses in the enlightenment category regarding abstracting too much away from the coder, I think it makes up for by attempting to be cross-browser compliant with a large array of user controls. But wrong thinking is wrong thinking, no matter the larger intent.) </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-33570123427349880942013-03-13T20:58:00.000-04:002013-03-13T20:59:08.356-04:00Why Sheffer's Stroke is NAND instead of NOR<p>As a young boy, diving into mathematics was a great release for me. I often manipulated numbers in my head, independently discovered cross-multiplication, and found the gradeschool introduction to logic fascinating. Logic, of course, one needs to understand intuitively in order to be an effective coder. </p><p>Famous science personalities were my heroes. From Newton himself playing with equations of planetary motion in his giant Principia tome, to Godel showing with marvelous simplicity that all mathematical investigation will be (hopelessly?) incomplete, to Hoffstadter's work on artificial intelligence (and from whom I first heard the word "meme"), to Feynman picking locks at Los Alamos for kicks and investigating what he found interesting with little regard to its future practical value. </p><p>These were the giants to be revered, the Atlases holding the world on their shoulders. If only we could all be like them, forgoing our petty disagreements, cults of personality, and clanishness... or so I thought. Later when I realized that the science and maths world contained all of these things, it was quite disillusioning. Yes, even the greats stepped on each other's work, stole each other's credit, and disagreed on basic notation and phraseology of important concepts, particularly as a new field was being developed. </p><p>"Sheffer's Stroke" is an example of this kind of thing. Denoted by "|" (or "pipe" as we techies call it), in modern notation it refers to the logical NAND function. <i>a|b</i> is true in all cases where <i>a</i> and <i>b</i> are not both true. However, Henry Sheffer explicitly refers to his proposed function as "neither-nor", false in all cases where <i>a</i> and <i>b</i> are not both false. In his 1913 paper <a href="http://www.ams.org/journals/tran/1913-014-04/S0002-9947-1913-1500960-1/S0002-9947-1913-1500960-1.pdf">"A set of five independent postulates for Boolean algebras, with application to logical constants"</a>, he has this to say: </p><a name='more'></a><p><img src="https://lh4.googleusercontent.com/-j9D5H8yPYMk/UUEdhPiA6DI/AAAAAAAAD98/7LEax1l-0OY/s673/perDef.PNG" /></p><p>Logic gate diagrams of either NAND or NOR can be used to express most of the definitions in Sheffer's paper by virtue of <a href="http://en.wikipedia.org/wiki/De_Morgan%27s_laws">De Morgan duality</a>. Most, but not all. Specifically, proof IIa: </p><p><img src="https://lh4.googleusercontent.com/-XeL91GHsOok/UUEdhXpVtvI/AAAAAAAAD-A/0z-d_u_Nl8k/s459/zNand.PNG" /></p><p>Here <i>z</i> refers to 0, or false, where <i>u</i> (unity/unary) is 1, or true. If z refers to 0, then IIa only works for NOR. </p><p>Jean Nicand, however, found a reason for preferring NAND. In his 1917 paper, <a href="http://en.wikisource.org/wiki/A_Reduction_in_the_number_of_the_Primitive_Propositions_of_Logic">"A Reduction in the Number of Primitive Propositions of Logic"</a>, the following paragraphs which suggested an alternate function should be assigned to the stroke: </p><p><img src="https://lh6.googleusercontent.com/-15l4UXPlS8k/UUEdhSPzTII/AAAAAAAAD-E/9XhNBPwpzz8/s518/whyNand.PNG" /></p><p>This takes a little decoding. Let's start with the symbols: </p><pre><br />~ NOT<br />â€¢ AND<br />∨ OR<br />⊃ Implies (modern notation is an arrow, →)<br />/|<b>|</b> The proposed new function, in order of operation if they are chained together<br /></pre><p>So there are two options for | proposed by Nicand. The first: </p><pre><br />~p â€¢ ~q = (NOT p) AND (NOT q) := pq f<br /> 00 1<br /> 01 0<br /> 10 0<br /> 11 0<br /></pre><p>This is the NOR function, what Nicand confusingly calls the "AND-form". The second option: </p><pre><br />~p ∨ ~p = (NOT p) OR (NOT q) := pq f <br /> 00 1 <br /> 01 1 <br /> 10 1 <br /> 11 0<br /></pre><p>This is the NAND function, what Nicand calls the "OR-form". The "Implies" function takes a little explaining if you know logic mainly from coding. It has this truth table: </p><pre><br />pq ⊃<br />00 1<br />01 1<br />10 0<br />11 1<br /></pre><p>It is false only if the first input (the cause) is true, but the second input (the effect) is false. Let's assert this proposition: if I punch you in the nose, your nose will bleed. Punching will be <i>p</i>, bleeding will be <i>q</i>. Only if <i>p</i> is true (I punch you), and <i>q</i> is false (you don't bleed) will my assertion be false. If I haven't hit you, your nose may or may not be bleeding for other reasons, so this doesn't disprove the assertion. If you don't care for the whole causality discussion, this function can also be thought of as "<i>p</i> is less than or equal to <i>q</i>". </p><p>Now, if we take Nicand's last statement, that expressing ⊃ is p|q/q for NAND, and p/p|q<b>|</b>p/p| q for NOR, and turn those into logic gate diagrams, we see that the two functions are more similar than the notation would suggest: </p><p><img src="https://lh4.googleusercontent.com/-vuc4Or75TCI/UUEdg7W-3WI/AAAAAAAAD9w/qdI5asc3wV0/s326/nandImplies.PNG" /><br /><img src="https://lh4.googleusercontent.com/-fEA4UF3XEDw/UUEdg8MVASI/AAAAAAAAD9o/N5Fcao87P8M/s453/norImplies.PNG" /></p><p>Basically the NOR form just needs to reverse its outputs by using the first rule put forth by Sheffer: </p><p><img src="https://lh4.googleusercontent.com/-T_kYFq_Opns/UUEdg2ZW5SI/AAAAAAAAD-I/ht7hq-OR704/s278/def2.PNG" /></p><p>However, the world adopted Nicand's view that Sheffer's definition should be changed, making it very confusing if you're a 40 year old software developer who didn't major in mathematics trying to wade through these papers for the first time. This isn't necessarily a bad thing. The NOR function didn't disappear from logic, and Sheffer's contribution is still of monumental importance, as chaining NAND or NOR gates in semiconductors to build more complex logical operations is exactly what makes them work. It's how math is performed, it's how RAM is stored. It's everything. </p><p>Point being, scientists step on each others toes and take liberties with their works in a very human fashion, further cementing my views that close examination of any hero will show things that are disappointing, and that the study of maths, like the study of computer science, is as much the study of history and notation as anything else. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-74536622761196449482013-02-28T12:52:00.000-05:002013-03-01T12:09:52.427-05:00HTML5 logic circuit builder using NAND gates<p>This blog entry won't serve as an introduction to Boolean Algebra or logic gates, but rather just to illustrate an HTML5 widget I wrote that lets you manipulate NAND gates. In fact, before you begin, you should understand what these tables mean: </p><pre><br />NOR = f(a,b) NAND = f(a,b)<br />a b f a b f <br />0 0 1 0 0 1 <br />0 1 0 0 1 1 <br />1 0 0 1 0 1 <br />1 1 0 1 1 0 <br /></pre><p>If that makes no sense, start with <a href="http://en.wikipedia.org/wiki/Logic_gate">this Wikipedia article</a> on logic gates. </p><p>In the early 20th century, the maths world went wild trying to expand on George Boole's work on logic. Ernst SchrÃ¶der, Edward Huntington, and Alfred Whitehead each developed conflicting notation systems and postulate sets, each with published works that were pretty arcane. For instance: </p><a name='more'></a><p><a href="http://www.ams.org/journals/tran/1904-005-03/S0002-9947-1904-1500675-4/S0002-9947-1904-1500675-4.pdf">Sets of Independent Postulates for the Algebra of Logic</a><br /><a href="http://projecteuclid.org/DPubS/Repository/1.0/Disseminate?handle=euclid.chmm/1263316515&view=body&content-type=pdf_1">A Treatise on Universal Algebra</a></p><p>Subsequently, Charles Peirce and Henry Sheffer tried to take these methodologies and simplify them, and both created similar works describing a new logic function. Peirce described the NOR function in his paper <a href="http://books.google.com/books?id=E7ZUnx3FqrcC&q=378+Winter#v=snippet&q=378%20Winter&f=false">"A Boolean Algebra with One Constant"</a>, which was a novel concept at the time. </p><p>Although he wrote it 30 years earlier, his paper wasn't published until well after Sheffer's work describing the same function. What's that, you say? Sheffer's paper was on the NAND function? No, no it wasn't. Here are the postulates from Sheffer's paper <a href="http://www.ams.org/journals/tran/1913-014-04/S0002-9947-1913-1500960-1/S0002-9947-1913-1500960-1.pdf">"A set of five independent postulates for Boolean algebras, with application to logical constants"</a>: </p><p><img src="https://lh5.googleusercontent.com/-O3qSiwK7jcA/US-XyuVUfnI/AAAAAAAAD44/z0fnBEZ6m-E/s800/postulates.PNG" /></p><p>Again, pretty arcane. The business with the class K, and K-rules isn't intuitive, but my understanding is that it refers to functions which take multiple inputs from a set, and returns one output from the same set. Logic functions as the software developer understands them are an example of this, as logic functions have inputs from a class (true/false, or 0/1, or z/u if you're old school), and produce one output from the same class. </p><p>The postulates above work equally well for the NOR and NAND functions. Later in the paper he submits these corollaries: </p><pre><br />IIa. There is a K-element z such that for any K-element a, (a | z)' = a.<br />IIb. There is a K-element u such that for any K-element a, a' | u' = a<br /></pre><p>In a footnote he compares z with 0 (zero) and u with 1 (unary), which would make the above equations true only in a NOR function. However, flipping the values of z and u give us NAND. The point of the paper wasn't specifically to illustrate NAND or NOR, but to posit that there exists a logical operation that is functionally complete, and can be used to express the entire gamut of logical operations. Sheffer is credited incorrectly with the NAND function, and the pipe symbol he used in the above paper is referred to as the "Sheffer Stroke". </p><p>Because NAND and NOR are functionally complete, they show up in integrated circuits, where they can be placed in series to perform more complex operations. The <a href="http://en.wikipedia.org/wiki/4000_series">4000 series</a> is a good example of this. </p><p>And that's also where the fun starts. I've used some logic gate builder web apps before, and I found them all pretty clunky. I wanted something with a simpler user interface, that maybe I could build on later to a full featured circuit builder, or just hook into a simple quiz page (quick, build me an OR gate using only NANDs!). This is where the project stands now: </p><style>canvas { border: 1px solid blue; } </style><p><canvas id='cnv' width=500 height=500></canvas><script>function Nand() { var cr = Math.PI * 2; // a circle, in radians var inputNames = "abcdefghijklmnopqrstuvwxyz".split(""); var toolboxColor = '#dadaff'; var inputRadius = 20; var cnv, ctx, img; var objs = []; var andx; // Active object's index /* Arrays for logical operators corresponding to this input table: a : 0011 b : 0101 */ var defs = { And : "0001" ,Nand : "1110" ,Or : "0111" ,Nor : "1000" ,Xor : "0110" ,Xnor : "1001" ,Not : "10" } function draw() { ctx.clearRect(0, 0, cnv.width, cnv.height); ctx.fillStyle = toolboxColor; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'black'; ctx.drawImage(img, 0, 0); drawInput(0, 50, 'a'); for (var i in objs) objs[i].draw(); } function drawInput(x, y, label) { ctx.save(); ctx.translate(32, 5); x += inputRadius; y += inputRadius; ctx.font = '20px sans-serif'; ctx.fillText(label, x, y); ctx.beginPath(); ctx.moveTo(x + inputRadius, y); ctx.arc(x, y, inputRadius, 0, cr, false); ctx.moveTo(x + inputRadius + 8, y); ctx.arc(x + inputRadius + 4, y, 4, cr, false); ctx.moveTo(x + inputRadius + 8, y); ctx.lineTo(x + inputRadius + 24, y); ctx.stroke(); ctx.restore(); } function drawConnection(a, b, top) { var ax = a.pos[0] + 95; var ay = a.pos[1] + 25; var bx = b.pos[0] + 5; var by = b.pos[1] + 16; if (!top) by += 17; ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(bx, by); ctx.stroke(); } window.addEventListener('mousedown', setClickedObject, false); window.addEventListener('mousemove', useClickedObject, false); window.addEventListener('mouseup', clearActive, false); document.addEventListener('touchstart', useTouchedObject, false); function clearConnections(obj) { for (var i in objs) { if (objs[i] instanceof Gate) { if (objs[i].top == obj) objs[i].top = null; if (objs[i].bottom == obj) objs[i].bottom = null; } } } var sp = 7; // Snap Pixels: how close you have to be to a gate input to drop a connection on it function checkConnection(obj, x, y) { for (var i in objs) { if (objs[i] instanceof Gate) { var ox = objs[i].pos[0] + 5; var oy = objs[i].pos[1] + 16; if (x >= ox-sp && x <= ox+sp && y >= oy-sp && y <= oy+sp) objs[i].top = obj; oy += 17; if (x >= ox-sp && x <= ox+sp && y >= oy-sp && y <= oy+sp) objs[i].bottom = obj; } } } function clearActive(e) { if (andx == null) return; var del = 0; var obj = objs[andx]; if (obj.mode == 1) checkConnection(obj, e.offsetX, e.offsetY); if (obj.pos[0] <= 100 && obj.pos[1] <= 100) { if (obj instanceof Input) obj.releaseLabel(); clearConnections(obj); objs.splice(andx, 1); del = 1; } andx = null; // Check connections for possible loops for (var i in objs) { var o = objs[i]; if (o.top != null && o.pos[0] <= o.top.pos[0]) o.top = null; if (o.bottom != null && o.pos[0] <= o.bottom.pos[0]) o.bottom = null; } draw(); if (del == 0 && obj instanceof Gate) { var tbl = obj.getTable(); if (tbl == null) return; for (var d in defs) { if (tbl == defs[d]) { tbl += " : " + d; break; } } ctx.fillText(tbl, obj.pos[0] + 50, obj.pos[1] - 5); } } function setClickedObject(e) { setActiveObject(e.offsetX, e.offsetY); } function useClickedObject(e) { if (andx == null) return; useObject(e.offsetX, e.offsetY); } function useTouchedObject(e) { if (e.touches.length == 1) { useObject(e.touches[0].pageX, e.touches[0].pageY); } e.preventDefault(); } function useObject(x, y) { if (andx == null) return; var obj = objs[andx]; x -= obj.offsetX; y -= obj.offsetY; use(obj, x, y); } function setActiveObject(x, y) { andx = null; if (x < 100 && y < 100) { andx = objs.length; var obj; if (y < 50) { obj = new Gate(); obj.offsetY = y; } else { obj = new Input(); obj.offsetY = y - 50; } objs.push(obj); obj.offsetX = x; obj.mode = 0; return; } for (var i in objs) { var obj = objs[i]; var ox = obj.pos[0]; var oy = obj.pos[1]; if (x >= ox && x <= ox + 100 && y >= oy && y <= oy + 50) { andx = i; obj.offsetX = x - ox; obj.offsetY = y - oy; obj.mode = obj.offsetX > 66 ? 1 : 0; return; } } } function init() { img = new Image(); img.onload = firstDraw; img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAADkUlEQVR4Xu2bSa7UMBCG37sBcAEaDsB4AB7DmkFiDTxgzbhmZomY1oCAPQKxB5oDMO6ZLgDcAP5PbUN30lHcnepn66UslZJO22WnftfgsrO85KUoCSwXNRofzJIDUtgkcEAckH8SeG0si73i91H0O/Ad6vpC9Mm4n4Wyy6UhgLHTWFh7GiT1KwDzRNe3C5WmAfOcgNA3s9qybBazLYHhYV0PiQZjHTzX/XnRD8tOLXmtN0CmyQaQTojOiTaECrd1vWgpSCtefQAkygowLgcN4dl70X5R9DlWMu3Ep0+AREFt1w3OHlP2TXREVIzj7yMgAIO2xMACp48vKwKUvgISteWRblZFX0W7SzBffQck+hJCcHwKoGQtDsjIfAEG4XL26MsBGekDjp5VPgVgvudSkyZArNMa1ffbEZyo9cKwixwfqPEp0TPR0S6MurRtAmTYhWlC220FAoLpIgzmuiLKkmZxkzU5ey7p53XRQ9HphIllXsUBmRTpIGgJa5NN5tJOYOiA1IXEmgTHTigcHX2CKG2qOCB1Od7SIxKPmK+booMiMsT4FcpQdEf00gaCSS4OSF2qCP6N6JWINP3JBsGTqLxhDYoD0gzIT/2FHyEbfFV0T0QEdlx0NzRjv8VUUxyQOiADPSL8jQUwrlWqXQkgoUUHLLXEAZkuzT9jj6fJCE0hEqNg3uYt+6oNHZB2QDaqSnUTaxyQLgvIFQekfS5XTdZZNblfaXZGv/EpbrLa5dm5BrMWM4RJQjsogPI03B8LYPCTul00pDZYN1l1/CIgzP7PItYg04qHvZ3nfhqDaQvDC2oaz32hEeybmIa7cWiuIXWQvujRVpGnTtIm8EJrDcSdNQhRVfQfC+2wytw3qCYlUmz6fbjgaVHqBhWZXjQDx24aPaXK033If0nFLdxsm1MMxQEZAVL8IYdUDZu3HocomAwlHHIgDfIuRFZ+DGheRA3bAcYu0YdwNWQ9O6u+myz8BRtQhLqAkv0kfF8BwUyRGokgEFX5YetMPgQHzpdU8YQiX1oVAUbfoiy0Aqe9Giw7PoMNouxmatzT9MFk8Ukb++B80hbTIZwaIWFYXFlvgCD8QZBy/OgT0xQLX04BDKdJiiw5ASGbitmwKk1rGkwSQDwWZUmHzPKCuQBhjMNZBppQl/0KnHP0CfDHeRfjsBPeIVvqJGVsvayTU0N6KfC2l3ZA2iS0xv87IGss8Lbu/gJ1Kawz2ni+uAAAAABJRU5ErkJggg=="; } function firstDraw() { cnv = document.getElementById('cnv'); ctx = cnv.getContext('2d'); ctx.lineWidth = 2; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; draw(); } function nand(a, b) { if (a == null || b == null) return null; return !(a && b); } function drawLine(obj, x, y) { ctx.beginPath(); ctx.moveTo(obj.pos[0] + 95, obj.pos[1] + 25); ctx.lineTo(x, y); ctx.stroke(); } function use(obj, x, y) { if (obj.mode == 0) { obj.pos = [x, y]; draw(); } else { draw(); drawLine(obj, x + obj.offsetX, y + obj.offsetY); } } function Input() { var g = this; var label = inputNames.shift(); this.getLabel = function() { return label; } this.pos = [0, 50]; var curval = 0; this.value = function() { return curval; } this.setValue = function(n) { curval = n; } this.draw = function() { drawInput(g.pos[0], g.pos[1], label); } this.releaseLabel = function() { inputNames.unshift(label); inputNames.sort(); } } function Gate() { var g = this; var tbl; this.pos = [0, 0]; this.top = null; this.bottom = null; this.value = function() { if (g.top == null || g.bottom == null) return null; var v = nand(g.top.value(), g.bottom.value()); if (v == null) return null; return v ? 1 : 0; } this.getTable = function() { var lastFlip = 1; var inputs = []; if (g.value() == null) return null; // Set all inputs to 0 for (var i in objs) { if (objs[i] instanceof Input) { objs[i].setValue(0); inputs.push(objs[i]); } } inputs.sort(function(a,b) { return a.getLabel() < b.getLabel() ? -1 : a.getLabel() > b.getLabel() ? 1 : 0 }).reverse(); var tbl = ''; while (lastFlip == 1) { tbl += g.value(); for (var i in inputs) { lastFlip = inputs[i].value() == 0 ? 1 : 0; inputs[i].setValue(lastFlip); if (lastFlip == 1) break; } } return tbl; } this.draw = function() { ctx.drawImage(img, g.pos[0], g.pos[1]); if (g.top != null) drawConnection(g.top, g, 1); if (g.bottom != null) drawConnection(g.bottom, g, 0); } return this; } init(); } Nand(); </script></p><p>Here is a video of me using the same to create other basic logic gates: </p><p><iframe width="420" height="315" src="http://www.youtube.com/embed/NbBv8LmRtcU" frameborder="0" allowfullscreen></iframe></p><p>And lastly, here are some images of using the app to illustrate a couple of Sheffer's postulates: </p><p>4. Whenever a, b, and the indicated combinations of a and b are K-elements, a | (b | b') = a'.<br /><img src="https://lh5.googleusercontent.com/-TgavUPwPulE/US-XyUyhvCI/AAAAAAAAD40/KGpbRZ28QNM/s800/b-nand-not-b.PNG" /></p><p>5. Whenever a,b,c, and the indicated combinations of a, b, and c are K-elements, (a | (b | c))' = (b' | a) | (c' | a).<br />Left side: <img src="https://lh4.googleusercontent.com/-sFRbjtm9joc/US-XyKuwTII/AAAAAAAAD4o/B7v3VEDnW6c/s800/abc-nand1.PNG" /><br />Right side: <img src="https://lh5.googleusercontent.com/-F6nGuLOTy9Y/US-XyHrGWTI/AAAAAAAAD4s/lmCYQVEcW8E/s800/abc-nand2.PNG" /></p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-79929118192608896392013-02-05T12:20:00.001-05:002013-02-11T10:14:47.869-05:00Tax tables from 1998 to 2013 at a glance<p>The form below is the result of combing through PDF files from irs.gov to get an accurate picture of how federal payroll withholding tables changed through the years. I later tacked on tax tables to compare with the payroll tables to see how they lined up (which they do pretty well... with a couple exceptions that I'll get to). I've populated data from the years 1998 to 2013, and added a simple HTML5 interface for charting the results. </p><p>Choose a salary from the slider (if you don't see a slider control, the charts won't work with your browser - the HTML5 "range" input type currently only works in Webkit browsers and Opera), and then click married or single, to see the chart for that salary. </p><p>The code makes some basic assumptions for the sake of simplicity. First, the salary is taxable income, after other deductions such as 401k, social security, etc. Second, the tax line adjusts the salary down by the standard deduction (twice that for the "married" category, assuming "married filing jointly" is the filing status), where the withholding line does not. This appears to be the intent of the descrepencies between withholding and tax tables, that is, separating what your employer knows about you from what the government does. Your employer doesn't know about your mortgage interest, whether you'll file joint or separately, whether the child tax credit applies to you, how much you spent for college tuition, or any of the hundreds of other deductions or credits you'll talk to Uncle Sam about. They know what you contribute to 401k, how many dependants you intend to claim, and that's about it. </p><a name='more'></a><p>While putting the tables together, I noticed some pretty odd things, and with some digging on senate.gov and gpo.gov, I was able to reconcile them all with the public laws they originated from. Let's start in the present and move back in time. </p><p><i>Note: I tend to avoid political commentary in this blog, but in this case numbers and politics are tightly intertwined, so some of my liberal ranting is inevitable. If that bugs you, feel free to ignore it all and scroll down to the charts.</i></p><p>First, in 2013, there is a very narrow salary range for single filers between the 35% and 39.6% brackets. Here is the tax table in question: </p><pre><br /> Salary Base withholding % of excess<br />$ 0.00 $ 0.00 10%<br />$ 8,925.00 $ 892.50 15%<br />$ 36,250.00 $ 4,991.25 25%<br />$ 87,850.00 $ 17,891.25 28%<br />$ 183,250.00 $ 44,603.25 33%<br />$ 398,250.00 $ 115,553.25 35%<br />$ 400,000.00 $ 116,165.75 39.6%<br /></pre><p>The 400k figure is from the <a href="http://www.gpo.gov/fdsys/pkg/BILLS-112hr8enr/pdf/BILLS-112hr8enr.pdf">Taxpayer Relief Act of 2012</a>, which specifies the very round figures of $400,000 for single and $450,000 for married as where the 39.6 percent tax bracket should be reintroduced. It dovetails with Obama's "pay your fair share, Mitt Romneys of the nation" reelection ads, preys on a common misunderstanding of how tax brackets work, and is completely ineffective at its stated goals. </p><p>The misconception is that, under this table, if someone makes over 400k, their tax rate is 39.6 percent. It isn't. If it was, the base withholding in the top bracket would be 39.6% of $400,000: $158,400, instead of the 116k and change listed in the table. It's only the salary <b>after</b> 400k that gets taxed at the higher rate. The $1,750 before that is taxed at 35%, the $215,000 before that is only taxed at 33%, etc. If you make exactly 400k, your effective tax rate under this table is a smidge over 29%, more than 10% less than the big figure you imagined was being used to stick it to the man. </p><p>It fails at its stated goal of making the rich pay a "fair share" (which we'll define as what they typically paid before the Bush tax cuts), as you can clearly see in the charts below by cranking the salary to the top, and comparing 2013 with 1998. It also fails at delivering on its name: providing taxpayer relief, as the other tax brackets remain essentially the same. </p><p>The next oddity happens in 2010, where there are two more payroll withholding brackets than there are tax brackets. This comes from the American Recovery and Reinvestment Act of 2009 (ARRA), otherwise known as the Stimulus Bill (or the Bailout, depending on your ideological leaning). The ARRA was purportedly enacted to cover the gap in private spending with more government spending. I don't want to go into whether or not that's right thinking, because I don't know. There are two basic camps about why the stimulus didn't fix anything: people who think John Keynes was a quack, and people who think that the bill didn't go far enough (most famously, Paul Krugman, who estimated the spending gap was three times what the bill was covering). </p><p>Oddly, the GPO link to the bill's text is broken, but <a href="http://www.finance.senate.gov/library/reports/conference/download/?id=baf954fc-01f7-47d7-90bc-dfb3cd036a67">here</a> is a link to the (4 megabyte) senate conference report, which includes details about the extra withholding brackets. In short, they relate to the $400 Earned Income Credit introduced in 2009. From pages 517 and 518 of the report: </p><blockquote><p>Income tax withholding </p><p>Taxpayersâ€™ reduced tax liability under the provision shall be expeditiously implemented through revised income tax withholding schedules produced by the Internal Revenue Service. </p><p>... </p><p>The provision provides eligible individuals a refundable income tax credit for two years (taxable years beginning in 2009 and 2010). The credit is the lesser of (1) 6.2 percent of an individualâ€™s earned income or (2) $400 ($800 in the case of a joint return). </p></blockquote><p>So basically the tax tables remained static, and withholding was lowered to get the $400 in everyone's hands quicker than waiting for a tax return. Since you only get the credit if you have a job, then that should work out for everyone. Except for small businesses without tax lawyers and a payroll IT department on hand, who would have to spend money to get the new rules implemented in the middle of a tax year. </p><p>At any rate, the charts below reflect the intent. Until around $80,000 for single and $140,000 for married is reached, you'll see a dramatic drop in withholding in 2009 (the new tables were released after the tax year began, so this makes sense), which tapers in 2010, then lines back up with the original curve in 2011 and forward. </p><p>What the IRS came up with in 2010 to line up payroll withholding with the $400 credits is nothing short of bizarre. Here are those tables: </p><pre><br />Single<br /> Salary Base withholding % of excess<br />$ 6,050.00 $ 0.00 10%<br />$ 10,425.00 $ 437.50 15%<br />$ 36,050.00 $ 4,281.25 25%<br />$ 67,700.00 $ 12,193.75 27%<br />$ 84,450.00 $ 16,716.25 30%<br />$ 87,700.00 $ 17,691.25 28%<br />$ 173,900.00 $ 41,827.25 33%<br />$ 375,700.00 $ 108,421.25 35%<br /><br />Married<br /> Salary Base withholding % of excess<br />$ 13,750.00 $ 0.00 10%<br />$ 24,500.00 $ 1,075.00 15%<br />$ 75,750.00 $ 8,762.50 25%<br />$ 94,050.00 $ 13,337.50 27%<br />$ 124,050.00 $ 21,437.50 25%<br />$ 145,050.00 $ 26,687.50 28%<br />$ 217,000.00 $ 46,833.50 33%<br />$ 381,400.00 $ 101,085.50 35%<br /></pre><p>The new withholding numbers were inserted between the 25 and 28 percent brackets. Look closely at those percentages. Single brackets go up from 25, to 27, to 30, then <b>back down</b> to 28. Married brackets go from 25 to 27, back to 25, then 28. Madness. There must have been a more mathematically sensible way to accomplish the same thing. Anyway, moving on. </p><p>In 2001 and 2003, withholding and tax tables had the same number of brackets, but their percentages don't match. After the 15% bracket, 2001's rates have a half percent difference, and in '03 that rate jumps to 2%. By 2004, there is an extra tax bracket, and the maximum rate drops from 39.6% to 35%. This is a result of the Bush tax cuts, introduced as two separate bills in 2001 and 2003. </p><p>2001 brought us the <a href="http://www.gpo.gov/fdsys/pkg/BILLS-107hr1836enr/pdf/BILLS-107hr1836enr.pdf">Economic Growth and Tax Relief Reconciliation Act</a>, which laid out this graduated change in tax rates: </p><pre><br />In the case of taxable years The corresponding percentages<br />beginning during calendar year: shall be substituted for <br /> the following percentages: <br /> 28.0% 31.0% 36.0% 39.6% <br />-----------------------------------------------------------------------<br />2001 27.5% 30.5% 35.5% 39.1%<br />2002 and 2003 27.0% 30.0% 35.0% 38.6%<br />2004 and 2005 26.0% 29.0% 34.0% 37.6%<br />2006 and thereafter 25.0% 28.0% 33.0% 35.0%<br /></pre><p>If I understand correctly, the intent was to give everyone more cash to go buy stuff, which would, despite there being no net change in the GDP, somehow grow the economy faster, make everyone richer in a few years, and give the government a net gain in revenue, which would have the national debt paid off by 2010. </p><p>The bill was signed into law in June of 2001, and before it could be put to the test, 9/11 happened, which, as you well know, had a large, negative effect on the financial health of the United States. </p><p>Rather than re-assess, Bush doubled down with the <a href="http://www.gpo.gov/fdsys/pkg/BILLS-108hr2enr/pdf/BILLS-108hr2enr.pdf">Jobs and Growth Tax Relief Reconciliation Act of 2003</a>, which sped up the transition to the new tax rates, changing the text of the original law to contain this graduated table: </p><pre><br />In the case of taxable years The corresponding percentages<br />beginning during calendar year: shall be substituted for <br /> the following percentages: <br /> 28.0% 31.0% 36.0% 39.6% <br />-----------------------------------------------------------------------<br />2001 27.5% 30.5% 35.5% 39.1%<br />2002 27.0% 30.0% 35.0% 38.6%<br />2003 and thereafter 25.0% 28.0% 33.0% 35.0%<br /></pre><p>If you look at the charts below, playing with the salary and filing status, you will see one unmistakable fact: After 2001, the government collected less money from everyone. Much less. The current estimate is that close to $1.4 trillion in lost government revenue was caused by these cuts, on par with the cost of the Iraq and Afghanistan wars. </p><p>The original bill in 2001 was lauded by the Heritage Foundation in <a href="http://origin.heritage.org/research/reports/2001/04/the-economic-impact-of-president-bushs-tax-relief-plan">this report</a>, peppered with language like "phase out the death tax", and including the following defense of the idea, an apologist response to a prior study by the CBPP (emphasis mine): </p><blockquote><p>The Center on Budget and Policy Priorities (CBPP) estimates that the Bush plan would reduce tax revenue by up to $2.1 trillion and increase interest payments by $400 billion over 10 years. This estimate is very misleading because it includes a number of provisions that are not in the Bush tax plan and because it does not recognize that <i>economic growth and the tax base will increase at a faster pace when tax rates are reduced.</i></p></blockquote><p>They had a separate report to expand on that idea called, simply enough, "Why the Center on Budget and Policy Priorities Is Wrong About the Cost of Bush's Tax Plan". As it turns out, they were both wrong, but the CBPP was at least estimating in the right direction. </p><p>Bush and those supporting his direction were pretty bad at numbers, it seems. During his first term, the Congressional Budget Office was still estimating the Iraq and Afghanistan actions as having a final tab in the low billions, as shown in these two reports: </p><p><a href="http://www.cbo.gov/sites/default/files/cbofiles/ftpdocs/46xx/doc4683/10-28-spratt.pdf">http://www.cbo.gov/sites/default/files/cbofiles/ftpdocs/46xx/doc4683/10-28-spratt.pdf</a><br /><a href="http://www.cbo.gov/sites/default/files/cbofiles/ftpdocs/33xx/doc3362/afghanistan.pdf">http://www.cbo.gov/sites/default/files/cbofiles/ftpdocs/33xx/doc3362/afghanistan.pdf</a></p><p>My conclusion is that it must be pretty hard to think in the midst of all that leadership.</p><p><img src="https://lh5.googleusercontent.com/-fuE4y2Sl6JU/URE_KgLxhVI/AAAAAAAAD3I/Up5958vEyxE/s800/pen.PNG" /></p><p>I'll leave you with this last observation: the tax table brackets are inflation-adjusted every year. The effect is, taking out the anomalies of the Bush tax cuts and the $400 credits in '09 and '10, a downward curve in tax revenue. Not only does the money the IRS collects every year have less buying power than the year before due to normal inflation, but they're deliberately collecting less each year. </p><p>In addition, with the exception of the newly re-added 39.6% bracket, the Bush tax cuts are now permanent, costing the Federal government billions each year in lost revenue, which if not creatively repealed under the Byrd rule, will eventually cause government services to be reduced (increasing unemployment), and possibly scuttling Social Security. Me, I'd rather lose some disposable income and have the government continue to operate. </p><form id="taxForm" onsubmit="return false" oninput="setInputs(this)"> $20,000 <input name="salarySlider" type="range" min="20000" max="500000" value="20000" step="10000" /> $500,000 <span class=op>Salary: <output name="currentSalary"></output> </span><br /><input type="hidden" name="maritalStatus" value="single" /><div class="selectBox"> <span class="styledSelect" id="statusSingle" onmousedown="setMarital(this)">Single</span> <span class="styledSelect" id="statusMarried" onmousedown="setMarital(this)">Married</span></div></form><br /><canvas id="graphCanvas" width=640 height=480></canvas><div id="withholding" style="width: 750px; margin: auto;"> <div style="text-align: center; font-size: 20pt; margin: 10px;">Federal Withholding Tables</div></div> <style>#taxForm { width : 620px; margin: auto; border: 1px solid blue; padding: 0 10px;} #graphCanvas { border: 1px solid blue; margin: auto; display: block; } input { vertical-align: middle; } .op { display: inline-block; float: right;} .selectBox { width: 100px; border: 1px solid grey; margin: 5px 0; } .styledSelect { display: block; padding: 0 10px; } .styledSelect:hover { cursor: pointer; } table { border-spacing: 0; margin-bottom: 20px; -webkit-print-color-adjust: exact; } th, td { padding: 0 10px; } td { text-align: right; } </style> <script>var startYear = 1998, endYear = 2013; // Slightly modified version of money formatter on jsfromhell.com/number/fmt-money Number.prototype.formatMoney = function(c){ var n = this, d = ".", t = ","; if (c == null) c = 2; var s = n < 0 ? "-$ " : "$ "; var i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + ""; var j = (j = i.length) > 3 ? j % 3 : 0; return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); }; var maritalStatus, curSalary, curSalMoney; /* Tax tables, standard deductions, personal exemptions and payroll withholding rates by year, 1998 - 2013, from irs.gov. Strangely, the tax tables and withholding percentages don't always match. e.g., http://www.irs.gov/pub/irs-prior/i1040tt--2001.pdf and p15--2001.pdf */ var taxes = { 2013 : { sd : 6100, exemption : 3900 ,singleW : [ [2200, 10], [11125, 15], [38450, 25], [ 90050, 28], [185450, 33], [400550, 35], [402200, 39.6] ] ,singleT : [ [ 0, 10], [ 8925, 15], [36250, 25], [ 87850, 28], [183250, 33], [398250, 35], [400000, 39.6] ] ,marriedW : [ [8300, 10], [26150, 15], [80800, 25], [154700, 28], [231350, 33], [406650, 35], [458300, 39.6] ] ,marriedT : [ [ 0, 10], [17850, 15], [72500, 25], [146400, 28], [223050, 33], [398350, 35], [450000, 39.6] ] } ,2012 : { sd : 5950, exemption: 3800 ,singleW : [ [2150, 10], [10850, 15], [37500, 25], [ 87800, 28], [180800, 33], [390500, 35] ] ,singleT : [ [ 0, 10], [ 8700, 15], [35350, 25], [ 85650, 28], [178650, 33], [388350, 35] ] ,marriedW : [ [8100, 10], [25500, 15], [78800, 25], [150800, 28], [225550, 33], [396450, 35] ] ,marriedT : [ [ 0, 10], [17400, 15], [70700, 25], [142700, 28], [217450, 33], [388350, 35] ] } ,2011 : { sd : 5800, exemption: 3700 ,singleW : [ [2100, 10], [10600, 15], [36600, 25], [ 85700, 28], [176500, 33], [381250, 35] ] ,singleT : [ [ 0, 10], [ 8500, 15], [34500, 25], [ 83600, 28], [174400, 33], [379150, 35] ] ,marriedW : [ [7900, 10], [24900, 15], [76900, 25], [147250, 28], [220200, 33], [387050, 35] ] ,marriedT : [ [ 0, 10], [17000, 15], [69000, 25], [139350, 28], [212300, 33], [379150, 35] ] } ,2010 : { sd : 5700, exemption: 3650 ,singleW : [ [6050, 10], [10425, 15], [36050, 25], [ 67700, 27], [ 84450, 30], [ 87700, 28], [173900, 33], [375700, 35] ] ,singleT : [ [ 0, 10], [ 8375, 15], [34000, 25], [ 82400, 28], [171850, 33], [373650, 35] ] ,marriedW : [[13750, 10], [24500, 15], [75750, 25], [ 94050, 27], [124050, 25], [145050, 28], [217000, 33], [381400, 35] ] ,marriedT : [ [ 0, 10], [16750, 15], [68000, 25], [137300, 28], [209250, 33], [373650, 35] ] } ,2009 : { sd : 5700, exemption: 3650 ,singleW : [ [7180, 10], [10400, 15], [36200, 25], [ 66530, 28], [173600, 33], [375000, 35] ] ,singleT : [ [ 0, 10], [ 8350, 15], [33950, 25], [ 82250, 28], [171550, 33], [372950, 35] ] ,marriedW : [[15750, 10], [24450, 15], [75650, 25], [118130, 28], [216600, 33], [380700, 35] ] ,marriedT : [ [ 0, 10], [16700, 15], [67900, 25], [137050, 28], [208850, 33], [372950, 35] ] } ,2008 : { sd : 5450, exemption: 3500 ,singleW : [ [2650, 10], [10300, 15], [33960, 25], [ 79725, 28], [166500, 33], [359650, 35] ] ,singleT : [ [ 0, 10], [ 8025, 15], [32550, 25], [ 78850, 28], [164550, 33], [357700, 35] ] ,marriedW : [ [8000, 10], [23550, 15], [72150, 25], [137850, 28], [207700, 33], [365100, 35] ] ,marriedT : [ [ 0, 10], [16050, 15], [65100, 25], [131450, 28], [200300, 33], [357700, 35] ] } ,2007 : { sd : 5350, exemption: 3400 ,singleW : [ [2650, 10], [10120, 15], [33520, 25], [ 77075, 28], [162800, 33], [351650, 35] ] ,singleT : [ [ 0, 10], [ 7825, 15], [31850, 25], [ 77100, 28], [160850, 33], [349700, 35] ] ,marriedW : [ [8000, 10], [23350, 15], [70700, 25], [133800, 28], [203150, 33], [357000, 35] ] ,marriedT : [ [ 0, 10], [15650, 15], [63700, 25], [128500, 28], [195850, 33], [349700, 35] ] } ,2006 : { sd : 5150, exemption: 3300 ,singleW : [ [2650, 10], [10000, 15], [32240, 25], [ 73250, 28], [156650, 33], [338400, 35] ] ,singleT : [ [ 0, 10], [ 7550, 15], [30650, 25], [ 74200, 28], [154800, 33], [336550, 35] ] ,marriedW : [ [8000, 10], [22900, 15], [68040, 25], [126900, 28], [195450, 33], [343550, 35] ] ,marriedT : [ [ 0, 10], [15100, 15], [61300, 25], [123700, 28], [188450, 33], [336550, 35] ] } ,2005 : { sd : 5000, exemption: 3200 ,singleW : [ [2650, 10], [ 9800, 15], [31500, 25], [ 69750, 28], [151950, 33], [328250, 35] ] ,singleT : [ [ 0, 10], [ 7300, 15], [29700, 25], [ 71950, 28], [150150, 33], [326450, 35] ] ,marriedW : [ [8000, 10], [22600, 15], [66200, 25], [120750, 28], [189600, 33], [333250, 35] ] ,marriedT : [ [ 0, 10], [14600, 15], [59400, 25], [119950, 28], [182800, 33], [326450, 35] ] } ,2004 : { sd : 4850, exemption: 3100 ,singleW : [ [2650, 10], [ 9700, 15], [30800, 25], [ 68500, 28], [148700, 33], [321200, 35] ] ,singleT : [ [ 0, 10], [ 7150, 15], [29050, 25], [ 70350, 28], [146750, 33], [319100, 35] ] ,marriedW : [ [8000, 10], [22300, 15], [64750, 25], [118050, 28], [185550, 33], [326100, 35] ] ,marriedT : [ [ 0, 10], [14300, 15], [58100, 25], [117250, 28], [178650, 33], [319150, 35] ] } ,2003 : { sd : 4750, exemption: 3050 ,singleW : [ [2650, 10], [ 8550, 15], [30100, 27], [ 65920, 30], [145200, 35], [313650, 38.6] ] ,singleT : [ [ 0, 10], [ 7000, 15], [28400, 25], [ 68800, 28], [143500, 33], [311950, 35] ] ,marriedW : [ [6450, 10], [18450, 15], [52350, 27], [111800, 30], [179600, 35], [316850, 38.6] ] ,marriedT : [ [ 0, 10], [14000, 15], [56800, 25], [114650, 28], [174700, 33], [311950, 35] ] } ,2002 : { sd : 4700, exemption: 3000 ,singleW : [ [2650, 10], [ 8550, 15], [29650, 27], [ 64820, 30], [142950, 35], [308750, 38.6] ] ,singleT : [ [ 0, 10], [ 6000, 15], [27950, 27], [ 67700, 30], [141250, 35], [307050, 38.6] ] ,marriedW : [ [6450, 10], [18450, 15], [51550, 27], [109700, 30], [176800, 35], [311900, 38.6] ] ,marriedT : [ [ 0, 10], [12000, 15], [46700, 27], [112850, 30], [171950, 35], [307050, 38.6] ] } ,2001 : { sd : 4550, exemption: 2900 ,singleW : [ [2650, 15], [28700, 28], [62200, 31], [138400, 36], [299000, 39.6] ] ,singleT : [ [ 0, 15], [27050, 27.5], [65550, 30.5], [136750, 35.5], [297350, 39.1] ] ,marriedW : [ [6450, 15], [49900, 28],[105200, 31], [171200, 36], [302050, 39.6] ] ,marriedT : [ [ 0, 15], [45200, 27.5],[109250, 30.5], [166500, 35.5], [297350, 39.1] ] } ,2000 : { sd : 4400, exemption: 2800 ,singleW : [ [2650, 15], [27850, 28], [59900, 31], [134200, 36], [289950, 39.6] ] ,singleT : [ [ 0, 15], [26250, 28], [63550, 31], [132600, 36], [288350, 39.6] ] ,marriedW : [ [6450, 15], [48400, 28],[101000, 31], [166000, 36], [292900, 39.6] ] ,marriedT : [ [ 0, 15], [43850, 28],[105950, 31], [161450, 36], [288350, 39.6] ] } ,1999 : { sd : 4300, exemption: 2750 ,singleW : [ [2650, 15], [27300, 28], [58500, 31], [131800, 36], [284700, 39.6] ] ,singleT : [ [ 0, 15], [25750, 28], [62450, 31], [130250, 36], [283150, 39.6] ] ,marriedW : [ [6450, 15], [47500, 28], [98500, 31], [163000, 36], [287600, 39.6] ] ,marriedT : [ [ 0, 15], [43050, 28],[104050, 31], [158550, 36], [283150, 39.6] ] } ,1998 : { sd : 4250, exemption: 2700 ,singleW : [ [2650, 15], [26900, 28], [57450, 31], [129650, 36], [280000, 39.6] ] ,singleT : [ [ 0, 15], [25350, 28], [61400, 31], [128100, 36], [278450, 39.6] ] ,marriedW : [ [6450, 15], [46750, 28], [96450, 31], [160350, 36], [282850, 39.6] ] ,marriedT : [ [ 0, 15], [42350, 28],[102300, 31], [155950, 36], [278450, 39.6] ] } } function makeBaseWithholding(arr) { var base = 0; for (var n = 0; n < arr.length; n++) { base += n == 0 ? 0 : (arr[n][0] - arr[n-1][0]) * arr[n-1][1] / 100; arr[n].push(base); } } function makeBaseWithholdings() { for (var n = startYear; n <= endYear; n++) { makeBaseWithholding(taxes[n]['singleW']); makeBaseWithholding(taxes[n]['singleT']); makeBaseWithholding(taxes[n]['marriedW']); makeBaseWithholding(taxes[n]['marriedT']); } } function init() { if (document.getElementById('taxForm').salarySlider.type != "range") { alert("This page may not be fully functional in your browser."); // return; } setMarital(document.getElementById('statusSingle')); setInputs(document.getElementById('taxForm')); makeBaseWithholdings() makeTables(); makeGraphs(); } function clearSelectBox(node) { for (var c = 0; c < node.childElementCount; c++) { node.children[c].style.background = ''; } } function setInputs(frm) { curSalary = parseFloat(frm.salarySlider.value); curSalMoney = frm.currentSalary.value = curSalary.formatMoney(0); makeGraphs(); } function setMarital(ip) { clearSelectBox(ip.parentNode); ip.style.background = "lightGrey"; maritalStatus = ip.id == 'statusSingle' ? 'single' : 'married'; if (curSalary != null) makeGraphs(); } function makeTable(year, marital, suffix) { var arr = taxes[year][marital + suffix]; var tbl = document.createElement('table'); var headerRow = document.createElement('tr'); var headerText = marital == 'single' ? 'Single' : 'Married'; headerText += suffix == 'W' ? ' Withholding' : ' Tax'; var headerCell = new Elem('th', headerText); headerCell.colSpan = 3; headerCell.style.fontSize = '16pt'; headerCell.style.fontWeight = 'normal'; headerRow.appendChild(headerCell); tbl.appendChild(headerRow); var captionRow = document.createElement('tr'); captionRow.appendChild(new Elem('th', 'Salary')); captionRow.appendChild(new Elem('th', 'Base withholding')); captionRow.appendChild(new Elem('th', '% of excess')); tbl.appendChild(captionRow); for (var n = 0; n < arr.length; n++) { var dataRow = arr[n]; var row = document.createElement('tr'); if (n % 2 == 0) row.style.background = 'lightGrey'; row.appendChild(new Elem('td', parseFloat(dataRow[0]).formatMoney())); row.appendChild(new Elem('td', parseFloat(dataRow[2]).formatMoney())); row.appendChild(new Elem('td', dataRow[1] + '%')); tbl.appendChild(row); } return tbl; } function Elem(name, text) { var elem = document.createElement(name); elem.innerHTML = text; return elem; } function makeTables() { var ctn = document.getElementById('withholding'); for (var n = endYear; n >= startYear; n--) { var yearHeader = new Elem('div', n); yearHeader.style.textAlign = 'center'; yearHeader.style.fontSize = '18pt'; yearHeader.style.clear = 'both'; ctn.appendChild(yearHeader); var single = makeTable(n, 'single', 'W'); single.style.display = 'inline-block'; ctn.appendChild(single); var married = makeTable(n, 'married', 'W'); married.style.float = 'right'; ctn.appendChild(married); var singleTax = makeTable(n, 'single', 'T'); singleTax.style.display = 'inline-block'; ctn.appendChild(singleTax); var marriedTax = makeTable(n, 'married', 'T'); marriedTax.style.float = 'right'; ctn.appendChild(marriedTax); } } function Graph(cnv, rect, label) { var ctx = cnv.getContext('2d'); var offsetX = 70, offsetY = 25; var border = { x: rect[0] + offsetX ,y: rect[1] + offsetY ,w: rect[2] - offsetX - 20 ,h: rect[3] - offsetY * 2 }; var axisX = {}, axisY = {}, sets = {}; this.addSet = function(name, color) { sets[name] = {}; sets[name].points = []; sets[name].color = color; } function checkLimits(axis, value) { if (axis.range == null) { axis.range = 0; axis.min = value; axis.max = value; } else if (value > axis.max) { axis.max = value; axis.range = axis.max - axis.min; } else if (value < axis.min) { axis.min = value; axis.range = axis.max - axis.min; } } this.addPoint = function(x, y, name) { sets[name].points.push([x, y]); checkLimits(axisX, x); checkLimits(axisY, y); } function scale(point) { var p = [] p[0] = (point[0]) * axisX.scale; p[1] = (point[1]) * axisY.scale; return p; } this.plot = function() { /* "NiceScale" comes from the math in the article "Nice Numbers for Graph Labels" by Paul Heckbert, retooled from the java implentation by Steffen Norgren on http://trollop.org/2011/03/15/algorithm-for-optimal-scaling-on-a-chart-axis/ The intention is to programmatically find non-distracting values to label a chart axis with based on the range of values on that axis. */ var nX = new NiceScale(axisX.min, axisX.max); var nY = new NiceScale(axisY.min, axisY.max); /* Use nice scale objects to create scale and offset transformations from data points to canvas coordinates. */ axisX.scale = border.w / nX.range; axisY.scale = -border.h / nY.range; axisX.offset = -nX.niceMin * axisX.scale; axisY.offset = -nY.niceMin * axisY.scale; // Save context state, reset to identity matrix minus half-pixel offset, // black stroke and fill pens, and draw graph border and label ctx.save(); ctx.setTransform(1, 0, 0, 1, -.5, -.5); ctx.strokeStyle = 'black'; ctx.fillStyle = 'black'; ctx.textAlign = 'center'; ctx.textBaseline = 'ideographic'; ctx.font = '16px sans-serif'; ctx.fillText(label, border.x + (border.w / 2), border.y - 4); ctx.strokeRect(border.x, border.y, border.w, border.h); // Draw color key ctx.textAlign = 'start'; ctx.textBaseline = 'middle'; ctx.font = '12px sans-serif'; var h = 5; for (var n in sets) { h += 15; ctx.strokeStyle = sets[n].color; ctx.strokeRect(border.x + border.w - 125, border.y + h, 20, 1); ctx.fillText(n, border.x + border.w - 100, border.y + h) } // Draw Y axis labels ctx.translate(border.x, border.y + border.h); ctx.textAlign = 'end'; for (var tick = 0; tick <= nY.range; tick += nY.tickSpacing) { var y = tick * axisY.scale; ctx.strokeRect(-4, y, 4, 1); var dollars = (tick + nY.niceMin).formatMoney(0); ctx.fillText(dollars, -8, y); } // Draw X axis labels ctx.textAlign = 'center'; ctx.textBaseline = 'hanging'; for (var tick = 0; tick <= nX.range; tick += nX.tickSpacing) { var x = tick * axisX.scale; ctx.strokeRect(x, 0, 1, 4); var year = (tick + nX.niceMin); ctx.fillText(year, x, 8); } ctx.translate(axisX.offset, axisY.offset); // Draw points on graph for (var n in sets) { ctx.strokeStyle = sets[n].color; var points = sets[n].points; ctx.beginPath(); var first = scale(points[0]); ctx.moveTo(first[0], first[1]); for (var n = 1; n < points.length; n++) { var point = scale(points[n]); ctx.lineTo(point[0], point[1]); } ctx.stroke(); } ctx.restore(); } } function makeGraphs() { var cnv = document.getElementById('graphCanvas'); var ctx = cnv.getContext('2d'); ctx.clearRect(0, 0, cnv.width, cnv.height); graphByYear(cnv); } function getTaxByBlock(taxBlock, salary) { var bracket; for (var b = 0; b < taxBlock.length; b++) { if (salary > taxBlock[b][0]) bracket = taxBlock[b]; else break; } return (bracket[2] + (salary - bracket[0]) * bracket[1] / 100); } function graphByYear(cnv) { var label = 'Taxes by year at ' + curSalMoney + ' salary, ' + maritalStatus; var g = new Graph(cnv, [0, 0, 640, 480], label); g.addSet('Withholding', 'black'); g.addSet('Taxes', 'blue'); for (var n = startYear; n <= endYear; n++) { var sd = taxes[n]['sd']; if (maritalStatus == 'married') sd *= 2; var w = getTaxByBlock(taxes[n][maritalStatus + 'W'], curSalary); var t = getTaxByBlock(taxes[n][maritalStatus + 'T'], curSalary - sd); g.addPoint(n, w, 'Withholding'); g.addPoint(n, t, 'Taxes'); } g.plot(); } function NiceScale(minPoint, maxPoint, maxTicks) { if (maxTicks == null) maxTicks = 10; function niceNum(range, round) { var exponent = Math.floor(Math.log(range) / Math.LN10); var mpe = Math.pow(10, exponent); var fraction = range / mpe; var niceFraction; if (round) { if (fraction < 1.5) niceFraction = 1; else if (fraction < 3) niceFraction = 2; else if (fraction < 7) niceFraction = 5; else niceFraction = 10; } else { if (fraction <= 1) niceFraction = 1; else if (fraction <= 2) niceFraction = 2; else if (fraction <= 5) niceFraction = 5; else niceFraction = 10; } return niceFraction * mpe; } var range = niceNum(maxPoint - minPoint, false); this.tickSpacing = niceNum(range / (maxTicks - 1), true); this.niceMin = Math.floor(minPoint / this.tickSpacing) * this.tickSpacing; this.niceMax = Math.ceil(maxPoint / this.tickSpacing) * this.tickSpacing; this.range = this.niceMax - this.niceMin; } init(); </script>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-21854251392555293012013-01-15T19:01:00.004-05:002013-01-15T19:01:43.044-05:00Â¿Hola?<br />So Scout and I stopped into the grocery store to buy a couple extra things for her lunch. We got to the checkout area, and decided to use a real lane instead of self-serve, since cashiers are much nicer to me when I have Scout in tow. (When I'm alone, it's self-serve only, unless the guy who looks like he just got out of prison is on duty at my local Kroger, he's alright.)<br /><br />"Do you have your Kroger card?"<br /><br />"No, but here, let me type in the number." I grabbed the giant rubber stylus, and poked at the "Alt ID" button, heard the reassuring click... and then nothing. So I poked at it a couple more times, and noticed that the button on the opposite side was occasionally gaining focus, and the language kept switching back and forth between Spanish and English.<br /><br />"Oh, looks like your touch-screen is misaligned..." she mashed some buttons on the register, "...or something, you should probably shut this lane down until that gets re..."<br /><br />"Try it now," she interrupted. Poke, poke, poke... Hello, Hola, Hello. Same thing.<br /><br /><a name='more'></a><br />"Yeah, I really think it's the touch screen, and..."<br /><br />"Here, give me all those groceries back," she called to the bagger, and then one at a time voided off each item, and then scanned them all again. Same thing. What followed was a series of continued fiddling with register buttons, voiding, scanning, Holas and Hellos, followed by calling the manager over.<br /><br />"If it's just about getting your Kroger points, we can..."<br /><br />"I don't care about that," I interrupted, uncharacteristically, "but I won't be able to pay if I can't enter my PIN or hit the credit button."<br /><br />"It was working earlier with my last customer," she said to the manager. The "you broke it" was silent. The manager used his thumb to poke the very right edge of the Alt ID button, and the next screen popped up. Looking amusedly smug, he chucklingly asked what my card number was.<br /><br />"804," I began. He typed, and 977 came up on the screen. "Yeah, you see the problem now, right?" He rang me up on another register successfully, and they cleared my order off the first register... BUT DID NOT SHUT IT DOWN!<br /><br />On the way to school, I talked to Scout about reputation capital, e.g., at my own work I would just have to say "I see the problem" and everyone would listen, where to Kroger I'm some random dude with a hairbrained theory who should be dismissed out of hand. We also talked about vendors who want to sell you new doo-dads all the time instead of just making a working system and supporting it.<br /><br />"They replace these so fast, they hardly ever break down, and when they do, no one has any idea how to troubleshoot. All so they can make more money, at the expense of their customers."<br /><br />The total time in the checkout line waiting out their lack of understanding was about 10 minutes, about how late to school we were. How long after I left they stubbornly refused to accept my assessment and annoyed other customers, I can't say.<br /><br />Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-19518903873099492642013-01-04T16:58:00.003-05:002013-01-04T16:58:45.456-05:00Manhandling Gentran for fun and profit<p>About 10am on Wednesday, an instant message window pops up on my workstation, from my buddy and coworker Jim. "Hey, you see all those Gentran errors?" </p><p>"Ah crap," I thought, "there goes my day." </p><p>I work as a software developer, but in a specialized role: my group writes software to support a production electronic commerce and energy trading system, fitting it with new regulatory requirements, feeds to new vendors, retrofitting logging and administrataion solutions, and our current nightmare, making our code compatible with new vendor frameworks, and migrating it en masse. (Vendor lock-in, in my opinion, is the biggest scam played out on American businesses, and 9 years of bellyaching about that in my job has netted me very few victories.) </p><p>In short, I write code, but for the purpose of keeping an enterprise integration system running. In my group, we rotate who is assigned for "production support", where you try to work on your coding tasks as you can, but when there are any hiccups in the system, you're suddenly in a system admin role instead, complete with (justifiably) panicked business users worrying about the status of their million dollar wire transfer, or avoiding regulatory fines for processing data late, etc. </p><a name='more'></a><p>Wednesday was my day, but it wasn't supposed to be. My aforementioned buddy, Jim, was up to the plate, except that his daughter was in the hospital, and naturally I kept support duties until someone else was available. Jim is like me, he wants to keep the system running, knowing the consequences to human lives if, say, the payroll file doesn't make it to the bank, or the system that routes linemen to downed power lines stops processing new outage notifications, or if we're slow in processing receipt of a late customer payment and the cutoff guy gets dispatched. </p><p>There are very human consequences if our system gets out of whack, and so we take our jobs seriously, often skirting controls and bending rules to keep the data moving, and trying our best to look contrite when auditors or bureacrats come around. Or at least I do. As far as I know, everyone else follows the rules 100%; that's my story, and I'm sticking to it. </p><p>Anyway, so there were all these Gentran errors, and Jim, logging in from the hospital, showed me an error that was going to eat up the entire day: </p><pre><br />::004,lftran , Compliance check error(s): <br />Seg Pos. Segment Element Data (1st 20 chars) Error<br />-------- ------- ------- -------------------- -----<br /> 0 GS 04 20130102 Invalid date<br /> 12 DTM 02 20130102 Invalid date<br /></pre><p class="section">A little background </p><p>One of the software packages we use to translate and deliver data is called Gentran:Server for Unix, written by my old employer Sterling Commerce. It's my understanding that the entire Gentran:Server suite has been out of support at Sterling for some time now, and they are pushing a new product line with "Integration" or "Broker" in the name somewhere. </p><p>We use Gentran mainly to translate ANSI X.12 (EDI) data to fixed-length records that our mainframe can process by referencing COBOL copybooks. The "GS" segment is the "group envelope" used in all X.12 transactions, and its configuration should never be toyed with by anyone but the vendor. Ever since version 4010 of the X.12 standard, all dates (except the one in the "ISA" or "interchange envelope") were changed from 6 digits to 8 digits, to make them Y2K compliant. 20130102 is, of course, January 2nd 2013, and is a completely valid date for the GS segment. </p><p>Something big was wrong, and it would affect all the documents we received until we fixed it. And since the software was out of support by the vendor, it was left to me, with a steadily increasing headache (our floor is being remodeled, and I was breathing in all the dust that would leave me wracked with sinus pain for most of the night... but that's another story), to modify things in the vendor code that we're never supposed to touch. </p><p>There was no putting it off, and nothing for it but to wade in. I applied my standard problem-solving algorithm: assume everything you know is wrong, and build the problem from the ground up. Since everything from December 31, 2012 was working, and now nothing was, I suspected that Gentran didn't like 2013 for some reason. So I started out of the gate by doing an internet search for "gentran 2013 invalid date", and the top link was this: </p><p><a href="http://www-01.ibm.com/support/docview.wss?uid=swg21557843">004,lftran , Compliance check error due to Invalid date</a></p><p>From the page: </p><p>Cause: When using the D12 format 2013, for example, is being read as month 13, which is bogus<br />Resolution: Set DTM segments to DC4 format (date with century). </p><p>So I opened Gentran's implementation of the 4010 standard, and, sure enough: </p><p><img src="https://lh6.googleusercontent.com/-SxqvGoXptVE/UOdOV7jvUJI/AAAAAAAADz0/6pR61XTrjBw/s800/standards1.PNG" /><br /><img src="https://lh6.googleusercontent.com/-rHmtUDRrfP0/UOdOV0RjGLI/AAAAAAAADzw/0aQxeqVqAdk/s800/standards2.PNG" /></p><p>...the GS date field was set to D12. So what does that mean? </p><p><img src="https://lh6.googleusercontent.com/-S7VCNYqDOWY/UOdOV3ujpxI/AAAAAAAADzs/bChyV4ah9Qw/s800/standards3.PNG" /></p><p>It means we're taking a field with a fixed length of 8 digits, and applying a 6 digit format string to it: </p><pre><br />20<b>13</b>0102<br />YY<b>MM</b>DD??<br /></pre><p>Month 13, indeed. Apparently, and this is what really irks me, this date pattern matching has always been completely wrong, and completely useless. We never did anything with the dates other than grab the string wholesale and stick it somewhere else. It never mattered that Gentran thought all of last year was December 2020... or 1920, but when the date was out of what it considered the correct range, document processing ceases. This has been wrong in the Gentran config for all 9 years I've worked here, and was never significant until Wednesday. </p><p>The DC4 format that the IBM article referenced matches the 20130102 string correctly: </p><p><img src="https://lh3.googleusercontent.com/-fBq3AqrmGiM/UOdOWac2aeI/AAAAAAAADz4/tSkcoo3yihY/s800/standards4.PNG" /></p><p>The same was true of 31 other fields in the invoice standard. I could use the GUI tool that these screenshots came from to edit each field as I found them, then recompile the map, but I didn't trust myself to find all of them, and I couldn't afford for this problem to go on any longer than it had to. So I decided to take a look at the map source code to see if there was some pattern matching I could do there. I found this: </p><pre><br />0<br />0<br />0<br />1<br />4<br />8<br />8<br />0<br />0<br />0<br />1<br />Data Interchange Date<br />M<br /><br />0<br />DT<br />S27<br />0029<br />D12<br /></pre><p>Now, there's no documentation (that I've seen, anyway) that says what the layout of a map file is, or what the consequences of poking around in it outside of the GUI tool will have, but I inferred a lot based on matching these lines with the GUI screens. The 4 must be the sequence number in the segment, the 0029 matches the reference number, the "Data Interchange Date" was the same as what was in the ELEMENT NAME field, and of course format D12 is right there in the text, waiting to be edited. </p><p>To be safe, I didn't want to globally replace all D12 instances with DC4, as there may be legitimate uses of YYMMDD, such as the ISA date. Instead, I wanted to only update D12 if it was in a field with a fixed length of 8, since that format/length pairing would always be a mismatch. I conjectured that the pair of 8s in the map file corresponded to the minimum and maximum field size. So I needed a script to match D12s with a pair of 8s 14 lines earlier. Enter perl. </p><pre><br />$ perl -pe 'push @ar,$_; shift @ar if $#ar>13;<br />> s/D12/DC4/ if $ar[0]==8 && $ar[1]==8' in.map > out.map<br /></pre><p>Yes, it's that simple. I've always loved perl for its expressiveness, especially for quick one-line commands like this, which it is especially well suited to. Here in roughly 100 characters, on the fly, and under time pressure, I updated everything in the map that needed it, and left legitimate D12 uses alone. To verify: </p><pre><br />$ diff in.map out.map<br />728c728<br />< D12<br />---<br />> DC4<br />1084c1084<br />< D12<br />---<br />> DC4<br />1928c1928<br />< D12<br />---<br />> DC4<br />...etc.<br /></pre><p>After that, I used the GUI to recompile the map, and ran some sample data through it to make sure it was functioning correctly on 2013 dates (and no new problems were introduced), and everything looked good. After getting the compiled map moved to our production server, new documents started processing successfully. </p><p>That left the ones that had already failed, and needing to match the raw error data with the correct Gentran partner profile. There are two basic gotchas when Gentran throws a document out because of a translation failure. First, you can't drop the file back in the main staging directory, because it will fail as a duplicate, since duplicate checking happens prior to translation. You have to name the document after the partner profile it is associated with, and drop it directly into the translator's trigger directory. Second, the errored file gets named "trans_failed" with a unique identifier after it, so you have to manually reconcile the raw data with the partner. </p><p>And since all inbound documents were affected until the map was fixed, there was a hefty set of files to sift through: 38 of them. I could open each by hand, and search for the partner by EDI code using Gentran's GUI, or, for the sake of speed, I could whip up something a little more programmatic. Once again, enter perl. </p><p>Gentran's partner database is a binary file separated, fortunately, by linefeeds after every row. Each column of a row is separated by null characters, but the contents are always in the same order, and are plain text. The first column is the partner code, the third is the EDI code. I was able to extract both in a simple list with this simple one-liner: </p><pre><br />perl -ne '@sp = /([\w\s]+)/g; print "$sp[0] $sp[2]\n" if /810R41/' tp.dat<br /></pre><p>That produced output similar to this: </p><pre><br />PARTNER1 EDICODE1<br />PARTNER2 EDICODE2<br />PARTNER3 ABCDEFGH<br /></pre><p>Matching each file to the right partner I took a little more care with, taking my partner list as inline input to the script with a virtual filehandle. Each file, fortunately, had the EDI code in the same fixed position, 35, padded with spaces out to a max length of 15. So I need to grab 15 characters at position 35, truncate the spaces, compare it to the partner list, and print partner codes matched to filenames. Here is the final product: </p><pre><br />#!/usr/bin/perl -w<br />use strict;<br /><br />my %partners = ();<br /><br />for (<DATA>) {<br /> my ($code, $id) = split;<br /> $partners{$id} = $code;<br />}<br /><br />for (@ARGV) {<br /> open FH, '<', $_;<br /> my $line = <FH>;<br /> close FH;<br /> my $sid = substr($line, 35, 15);<br /> $sid =~ s/\s+$//;<br /> print "$_ : $partners{$sid}\n";<br />}<br /><br />__DATA__<br />PARTNER1 EDICODE1<br />PARTNER2 EDICODE2<br />PARTNER3 ABCDEFGH<br /></pre><p>Still relatively compact, and easy to grok for even a novice perl coder. </p><p>So that was me manhandling Gentran with perl to make short work of something that would have taken a much longer time doing it with the available vendor tools, and that pretty accurately describes the niche I fill at my work: the Hail Mary, fixing a critical problem in a hurry, deciphering using my intuition where there is no documentation, taking somewhat ill advised shortcuts, and not being afraid to be bold... because when there's a lot at stake, second guessing yourself is suicide. </p><p>The end result? With Jim's help communicating with the business users, creating official "change requests" to promote the new map to production, and manually renaming and dropping errored files back to the translator and verifying the results, we're back in business. With any luck, he'll get all the credit (especially for doing all that while his kid was sick), and my commandline insanity and being knee deep in vendor code long past its end-of-life will go unnoticed. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-32167308640445772032012-12-25T07:33:00.000-05:002012-12-25T07:33:13.972-05:00Pufftygraph, an HTML5 Spirograph with touch-driven gears<p>First, thank you, now defunct Kenner, for the most excellent Spirograph toy. I had a Kenner 401 Spirograph set as a boy, and I fondly remember the joy of trying to drive the gears with a pen, and watching as the hidden shapes were revealed. </p><p>About a year back, I did some searches for online spirograph apps, thinking little Scout, then 6 years old, would enjoy it. I found some nice sites talking about how hypotrochoid math works, a complete listing of the gear ratios of one of the Kenner 401 set with close-up images of each gear, and several Flash, java, and HTML5 apps to draw hypotrochoids with given gear ratio inputs. However, nowhere did I find an app that showed the gears move as you drew, which as far as I was concerned was the main draw. </p><p>I decided that was a perfect excuse to brush up on my HTML5 animation skills, and within a few days I had code that would draw actual gears that followed mouse movements or touch gestures, drawing a hypotrochoid in its wake. I added the necessary elements to let it function as a smartphone web app, posted it <a href="http://etchapps.appspot.com/sg.html">here</a>, and showed it to my daughter Scout, who thought it was a fun enough idle amusement for a minute or two, but since it could only make the one shape, didn't have much replay value. </p><p>I shelved the code and moved onto other things, but late this year I returned to the app and spruced it up some, and presented it to Scout under the title Pufftygraph ("Puff" is one of her nicknames) as one of her official Christmas presents. She and her mom enjoyed playing with it while Christmas cookies were in the oven, and it seemed to be a big hit with both of them. Here's an example of a drawing it can produce now: </p><p><img src="https://lh3.googleusercontent.com/-JxL65sLWWK8/UM9yMcmytEI/AAAAAAAADoY/WhTS_citgTw/s800/spiroPreview.png" /></p><a name='more'></a><p>...and here's a video of me demonstrating it. (I intended to show a webcam video of this live on an actual iPhone, but my webcam is in dire need of being upgraded, so this is me using the app on a laptop with a screen capture program.) </p><iframe width="420" height="315" src="http://www.youtube.com/embed/iGNidoJupMk" frameborder="0" allowfullscreen></iframe><p>The web app is available to play with here: <a href="http://etchapps.appspot.com/pg.html">etchapps.appspot.com/pg.html</a>... and here's that splash-screen, in case it went by too fast: </p><p><img src="https://lh5.googleusercontent.com/-8ZUONYMxzTI/UM9yMQVgX6I/AAAAAAAADoY/hAmP8b2W9Hc/s800/pgSplash.png" /></p><p>That's no overstatement; Scout is the best big sister I could ever have hoped baby Adelaide could have, and she's definitely worth the effort it took to create the app. </p><p>To be clear, what I have written is a hypotrochoid generator that is unaffiliated with Hasbro, Kenner, Denys Fisher, or the trademarked Spirograph product line. My use of the word "Spirograph" is equal parts a throwback to my generation's habit of adopting popular brand names in place of generic terms (e.g., Coke and Kleenex instead of soda and tissue) and the fact that "hypotrochoid generator" sounds flat and mathy, where "spirograph" sounds fun and nostalgic. </p><p class="section">Mathy stuff </p><p>The first problem I had to solve was simply how to draw a gear. I decided to make the teeth isosceles trapezoids, where the base and height were the same, and the top line would be half the width of the base: </p><p><img src="https://lh6.googleusercontent.com/-Z-9eoAArNF8/UM9yNT4JaRI/AAAAAAAADoY/T5p55uyyM2E/s800/tooth.PNG" /></p><p>The teeth would always be a fixed size, regardless of the other gear properties, which allows the teeth of the inner gear (the smaller gear that turns) to visually "fit into" the outer ring (the larger gear that stays in a fixed place): </p><p><img src="https://lh4.googleusercontent.com/-7L4MA1kTbnY/UM9yLFV0xiI/AAAAAAAADoY/opjsH3cxUb4/s800/fit.PNG" /></p><p>So to draw a gear, take a circle, find its edge, draw a tooth on top of it, rotate a fraction, and repeat. The fraction changes with the number of teeth you want on the gear, and the circle's radius adjusts to make the gears fit comfortably side by side. For the inner gears that have holes on them, compare an official Spirograph gear with what my algorithm creates with the same number of teeth and holes: </p><p><img src="https://lh3.googleusercontent.com/-ITIBGdoj_B4/UM9yLMoLDJI/AAAAAAAADoY/Bw7ibZqaQOc/s800/84.PNG" /></p><p>Similar. The physical gear appears to be a spiral where the holes are more or less lined up with the "spokes" on the gear, which leads to some unevenness in appearance as you get close to the edge. I kept the holes a fixed distance away from each other, and evenly spread from the gear's center to edge. This was accomplished by creating increasingly large circles from the gear's center, and fixed-size circles at the previous hole's center. Where the two circles intersected was the location of the next hole. </p><p>In my app, everything is drawn on the canvas except for the actual gears, which are separate image elements rotated with a CSS rule for "webkit-transform", for example: </p><pre><br />gearImage.style['-webkit-transform'] = "rotate(1.7rad)";<br /></pre><p>My first build had the gears also drawn on the canvas directly, but the overhead of constant updates to the canvas' transformation matrix and redrawing the gear shapes after each movement was far less efficient than setting the image's position and rotation CSS elements and letting the browser handle the redraw on its own. Although I get a lot of joy out of figuring out how to do things by rote, in some cases micromanaging works against you. </p><p class="section">Moving the gears </p><p>There are two parts to consider when the user drags the gear, namely what position the screen touch means, and how much to rotate the gear to get it there. </p><p>I decided first that touches must be inside the inner gear (which is intuitive, that's the thing you're trying to move, after all) and all the action would be clockwise. After that, position is a function of finding the angle inside the outer ring that corresponded to where the touch happened. This is straightforward enough, just take the arctangent of the touch after setting the origin to the outer ring's center, and you have the radian value of the touch. </p><p>The next part is less intuitive, namely figuring out how much to rotate the gear. Some basic math is performed to figure out what outer ring tooth corresponds to the touch event, then how many teeth away from the current position that is. Once the new ring and gear tooth numbers are determined, rotation works like this: </p><pre><br />var rotation = ringPos * ring.step - gearPos * gear.step;<br /></pre><p>The "ring.step" and "gear.step" constants are the radian values of turning one tooth. If the gear has 63 teeth, gear.step is 1/63rd of a circle. In the CSS rotation rule, positive numbers are clockwise rotations, and negative numbers are counter-clockwise. So, as the gear's <b>position</b> moves clockwise around the circle, its <b>rotation</b> is counter-clockwise. </p><p>Consider this example, a 63-cog gear moving through all 63 teeth on a 96-cog outer ring: </p><p><img src="https://lh4.googleusercontent.com/-nzvB7PXjfh0/UM9yL129FBI/AAAAAAAADoY/Wwq6xN9EENM/s800/gearRotation.png" /></p><p>The gear has rotated counter-clockwise a full circle, and if it were moving across a straight line, it would be rotated back to where it started. But it's not rotating along a straight line, it's rotating around a circle. So while it has gone counter-clockwise by 63 of its own teeth, it has gone clockwise by 63 of the outer gear's 96 teeth, or about 2/3rds of a circle. </p><p class="section">Drawing the hypotrochoid </p><p>When the ring, gear, and pen hole are all set, I build each position in the hypotrochoid ahead of time, before any drawing happens, and slap it into an array. When the user draws with the gears, I look up the location of each position the gears move through, and just ctx.lineTo them. Here's the code for building the positions, followed by a simpler summary: </p><pre><br /> function buildPositions() {<br /> var offset = gear.radius - ring.radius;<br /> var hole = gear.holes[gear.activeHole];<br /><br /> for (var n = 0; n < ring.cogs; n++) {<br /> var rotation = (n % ring.cogs) * ring.step;<br /> var x = (-Math.sin(rotation) * offset);<br /> var y = (Math.cos(rotation) * offset);<br /> ringPositions.push([x, y]);<br /> }<br /> // Least common multiple, which I define earlier in the code<br /> var lcm = Math.lcm(ring.cogs, gear.cogs);<br /> for (var n = 0; n < lcm; n++) {<br /> var ringPos = n % ring.cogs;<br /> var gearPos = n % gear.cogs;<br /> var rotation = ringPos * ring.step - gearPos * gear.step;<br /> var sin = Math.sin(rotation);<br /> var cos = Math.cos(rotation)<br /> var x = (cos * hole[0] - sin * hole[1]) + ringPositions[ringPos][0] + centerX;<br /> var y = (sin * hole[0] + cos * hole[1]) + ringPositions[ringPos][1] + centerY;<br /> gearPositions.push([x, y, rotation]);<br /> }<br /> }<br /></pre><p>The first loop, iterating through ring.cogs, creates a circle that follows the inner gear's center as it moves around the outer ring. The second loop, iterating through all the possible gear positions until the first teeth of the gear and ring are touching again, follows the first circle, and then adds to it the x and y of the active pen hole <i>at the gear's current rotation</i>. This is similar to the official hypotrochiod formula shown on <a href="http://mathworld.wolfram.com/Hypotrochoid.html">Wolfram</a>, but I arrived at my method independently, just trying to follow the pen holes as they moved around. </p><p>The position building is all done before any drawing happens. It may seem a inefficient to pre-calculate all the positions and stick them in an array, but it's not as bad as you might think. The majority of gear/ring combinations comes in at under 1000 possible positions, and the largest was only 6,720, where the 105-tooth ring and 64-tooth gear are married, the largest co-prime pairing. </p><p>It may also seem ill-advised to calculate where a pen hole from a separate image element should track through on the canvas, but if the math works, it works. This example, as well as the 4 screenshots of the 63/96 gears above, shows that the math does indeed work. </p><p><img src="https://lh3.googleusercontent.com/-iuUoWmTCW9s/UM9yLMTvkWI/AAAAAAAADoY/1tRyuXo2Yq8/s800/follow.PNG" /></p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-84888658716179048922012-11-27T12:37:00.000-05:002012-11-28T12:21:51.595-05:00iPhone multi-touch events and braille-like input<p>Below is a quick demo of me "typing" a short phrase and some numbers using a modified version of my Blind Input iPhone web app. This is a proof of concept app that doesn't integrate into the phone's other text entry forms, and is arguably more difficult to learn than braille. In case it appears I've solved some great accessibility problem, Siri and the iPhone's native accessibility features has me beat in spades, and my app is only an interesting idea I wanted to experiment with. </p><p><iframe width="420" height="315" src="http://www.youtube.com/embed/psBK86UEgPA" frameborder="0" allowfullscreen></iframe></p><a name='more'></a><p>The current manager of my group at work, Dennis DeVendra, is blind. He was the manager of my group 2 re-orgs ago, from 2003 to 2005, and he is again after our most recent re-org in late 2010. (If you're in IT and you aren't working for a startup, you're familiar with re-orgs - they happen about as often as Superman's origin story gets re-written, and for roughly the same reason.) Anyway, we've had several conversations over the years about what being visually impaired is like. He introduced me to the world of blind advocacy, the story of blind skier Mike May (<a href="http://www.senderogroup.com/mm/mikebook.htm">Crashing Through</a>), and Dennis himself is an accomplished wood turner, as shown in the video below, making bowls, pens, perfume bottle stoppers, and the like as a hobby. (And you should buy his wares from <a href="http://www.zibbet.com/dpdevendra">his Zibbet store</a>, they make great Christmas presents for your discerning, artsy kin.) </p><p><iframe width="420" height="315" src="http://www.youtube.com/embed/oEChp7V7gUY" frameborder="0" allowfullscreen></iframe></p><p>At work, he uses a screen reader, JAWS, to interface with his computer, and having only touch to go on, is a more effective touch-typist than I am. It's likely he could keep pace with my 70ish WPM average, despite not being able to see the keyboard or the screen. Despite his intuition and skills, however, if an application is not built with accessibility in mind, it becomes an order of magnitude more difficult to use. Hold that thought for a minute. </p><p>When coding my HTML5 spirograph app (a work in progress, it's semi-functional for iPhones, and you can test it <a href="http://etchapps.appspot.com/sg.html">here</a>), I had to figure out exactly how touch events work. Without going into too much detail, touches trigger event listeners the same way mouse events do, but the object passed to the listener contains, instead of a single event, an array of all the active touches (in most cases), up to a maximum of 5. </p><p>Once I got my head around that, it was a small jump to the idea of using multiple touches instead of a pop-up keyboard for text entry. I toyed around with different ways to express the idea of <a href="http://en.wikipedia.org/wiki/Chorded_keyboard">chording</a> on an iPhone, trying to find a way that would be fun and easy to learn. I abandoned a couple early ideas as being too difficult to learn or code, and put the project back on the to-do pile for a while. </p><p>Sometime later, my group at work had a long team meeting. In hour 2 of the meeting without getting up to stretch or grab a coffee, my mind predictably started wandering, and I glanced over at Dennis while thinking about the chording idea. I quickly jotted down a note for myself: </p><p>"Blind input?" </p><p>I spent some of that night lying in bed and tapping on a book in various ways, experimenting with different ways to enter data, and drawing a few odd glances from my wife. The final method I settled on involves chording in four "quadrants" on the phone's screen- basically, tapping the screen's corners. The method has a few simple rules: </p><p><li /> The screen is divided into four equal parts by drawing axis lines down the horizontal and vertical middles of the screen. Touches are identified by what quadrant they fall into. <li /> Chords of up to three simultaneous touches define characters. <li /> A quandrant can contain no more than two touches - any more than that is uncomfortable to try to squeeze into one quarter of an iPhone screen. <li /> Each time a new finger touches the screen, all current touches define the active chord. <li /> When the number of touches reaches 0 (when the last finger lifts off the screen), the active chord's character is typed. </p><p>The last two rules basically mean that as long as no new finger touches the screen, once you have your "chord" defined, you can remove your fingers from the screen in any order, slowly or all at once, maybe accidentally dragging a finger across an axis as you lift it, and still get the correct character. </p><p>For two-touch chords, there are 10 possibilities using the combinatorics with repetition formula <i>(n+r-1)!/r!(n-1)!</i> where n is 4 (4 quadrants) and r is 2 (two touches). Observe: </p><pre><br />(n + r - 1)! = (4 + 2 - 1)! = 5! = 120<br />r! (n - 1)! = 2! (4 - 1)! = 2! 3! = 2 * 6 = 12<br />120 / 12 = 10<br /></pre><p>The 10 possibilities lends itself to representing the 10 digits, which I assigned like this: </p><pre><br />Quadrants <br />1 2<br />3 4<br /><br />Positions Character<br />11 1<br />12 2<br />13 3<br />14 4<br />22 5<br />23 6<br />24 7<br />33 8<br />34 9<br />44 0<br /></pre><p>In other words, tapping two fingers in the upper-left types the number 1. Tapping a finger in the upper-left and one in the upper-right types 2. Etc. </p><p>For three finger touches, the math gets more complicated since each quadrant can only repeat one time. It was a small effort to enumerate through all the possibilities, so I skipped the math. There were 16 possible three-touch chords. 16 plus 10 is 26, so this is ideal for a "mode" for alphabetic entry, and another for numbers and symbols. </p><p>Here is my current draft of how the chords map to characters (Tapping quadrant 1 alone puts you in shift mode, 2 alone puts you in number mode): </p><pre><br />Positions Normal Shift Number Shift + Number<br />1 Shift Normal Normal Normal<br />2 Number Number ShiftNum Number <br />3 Space Tab , <<br />4 Enter Enter . ><br />11 a A 1 !<br />12 b B 2 @<br />13 c C 3 #<br />14 d D 4 $<br />22 e E 5 %<br />23 f F 6 ^<br />24 g G 7 &<br />33 h H 8 *<br />34 i I 9 (<br />44 j J 0 )<br />112 k K - _<br />113 l L = +<br />114 m M [ {<br />122 n N ] }<br />123 o O \ |<br />124 p P ; :<br />133 q Q ' "<br />134 r R / ?<br />144 s S ` ~<br />223 t T<br />224 u U<br />233 v V<br />234 w W<br />244 x X<br />334 y Y<br />344 z Z<br /></pre><p>For example, if I want to type "The quick brown fox", as I did in the video example above, I would enter the following screen touches: </p><p><img src="https://lh4.googleusercontent.com/-Xs2l4ksy6CI/ULZHasdvWWI/AAAAAAAADmQ/TT80LXq5Bm8/s800/quickBrownFox.PNG"></p><p>What I have so far is still in its infancy, and my hope is that it can be fleshed out to be a full-featured text input system that interfaces well with the iPhone's <a href="http://www.apple.com/accessibility/iphone/vision.html">VoiceOver</a> features. The app can be played with here: <a href="http://etchapps.appspot.com/key.html">http://etchapps.appspot.com/key.html</a>, however as I said above, it isn't integrated into anything, and it would be pretty clumsy to cut and paste out of this web app into other apps. For now. Maybe this will turn out to be Just The Thing for blind input in a future build, but for now it's only an interesting experiment. </p><p>As a closing note, Dennis told me that the iPhone's native accessibility features combined with Siri make it the first electronic device that has been fully usable by him out of the box, with no third party add-ons. That's one hell of an endorsement. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-39018665744724946172012-11-12T13:54:00.000-05:002012-11-15T10:01:27.729-05:00The math behind HTML5 context transforms<p>If you've drawn widgets with HTML5 canvases, you've no doubt come across such methods as ctx.translate(x, y), ctx.scale(x, y), and ctx.rotate(radians). These are all convenience methods for ctx.transform(a, b, c, d, e, f), which applies a generic affine transform to the canvas. Consulting the specs will show that the a-f variables refer to this transformation matrix: </p><p><img src="https://lh4.googleusercontent.com/-ZdAr0H7JpKw/UKEzm7KXFOI/AAAAAAAADkc/1qaUsLXHuVE/s800/transform1.png" /></p><p>If this looks incomprehensible, it might help to express the matrix in basic Algebraic terms: </p><pre><br /> x' = ax + cy + e, and<br /> y' = bx + dy + f<br /></pre><a name='more'></a><p>In the above matrix and equations, x' and y' ("x prime" and "y prime" for the maths-averse) are the coordinates to draw to in the viewport (canvas), where plain x and y are coordinates in the source image. Iterate over all the pixels in the source image, run their coordinates through the affine transform, draw them at the (x', y') locations on the canvas, and you have a transformed image. Before we look at what each of the a-f variables does to the image, let's see how we got from the matrix to the Algebra, starting with some basic background. </p><p class="section">Dot Product </p><p>Dot Products are very easy: Take two lists of numbers, multiply the ones in the same list position, and add up the products. Here's an example, showing the dot product of (1, 2, 3) and (4, 5, 6): </p><pre><br />1 * 4 = 4<br />2 * 5 = 10<br />3 * 6 = +18<br /> --<br /> 32<br /></pre><p>fin. Really, that's it. </p><p class="section">Matrix multiplication </p><p>As Sal Khan will tell you, the rules for multiplying matrices are completely arbitrary, a cultural invention rather than a natural law. The two rules for multiplying matrices that have been settled on are also fairly simple: </p><p><li>The product of two matrices will be another matrix that has the number of rows that the left-side matrix has, and the number of columns that the right-side matrix has; order is important. <li>Each element of the product matrix is the dot product of the corresponding row in the left matrix, and the corresponding column in the right matrix. </p><p>The second rule takes a minute to get your head around: Each number in the product matrix is the dot product of an entire row from one matrix and column from another. If the number in question on the product matrix is in row 5, column 2, it is the dot product of the left matrix's row 5, and the right matrix's column 2. So, in the following matrix equation: </p><p><img src="https://lh4.googleusercontent.com/-ZdAr0H7JpKw/UKEzm7KXFOI/AAAAAAAADkc/1qaUsLXHuVE/s800/transform1.png" /></p><p>...the product matrix will be [x', y', 1]: </p><p><img src="https://lh6.googleusercontent.com/-OguC4nrY2uE/UKEzm_PV2uI/AAAAAAAADkk/cZZUx11HJ0o/s800/transform1a.PNG" /></p><p>...and the two matrices to be multiplied are: </p><p><img src="https://lh6.googleusercontent.com/-qkMSSKX7xzc/UKEznEQELwI/AAAAAAAADk0/0_GniftXmaA/s800/transform1b.PNG" /></p><p>The left matrix has three rows, and the right has 1 column, as does the product matrix. Calculating dot products from a row and a column works like this: </p><p><img src="https://lh6.googleusercontent.com/-VzdbvKxeejg/UKEzjnyKUpI/AAAAAAAADjY/H1LIFcQ3DI8/s800/dotProduct1.png" /></p><p>So, x' will be the dot product of the first row of the left matrix, [a, c, e], and the only column of the right matrix, [x, y, 1]: </p><p><img src="https://lh3.googleusercontent.com/-8NmwHEZpzsU/UKEzjipjwOI/AAAAAAAADjs/RRSXf4Or-74/s800/dotProduct2.png" /></p><p>The next variable, y', is on the second row of the product matrix, so to find its value we take the dot product of the left matrix's second row, [b, d, f], and again, the only column in the right matrix: </p><p><img src="https://lh5.googleusercontent.com/-vpXTlLy4OxE/UKEzjh4d_PI/AAAAAAAADjc/oeDE8ycOHjI/s800/dotProduct3.png" /></p><p>...and we see that the Algebra from above was correct, x' = ax + cy + e, and y' = bx + dy + f. Now, what do these variables all do? Let's do some sample transformations on a picture of my daughter, Adelaide, taking her first dip in the Atlantic at Nag's Head, North Carolina: </p><p><img src="https://lh5.googleusercontent.com/-nNy5XnYL3GQ/UKEzl-N8PDI/AAAAAAAADkI/-Qvnla-_F64/s800/nags_src.PNG" /></p><p>Variables a and d are for scaling. a sets x' to a multiple of x, and d sets y' to a multiple of y. The method ctx.scale(scaleX, scaleY) calls ctx.transform(scaleX, 0, 0, scaleY, 0, 0), giving: </p><pre><br />|scaleX 0 0| x' = scaleX * x + 0y + 0, x' = scaleX * x,<br />| 0 scaleY 0| -> y' = 0x + scaleY * y + 0 -> y' = scaleY * y<br />| 0 0 1|<br /></pre><p>Setting scaleX to 2, scaleY to .5 gives an image doubled in width, halved in height: </p><p><img src="https://lh3.googleusercontent.com/-6raKqkWNrGQ/UKEzk9_mj5I/AAAAAAAADj4/MqOFlUkZGz8/s640/nags_scale.png" /></p><p>Variables e and f are for offsets (or changing the origin location, however you like to think of it). The method ctx.translate(offsetX, offsetY) calls ctx.transform(1, 0, 0, 1, offsetX, offsetY), giving: </p><pre><br />|1 0 offsetX| x' = 1x + 0y + offsetX, x' = x + offsetX,<br />|0 1 offsetY| -> y' = 0x + 1y + offsetY -> y' = y + offsetY<br />|0 0 1|<br /></pre><p>Since canvases have an origin in the upper-left, and coordinates increase going right and down, setting offsetX to 200, offsetY to -200 moves the image to the right and up: </p><p><img src="https://lh5.googleusercontent.com/-djrS_VCF7aE/UKEzkpKB6VI/AAAAAAAADjw/KLS5iW0gsow/s800/nags_offset.png" /></p><p>Variables b and c set the shear (slant), because they work on the opposite axis of the variable they apply to. b is multiplied by x to affect y', and c by y to set x'. To keep things simple, we'll just set b to 1 and leave c alone. There is no method ctx.shear or ctx.slant, so we need to call ctx.transform directly. So, ctx.transform(1, <b>1</b>, 0, 1, 0, 0) (b is bolded) gives: </p><pre><br />|1 0 0| -> x' = 1x + 0y + 0 -> x' = x<br />|1 1 0| -> y' = 1x + 1y + 0 -> y' = x + y<br />|0 0 1|<br /></pre><p><img src="https://lh3.googleusercontent.com/-h4_9WQE_lK8/UKEzlzi6OlI/AAAAAAAADkU/B6wppPvUS3I/s800/nags_shear_a.png" /></p><p>In this case, each column of pixels is one more down than the one to its left. When x goes up by one, all the y's on that column also go up by one. This can be combined with a negative y offset to pull the picture up into the canvas more. ctx.transform(1, 1, 0, 1, 0, -200) gives: </p><pre><br />x' = x, y' = x + y - 200<br /></pre><p><img src="https://lh3.googleusercontent.com/-FZPmO_hzoHw/UKEzlGvd8ZI/AAAAAAAADj8/_1d8Xb3lyC0/s800/nags_shear.png" /></p><p>We've already accounted for the functions of all the variables; what, then, of rotation? It turns out to be a combination of scale and shear, with some sines and cosines thrown in. If r is the number of radians you wish to rotate a picture, method ctx.rotate(r) calls: </p><pre><br />ctx.transform(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)<br /></pre><p>This sets the Algebraic transform to: </p><pre><br />x' = cos(r)x - sin(r)y<br />y' = sin(r)x + cos(r)y<br /></pre><p>To see why this works, let's look at the sine and cosine values for no turn and a quarter turn: </p><pre><br />No turn, r = 0<br />cos(r) = 1, sin(r) = 0<br />x' = cos(r)x - sin(r)y -> x' = 1x - 0y -> x' = x<br />y' = sin(r)x + cos(r)y -> y' = 0x + 1y -> y' = y<br /></pre><p>So, no change. x' = x, y' = y. </p><pre><br />Quarter turn, r = pi/2 radians<br />cos(r) = 0, sin(r) = 1<br />x' = cos(r)x - sin(r)y -> x' = 0x - 1y -> x' = -y<br />y' = sin(r)x + cos(r)y -> y' = 1x + 0y -> y' = x<br /></pre><p>At a quarter turn, cos(r) is now 0 and sin(r) is 1, effectively swapping x and y. However, if we swapped x and y and left their signs unchanged, we would have an image rotated to the left and reversed. By inverting y's sign before assigning it to x prime, high y's are now low x's. Off the screen x's, in fact. Rotation using this method happens at the origin, and needs to be combined with a translate to scoot the image back into view. </p><p>In fact, this is a basic challenge to be aware of when dealing with rotation. Often you will want to rotate an object and draw it in a specific place in an unrotated larger canvas, or have an object that spins on its center. It will take some experimentation to get an intuitive grasp of how the math relates to the final canvas rendering, but in short there are two things to consider: where the center of the object is, and where it will be placed on the canvas. These total two translations. </p><p>But let's stop talking about pesky reality and get back to theory. What happens if we aren't rotating a quarter turn, but somewhere in the middle? Let's look at 45 degrees, or pi/4 radians. The following transform: </p><pre><br />var r = Math.PI / 4; // 45 degrees<br />ctx.setTransform(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 450/2, -450/4)<br /></pre><p>...produces this image: </p><p><img src="https://lh3.googleusercontent.com/-Fxw-oOL72-U/UKEzkx5ySlI/AAAAAAAADkA/haptpYFeyKw/s800/nags_rotate.png" /></p><p>The first thing that should jump out is the offsets: e is set to half the canvas width, where f is set to a quarter of its height (the canvas is square, simplifying things a little). Why were these values required to center the image? Well, it's not perfectly centered, but we'll get to that in a minute. The image is still rotating on its origin point. Consider these pairs of colored boxes with Xs in them; the blue stays stationary, while the red rotates on the origin until it hits 45 degrees: </p><p><img src="https://lh6.googleusercontent.com/-7dWPuBTjkII/UKEzmgoGGpI/AAAAAAAADkg/jZeZRgH6Ggk/s800/squares.png" /></p><p>In the final image, you can see that the center of the red x is on the left edge of the blue image, and three-quarters of the way down. To move it back to the center, it needs to move to the right by half the image width, and up by only a quarter... unless you want the picture perfectly centered, which I wasn't concerned about. It was a visual approximation, but moving up a fifth would have been close. The correct distance would have been √2/2 - .5, or about 0.20710678... (the difference between half the length of the diagonal and half the length of the square's side). </p><p>It's interesting to look at some of the numbers used in the rotation. Here's a quick hack to return the same values that ctx.rotate uses, and a few sample inputs: </p><pre><br />var r = Math.PI / 4; // 45 degrees<br />function rotate(x, y) {<br /> return [x * Math.cos(r) + y * -Math.sin(r), x * Math.sin(r) + y * Math.cos(r)];<br />}<br />rotate(0, 0)<br />[0, 0]<br />rotate(0, 1)<br />[-0.7071067811865475, 0.7071067811865476]<br />rotate(0, 2)<br />[-1.414213562373095, 1.4142135623730951]<br />rotate(0, 3)<br />[-2.1213203435596424, 2.121320343559643]<br />rotate(0, 4)<br />[-2.82842712474619, 2.8284271247461903]<br />rotate(101, 100)<br />[0.7071067811865674, 142.12846301849606]<br />rotate(102, 100)<br />[1.4142135623731065, 142.83556979968262]<br />rotate(103, 100)<br />[2.1213203435596597, 143.54267658086914]<br />rotate(104, 100)<br />[2.828427124746213, 144.2497833620557]<br /></pre><p>If you're moving along a horizontal or vertical line, the x and y values both increase (or decrease, depending on context) by the same number: .7071067811865475, or half of √2 (or about that, there is some floating point approximation going on). And since a 45 degree angle makes a 1,1, √2 right-triangle, this makes intuitive sense. </p><p class="section">Combining transforms </p><p>The method ctx.setTransform sets the current transform explicitly, overriding anything else already going on - you're back to no rotation, offset, shear, or scaling, and then multiply in the matrix of the setTransform call. The initial reset uses setTransform(1, 0, 0, 1, 0, 0), which uses this matrix: </p><pre><br />|1 0 0|<br />|0 1 0|<br />|0 0 1|<br /></pre><p>If you're familiar with matrices, you'll recognize this as the identity matrix (or the identity matrix as far as 3x3 matrices is concerned). If you multiply that by any other matrix, you'll get the new matrix back as the product. For example: </p><pre><br />|1 0 0| |1 2 3| |1 2 3|<br />|0 1 0| * |4 5 6| = |4 5 6|<br />|0 0 1| |7 8 9| |7 8 9|<br /></pre><p>Consider the product matrix's 8 value in the third row, second column. That is the dot product of the left matrix's third row, [0, 0, 1] and the right matrix's second column, [2, 5, 8]: </p><pre><br />0 * 2 = 0<br />0 * 5 = 0<br />1 * 8 = +8<br /> -<br /> 8<br /></pre><p>The 1s of the identity matrix are positioned strategically to only evaluate the significant digit from the other side of the dot product. It also works if the identity matrix is multiplied on the right side instead of the left, but I'll leave it to you to find out why, if you're so moved. </p><p>If you want to apply an additional transform to one that's already present, like adding a scale to an offset, the method ctx.transform(a, b, c, d, e, f) will multiply the matrix </p><pre><br />|a c e|<br />|b d f|<br />|0 0 1|<br /></pre><p>...against whatever transform is already active, and the result will be this: </p><p><img src="https://lh6.googleusercontent.com/-5Q1Rem_IUKY/UKEznVKzrRI/AAAAAAAADko/KzCeoKgDrYk/s800/transform2.png" /></p><p>Here's the same image with a couple items highlighted to show what rows and columns were used in the dot product: </p><p><img src="https://lh5.googleusercontent.com/-9UtEDXYD0qw/UKEznufhFcI/AAAAAAAADks/QCyVCnJxmt0/s800/transform3.PNG" /></p><p>With this example, it becomes evident what the bottom [0, 0, 1] row is useful for: it lets the translate variables, e and f, come into play without being multiplied against x or y. </p><p>Now, let's say we want to combine a ctx.scale(2, .5) with a ctx.translate(15, 20). The scale maps to ctx.transform(2, 0, 0, .5, 0, 0), and the translate maps to (1, 0, 0, 1, 15, 20). Multiplying the corresponding matrices gives: </p><p><img src="https://lh3.googleusercontent.com/-1tFsng7PINw/UKEzjy2qObI/AAAAAAAADjg/2VgjoptEbjE/s800/example1.png" /></p><p>Taking the product matrix and plugging it into the Algebra conversion gives: </p><p><img src="https://lh6.googleusercontent.com/-dKLv-M7g3yw/UKEzkAms_9I/AAAAAAAADjk/1NKPbwUmO-s/s800/example2.png" /></p><p>The end result is the scale stays the same, and the offset gets modified, doubling the horizontal, and halving the vertical, as you'd expect. </p><p class="section">Scaling: overcoming built-in interpolation </p><p>The last item I'll leave you with is this: the math described for scaling needs a little nudge to work as the user expects it to. What happens if we double x and y? By the affine transformation rules, each pixel of the source image is mapped to the viewport two pixels away from its adjacent neighbors, leaving a grid of empty spaces. Needless to say, this isn't the optimal mapping. </p><p>To make scaling more sensible, canvas engines compensate using various interpolation methods, varying with the engine, and some engines have a variety of image rendering modes that can be set with css rules, e.g. </p><p><style> canvas: { image-rendering:optimize-contrast; } </style> </p><p>If I take a very simple image of a 10-pixel diagonal line: </p><p><img src="https://lh5.googleusercontent.com/-qnZjAxzwiq8/UKEzkOQTLII/AAAAAAAADjo/UoTY12WI_Us/s800/line.PNG" /></p><p>...and scale it to (8, 8), my browser of choice, Chrome, renders this: </p><p><img src="https://lh5.googleusercontent.com/-gdqX6q982j8/UKEzl14wyzI/AAAAAAAADkM/R5xduTKNPko/s800/scale1.PNG" /></p><p>If I take that and magnify it (a true zoom): </p><p><img src="https://lh6.googleusercontent.com/-6r69wmNXiX0/UKEzmflncpI/AAAAAAAADkY/5fh5YOIK-lo/s800/scaleZoomed.PNG" /></p><p>...I can see the basic algorithm in use has indeed spaced the source pixels 8 apart, and then blended with the surrounding colors. The further a pixel is away from the true transformed pixel, the less red it is (and with some weird behavior at the image borders). For photographs that aren't scaled up too much, this will blend some of the pixelation that a true zoom (the "nearest neighbor" interpolation algorithm) would cause. </p><p>But what if you want the pixelation? If you wish to use canvases with other HTML5 features to make an image editing web app, you need a way to zoom the image and have it be a true representation, not one that gets blended as you scale up. You need to be able to scale up the line image from before and get this: </p><p><img src="https://lh3.googleusercontent.com/-PDEA1T3Dyc4/UKEznnFz5UI/AAAAAAAADkw/q1xCXLLbdk8/s800/zoom.PNG" /></p><p>Good ol' MS Paint to the rescue. </p><p>Unfortunately, at this early stage in the HTML5 game, not all browsers can be forced into this scaling mode. In fact, here is the best cross-browser CSS rule list I could find to even make the attempt: </p><pre><br /><style><br /> canvas: {<br /> image-rendering:optimizeSpeed; /* Legal fallback */<br /> image-rendering:-moz-crisp-edges; /* Firefox */<br /> image-rendering:-o-crisp-edges; /* Opera */<br /> image-rendering:-webkit-optimize-contrast; /* Chrome (and eventually Safari) */<br /> image-rendering:optimize-contrast; /* CSS3 Proposed */<br /> -ms-interpolation-mode:nearest-neighbor; /* IE8+ */<br /> }<br /></style><br /></pre><p>None of this explicitly declares what interpolation algorithm to use, only that the user is seeking crisp edges, speed, or good contrast. Oddly, IE8 is the sole exception, giving the user the ability to force nearest-neighbor interpolation. My version of Chrome, using WebKit 536.11, hasn't been upgraded in a while. It's possible that this has been fixed in Chrome for Windows by now, but in the version I'm using, nearest-neighbor mode doesn't work, at least not within canvases using ctx.drawImage. </p><p>To work around this, I coded up a quick hack at a nearest-neighbor algorithm, which leaves the context scale level at 1, and uses ctx.getImageData and putImageData to work with pixels directly. At the top of this is some stubbed out code to declare the identity matrix and to multiply transformation rules, which I plan to use later as my HTML5 image library grows. </p><pre><br />var identity = [1, 0, 0, 1, 0, 0];<br />var transform = identity;<br /><br />function multiplyTransforms(t1, t2) {<br /> var a = t1[0]; var A = t2[0];<br /> var b = t1[1]; var B = t2[1];<br /> var c = t1[2]; var C = t2[2];<br /> var d = t1[3]; var D = t2[3];<br /> var e = t1[4]; var E = t2[4];<br /> var f = t1[5]; var F = t2[5];<br /> return([a*A+c*B, b*A+d*B, a*C+c*D, b*C+d*D, a*E+c*F+e, b*E+d*F+f]);<br />}<br /><br />// vp = View Port<br />var vpZoom = 8; // Current zoom level<br />var vpOrigin = [0, 0]; // x,y of source image currently in upper-left of viewport<br /><br />function scaleImage() {<br /> // Source image width and height to grab is canvas size divided by zoom level,<br /> // unless image size is already smaller than that<br /> var w = Math.min(img.width, Math.ceil(cnv.width / vpZoom));<br /> var h = Math.min(img.height, Math.ceil(cnv.height / vpZoom));<br /><br /> // Set size of background canvas to match slice of source image to be grabbed<br /> bgCnv.width = w;<br /> bgCnv.height = h;<br /> // Draw image slice onto background canvas, vpOrigin is upper-left corner of slice<br /> bgCtx.drawImage(img, vpOrigin[0], vpOrigin[1], w, h, 0, 0, w, h);<br /><br /> // We'll be working with pixel bytes, so get bytes for image slice and create<br /> // a larger image data object to hold scaled-up image<br /> var src = bgCtx.getImageData(0, 0, w, h);<br /> var dst = bgCtx.createImageData(w * vpZoom, h * vpZoom);<br /><br /> var srcOffset = 0;<br /> var dstOffset = 0;<br /><br /> // lineArray holds the current line of the source image stretched out horizontally<br /> var lineLength = 4 * w * vpZoom;<br /> var lineArray = new Uint8ClampedArray(lineLength);<br /><br /> // For each line of the source image...<br /> for (var y = 0; y < h; y++) {<br /> var lineOffset = 0;<br /> // ...for each pixel on the current line...<br /> for (var x = 0; x < w; x++) {<br /> // Grab the four bytes representing the current pixel<br /> var byte = src.data.subarray(srcOffset, srcOffset + 4);<br /> // ...and append them vpZoom times to lineArray<br /> for (var n = 0; n < vpZoom; n++, lineOffset += 4) lineArray.set(byte, lineOffset);<br /> srcOffset += 4;<br /> }<br /> // Once lineArray is created, append it vpZoom times to the destination image object<br /> for (var n = 0; n < vpZoom; n++, dstOffset += lineLength ) {<br /> dst.data.set(lineArray, dstOffset);<br /> }<br /> }<br /><br /> ctx.putImageData(dst, 0, 0);<br />}<br /></pre><p>Using this method, scaling now becomes zooming, the desired operation. </p><p><img src="https://lh5.googleusercontent.com/-ALq9uh6S6eU/UKEzmbIiFjI/AAAAAAAADkQ/kZROTzX7UZc/s800/scale2.PNG" /></p><p>Of course, there are better ways to do this, and on a large image with a low zoom level this is much slower than native scaling. My hope is that CSS rules to do this natively improve and are incorporated into release versions of browsers. <p class="section"><i>EDIT</i></p><p>There was a much faster way to do nearest-neighbor scaling, which I adapted from <a href="https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipulation-with-typed-arrays/">this mozilla.org page</a>. Since color information doesn't change, you can read 32-bit bytes out of the pixel array buffer. On my computer, scaling at slowest setting (2x) took about a fifth as long as the method above... your mileage may vary.</p><pre><br /><br />function scaleImage2() {<br /> var w = Math.min(img.width, Math.ceil(cnv.width / vpZoom));<br /> var h = Math.min(img.height, Math.ceil(cnv.height / vpZoom));<br /> bgCnv.width = w;<br /> bgCnv.height = h;<br /> bgCtx.drawImage(img, vpOrigin[0], vpOrigin[1], w, h, 0, 0, w, h);<br /><br /> // We'll be working with pixel bytes, so get bytes for image slice and create<br /> // a larger image data object to hold scaled-up image<br /> var src = bgCtx.getImageData(0, 0, w, h);<br /> var dst = bgCtx.createImageData(w * vpZoom, h * vpZoom);<br /><br /> // Since no colors will be altered, create 32-bit views into pixel buffer to read an<br /> // entire pixel at a time<br /> var srcPx = new Uint32Array(src.data.buffer);<br /> var dstPx = new Uint32Array(dst.data.buffer);<br /><br /> var srcOffset = 0;<br /> var dstOffset = 0;<br /><br /> // lineBuf holds the current line of the source image stretched out horizontally<br /> var lineLength = w * vpZoom; // lineLength is pixels per line of scaled image<br /> var lineBuf = new ArrayBuffer(lineLength * 4); // Array buffer needs to by 4x the size for RGBA<br /> var linePx = new Uint32Array(lineBuf);<br /><br /> for (var y = 0; y < h; y++) {<br /> var lineOffset = 0;<br /> for (var x = 0; x < w; x++, srcOffset++) {<br /> // Append the current pixel vpZoom times to lineBuf's 32-bit view<br /> for (var n = 0; n < vpZoom; n++) linePx[lineOffset++] = srcPx[srcOffset];<br /> }<br /> // Once lineArray is created, append it vpZoom times to the destination image object<br /> for (var n = 0; n < vpZoom; n++, dstOffset += lineLength ) {<br /> dstPx.set(linePx, dstOffset);<br /> }<br /> }<br /><br /> ctx.putImageData(dst, 0, 0);<br />}<br /></pre>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-16762891025515187852012-11-04T22:46:00.000-05:002012-11-04T22:46:47.487-05:00Simple Javascript RGB/HSV converterThis is a simple and straightforward converter between RGB and HSV values, with RGBs from 0 - 255 and HSV values with hue from 0 - 359 and S/V from 0 - 1. I wrote it for an as yet unfinished HTML5 red-eye correction tool, but I'll be unable to devote much time to side-projects until after the holidays, so I thought I'd put this up now in case someone found it useful. <p>Enjoy! </p><pre><br /><br />function getHSV(r, g, b) {<br /> var h, s, v, max, delta;<br /><br /> // v (value) is the highest of the RGB values scaled to [0-1]<br /> max = Math.max(r, Math.max(g, b));<br /> if (max == 0) return [0, 0, 0];<br /> v = max / 255;<br /><br /> // delta is the difference between the largest and smallest of RGB<br /> delta = max - Math.min(r, Math.min(g, b));<br /> if (delta == 0) return [0, 0, v]; // No delta = grey, only v is significant<br /><br /> // s (saturation) is delta divided by max<br /> s = delta / max;<br /><br /> // h (hue) is a number in degrees describing the color's angle on the hsv color wheel<br /> if (r == max) h = (g - b) / delta;<br /> else if (g == max) h = 2 + (b - r) / delta;<br /> else if (b == max) h = 4 + (r - g) / delta;<br /><br /> // convert hue to degrees<br /> if (h < 0) h += 6;<br /> h *= 60;<br /><br /> return [h, s, v];<br />}<br /><br />function getRGB(h, s, v) {<br /> var quadrant, max, mid, min, fraction;<br /> max = Math.round(v * 255);<br /> if( s == 0 ) return ([max, max, max]); // Greys<br /><br /> // Quadrant is between 0 and 5, where 0, 2, and 4 are red, green, and blue<br /> h /= 60;<br /> quadrant = Math.floor(h);<br /><br /> // fraction is distance away from quadrant representing hue's primary color<br /> fraction = (quadrant % 2 == 0) ? (1 - h % 1) : h % 1;<br /><br /> // min and mid are the smaller two RGB colors in the final return array<br /> // We don't know what primary colors they represent...<br /> min = Math.round(max * ( 1 - s ));<br /> mid = Math.round(max * ( 1 - s * fraction ));<br /><br /> // ...until we check what quadrant we're in<br /> switch (quadrant) {<br /> // reds<br /> case 5: return [max, min, mid];<br /> case 0: return [max, mid, min];<br /> // greens<br /> case 1: return [mid, max, min];<br /> case 2: return [min, max, mid];<br /> // blues<br /> case 3: return [min, mid, max];<br /> case 4: return [mid, min, max];<br /> }<br />}<br /></pre>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-71195687110421899802012-10-11T11:49:00.001-04:002012-10-11T11:49:16.819-04:00Internet Explorer rendering issue fixedI noticed today that viewing this site in IE blew things up. This was due mainly to putting javascript source for HTML5 canvas operations above the fold in a couple of blog entries.<br /><br />I fixed that, so the page should look normal to IE users... however, you're missing out. Use an HTML5-capable browser instead to get the full gist of what I've been working on lately. And also the cool pacman banner.<br /><br />In other news, Adelaide turns one year old on Sunday! Here she is with her sister Scout, the person who has given Liberty and I the most help with the baby, and who is possibly the most loving and patient sister a baby could have. I love them both more than I can say... but I'll keep trying anyway.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-fY2yhmgvd8g/UHbqIaz2JtI/AAAAAAAADhU/Hifq29ROuqA/s1600/maze.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-fY2yhmgvd8g/UHbqIaz2JtI/AAAAAAAADhU/Hifq29ROuqA/s1600/maze.jpg" /></a></div><br /><br />Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-2465220843511512642012-07-29T17:01:00.001-04:002012-07-29T17:01:34.959-04:00Counting Squares<p>NPR's "Science Friday" posted a square-counting puzzle on their <a href="https://www.facebook.com/scifri">Facebook page</a> yesterday, and a friend of mine challenged me to write a program to count them, hopefully coming up with the same answer (40) that we did with a manual count. </p><p>My first pass is a generalized (more or less) solution where different sized boards can be created with arbitrary horizontal or vertical lines. It's not the most solid thing I've ever written, doesn't take user inputs or validate the hardcoded input, but it illustrates a simple, brute-force attack of the problem. </p><p>This requires an HTML5-capable browser (Chrome, Firefox, Safari), and should start counting 5 seconds after loading. </p><a name='more'></a><canvas id="cnv" width=500 height=500 /><style>canvas {border : 2px solid black} </style><script>var cnv, ctx, interval; var scale = 50; var size = 9; var current = 0; var lines = [ [[0,0], [8,0]], [[3,1], [5,1]], [[0,2], [8,2]], [[3,3], [5,3]], [[0,4], [8,4]], [[3,5], [5,5]], [[0,6], [8,6]], [[3,7], [5,7]], [[0,8], [8,8]], [[0,0], [0,8]], [[2,0], [2,8]], [[3,1], [3,3]], [[3,5], [3,7]], [[4,0], [4,8]], [[5,1], [5,3]], [[5,5], [5,7]], [[6,0], [6,8]], [[8,0], [8,8]] ]; var rows = []; var cols = []; var squares = []; function init() { interval = null; for (var n = 0; n <= size; n++) { rows[n] = []; cols[n] = []; } cnv = document.getElementById('cnv'); ctx = cnv.getContext('2d'); ctx.translate(10.5, 10.5); ctx.scale(scale, scale); ctx.lineWidth = 1 / scale; ctx.font = "2px sans-serif"; for (var n = 0; n < lines.length; n++) { var line = lines[n]; var p = line[0]; var q = line[1]; if (p[0] != q[0] && p[1] != q[1]) { alert ("Not a horizontal or vertical line"); return; } var vertical = (p[0] == q[0]); if (vertical) { for (var i = p[1]; i < q[1]; i++) cols[p[0]][i] = 1; } else { for (var i = p[0]; i < q[0]; i++) rows[i][p[1]] = 1; } } for (var x = 0; x < size - 1; x++) { for (var y = 0; y < size - 1; y++) check(x, y); } drawBoard(); setTimeout(begin, 5000); } function begin() { interval = setInterval(frame, 250); } function check(x, y) { var lim = x > y ? size - x: size - y; for (var n = 0; n < lim; n++) { var square = true; // if top or left bar stops, stop checking if (rows[x + n][y] == null || cols[x][y + n] == null) return; for (var f = 0; f <= n; f++) { // check bottom if (rows[x + f][y + n + 1] == null) { square = false; break; } // check right if (cols[x + n + 1][y + f] == null) { square = false; break; } } if (square) squares[squares.length] = [x, y, n + 1]; } } function drawBoard() { ctx.beginPath(); for (var n = 0; n < lines.length; n++) { var line = lines[n]; var p = line[0]; var q = line[1]; ctx.moveTo(p[0], p[1]); ctx.lineTo(q[0], q[1]); } ctx.stroke(); } function frame() { if (current == squares.length) { clearInterval(interval); return; } ctx.save(); ctx.clearRect(0, 0, 20, 20); drawBoard(); ctx.strokeStyle = "blue"; ctx.lineWidth = 2 / scale; var square = squares[current]; ctx.strokeRect(square[0], square[1], square[2], square[2]); current++; ctx.fillText(current, 4, 4); ctx.restore(); } init(); </script>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-84283175939174241562012-07-10T23:09:00.002-04:002012-10-11T11:34:58.672-04:00Dear Scout<p>So Scout's father moved to California, and this weekend I flew her out to see him, where she'll stay until just before school starts. Combine missing a girl I love with my recent dive into cryptography, and the natural outcome is, of course, to write her a letter - in secret code! </p><p>When I was in high school, I had the idea to write a substitution cipher using symbols for letters. My code consisted of squares, circles, triangles, stars, horizontal, vertical, and diagonal lines, plus and equals signs, and some other unique but easy to draw symbols that the intervening 25 years has wiped from my memory. I used it to write a letter of no particular consequence to my friend Rachel (actually she was my friend Bill's friend, who I poached for purposes of odd geeky stuff like inventing strategy games to play and reviewing her early short stories), and she decoded it with little effort, predictable given her intelligence. What made it pretty straightforward to decode was context. I had formatted the letter like a letter, complete with spaces between words, and the encoded greeting "Dear Rachel" which let her immediately translate the 7 letters a, c, d, e, h, l, and r. Since "a" was included, a lone "I" was easy to translate, and then the rest could be filled in with a style akin to Sudoku, filling in words that were mostly complete already, and adding the newly translated letters to other words. </p><p>I wanted to do something like that for Scout, but I thought of a symbol set that would be both easy to write a program for, and comprise 26 letters and 10 digits: pairs of dice. Each die face could have a value of 1 through 6, giving 36 possibilities for a pair. I didn't want the code to be as easy as iterating through the alphabet and replacing 1.1 with a, 1.2 with b, etc., but at the same time I didn't want it to be so complex as to be off-putting for a 7 year old. It eventually ocurred to me that "Dear Scout" was an even better greeting than Rachel's, as it had no overlapping letters. If I kept the cipher alphabetized, but pulled "Dear Scout" to the beginning of the cipher, then the greeting would look like this: </p><p><img src="https://lh4.googleusercontent.com/-4jpqlqLdfkg/T_ztICY_HkI/AAAAAAAADfI/OsxOK_XPKFc/s800/greeting.PNG"></p><a name='more'></a><p>(Yes, punctuation is shown normally, not translated into anything.) So not only is it clear from context what letters those dice are supposed to represent, but "Dear Scout" is the entire basis for the code's order. It is <b>her</b> secret code! </p><p>Now, it would have taken me perhaps on hour or two to write something up by hand on graph paper (in fact I have a book of graph paper about 3 feet from my computer that I used when figuring out the compass and straightedge geometry problems from a few articles ago), but I decided to code it up with an HTML5 canvas, my tool of choice these days for graphics work, so that I could type a message, hit a button, and get dice drawn as output. </p><p>Here is the final result, including a working "key" section at the bottom to build a translation table. I'll print this letter out and mail it to California in the morning. </p><p><img src="https://lh3.googleusercontent.com/-RKyCUgxwR1M/T_ztK1mjxFI/AAAAAAAADfQ/Zra4CdjGKeY/s800/letter_small.PNG" /></p><p>I originally planned to explain the code here, but I wrote it quickly in order to be able to get something finished for Scout as soon as possible, since she'll only be in California for 6 weeks. As a result of optimizing for speed of development, the code is sloppy, ignores good coding practices, and has little separation of model, controller, etc. Instead, here's the working code for you to examine at your leasure, or just to play with by creating dice messages in Scout-code. </p><p>Enjoy! </p><br /><br /><textarea cols=80 rows=10 id="ta">Dear Scout, Walnuts can be used to ward off forest sprites. True story! </textarea><input type=button value="Dicify" onclick=handleInput() /> <style> canvas { border: 1px solid black; margin: 0 auto; display: block; background:#FFF; } textarea { display: block; border: 1px solid black; padding: 0; } .close { text-align: right; margin: 10px; } </style><script type="text/javascript">var highestZ = 0; function promptBoxClosures() { var fullScreenBox = document.createElement('div'); var fs = fullScreenBox.style; fs.zIndex = highestZ; fs.position = "fixed"; fs.top = "0px"; fs.left = "0px"; fs.width = "100%"; fs.height = "100%"; fs.opacity = "0.7"; fs.filter = "alpha(opacity=70)"; fs.background = "black"; fs.border = "0px"; fs.display = "none"; fullScreenBox.innerHTML = '<div class="close"><a style="background:#E4E4E4;padding:0 5px;" href="#ta" onclick=closePromptBox()>Close</a></div>'; document.body.appendChild(fullScreenBox); var promptBox = document.createElement('div'); var ps = promptBox.style; ps.display = "none" ps.position = "fixed" ps.top = "10%" ps.left = "10%" ps.width = "80%" ps.height = "80%" ps.padding = "10px" ps.zIndex = highestZ + 1; document.body.appendChild(promptBox); openPromptBox = function() { fs.display = 'inline'; ps.display = 'inline'; } closePromptBox = function() { fs.display = 'none'; ps.display = 'none'; } setPrompt = function(html) { promptBox.innerHTML = html; } setPrompt('<canvas width=800 height=600 id="cnv"></canvas>'); } var cr = Math.PI*2; // a circle, in radians var dotOffset = 6; var dotRadius = 2; var dieSize = dotOffset * 4; var dieSpacing = dieSize + 5; var cnv, ctx; var posX, posY; var margin = 10; var dice = { 'd' : [1, 1], 'e' : [1, 2], 'a' : [1, 3], 'r' : [1, 4], 's' : [1, 5], 'c' : [1, 6], 'o' : [2, 1], 'u' : [2, 2], 't' : [2, 3], 'b' : [2, 4], 'f' : [2, 5], 'g' : [2, 6], 'h' : [3, 1], 'i' : [3, 2], 'j' : [3, 3], 'k' : [3, 4], 'l' : [3, 5], 'm' : [3, 6], 'n' : [4, 1], 'p' : [4, 2], 'q' : [4, 3], 'v' : [4, 4], 'w' : [4, 5], 'x' : [4, 6], 'y' : [5, 1], 'z' : [5, 2], '0' : [5, 3], '1' : [5, 4], '2' : [5, 5], '3' : [5, 6], '4' : [6, 1], '5' : [6, 2], '6' : [6, 3], '7' : [6, 4], '8' : [6, 5], '9' : [6, 6] }; var nums = { '1' : [4], '2' : [1, 7], '3' : [1, 4, 7], '4' : [1, 3, 5, 7], '5' : [1, 3, 4, 5, 7], '6' : [1, 2, 3, 5, 6, 7] }; var dots = { '1' : [0, 0], '2' : [0, 1], '3' : [0, 2], '4' : [1, 1], '5' : [2, 0], '6' : [2, 1], '7' : [2, 2], }; function init() { promptBoxClosures(); cnv = document.getElementById('cnv'); ctx = cnv.getContext('2d'); ctx.translate(.5, .5); ctx.font = "30px sans-serif"; posX = margin; posY = margin; } function drawLetter(letter) { if (letter == "\n") { posX = margin; posY += dieSpacing * 3; return; } if (posX > 750) { posX = margin; posY += dieSpacing * 3; } ctx.save(); ctx.translate(posX, posY); var d = dice[letter.toLowerCase()]; if (d == null) { ctx.fillText(letter, 0, dieSpacing + dieSize); } else { drawDots(d[0]); ctx.translate(0, dieSpacing); drawDots(d[1]); } ctx.restore(); posX += dieSpacing; } function drawDots(num) { ctx.strokeRect(0, 0, dieSize, dieSize); var numArray = nums[num]; for (var n in numArray) { var dotArray = dots[numArray[n]]; drawCircle(dotArray[0], dotArray[1]); } } function drawCircle(h, k) {// console.log(h + " : " + k); var x = (h + 1) * dotOffset; var y = (k + 1) * dotOffset; ctx.beginPath(); ctx.moveTo(x + dotRadius, y); ctx.arc(x, y, dotRadius, cr, false); ctx.fill(); } function handleInput() { openPromptBox(); ctx.clearRect(0, 0, cnv.width, cnv.height); posX = margin; posY = margin; var input = document.getElementById('ta').value; var inputArray = input.split(''); for (var i in inputArray) { drawLetter(inputArray[i]); } } init(); </script>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-23814654450966028212012-07-07T10:35:00.001-04:002012-07-08T23:00:10.854-04:00Encryption, part 2: Elgamal and the Discrete Logarithm<style> .pi { font-family: 'Times New Roman'; } </style> <p>In my quest to better understand the mechanics of encryption, I've found the Elgamal method the hardest to get my head around. The problem was not that the underlying math is difficult, it was that I didn't know some of the background terminology (cyclic group, congruence class, group isomorphism), the various explanations online all used different variables, and made references to "mathy" things it was supposed the reader was already familiar with. Lastly, the articles on discrete logs and Elgamal encryption on my go-to site for techie info, Wikipedia, were a hot mess, mistaking modular multiplicative inverse for division, and leaving out crucial middle steps (how to get from "collect underpants" to "profit", as it were). The whole affair really shook my faith in using Wikipedia as a trusted source for tech info. </p><p>A relatively simple explanation is this: You and your buddy both have a secret that combines to form an encryption key. To encrypt a message, multiply the key by the message value, divide by a modulus, and the remainder is your ciphertext. To decrypt, multiply the ciphertext by the key's inverse, and, again, divide by the modulus, and the remainder is the original message. Fin. </p><p>There is, naturally, some math involved, most of it geared around how to build the secret key so only you and your buddy know it, without either of you revealing your part of the secret. Let's start with some of the basic terminology, then get into the mechanics of building the encryption key. <a name='more'></a></p><p class="section">Background </p><p><b>Modular arithmetic, and modular multiplicative inverse</b> - see my <a href="http://cautery.blogspot.com/2012/06/rsa-encryption-and-other-sun-tzu.html">previous entry on RSA</a>. Seriously, go read it if you don't know what (mod p) or a three-lined equals sign (≡) means. </p><p><b>Discrete Logarithm</b></p><p>The answer to the question "What power do I raise 10 to in order to get 1000?" is the logarithm of 1000, base 10. The answer, of course, is 3, because 10 cubed is 1000. Standard notation looks like this: </p><pre><br />log<sub>10</sub>(1000) = 3<br /></pre><p>But that's just a logarithm, not a discrete logarithm. A discrete log happens when you throw a modulus into the mix. </p><p>The answer to the question "What power do I raise 3 to in order to get 2 (mod 17)" is asking for a discrete log. The answer is 14, because 3<sup>14</sup> = 4,782,969 ≡ 2 (mod 17). The notation for that is a little different than the above log(1000) equation: </p><pre><br />14 ≡ ind<sub>3</sub> 2 (mod 17)<br /></pre><p>The "ind" refers to the "index" in the base 3/mod 17 table where we'll find 2. "Index" and discrete log mean basically the same thing in this context. Check the chart below to see that it is, indeed, index 14 where we find 2. </p><p><b>Multiplicative Order</b></p><p>If p is a prime number, and n is a number less than p, then the smallest positive exponent to raise n to and get ≡ 1 (mod p) is n's multiplicative order, mod p. </p><p>If n's multiplicative order mod p is (p-1), then you can generate all the numbers from 1 to p-1 by raising n to increasing powers mod p. For example, here is a table with p as 17, and n as 3: </p><pre><br /> k 3<sup>k</sup> 3<sup>k</sup> (mod 17)<br /> 1 3 3<br /> 2 9 9<br /> 3 27 10<br /> 4 81 13<br /> 5 243 5<br /> 6 729 15<br /> 7 2,187 11<br /> 8 6,561 16<br /> 9 19,683 14<br />10 59,049 8<br />11 177,147 7<br />12 531,441 4<br />13 1,594,323 12<br />14 4,782,969 2<br />15 14,348,907 6<br />16 43,046,721 1<br /></pre><p>In the right-hand column, all the numbers from 1 to 16 (p-1) have been generated, making 3 a "primitive element" of 17. If we change p to 11, though, this happens: </p><pre><br /> k 3<sup>k</sup> 3<sup>k</sup> (mod 11)<br /> 1 3 3<br /> 2 9 9<br /> 3 27 5<br /> 4 81 4<br /> 5 243 1<br /> 6 729 3<br /> 7 2,187 9<br /> 8 6,561 5<br /> 9 19,683 4<br />10 59,049 1<br /></pre><p>This doesn't generate all the numbers from 1 to 10, due to 3's multiplicative order mod 11 being 5, and not 10. You don't have to generate the whole table to find this out, though, you can instead take the prime roots of p-1 (in this case, p-1 is 10, whose prime roots are 5 and 2), and raise n to those powers. If any of that comes out to ≡ 1 (mod p), then the multiplicative order isn't p-1. </p><p>Why is any of that important? Because we're trying to generate a substitution pad without duplicates. If there are duplicates, the "shared key" calculations become ambiguous, breaking the system. </p><p class="section">The generator and the group </p><p>The book "Handbook of Applied Cryptography" by Menezes, et al., has a lot to say about the difficulty of finding generators, parts of which make sense, other parts are arcane and hard to follow without a lot of background in the field. For example, this is a snippet from chapter 4.6: </p><blockquote>Recall (Definition 2.169) that if G is a (multiplicative) finite group, the <i>order</i> of an element <span class="pi">α</span> ∈ G is the least positive integer t such that a<sup>t</sup> = 1. If there are <i>n</i> elements in G, and if <span class="pi">α</span> ∈ G is an element of order <i>n</i>, then G is said to be <i>cyclic</i> and <span class="pi">α</span> is called a <i>generator</i> or a <i>primitive element</i> of G (Definition 2.167). Of special interest for cryptographic applications are the multiplicative group ℤ<sup>*</sup><sub>p</sub> of the integers modulo <span class="pi">α</span> prime <i>p</i>, and the multiplicative group <b>F</b><sup>*</sup><sub>2m</sub> of the finite field <b>F</b><sub>2m</sub> of characteristic two; these groups are cyclic (Fact 2.213). Also of interest is the group ℤ<sup>*</sup><sub>n</sub> (Definition 2.124), where <i>n</i> is the product of two distinct odd primes. This section deals with the problem of finding generators and other elements of high order ℤ<sup>*</sup><sub>p</sub>, <b>F</b><sup>*</sup><sub>2m</sub>, and ℤ<sup>*</sup><sub>n</sub>. See §2.5.1 for background in group theory and §2.6 for background in finite fields.</blockquote><p>Those bold F's are mathematical double-strike F's in the source text, but they're a high-unicode value (x1d53d) that doesn't render in all browers. Also, wow, I think this is why most people hate math and technical docs. If nothing else, this shows that before you can intelligently follow the discussion, there is a wealth of background information on group theory and finite fields that needs to be learned first. The takeaways from this without doing any background reading are: </p><ul><li />"Generator" and "primitive element" are the same thing. <li /> ...and they are hard to find for a large modulus. </ul><p>Generators are the bases that have multiplicative order p-1, which we just discussed, that can be raised to incrementing powers (mod p) to get all the numbers between 1 and p. In this case, our generator, <span class="pi">α</span>, is 3, our "p" is 17, and that produces a group G that is from the table above: </p><pre><br />α = 3, p = 17<br />G(k) = 3<sup>k</sup> (mod 17) ...so:<br />G = (3, 9, 10, 13, 5, 15, 11, 16, 14, 8, 7, 4, 12, 2, 6, 1)<br /></pre><p>The group doesn't actually get generated and stored anywhere (which would be impossible with a large enough modulus); it's enough to prove that there are no duplicate values in it. </p><p class="section">The shared key </p><p>The shared key is where the real mojo happens, and the encryption and decryption are trivial compared to generating the key. It is a value both sides can calculate with information the other gives them, based on the following identity: </p><pre><br />α<sup>ab</sup> mod p = (α<sup>a</sup>)<sup>b</sup> mod p = (α<sup>b</sup>)<sup>a</sup> mod p<br /></pre><p>The "a" and "b" variables refer to secret values that the message sender and recipient (Alice and Bob, in geek parlance) pick. A secret value, <span class="pi">α</span><sup>ab</sup> (let's just call it "K", for "key" ... also, I'm going to omit the "mod p" after every term for brevity's sake), can be generated if I know "a" and <span class="pi">α</span><sup>b</sup>, or if I know "b" and <span class="pi">α</span><sup>a</sup>. </p><p>Through the difficult nature of discrete logarithms, you can't determine "a" from <span class="pi">α</span><sup>a</sup>, nor can you find "b" from <span class="pi">α</span><sup>b</sup>. And here's the kicker: you can't determine K if you know both <span class="pi">α</span><sup>a</sup> and <span class="pi">α</span><sup>b</sup>, you have to also know one of the secret values. This means that the <span class="pi">α</span><sup>a</sup> and <span class="pi">α</span><sup>b</sup> values can be transmitted in the clear. </p><p>Additionally, each block of a message Alice sends can have a different secret key. In Elgamal encryption, each cipher block has two values, <span class="pi">α</span><sup>a</sup> mod p, and the message ciphertext. In each block Bob receives, he'll be calculating what K for that block is, and using it to decrypt the ciphertext. </p><p class="section">The encryption </p><p>Alice wants to send a message "m" to Bob, so she asks for his public key. Bob sends Alice the key, which contains the generator (<span class="pi">α</span>), the modulus (p), and his public part of the shared key, <span class="pi">α</span><sup>b</sup> mod p. </p><p>Alice then picks her own secret, "a", and calculates the shared secret K with (<span class="pi">α</span><sup>b</sup>)<sup>a</sup> mod p. Once K is calculated, Alice multiplies it by "m", the message. Then she sends Bob her public part of the shared key, <span class="pi">α</span><sup>a</sup> mod p (denoted as "c1" in most of the online descriptions), so he can also figure out what K is, and the ciphertext Km mod p ("c2"). </p><p>Bob, then, takes c1, and raises it to b mod p, to calculate K. This will be the same K Alice multiplied m by. Bob determines the modular multiplicative inverse of K (see the RSA entry for info on what an MMI is), and multiplies that by c2 mod p, revealing the original message, m. </p><p class="section">Working example </p><p>As we've shown above, the modulus 17 has a primitive element 3. We'll call those "p" and "<span class="pi">α</span>". Bob picks 11 as his secret. We'll call his secret x<sub>b</sub>, and the public (<span class="pi">α</span><sup>x<sub>b</sub></sup> mod p) transformation of it y<sub>b</sub></p><pre><br />p: 17<br />α: 3<br />x<sub>b</sub>: 11<br />y<sub>b</sub> = 3<sup>11</sup> mod 17 ≡ 177,147 mod 17 = 7<br /></pre><p>Bob's public key is therefore: (17, 3, 7) </p><p>Once Alice receives the public key, she picks 6 as her private key. We'll call her private key x<sub>a</sub>, and the public transformation of it y<sub>a</sub>: </p><pre><br />x<sub>a</sub> = 6<br />y<sub>a</sub> = 3<sup>6</sup> mod 17 ≡ 729 mod 17 = 15<br /></pre><p>Alice then generates the shared secret, K, by raising y<sub>b</sub> (Bob's public part of the shared key) to 6, and taking mod 17: </p><pre><br />K = 11<sup>6</sup> mod 17 ≡ 117,649 mod 17 = 9<br /></pre><p>The message ("m") she wants to transmit is 11. Generating the ciphertext is as easy as multiplying m by K mod p: </p><pre><br />Km mod p = 9 * 11 mod 17 ≡ 99 mod 17 = 14<br /></pre><p>The transmitted text is a combination of y<sub>a</sub> from above (or "c1"), and the ciphertext just calculated ("c2"): (15, 14) </p><p>Bob now needs to decrypt the message. First he calculates the shared key using the information he has, y<sub>a</sub>, and x<sub>b</sub> (11 and 15): </p><pre><br />K = 15<sup>11</sup> mod 17 ≡ 8,649,755,859,375 mod 17 = 9<br /></pre><p>This is the same K that Alice calculated with the other elements, y<sub>b</sub> and x<sub>a</sub>. Now that Bob knows K, he needs to find its inverse mod 17: </p><pre><br />K<sup>-1</sup> = 2 ( because 9 * 2 = 18 ≡ 1 mod 17)<br /></pre><p>With K<sup>-1</sup>, m is easily revealed: </p><pre><br />m = c2 * K<sup>-1</sup> mod p ≡ 14 * 2 mod 17 ≡ 28 mod 17 = 11<br /></pre><p>And that's all there is to it. In the real world, of course, the modulus would be much larger, and a lot of effort would be expended up front looking for a primitive element. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-71480191151667417812012-06-28T20:18:00.001-04:002012-06-29T00:51:15.410-04:00RSA encryption and the other Sun Tzu<p class="section">Background </p><p>Before we get started on how exactly RSA works and how it's related to Chinese mathematicians, it's important to have a basic comprehension of modular arithmetic, totients, and the "modular multiplicative inverse". None of these are very difficult, but they aren't commonly talked about, so here's a quick refresher: </p><p><b>Modulus</b></p><p>In simplest terms, what you're looking for in a modular problem is the remainder of a division. Dividing 7 by 3 gives 2 with a remainder of 1. Expressed as a modular equation, 3 is the modulus, and 7 and 1 are congruent. The equation can be expressed like this: </p><pre><br />7 ≡ 1 (mod 3)<br /></pre><a name='more'></a><p>The equals sign with three lines denotes congruence, not equality. Other values are congruent to 7 and 1 modulo 3. 4 is, so is 10. These equations all say the same thing: </p><pre><br />10 ≡ 1 (mod 3)<br /> 7 ≡ 4 (mod 3)<br /></pre><p>In most cases where a modular function comes into play, you're looking for the lowest positive value that satisfies the congruence, which is the same as dividing by the modulus and taking the remainder. This is the answer that scientific calculators and most programming languages give you. </p><p>Lastly, this identity will came up later in the encryption "punchline" below: </p><pre><br />If a ≡ b (mod n) then a<sup>x</sup> ≡ b<sup>x</sup> (mod n)<br /></pre><p><b>Totient</b></p><p>The totient of a number is the count of integers less than it that do not have any common factors with it. The number 8, for example, has a totient of 4, referring to the odd integers 1, 3, 5, and 7. The even numbers less than 8 all share a common factor (2) with 8, so they are not part of the totient. </p><p>The totient of a prime number is the number minus 1. Since primes have no factors, all the integers less than a prime number will not have any factors in common with it, and hence will be included in the totient. The totient of 7, then, is 6. </p><p>Totients are sometimes referred to as the "phi function" of a number, and expressed as φ(n). The above examples can be expressed this way: </p><pre><br />φ(8) = 4<br />φ(7) = 6<br /></pre><p>The totient function is multiplicative if its arguments are coprime. </p><pre><br />φ(ab) = φ(a)φ(b)<br /></pre><p>For example, 15 is a "semiprime", or the product of two prime numbers, 3 and 5 in this case. Since the totient of a prime p is p - 1, the totients of 3 and 5 will be 2 and 4, giving this equation: </p><pre><br />φ(15) = φ(3)φ(5) = 2 * 4 = 8<br /></pre><p>So there should be 8 integers less than 15 with no factors in common with it. To see if this is true, let's list the numbers from 1 to 14, and cross out multiples of 3 and 5, and count how many are left: </p><pre><br />1<br />2<br /><del>3</del><br />4<br /><del>5</del><br /><del>6</del><br />7<br />8<br /><del>9</del><br /><del>10</del><br />11<br /><del>12</del><br />13<br />14<br /></pre><p>The totient set is (1, 2, 4, 7, 8, 11, 13, 14) for a total of 8 values. </p><p><b>Modular Multiplicative Inverse</b></p><p>The MMI of "a (mod n)" ...is the number I can multiply by a to get 1 (mod n). For example, the MMI of 4 (mod 7) is 2, since: </p><pre><br />4 * 2 = 8 ≡ 1 (mod 7)<br /></pre><p>The common notation for this, which I don't particularly care for, is: </p><pre><br />4<sup>-1</sup> ≡ 2 (mod 7)<br /></pre><p>With the math refresher course out of the way, let's talk about who the "other" Sun Tzu is, and why he's important, and then we can get into the meat of how RSA works. </p><p class="section">The Chinese Remainder Theorem </p><p>In the 5th century CE, the mathemetician Sun Zi (sometimes Romanized as Sun Tzu, although the mathematician was born some 900 years after the famous military strategist) published the work "Sun Zi Suanjing", or "Sun Zi's Calculation Classic". There are several "Suanjing" books collected as part of a canon of Chinese maths texts, and they show that China was a profoundly effective player on the world's mathematical stage. For example, Zhou Bi's geometrical proof of hypotenuse lengths predates Pythagoras (probably, the possible date range is pretty wide), and Sun Zi himself introduced a rudimentary base-10 number system that predates the Hinduâ€“Arabic numeral system by hundreds of years. </p><p>The item in Sun Zi's Suanjing that peeks everyone's interest, however, is known as the "Chinese Remainder Theorem". In problem 26 of Chapter 3, we find the following text: </p><blockquote>Now there are an unknown number of things. If we count by threes, there is a remainder 2; if we count by fives, there is a remainder 3; if we count by sevens, there is a remainder 2. Find the number of things. <p>Answer: 23. </p><p>Method: If we count by threes and there is a remainder 2, put down 140. If we count by fives and there is a remainder 3, put down 63. If we count by sevens and there is a remainder 2, put down 30. Add them to obtain 233 and subtract 210 to get the answer. If we count by threes and there is a remainder 1, put down 70. If we count by fives and there is a remainder 1, put down 21. If we count by sevens and there is a remainder 1, put down 15. When [a number] exceeds 106, the result is obtained by subtracting 105. </p></blockquote>Unfortunately, a direct English translation of Chinese ideograms is always problematic (which is why there are so many translations of the Tao Te Ching), so what is being said, and why it works, is a little difficult to see, and was most likely introduced to students with an explanatory lecture. Let's start with a rewrite of the question itself: <p>What is the smallest number that if divided by 3 has a remainder of 2, by 5 a remainder of 3, and by 7 a remainder of 2? ...and more importantly, what is a general way to calculate such a thing? </p><p>The general method found was, for each divisor, find a number that is a multiple of the product of the other two divisors, but has a remainder of 1. Multiply each of those numbers by the remainder you're looking for, add everything up, and keep subtracting the product of all three divisors. Strange, but watch how it works. Here's the meat of the "If we count by threes and there is a remainder 1, put down 70" section: </p><pre><br /> 70 / 3 = 23 r1: 70 is a multiple of 5 * 7, the other two divisors (35 itself doesn't work since 35 / 3 has a remainder of 2, so that can't be used)<br /> 21 / 5 = 4 r1: 21 is 3 * 7, the other two divisors<br /> 15 / 7 = 2 r1: 15 is 3 * 5... again, the other two divisors<br /></pre><p>Our "goal" remainder when dividing by 3 is 2, so take our 70 line, and multiply by 2: </p><pre><br />140 / 3 = 46 r2<br /></pre><p>For 5 and 7, we want a remainder of 3 and 2, respectively, so multiply those equations by 3 and 2: </p><pre><br /> 63 / 5 = 12 r3<br /> 30 / 7 = 4 r2<br /></pre><p>...Now add up 140, 63, and 30 to get 233. Then take 233 and keep subtracting 105 (the product of all the divisors: 3 * 5 * 7 = 105). 233 - 105 = 128. 128 - 105 = 23. </p><p>And just to check </p><pre><br />23 / 3 = 7 r2<br />23 / 5 = 4 r3<br />23 / 7 = 3 r2<br /></pre><p>This is all pretty arcane, (and until the explanation is laid bare, pretty nonsensical), but it is the world's first written approach to modular arithmetic, 1200 years before Euler. This is now commonly presented as a simple theorem that belies its usefulness: </p><p>There exists a number that when divided by given divisors leaves given remainders. If n is the product of all the divisors, then there is exactly one number between 0 and n - 1 that produces all the required remainders. </p><p>Boring, and easily glossed over if you don't immediately see its power. Gauss, however, improved the notation of the algorithm this way: </p><pre><br />If...<br />x = x<sub>1</sub> (mod n<sub>1</sub>)<br />x = x<sub>2</sub> (mod n<sub>2</sub>)<br />...<br />x = x<sub>k</sub> (mod n<sub>k</sub>)<br /><br />then...<br />n = n<sub>1</sub> * n<sub>2</sub> ... * n<sub>k</sub><br />r<sub>k</sub> = n / n<sub>k</sub> (i.e., the products of the other divisors)<br />s<sub>k</sub> = r<sub>k</sub><sup>-1</sup> (mod n<sub>k</sub>) (Sun Zi did this part first - this is the value that gets the initial remainder of 1)<br /><br />and...<br />x = (x<sub>1</sub>r<sub>1</sub>s<sub>1</sub> + x<sub>2</sub>r<sub>2</sub>s<sub>2</sub> ... + x<sub>k</sub>r<sub>k</sub>s<sub>k</sub>) (mod n)<br /></pre><p>This is the same method Sun Zi used, a millennium before the term "modular multiplicative inverse" was invented. </p><p class="section">What the heck is RSA? </p><p>The letters "RSA" are the initials of the team that invented it, Rivest, Shamir and Adleman. It's a cryptography protocol introduced in the late 70s whose security is linked to the fact that it's very hard to factor very large semiprimes (the product of two prime numbers), and this mathematical identity: </p><pre><br />m<sup>1 (mod φ(n))</sup> ≡ m (mod n)<br /></pre><p>If I raise "m" to any number that is congruent to 1 modulo n's totient, I get a number congruent to m modulo n. That may sound a little unclear, so let's throw in some real numbers and see how it behaves. </p><p>Let's say m (the "message") is 4, and n is 7. Since 7 is prime, φ(n) is 6. Therefore: </p><pre><br />4<sup>1 (mod 6)</sup> ≡ 4 (mod 7)<br /></pre><p>The simplest example of 1 (mod 6) is just 1, so: </p><pre><br />4<sup>1</sup> ≡ 4 (mod 7)<br />4 ≡ 4 (mod 7)<br /></pre><p>Clearly we need a different number congruent to 1 mod 6. Let's try 7: </p><pre><br />4<sup>7</sup> ≡ 4 (mod 7)<br /></pre><p>4 to the 7th is 16384. If you divide 16384 by 7, you get 2340, with a remainder of 4, ergo: </p><pre><br />16384 ≡ 4 (mod 7)<br /></pre><p>Since the exponent is prime, and the same as the modulus, this matches Fermat's Little Theorem which says for any number "a" and any prime number "p": </p><pre><br />a<sup>p</sup> ≡ a (mod p)<br /></pre><p>Let's use the next value of 1 mod 6, 13. Our equation becomes: </p><pre><br />4<sup>13</sup> ≡ 4 (mod 7)<br /></pre><p>4 to the 13th is 67,108,864. Divide that by 7 to get 9,586,980 with, sure enough, a remainder of 4, making: </p><pre><br />67,108,864 ≡ 4 (mod 7)<br /></pre><p>So that's all well and good, but how does it help us encrypt anything? It has to do with the modular multiplicative inverse. In the identity: </p><pre><br />m<sup>1 (mod φ(n))</sup> ≡ m (mod n)<br /></pre><p>... you can replace "1 (mod φ(n))" with any number e and its MMI d (mod φ(n)). And here's the punchline, from a basic exponentiation identity taught in 6th grade math class: </p><pre><br />m<sup>ed</sup> = (m<sup>e</sup>)<sup>d</sup><br /></pre><p>...and one that I mentioned was important earlier: </p><pre><br />If a ≡ b (mod n) then a<sup>x</sup> ≡ b<sup>x</sup> (mod n)<br /></pre><p>I can give someone the modulus n, and the exponent e ("encryption"), and they can encrypt their message, m, by giving me m<sup>e</sup> (mod n). Since only I know d ("decryption"), only I will be able to retrieve the original m. This only works, though, if n is a very large semiprime that is mathematically hard to split into its two prime factors. If n can be factored, it's totient can be calculated, and that can be used with e to find the MMI d. </p><p>Here is an example of a real encryption/decryption using small numbers for the sake of being able to comprehend what's going on. In the real world, this would be pretty sucky encryption. </p><p>Our modulus will be 77, a semiprime composed of 7 and 11, which we'll call p and q: </p><pre><br />n = 77<br />p = 7, q = 11<br /></pre><p>The totient, then, will be p - 1 (6), times q - 1 (10), for a total of 60. </p><pre><br />φ(n) = (p-1)(q-1) = 60<br /></pre><p>The encryption exponent's only requirements are that it is less than the totient, and coprime to it. We'll be finding the MMI modulo 60 to get d, but we can pick any e that satisfies the above requirements. I pick 13. </p><pre><br />e = 13<br /></pre><p>The decryption exponent, d, is the MMI mod the totient. In this case it is 37, since 13 * 37 is 481, which is 1 more than 480, a multiple of 60: </p><pre><br />d ≡ e<sup>-1</sup> (mod φ(n)) = 37<br /></pre><p>Now I can give n and e to my buddy, and he can encrypt a message only I can decrypt... provided no one can factor 77. </p><p>My message, m, is an integer between 0 and the modulus. In real-world encryption, n is huge, say, 1024 bits, and m is an integer representation of the first n - 1 bits of the text to be encrypted. For the purpose of this example, though, we'll keep m small: 51. </p><pre><br />m = 51<br />c (ciphertext) = m^e (mod n)<br />c ≡ 51^13 ≡ 15,791,096,563,156,692,195,651 ≡ 2 (mod 77)<br />c = 2<br /></pre><p>When I receive the ciphertext "2" from my buddy, I can decrypt it with d: </p><pre><br />m ≡ 2^37 ≡ 137,438,953,472 ≡ 51 (mod 77)<br />m = 51<br /></pre><p>Since 51 gives 2, does iterating from 1 to 76 just give a simple transformation pad? Yes: </p><pre><br /> m c | m c<br />-------------------<br /> 1 1 | 39 18<br /> 2 30 | 40 68<br /> 3 38 | 41 6<br /> 4 53 | 42 14<br /> 5 26 | 43 43<br /> 6 62 | 44 44<br /> 7 35 | 45 45<br /> 8 50 | 46 74<br /> 9 58 | 47 5<br />10 10 | 48 20<br />11 11 | 49 70<br />12 12 | 50 29<br />13 41 | 51 2<br />14 49 | 52 17<br />15 64 | 53 25<br />16 37 | 54 54<br />17 73 | 55 55<br />18 46 | 56 56<br />19 61 | 57 8<br />20 69 | 58 16<br />21 21 | 59 31<br />22 22 | 60 4<br />23 23 | 61 40<br />24 52 | 62 13<br />25 60 | 63 28<br />26 75 | 64 36<br />27 48 | 65 65<br />28 7 | 66 66<br />29 57 | 67 67<br />30 72 | 68 19<br />31 3 | 69 27<br />32 32 | 70 42<br />33 33 | 71 15<br />34 34 | 72 51<br />35 63 | 73 24<br />36 71 | 74 39<br />37 9 | 75 47<br />38 59 | 76 76<br /></pre><p>...so can't I just generate a lookup table for all values between 1 and n - 1, and use that to decrypt? If n is 77, sure. If n is 1024 bits long, no. 2<sup>1024</sup> is 1.8 * 10<sup>308</sup>. Compare that to the estimated number of atoms in the universe, 1 * 10<sup>80</sup>, or the estimated hard drive space on the Internet, 500 * 10<sup>18</sup> bytes, and you can see the problem with scale. </p><p>Scale is also the security for factoring the large n. If your n is 1024 bits, it's likely its factors are both 512 bits. There are roughly 6.7 * 10<sup>153</sup> primes of 512 bits. The number of semiprimes that can be produced with them is the square of that, 4.5 * 10<sup>307</sup>. The idea is that once computer processing speed makes iterating through all those trivial, we can just throw more bits into encryption keys until it's hard again. Until we get magical quantum computers, that is. </p><p class=section>Tying it all together </p><p>So, how does all this tie in with the Chinese Remainder Theorem? Raising a large number to a large power is computationally expensive. If I pick a low-ish e to encrypt with, my buddy will be able to encrypt relatively cheaply, but it's likely that my d will be large, making the decryption harder. However, I can precompute some numbers to make the decryption easier. </p><p>Why is this so? Because I am uninterested in the value of c<sup>d</sup>, I am only interested in c<sup>d</sup> (mod n), and there are various identities based on the Chinese Remainder Theorem and Fermat's Little Theorem that let me find a congruent number to c<sup>d</sup> (mod n), without raising c to d, but instead raising c to two lower powers, and combining the results with copious modular arithmetic. </p><p>First, let's revisit the Gauss notation for the Chinese Remainder Theorem, but drop it down to two variables: </p><pre><br />If...<br />x = x<sub>1</sub> (mod n<sub>1</sub>)<br />x = x<sub>2</sub> (mod n<sub>2</sub>)<br /><br />then...<br />n = n<sub>1</sub> * n<sub>2</sub><br />r<sub>1</sub> = n / n<sub>1</sub> = n<sub>2</sub><br />r<sub>2</sub> = n / n<sub>2</sub> = n<sub>1</sub><br />(Since r<sub>1</sub> = n<sub>2</sub> and vice-versa, I'll just substitute those from here out)<br />s<sub>1</sub> = n<sub>2</sub><sup>-1</sup> (mod n<sub>1</sub>)<br />s<sub>2</sub> = n<sub>1</sub><sup>-1</sup> (mod n<sub>1</sub>)<br /><br />and...<br />x = (x<sub>1</sub>n<sub>2</sub>s<sub>1</sub> + x<sub>2</sub>n<sub>1</sub>s<sub>2</sub>) (mod n)<br /></pre><p>Now, what if x was the message we encrypted, n was the encryption modulus, and n<sub>1</sub> and n<sub>2</sub> were p and q? </p><pre><br />m = (m<sub>1</sub>q</sub>s<sub>1</sub> + m<sub>2</sub>ps<sub>2</sub>) (mod n)<br /></pre><p>m<sub>1</sub> refers to the remainder after dividing by n<sub>1</sub>, or p, which we can determine by m (mod p). Of course, since this is during the decryption, we wouldn't know what m was, so we need another way to get m (mod p). </p><p>Raising c to d is what we're trying to avoid, but a corollary to Fermat's Little Theorem tells us that c<sup>d</sup> (mod p) ≡ c<sup>d (mod (p - 1))</sup> (mod p). m<sub>2</sub> can be obtained with a similar transformation with q. This makes our new equation: </p><pre><br />m = (c<sup>d (mod (p - 1))</sup> (mod p)q</sub>s<sub>1</sub> + c<sup>d (mod (q - 1))</sup> (mod q)ps<sub>2</sub>) (mod n)<br /></pre><p>That's starting to look pretty ugly. After another transformation to find the s values, it's going to look horrendous, but you can sort of see where we're going. A much better exposition of plugging the Chinese Remainder Theorem into RSA was done by Johann GroÃŸschÃ¤dl <a href="http://www.acsac.org/2000/papers/48.pdf">here</a>. In fact I can't recommend this document highly enough if you're interested in programming RSA. </p><p>I'll skip to what the final variables and equation looks like: </p><pre><br />r<sub>p</sub> = q<sup>p - 1</sup> (mod n)<br />r<sub>q</sub> = p<sup>q - 1</sup> (mod n)<br />(Since the "r" variables don't depend on the "message" or "ciphertext", they can be computed once when d is generated, and stored as part of the private key)<br />c<sub>p</sub> = c (mod p)<br />c<sub>q</sub> = c (mod q)<br />d<sub>p</sub> = d (mod p - 1)<br />d<sub>q</sub> = d (mod q - 1)<br />m<sub>p</sub> = c<sub>p</sub><sup>d<sub>p</sub></sup> (mod p)<br />m<sub>q</sub> = c<sub>q</sub><sup>d<sub>q</sub></sup> (mod q)<br />s<sub>p</sub> = m<sub>p</sub>r<sub>p</sub> (mod n)<br />s<sub>q</sub> = m<sub>q</sub>r<sub>q</sub> (mod n)<br />m = s<sub>p</sub> + s<sub>q</sub> (mod n)<br /></pre><p>Let's apply this to my original, trivial example. Here is where we left off </p><pre><br />m = 51 (message)<br />n = 77 (modulus)<br />p = 7 (first prime factor of modulus)<br />q = 11 (second prime factor of modulus)<br />φ(n) = 60 (totient of modulus)<br />e = 13 (encryption exponent)<br />d = 37 (decryption exponent, MMI of e (mod φ(n))<br />c ≡ m<sup>e</sup> (mod n) ≡ 51<sup>13</sup> (mod 77) = 2 (ciphertext)<br /></pre><p>With those starting variables, here is where we end up: </p><pre><br />r<sub>p</sub> = q<sup>p - 1</sup> (mod n) = 11<sup>6</sup> (mod 77) = 22<br />r<sub>q</sub> = p<sup>q - 1</sup> (mod n) = 7<sup>10</sup> (mod 77) = 56<br />c<sub>p</sub> = c (mod p) = 2 (mod 7) = 2<br />c<sub>q</sub> = c (mod q) = 2 (mod 11) = 2<br />d<sub>p</sub> = d (mod p - 1) = 37 (mod 6) = 1<br />d<sub>q</sub> = d (mod q - 1) = 37 (mod 10) = 7<br />m<sub>p</sub> = c<sub>p</sub><sup>d<sub>p</sub></sup> (mod p) = 2<sup>1</sup> (mod 7) = 2<br />m<sub>q</sub> = c<sub>q</sub><sup>d<sub>q</sub></sup> (mod q) = 2<sup>7</sup> (mod 11) = 7<br />s<sub>p</sub> = m<sub>p</sub>r<sub>p</sub> (mod n) = 2 * 22 (mod 77) = 44<br />s<sub>q</sub> = m<sub>q</sub>r<sub>q</sub> (mod n) = 7 * 56 (mod 77) = 7<br />m = s<sub>p</sub> + s<sub>q</sub> (mod n) = 44 + 7 (mod 77) = 51<br /></pre><p>With this method, I have successfully retrieved my original m of 51, and the most expensive operation raised a number to the 7th power, rather than the 37th power called for in the original RSA method. </p><p class="section">Summary </p><p>The mathematicians Fermat, Euler, and Gauss, (geniuses all, and well deserving of all the reverie and accolades they receive) popularized modular arithmetic, residue systems, etc., and their works have been combined to formulate the mathematical basis of a strong encryption system. But making decryption less expensive, and hence encouraging the use of stronger encryption keys, originated with Sun Zi in the Middle Kingdom. It's a funny juxtaposition, given how the Western world views China's record of human rights, fascist government, the "Great Firewall", what have you, that a Chinese mathematician, a true genius making giant leaps and having much less prior work to build on, and over a millennium before the European maths renaissance, gave the world more privacy and more security. </p><p>How so? Every time you connect to your bank's website to check your balance, Sun Zi's math is involved. The same is true when you buy a book on Amazon, when your company transmits a payroll file, when you sign onto your email or Facebook. Whether China's net balance is on the side of privacy or totalitarianism depends, I suppose, on whether or not you're a technophile. </p>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-47108479115909594162012-06-13T20:04:00.001-04:002013-03-05T17:44:46.160-05:00Four ways to construct a pentagon <p>There are animations of the 4 compass and straightedge construction techniques at the bottom of this entry, but they require an HTML5 capable browser. (I suggest Chrome, but the animations should work with a modern Firefox, Safari, or Chromium... but let me know if they don't.) </p><p>Also included are animations of the first 6 "problems" in book 1 of Euclid's Elements, as a preview of a larger project I'm working on, and two variations on creating a heptadecagon (a seventeen-sided polygon). </p><p>Over the last few weeks I've been spending what free time I have obsessing over the odd mathematics of Argand diagrams, complex roots, and how they relate to constructing regular polygons with a compass and straightedge. I found, among other things, that I had forgotten quite a bit from my trig and geometry classes from 25 years ago. <a name='more'></a></p><p>The spark for this obsession was when I stumbled on the Wikipedia article <a href="http://en.wikipedia.org/wiki/Compass_and_straightedge_constructions">Compass and straightedge constructions</a>, a topic I thoroughly enjoyed in my freshman Geometry class back in high school. The article mentions the discovery by Guass that a regular heptadecagon (a 17-sided polygon) was constructable because the cosine of (2<span class="pi">π</span>/17) was equal to some arcane madness that had several nested square roots of 17 in it. On further digging, it mentions his proof of this had to do with finding the complex roots for x<sup>17</sup> = 1, raising e to the (2<span class="pi">π</span>i/17), and liberal application of Euler's formula (e<sup>ix</sup> = cos x + i sin x), all of which either seemed unrelated to drawing circles and lines, or just went right over my head. </p><p>After returning to the article and reading other texts about the topic, I started to see how it all connected together, but not enough to be able to explain it to anyone, and then miraculously, it all clicked after watching <a href="http://www.khanacademy.org/math/precalculus/v/exponential-form-to-find-complex-roots">this video</a> on Khan Academy, which explains how to find complex roots. (Thanks, Sal, you da man.) </p><p>There are two sides to this: the crazy math which concerns itself with complex roots and whatnot, which deals mainly with proving that a shape is or is not constructable, and the much simpler math that deals with finding where on a circle to find the next vertex of a shape. The simpler math, while easier to understand to laymen like myself, turns out to be pretty interesting on its own. </p><p class="section">0: Basic concepts </p><p>Let's start with a triangle. </p><p>No, let's start with <span class="pi">π</span> (pi), the ratio of a circle's circumference to it's diameter. If you've read this far, I imagine you've heard of it, as well as the basic formula C = 2<span class="pi">π</span>r. The circumference of a circle is equal to 2 times <span class="pi">π</span> times the circle's radius. </p><p>If the circle's radius is 1 (a "unit circle"), your current distance around the circle can be described as a fraction of 2<span class="pi">π</span>, the total circumference. If you are a third of the way around, you are at 2<span class="pi">π</span>/3 from your starting point. To describe an equilateral triangle on the circle, you start at any point on the circle's edge, then add a second point one third the way around, at 2<span class="pi">π</span>/3, then add a third point another 2<span class="pi">π</span>/3 around, then connect the dots: </p><p><img src="https://lh4.googleusercontent.com/-MB6PYdn2kXE/T9Sn6uDxMmI/AAAAAAAADc4/Fn_ZYKIPL6Q/s800/tri_1.PNG" /></p><p>So far so good, but how does knowing you have to go 2<span class="pi">π</span>/3 around help you? With our good friend the cosine, of course! If you took a trigonometry course in school, you may have heard of the acronym SOH-CAH-TOA (it's explanation, unfortunately, commonly comes with a story that mocks native Americans - old chief Sohcahtoa, uncle to the lost brave Fallen Rocks, etc.). The acronym tells us that the Cosine is equal to the Adjacent side divided by the Hypotenuse. Cosine, Adjacent, Hypotenuse - the "CAH" of SOH-CAH-TOA. What does all that mean? </p><p>This is in reference to right-triangles. With our example above, we can draw a right triangle from the point we're trying to find to the diameter line of the circle: </p><p><img src="https://lh4.googleusercontent.com/-i44WvdU5jrc/T9Sn5URyJ9I/AAAAAAAADbw/inI5UnEH1No/s800/cosine.PNG" /></p><p>This is a unit circle, so it has a radius of one. The cosine of our 2<span class="pi">π</span>/3 angle will be the length of the line segment marked "adjacent", divided by the hypotenuse. Since the hypotenuse in this case is also the circle's radius, we're just dividing by one, so the cosine tells us how far to move from the center along the diameter line to get "under" the point at 2<span class="pi">π</span>/3. </p><p>The cosine of 2<span class="pi">π</span>/3 radians (120 degrees if your calculator doesn't understand radians), is -.5, or half the distance from the circle's center to the left side of the diameter line. This is where the numbers turn into drawing instructions. One of the basic constructs with a compass and straightedge is dividing something in two, in this case the line segment between the circle's center and the left edge: </p><p><img src="https://lh6.googleusercontent.com/-qpzz8fsBLIk/T9Sn5H_lxrI/AAAAAAAADbk/Fq6pQHXuEkU/s800/bisect.PNG" /></p><p>Start with the circle in the picture above, then draw a circle with Q as the center, out to radius QO, and the circle/circle intersection points bisect the line segment QO, putting us -.5 from center on the diameter line, or immediately under the 2<span class="pi">π</span>/3 point. </p><p><img src="https://lh4.googleusercontent.com/-oHowY8ifb7k/T9Sn5WX6A3I/AAAAAAAADbs/mOicqbWGO4I/s800/bisected.PNG" /></p><p>Now to find the second point of the triangle, we need a line perpendicular to QO, which will intersect the original circle at 2<span class="pi">π</span>/3... what's that? Yes, in fact, we already have one of those. Additionally, the point it intersected was already established by the circle/circle intersection. Additionally...er, the bottom intersection point was at 4<span class="pi">π</span>/3 (whose cosine is also -.5, but whose sine is negative). So after we draw circle QO, we can mark off the second and third points of the inscribed triangle, and connect them: </p><p><img src="https://lh5.googleusercontent.com/-57DMayF8qQg/T9Sn6kd2PUI/AAAAAAAADc0/UVyagEQ46bI/s800/tri_2.PNG" /></p><p>Fairly simple. For any regular polygon you want to inscribe in a circle, you pick a point on the circle's edge, and then find the point at 2<span class="pi">π</span>/n, where n is the number of sides. Once you find one leg of the shape, you can either repeat the procedure starting at the new point, or just draw a new circle centered on one point with a radius out to the other, and the circle-circle intersection shows where the next point is.: </p><p><img src="https://lh5.googleusercontent.com/-A5SBv5vLwe0/T9Sn545ZLEI/AAAAAAAADcE/yOQs_glWlb8/s800/hep_circles.PNG" /></p><p>Alternatively, find f2<span class="pi">π</span>/n, where n is the number of sides, and f is co-prime to n. "Say what?" Basically, instead of finding the next point, you can find some other point, so long as skipping that many points and repeating will eventually fill in all the dots. </p><p class="section">1: Euclid's method </p><p>There are other methods to find a point than by using cosines. With pentagons, the cosine math gets a little trickier. Since there are five points, the distance along the circumference between each will be 2<span class="pi">π</span>/5. If you ask a calculator for the cosine of 2<span class="pi">π</span>/5 radians (or 72 degrees), you get an irrational number that starts with 0.309016994. This turns out to be (√5 - 1)/4, but let's assume we don't know that yet. Point being, no method of finding that point with compass and straightedge jumps out at you. </p><p>In Euclid's Elements, the construction method was different: Inscribe an isosceles triangle on a circle, making the angles on the base double the one on top. Bisect the bigger angles, and then you should have 5 equal angles extending to 5 points equidistant from each other. Creating the triangle involves mechanically finding the golden ratio, (√5 + 1)/2, commonly denoted by the Greek letter phi (φ). The link below titled "Pentagon, Euclid's method" shows an animation of that, but here's the gist of it: </p><p><img src="https://lh3.googleusercontent.com/-cgk_A0QJgAY/T9fvmRlGtBI/AAAAAAAADew/kxb3iOJKV4k/s800/golden_triangle.PNG" /></p><p>The type of triangle required for this is a "golden triangle", meaning the ratio between the long sides and short side is the golden ratio. That knowledge, combined with Ptolemy's little gem, can tell us everything we need to know about a pentagon, including ways to construct it beyond Euclid's. </p><p class="section">2: Phi method (or "The Kite") </p><p>Ptolemy's theorem, if you've never heard of it, is the bad-ass sequel to the Pythagorean theorem... the "Godfather 2" to Pythagoras' "The Godfather". Unfortunately, it never made its way into popular parlance, which I attribute to its difficulty in being expressed succinctly in English. It goes, roughly, thusly: </p><p>If you inscribe a 4 sided polygon on a circle, the product of the lengths of the diagonals is the same as the sum of the products of each pair of opposite sides. For example, if you have a quadrilateral ABCD inscribed on a circle: </p><p><img src="https://lh5.googleusercontent.com/-D3xXevbXWqQ/T9Sn6a73bdI/AAAAAAAADck/iOglGDA1frA/s800/ptolemy.PNG" /></p><p>...then <span class="ov">AC</span> * <span class="ov">BD</span> = (<span class="ov">AB</span> * <span class="ov">CD</span>) + (<span class="ov">BC</span> * <span class="ov">AD</span>). Why this relationship works isn't intuitive (to me), especially since it doesn't rely on right angles. In the case of pentagons, it's surprisingly useful. To start, remember the golden triangle that can be drawn with three of the pentagon's points. The long sides (B from above) amount to connecting pentagon vertex points that are two apart on the circle's edge, where the short side (A) connects points that are adjacent. The ratio between them, B/A is equal to φ, the golden ratio. </p><p>Now with that in mind, draw a line from one of the pentagon's points, through the circle's center, and connect it with the opposite end of the circle, making a diameter line. Then draw a kite: </p><p><img src="https://lh4.googleusercontent.com/-9e7ClAjpZ1o/T9Sn5y8I7QI/AAAAAAAADcI/GENV-8yelUo/s800/kite.PNG" /></p><p>Note that point Q, the end of the diameter line, is not a point on the pentagon. The lines A and B in the drawing above have the same lengths as A and B from the golden triangle. We don't know what their individual lengths are... yet. The diameter line is D, which we know is length 2 since this is a unit circle. The two Z lines are, for now, complete unknows. Now let's apply Ptolemy's theorem and a little algebra: </p><pre><br />DB = AZ + AZ, or, DB = 2AZ. Divide both sides by 2A to solve for Z.<br />Z = DB/2A<br />We know the diameter, D, is 2, so:<br />Z = 2B/2A, which reduces to Z = B/A.<br />Lastly, we know from Euclid that B/A is the golden ratio, so:<br />Z = φ = (√5 + 1)/2<br /></pre><p>This gives us our second method of constructing a pentagon with compass and straightedge, namely finding a point phi away from the opposite end of P0. We do this by finding the √5/2 part and the 1/2 part separately using nothing more than bisecting lines and drawing circles with known points. The link "Pentagon, Phi method" below shows an animation of that, but the method is simply drawing a circle with a 1/2 radius inside the unit circle, with a diameter from the unit circle's center to it's edge: </p><p><img src="https://lh5.googleusercontent.com/-MjU1LWExUwY/T9Sn6IVELDI/AAAAAAAADco/Pmnlbr-seck/s800/phi_method_1.PNG" /></p><p>The triangle QBO is a right triangle with base 1, height 1/2, and hypotenuse √(1<sup>2</sup> + (1/2)<sup>2</sup>) = √(1 + 1/4) = √(5/4) = √5/2. So from Q to B we need only 1/2 more to be at the radius φ... which also happens to be the radius of the inner circle. </p><p>So we just extend line QB, and the point where it intersects the inner circle, N, is φ away from Q. All that's left is to draw a semicircle from center Q to radius N, which will cross the unit circle at P1 and P4: </p><p><img src="https://lh3.googleusercontent.com/-oGwyXIRKz5Y/T9Sn6NU62TI/AAAAAAAADcY/R1TbjgP1_Ho/s800/phi_method_2.PNG" /></p><p class="section">Intermission: Exact value of 0.309016994... </p><p>With some further applied trig and algebra, we can find the exact value of cosine(2<span class="pi">π</span>/5). Let's return to the kite: </p><p><img src="https://lh3.googleusercontent.com/-YEbwEvP6NHk/T9Sn6DMGw4I/AAAAAAAADcU/2AEHS1z4rLQ/s800/kite2.PNG" /></p><p>According to Thales' theorem if two points on a circle lie on opposite ends of a diameter line, connecting them to any other point on the circle forms a right angle. In the kite diagram above, triangle ZAD is a right triangle. D and Z are known values, so A is √(D<sup>2</sup> - Z<sup>2</sup>), or √(4 - φ<sup>2</sup>), which reduces to the very un-sexy √((5 - √5)/2). </p><p>Now in the Ptolemy equation, only B is unknown. A little algebra finds its length to be √((5 + √5)/2). If line B intersects the diameter line at J: </p><p><img src="https://lh6.googleusercontent.com/-0uZFxRlzVZA/T9dX0JJW8KI/AAAAAAAADd8/1L-SrA_vpug/s800/J.PNG" /></p><p>...then we have enough information to solve for the missing length <span style="ov">J-P0</span> in right triangle J-P1-P0 (5/4 - √(5/4)), leading us, at long last, to the length between O and J, the same as the cosine of 2<span class="pi">π</span>/5: (√5 - 1)/4. </p><p class="section">3: Cosine method (or "Richmond's epiphany") </p><p>The 19th century gave us an interesting mathematician who is very overlooked: Herbert William Richmond. His specialty was algebraic geometry. His papers were compact, and contained simplifications of already discovered methods. Two years before his death, he was writing about the failings of algebraic techniques to handle problems of higher than three dimensions, and had this to say about his style: </p><blockquote>"It is true that the scope of these methods is restricted, but there is compensation in the fact that when geometry is successful in solving a problem the solution is almost invariably both simple and beautiful. </p><p>The last sentence explains why so many of my published papers are very short. A result already known is obtained in a simple manner. </p><p>Admittedly I have spent much time advocating old-fashioned methods which have fallen into undeserved neglect (in my opinion). This does not imply lack of appreciation of modern methods..." </blockquote><p>In 1893, Richmond published a two-page paper in "The Quarterly Journal Of Pure And Applied Mathematics" titled "A Construction for a Regular Polygon of Seventeen Sides." The paper expands on Gauss' work on the heptadecagon, showing a practical compass and straightedge construction method. As a footnote, the paper ends with a quick discussion of pentagons: </p><blockquote>It may similarly be shewn that, if 2C be the acute angle whose tangent is 2, and α stand for 2<span class="pi">π</span>/5, then will </p><p><div style="text-align:center;">2 cos α = tan C, and 2 cos 2α = - cot C. </div></p><p>Therefore (fig. 7), if OA, OB be two perpendicular radii of a [circle] and J the middle point of OBl then the ordinates drawn circle, through E and F, the points where the bisectors of OJA meet OA, determine on the circle four points which with A form a regular pentagon. </p><p><img src="https://lh5.googleusercontent.com/-4iVAzRZJ3O0/T9dXuMQdN6I/AAAAAAAADdk/QT1prW-wCL8/s800/fig7.PNG" /></p></blockquote><p>Another way of expressing Richmond's equation is this: </p><pre><br />tan(arctan(cos(2π/5) * 2) * 2) = 2<br /></pre><p>What it's trying to show is that we're flipping the triangle the other way, with the vertex at 90°, changing from a hypotenuse of 1 to a height of 1, and examining the angle at A instead of the angle at O: </p><p><img src="https://lh3.googleusercontent.com/-SSOvQrSUAd8/T9dXv2zgwsI/AAAAAAAADds/Bo-P1lRvT1U/s400/flip.PNG" /></p><p>What Richmond found, and it's unclear what sparked his epiphany, is that if you cut the adjacent side in half, and double the angle, you get a tangent of 2. In the case of the unit circle, this puts the apex of the triangle at (0, 1/2), and the base points at O and P0. </p><p>Going backwards from the epiphany, if you bisect line OA at B, then bisect the angle at O-B-P0, you intersect <span class="ov">O-P0</span> at (√5 - 1)/4. A perpendicular line up from there intersects the unit circle at P1: </p><p><img src="https://lh6.googleusercontent.com/-vOnEUFqDSPA/T9dXxVD5VfI/AAAAAAAADd0/j6GVOYEGzic/s800/half_cosine.PNG" /></p><p>The link below, "Pentagon, Cosine method" shows an animation of this that may make more sense than my explanation. </p><p class="section">4: Carlyle Circles </p><p>In the early 19th century, Thomas Carlyle gave us a gem of a geometric construct - shortly before retiring from teaching mathematics and moving on to political writing including satire, a history of the French Revolution, and advocating against democracy and for moving back to a fuedal society. Takes all types. </p><p>Anyway, what he discovered was a way to mechanically find the quadratic roots of equations of the form x<sup>2</sup> -sx + p. The method is remarkably simple: draw a circle with a diameter from (0,1) to (s,p), and where the circle crosses the x axis are the roots. </p><p>For example, here's Wolfram Alpha's rendition of a Carlyle Circle describing x<sup>2</sup> -5x + 6: </p><p><img src="https://lh3.googleusercontent.com/-Vmr3Gaube1k/T9Sn4cVV2zI/AAAAAAAADbA/qF1h-twiAmM/s800/5_6_a.PNG" /></p><p>The equation at the bottom is not the quadratic formula we're trying to solve, but rather the circle's equation in the format (x - h)<sup>2</sup> + (y - k)<sup>2</sup> = r<sup>2</sup>. Visually it appears the circle is crossing 2 and 3, but here's an official declaration from Wolfram of the intersect points: </p><p><img src="https://lh4.googleusercontent.com/-LKU-bJNs5j0/T9Sn4QeSI4I/AAAAAAAADbI/hg8L6POoprg/s800/5_6_b.PNG" /></p><p>(Unfortunately, I couldn't find the exact syntax on Wolfram's input line to do this in one step, hence the examples all have two screenshots.) As it appeared, 2 and 3, making the root formula (x - 2)(x - 3), which, if you're familiar with this type of thing, is pretty obvious. </p><p>For a second example, here is how the Carlyle circle behaves when the roots are the same, solving x<sup>2</sup> -6x + 9: </p><p><img src="https://lh5.googleusercontent.com/-PjaB0f0zMik/T9Sn4ZXj9HI/AAAAAAAADbE/UMLbA9Q1BQE/s800/6_9_a.PNG" /><br /><img src="https://lh4.googleusercontent.com/-UhGfJgFhNKI/T9Sn41-4mEI/AAAAAAAADbQ/Ulmw1PLGx7I/s800/6_9_b.PNG" /></p><p>One intersection, the x axis tangent to the circle, making the root formula (x - 3)<sup>2</sup></p><p>How does any of this help us construct a pentagon? It goes back to the cosines of the individual points. Remember that when we find cosines of points on the edge of a unit circle (actually cosines of the angles at the origin), we end up with the length of the base of the right triangle - positive for points right of the origin, negative for points left of the origin, and 0 for points at exactly 90 and 270 degrees. If you think about this for a minute, you might come to an interesting conclusion: </p><p>For a regular polygon, the cosines of the points add up to 0. </p><p>As a corollary of that, consider that all of our examples have had a vertex point at (1,0), or on the x axis, at distance 1 from the origin. The cosine of that point is 1. If you add up the cosines of every other point, then, you get -1. Some regular polygons have another property: certain combinations of vertex cosines multiply together to form whole numbers or simple fractions. </p><p>Consider the four points of a pentagon other than the starting point at (1,0): </p><pre><br />Point 1: cos(1 * 2π/5) = 0.30901699437494742410229341718282<br />Point 2: cos(2 * 2π/5) = -0.80901699437494742410229341718282<br />Point 3: cos(3 * 2π/5) = -0.80901699437494742410229341718282<br />Point 4: cos(4 * 2π/5) = 0.30901699437494742410229341718282<br /></pre><p>First, clearly the two positive numbers (points 1 and 4) correspond to points on the right of the origin, where the two negative numbers (points 2 and 3) are left of the origin. It turns out that the positive points are the same x distance from the origin, and the same is true of the negative points. </p><p>To verify what I said above, these values all add up to -1 (note after the 3 and 8, the rest of the digits are the same, so you have .3-.8-.8+.3 = -1.0. Now, if you add the negative and positive pairs together, let's call them "n1" and "n2", we get this: </p><pre><br />n1 = -0.80901699437494742410229341718282 * 2 = -1.618033988749894848204586834364<br />n2 = 0.30901699437494742410229341718282 * 2 = 0.618033988749894848204586834364<br /><br />n1 + n2 = -1<br /></pre><p>and here's the punchline: </p><pre><br />n1 * n2 = -1<br /></pre><p>Now the digits I have for n1 and n2 are estimates, so if you're checking the product with a calculator, you'll get a -0.99999... number just short of -1. The real answer is -1, and I'll spare you the trigonometric exegesis that shows it. </p><p>So, if I plug (-1,-1) into the Carlyle circle, the two points crossing the x axis should equal n1 and n2, by virtue of "s" being n1 + n1, and "p" being n1 * n2. Both points are double the x distance from the origin as their respective pentagon vertex points: </p><p><img src="https://lh4.googleusercontent.com/-ZEwwqr--FrM/T9dX4zFBLaI/AAAAAAAADeI/d6Gxtyd7J2s/s800/carlyle_1.PNG" /></p><p>Visually the intersection points look to be at roughly n1 and n2 from above, but here's the exact answer from Wolfram (I've done the obvious simplification of their equation of subtracting 1/4 from both sides): </p><p><img src="https://lh3.googleusercontent.com/-hpkCaew43js/T9dX4-ocSCI/AAAAAAAADeU/D6-iSSMWajY/s800/carlyle_2.PNG" /></p><p>Using compass and straightedge rules, finding point (0,1) is trivial. So is finding (-.5,0), the midpoint between (-1,-1) and (0,1), to be the circle's center: </p><p><img src="https://lh4.googleusercontent.com/-bA9MjZY-GUg/T9dX48qFMoI/AAAAAAAADeE/kCGdzFEW0v8/s400/carlyle_m_a.PNG" /></p><p>From there a circle from center M to radius A intersects the x axis at our n1 and n2 points: </p><p><img src="https://lh4.googleusercontent.com/-Pd3A-rSJtE0/T9doYRtz3YI/AAAAAAAADeg/m204DyME3t8/s800/carlyle_n1_n2.PNG" /></p><p>And now what do we do? Lines O-n1 and O-n2 are both twice as long as the x position of their respective vertex points. From here there are two basic approaches. First, you can bisect the lines, then a perpendicular line from the bisection points will cross the unit circle at P2 and P3 on the negative side, and P1 and P4 on the positive side. Alternately, we can draw new unit circles at n1 and n2, and the circle/circle intersections will occur in the same places as the first approach. </p><p>If you add the ability of remembering a compass length to the classical rules of compass and straightedge, the unit circle approach is decidedly the simpler one. In the link to my example below titled "Pentagon, Carlyle Circle method", I performed the unit-circle method on one side, but stuck to the classical Greek rules of collapsing a compass when you lift it from the page. To get the unit length over to point n1, I used Book I, Proposition II of Euclid's Elements to copy the length over from line QO. </p><p>So, that's about all. There are other methods to create a pentagon which I haven't covered here, such as Ptolemy's, but I think I've covered the topic pretty thoroughly. I hope I have laid this out simply enough for a layman (or a high-school student trying to decide if math is a worthwhile thing to think about) to follow. Enjoy the animations below. </p>Curtis <p><input type=text id=delay value=100 size=4 /> Frame delay<br /><input type=button onclick=run() value=Run /><br /><a href=javascript:inscribedTriangle()>Simple inscribed triangle</a><br /><br />Pentagons<br /><a href=javascript:euclid()>Pentagon, Euclid's method</a><br /><a href=javascript:phi()>Pentagon, Phi method</a><br /><a href=javascript:cosine()>Pentagon, Cosine method</a><br /><a href=javascript:carlyle()>Pentagon, Carlyle Circle method</a><br /><br />Heptadecagons<br /><a href=javascript:hep()>Richmond method</a><br /><a href=javascript:hep2()>Carlyle circle method</a><br /><br />Selections from Book 1 of Euclid's Elements<br /><a href=javascript:b1p1()>B1P1 - Create an equilateral triangle</a><br /><a href=javascript:b1p2()>B1P2 - Create a line of another line's length</a><br /><a href=javascript:b1p3()>B1P3 - Cut a line at a smaller line's length</a><br /><a href=javascript:b1p9()>B1P9 - Bisect an angle</a><br /><a href=javascript:b1p10()>B1P10 - Bisect a line segment</a><br /><a href=javascript:b1p11()>B1P11 - Create a right angle at a given point</a><br /><br /><textarea id="ta"></textarea></p> <style> .ov { text-decoration:overline; } .pi { font-family: 'Times New Roman'; } canvas { border: 1px solid black; margin: 0 auto; display: block; background:#FFF; } textarea { display: block; width:640px; height:480px; border: 1px solid black; padding: 0; } .close { text-align: right; margin: 10px; } </style> <script type="text/javascript">/* Things I'm allowed to do: http://en.wikipedia.org/wiki/File:Basic-construction-demo.png Proof of concept code to create compass/straightedge geometrical constructs with HTML5 canvases. The math for finding intersections I pulled from Wolfram's "Math World" as well as some independent work I did here: http://cautery.blogspot.com/2010/09/detecting-when-mouse-cursor-is-near.html All work herein is freely redistributable under LGPL3. Curtis Autery June 2012 */ var highestZ = 0; var cnv, ctx; // Canvas, 2d context var cr = Math.PI * 2; var Type = {POINT : 0, LINE : 1, CIRCLE : 2}; var col, sm, interval, label; var pointRadius = 3; var textOffsetX = 5; var textOffsetY = -5; var canvasBounds = {}; var highlightColor = '#0000FF'; function Point(x, y) { this.x = x; this.y = y; this.highlighted = false; this.type = Type.POINT; this.draw = function() { ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.arc(this.x, this.y, pointRadius, 0, cr, false); ctx.fill(); } } function Circle(center, R) { this.center = center; // a Point this.R = R; this.radius = dist(center, R); this.r2 = this.radius * this.radius; this.highlighted = false; this.type = Type.CIRCLE; this.draw = function() { ctx.beginPath(); ctx.moveTo(this.center.x + this.radius, this.center.y); ctx.arc(this.center.x, this.center.y, this.radius, 0, cr, false); ctx.stroke(); } this.getCircleIntersect = function(circle) { var d = dist(this.center, circle.center); if (d > this.radius + circle.radius) { return new Intersect([], "Circles do not intersect"); } if (d < Math.abs(this.radius - circle.radius) || d == 0) { return new Intersect([], "One circle encloses the other"); } var a = (this.r2 - circle.r2 + d * d) / (2 * d); var h = Math.sqrt(this.r2 - a * a); var midX = this.center.x + a * (circle.center.x - this.center.x) / d; var midY = this.center.y + a * (circle.center.y - this.center.y) / d; var hyy = h * (circle.center.y - this.center.y) / d; var hxx = h * (circle.center.x - this.center.x) / d var points = [] points[0] = new Point(midX + hyy, midY - hxx); // if d = r1 + r2, circles are tangent, else add second intersect point if (d < this.radius + circle.radius) { points[1] = new Point(midX - hyy, midY + hxx); } return new Intersect(points, ""); } this.getLineIntersect = function(line) { var x1 = line.A.x - this.center.x; var x2 = line.B.x - this.center.x; var y1 = line.A.y - this.center.y; var y2 = line.B.y - this.center.y; var dx = x2 - x1; var dy = y2 - y1; var dr = Math.sqrt(dx * dx + dy * dy); var dr2 = dr * dr; var D = x1 * y2 - x2 * y1; var D2 = D * D; var sgn = dy < 0 ? -1 : 1; var disc = this.r2 * dr2 - D2; if (disc < 0) return new Intersect([], "Line does not intersect circle"); var points = []; var tmpSqrt = Math.sqrt(this.r2 * dr2 - D2); var x = (D * dy + sgn * dx * tmpSqrt)/ dr2 + this.center.x; var y = (-D * dx + Math.abs(dy) * tmpSqrt) / dr2 + this.center.y; points[0] = new Point(x, y); if (disc > 0) { x = (D * dy - sgn * dx * tmpSqrt)/ dr2 + this.center.x; y = (-D * dx - Math.abs(dy) * tmpSqrt) / dr2 + this.center.y; points[1] = new Point(x, y); } return new Intersect(points, ""); } } function Line(A, B) { this.A = A; // A and B are Points this.B = B; var extended = false; this.type = Type.LINE; this.highlighted = false; this.extend = function() { if (extended) return; extended = true; // move points A and B to edge of canvas var i = this.getLineIntersect(canvasBounds.top); if (i.points.length == 0) i = this.getLineIntersect(canvasBounds.left); var j = this.getLineIntersect(canvasBounds.bottom); if (j.points.length == 0) j = this.getLineIntersect(canvasBounds.right); this.A = i.points[0]; this.B = j.points[0]; } this.draw = function() { ctx.beginPath(); ctx.moveTo(this.A.x, this.A.y); ctx.lineTo(this.B.x, this.B.y); ctx.stroke(); } this.getLineIntersect = function(line) { var x1 = this.A.x; var x2 = this.B.x; var x3 = line.A.x; var x4 = line.B.x; var y1 = this.A.y; var y2 = this.B.y; var y3 = line.A.y; var y4 = line.B.y; var denom = det(x1 - x2, y1 - y2, x3 - x4, y3 - y4); if (denom == 0) return new Intersect([], "Lines don't intersect"); var leftA = det(x1, y1, x2, y2); var leftC = det(x3, y3, x4, y4); var x = det(leftA, x1 - x2, leftC, x3 - x4) / denom; var y = det(leftA, y1 - y2, leftC, y3 - y4) / denom; return new Intersect([new Point(x, y)], ""); } } function det(a, b, c, d) { // Determinate in format |a b| // |c d| return a * d - b * c; } function dist(A, B) { var x = A.x - B.x; var y = A.y - B.y; return Math.sqrt(x * x + y * y); } function Intersect(points, message) { this.points = points; this.message = message; } function clear() { if (interval == null) return; clearInterval(interval); interval = null; } function init() { promptBoxClosures(); clear(); cnv = document.getElementById('cnv'); var tl = new Point(0, 0); var tr = new Point(cnv.width, 0); var bl = new Point(0, cnv.height); var br = new Point(cnv.width, cnv.height); canvasBounds.top = new Line(tl, tr); canvasBounds.bottom = new Line(bl, br); canvasBounds.left = new Line(tl, bl); canvasBounds.right = new Line(tr, br); ctx = cnv.getContext('2d'); ctx.translate(.5, .5); ctx.strokeStyle = "black"; col = {}; sm = new StepManager(); } function frame() { sm.frame(); } function drawBoard() { // Clear board ctx.clearRect(0, 0, cnv.width, cnv.height) // Draw current label, if any if (label != "") ctx.fillText(label, 20, 20); // Iterate through collection (col) of points, lines and circles, and call their draw() methods for (var e in col) { var elem = col[e]; if (elem.highlighted) { ctx.save(); ctx.strokeStyle = highlightColor; ctx.fillStyle = highlightColor; ctx.lineWidth = 2; elem.draw(); ctx.restore(); } else elem.draw(); // ...and label the points if (elem.type == Type.POINT) { ctx.fillText(e, elem.x + textOffsetX, elem.y + textOffsetY); } } } function run() { var nextStep = function() { cnv.onclick = null; if (sections == null || sections[index] == null) return; var section = sections[index++]; sm.init(section, nextStep); } label = ""; col = {}; clear(); var lines = document.getElementById('ta').value.split(/\n+/); var sections = []; var index = 0; sections[0] = {}; sections[0].rules = []; for (var l in lines) { var line = lines[l]; if (line == '' || line.charAt(0) == '#') continue; if (line.charAt(0) == ':') { sections[index].prompt = line.substr(1); index++; sections[index] = {}; sections[index].rules = []; } else { sections[index].rules[sections[index].rules.length] = line; } } index = 0; openPromptBox(); nextStep(); } function StepManager() { var index, done, i, steps, callback, prompt; this.init = function(s, c) { steps = s.rules; prompt = s.prompt callback = c; i = null; index = 0; done = false; drawBoard(); interval = setInterval(frame, document.getElementById('delay').value); } function point(x, y, name) { y = parseInt(y); if (x == 'i') col[name] = i.points[y]; else col[name] = new Point(parseInt(x), y); } function line(a, b) { var name = '_ln_' + a + '_' + b; col[name] = new Line(col[a], col[b]); } function extend(a, b) { var name = '_ln_' + a + '_' + b; col[name].extend(); } function circle(a, b) { var name = '_cr_' + a + '_' + b; col[name] = new Circle(col[a], col[b]); } function getObj(type, a, b) { return col[nameFromElems(type, a, b)]; } function intersect(typeA, a, b, typeB, c, d) { var objA = getObj(typeA, a, b); var objB = getObj(typeB, c, d); if (typeB == "circle") i = objA.getCircleIntersect(objB); else i = objA.getLineIntersect(objB) } function nameFromElems(type, a, b) { if (type == "circle")return '_cr_' + a + '_' + b; if (type == "line") return '_ln_' + a + '_' + b; return a; } function deleteObj(type, a, b) { delete(col[nameFromElems(type, a, b)]); } function setHighlighted(type, a, b) { col[nameFromElems(type, a, b)].highlighted = true; } function display(msg) { label = msg; } this.frame = function() { if (done == true) { clear(); showPrompt(prompt, callback); return; } this.nextStep(); drawBoard(); } function showPrompt(prompt, callback) { if (prompt == null) { label = "done"; drawBoard(); return; } label = prompt; drawBoard(); showArrow(); cnv.onclick = callback; } this.nextStep = function() { if (index >= steps.length) { done = true; return; } var step = steps[index++]; var elems = step.split(/\s+/); switch(elems[0]) { case '': case '#': this.nextStep(); break; case 'point': point(elems[1], elems[2], elems[3]); break; case 'circle': circle(elems[1], elems[2]); break; case 'line': line(elems[1], elems[2]); break; case 'extend': extend(elems[1], elems[2]); break; case 'intersect': intersect(elems[1], elems[2], elems[3], elems[4], elems[5], elems[6]); this.nextStep(); break; case 'delete': deleteObj(elems[1], elems[2], elems[3]); break; case 'highlight': setHighlighted(elems[1], elems[2], elems[3]); break; default: display("Unknown step type: " + elems[0]); done = true; return; } } } function showArrow() { ctx.save(); ctx.fillStyle = "#A0FFA0"; ctx.translate(cnv.width - 50, cnv.height - 50); ctx.beginPath(); ctx.moveTo( 0, 16); ctx.lineTo(25, 16); ctx.lineTo(25, 0 ); ctx.lineTo(49, 24); ctx.lineTo(25, 48); ctx.lineTo(25, 32); ctx.lineTo( 0, 32); ctx.lineTo( 0, 16); ctx.fill(); ctx.restore(); } function inscribedTriangle() { document.getElementById('ta').value = ":Inscribe an equilateral triangle in an existing circle\npoint 270 240 O\npoint 370 240 P0\n:Let O be the circle's center, and P0 be any point on the circle's edge\ncircle O P0\n:Draw a line from O to P0, and extend it to the circle's other side to find Q\nline O P0\nextend O P0\nintersect circle O P0 line O P0\npoint i 1 Q\n:Draw a circle from center Q with radius O\ncircle Q O\n:The intersection points between the two circles will be the triangle's other two points\nintersect circle O P0 circle Q O\npoint i 1 P1\npoint i 0 P2\n:Delete debris, connect the dots\ndelete circle Q O\ndelete point Q\ndelete line O P0\nline P0 P1\nline P1 P2\nline P2 P0\n"; } function bisect() { document.getElementById('ta').value = ":Draw angle ABC\npoint 280 100 A\npoint 320 240 B\npoint 420 240 C\nline A B\nline B C\n:Draw circle at B with C radius\ncircle B C\n:Mark intersect point of circle and line AB as D\nintersect circle B C line A B\npoint i 1 D\n:Draw circles DB and CB\ncircle D B\ncircle C B\n:Mark intersect point of two new circles (other than B) as E\nintersect circle D B circle C B\npoint i 0 E\n:Draw bisecting line BE\nline B E\nextend B E\n:Erase chaff\ndelete circle D B\ndelete circle C B\ndelete point D\ndelete circle B C\n"; } function hep() { document.getElementById('ta').value = ":Draw origin and P1, circle, and diameter\npoint 320 300 O\npoint 490 300 P1\ncircle O P1\nline O P1\nextend O P1\n\n:Find perpendicular bisector of diameter\nintersect circle O P1 line O P1\npoint i 1 tmp\ncircle tmp P1\ncircle P1 tmp\nintersect circle tmp P1 circle P1 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P1 line b1 O\npoint i 1 B\ndelete circle tmp P1\ndelete circle P1 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n\n:Create point J 1/4 of the way up OB\ncircle B O\nintersect circle B O circle O P1\npoint i 1 b1\npoint i 0 b2\nline b1 b2\nintersect line O B line b1 b2\npoint i 0 tmp\ndelete circle B O\ndelete line b1 b2\ndelete point b1\ndelete point b2\ncircle O tmp\ncircle tmp O\nintersect circle O tmp circle tmp O\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nline O tmp\nintersect line b1 b2 line O tmp\npoint i 0 J\ndelete circle O tmp\ndelete circle tmp O\ndelete point tmp\ndelete line b1 b2\ndelete point b1\ndelete point b2\ndelete line O tmp\n\n:Create line JP1 and find E on bisector line where OJE is 1/4 of OJP1\nline J P1\ncircle J O\nintersect circle J O line J P1\npoint i 0 tmp\ncircle O J\ncircle tmp J\nintersect circle O J circle tmp J\ndelete point tmp\npoint i 1 t\nline J t\ndelete circle tmp J\nintersect circle J O line J t\npoint i 0 tmp\nline J tmp\ndelete line J t\ndelete point t\ndelete circle J O\ncircle tmp J\nintersect circle O J circle tmp J\npoint i 1 t\nline J t\ndelete circle O J\ndelete circle tmp J\ndelete line J tmp\ndelete point tmp\nintersect line O P1 line J t\npoint i 0 E\nline J E\ndelete line J t\ndelete point t\n\n:create F on circle bisect line such that EJF is 45 degrees\ncircle J E\nextend J E\nintersect circle J E line J E\npoint i 1 tmp\ncircle tmp E\ncircle E tmp\nintersect circle tmp E circle E tmp\npoint i 1 t\nline J t\ndelete circle tmp E\ndelete circle E tmp\ndelete point tmp\nline J E\nintersect circle J E line J t\npoint i 0 tmp\ndelete line J t\ndelete point t\ncircle E J\ncircle tmp J\nintersect circle E J circle tmp J\npoint i 0 t\nline J t\ndelete circle E J\ndelete circle tmp J\ndelete point tmp\ndelete circle J E\nintersect line O P1 line J t\npoint i 0 F\nline J F\ndelete line J t\ndelete point t\n\n:Create circle with diameter F P1\ncircle F P1\ncircle P1 F\nintersect circle F P1 circle P1 F\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nintersect line b1 b2 line O P1\npoint i 0 t\ndelete circle F P1\ndelete circle P1 F\ndelete line b1 b2\ndelete point b1\ndelete point b2\ncircle t P1\nintersect circle t P1 line O B\npoint i 1 K\ndelete point t\ncircle E K\nintersect circle E K line O P1\npoint i 0 N4\n\n:Create perpendicular line through OP1 at N4\ncircle N4 E\nintersect circle N4 E line O P1\npoint i 0 t\ncircle t E\ncircle E t\nintersect circle t E circle E t\npoint i 1 b1\nline N4 b1\ndelete circle t E\ndelete circle E t\ndelete circle N4 E\ndelete point t\nextend N4 b1\n\n:Perpendicular line at N4 cuts circle O P1 at P4\nintersect circle O P1 line N4 b1\npoint i 1 P4\nline N4 P4\ndelete line N4 b1\ndelete point b1\n\n:Delete artifacts\ndelete circle t P1\ndelete circle E K\ndelete point K\ndelete line J F\ndelete point F\ndelete line J E\ndelete point E\ndelete line J P1\ndelete point J\ndelete line N4 P4\ndelete point N4\ndelete line O B\ndelete point B\n\n:Create remaining 15 points using P1/P4 as initial radius\ncircle P4 P1\nintersect circle O P1 circle P4 P1\npoint i 0 P7\ndelete circle P4 P1\ncircle P7 P4\nintersect circle O P1 circle P7 P4\npoint i 0 P10\ndelete circle P7 P4\ncircle P10 P7\nintersect circle O P1 circle P10 P7\npoint i 0 P13\ndelete circle P10 P7\ncircle P13 P10\nintersect circle O P1 circle P13 P10\npoint i 0 P16\ndelete circle P13 P10\ncircle P16 P13\nintersect circle O P1 circle P16 P13\npoint i 0 P2\ndelete circle P16 P13\ncircle P2 P16\nintersect circle O P1 circle P2 P16\npoint i 0 P5\ndelete circle P2 P16\ncircle P5 P2\nintersect circle O P1 circle P5 P2\npoint i 0 P8\ndelete circle P5 P2\ncircle P8 P5\nintersect circle O P1 circle P8 P5\npoint i 0 P11\ndelete circle P8 P5\ncircle P11 P8\nintersect circle O P1 circle P11 P8\npoint i 0 P14\ndelete circle P11 P8\ncircle P14 P11\nintersect circle O P1 circle P14 P11\npoint i 0 P17\ndelete circle P14 P11\ncircle P17 P14\nintersect circle O P1 circle P17 P14\npoint i 0 P3\ndelete circle P17 P14\ncircle P3 P17\nintersect circle O P1 circle P3 P17\npoint i 0 P6\ndelete circle P3 P17\ncircle P6 P3\nintersect circle O P1 circle P6 P3\npoint i 0 P9\ndelete circle P6 P3\ncircle P9 P6\nintersect circle O P1 circle P9 P6\npoint i 0 P12\ndelete circle P9 P6\ncircle P12 P9\nintersect circle O P1 circle P12 P9\npoint i 0 P15\ndelete circle P12 P9\n\n:Connect the dots\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P5\nline P5 P6\nline P6 P7\nline P7 P8\nline P8 P9\nline P9 P10\nline P10 P11\nline P11 P12\nline P12 P13\nline P13 P14\nline P14 P15\nline P15 P16\nline P16 P17\nline P17 P1\ndelete circle O P1\ndelete line O P1\ndelete point O"; } function hep2() { document.getElementById('ta').value = ":Heptadecagon, Carlyle circle method\npoint 320 240 O\npoint 440 240 P0\ncircle O P0\nline O P0\nextend O P0\nintersect circle O P0 line O P0\npoint i 1 Q\ncircle Q P0\ncircle P0 Q\nintersect circle Q P0 circle P0 Q\npoint i 0 b1\nline b1 O\ndelete circle Q P0\ndelete circle P0 Q\nintersect circle O P0 line b1 O\npoint i 1 A\nline A O\nextend A O\ndelete line b1 O\ndelete point b1\ncircle Q O\nintersect circle O P0 circle Q O\npoint i 1 b1\npoint i 0 b2\nline b1 b2\nintersect line O P0 line b1 b2\npoint i 0 Q'\ndelete point b1\ndelete point b2\ndelete circle Q O\ncircle Q' P0\nintersect circle Q' P0 line b1 b2\npoint i 0 M0\ndelete line b1 b2\ndelete circle Q' P0\nline Q' M0\ncircle M0 A\nintersect circle M0 A line O P0\npoint i 0 H0,2\npoint i 1 H1,2\ndelete circle M0 A\ndelete line Q' M0\ndelete point Q'\ndelete point M0\ncircle O H1,2\ncircle H1,2 O\nintersect circle O H1,2 circle H1,2 O\npoint i 1 b1\npoint i 0 b2\nline b1 b2\ndelete circle O H1,2\ndelete circle H1,2 O\ndelete point H1,2\nintersect line b1 b2 line O P0\npoint i 0 M1,2\ncircle O H0,2\ncircle H0,2 O\nintersect circle O H0,2 circle H0,2 O\npoint i 1 b1\npoint i 0 b2\nline b1 b2\ndelete circle O H0,2\ndelete circle H0,2 O\ndelete point H0,2\nintersect line b1 b2 line O P0\npoint i 0 M0,2\ndelete point b1\ndelete point b2\ndelete line b1 b2\ncircle M0,2 A\nintersect circle M0,2 A line O P0\npoint i 0 H0,4\ndelete circle M0,2 A\ndelete point M0,2\ncircle M1,2 A\nintersect circle M1,2 A line O P0\npoint i 0 H1,4\ndelete circle M1,2 A\ndelete point M1,2\ncircle O H1,4\ncircle H1,4 O\nintersect circle O H1,4 circle H1,4 O\npoint i 0 F\nline F O\nline F H1,4\ndelete circle O H1,4\ndelete circle H1,4 O\nextend F O\nextend F H1,4\ncircle H1,4 Q\nintersect circle H1,4 Q line F H1,4\npoint i 0 G\ncircle F G\ndelete circle H1,4 Q\nintersect circle F G line F O\npoint i 0 L\ndelete circle F G\ndelete point G\ndelete line F H1,4\ndelete point F\ncircle O L\nintersect circle O L line A O\npoint i 1 Y\ndelete line F O\ndelete circle O L\ndelete point L\nline Y H0,4\ncircle Y H0,4\ncircle H0,4 Y\nintersect circle Y H0,4 circle H0,4 Y\npoint i 0 b1\npoint i 1 b2\nline b1 b2\ndelete circle Y H0,4\ndelete circle H0,4 Y\nintersect line Y H0,4 line b1 b2\npoint i 0 M0,4\ndelete line b1 b2\ndelete point b1\ndelete point b2\ncircle M0,4 A\nintersect circle M0,4 A line O P0\npoint i 0 H0,8\ndelete circle M0,4 A\ndelete point M0,4\ndelete line Y H0,4\ndelete point H0,4\ndelete point H1,4\ndelete point Y\ndelete point Q\ndelete point A\ncircle P0 H0,8\ncircle H0,8 P0\nintersect circle P0 H0,8 circle H0,8 P0\npoint i 0 F\nline F P0\nline F H0,8\ndelete circle P0 H0,8\ndelete circle H0,8 P0\nextend F P0\nextend F H0,8\nintersect circle O P0 line F P0\npoint i 0 G\ncircle F G\nintersect circle F G line F H0,8\npoint i 0 L\ndelete circle F G\ndelete point G\ndelete line F P0\ncircle H0,8 L\nintersect circle O P0 circle H0,8 L\npoint i 0 P1\npoint i 1 P16\ndelete circle H0,8 L\ndelete point L\ndelete point F\ndelete line F H0,8\ndelete point H0,8\ndelete line A O\ndelete line O P0\ndelete point O\ncircle P1 P0\nintersect circle O P0 circle P1 P0\npoint i 0 P2\ndelete circle P1 P0\ncircle P2 P1\nintersect circle O P0 circle P2 P1\npoint i 0 P3\ndelete circle P2 P1\ncircle P3 P2\nintersect circle O P0 circle P3 P2\npoint i 0 P4\ndelete circle P3 P2\ncircle P4 P3\nintersect circle O P0 circle P4 P3\npoint i 0 P5\ndelete circle P4 P3\ncircle P5 P4\nintersect circle O P0 circle P5 P4\npoint i 0 P6\ndelete circle P5 P4\ncircle P6 P5\nintersect circle O P0 circle P6 P5\npoint i 0 P7\ndelete circle P6 P5\ncircle P7 P6\nintersect circle O P0 circle P7 P6\npoint i 0 P8\ndelete circle P7 P6\ncircle P8 P7\nintersect circle O P0 circle P8 P7\npoint i 0 P9\ndelete circle P8 P7\ncircle P9 P8\nintersect circle O P0 circle P9 P8\npoint i 0 P10\ndelete circle P9 P8\ncircle P10 P9\nintersect circle O P0 circle P10 P9\npoint i 0 P11\ndelete circle P10 P9\ncircle P11 P10\nintersect circle O P0 circle P11 P10\npoint i 0 P12\ndelete circle P11 P10\ncircle P12 P11\nintersect circle O P0 circle P12 P11\npoint i 0 P13\ndelete circle P12 P11\ncircle P13 P12\nintersect circle O P0 circle P13 P12\npoint i 0 P14\ndelete circle P13 P12\ncircle P14 P13\nintersect circle O P0 circle P14 P13\npoint i 0 P15\ndelete circle P14 P13\nline P0 P1\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P5\nline P5 P6\nline P6 P7\nline P7 P8\nline P8 P9\nline P9 P10\nline P10 P11\nline P11 P12\nline P12 P13\nline P13 P14\nline P14 P15\nline P15 P16\nline P16 P0\ndelete circle O P0\n"; } function triangle() { document.getElementById('ta').value = ":Draw origin and P0, circle, and diameter\npoint 320 300 O\npoint 490 300 P0\ncircle O P0\nline O P0\nextend O P0\n\n:Find perpendicular bisector of diameter\nintersect circle O P0 line O P0\npoint i 1 tmp\ncircle tmp P0\ncircle P0 tmp\nintersect circle tmp P0 circle P0 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P0 line b1 O\npoint i 1 B\ndelete circle tmp P0\ndelete circle P0 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n\n:Circle BO cuts circle O P0 at top two points of triangle\ncircle B O\nintersect circle O P0 circle B O\npoint i 0 P1\npoint i 1 P2\nline P1 P2\ndelete circle B O\n\n:Extend line OB to other end of circle to find final point of triangle\nextend O B\nintersect circle O P0 line O B\npoint i 0 P3\nline P1 P3\nline P3 P2\ndelete line O B\ndelete point B\ndelete line O P0\ndelete point P0\ndelete point O\ndelete circle O P0"; } function b1p1() { document.getElementById('ta').value = ":Book I, Proposition I, to describe an equilateral triangle upon a given finite straight line\n:Let AB be the given straight line. It is required to describe an equilateral triangle upon AB\npoint 270 240 A\npoint 370 240 B\nline A B\n:From the center A at the distance AB, describe a circle\ncircle A B\n:From the center B at the distance BA, describe a circle\ncircle B A\n:Call the topmost intersection of the two circles point C\nintersect circle A B circle B A\npoint i 0 C\n:Draw the straight lines CA, CB to the points A, B\nline C A\nline C B\n:Then ABC shall be an equilateral triangle\nhighlight line C A\nhighlight line A B\nhighlight circle A B\n:The circle with center A has radius lines AB and CA which are equal\ncircle A B\nline C A\nhighlight line C B\nhighlight circle B A\n:and the circle with center B has radius lines AB and CB which are equal\ncircle B A\nhighlight line C A\n:Therefore AB = CB = CA, and triangle ABC is equilateral\n"; } function b1p2() { document.getElementById('ta').value = ":Book I, Proposition II. From a given point, to draw a straight line equal to the given straight line\n:Let A be the given point, and BC the given straight line. It is required to draw from the point A a straight line equal to BC\npoint 320 260 A\npoint 270 300 B\npoint 170 300 C\nline B C\n:From the point A to B draw the straight line AB\nline A B\n:Upon AB describe the equilateral triangle ABD\ncircle A B\ncircle B A\nintersect circle A B circle B A\npoint i 1 D\nline D A\nline D B\ndelete circle A B\ndelete circle B A\n:Extend lines DA and DB\nextend D A\nextend D B\n:From the center B at the distance BC, describe a circle, cutting line DB at G\ncircle B C\nintersect circle B C line D B\npoint i 0 G\ndelete line D B\nline D G\n:From the center D at the distance DG, describe a circle, cutting line DA at L\ncircle D G\nintersect circle D G line D A\npoint i 0 L\ndelete line D A\nline D L\nhighlight circle B C\nhighlight line B C\nline B G\nhighlight line B G\n:Circle with center B has radius lines BC and BG, which are equal\ncircle B C\nline B C\nline B G\nhighlight circle D G\nhighlight line D L\nhighlight line D G\n:Circle with center D has radius lines DL and DG, which are equal\ncircle D G\nline D L\nline D G\nline D A\nline D B\nhighlight line D A\nhighlight line D B\n:Lines DA and DB are equal, being legs of the equilateral triangle\ndelete line D A\ndelete line D B\nline A L\nhighlight line A L\nhighlight line B G\n:Therefore the remainders AL and BG are equal\nhighlight line B C\n:Therefore BC = BG = AL\n"; } function b1p3() { document.getElementById('ta').value = ":Book I, Proposition III. From the greater of two given straight lines to cut off a part equal to the less\n:Let AB and C1C2 be the two given straight lines, of which AB is the greater\npoint 320 240 A\npoint 470 240 B\nline A B\npoint 250 200 C1\npoint 250 300 C2\nline C1 C2\n:From the point A draw the straight line AD equal to C1C2\nline A C1\ncircle A C1\ncircle C1 A\nintersect circle A C1 circle C1 A\npoint i 1 t\nline t C1\nline t A\ndelete circle A C1\ndelete circle C1 A\nextend t A\nextend t C1\ncircle C1 C2\nintersect circle C1 C2 line t C1\npoint i 0 G\ncircle t G\nintersect circle t G line t A\npoint i 0 D\ndelete circle t G\ndelete circle C1 C2\ndelete point G\ndelete line t C1\nline A D\ndelete line t A\ndelete point t\ndelete line A C1\n:From the center A at the distance AD, describe a circle cutting line A B at point E\ncircle A D\nintersect circle A D line A B\npoint i 0 E\nline A E\nhighlight line A D\nhighlight line C1 C2\n:From bk I prop II, line AD equals line C1C2\nline C1 C2\nhighlight circle A D\nhighlight line A E\n:Circle with center A has radius points AD and AE, which are equal\ncircle A D\nhighlight line C1 C2\n:Therefore C1C2 = AD = AE\n"; } function b1p9() { document.getElementById('ta').value = ":Book I, Proposition IX - To bisect a given rectilineal angle, that is, to divide it into two equal angles\n:Let BAC be the given rectilineal angle. It is required to bisect it\npoint 320 150 A\npoint 280 240 B\npoint 370 250 C\nline A B\nline A C\n:Create point D on AB and point E on AC of equal length\npoint 370 165 t\nline A t\ncircle A t\nintersect circle A t line A B\npoint i 0 D\nintersect circle A t line A C\npoint i 0 E\ndelete line A t\ndelete point t\ndelete circle A t\n:Describe the equilateral triangle DEF, pointed away from A\nline D E\ncircle D E\ncircle E D\nintersect circle D E circle E D\npoint i 1 F\nline F D\nline F E\ndelete circle D E\ndelete circle E D\nline A F\n:The straight line AF shall bisect the angle BAC\nline A D\nline A E\nhighlight line A D\nhighlight line A E\nhighlight line A F\n:Because AD equals AE, and AF is common to the two triangles DAF, EAF\ndelete line A D\ndelete line A E\nline A F\nline D F\nline E F\nhighlight line D F\nhighlight line E F\n:And the base DF is equal to the base EF\nline D F\nline E F\nhighlight line A B\nhighlight line A C\nhighlight line A F\n:Therefore the angle DAF is equal to the angle EAF, wherefor the angle BAC is bisected by AF\n"; } function b1p10() { document.getElementById('ta').value = ":Book I, Proposition X - To bisect a given finite straight line, that is, to divide it into two equal parts\n:Let AB be the given straight line\npoint 270 240 A\npoint 370 240 B\nline A B\n:Upon AB describe the equilateral triangle ABC\ncircle A B\ncircle B A\nintersect circle A B circle B A\npoint i 0 C\ndelete circle A B\ndelete circle B A\nline C A\nline C B\n:Bisect the angle ACB by the straight line CD, meeting AB in the point D\ncircle A B\ncircle B A\nintersect circle A B circle B A\npoint i 1 t\nline t A\nline t B\ndelete circle A B\ndelete circle B A\nline C t\nintersect line C t line A B\npoint i 0 D\nline C D\ndelete line C t\ndelete line t A\ndelete line t B\ndelete point t\n:Then AB shall be cut into two equal parts in the point D.\nhighlight line C A\nhighlight line C B\nhighlight line C D\n:Because AC = CB, and CD is common to the two triangles ACD, BCD; and the angle ACD is equal to BCD;\nline C A\nline C B\nline C D\nhighlight line A B\n:Therefore the base AD = the base BD, wherefore the straight line AB is divided into two equal parts in the point D.\n"; } function b1p11() { document.getElementById('ta').value = ":Book I, Proposition XI - To draw a straight line at right angles to a given straight line, from a given point in the same.\n:Let AB be a line, and C a given point in it. It is required to draw a straight line from the point C at right angles to point AB\npoint 150 240 A\npoint 390 240 B\npoint 300 240 C\nline A B\n:In AC, take any point D and make CE equal to CD\ncircle A C\ncircle C A\nintersect circle A C circle C A\npoint i 0 t\npoint i 1 z\nline t z\nintersect line A B line t z\npoint i 0 D\ndelete line t z\ndelete point t\ndelete point z\ndelete circle A C\ndelete circle C A\ncircle C D\nintersect circle C D line A B\npoint i 0 E\ndelete circle C D\n:Upon DE describe the equilateral triangle DEF, and join CF\ncircle D E\ncircle E D\nintersect circle D E circle E D\npoint i 0 F\nline F D\nline F E\ndelete circle D E\ndelete circle E D\nline F C\nline D C\nline E C\nhighlight line D C\nhighlight line E C\nhighlight line F C\n:Then CF shall be at right angles to AB. Because DC is equal to EC, and FC is common to the two triangles DCF, ECF;\nline F C\nline D C\nline E C\nhighlight line F D\nhighlight line F E\n:And the base DF is equal to the base EF, therefore DCF = ECF:\nline F D\nline F E\nhighlight line D C\nhighlight line E C\nhighlight line F C\n:...and since DCF and ECF are equal, adjacent angles, on the same line, they are both right angles\n"; } function inscribed_triangle() { ":Inscribe an equilateral triangle in an existing circle\npoint 270 240 O\npoint 370 240 P0\n:Let O be the circle's center, and P0 be any point on the circle's edge\ncircle O P0\n:Draw a line from O to P0, and extend it to the circle's other side to find Q\nline O P0\nextend O P0\nintersect circle O P0 line O P0\npoint i 1 Q\n:Draw a circle from center Q with radius O\ncircle Q O\n:The intersection points between the two circles will be the triangle's other two points\nintersect circle O P0 circle Q O\npoint i 1 P1\npoint i 0 P2\n:Delete debris, connect the dots\ndelete circle Q O\ndelete point Q\ndelete line O P0\nline P0 P1\nline P1 P2\nline P2 P0\n"; } function euclid() { document.getElementById('ta').value = ":Pentagon, Euclid's method. Create line F G, divided at C so that FC/CG = the golden ratio\npoint 150 200 F\npoint 150 275 G\nline F G\ncircle F G\nextend F G\nintersect circle F G line F G\npoint i 1 t\ncircle t G\ncircle G t\nintersect circle t G circle G t\npoint i 0 u\nline F u\ndelete circle t G\ndelete circle G t\ndelete point t\nintersect circle F G line F u\npoint i 0 C\ndelete point u\ndelete line F u\ndelete circle F G\nline F C\ndelete circle F G\nline F G\ncircle F C\ncircle C F\nintersect circle F C circle C F\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nintersect line F C line b1 b2\npoint i 0 E\ndelete point b1\ndelete point b2\ndelete line b1 b2\ndelete circle F C\ndelete circle C F\nline G E\ncircle E G\nextend F C\nintersect circle E G line F C\npoint i 1 I\ndelete circle E G\ncircle F I\nintersect circle F I line F G\npoint i 0 C\ndelete circle F I\ndelete point E\ndelete line G E\ndelete line F C\ndelete point I\n:Construct golden triangle FGH\ncircle F G\ncircle C G\ncircle G C\nintersect circle C G circle G C\npoint i 1 t\nline t C\nline t G\nextend t C\nextend t G\ndelete circle C G\ndelete circle G C\ncircle C F\nintersect circle C F line t C\npoint i 1 u\ndelete circle C F\ncircle t u\nintersect circle t u line t G\npoint i 0 v\ndelete circle t u\ndelete line t C\ndelete point u\ncircle G v\nintersect circle F G circle G v\npoint i 0 H\ndelete circle G v\ndelete point v\ndelete line t G\ndelete point t\nline G H\nline H F\ndelete point C\ndelete circle F G\n:Construct circle which will contain inscribed pentagon, and tangent line LAN\npoint 400 290 O\npoint 500 290 R\npoint 300 190 L\ncircle O R\nline O L\ncircle O L\nintersect circle O R line O L\npoint i 1 t\ncircle t L\nintersect circle t L line O L\npoint i 0 u\ndelete circle t L\ncircle L u\ncircle u L\nintersect circle L u circle u L\npoint i 0 b1\nline t b1\nextend t b1\nintersect circle O L line t b1\npoint i 1 v\ndelete line b1 b2\ndelete point b1\ndelete point b2\ndelete circle L u\ndelete circle u L\ndelete point u\ndelete line t b1\nline t v\nline O v\nintersect circle O R line O v\npoint i 1 A\nline L A\nextend L A\nintersect circle O L line L A\npoint i 0 N\ndelete line L A\nline L N\ndelete circle O L\ndelete line t v\ndelete line O v\ndelete point v\ndelete line O L\ndelete point t\ndelete point R\n:Create angle NAD equal to FGH\nline H A\ncircle H A\ncircle A H\nintersect circle H A circle A H\npoint i 1 b\nline b H\nline b A\nextend b H\nextend b A\ndelete circle H A\ndelete circle A H\ncircle H F\nintersect circle H F line b H\npoint i 0 c\ncircle b c\nintersect circle b c line b A\npoint i 1 d\ncircle A d\nintersect circle A d line L N\npoint i 1 M\ndelete circle A d\ndelete point c\ndelete point d\ndelete circle b c\ndelete circle H F\ncircle H G\nintersect circle H G line b H\npoint i 0 c\ncircle b c\ndelete circle H G\nintersect circle b c line b A\npoint i 1 d\ncircle A d\ndelete circle b c\ndelete line b c\ndelete point c\ndelete line b H\ncircle M A\nintersect circle M A circle A d\npoint i 0 e\ndelete line H A\ndelete line b A\ndelete point b\nline A e\ndelete circle M A\nextend A e\nintersect circle O R line A e\npoint i 0 D\ndelete point e\ndelete line A e\nline A D\n:Create angle LAC equal to angle FHG\ncircle A M\nintersect circle A M line L N\npoint i 0 b\ndelete circle A M\ncircle b A\nintersect circle b A circle A d\npoint i 1 e\ndelete circle A d\ndelete point d\ndelete circle b A\ndelete point b\ndelete point M\nline A e\nextend A e\nintersect circle O R line A e\npoint i 0 C\ndelete point e\ndelete line A e\nline A C\n:Connect C to D to create inscribed golden triangle, bisect larger angles\nline C D\ndelete line L N\ndelete point L\ndelete point N\ncircle C D\nintersect circle C D line A C\npoint i 1 t\ncircle t C\ncircle D C\nintersect circle t C circle D C\npoint i 0 E\nline C E\ndelete circle t C\nintersect circle D C line A D\npoint i 1 t\ndelete circle D C\ncircle t D\nintersect circle t D circle C D\npoint i 1 B\nline D B\ndelete circle C D\ndelete circle t D\ndelete point t\n:Remove debris, connect the dots\ndelete point O\ndelete line C E\ndelete line D B\ndelete line A C\ndelete line A D\nline A B\nline B C\nline D E\nline E A\n"; } function phi() { document.getElementById('ta').value = ":Pentagon, Phi method. Start by creating unit circle at O with diameter Q-P0\npoint 320 240 O\npoint 420 240 P0\ncircle O P0\nline O P0\nextend O P0\nintersect circle O P0 line O P0\npoint i 1 Q\n:Create line AO at circle's top, perpendicular to diameter line\ncircle Q P0\ncircle P0 Q\nintersect circle Q P0 circle P0 Q\npoint i 0 b1\nline b1 O\nintersect circle O P0 line b1 O\npoint i 1 A\nline A O\ndelete circle Q P0\ndelete circle P0 Q\ndelete line b1 O\ndelete point b1\n:Create circle with center B at midpoint of AO, with diameter AO\ncircle A O\nintersect circle A O circle O P0\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nintersect line A O line b1 b2\npoint i 0 B\ndelete circle A O\ndelete line A O\ndelete line b1 b2\ndelete point b1\ndelete point b2\ncircle B A\n:Connect Q to B, extend, and find intersection points M and N in smaller circle\nline Q B\nextend Q B\nintersect circle B A line Q B\npoint i 0 M\npoint i 1 N\nline Q N\ndelete line Q B\ndelete point B\n:Draw circles from center Q to radius M and N. These intersect unit circle at pentagon's vertex points \ncircle Q M\ncircle Q N\ndelete circle B A\ndelete point A\nintersect circle O P0 circle Q N\npoint i 1 P1\npoint i 0 P4\nintersect circle O P0 circle Q M\npoint i 1 P2\npoint i 0 P3\n:Clear artifacts, connect the dots\ndelete line Q N\ndelete point M\ndelete point N\ndelete circle Q M\ndelete circle Q N\ndelete point Q\nline P0 P1\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P0\ndelete circle O P0\ndelete point O\n"; } function cosine() { document.getElementById('ta').value = ":Pentagon, Cosine method. Draw unit circle with origin O and radius point P0 on x axis\npoint 320 240 O\npoint 420 240 P0\ncircle O P0\nline O P0\nextend O P0\n:Find perpendicular bisector of diameter\nintersect circle O P0 line O P0\npoint i 1 tmp\ncircle tmp P0\ncircle P0 tmp\nintersect circle tmp P0 circle P0 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P0 line b1 O\npoint i 1 B\ndelete circle tmp P0\ndelete circle P0 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n:Bisect line OB at point D, connect to P0\ncircle B O\nintersect circle B O circle O P0\npoint i 1 b1\npoint i 0 b2\nline b1 b2\nintersect line O B line b1 b2\npoint i 0 D\ndelete circle B O\ndelete line b1 b2\ndelete point b1\ndelete point b2\nline D P0\n:Bisect angle ODP0, connect to line OP0 as N2\ncircle D O\nintersect circle D O line D P0\npoint i 0 tmp\ndelete circle D O\ncircle O D\ncircle tmp D\nintersect circle O D circle tmp D\npoint i 1 t\nline D t\ndelete circle O D\ndelete circle tmp D\ndelete point tmp\nintersect line O P0 line D t\npoint i 0 N2\nline D N2\ndelete line D t\ndelete point t\n:Line at N2 perpendicular to line OP0 is point P1\ncircle N2 O\nintersect circle N2 O line O P0\npoint i 0 t\ncircle O t\ncircle t O\ndelete circle N2 O\nintersect circle O t circle t O\npoint i 0 b1\nline b1 N2\nextend b1 N2\ndelete circle O t\ndelete circle t O\ndelete point t\nintersect circle O P0 line b1 N2\npoint i 1 P1\ndelete point b1\nline N2 P1\ndelete line b1 N2\n:Delete artifacts\ndelete line N2 P1\ndelete line D N2\ndelete point N2\ndelete line D P0\ndelete point D\ndelete line O B\ndelete point B\n:Create remaining 3 points using P0/P1 as initial radius\ncircle P1 P0\nintersect circle O P0 circle P1 P0\npoint i 0 P2\ndelete circle P1 P0\ncircle P2 P1\nintersect circle O P0 circle P2 P1\npoint i 0 P3\ndelete circle P2 P1\ncircle P3 P2\nintersect circle O P0 circle P3 P2\npoint i 0 P4\ndelete circle P3 P2\n:Connect the dots\nline P0 P1\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P0\ndelete line O P0\ndelete circle O P0\ndelete point O\n"; } function carlyle() { document.getElementById('ta').value = ":Pentagon, Carlyle circle method\npoint 320 240 O\npoint 420 240 P0\ncircle O P0\nline O P0\nextend O P0\nintersect circle O P0 line O P0\npoint i 1 Q\ncircle Q P0\ncircle P0 Q\nintersect circle Q P0 circle P0 Q\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nintersect circle O P0 line b1 b2\npoint i 1 A\ndelete circle Q P0\ndelete circle P0 Q\ncircle Q O\nintersect circle O P0 circle Q O\npoint i 1 b1\npoint i 0 b2\nline b1 b2\nintersect line b1 b2 line O P0\npoint i 0 M\ndelete point b1\ndelete point b2\ndelete line b1 b2\ndelete circle Q O\ncircle M A\nintersect circle M A line O P0\npoint i 0 H0\npoint i 1 H1\ndelete circle M A\ncircle Q H1\ncircle H1 Q\nintersect circle Q H1 circle H1 Q\npoint i 1 F\ndelete circle Q H1\ndelete circle H1 Q\nline F Q\nline F H1\nextend F Q\nextend F H1\ncircle Q O\nintersect circle O P0 circle Q O\npoint i 0 G\ndelete circle Q O\ncircle F G\nintersect circle F G line F H1\npoint i 0 L\ndelete circle F G\ndelete point F\ndelete point G\ndelete line F Q\ncircle H1 L\ndelete line F H1\ndelete point L\nintersect circle O P0 circle H1 L\npoint i 1 P2\npoint i 0 P3\ndelete circle H1 L\ncircle P2 P3\nintersect circle O P0 circle P2 P3\npoint i 1 P1\ndelete circle P2 P3\ncircle P3 P2\nintersect circle O P0 circle P3 P2\npoint i 0 P4\ndelete circle P3 P2\ndelete point H0\ndelete point H1\ndelete point M\ndelete point A\ndelete point Q\ndelete point O\nline P0 P1\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P0\ndelete circle O P0\n"; } function promptBoxClosures() { var fullScreenBox = document.createElement('div'); var fs = fullScreenBox.style; fs.zIndex = highestZ; fs.position = "fixed"; fs.top = "0px"; fs.left = "0px"; fs.width = "100%"; fs.height = "100%"; fs.opacity = "0.7"; fs.filter = "alpha(opacity=70)"; fs.background = "black"; fs.border = "0px"; fs.display = "none"; fullScreenBox.innerHTML = '<div class="close"><a style="background:#E4E4E4;padding:0 5px;" href="#delay" onclick=closePromptBox()>Close</a></div>'; document.body.appendChild(fullScreenBox); var promptBox = document.createElement('div'); var ps = promptBox.style; ps.display = "none" ps.position = "fixed" ps.top = "10%" ps.left = "10%" ps.width = "80%" ps.height = "80%" ps.padding = "10px" ps.zIndex = highestZ + 1; document.body.appendChild(promptBox); openPromptBox = function() { fs.display = 'inline'; ps.display = 'inline'; } closePromptBox = function() { clear(); fs.display = 'none'; ps.display = 'none'; } setPrompt = function(html) { promptBox.innerHTML = html; } setPrompt('<canvas width=640 height=480 id="cnv"></canvas>'); } init(); </script>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0tag:blogger.com,1999:blog-11885757.post-90240294640916266382012-05-18T23:05:00.000-04:002013-01-07T11:18:57.390-05:00Compass and Straightedge geometry meets HTML5<p>This is a proof of concept build of an HTML5 engine to perform classical Greek compass and straightedge calculations, as described in <a href="http://en.wikipedia.org/wiki/Compass_and_straightedge_constructions">this Wikipedia article</a>. In brief, an initial set of points can be created, and then points can be connected by lines, circles can be drawn with one point as center and another as one point in the circumference, and finally the points where circles and/or lines intersect can be added as new points. With those simple rules, angles and line segments can be bisected, and certain regular (equilateral) shapes can be created. </p><p>I have created a rudimentary language to describe the adding of points, lines, and circles, and finding their intersections, which I discuss briefly after the demo below. Without further ado, choose a sample "program" to run to bisect an angle or create a regular shape. If you're a fan of Gauss, be sure to check out the heptadecagon. </p><a name='more'></a>Display area<br /><canvas width=640 height=480 id="cnv"></canvas><br /><input type=text id=delay value=250 size=4 /> Frame delay (ms)<br /><input type=button onclick=run() value=Run /><br /><br />Samples<br /><a href=javascript:bisect()>Bisect an angle</a><br /><a href=javascript:triangle()>Construct a regular triangle</a><br /><a href=javascript:pentagon()>Pentagon</a><br /><a href=javascript:hep()>Heptadecagon</a><br />Instructions<br /><textarea id="ta"></textarea> <style type="text/css">canvas { border: 1px solid black; display: block; background:#FFF; } textarea { display: block; width:640px; height:480px; border: 1px solid black; padding: 0; } </style> <script type="text/javascript"> // ****************************** // Url, uryc zr fraq zl avrpr gb pbyyrtr! uggc://pnhgrel.oybtfcbg.pbz/c/crney.ugzy // ****************************** // Things I'm allowed to do: // http://en.wikipedia.org/wiki/File:Basic-construction-demo.png var cnv, ctx; // Canvas, 2d context var cr = Math.PI * 2; var Type = {POINT : 0, LINE : 1, CIRCLE : 2}; var col, sm, interval, label; var pointRadius = 3; var textOffsetX = 5; var textOffsetY = -5; var canvasBounds = {}; function Point(x, y) { this.x = x; this.y = y; this.type = Type.POINT; this.draw = function() { ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.arc(this.x, this.y, pointRadius, 0, cr, false); ctx.fill(); } } function Circle(center, R) { // Add orientation stuff this.center = center; // a Point this.R = R; this.radius = dist(center, R); this.r2 = this.radius * this.radius; this.type = Type.CIRCLE; this.draw = function() { ctx.beginPath(); ctx.moveTo(this.center.x + this.radius, this.center.y); ctx.arc(this.center.x, this.center.y, this.radius, 0, cr, false); ctx.stroke(); } this.getCircleIntersect = function(circle) { var d = dist(this.center, circle.center); if (d > this.radius + circle.radius) { return new Intersect([], "Circles do not intersect"); } if (d < Math.abs(this.radius - circle.radius) || d == 0) { return new Intersect([], "One circle encloses the other"); } var a = (this.r2 - circle.r2 + d * d) / (2 * d); var h = Math.sqrt(this.r2 - a * a); var midX = this.center.x + a * (circle.center.x - this.center.x) / d; var midY = this.center.y + a * (circle.center.y - this.center.y) / d; var hyy = h * (circle.center.y - this.center.y) / d; var hxx = h * (circle.center.x - this.center.x) / d var points = [] points[0] = new Point(midX + hyy, midY - hxx); // if d = r1 + r2, circles are tangent, else add second intersect point if (d < this.radius + circle.radius) { points[1] = new Point(midX - hyy, midY + hxx); } return new Intersect(points, ""); } this.getLineIntersect = function(line) { var x1 = line.A.x - this.center.x; var x2 = line.B.x - this.center.x; var y1 = line.A.y - this.center.y; var y2 = line.B.y - this.center.y; var dx = x2 - x1; var dy = y2 - y1; var dr = Math.sqrt(dx * dx + dy * dy); var dr2 = dr * dr; var D = x1 * y2 - x2 * y1; var D2 = D * D; var sgn = dy < 0 ? -1 : 1; var disc = this.r2 * dr2 - D2; if (disc < 0) return new Intersect([], "Line does not intersect circle"); var points = []; var tmpSqrt = Math.sqrt(this.r2 * dr2 - D2); var x = (D * dy + sgn * dx * tmpSqrt)/ dr2 + this.center.x; var y = (-D * dx + Math.abs(dy) * tmpSqrt) / dr2 + this.center.y; points[0] = new Point(x, y); if (disc > 0) { x = (D * dy - sgn * dx * tmpSqrt)/ dr2 + this.center.x; y = (-D * dx - Math.abs(dy) * tmpSqrt) / dr2 + this.center.y; points[1] = new Point(x, y); } return new Intersect(points, ""); } } function Line(A, B) { this.A = A; // A and B are Points this.B = B; var extended = false; this.type = Type.LINE; this.extend = function() { if (extended) return; extended = true; // move points A and B to edge of canvas var i = this.getLineIntersect(canvasBounds.top); if (i.points.length == 0) i = this.getLineIntersect(canvasBounds.left); var j = this.getLineIntersect(canvasBounds.bottom); if (j.points.length == 0) j = this.getLineIntersect(canvasBounds.right); this.A = i.points[0]; this.B = j.points[0]; } this.draw = function() { ctx.beginPath(); ctx.moveTo(this.A.x, this.A.y); ctx.lineTo(this.B.x, this.B.y); ctx.stroke(); } this.getLineIntersect = function(line) { var x1 = this.A.x; var x2 = this.B.x; var x3 = line.A.x; var x4 = line.B.x; var y1 = this.A.y; var y2 = this.B.y; var y3 = line.A.y; var y4 = line.B.y; var denom = det(x1 - x2, y1 - y2, x3 - x4, y3 - y4); if (denom == 0) return new Intersect([], "Lines don't intersect"); var leftA = det(x1, y1, x2, y2); var leftC = det(x3, y3, x4, y4); var x = det(leftA, x1 - x2, leftC, x3 - x4) / denom; var y = det(leftA, y1 - y2, leftC, y3 - y4) / denom; return new Intersect([new Point(x, y)], ""); } } function det(a, b, c, d) { // Determinate in format |a b| // |c d| return a * d - b * c; } function dist(A, B) { var x = A.x - B.x; var y = A.y - B.y; return Math.sqrt(x * x + y * y); } function Intersect(points, message) { this.points = points; this.message = message; } function init() { if (interval != null) clearInterval(interval); cnv = document.getElementById('cnv'); var tl = new Point(0, 0); var tr = new Point(cnv.width, 0); var bl = new Point(0, cnv.height); var br = new Point(cnv.width, cnv.height); canvasBounds.top = new Line(tl, tr); canvasBounds.bottom = new Line(bl, br); canvasBounds.left = new Line(tl, bl); canvasBounds.right = new Line(tr, br); ctx = cnv.getContext('2d'); ctx.translate(.5, .5); ctx.strokeStyle = "black"; col = {}; sm = new StepManager(); bisect(); } function frame() { sm.frame(); } function drawBoard() { // Clear board ctx.clearRect(0, 0, cnv.width, cnv.height) // Draw current label, if any if (label != "") ctx.fillText(label, 20, 20); // Iterate through collection (col) of points, lines and circles, and call their draw() methods for (var e in col) { var elem = col[e]; elem.draw(); // ...and label the points if (elem.type == Type.POINT) { ctx.fillText(e, elem.x + textOffsetX, elem.y + textOffsetY); } } } function run() { col = {}; if (interval != null) clearInterval(interval); sm.init(document.getElementById('ta').value.split(/\n+/)); interval = setInterval(frame, document.getElementById('delay').value); } function StepManager() { var index, done, i, steps; this.init = function(s) { steps = s; i = null; index = 0; done = false; label = ""; drawBoard(); } function point(x, y, name) { y = parseInt(y); if (x == 'i') col[name] = i.points[y]; else col[name] = new Point(parseInt(x), y); } function line(a, b) { var name = '_ln_' + a + '_' + b; col[name] = new Line(col[a], col[b]); } function extend(a, b) { var name = '_ln_' + a + '_' + b; col[name].extend(); } function circle(a, b) { var name = '_cr_' + a + '_' + b; col[name] = new Circle(col[a], col[b]); } function getObj(type, a, b) { return col[nameFromElems(type, a, b)]; } function intersect(typeA, a, b, typeB, c, d) { var objA = getObj(typeA, a, b); var objB = getObj(typeB, c, d); if (typeB == "circle") i = objA.getCircleIntersect(objB); else i = objA.getLineIntersect(objB) } function nameFromElems(type, a, b) { if (type == "circle")return '_cr_' + a + '_' + b; if (type == "line") return '_ln_' + a + '_' + b; return a; } function deleteObj(type, a, b) { delete(col[nameFromElems(type, a, b)]); } function display(msg) { label = msg; } this.frame = function() { if (done == true) { clearInterval(interval); } else this.nextStep(); drawBoard(); } this.nextStep = function() { if (index >= steps.length) { done = true; display("done"); return; } var step = steps[index++]; if (step.charAt(0) == '#') this.nextStep(); else if (step.charAt(0) == ':') { display(step.substr(1)); } else { var elems = step.split(' '); switch(elems[0]) { case '': return; case 'point': point(elems[1], elems[2], elems[3]); break; case 'circle': circle(elems[1], elems[2]); break; case 'line': line(elems[1], elems[2]); break; case 'extend': extend(elems[1], elems[2]); break; case 'intersect': intersect(elems[1], elems[2], elems[3], elems[4], elems[5], elems[6]); this.nextStep(); break; case 'delete': deleteObj(elems[1], elems[2], elems[3]); break; default: display("Unknown step type: " + elems[0]); done = true; return; } } } } function bisect() { document.getElementById('ta').value = "#Bisect an angle\n:Draw angle ABC\npoint 280 100 A\npoint 320 240 B\npoint 420 240 C\nline A B\nline B C\n:Draw circle at B with C radius\ncircle B C\n:Mark intersect point of circle and line AB as D\nintersect circle B C line A B\npoint i 1 D\n:Draw circles DB and CB\ncircle D B\ncircle C B\n:Mark intersect point of two new circles (other than B) as E\nintersect circle D B circle C B\npoint i 0 E\n:Draw bisecting line BE\nline B E\nextend B E\n:Erase chaff\ndelete circle D B\ndelete circle C B\ndelete point D\ndelete circle B C\n"; } function hep() { document.getElementById('ta').value = "#Construct a regular 17-gon\n:Draw origin and P1, circle, and diameter\npoint 320 300 O\npoint 490 300 P1\ncircle O P1\nline O P1\nextend O P1\n\n:Find perpendicular bisector of diameter\nintersect circle O P1 line O P1\npoint i 1 tmp\ncircle tmp P1\ncircle P1 tmp\nintersect circle tmp P1 circle P1 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P1 line b1 O\npoint i 1 B\ndelete circle tmp P1\ndelete circle P1 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n\n:Create point J 1/4 of the way up OB\ncircle B O\nintersect circle B O circle O P1\npoint i 1 b1\npoint i 0 b2\nline b1 b2\nintersect line O B line b1 b2\npoint i 0 tmp\ndelete circle B O\ndelete line b1 b2\ndelete point b1\ndelete point b2\ncircle O tmp\ncircle tmp O\nintersect circle O tmp circle tmp O\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nline O tmp\nintersect line b1 b2 line O tmp\npoint i 0 J\ndelete circle O tmp\ndelete circle tmp O\ndelete point tmp\ndelete line b1 b2\ndelete point b1\ndelete point b2\ndelete line O tmp\n\n:Create line JP1 and find E on bisector line where OJE is 1/4 of OJP1\nline J P1\ncircle J O\nintersect circle J O line J P1\npoint i 0 tmp\ncircle O J\ncircle tmp J\nintersect circle O J circle tmp J\ndelete point tmp\npoint i 1 t\nline J t\ndelete circle tmp J\nintersect circle J O line J t\npoint i 0 tmp\nline J tmp\ndelete line J t\ndelete point t\ndelete circle J O\ncircle tmp J\nintersect circle O J circle tmp J\npoint i 1 t\nline J t\ndelete circle O J\ndelete circle tmp J\ndelete line J tmp\ndelete point tmp\nintersect line O P1 line J t\npoint i 0 E\nline J E\ndelete line J t\ndelete point t\n\n:create F on circle bisect line such that EJF is 45 degrees\ncircle J E\nextend J E\nintersect circle J E line J E\npoint i 1 tmp\ncircle tmp E\ncircle E tmp\nintersect circle tmp E circle E tmp\npoint i 1 t\nline J t\ndelete circle tmp E\ndelete circle E tmp\ndelete point tmp\nline J E\nintersect circle J E line J t\npoint i 0 tmp\ndelete line J t\ndelete point t\ncircle E J\ncircle tmp J\nintersect circle E J circle tmp J\npoint i 0 t\nline J t\ndelete circle E J\ndelete circle tmp J\ndelete point tmp\ndelete circle J E\nintersect line O P1 line J t\npoint i 0 F\nline J F\ndelete line J t\ndelete point t\n\n:Create circle with diameter F P1\ncircle F P1\ncircle P1 F\nintersect circle F P1 circle P1 F\npoint i 0 b1\npoint i 1 b2\nline b1 b2\nintersect line b1 b2 line O P1\npoint i 0 t\ndelete circle F P1\ndelete circle P1 F\ndelete line b1 b2\ndelete point b1\ndelete point b2\ncircle t P1\nintersect circle t P1 line O B\npoint i 1 K\ndelete point t\ncircle E K\nintersect circle E K line O P1\npoint i 0 N4\n\n:Create perpendicular line through OP1 at N4\ncircle N4 E\nintersect circle N4 E line O P1\npoint i 0 t\ncircle t E\ncircle E t\nintersect circle t E circle E t\npoint i 1 b1\nline N4 b1\ndelete circle t E\ndelete circle E t\ndelete circle N4 E\ndelete point t\nextend line N4 b1\n\n:Perpendicular line at N4 cuts circle O P1 at P4\nintersect circle O P1 line N4 b1\npoint i 1 P4\nline N4 P4\ndelete line N4 b1\ndelete point b1\n\n:Delete artifacts\ndelete circle t P1\ndelete circle E K\ndelete point K\ndelete line J F\ndelete point F\ndelete line J E\ndelete point E\ndelete line J P1\ndelete point J\ndelete line N4 P4\ndelete point N4\ndelete line O B\ndelete point B\n\n:Create remaining 15 points using P1/P4 as initial radius\ncircle P4 P1\nintersect circle O P1 circle P4 P1\npoint i 0 P7\ndelete circle P4 P1\ncircle P7 P4\nintersect circle O P1 circle P7 P4\npoint i 0 P10\ndelete circle P7 P4\ncircle P10 P7\nintersect circle O P1 circle P10 P7\npoint i 0 P13\ndelete circle P10 P7\ncircle P13 P10\nintersect circle O P1 circle P13 P10\npoint i 0 P16\ndelete circle P13 P10\ncircle P16 P13\nintersect circle O P1 circle P16 P13\npoint i 0 P2\ndelete circle P16 P13\ncircle P2 P16\nintersect circle O P1 circle P2 P16\npoint i 0 P5\ndelete circle P2 P16\ncircle P5 P2\nintersect circle O P1 circle P5 P2\npoint i 0 P8\ndelete circle P5 P2\ncircle P8 P5\nintersect circle O P1 circle P8 P5\npoint i 0 P11\ndelete circle P8 P5\ncircle P11 P8\nintersect circle O P1 circle P11 P8\npoint i 0 P14\ndelete circle P11 P8\ncircle P14 P11\nintersect circle O P1 circle P14 P11\npoint i 0 P17\ndelete circle P14 P11\ncircle P17 P14\nintersect circle O P1 circle P17 P14\npoint i 0 P3\ndelete circle P17 P14\ncircle P3 P17\nintersect circle O P1 circle P3 P17\npoint i 0 P6\ndelete circle P3 P17\ncircle P6 P3\nintersect circle O P1 circle P6 P3\npoint i 0 P9\ndelete circle P6 P3\ncircle P9 P6\nintersect circle O P1 circle P9 P6\npoint i 0 P12\ndelete circle P9 P6\ncircle P12 P9\nintersect circle O P1 circle P12 P9\npoint i 0 P15\ndelete circle P12 P9\n\n:Connect the dots\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P5\nline P5 P6\nline P6 P7\nline P7 P8\nline P8 P9\nline P9 P10\nline P10 P11\nline P11 P12\nline P12 P13\nline P13 P14\nline P14 P15\nline P15 P16\nline P16 P17\nline P17 P1\ndelete circle O P1\ndelete line O P1\ndelete point O"; } function triangle() { document.getElementById('ta').value = "#Construct an equilateral triangle\n:Draw origin and P0, circle, and diameter\npoint 320 300 O\npoint 490 300 P0\ncircle O P0\nline O P0\nextend O P0\n\n:Find perpendicular bisector of diameter\nintersect circle O P0 line O P0\npoint i 1 tmp\ncircle tmp P0\ncircle P0 tmp\nintersect circle tmp P0 circle P0 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P0 line b1 O\npoint i 1 B\ndelete circle tmp P0\ndelete circle P0 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n\n:Circle BO cuts circle O P0 at top two points of triangle\ncircle B O\nintersect circle O P0 circle B O\npoint i 0 P1\npoint i 1 P2\nline P1 P2\ndelete circle B O\n\n:Extend line OB to other end of circle to find final point of triangle\nextend O B\nintersect circle O P0 line O B\npoint i 0 P3\nline P1 P3\nline P3 P2\ndelete line O B\ndelete point B\ndelete line O P0\ndelete point P0\ndelete point O\ndelete circle O P0"; } function pentagon() { document.getElementById('ta').value = "#Construct a regular pentagon\n:Draw origin and P1, circle, and diameter\npoint 320 300 O\npoint 490 300 P1\ncircle O P1\nline O P1\nextend O P1\n\n:Find perpendicular bisector of diameter\nintersect circle O P1 line O P1\npoint i 1 tmp\ncircle tmp P1\ncircle P1 tmp\nintersect circle tmp P1 circle P1 tmp\npoint i 0 b1\nline b1 O\nintersect circle O P1 line b1 O\npoint i 1 B\ndelete circle tmp P1\ndelete circle P1 tmp\ndelete point tmp\ndelete line b1 O\ndelete point b1\nline O B\n\n:Bisect line OB at point D, connect to P1\ncircle B O\nintersect circle B O circle O P1\npoint i 1 b1\npoint i 0 b2\nline b1 b2\nintersect line O B line b1 b2\npoint i 0 D\ndelete circle B O\ndelete line b1 b2\ndelete point b1\ndelete point b2\nline D P1\n\n:Bisect angle ODP1, connect to line OP1 as N2\ncircle D O\nintersect circle D O line D P1\npoint i 0 tmp\ndelete circle D O\ncircle O D\ncircle tmp D\nintersect circle O D circle tmp D\npoint i 1 t\nline D t\ndelete circle O D\ndelete circle tmp D\ndelete point tmp\nintersect line O P1 line D t\npoint i 0 N2\nline D N2\ndelete line D t\ndelete point t\n\n:Line at N2 perpendicular to line OP1 is point P2\ncircle N2 O\nintersect circle N2 O line O P1\npoint i 0 t\ncircle O t\ncircle t O\ndelete circle N2 O\nintersect circle O t circle t O\npoint i 0 b1\nline b1 N2\nextend b1 N2\ndelete circle O t\ndelete circle t O\ndelete point t\nintersect circle O P1 line b1 N2\npoint i 1 P2\ndelete point b1\nline N2 P2\ndelete line b1 N2\n\n:Delete artifacts\ndelete line N2 P2\ndelete line D N2\ndelete point N2\ndelete line D P1\ndelete point D\ndelete line O B\ndelete point B\n\n:Create remaining 3 points using P1/P2 as initial radius\ncircle P2 P1\nintersect circle O P1 circle P2 P1\npoint i 0 P3\ndelete circle P2 P1\ncircle P3 P2\nintersect circle O P1 circle P3 P2\npoint i 0 P4\ndelete circle P3 P2\ncircle P4 P3\nintersect circle O P1 circle P4 P3\npoint i 0 P5\ndelete circle P4 P3\n\n:Connect the dots\nline P1 P2\nline P2 P3\nline P3 P4\nline P4 P5\nline P5 P1\ndelete line O P1\ndelete circle O P1\ndelete point O"; } init(); </script> <p class="section" style="clear:both">The language</p><ul><li />point [x] [y] [name] <br />This creates a point at canvas position [x,y], and labels it [name]. This is intended only to be used for creating starting points to be used by the remaining commands. In HTML5 canvases, point 0,0 is the upper-left corner, and numbers increase as you move right and down. <li />line [a] [b] <br />This draws a line between points a and b. <li />circle [a] [b] <br />This draws a circle centered at point a, with point b as a point on the circumference (line ab describes the circle's radius) <li />intersect [circle|line] [a] [b] [circle|line] [c] [d] <br />Identify's the intersection points between two circles, two lines, or a circle and a line (for this last combination, specify the circle first). This doesn't add any new points, it just identifies the intersections. A separate "point" command must follow an intersect command to add new points <li />point i [0|1] [a] <br />This creates a new point [a] based on the two possible results of the last "intersect" command. You will need to test whether intersect point 0 or 1 is the one you need. In fact, if you're building your own program, you'll want to do several tests as you go. I recommend setting the frame delay to 0 before testing to speed things up. <li />: [text] <br />Sets the current "label" in the display area, used for giving the audience an idea about what they're looking at. <li /># [comment] <br />Comments in the instructions that aren't shown in the display area </ul>Enjoy!Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com10tag:blogger.com,1999:blog-11885757.post-9672190855639532902012-05-10T21:32:00.000-04:002013-01-07T11:20:57.593-05:00Animated prime number machine<p>This uses the HTML5 canvas architecture, so no IE8 or less; sorry. I have only tested it with Google Chrome, so it may not work in other HTML5-capable browsers. If you find this to be the case, please leave feedback with any errors you see, and what browser version you're using, and I'll see what I can do to get it working for you. </p><p>The principle of the prime number machine is of rolling coins and elevators. I created this because I thought it would be neat to have a way for prime numbers to be produced by a set of mechanical rules rather than with pure mathematical methods. Here is a basic description of the rules: <ul><li />If a coin rolls into an empty box, it creates an elevator with the height shown on the coin, and a new empty box appears. <li />If a coin hits the top or bottom of an existing elevator, it bounces off with no effect. <li />The coin's number increments after each cycle <li />The elevators go down a floor during each cycle until the top car is flush with the ground line, then they begin going back up to where they started. </ul></p><p>This results in two basic phenomena: All new elevators correspond to prime numbers. When a coin does not have a prime number on it, all of the elevators corresponding to the coin's prime factors have a car flush with the ground. </p><p>Enjoy!</p><a name='more'></a><canvas width=640 height=480 id="cnv"></canvas> <style type="text/css">canvas { border: 1px solid black; margin: 0 auto; display: block; } </style> <script type="text/javascript"> // ****************************** // Url, uryc zr fraq zl avrpr gb pbyyrtr! uggc://pnhgrel.oybtfcbg.pbz/c/crney.ugzy // ****************************** var cnv, ctx; // Canvas, 2d context var elevatorLeft = 30; var columnWidth = 25; var barWidth = 20; var rowHeight = 25; var barHeight = 16; var textOffsetX = 9; var textOffsetY = 11; var bw2 = barWidth / 2; var speed = 1; var em = new ElevatorManager(); var mh = new ModeHandler(); var ball = new Ball(); var interval; var centerY, baseY; function init() { if (interval != null) clearInterval(interval); cnv = document.getElementById('cnv'); ctx = cnv.getContext('2d'); ctx.translate(.5, .5); ctx.strokeStyle = "black"; ctx.textAlign = "center"; centerY = cnv.height / 2; baseY = centerY - barHeight; mh.init(); ball.init(); em.init(); em.add(); interval = setInterval(frame, 0); // frame(); } function frame() { if (mh.isGameOver()) { clearInterval(interval); return; } mh.isModeDone() ? mh.nextMode() : mh.nextFrame(); drawBoard(); } function drawBoard() { // Clear board ctx.clearRect(0, 0, cnv.width, cnv.height) // Draw elevators em.draw(); // Draw ball ball.draw(); // Draw center line ctx.strokeRect(0, centerY, cnv.width, 0); } function ModeHandler() { // ROLL_BALL adds an elevator if nothing is blocking // MOVE_ELEVATORS starts with incrementing ballNum var Mode = { FIND_BALL_DEST : 0 , ROLL_BALL : 1 , ADD_ELEVATOR : 2 , INCREMENT_BALLNUM : 3 , MOVE_ELEVATORS : 4 }; var mode, gameOver, modeDone; var mode, gameOver, modeDonel this.init = function() { mode = Mode.FIND_BALL_DEST; gameOver = false; modeDone = false; } this.init(); this.isGameOver = function() { return gameOver; } this.isModeDone = function() { return modeDone; } var setModeDone = function() { modeDone = true; } this.nextMode = function() { if (++mode > 4) mode = 0; modeDone = false; } this.nextFrame = function() { if (mode == Mode.FIND_BALL_DEST) { // if (ball.getNum() > 89) { gameOver = true; return; } ball.setDestPos(em.findDestPos()); modeDone = true; return; } if (mode == Mode.ROLL_BALL) { // ball.frame(setModeDone); return; } if (mode == Mode.ADD_ELEVATOR) { // if (!em.isBlocking()) em.setPrime(ball.getNum()); modeDone = true; return; } if (mode == Mode.INCREMENT_BALLNUM) { ball.inc(); modeDone = true; return; } if (mode == Mode.MOVE_ELEVATORS) { // em.frame(setModeDone); } } } function ElevatorManager() { var elevators, frameNum; var initialized = false; var blocking; this.init = function() { elevators = []; frameNum = 0; done = false; initialized = true; blocking = false; } this.isDone = function() { return done; } this.isBlocking = function() { return blocking; } this.add = function() { if (!initialized) init(); var el = elevators.length; elevators[el] = new Elevator(el); } this.frame = function(callback) { if (done == true) done = false; for (var n = 0; n < elevators.length; n++) { var e = elevators[n]; e.offset = frameNum; } if (++frameNum >= rowHeight) { frameNum = 0; increment(); callback(); } } this.findDestPos = function() { var el = elevators.length - 1; // Last elevator doesn't block for (var n = 0; n < el; n++) { if (elevators[n].isBlocking()) { blocking = true; return elevatorLeft + n * columnWidth; } } blocking = false; return elevatorLeft + el * columnWidth + bw2; } function increment() { var el = elevators.length - 1; // Last elevator doesn't get incremented for (var n = 0; n < el; n++) { var e = elevators[n]; e.increment(); } } this.setPrime = function(n) { elevators[elevators.length - 1].setPrime(n); this.add(); } this.draw = function() { for (var n = 0; n < elevators.length; n++) { elevators[n].draw(); } } } function Elevator(pos) { var prime = null; var currentHeight = 0; var inc = 0; var baseX = pos * columnWidth + elevatorLeft; var length = 0; this.offset = 0; var y = baseY; var bl, bly; this.draw = function() { ctx.save(); ctx.translate(baseX, y - inc * this.offset); ctx.strokeRect(0, 0, barWidth, barHeight); if(prime != null) { ctx.strokeRect(bw2, barHeight, 0, length); ctx.strokeRect(0, bl, barWidth, barHeight); ctx.fillText(prime, textOffsetX, textOffsetY); ctx.fillText(prime, textOffsetX, bly); } ctx.restore(); } this.setPrime = function(n) { prime = n; length = n * rowHeight - barHeight; currentHeight = n; inc = -1; y = baseY - n * rowHeight; bl = length + barHeight; bly = bl + textOffsetY; } this.increment = function() { currentHeight += inc; y = baseY - currentHeight * rowHeight; this.offset = 0; if (this.isBlocking()) inc *= -1; } this.isBlocking = function() { return (currentHeight == 0 || currentHeight == prime); } } function Ball() { var num, destPos, angle, x; var cr = Math.PI*2; // a circle, in radians var y, rotateStep; this.init = function() { num = 2; y = centerY - bw2; rotateStep = 2 * speed / columnWidth; this.reset(); } this.reset = function() { x = bw2; destPos = 0; angle = 0; } this.setDestPos = function(n) { if (destPos == 0) destPos = n; } this.draw = function() { // draw Circle at current location ctx.beginPath(); ctx.moveTo(x + bw2, y); ctx.arc(x, y, bw2, cr, false); ctx.stroke(); // draw num in circle, rotated at current angle ctx.save(); ctx.translate(x, y); ctx.rotate(angle); ctx.fillText(num, 0, 3); ctx.restore(); } this.frame = function(callback) { if (x >= destPos) { callback(); return; } x += speed; angle += rotateStep; } this.inc = function() { num++; if (num == 30) speed = 2; this.reset(); } this.getNum = function() { return num; } } init(); </script>Curtis Auteryhttps://plus.google.com/107677530285177731535noreply@blogger.com0