problem using goncurses in tetris clone

200 views
Skip to first unread message

nicka...@gmail.com

unread,
Jul 11, 2013, 9:51:05 PM7/11/13
to golan...@googlegroups.com
I'm writing a tetris clone I'm calling "grin" using the goncurses curses library.  The game mostly works and it's pretty playable, but every now and then garbage will appear on screen.  Presumably, this is due to ncurses lack of support for concurrency, described here: https://code.google.com/p/goncurses/wiki/Concurrency

It isn't reliably reproducible, but usually comes up in a minute or two of play.  I've tried to work around it using a select statement, and then using a mutex, but the problem still comes up.  I'm new to programming in Go, so I'm hoping someone can point out where I'm going wrong.

I use a goroutine for the keyboard input and a goroutine for the timer that causes the block to drop every few ticks.  The goroutes each communicate with the main program through a different channel.  The ncurses object is passed to the keyboard goroutine.  The timer goroutine just sleeps.

Here are the relevant parts of my program:

func main() {

// declare variables, etc

// mutex
var nlock struct {
sync.Mutex
}

// keyboard channel - keyboard input comes through here
ck := make(chan int)
go keys_in(stdscr, ck, nlock)

// timer channel - tetris timer to drop the block comes through here
ct := make(chan int)
go t_timer(ct, 1, nlock)

// main loop of the game
for keep_going := true; keep_going == true; {

// wait for either keyboard input or timer
select {
case somechar = <-ct:
action = "timeout"
case somechar = <-ck:
action = "keyboard"
}

nlock.Lock()
// game logic and drawing blocks comes here
// nothing until the end of the loop calls on the channels or the goroutines
nlock.Unlock()

switch {
case action == "timeout":
go t_timer(ct, speed, nlock)
case action == "keyboard":
go keys_in(stdscr, ck, nlock)
}

}

}

func keys_in(stdscr gc.Window, ck chan int, nlock struct{ sync.Mutex }) {
nlock.Lock()
somechar := int(stdscr.GetChar())
ck <- somechar
nlock.Unlock()
}

func t_timer(ct chan int, speed int, nlock struct{ sync.Mutex }) {
mseconds := time.Duration(1000 / speed)
time.Sleep(mseconds * time.Millisecond)
nlock.Lock()
ct <- 110 // drop the block
nlock.Unlock()
}


The whole this is here: https://github.com/nickaubert/grin

Thanks.

mortdeus

unread,
Jul 12, 2013, 12:42:54 AM7/12/13
to golan...@googlegroups.com, nicka...@gmail.com
Use termbox. 

Kamil Kisiel

unread,
Jul 12, 2013, 2:50:02 AM7/12/13
to golan...@googlegroups.com, nicka...@gmail.com
Try compiling your program with the -race flag to see if the race detector can help you turn up the source of the problem.

In general I would suggest designing something like that with a single goroutine that deals with everything ncurses-related and communicates with other parts of the program via channels.

Carlos Castillo

unread,
Jul 12, 2013, 4:29:44 AM7/12/13
to golan...@googlegroups.com, nicka...@gmail.com
I concur with the termbox suggestion, and add a useful link: http://godoc.org/github.com/nsf/termbox-go

Unless you need the power of ncurses, termbox-go is simpler, requires no external library (ie: it's pure go), is cross platform (linux/osx/windows), and much more amenable to concurrency (you should still test for data-races though).

If you want to stick to using goncurses, I also noticed the following:
  • Several methods which take a gc.Window but not the lock. If goncurses is not thread-safe, then all ncurses calls should be protected by the mutex
  • You've defined an anonymous struct type that embeds a sync.Mutex, that seems redundant and unnecessary.
My suggestion would be to write your own functions that act on a global lock & ncurses objects, or methods on a struct that contain both entities, and put the resulting code in a new file. This way your tetris-logic is separated from your ncurses code, so it's easier to write both, and ensure that you are locking access to ncurses properly. If you create a type with a useful interface, you can re-use it in another project (or at least look at it easier), or you can build an implementation that uses a different backend (say termbox, or OpenGL) and swap it in later.

Nick Aubert

unread,
Jul 12, 2013, 7:44:02 AM7/12/13
to golan...@googlegroups.com, Carlos Castillo

Termbox looks good.  I'll give it a try.  Thanks!
Reply all
Reply to author
Forward
0 new messages