learnr.org dev blog

-
Feb 9
ImgBurn rocks the shizzle. ImgBurn rocks the shizzle.

Comments (View)
Jan 28
Huzzah! Huzzah!

Comments (View)
Nov 15

First Clojure program

So, I watched a few of Rich’s videos on Clojure a couple months back, but I finally got an afternoon to spend trying it out.

I skimmed through Ruby Quiz to find an interesting but simple problem, and I decide to write a Sokoban clone.

My initial thought was to do it as a gui app, but I haven’t written any Java code since JDK 1.0-alpha in late 1995, and honestly, I didn’t really feel like putting effort into learning the Java GUI API only to end up with a bizarrely emulated, slightly broken GUI.

So, I grabbed Compojure off of git, and made the UI a hack webapp. (Yes, it’s bizarrely emulated and slightly broken still, but at least it was easy to write).

First, have a look at the result: Clojban!

If you care, the (probably horrible and un-idiomatic) code follows.

(ns clojban
  (:use (compojure html
                   http
                   file-utils)
        (clojure.contrib str-utils
                         seq-utils)
        (clojure set))
  (:import (java.util.regex Pattern)))

(defstruct pos :x :y)
(defstruct level
           :player ; pos
           :boxes  ; set-of-pos
           :goals  ; set-of-pos
           :walls  ; set-of-pos
           )

(defn explode
  "return a pos/type seq for each char in the line"
  [y line]
  (map (fn [x y type] (vector (struct pos x y) type))
       (iterate inc 0) (repeat y) line))

(defn line-explode
  "call explode on each line with the appropriate y"
  [lines]
  (let [linesplit (. Pattern compile "\n" (. Pattern MULTILINE))] ; must be a better way?
    (reduce into
            (map explode
                 (iterate inc 0)
                 (. linesplit split lines)))))

(defn filter-level
  "extracts a set of the pos's that match types"
  [expl-level type1 type2 & type3]
  (set (map first
            (filter (fn [item]
                      (or (= (second item) type1)
                          (= (second item) type2)
                          (= (second item) type3)))
                    expl-level))))

(defn load-levels
  "load and return seq of level's"
  [filename]
  (let [contents (slurp filename)
        levelsplit (. Pattern compile "^$\n" (. Pattern MULTILINE)) ; each level separated by blank line
        levels (seq (. levelsplit split contents))]

    (map (fn [levelstr]
           (let [expl-level (line-explode levelstr)]
             (struct-map level
                         :player (first (filter-level expl-level \@ \+))
                         :boxes (filter-level expl-level \o \*)
                         :goals (filter-level expl-level \. \* \+)
                         :walls (filter-level expl-level \# \#))))
         levels)))

(def Levels (load-levels "sokoban_levels.txt"))
(def LvlWidth 19)
(def LvlHeight 16)
(def PlayerRenderOpen "♦")
(def PlayerRenderGoal "♦")
(def BoxRenderOpen "▨")
(def BoxRenderGoal "▨")
(def WallRender "█")
(def GoalRender "░")

(defn level-index [x y] (+ (* y LvlWidth) x))



(defn render-level-layer
  "my, what an insane level representation i have here"
  [target items if-empty if-full]
  (loop [target target
         remain (seq items)]
    (if remain
      (let [x (:x (first remain))
            y (:y (first remain))
            i (level-index x y)
            at (target i)]
        (recur (assoc target i (if (= at \space) if-empty if-full))
               (rest remain)))
      target)))

(defn render-level
  [lvl]
  (let [finallevel (reduce (fn [lvl data] (render-level-layer lvl (nth data 0) (nth data 1) (nth data 2))) ; todo how to splat `data'
                           (vec (replicate (* LvlWidth LvlHeight) \space))
                           `((~(:walls lvl) ~WallRender ~WallRender)
                             (~(:goals lvl) ~GoalRender ~GoalRender)
                             (~(:boxes lvl) ~BoxRenderOpen ~BoxRenderGoal)
                             (~(set (list (:player lvl))) ~PlayerRenderOpen ~PlayerRenderGoal)))]
    (dotimes y LvlHeight
      (print "")
      (dotimes x LvlWidth
        (print "")
        (print (finallevel (level-index x y)))
        (print ""))
      (print ""))))

(def AsciiToDx {72 -1,
                76  1,
                75  0,
                74  0})
(def AsciiToDy {72  0,
                74  1,
                75  -1,
                76  0})

(defn do-player-move
  "handle player movement, input is int ascii code for HJKL."
  [level input]
  (let [dx (get AsciiToDx input)
        dy (get AsciiToDy input)
        curpos (:player level)
        candidatepos (struct pos (+ (:x curpos) dx) (+ (:y curpos) dy))
        pastcandidatepos (struct pos (+ (:x curpos) dx dx) (+ (:y curpos) dy dy))
        walls (:walls level)
        boxes (:boxes level)]
    (if (walls candidatepos)
      level
      (if (boxes candidatepos)
        (if (or (walls pastcandidatepos) (boxes pastcandidatepos))
          level
          (assoc
            (assoc level :boxes (conj (disj boxes candidatepos) pastcandidatepos)) ; move box
            :player candidatepos)) ; and player
        (assoc level :player candidatepos))))) ; only player

(def JSCode "
function postwith (p) {
  var myForm = document.createElement('form');
  myForm.method='post';
  myForm.action='/';
  for (var k in p) {
    var myInput = document.createElement('input');
    myInput.setAttribute('name', k);
    myInput.setAttribute('value', p[k]);
    myForm.appendChild(myInput);
  }
  document.body.appendChild(myForm) ;
  myForm.submit() ;
  document.body.removeChild(myForm) ;
}

function handlekey(e) {
  if (!e) var e = window.event
  if (e.keyCode) code = e.keyCode;
  else if (e.which) code = e.which;
  if (code == 37) code = 72;
  if (code == 38) code = 75;
  if (code == 39) code = 76;
  if (code == 40) code = 74;
  if (code == 72 || code == 74 || code == 75 || code == 76 || code == 82 || code == 65 || code == 90) postwith({'code': code});
}
")

(defn restart-level [session]
  (alter session assoc :curlevel (or (session :curlevel) 0))
  (alter session assoc :complete (or (session :complete) (set nil)))
  (alter session assoc :nummoves 0)
  (alter session assoc :level (nth Levels (or (session :curlevel) 0))))

(defn next-level [session]
  (alter session assoc :curlevel (min
                                   (inc (session :curlevel))
                                   (- (count Levels) 1)))
  (restart-level session))

(defn prev-level [session]
  (alter session assoc :curlevel (max (dec (session :curlevel)) 0))
  (restart-level session))

(defservlet clojban-servlet
  (POST "/"
        (dosync
          (let [prevlevel (session :level)
                keycode (. Integer parseInt (params :code))]
            (if (= keycode 82)
              (restart-level session)
              (if (= keycode 65)
                (prev-level session)
                (if (= keycode 90)
                  (next-level session)
                  (let [moveresult (do-player-move prevlevel keycode)]
                    (if (= 0 (count (difference (:boxes moveresult) (:goals moveresult))))
                      (do
                        (alter session assoc :complete (conj (session :complete) (session :curlevel)))
                        (next-level session))
                      (do
                        (alter session assoc :nummoves (inc (session :nummoves)))
                        (alter session assoc :level moveresult)))))))))
        (redirect-to "/"))
  (GET "/"
       (let [levelstate (or (session :level) (dosync (restart-level session) (session :level)))]
         [{"Content-Type" "text/html"}
          (html
            (doctype :xhtml-transitional)
            [:html {:xmlns "http://www.w3.org/1999/xhtml" :lang "en"}
             [:head
              [:title "Clojban!"]
              [:meta {:http-equiv "Content-Type", :content "text/html; charset=utf-8"}]
              [:script {:type "text/javascript"} JSCode]]
             [:body {:onkeydown "handlekey(event);" :style "font-family: arial; font-size: 13px;"}
              [:h1 {:style "font-family: arial;"} "Clojban"]
              [:p "This is a silly Sokoban implemented in " (link-to "http://clojure.org" "Clojure") ". It's hitting the server every time you move (rather than Javascript) so it might not respond too quickly. The unicode box drawing craziness looks OK in FF, and reasonable in IE, but not so hot in Chrome/Webkit. Sorry."]
              [:table {:style "border-style: none; font-size: 36px; font-family: courier; padding: 0px 0px 0px 0px; margin:0px 0px 0px 0px; line-height: 1em;"
                       :border "0"
                       :cellpadding "0"
                       :cellspacing "0"}
               (with-out-str (render-level levelstate))]
              [:p "Use arrow keys or hjkl to move all the boxes into the goals. Press r to restart level, or a/z to give up and skip through levels."]
              [:p "Level: " (session :curlevel)]
              [:p "Moves: " (session :nummoves)]
              [:p "Completed: " (str-join " " (map (fn [i]
                                                     (let [complete (session :complete)]
                                                       (format "%d"
                                                               (if (complete i) "green" "#ccc")
                                                               i)))
                                                   (range (count Levels))))]
              ]])]))
  (ANY "*"
       (page-not-found)))

Comments (View)
Nov 10

Configuring Vim, some more

As an addendum to http://items.sjbach.com/319/configuring-vim-right

Here’s a few more that I think are un-live-able-without:

" ease of use keyboard mappings (why do I care about top/bottom of screen?)
map H ^
map L $

" buffer switching/management, might as well use those keys for something useful
map <Right> :bnext<CR>
imap <Right> <ESC>:bnext<CR>
map <Left> :bprev<CR>
imap <Left> <ESC>:bprev<CR>
map <Del> :bd<CR>

" get rid of stupid scrollbar/menu/tabs/etc
set guioptions=a

" don't need /g after :s or :g
set gdefault

" i prefer this to visualbell
set noerrorbells
" Hide the mouse pointer while typing
set mousehide

I’ve used all of these for longer than I can remember, probably time to troll through recent help files and vim.org to find new and exciting juicy settings.

EDIT: forgot the one I miss the most when I don’t have my .vimrc:

cab o find

so that :o does something useful. I’d like one of the emacs-style smart-fuzz-file-opener command line thing, I should probably hunt around for a plugin to do that.


Comments (View)
Aug 24

Bill C-61

Canada’s Bill C-61 is an even-worse version of the United States’ DMCA.

For more information, http://www.michaelgeist.ca/ is a reasonable starting point. I rolled up my sleeves and had a go at http://www2.parl.gc.ca/HousePublications/Publication.aspx?Docid=3570473&file=4 which is relatively readable.

Here’s the letter I just posted (yes, on actual paper, in an envelope!) to my MP.

If you’re Canadian, please read the above links and if you don’t agree with this Bill, as I’m confident you won’t, phone or write to your MP (especially if your MP is a Conservative Party member!)

————————————

Office of the Honourable Dr. Hedy Fry, P.C., M.P.
Confederation Building House of Commons
Ottawa, Ontario
K1A 0A6

Hello Dr. Fry

I am a resident of Vancouver Centre, and I’m writing to you to express my absolute horror at the current state of Bill C-61.

I am employed as a software engineer in the entertainment industry, and as such, fully respect and understand the need for copyright reform in Canada.

However, from reading this bill (at http://www2.parl.gc.ca/HousePublications/Publication.aspx?Docid=3570473), the reproduction rights afforded to Canadians are absolutely useless because of section 29.21(c). This section subordinates any time-shifting and media-shifting rights that should be allowed, merely by having any kind of digital lock mechanism on the content. But, virtually all media sold in Canada now contains a digital lock of some sort, so it’s almost pointless to “grant” these rights.

Inevitably, the devices or internet services which control the digital lock access fail, go out of business, or otherwise are not maintained. If we are unable to legally shift to other formats, and the media become un-unlockable, they are effectively gone. Will all of the companies and individuals controlling the locks exist in 5 years? Maybe. 50 years? Unlikely.

I would also like to especially point out that this does not solely concern things that could perhaps be considered “frivolous”, like pop-consumer-ish music and video. Over the next 10-15 years, it is all but guaranteed that most books, and crucially text books, will be delivered and consumed in electronic form. My wife is a high school teacher employed by the Vancouver School Board, and I would be horrified to find that our already underfunded education system had lost access to teaching resources, simply because the company controlling a digital lock had failed.

I sincerely hope you will make sure that this Bill is heavily modified, or defeated, when the time comes.

Thank you for your time,





Scott Graham
<my address removed>


Comments (View)
Aug 9

Braid

Braid is very cool. I often enjoyed reading The Inner Product in GDMag, but always got the impression Jonathan was a little “ivory tower”. Turns out, I don’t know shit. Congrats to Mr Blow! (just grabbed the full 360 version: it’s fantastic).

Comments (View)
Aug 6

floating point

Floating-point tends to chafe my ass. I was thinking perhaps I’d try to really understand it. These look interesting:

http://citeseer.ist.psu.edu/goldberg91what.html

http://www.cant.ua.ac.be/ieeecc754.html

http://fluorescence.fjfi.cvut.cz/~adamek/nm/ieee754.pdf

Only way to understand something is to implement it though…

[Update]: even better by the looks of it: http://docs.sun.com/source/806-3568/ncgTOC.html


Comments (View)
Jul 29

Second "das keyboard"

So, I just got my second http://www.daskeyboard.com/ in the mail. The first one I had was the “original”, and I loved and continue to love it. I’ve been using it as my work keyboard (where I do the majority of my coding) for the last 3 years or so.

I just got a second one, but it’s the “new” version (the only one they offer now). It’s cleaner design-wise, but there’s some things that I think they’ve made worse.

The first is that the keyboard is just too thick. It’s gotten half an inch (or so) thicker. It feels more solid now, and is quite a bit heavier (so it doesn’t move around at all, which is nice), but the angle of my forearms is slightly higher, which inevitably means my wrists are more crooked when I get more tired and drop my arms, and slouch, etc. Don’t like that very much, especially as I’ve had some wrist and arm pain recently.

They also seem to have killed the variable resistance on keys (or at least tuned them differently). The only key I notice that’s harder resistance is the backspace key, whereas the space key used to have a lot more resistance, and doesn’t all now. Perhaps the resistance has changed over time on my original one, so it’s just a matter of getting used to this one, or breaking it in more. Maybe it’s more expensive to do the bands of keys with different resistances, but I’m pretty sad about it, especially since this one ended up being about 30% more expensive than the original.

Oh, and the F and J have icky bumps instead of being nicely scooped. :( I’m guessing that one was a concession to people who weren’t very good typists, and couldn’t find the home row, but I prefer the scoops, since we are going for “stealth” here, after all. They just felt nicer on your finger tips too.

The sound of the switches has changed too, but that’s just… different, not really better or worse.

I’m going to guard my old one even more carefully now with the knowledge that they’re not really replaceable. Hopefully I’ll grow to love the new one as much, but the jury’s still out.


Comments (View)
Jul 26

Problems with Vinagre

So, the default remote desktop app in Ubuntu changed from, um, I don’t know what actually, to a new app called “Vinagre”.

You’d think that’d be something I wouldn’t give a patooty about. The first thing you notice is that it has a list on the side that lets you keep track of servers you connect to which seemed nice enough.

I didn’t use it much at first, so I thought nothing more about it.

Today, I started using it. Dear god:

  1. Tried to connect to a fully up-to-date Fedora box (from a fully up-to-date Ubuntu). vino-server crashes on the Fedora machine. I don’t know who’s fault it is, but I don’t really care. Working around that by using XDMCP for the time being, but that’s pretty irritating, interface-wise.
  2. While repeatedly trying to connect to the Fedora box (it silently fails on the Ubuntu machine), Vinagre doesn’t save the last-entered machine name, so I have to keep entering “192.168.0….” every time. Irritating.
  3. Insanely hard to send Ctrl-Alt-Del to log into Windows machines. For same reason, Ctrl-Alt was chosen as the “Capture/Release” input. So, in order to send Ctrl-Alt-Del to login to a Windows box, you have to first focus the Viagre app (click/whatever), then make sure you’ve uncaptured the input by doing Ctrl-Alt, then finally, recapture the input by doing Ctrl-Alt again, and then without letting go of Ctrl-Alt, press Del to get the final combo. If you just push that combo of course, the Ctrl-Alt first removes capture, and then sends Ctrl-Alt-Del to the local machine. Not very intuitive, especially since there’s very little (no?) indication of whether input is captured.
  4. Related, there seems to be absolutely no way to send F11 to the client machine. The general hotkey behaviour is irritating enough as it is: Alt-F4 closes either all of Vinagre, or an app on the client, depending on whether you’ve pressed Ctrl-Alt recently, with no indication of which “mode” you’re in). But, it seems that F11 is even worse. If you’re in captured mode, then most things seem to be sent to the target machien, but apparently F11 was deemed too important to be handled normally, and there’s no configuration mechanism. Fine, until I try to debug something and naturally hit F11 to “Step Into” a function.
  5. [Update] When input is captured, there’s no way to scroll the virtual desktop if it’s bigger than your current monitor. WTF?

Basically, it just seems like this app was pushed out way too quick to the main/default user stream. Please give me back my old VNC/RDP viewer. :(


Comments (View)
Jul 12

Windows iTunes

I genuinely don’t understand.

I’m not trying to be difficult. I really tried to use iTunes this time. It seemed like the march of inevitability if I wanted to get iPhone apps, and I thought perhaps it would be nice to use. I grabbed the new 7.7 that has iPhone apps, since I wanted to have a look at what was available. It was a little large at 60meg, but hey, whatever, there’s lots of bandwidth to go around these days.

First of all, I had to reboot after installing it. Insane. Strike one. If that was it, well fine. But it’s completely unusably slow on my laptop. The laptop is about 2 years old. It was Dell’s top-of-the-line (XPS M170) when I bought it, so it’s not crazy fast, but it’s not so slow either. I’m pretty sure that it’s still well above the current median machine in performance.

I added my music collection (largely stored on a network drive). It’s about 65 gig, comprised mostly of mp3s and flacs, both ripped from CDs and downloaded. Maybe a little larger than some collections, but certainly not unreasonably large.

I started the “Import” last night around 8pm. It’s now 2pm the next day, and it’s still trying to download album artwork. That’d be fine, except that apparently while it’s doing that, it’s impossible for the UI to be responsive. WTF! Is this goddamn amateur hour? It only refreshes the UI at the end of each attempt to download an album cover?!

Pausing or unpausing music, or changing the volume (since apparently it feels the need to steal the functionality hardware media keys) takes over 10 seconds. That is not hyperbole! It actually takes longer than 10 seconds for anything to happen. On top of that, the mouse event handling also appears to be “sampling” rather than using the event queue properly, because if I just click the play/pause button nothing happens. I have to hold the fucking mouse button down until the UI responds (10 to 15 seconds, remember!), putting the button into the down state, and then release it.

On a side note, iTunes.exe currently owns roughly 300 threads, and has had a continuous IO delta of around 500k/sec for nearly 24 hours now. Jumped up Jesus Christ. You’re not controlling air traffic, you’re cataloging and playing some fucking music.

If this is Apple’s shitty attempt to create some sort of Mac “halo”, they’ve failed miserably. With 100% certainty, I will never, ever, by a Mac with this as the demo of the user experience.


Comments (View)
Page 1 of 4