`)
for i := 0; i < 7; i++ {
sb.WriteString(fmt.Sprintf(`
`, i))
- for j := 0; j < 6; j++ {
- switch board.board[i][j] {
+ for j := 5; j >= 0; j-- {
+ switch board.game.board[i][j] {
case CircleTypeRed:
sb.WriteString(`
`)
case CircleTypeBlack:
@@ -120,3 +170,84 @@ func (board *Board) Render() string {
sb.WriteString("
")
return sb.String()
}
+
+const (
+ BoardWidth = 7
+ BoardHeight = 6
+)
+
+type Game struct {
+ board [BoardWidth][BoardHeight]CircleType
+ winner CircleType
+}
+
+func (g *Game) moveNumber() int {
+ var count int
+ for _, column := range g.board {
+ for _, square := range column {
+ if square != CircleTypeNone {
+ count++
+ }
+ }
+ }
+ return count
+}
+func (g *Game) whosMove() CircleType {
+ if g.moveNumber()%2 == 0 {
+ return CircleTypeRed
+ }
+ return CircleTypeBlack
+}
+
+func (g *Game) didTheyWin(circle CircleType) bool {
+ // Taken from: https://stackoverflow.com/a/38211417/1333724
+ for j := 0; j < BoardHeight-3; j++ {
+ for i := 0; i < BoardWidth; i++ {
+ if g.board[i][j] == circle && g.board[i][j+1] == circle && g.board[i][j+2] == circle && g.board[i][j+3] == circle {
+ return true
+ }
+ }
+ }
+ for i := 0; i < BoardWidth-3; i++ {
+ for j := 0; j < BoardHeight; j++ {
+ if g.board[i][j] == circle && g.board[i+1][j] == circle && g.board[i+2][j] == circle && g.board[i+3][j] == circle {
+ return true
+ }
+ }
+ }
+ for i := 3; i < BoardWidth; i++ {
+ for j := 0; j < BoardHeight-3; j++ {
+ if g.board[i][j] == circle && g.board[i-1][j+1] == circle && g.board[i-2][j+2] == circle && g.board[i-3][j+3] == circle {
+ return true
+ }
+ }
+ }
+ for i := 3; i < BoardWidth; i++ {
+ for j := 3; j < BoardHeight; j++ {
+ if g.board[i][j] == circle && g.board[i-1][j-1] == circle && g.board[i-2][j-2] == circle && g.board[i-3][j-3] == circle {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func (g *Game) play(circle CircleType, column int) (winner CircleType) {
+ if g.winner != CircleTypeNone {
+ return
+ }
+ if circle != g.whosMove() {
+ return
+ }
+ for i, square := range g.board[column] {
+ if square == CircleTypeNone {
+ g.board[column][i] = circle
+ if g.didTheyWin(circle) {
+ g.winner = circle
+ return circle
+ }
+ break
+ }
+ }
+ return CircleTypeNone
+}
diff --git a/examples/connect4/main_test.go b/examples/connect4/main_test.go
new file mode 100644
index 0000000..5d18d42
--- /dev/null
+++ b/examples/connect4/main_test.go
@@ -0,0 +1,100 @@
+package main
+
+import (
+ "testing"
+)
+
+func TestGame_didTheyWin(t *testing.T) {
+ type args struct {
+ circle CircleType
+ column int
+ row int
+ }
+ tests := []struct {
+ name string
+ board [7][6]CircleType
+ args args
+ want bool
+ }{
+ {
+ name: "no win",
+ board: [7][6]CircleType{},
+ args: args{
+ circle: CircleTypeRed,
+ column: 0,
+ row: 0,
+ },
+ want: false,
+ },
+ {
+ name: "across columns",
+ board: [7][6]CircleType{
+ {CircleTypeRed},
+ {CircleTypeRed},
+ {CircleTypeRed},
+ {CircleTypeRed},
+ },
+ args: args{
+ circle: CircleTypeRed,
+ column: 0,
+ row: 0,
+ },
+ want: true,
+ },
+ {
+ name: "down a column",
+ board: [7][6]CircleType{
+ {},
+ {CircleTypeBlack, CircleTypeBlack, CircleTypeBlack, CircleTypeBlack},
+ },
+ args: args{
+ circle: CircleTypeBlack,
+ column: 1,
+ row: 0,
+ },
+ want: true,
+ },
+ {
+ name: "diag1",
+ board: [7][6]CircleType{
+ {},
+ {CircleTypeBlack},
+ {CircleTypeNone, CircleTypeBlack},
+ {CircleTypeNone, CircleTypeNone, CircleTypeBlack},
+ {CircleTypeNone, CircleTypeNone, CircleTypeNone, CircleTypeBlack},
+ },
+ args: args{
+ circle: CircleTypeBlack,
+ column: 1,
+ row: 0,
+ },
+ want: true,
+ },
+ {
+ name: "diag2",
+ board: [7][6]CircleType{
+ {},
+ {CircleTypeNone, CircleTypeNone, CircleTypeNone, CircleTypeBlack},
+ {CircleTypeNone, CircleTypeNone, CircleTypeBlack},
+ {CircleTypeNone, CircleTypeBlack},
+ {CircleTypeBlack},
+ },
+ args: args{
+ circle: CircleTypeBlack,
+ column: 1,
+ row: 0,
+ },
+ want: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := &Game{
+ board: tt.board,
+ }
+ if got := g.didTheyWin(tt.args.circle, tt.args.column, tt.args.row); got != tt.want {
+ t.Errorf("Game.didTheyWin() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/internal/messages/messages.go b/internal/messages/messages.go
index 383f13a..8addcad 100644
--- a/internal/messages/messages.go
+++ b/internal/messages/messages.go
@@ -47,7 +47,7 @@ func (sm ServerMessage) Serialize() ([]byte, error) {
}
if sm.Data == nil {
- return nil, nil
+ return marshal(onWire)
}
onWire = append(onWire, nil)
// we must use our internal marshal so that html is not escaped
@@ -57,7 +57,7 @@ func (sm ServerMessage) Serialize() ([]byte, error) {
}
if sm.ComponentID == "" {
- return nil, nil
+ return marshal(onWire)
}
onWire = append(onWire, nil)
onWire[2], err = json.Marshal(sm.ComponentID)
diff --git a/websocket.go b/websocket.go
index afe69b8..d907dbc 100644
--- a/websocket.go
+++ b/websocket.go
@@ -44,8 +44,9 @@ type componentRenderer struct {
type liveComponent struct {
renderer Renderer
+ conn *websocket.Conn
tree *html.Node
-
+ session Session
// need to check if we've seen the subcomponent before
// need to take an action from a server and direct it to the right subcomponent
// need to select an existing index for rendering
@@ -54,6 +55,31 @@ type liveComponent struct {
counter int
}
+func newLiveComponent(r Renderer, session Session) (*liveComponent, error) {
+ var buf bytes.Buffer
+ lc, err := renderOnce(r, &buf)
+ if err != nil {
+ return nil, err
+ }
+ node, err := parseHTMLToNode(buf.String())
+ if err != nil {
+ return nil, err
+ }
+ lc.tree = node
+ lc.session = session
+ // printNode(lc.tree, 0)
+ return lc, nil
+}
+
+func renderOnce(r Renderer, w io.Writer) (*liveComponent, error) {
+ lc := liveComponent{
+ renderer: r,
+ subcomponents: map[uintptr]componentRenderer{},
+ idMap: map[int]uintptr{},
+ }
+ return &lc, lc.render(r, w)
+}
+
func (lc *liveComponent) render(renderer Renderer, w io.Writer) error {
t := template.New("").Funcs(template.FuncMap{
"add": add,
@@ -96,30 +122,6 @@ func (lc *liveComponent) renderFn(i interface{}) (interface{}, error) {
return buf.String(), nil
}
-func renderOnce(r Renderer, w io.Writer) (*liveComponent, error) {
- lc := liveComponent{
- renderer: r,
- subcomponents: map[uintptr]componentRenderer{},
- idMap: map[int]uintptr{},
- }
- return &lc, lc.render(r, w)
-}
-
-func newLiveComponent(r Renderer) (*liveComponent, error) {
- var buf bytes.Buffer
- lc, err := renderOnce(r, &buf)
- if err != nil {
- return nil, err
- }
- node, err := parseHTMLToNode(buf.String())
- if err != nil {
- return nil, err
- }
- lc.tree = node
- // printNode(lc.tree, 0)
- return lc, nil
-}
-
func (lc *liveComponent) diff() ([]Patch, error) {
old := lc.tree
var buf bytes.Buffer
@@ -139,14 +141,15 @@ func (wss *websocketSession) sendError(err error) {
if err == nil {
return
}
+ spew.Dump(err)
msg := messages.ServerMessage{Type: messages.ServerTypeError, Data: []string{err.Error()}}
b, err := msg.Serialize()
if err != nil {
// TODO
panic(err)
}
-
// TODO: confirm this error is likely just a broken conn and nothing more we should be worried about
+ fmt.Println("sending error", string(b))
_ = wss.conn.WriteMessage(websocket.TextMessage, b)
}
@@ -203,13 +206,15 @@ func (wss *websocketSession) handleClientMessage(msg messages.ClientMessage) err
}
renderer := rendererFunc()
var err error
- wss.components[componentID], err = newLiveComponent(renderer)
+ var session Session = &scopedSession{wss: wss, componentID: componentID}
+ lc, err := newLiveComponent(renderer, session)
+ wss.components[componentID] = lc
if err != nil {
return err
}
onmounter, ok := renderer.(OnMounter)
if ok {
- onmounter.OnMount(wss.req)
+ onmounter.OnMount(session)
return wss.reRenderComponent(componentID)
}
}
@@ -252,6 +257,17 @@ func (wss *websocketSession) handleClientMessage(msg messages.ClientMessage) err
return nil
}
+type scopedSession struct {
+ wss *websocketSession
+ componentID string
+}
+
+func (ss *scopedSession) Render() {
+ if err := ss.wss.reRenderComponent(ss.componentID); err != nil {
+ ss.wss.sendError(err)
+ }
+}
+
func (wss *websocketSession) reRenderComponent(componentID string) error {
component, ok := wss.components[componentID]
if !ok {
@@ -274,5 +290,6 @@ func (wss *websocketSession) reRenderComponent(componentID string) error {
if err != nil {
return err
}
+ fmt.Println("sending diff", string(b))
return wss.conn.WriteMessage(websocket.TextMessage, b)
}