About The Contest
The first ever Olympiad of Misguided Geeks contest at Worse Than Failure (or OMGWTF for short) is a new kind of programming contest. Readers are invited to be creative with devising a calculator with the craziest code they can write. One lucky and potentially insane winner will get either a brand new MacBook Pro or comparable Sony VAIO laptop.
Entry #100099: estimator
by Keith Lucas
This calculator is a Gtk+, Unix calculator with a client-server model. Upon start up, two processes are created with fork(). One process is the client process; the other the server process.
The client process is the Gtk+ portion. The client process sets up the display and the buttons. The display is updated when an X11 Property is changed for the client's X11 Window. The Property is an XML string, from which the value to be displayed is extracted.
The client also handles button events. Button events cause a UDP message to be sent to the server process. Each button press is sent to the server, which should send a new XML string for the change in internal state caused by the button press.
The server process open sixteen or more UDP sockets, each bound to a different IP address and port. The IP addresses are on localhost; there is some logic to create a different IP address on the 127.0.0.0/24 network. (You can use any IP address in the 127.0.0.1-254 range, not just 127.0.0.1). The port is just something calculated to be greater than 1024, so that you don't have to be root to run this program.
I avoided saving the file descriptor for each socket to save memory. I only record the first socket's file descriptor. Then I rely on the fact that the each subsequent socket's file descriptor will be the previous socket's file descriptor plus one.
I select() on how ever many sockets that I make. (I'm not entirely sure if it's sixteen or seventeen or more.) When one of the socket's is readable, I read four bytes from its descriptor, and retrieve the IP address from the descriptor. I then place the four bytes (the client ID) and the IP on a queue. I have no idea if this queue works if there is more than one item on it, because in most scenarios, it isn't possible for more than one item to be on it.
After I read from all readable sockets and place them read information on the queue, I pop each item off the queue and process it. I attempt to extract the number of the button clicked from the IP address. If it's not a number, I assume it's a non-numeric button. The decoded IP address is used to update an internal state, which is two numerical strings and a character operator.
If the IP address was determined to be the "=" button IP address, the operation is submitted to another piece of code to determine the result. The other piece of code is a limited lisp parser, so the state has to be converted to lisp code. The state is converted to "( OP NUMBER1 NUMBER2 )", where OP is '+', '-', '*', or '/'.
The lisp parser only supports the lisp functions '+', '-', '*', and '/', and these functions can only handle two arguments. We might send a string that can't be parser correctly to the lisp parser, but this is good because then the calculator will display "Err". I think the lisp code "( )" will be sent to the lisp parser if you click = upon starting the calculator.
The lisp parser extracts the two numbers and the operation. During parsing, the operation is stored as a string. The third character of the string, and the sums of the ASCII value for each character of the string is used to determine which operation function to call.
The functions sum(), difference(), product(), and quotient() determine the actual result. sum() calls difference(), and quotient() calls product().
difference() basically decrements both numbers until the second number is zero. x - y = (x - 1) - (y - 1). It does this via recursive. The second number will eventually be zero because the user interface guarantees that the second number is a positive integer. When the second number is zero, the first number is returned. This is identity element of addition. x - 0 = x + -0 = x + 0 = x.
If the second number is big, difference() will provide overflow the stack, so if the second number is greater than 100, I decrement by 100 instead of 1.
sum() calls difference() taking advantage of the fact that x + y = -(-x - y).
product() uses the fact that x * y = x - ((-x) * (y - 1)). This is a recursive definition which also relies on the fact that second number is a positive integer because of the user interface. quotient() uses the fact that x / y = e^(-ln(e^(-ln(x)) * y)). Proofs are left as exercises for the reader. quotient() checks if the second number is zero. If it is, it will return double value of the first 32 bits of the double value's memory being set to 0xdeadbeef.
The result is convert to a string with a custom conversion function, with 10^-1 accuracy, unless the double has the 0xdeadbeef flag in it, in which case the string will be "Err". The result string encoded into XML and sent to the client, via an X11 property.
Besides the stupidity of the design, this code is full of WTFs and bugs. The first one is when I fork() a new process for the server, and there is no way of stop this new process other than killing it.
The server silently fails and doesn't do anything on a second instance of the program, because it can't bind to the same IP and port as the first server. So the server is shared between the programs, but the server only maintains one state. So, for example, if you press one in client one, then two in client two, client two will display "12".
Operator representation constantly changes. Addition starts off as "PLUS", then gets converted to 'L' + 'U' + 'S', then gets converted to 11, then gets converted to 12, then gets converted to 127.0.0.24, then gets converted to 12, then gets converted to 11, then gets converted to 1, then gets converted to '+', then gets converted to "+", then gets converted to "PLUS", then gets converted to 'U', and then gets converted to 'P' + 'L' + 'U' + 'S'.
The utility functions mostly in util.c all duplicate standard library functions in stupid ways. memory_clear() open and copies zeros from /dev/zero one bite at a time and doesn't close the file descriptor. string_length() calculates the length of a string by converting it to a int to do it "more quickly." I think it might segfault if the size of the memory region of the string not divisible by four, so I tried to make the size of all my strings divisible by four. string_equals() has completely wrong logic. I avoided using any standard library functions for strings.
If you enter enough digits, the display resets. If you do a calculation on a big number, you can cause and error can makes the display always display a '?' at the end that's impossible to clear without restarting. You can perform an operation that causes the server process to crash, with just causes the client to stop doing anything when a button is clicked.
There are too many WTFs and bugs to completely list.