(define (new-input)
(cons 'input (get-input)))
(define (input? x)
(and (pair? x)
(eq (car x) 'input)))
(define (input-val input)
(cdr input))
(define (current-input input)
(if (input? input)
(input-val input)
(car input)))
(define (future-input input)
(if (input? input)
(new-input)
(cdr input)))
...
(define (run-game initial-world input)
(define (run-step world input)
...
(run-step (update-world world (current-input input))
(future-input input)))
(run-step initial-world input))
(run-game (make-initial-world) (new-input))
Now you can just pass a list as the input, to test stuff or to run a demo. Note that current-input behaves much like car; you can call it as many times as you want and you get the same result (this is called referential transparency). You can wrap your random number generator and other stuff in a similar way.
This can be further refactored so you can get out a stream of worlds too, so you can compare it to pre-recorded or hand authored test data. For practical purposes, you'll normally be testing smaller parts of your system; the same principle works for those.
I learned C before I learned any functional languages and I regret it to this day. It took me _years_ to undo the damage. I really dug myself into a hole, too, by learning all about pointers and low-level stuff like that before I ever knew what Python, Ruby, etc. were.
A functional-ish way to run a game world simulation:
(define (run-game)
(define (make-initial-world) ...)
(define (get-input) ...)
(define (wait-for-next-frame) ...)
(define (update-world world input) ...)
(define (game-over? world) ...)
(define (run-step world)
(display-world world)
(wait-for-next-frame)
(if (game-over? world)
'bye
(run-step (update-world world (get-input)))))
(run-step (make-initial-world)))
It's not purely functional: the calls to display-world and wait-for-next-frame are made for their side effects. But they are not mutating any program state, so this is good enough. You _can_ try and get more 'pure' with Haskell and monads when you feel brave, but there is plenty to grok before going there.
Let's study the parts:
make-initial-world: self-explanatory; I'll only add that this object is held 'in the stack' and (conceptually) never overwritten.
get-input returns the player actions, game time, and whatever other 'delta' data is relevant for the current update. You can think of the input over a whole game session as an indefinitely long list which you iterate over, one element (game update) at a time. You can indeed run automated tests replacing this data with an actual list; the program shouldn't notice the difference.
wait-for-next-frame causes a time delay that is conceptually irrelevant to the rest of your logic (you deal with system time, if at all, in the body of (get-input)). In test situations, you should be able to just replace this with a no-op.
game-over? tells you whether the player has quit. I make it a function of the world and not the input because the input is meaningless without the context that the world object represents.
(By this time you may have noticed that I'm being a bit sloppy with terminology, for color and simplicity. 'world' is not only the game world, but also the program state (e.g., is the user in a configuration screen?). Similarly, 'game over' is not the end of one game/'match' but of the playing session. The principles are the same.)
run-step is just a loop that keeps updating and displaying the world state until the game is over. Don't be scared by the recursive call to run-step itself; that's the idiomatic way to loop in Scheme, and implementations are required to handle it efficiently (google for 'tail recursion').
The crux of the problem is in update-world. This is an ordinary function that takes the world and the input for this game tick, and returns the world corresponding to the next tick.
You'd think there are only two options here: either modifying the existing world in place- definitely non-functional!-, or building a new one from scratch, which sounds inefficient.
Actually, there is much more to be said about purely functional data structures. In a nutshell, you can often make a new world that shares most of the data with an old one, so only the delta is created anew. References to the old world will not be affected at all by the new one, so you can keep as much history of your game as you have memory for. As a basic example, in Lisp you do this with lists all the time. But it can get more complex:
http://www.cs.cmu.edu/~rwh/theses/okasaki.pdf
(This thesis was expanded upon and published as a book by the same name, with examples in ML and Haskell. If someone knows of more resources about this, please let me know.)
Depending on the ratio of changing-data-per-frame versus total data in your application, this may be an interesting option. The learning curve is considerable, though.
But note that when update-world is called the old world will never be used again (most games don't keep the state of every past frame). So in this case it's OK to modify the old world in place and use that as the return value. The rest of the code doesn't care; again, you might as well be iterating over a stream of worlds (with the same desirable implications for testing).
If you do mutate the world, though, you may want to make it explicit by saying something like
(define (run-game)
...
(define (run-step)
(update-world! world (get-input))))
(display-world world)
...)
(define world (make-new-world))
(run-step))
This is harder to test and more work if you want to switch to a purely functional update-world later, but there's less risk you or others will try and use (or modify!) a world after it's been updated.
Part of the calculations _within_ update-world can still be done in a functional way, and where you need to mutate state, you can often hide that behind a functional interface, with varying degrees of hygiene.
The bottom line is you don't need religious avoidance of mutable state to benefit from functional programming at all; you strive for stateless code by default, you compromise where it makes sense, and you try to minimize the impact of such sins by wrapping functional interfaces, or placing warning signs, around them. The mere habit of paying attention to these matters will improve your taste.
There is a lot more to be said about this, but I hope you can already see how this is not an easy topic to introduce, out of the blue, via IRC. In all fairness to the Lisp folks, I have to say the question about Python packages was much more concrete and easier to address without both sides losing patience.