The Slog

I just finished reading Jill from Juneau‘s report of her adventures in the Iditarod Trail Invitational, a 350 mile human powered (bike in her case) race across the interior of Alaska in February. She tells an amazing story.

Melissa was a little worried that reading this would inspire me to compete in this event next year… This is a little crazy even by my standards.

The Throb

I don’t get headaches. Well, maybe once every 3 or 4 months I will have a mild headache. Yesterday, completely out of the blue, I had the worst headache I’ve had in years.

Yesterday while eating lunch with Melissa, my parents, aunt, uncle, and cousin it just hit me. I felt perfectly fine before going into the restaurant. As soon as I sat down at the table I started seeing spots. I just assumed it was the bright windows contrasting with the dark restaurant, so I didn’t think too much of it. Be the time we finished eating I was no longer seeing spots but my head was absolutely throbbing. I went to the bathroom and saw in the mirror the veins in my forehead were bulging out. I had never experienced anything like this before.

After returning home I took some ibuprofin and went to bed. I ended up sleeping for 4 hours. I was pretty disoriented when I woke up around 7 p.m. thinking it was the middle of the night. But the good news was the throbbing headache was gone. I felt tired and weak, but this uncomfortable feeling was nothing compared to what I was feeling before the nap.

So in short, I felt terrible yesterday, but I’m much better now. No need for worries.

The Practice Start

Yesterday was the Earth, Wind, & Fire 5K run put on the by the Illinois State Geological Survey at the University of Illinois. The race started at 8 a.m. When I woke up at 6:20 a.m. the temperature was 13˚ F. This was going to be interesting.

The start was less than a mile from our house, so I ran over there to warm up. I registered for the race and waited around inside the Natural Resources Building until closer to race time. There was a Festival of Maps (I like maps) lining the hallway so I kept myself occupied. About 20 minutes before the start I ran another mile outside to warm up then I headed to the start line.

There were a surprising number of people there. I thought there would be maybe 20 of the most hardcore runners that braved the frigid weather and howling winds for this race, but I was mistaken. About 150 people showed up.

As everyone was mulling around near the starting line, trying not to freeze, the race directors decided to blow the air horn to test it out and make sure it was working. The thing is, they didn’t tell anyone what they were doing. As soon as the horn blew a few people took off running. Seeing them, everyone else took off running. The race directors were standing right in front of the line and all of the sudden the entire field was running around them. They quickly got on the bullhorn to announce that this wasn’t actually the start. The runners eventually stopped and headed back to the starting line. When it was time for the real start the directors were much more careful to explain exactly what was going to happen. Fair enough. They gave a 5 second count down then we were off.

The race went surprisingly well given the poor weather conditions. I was able to pull out a 19:20, which was good enough for 5th place overall and 2nd in my age group. I guess I’m not as out of shape as I thought I was.

Since the race was put on by geologists, the winners received some sort of special rock as an award.

Update: They posted race photos, including a couple of me.

Rob finishing the 5K (photo courtesy of Joel Dexter)

Rob receiving award (photo courtesy of Joel Dexter)

The Bonk

Several of my regular bicycling partners have started a racing team this year called Wild Card Cycling. This weekend we planned a long (even by my standards) training ride together. The plan was to do a century (100 miles). My previous longest ride of the year was around 50 miles or so, so this was a big jump. After 75-80 miles I bonked (*).

Bonking is basically the same thing that marathon runners refer to has “hitting the wall.” It is the point at which your muscles run out of glycogen and can no longer continue to function at their current capacity. In a matter of minutes you become incredibly tired and your heart rate skyrockets. You can keep going after you bonk, but your performance drops significantly. Your body has no stored carbohydrates so it has to burn only fat, which is a poor source of energy.

The only way to avoid bonking on really long workouts is to continue to eat during the workout. I was eating during the ride yesterday, but apparently it was not enough. After 4.5-5 hours or so my body stopped cooperating.

Fortunately, another guy bonked at roughly the same time and we were able to ride together the last 20 miles into town. It seemed to take forever. I ended up at home after 96.33 miles and decided not to do another 4 miles. I didn’t have it in me. I was past the point of enjoying myself anyway. There will be other days.

No “peddlers” allowed in Cerro Gordo

The route from Urbana to Cerro Gordo

This morning I did another 35 miles with the group. It took 20 or so miles to work the lactic acid from yesterday’s ride out of my legs, but I felt pretty good by the end. It was dark and rainy right until the end of the ride then the sun came out and it’s now a beautiful day. If only we had waited an hour.

(*) Incidentally, we used to ride with an Australian woman who informed us that “bonk” had a totally different meaning in her country.

The Glide

I’m starting to get the hang of skiing. As I watched the snow fall all day Friday at work my excitement grew. I had been hoping for one more good snowfall this season so I could get just a little more skiing in.

Friday night we had a dinner party with neighbors, then after that (around 12:30 am) I headed over to the Arboretum for about 20 minutes of skiing. The sledding hill was packed with (fairly uncooperative) people, most of which appeared to be college students. The snow was a little bit too powdery, but it was a good time nonetheless.

Saturday was supposed to be the big day. I ended up heading out to Allerton Schroth trail at about 2 pm. Unfortunately, much of the snow had melted (though it was still below freezing), and it was very spotty. That is, there was lots of bare ground that I had to try to avoid. So it was good, but not great. I thought the chances for good snow on Sunday were pretty slim.

Fortunately, I was proven wrong. The snow was actually fantastic. I again went over to the Arboretum. The snow was just a little icy, but not too much, and it was less spotty than it had been at Allerton on Saturday. In fact, I was skiing so well that I deviated from my original plan of doing a couple of loops at the Arboretum and just kept heading farther and farther out. There is some wooded area just across Windsor road. When I got to Windsor I took the skis off, ran across the road, put the skis back on, and kept right on going. The snow in this area was packed down and frozen just enough that I was able to glide nearly effortlessly along. After I skied in that wooded area for a while I crossed Race street and skied around at Meadowbrook park. I was now a few miles from home.

It felt so hot Sunday that I took my hat off. Then I took my gloves off (yes, skiing without gloves… it was weird). Then I had to roll up my sleeves. I had the foresight to bring a water bottle with me. Just when I reached to the back of my waist pack to get it the lid popped off and all my water spilled onto the snow. Crap. I guess it was time to head home soon anyway.

After lunch, I went back out for a 40 mile bike ride, which was somewhat enjoyable, though fairly boring. Everything looked like this:

You couldn’t really tell where the ground ended and the sky began.

The Tour de Groundhog

Last night after dinner my dad told me I was crazy for planning to do a bike race today. Of course, at the time it was 34˚ and raining. Luckily the weather was a little bit better for the Tour de Groundhog today. In fact, the weather started out a little too good, as I had to shed several layers once I started warming up.

I noticed on the drive over to Springfield that there was a lot of standing water/ice on the ground from some recent flooding. It wasn’t until I started warming up that I realized what this would mean for the race: mud, and lots of it.

I did the masters 30+ race (which is interesting given that I am 29, but apparently they use your age at the end of the calendar year…). There were only 7 people in the race, so it wasn’t too crowed. After about 10 meters the course immediately turned into the woods and from there it was a battle of wills more than an athletic competition. A good 30-40% of the course was unridable. So there was lots of dismounting and running/walking with the bike. About 2 times each lap my tires would literally stop spinning, so I would have to stop and pull a handful of mud out of both my front and rear brakes in order to be able to ride again.

The course map

It certainly was an adventure. I did get lapped by the winner, but that wasn’t totally unexpected. I ended up 5th of the 7 people in the masters race, which was good enough to win some socks and water bottles (two things of which I own copious quantities, but still never enough). Just to give you an idea how bad the conditions were, I travelled less than 3 miles in 33 minutes of racing. This is a slower pace than I averaged running for 28.4 miles at the Riddle Run ultramarathon 3 weeks ago. Perhaps my dad knew what he was talking about.

The Heart Shaped Window

On our first Valentine’s Day Melissa was mad at me for some reason. So for our second Valentine’s Day I decided to make her a really special gift. I stayed up until about 2-3 a.m. (which was quite rare, as I like to get to bed early) the night before writing a computer program that made a heart shaped window with a customized message in it. With the state of software in 1998 this was actually a pretty challenging task, but I worked hard and made something I thought we could both be proud of.

When I showed it to Melissa the next day (2/14/1998) I didn’t quite get the reaction I was hoping for. Her attitude was basically that if I hadn’t spent so much time working on the program I would have had more time to spend with her. Sheesh.

Anyway, here’s what it looked like:

And here’s the source code (wow I used to write some really ugly code):

//
// HeartWDEF.c
//

#define HASGOAWAY 0


pascal long main(short, CWindowPtr, short, long);
long  DoDraw(CWindowPtr, long);
long  DoHit(CWindowPtr, long);
long  DoCalcRgns(CWindowPtr, long);
void GetStrucRegion(CWindowPtr window, RgnHandle rgn);
void  GetGoAwayBox(CWindowPtr w, Rect *r);

OSErr PictToRgn(PicHandle pic, RgnHandle rgn){
 int   width = 0, height = 0;
 GrafPtr  mask, savePort;
 Rect   r = (*pic)->picFrame;
 OSErr  err;
 
 width = r.right - r.left;
 height = r.bottom - r.top;
 
 SetRect(&r, 0, 0, width, height);
 //OffsetRect(&r, r.left, r.top);

 mask = (GrafPtr) NewPtr(sizeof(GrafPort));
 OpenPort(mask);
 
 mask->portBits.bounds = r;
 mask->portBits.rowBytes = ((width + 15) / 8);
 mask->portBits.baseAddr = NewPtr(mask->portBits.rowBytes * height);
 
 GetPort(&savePort);
 SetPort(mask);
 
 EraseRect(&r);
 DrawPicture(pic, &r);
 
 SetPort(savePort);
 
 err = BitMapToRegion(rgn, &(mask->portBits));
 
 DisposePtr((Ptr) mask->portBits.baseAddr);
 DisposePtr((Ptr) mask);
 
 return err;
}

pascal long main(short varCode, CWindowPtr w, short message, long param){
 long  val = 0;
 
 switch(message){
  case wDraw:
   val = DoDraw(w, param);
   break;
  
  case wHit:
   val = DoHit(w, param);
   break;
   
  case wCalcRgns:
   val = DoCalcRgns(w, param);
   break;
   
  case wNew:
  case wDispose:
   break;
 }
 return val;
}

long DoDraw(CWindowPtr w, long param){
 long  val = 0;
 WindowPeek wp;
 Rect  r;
 RGBColor save;
 PicHandle pict;
 Point  p;
 WindowPtr window;
 GWorldPtr saveWorld;
 GDHandle device;
 PenState savePen;
 
 GetForeColor(&save);
 GetPenState(&savePen);
 
 PenNormal();
 wp = (WindowPeek) w;
 
 if(param == 0){
  // copy the regions so we can offset them
  RgnHandle diff = NewRgn();
  
  DiffRgn(wp->strucRgn, wp->contRgn, diff);
  
  ForeColor(redColor);
  PaintRgn(diff);
  
  PenNormal();
  /*if(wp->hilited){
   ForeColor(blackColor);
   FrameRgn(diff);
  }*/
  
  DisposeRgn(diff);

#if HASGOAWAY  
  if(wp->hilited){
   GetGoAwayBox(w, &r);
   
   ForeColor(whiteColor);
   PaintRect(&r);
  }
#endif

 }
 
#if HASGOAWAY 
 else if(param == wInGoAway){
  GetGoAwayBox(w, &r);
  InvertRect(&r);
 }
#endif

 SetPenState(&savePen);
 RGBForeColor(&save);
 
 return val;
}

long DoHit(CWindowPtr w, long param){
 long  val = 0;
 WindowPeek wp;
 Point  p;
 Rect  r;
 
 p.h = LoWord(param);
 p.v = HiWord(param);
 
 wp = (WindowPeek) w;
 
 if(PtInRgn(p, wp->contRgn)) val = wInContent;
 
 else if(PtInRgn(p, wp->strucRgn)){
  val = wInDrag;
  
#if HASGOAWAY  
  GetGoAwayBox(w, &r);
  if(PtInRect(p, &r)) val = wInGoAway;
#endif

 }
 else val = wNoHit;
 
 return val;
}

long DoCalcRgns(CWindowPtr w, long param){
 SInt32  val = 0;
 WindowPeek wp;
 PicHandle pic;
 Rect  r;
 RgnHandle rgn;
 
 wp = (WindowPeek) w;
 
 r = w->portRect;
 OffsetRect(&r, - ((WindowPtr)w)->portBits.bounds.left, - ((WindowPtr)w)->portBits.bounds.top);
 
 rgn = NewRgn();
 pic = GetPicture(4096);
 PictToRgn(pic, rgn);
 ReleaseResource((Handle) pic);
 OffsetRgn(rgn, r.left, r.top);
 CopyRgn(rgn, wp->strucRgn);
 DisposeRgn(rgn);
 
 rgn = NewRgn();
 pic = GetPicture(4097);
 PictToRgn(pic, rgn);
 ReleaseResource((Handle) pic);
 OffsetRgn(rgn, r.left, r.top);
 CopyRgn(rgn, wp->contRgn);
 DisposeRgn(rgn);
 
 return val;
}

void GetGoAwayBox(CWindowPtr w, Rect *r){
 WindowPeek  wp = (WindowPeek) w;
 
 *r = (*(wp->contRgn))->rgnBBox;
 
 r->top -= 0;
 r->bottom = r->top + 10;
 r->left += 12;
 r->right = r->left + 10;
}


//
// HeartWindow.c
//
#define kNumFields 7

typedef struct{
	Str255	str[kNumFields];
} Message;
typedef Message* MessagePtr;
typedef MessagePtr* MessageHandle;

void DoUpdate(void);

Boolean 		quitting;
WindowPtr		window;
Message			message;

void LiveDragger(WindowPtr w, Point p, Rect *r){
	Point				mouse, newmouse, original, origin;
	EventRecord			e;
	GrafPtr				save;
	
	EventAvail(mouseDown, &e);
	
	if(!(e.modifiers & optionKey)){
		DragWindow(w, p, r);
	}
	else{
		GetPort(&save);
		SetPort(w);
		
		GetMouse(&mouse);
		LocalToGlobal(&mouse);
		SetPt(&origin, 0, 0);
		LocalToGlobal(&origin);
		original = origin;
		
		SetPort(save);
		
		while(StillDown()){
			if(WaitNextEvent(updateMask, &e, 0, NULL)){
				DoUpdate();
			}
			else{
				GetPort(&save);
				SetPort(w);
				
				GetMouse(&newmouse);
				LocalToGlobal(&newmouse);
				
				if(newmouse.h != mouse.h || newmouse.v != mouse.v){
					if(PtInRect(newmouse, r)){
						MoveWindow(w, origin.h + newmouse.h - mouse.h, origin.v + newmouse.v - mouse.v, false);
						SetPt(&origin,0,0);
						LocalToGlobal(&origin);
						mouse = newmouse;
					}
					else{
						MoveWindow(w, original.h, original.v, false);
					}
				}
				SetPort(save);
			}
		}
		
		GetPort(&save);
		while(w != nil){
			SetPort(w);
			InvalRect(&w->portRect);
			w = (WindowPtr) ((WindowPeek) w)->nextWindow;
		}
		SetPort(save);	
	}		
}

void GetItemHandle(DialogPtr d, short index, Handle *h){
	short	type;
	Rect	r;
	
	GetDialogItem(d, index, &type, h, &r);
}

void EraseWindow(){
	Rect		r;
	GrafPtr		savePort;
	
	GetPort(&savePort);
	SetPort(window);
			
	r = (*(((WindowPeek) window)->contRgn))->rgnBBox;
	OffsetRect(&r, -r.left, -r.top);
	EraseRect(&r);
	InvalRect(&r);
	
	SetPort(savePort);
}

void PStringCopy( ConstStr255Param srcString, Str255 destString ){
	SInt16 index = StrLength( srcString );

	do {
		*destString++ = *srcString++;
	} while ( --index >= 0 );
}

void CopyMessage(MessagePtr src, MessagePtr dst){
	int		i;
	
	for(i = 0; i < kNumFields; i++) PStringCopy(src->str[i], dst->str[i]);
}

void LoadMessage(){
	int				i;
	MessageHandle 	mh = (MessageHandle) GetResource('hwms', 128);
	
	if(mh == nil){	
		for(i = 0; i < kNumFields; i++){
			GetIndString(message.str[i], 128, i + 1);
		}
	}
	else{
		CopyMessage(*mh, &message);
		ReleaseResource((Handle) mh);
	}
}

void EditMessage(){
	short	item = 0, i;
	Str255	str;
	Handle	h;
	
	DialogPtr d = GetNewDialog(129, nil, (WindowPtr) -1);
	
	SetDialogDefaultItem(d, 1);
	SetDialogCancelItem(d, 2);
	SetDialogTracksCursor(d, true);
	
	for(i = 0; i < kNumFields; i++){
		GetItemHandle(d, i + 3, &h);
		
		SetDialogItemText(h, message.str[i]);
	}
	
	ShowWindow(d);
	while(item != 1 && item != 2){
		ModalDialog(nil, &item);
	}
	HideWindow(d);
	
	// if the user hit ok
	if(item == 1){
		MessageHandle	old;
		
		// save the info to the app's resource fork
		for(i = 0; i < kNumFields; i++){
			GetItemHandle(d, i + 3, &h);
			
			GetDialogItemText(h, message.str[i]);
		}
		
		old = (MessageHandle) GetResource('hwms', 128);
		if(old == nil){
			old = (MessageHandle) NewHandle(sizeof(Message));
		
			if(old == nil){
				StopAlert(130, nil);
			}
            else{	
				CopyMessage(&message, *old);
		
				AddResource((Handle) old, 'hwms', 128, "\p");
				ChangedResource((Handle) old);
				
				ReleaseResource((Handle) old);
			}
		}
		else{
			CopyMessage(&message, *old);
		
			WriteResource((Handle) old);
			ChangedResource((Handle) old);
			
			ReleaseResource((Handle) old);
		}
		
		
		EraseWindow();
	}
	
	DisposeDialog(d);
	InitCursor();
}

void DoAbout(){
	Alert(128, nil);
}

void DoAppleChoice(short item){
	if(item == 1) DoAbout();
	else{
		Str255	daName;

		GetMenuItemText(GetMenuHandle(128), item, daName);
		OpenDeskAcc(daName);
	}
}

void DoFileChoice(short item){
	switch(item){
		case 1:
			// edit the text
			EditMessage();
			break;
			
		default:
			quitting = true;
			break;
	}
}	

void DoEditChoice(short item){

}

void DoMenuChoice(long choice, EventModifiers modifiers){
	short	id, item;

	id = HiWord(choice);
	item = LoWord(choice);

	switch(id){
		case 128:
			DoAppleChoice(item);
			break;

		case 129:
			DoFileChoice(item);
			break;

		case 130:
			DoEditChoice(item);
			break;
	}

	HiliteMenu(0);
}

void DoDiskEvent(const EventRecord *e){
	Point 	dialogCorner;

	if((e->message >> 16 ) != noErr){
		SetPt(&dialogCorner, 112, 80);
		DIBadMount(dialogCorner, e->message);
	}
}

void DoOSEvent(const EventRecord *e){
	SInt16		osMessage;
	WindowPtr	w;

	osMessage = (e->message & osEvtMessageMask) >> 24;

	switch(osMessage){
		case suspendResumeMessage:
			break;

		case mouseMovedMessage:
			break;
	}
}

void DoNullEvent(const EventRecord *e){
	WindowPtr 	w;	
}

void DrawCenteredString(Str255 str, short y){
	MoveTo((328 - StringWidth(str))/2, y);
	DrawString(str);
}

void DoUpdate(){
	GrafPtr		savePort;
	RGBColor	saveColor;
	Rect		r;
	int			size = 18, i, y = 116;
	Str255		str;
	
	BeginUpdate(window);
	GetForeColor(&saveColor);
	GetPort(&savePort);
	SetPort(window);
	
	ForeColor(redColor);
	TextFont(applFont);
	TextSize(size);
	
	for(i = 0; i < kNumFields; i++, y += (size + 6)){
		DrawCenteredString(message.str[i], y);
	}
	
	SetPort(savePort);
	RGBForeColor(&saveColor);
	EndUpdate(window);
}

void DoMouseDown(EventRecord *e){
	WindowPtr	w;
	SInt16 		part;

	part = FindWindow(e->where, &w);

	switch(part){
		case inMenuBar:
			DoMenuChoice(MenuSelect(e->where), e->modifiers);
			break;
			
		case inSysWindow:
			SystemClick(e, w);
			break;

		case inContent:
			//quitting = true;
			break;

		case inDrag:
			//DragWindow(w, e->where, &qd.screenBits.bounds);
			LiveDragger(w, e->where, &qd.screenBits.bounds);
			break;

		case inGoAway:
			if(TrackGoAway(w, e->where)){
				quitting = true;
			}
			break;
	}
}

void DoKeyDown(const EventRecord *e){
	SInt16 	key;

	key = (e->message & charCodeMask);

	if(e->modifiers & cmdKey){
		DoMenuChoice(MenuKey(key), e->modifiers);
	}
}

void ProcessEvents(void){
	EventRecord 	e;

	if(WaitNextEvent(everyEvent, &e, 0, nil)){
		
		SetCursor(&qd.arrow);
		
		switch(e.what){
			case nullEvent:
				DoNullEvent(&e);
				break;
	
			case mouseDown:
				DoMouseDown(&e);
				break;
				
			case keyDown:
			case autoKey:
				DoKeyDown(&e);
				break;
			
			case updateEvt:
				DoUpdate();
			
			case diskEvt:
				DoDiskEvent(&e);
				break;
			
			case kHighLevelEvent:
				AEProcessAppleEvent(&e);
				break;
		}
	}
	
	DoNullEvent(&e);
}

pascal OSErr	HandleOpenDocument(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
	return noErr;
}

pascal OSErr	HandleOpenApplication(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
	
	return noErr;
}

pascal OSErr	HandleQuitApplication(const AppleEvent *ae, AppleEvent *reply, SInt32 refCon){
	
	quitting = true;
	
	return noErr;
}

OSErr InitEvents(void){
	OSErr	err;

	if((err = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc(HandleOpenApplication), 0, false)) != noErr)
		return err;

	if((err = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc(HandleOpenDocument), 0, false)) != noErr)
		return err;

	if((err = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, NewAEEventHandlerProc(HandleOpenDocument), 0, false)) != noErr)
		return err;

	if((err = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc(HandleQuitApplication), 0, false)) != noErr)
		return err;

	return noErr;
}

void CheckMachine(){
	SysEnvRec	thisMac;

	SysEnvirons(7, &thisMac);
  	
  	if(!thisMac.hasColorQD){
  		StopAlert(131, nil);
  		ExitToShell();	
  	}
	
	if(thisMac.systemVersion < 0x0700){
		StopAlert(131, nil);
		ExitToShell();
	}
}

OSErr Initialize(void){
	OSErr		err;
	short		w1, w2, h1, h2;
	Handle		code;
	MenuHandle	menu;
	
	InitGraf(&qd.thePort);
	InitFonts();
	InitWindows();
	InitMenus();
	TEInit();	
	InitDialogs(nil);
	InitCursor();
	FlushEvents(everyEvent, 0);

	MaxApplZone();
	MoreMasters();
	MoreMasters();
	MoreMasters();

	CheckMachine();

	err = InitEvents();
	if(err != noErr) return err;
	
	LoadMessage(&message);
	
	SetMenuBar(GetNewMBar(128));
	menu = GetMenu(128);
	AppendResMenu(menu, 'DRVR');
	DrawMenuBar();
	
	quitting = false;
	
	window = GetNewCWindow(128, nil, (WindowPtr) -1);
	
	code = GetResource('WDEF',4096);						
	if(code != nil) ((WindowPeek) window)->windowDefProc = code;
	
	ShowWindow(window);
	
	return noErr;
}

void Finalize(void){
	HideWindow(window);
	DisposeWindow(window);
}

void main(void){
	if(Initialize() == noErr){
		
		while(!quitting){
			ProcessEvents();
		}
	}
	
	Finalize();
}