Separating logic out is an important part of good software engineering. It's great you picked up on that need.
Of course, usually the way it works is you start off writing a Horrible Mess, then later (but not too much later) you go through and separate it out. If you're inexperienced, you probably want to start this way. Worrying about architecture before you can "play" your first game is usually a Bad Thing.
I've yet to figure out a way to do this without using a ton of globals (or by faking globals through using static class singletons).[/quote]
One thing I didn't understand for a long time was why you should avoid globals. All programs are taught that globals and singletons are a sin against mankind, but the actual lesson to be learned is omitted from the lecture. My advice: use globals and/or singletons where you feel you need to. There will come a point where one of two things will happen:
1) You will forget where something is declared, or
2) You will want two instances of something that there is (by definition) only one copy of.
Both can be fixed. How hard it is to fix it depends on how proliferated the problem is in your code.
One important lesson often omitted is that EVERY application eventually references a singleton. You eventually want to reference THE screen, THE database, THE network, THE sound subsystem, etc. It's helpful to have those things loosely coupled for testing (talking to a mock database, for example), but in an actual application, there's only going to be one of each of these things in existence. Ever. So, it's perfectly fine to make them singletons.
Keep in mind the cost of making something local (as opposed to global) is that you are responsible for keeping track of it. You always either have to pass a reference to it or some object which keeps a reference to it. This kind of plumbing can be its own nightmare to manage.
In terms of events, what you probably want is two layers of events. You have UI events (mouse clicks and keyboard inputs) and you have game events (move commands, attack commands, etc). Then, at the top level of your program, your job is to translate UI events into game events and pass them to your game's event loop. From the event loop's perspective, all it does is expose one callback ("sendEvent"), and other than that, all it knows about are events. The UI, on the other hand, knows how to create a game event and how to send it (using "sendEvent"), but it doesn't know what the events mean past what creates them.
So as far as defining a class hierarchy, if it were me, I'd create a singleton game event loop, give it a sendEvent() method, then have the UI callbacks (onMouseClick() or whatever) create game events and send them via GameLoop::singleton()->sendEvent(...). There's no real need for inheritance here. (Though I'm sure you could find another good architecture that uses it well).
If you have a setup like this, and you need to swap out the UI component (say you're porting your game to a console system or a smartphone platform), you just need to rewrite the UI with whatever toolkit is at your disposal (provided by iOS or PS SDK or whatever). As long as you keep most of the game logic in the event loop, you don't have to rewrite very much at all.