When you write a game without using events... all you are really doing it implementing events yourself. This is advantageous, because you can make it so much more efficient than by using your language's built-in events. Games written this way are less error prone if you know what you are doing.
For example, when I was trying to teach my brother how games are written, I wrote a simple snake game for him. I had the main loop in a thread, move the snake and draw it at it's new position in a cycle. I would have a thread running at the same time that continuously checks 4 things:
1) If the snake crashed into itself (game over); if game over occurs, halt the main thread that updates the main position of the snake, print game over onto the screen, await key input, then restart the game.
2) If the snake had eaten an apple; increment the counter variable that says how many apples have been eaten, and print this new value on the screen, over-writing what was previously there.
3) If the snake had eaten an amount of apples divisible by 10 (snake grows by 1 cell, subtract from a wait variable that says how much time should pass between each movement the snake makes)
4) If an arrow key has been pressed. If left, set move to 0, if right set move to 1, if down set move to 2, if up set move to 3. The int that this is stored in is a pointer to an array of 4 delegates that make the snake move in the right direction.
The main loop that updates the position of the snake would tell the thread checking these 4 things what the snake is doing. The way I do this is I have every cell on the screen that the snake's head moves to refer to a 2-dimensional array of delegates. About this array of delegates:
The game is written in console mode, and uses console colours. The console is set to 80x50 characters. A delegate as follows: "delegate void ptr()"; then I create the array with:
"ptr[,] pos = new ptr[80,50]". Say the snake's head is at position (4,5) on the screen, after it has moved there the main loop would execute "pos[4,5].Invoke();".
One of them:
When the snake moves to a new position, the main loop thread would get each cell that the snake covers on the screen, and set the delegate at that position to point to a function called "void gameover()" which would set the gameover_ variable to true. So when the loop thread that checks the status of the game checks for gameover, it freezes the game and prints game over on the screen.
Another:
When an apple is drawn on the screen, the delegate position it gets drawn at (which is randomized) is set to point to "void increment_apple()" which increments the apple counter, removes the current apple from view, and draws a new apple on the screen, setting the old apple position to point to a "void nop()" which does nothing, and the new apple position to point to "void increment_apple()".
This is basically how the game works. As you can see, the snake moves to these positions on the screen, and it without performing any explicit checks like "if(snake_position == some_position)", the game automatically does everything it is supposed to for everything that happens in the game, much like how when you click a button on a form, an action assigned to that event is automatically executed, without you having to check for the event yourself.
So you see, I could have used a form and the default events that C# provides, but I didn't. I used the console interface, and implemented my own events system.
This is how it works behind the scenes:
the main loop for your form app will run in a thread that checks for input from all the buttons, etc on the screen. Each of these items will set a boolean variable they use to true. When you click this button, another thread running a loop checks what you have pressed, and say you pressed a button called "button1", that button would have had a delegate assigned to it; that delegate is then executed with whatever it points to.
Kind of hard to explain, but does this make sense to you?