From ef9cb8df9ee65af487752e7c25624383c0962944 Mon Sep 17 00:00:00 2001 From: imw Date: Sat, 26 Feb 2022 13:32:09 +0100 Subject: [PATCH] Add Test Engine (#3) * add test setup and game engine * set debug to false during tests * fix nil pointer bug, rename rpc --- board/square.go | 3 +- display/display.go | 15 ++---- game/game.go | 22 ++++++--- main.go | 113 ++++++++++++++++++++++++++++++--------------- main_test.go | 88 +++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 55 deletions(-) create mode 100644 main_test.go diff --git a/board/square.go b/board/square.go index 8055aa7..45b8ccb 100644 --- a/board/square.go +++ b/board/square.go @@ -24,8 +24,9 @@ func (s *Square) Name() string { func (s *Square) SetOccupant(p *Piece) { if p == nil { s.occupant = nil + } else { + s.occupant = *p } - s.occupant = *p } //Occupant returns an occupying piece, if any diff --git a/display/display.go b/display/display.go index 2bc470e..34e3f4c 100644 --- a/display/display.go +++ b/display/display.go @@ -2,6 +2,7 @@ package display import ( "0x539.lol/rtc/board" + "0x539.lol/rtc/game" "0x539.lol/rtc/util" "github.com/gdamore/tcell/v2" ) @@ -27,17 +28,6 @@ func Loading(s tcell.Screen) { s.Show() } -//Greeting displays a ready greeting -func Greeting(s tcell.Screen) { - w, h := s.Size() - s.Clear() - style := tcell.StyleDefault.Foreground(tcell.ColorCadetBlue.TrueColor()).Background(tcell.ColorWhite) - drawText(s, w/2-8, h/2, w/2+8, h/2, style, "REAL TIME CHESS") - drawText(s, w/2-9, h/2+1, w/2+9, h/2+1, tcell.StyleDefault, "Press ESC to exit.") - drawText(s, w/2-12, h/2+2, w/2+12, h/2+2, tcell.StyleDefault, "Press any key to begin.") - s.Show() -} - func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) { row := y1 col := x1 @@ -76,7 +66,8 @@ func drawSquare(s tcell.Screen, x1, y1, x2, y2 int, boardStyle tcell.Style, piec } //Render updates a board drawing from a game board -func Render(b *board.Board, s tcell.Screen) { +func Render(g *game.Game, s tcell.Screen) { + b := g.GetBoard() s.Clear() defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset) s.SetStyle(defStyle) diff --git a/game/game.go b/game/game.go index c1e707e..00bacad 100644 --- a/game/game.go +++ b/game/game.go @@ -7,6 +7,7 @@ import ( "0x539.lol/rtc/board" "0x539.lol/rtc/util" + "github.com/gdamore/tcell/v2" ) //Game encapsulates data about a game session @@ -17,23 +18,32 @@ type Game struct { } //New returns a new Game struct and GameRPC client -func New(b *board.Board) (*Game, *RPC) { +func New(b *board.Board) (*Game, *GameRPC) { g := &Game{ board: b, } - gr := &RPC{ + gr := &GameRPC{ game: g, } return g, gr } +func (g *Game) GetBoard() *board.Board { + return g.board +} + +func (g *Game) RecieveEvent(ev *tcell.EventKey) board.Move { + b := g.GetBoard() + return b.ProcessEvent(ev) +} + //SetClient updates the rpc client for a game func (g *Game) SetClient(c *rpc.Client) { g.client = c } -//RPC is an RPC wrapper for the Game structure -type RPC struct { +//GameRPC is an RPC wrapper for the Game structure +type GameRPC struct { game *Game } @@ -54,7 +64,7 @@ func (g *Game) Peered() bool { } //DoMove executes a move against a local game board -func (gr *RPC) DoMove(m board.Move, exit *int) error { +func (gr *GameRPC) DoMove(m board.Move, exit *int) error { util.Write(fmt.Sprintf("Local move: %v", m)) loc := gr.game.board.Position(m.Loc) tgt := gr.game.board.Position(m.Tgt) @@ -75,7 +85,7 @@ func (gr *RPC) DoMove(m board.Move, exit *int) error { } //Register updates the peerID for a game session -func (gr *RPC) Register(clientID string, exit *int) error { +func (gr *GameRPC) Register(clientID string, exit *int) error { gr.game.peerID = clientID return nil } diff --git a/main.go b/main.go index 1397dc4..9d9805c 100644 --- a/main.go +++ b/main.go @@ -29,17 +29,31 @@ type Config struct { Debug bool `default:"false"` } -func main() { +type GameEngine struct { + ID string + game *game.Game + rpc *game.GameRPC + listener net.Listener +} +type ExitCode int + +const ( + Success ExitCode = iota + Failure +) + +func main() { var c Config err := envconfig.Process("rtc", &c) if err != nil { log.Fatal(err.Error()) } - util.Debug = c.Debug + os.Exit(int(entrypoint(c))) +} - util.Write(fmt.Sprintf("Config: %v", c)) +func setup(c Config) (GameEngine, error) { var b *board.Board @@ -66,26 +80,67 @@ func main() { //register RPC rpc.Register(gr) + + mux := http.NewServeMux() + http.DefaultServeMux = mux rpc.HandleHTTP() + l, e := net.Listen("tcp", localhost+":"+localport) if e != nil { log.Fatal("listen error:", e) } go http.Serve(l, nil) - defer l.Close() util.Write(fmt.Sprintf("Listening with listener: %v", l)) + dialCount := 1 + for { + //init client for peer + util.Write(fmt.Sprintf("Dial #%d", dialCount)) + client, e := rpc.DialHTTP("tcp", remotehost+":"+remoteport) + if e != nil { + util.Write(fmt.Sprintf("error attempting to dial %s:%s: %s", remotehost, remoteport, e)) + if dialCount > 10 { + return GameEngine{}, e + } + } else { + g.SetClient(client) + break + } + dialCount = dialCount + 1 + /* + switch ev := s.PollEvent().(type) { + case *tcell.EventKey: + if ev.Key() == tcell.KeyEscape { + s.Fini() + return Success + } + } + */ + time.Sleep(time.Duration(1) * time.Second) + } + + return GameEngine{c.ID, g, gr, l}, nil + +} + +func entrypoint(c Config) ExitCode { + + util.Debug = c.Debug + util.Write(fmt.Sprintf("Config: %v", c)) + //setup display encoding.Register() s, e := tcell.NewScreen() if e != nil { fmt.Fprintf(os.Stderr, "%v\n", e) - os.Exit(1) + return Failure } + if e := s.Init(); e != nil { fmt.Fprintf(os.Stderr, "%v\n", e) - os.Exit(1) + return Failure } + defStyle := tcell.StyleDefault. Background(tcell.ColorBlack). Foreground(tcell.ColorWhite) @@ -93,35 +148,19 @@ func main() { display.Loading(s) - dialCount := 1 - for { - //init client for peer - util.Write(fmt.Sprintf("Dial #%d", dialCount)) - client, err := rpc.DialHTTP("tcp", remotehost+":"+remoteport) - if err != nil { - util.Write(fmt.Sprintf("error attempting to dial %s:%s: %s", remotehost, remoteport, err)) - } else { - g.SetClient(client) - break - } - dialCount = dialCount + 1 - switch ev := s.PollEvent().(type) { - case *tcell.EventKey: - if ev.Key() == tcell.KeyEscape { - s.Fini() - os.Exit(0) - } - } - time.Sleep(time.Duration(1) * time.Second) + engine, e := setup(c) + if e != nil { + fmt.Fprintf(os.Stderr, "%v\n", e) + return Failure } + defer engine.listener.Close() - display.Greeting(s) - go func(b *board.Board, s tcell.Screen) { + go func(g *game.Game, s tcell.Screen) { t := time.NewTicker(10 * time.Millisecond) for range t.C { - display.Render(b, s) + display.Render(g, s) } - }(b, s) + }(engine.game, s) for { switch ev := s.PollEvent().(type) { @@ -130,16 +169,18 @@ func main() { case *tcell.EventKey: if ev.Key() == tcell.KeyEscape { s.Fini() - os.Exit(0) + return Failure } else { - move := b.ProcessEvent(ev) - var exit int + util.Write(fmt.Sprintf("Event registered: %v", ev)) + move := engine.game.RecieveEvent(ev) + util.Write(fmt.Sprintf("Move generated: %v", move)) + var rpcExitCode int if move.Seq > 0 { - g.SendMove(move) - gr.DoMove(move, &exit) + engine.game.SendMove(move) + engine.rpc.DoMove(move, &rpcExitCode) } - display.Render(b, s) + display.Render(engine.game, s) } } } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..07f109a --- /dev/null +++ b/main_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "testing" + + "github.com/gdamore/tcell/v2" +) + +func TestEngine(t *testing.T) { + configA := Config{ + HostA: "localhost", + PortA: "1234", + HostB: "localhost", + PortB: "4321", + ID: "A", + Debug: false, + } + + configB := configA + configB.ID = "B" + + ready := make(chan GameEngine) + go doSetup(t, configA, ready) + go doSetup(t, configB, ready) + + var engineA *GameEngine + var engineB *GameEngine + + waiting := 2 + for { + engine := <-ready + waiting = waiting - 1 + if engine.ID == "A" { + engineA = &engine + } + engineB = &engine + if waiting == 0 { + break + } + } + + defer engineA.listener.Close() + defer engineB.listener.Close() + + actions := []struct { + engine *GameEngine + key tcell.Key + }{ + {engineA, tcell.KeyEnter}, + {engineA, tcell.KeyUp}, + {engineA, tcell.KeyUp}, + {engineA, tcell.KeyEnter}, + {engineB, tcell.KeyLeft}, + {engineB, tcell.KeyEnter}, + {engineB, tcell.KeyDown}, + {engineB, tcell.KeyDown}, + } + + for _, action := range actions { + ev := generateKeyPress(action.key) + t.Logf("Executing move %s on engine %s", ev.Name(), action.engine.ID) + t.Logf("Got tcell ev %v", ev) + move := action.engine.game.RecieveEvent(ev) + t.Logf("Got move: %v", move) + t.Logf("Game is %v", action.engine.game) + t.Logf("Board is %v", action.engine.game.GetBoard()) + if move.Seq > 0 { + action.engine.game.SendMove(move) + var rpcExitCode int + action.engine.rpc.DoMove(move, &rpcExitCode) + } + } + +} + +func doSetup(t *testing.T, config Config, ready chan<- GameEngine) { + t.Logf("Setting up with config: %v", config) + running, e := setup(config) + if e != nil { + t.Errorf("Error setting up engine. Config: %v, Error: %v", config, e) + } + t.Logf("Setup succeeded for %s", config.ID) + ready <- running +} + +func generateKeyPress(k tcell.Key) *tcell.EventKey { + return tcell.NewEventKey(k, 0, tcell.ModNone) +}