No - threads aren't necessary.
Real concurrency was implemented as a performance hack. You should never use concurrency* except when you need the performance boost it may offer. You almost certainly shouldn't use it to apparently do more than one thing at once.
The difference is that instead of blocking to wait to see if the user presses keys, you will
poll to see if any events are waiting for you to deal with. If there are, you can handle them, but if not, you will continue.
Games (well,
event-based programs in general) usually have a
main loop. Such a loop might look like this:
1 2 3 4 5 6 7 8 9
|
loop forever
do
for each unhandled event
do
handle the event
done
update the screen
run a program tick (i.e., update everything that you need to)
done
|
There's other ways to do this, too, but notice that because calls to
std::istream::operator>>
(Of which
cin >> garbage
is an example) block while waiting for input, you will not be able to use them unless you get a bit clever -- you need to put your terminal in
raw mode so that you will be able to receive characters as the user enters them.
You should consider using
ncurses if you need to do this sort of GUI programming in the terminal. It will make things easier for you by hiding the required platform-specific code behind a uniform API.
*You can be a bit looser with things that mock concurrency like certain forms of continuation-passing and coroutines, since they won't require adding the error-prone synchronization code required to implement true concurrency.