// FromLinesAndFormats generates a *VT100 whose state is set according // to s (for content) and a (for attributes). // // Dimensions are set to the width of s' first line and the height of the // number of lines in s. // // If a is nil, the default attributes are used. func FromLinesAndFormats(s string, a [][]vt100.Format) *vt100.VT100 { lines := strings.Split(s, "\n") v := vt100.NewVT100(len(lines), utf8.RuneCountInString(lines[0])) for y := 0; y < v.Height; y++ { x := 0 for _, r := range lines[y] { v.Content[y][x] = r if a != nil { v.Format[y][x] = a[y][x] } x++ } } return v }
// NewGame makes a new Game. The game immediately starts parsing the Nethack input // stream and building a model of the world. It also takes over control of the output // stream to nethack. From this point, your only interaction with these IO objects // should be through this instance of Game. // // It is safe to examine this between calls to Do, but not during any given Do // call. func NewGame(in io.Reader, out io.Writer, win WindowSize) (*Game, error) { if win.Y < 24 || win.X < 80 { panic(fmt.Errorf("screen dimensions must be at least 24x80 (got: %dx%d)", win.Y, win.X)) } cmds, errs := inputUntilClosed(in) g := &Game{ Level: make(map[level.LevelID]*level.Level), Events: NewTurnEventsList(), out: out, vt: vt100.NewVT100(win.Y, win.X), inputCommands: cmds, inputErrs: errs, } return g, g.waitIdle() }
func main() { flag.Parse() origAttr, err := makeRaw(os.Stdout.Fd()) if err != nil { panic(err) } defer func() { if err := tcsetattr(os.Stdout.Fd(), origAttr); err != nil { glog.Error(err) // Nothing much we can do about this error. } }() c := exec.Command("nethack") c.Env = append(c.Env, os.Environ()...) // I have no idea if this is right, but it seems to work. c.Env = append(c.Env, "TERM=xterm") pty, err := pty.Start(c) if err != nil { panic(err) } v := vt{vt100.NewVT100(24, 80), new(sync.Mutex)} vtexport.Export("/debug/vt100", v.VT100, v) server := http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: http.DefaultServeMux, } vReaderRaw, vWriter := io.Pipe() vReader := bufio.NewReader(vReaderRaw) dupOut, err := ioutil.TempFile("", "nh_output.txt") if err != nil { panic(err) } exit := make(chan struct{}, 0) go func() { for { _, err := io.Copy(pty, os.Stdin) if err != nil { if err != io.EOF { glog.Error(err) } return } } }() go func() { defer func() { exit <- struct{}{} }() multi := io.MultiWriter(os.Stdout, dupOut, vWriter) for { _, err := io.Copy(multi, pty) if err != nil { if err != io.EOF { glog.Error(err) } return } } }() go func() { for { cmd, err := vt100.Decode(vReader) if err == nil { v.Lock() err = v.Process(cmd) v.Unlock() } if err == nil { continue } if _, isUnsupported := err.(vt100.UnsupportedError); isUnsupported { // This gets exported through an expvar. continue } if err != io.EOF { glog.Error(err) } return } }() downServer, err := httpdown.HTTP{ StopTimeout: time.Second * 5, }.ListenAndServe(&server) if err != nil { panic(err) } <-exit downServer.Stop() downServer.Wait() }