func main() { rand.Seed(time.Now().Unix()) logfile, err := os.Create("roguelike.log") if err != nil { panic(err) } log.SetOutput(logfile) initCurses() defer endCurses() currentLevel = level.New(level.Test) player = entity.NewPlayer() currentLevel.Put(player, 40, 13) drawCurrentLevel() drawInfoBar() for !quit { var a entity.Action switch ch := C.getch(); ch { case C.KEY_UP, 'k', '8': a = entity.MoveAction{direction.North} case C.KEY_DOWN, 'j', '2': a = entity.MoveAction{direction.South} case C.KEY_RIGHT, 'l', '6': a = entity.MoveAction{direction.East} case C.KEY_LEFT, 'h', '4': a = entity.MoveAction{direction.West} case 'y', '7': a = entity.MoveAction{direction.NorthWest} case 'u', '9': a = entity.MoveAction{direction.NorthEast} case 'm', '3': a = entity.MoveAction{direction.SouthEast} case 'n', '1': a = entity.MoveAction{direction.SouthWest} case 'i': updateInventory() displayInventory() C.getch() case ',': items := displayPickUpChoice() a = entity.PickUpAction{items} case 'q': quit = true } player.SetAction(a) currentLevel.Run() drawCurrentLevel() drawInfoBar() } }
func consume(expects ...rune) bool { for _, r := range expects { if int(C.getch()) != int(r) { return false } } return true }
func Hello(s string) { C.initscr() p := C.CString(fmt.Sprintf("Hello, %s", s)) C.Printw(p) C.free(unsafe.Pointer(p)) C.refresh() C.getch() C.endwin() }
func Init(theme *ColorTheme, black bool, mouse bool) { C.setlocale(C.LC_ALL, C.CString("")) tty := C.c_tty() if tty == nil { fmt.Println("Failed to open /dev/tty") os.Exit(2) } _screen = C.c_newterm(tty) if _screen == nil { fmt.Println("Invalid $TERM: " + os.Getenv("TERM")) os.Exit(2) } C.set_term(_screen) if mouse { C.mousemask(C.ALL_MOUSE_EVENTS, nil) C.mouseinterval(0) } C.noecho() C.raw() // stty dsusp undef C.nonl() C.keypad(C.stdscr, true) delay := 50 delayEnv := os.Getenv("ESCDELAY") if len(delayEnv) > 0 { num, err := strconv.Atoi(delayEnv) if err == nil && num >= 0 { delay = num } } C.set_escdelay(C.int(delay)) _color = theme != nil if _color { C.start_color() InitTheme(theme, black) initPairs(theme) C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal)))) _colorFn = attrColored } else { _colorFn = attrMono } C.nodelay(C.stdscr, true) ch := C.getch() if ch != C.ERR { C.ungetch(ch) } C.nodelay(C.stdscr, false) }
func escSequence() Event { C.nodelay(C.stdscr, true) defer func() { C.nodelay(C.stdscr, false) }() c := C.getch() switch c { case C.ERR: return Event{ESC, 0, nil} case CtrlM: return Event{AltEnter, 0, nil} case '/': return Event{AltSlash, 0, nil} case ' ': return Event{AltSpace, 0, nil} case 127, C.KEY_BACKSPACE: return Event{AltBS, 0, nil} case '[': // Bracketed paste mode (printf "\e[?2004h") // \e[200~ TEXT \e[201~ if consume('2', '0', '0', '~') { return Event{Invalid, 0, nil} } } if c >= 'a' && c <= 'z' { return Event{AltA + int(c) - 'a', 0, nil} } if c >= '0' && c <= '9' { return Event{Alt0 + int(c) - '0', 0, nil} } // Don't care. Ignore the rest. for ; c != C.ERR; c = C.getch() { } return Event{Invalid, 0, nil} }
func displayPickUpChoice() []entity.ID { var str *C.char C.clear() C.move(0, 0) str = C.CString("Pick up what?\n") C.addstr(str) C.free(unsafe.Pointer(str)) px, py := currentLevel.EntityLocation(player.EntityID()) itemsAvailable := currentLevel.ItemsAt(px, py) for i, eid := range itemsAvailable { C.addch(C.chtype(inventoryChar(byte(i)))) str = C.CString(" - ") C.addstr(str) C.free(unsafe.Pointer(str)) str = C.CString(currentLevel.EntityByID(eid).EntityName()) C.addstr(str) C.free(unsafe.Pointer(str)) } itemsChosen := make([]bool, len(itemsAvailable)) for { ch := C.getch() if ch == C.KEY_ENTER || ch == ' ' || ch == '\n' { break } if ch > C.int(255) { continue } if i := inventoryIndex(byte(ch)); (int(i) < len(itemsChosen)) && (i != 255) { if itemsChosen[i] { itemsChosen[i] = false C.mvaddch(C.int(i+1), 2, C.chtype('-')) } else { itemsChosen[i] = true C.mvaddch(C.int(i+1), 2, C.chtype('+')) } } } result := make([]entity.ID, 0, len(itemsAvailable)) for i, v := range itemsChosen { if v { result = append(result, itemsAvailable[i]) } } return result }
func (r *FullscreenRenderer) Init() { C.setlocale(C.LC_ALL, C.CString("")) tty := C.c_tty() if tty == nil { errorExit("Failed to open /dev/tty") } _screen = C.c_newterm(tty) if _screen == nil { errorExit("Invalid $TERM: " + os.Getenv("TERM")) } C.set_term(_screen) if r.mouse { C.mousemask(C.ALL_MOUSE_EVENTS, nil) C.mouseinterval(0) } C.noecho() C.raw() // stty dsusp undef C.nonl() C.keypad(C.stdscr, true) delay := 50 delayEnv := os.Getenv("ESCDELAY") if len(delayEnv) > 0 { num, err := strconv.Atoi(delayEnv) if err == nil && num >= 0 { delay = num } } C.set_escdelay(C.int(delay)) if r.theme != nil { C.start_color() initTheme(r.theme, r.defaultTheme(), r.forceBlack) initPairs(r.theme) C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal.index())))) _colorFn = attrColored } else { initTheme(r.theme, nil, r.forceBlack) _colorFn = attrMono } C.nodelay(C.stdscr, true) ch := C.getch() if ch != C.ERR { C.ungetch(ch) } C.nodelay(C.stdscr, false) }
func Getch() int { return int(C.getch()) }
func getch(termDescriptor int) byte { return byte(C.getch(C.int(termDescriptor))) }
func (r *FullscreenRenderer) GetChar() Event { c := C.getch() switch c { case C.ERR: // Unexpected error from blocking read r.Close() errorExit("Failed to read /dev/tty") case C.KEY_UP: return Event{Up, 0, nil} case C.KEY_DOWN: return Event{Down, 0, nil} case C.KEY_LEFT: return Event{Left, 0, nil} case C.KEY_RIGHT: return Event{Right, 0, nil} case C.KEY_HOME: return Event{Home, 0, nil} case C.KEY_END: return Event{End, 0, nil} case C.KEY_BACKSPACE: return Event{BSpace, 0, nil} case C.KEY_F0 + 1: return Event{F1, 0, nil} case C.KEY_F0 + 2: return Event{F2, 0, nil} case C.KEY_F0 + 3: return Event{F3, 0, nil} case C.KEY_F0 + 4: return Event{F4, 0, nil} case C.KEY_F0 + 5: return Event{F5, 0, nil} case C.KEY_F0 + 6: return Event{F6, 0, nil} case C.KEY_F0 + 7: return Event{F7, 0, nil} case C.KEY_F0 + 8: return Event{F8, 0, nil} case C.KEY_F0 + 9: return Event{F9, 0, nil} case C.KEY_F0 + 10: return Event{F10, 0, nil} case C.KEY_F0 + 11: return Event{F11, 0, nil} case C.KEY_F0 + 12: return Event{F12, 0, nil} case C.KEY_DC: return Event{Del, 0, nil} case C.KEY_PPAGE: return Event{PgUp, 0, nil} case C.KEY_NPAGE: return Event{PgDn, 0, nil} case C.KEY_BTAB: return Event{BTab, 0, nil} case C.KEY_ENTER: return Event{CtrlM, 0, nil} case C.KEY_SLEFT: return Event{SLeft, 0, nil} case C.KEY_SRIGHT: return Event{SRight, 0, nil} case C.KEY_MOUSE: var me C.MEVENT if C.getmouse(&me) != C.ERR { mod := ((me.bstate & C.BUTTON_SHIFT) | (me.bstate & C.BUTTON_CTRL) | (me.bstate & C.BUTTON_ALT)) > 0 x := int(me.x) y := int(me.y) /* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */ if (me.bstate & C.BUTTON1_PRESSED) > 0 { now := time.Now() if now.Sub(r.prevDownTime) < doubleClickDuration { r.clickY = append(r.clickY, y) } else { r.clickY = []int{y} r.prevDownTime = now } return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}} } else if (me.bstate & C.BUTTON1_RELEASED) > 0 { double := false if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] && time.Now().Sub(r.prevDownTime) < doubleClickDuration { double = true } return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}} } else if (me.bstate&0x8000000) > 0 || (me.bstate&0x80) > 0 { return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}} } else if (me.bstate & C.BUTTON4_PRESSED) > 0 { return Event{Mouse, 0, &MouseEvent{y, x, 1, false, false, mod}} } } return Event{Invalid, 0, nil} case C.KEY_RESIZE: return Event{Resize, 0, nil} case ESC: return escSequence() case 127: return Event{BSpace, 0, nil} } // CTRL-A ~ CTRL-Z if c >= CtrlA && c <= CtrlZ { return Event{int(c), 0, nil} } // Multi-byte character buffer := []byte{byte(c)} for { r, _ := utf8.DecodeRune(buffer) if r != utf8.RuneError { return Event{Rune, r, nil} } c := C.getch() if c == C.ERR { break } if c >= C.KEY_CODE_YES { C.ungetch(c) break } buffer = append(buffer, byte(c)) } return Event{Invalid, 0, nil} }
func getch() byte { return byte(C.getch()) }