Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cave.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ import (
type OnSubmiter interface {
OnSubmit(name string, form map[string]string)
}

type OnClicker interface {
OnClick(name string)
}
type Renderer interface {
Render() string
}

// MAYBE
type OnMounter interface {
OnMount(req *http.Request)
OnMount(Session)
}

type Form struct{}
type Session interface {
Render()
}

type Cave struct {
template *template.Template
Expand Down
2 changes: 1 addition & 1 deletion cave_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestCaveBasic(t *testing.T) {

func TestCaveDiff(t *testing.T) {
tc := NewTestComponent()
ls, err := newLiveComponent(tc)
ls, err := newLiveComponent(tc, nil)
if err != nil {
t.Fatal(err)
}
Expand Down
177 changes: 154 additions & 23 deletions examples/connect4/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@ package main
import (
"fmt"
"log"
"math/rand"
"strconv"
"strings"

"github.com/gin-gonic/gin"
"github.com/maxmcd/cave"
)

func main() {

gm := GameMaster{}
gm.startGameMaster()

r := gin.Default()
cavern := cave.New()
if err := cavern.AddTemplateFile("main", "layout.html"); err != nil {
log.Fatal(err)
}
cavern.AddComponent("main", NewConnect4)
cavern.AddComponent("main", gm.NewConnect4)

r.Use(func(c *gin.Context) {
_, ok := c.Request.URL.Query()["cavews"]
if ok {
if _, ok := c.Request.URL.Query()["cavews"]; ok {
cavern.ServeWS(c.Writer, c.Request)
c.Abort()
}
Expand All @@ -30,33 +35,69 @@ func main() {
panic(err)
}
})
r.GET("/bundle.js", func(c *gin.Context) {
cavern.ServeJS(c.Writer, c.Request)
})
r.GET("/bundle.js.map", func(c *gin.Context) {
cavern.ServeJS(c.Writer, c.Request)
})
r.GET("/bundle.js", func(c *gin.Context) { cavern.ServeJS(c.Writer, c.Request) })
r.GET("/bundle.js.map", func(c *gin.Context) { cavern.ServeJS(c.Writer, c.Request) })
log.Fatal(r.Run())
}

func NewConnect4() cave.Renderer {
return &Connect4{}
type GameMaster struct {
playerRequests chan *Connect4
}

func (gm *GameMaster) startGameMaster() {
gm.playerRequests = make(chan *Connect4)
go func() {
for {
first := <-gm.playerRequests
second := <-gm.playerRequests
fmt.Println("got two players", first, second)
first.Opponent = second
second.Opponent = first
side := rand.Intn(1)
first.Board = &BoardView{parent: first}
second.Board = &BoardView{parent: second}
if side == 1 {
first.Board.Side = CircleTypeBlack
second.Board.Side = CircleTypeRed
} else {
first.Board.Side = CircleTypeRed
second.Board.Side = CircleTypeBlack
}
game := &Game{}
first.Board.game = game
second.Board.game = game
first.session.Render()
second.session.Render()
}
}()
}
func (gm *GameMaster) NewConnect4() cave.Renderer {
return &Connect4{
playerRequests: gm.playerRequests,
}
}

type Connect4 struct {
Username string
Board *Board
Username string
Board *BoardView
session cave.Session
Opponent *Connect4
playerRequests chan *Connect4
}

var (
_ cave.OnSubmiter = new(Connect4)
_ cave.Renderer = new(Connect4)
)

func (tda *Connect4) OnMount(session cave.Session) {
tda.session = session
}

func (tda *Connect4) OnSubmit(name string, form map[string]string) {
if name == "username" {
tda.Username = form["username"]
tda.Board = &Board{}
tda.playerRequests <- tda
}
}
func (tda *Connect4) Render() string {
Expand All @@ -65,7 +106,12 @@ func (tda *Connect4) Render() string {
<h3>Connect4</h3>
{{ if .Username -}}
<p><b>username:</b> {{.Username}}</p>
{{ render .Board }}
{{ if .Board }}
Playing against {{ .Opponent.Username }}
{{ render .Board }}
{{ else }}
Waiting for another player to join...
{{- end}}
{{- end}}
{{ if eq .Username "" }}
<form cave-submit=username>
Expand All @@ -88,25 +134,29 @@ const (
CircleTypeBlack
)

type Board struct {
board [7][6]CircleType
type BoardView struct {
game *Game
Side CircleType
parent *Connect4
}

var (
_ cave.OnClicker = new(Board)
_ cave.OnClicker = new(BoardView)
)

func (board *Board) OnClick(name string) {
board.board[2][4] = CircleTypeBlack
func (board *BoardView) OnClick(name string) {
column, _ := strconv.Atoi(name)
_ = board.game.play(board.Side, column)
board.parent.Opponent.session.Render()
}

func (board *Board) Render() string {
func (board *BoardView) Render() string {
var sb strings.Builder
sb.WriteString(`<div class="board">`)
for i := 0; i < 7; i++ {
sb.WriteString(fmt.Sprintf(`<div class="column" cave-click="%d">`, 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(`<div class="red circle"></div>`)
case CircleTypeBlack:
Expand All @@ -120,3 +170,84 @@ func (board *Board) Render() string {
sb.WriteString("</div>")
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
}
100 changes: 100 additions & 0 deletions examples/connect4/main_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
Loading