├── README.md ├── conn.go ├── game └── game.go ├── main.go ├── room.go ├── templates └── home.html └── utils └── helpers.go /README.md: -------------------------------------------------------------------------------- 1 | wsgame 2 | ====== 3 | 4 | WebSocket Golang game 5 | 6 | Demo: http://bombixgame.com 7 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/alehano/wsgame/game" 5 | "github.com/gorilla/websocket" 6 | ) 7 | 8 | type playerConn struct { 9 | ws *websocket.Conn 10 | *game.Player 11 | room *room 12 | } 13 | 14 | // Receive msg from ws in goroutine 15 | func (pc *playerConn) receiver() { 16 | for { 17 | _, command, err := pc.ws.ReadMessage() 18 | if err != nil { 19 | break 20 | } 21 | // execute a command 22 | pc.Command(string(command)) 23 | // update all conn 24 | pc.room.updateAll <- true 25 | } 26 | pc.room.leave <- pc 27 | pc.ws.Close() 28 | } 29 | 30 | func (pc *playerConn) sendState() { 31 | go func() { 32 | msg := pc.GetState() 33 | err := pc.ws.WriteMessage(websocket.TextMessage, []byte(msg)) 34 | if err != nil { 35 | pc.room.leave <- pc 36 | pc.ws.Close() 37 | } 38 | }() 39 | } 40 | 41 | func NewPlayerConn(ws *websocket.Conn, player *game.Player, room *room) *playerConn { 42 | pc := &playerConn{ws, player, room} 43 | go pc.receiver() 44 | return pc 45 | } 46 | -------------------------------------------------------------------------------- /game/game.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | type Player struct { 8 | Name string 9 | Enemy *Player 10 | } 11 | 12 | func NewPlayer(name string) *Player { 13 | player := &Player{Name: name} 14 | return player 15 | } 16 | 17 | func PairPlayers(p1 *Player, p2 *Player) { 18 | p1.Enemy, p2.Enemy = p2, p1 19 | } 20 | 21 | func (p *Player) Command(command string) { 22 | 23 | log.Print("Command: '", command, "' received by player: ", p.Name) 24 | } 25 | 26 | func (p *Player) GetState() string { 27 | return "Game state for Player: " + p.Name 28 | } 29 | 30 | func (p *Player) GiveUp() { 31 | log.Print("Player gave up: ", p.Name) 32 | } 33 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/alehano/wsgame/game" 5 | "github.com/gorilla/websocket" 6 | "html/template" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | const ( 13 | ADDR string = ":8080" 14 | ) 15 | 16 | func homeHandler(c http.ResponseWriter, r *http.Request) { 17 | var homeTempl = template.Must(template.ParseFiles("templates/home.html")) 18 | data := struct { 19 | Host string 20 | RoomsCount int 21 | }{r.Host, roomsCount} 22 | homeTempl.Execute(c, data) 23 | } 24 | 25 | func wsHandler(w http.ResponseWriter, r *http.Request) { 26 | ws, err := websocket.Upgrade(w, r, nil, 1024, 1024) 27 | if _, ok := err.(websocket.HandshakeError); ok { 28 | http.Error(w, "Not a websocket handshake", 400) 29 | return 30 | } else if err != nil { 31 | return 32 | } 33 | 34 | playerName := "Player" 35 | params, _ := url.ParseQuery(r.URL.RawQuery) 36 | if len(params["name"]) > 0 { 37 | playerName = params["name"][0] 38 | } 39 | 40 | // Get or create a room 41 | var room *room 42 | if len(freeRooms) > 0 { 43 | for _, r := range freeRooms { 44 | room = r 45 | break 46 | } 47 | } else { 48 | room = NewRoom("") 49 | } 50 | 51 | // Create Player and Conn 52 | player := game.NewPlayer(playerName) 53 | pConn := NewPlayerConn(ws, player, room) 54 | // Join Player to room 55 | room.join <- pConn 56 | 57 | log.Printf("Player: %s has joined to room: %s", pConn.Name, room.name) 58 | } 59 | 60 | func main() { 61 | http.HandleFunc("/", homeHandler) 62 | http.HandleFunc("/ws", wsHandler) 63 | 64 | http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) { 65 | http.ServeFile(w, r, r.URL.Path[1:]) 66 | }) 67 | 68 | if err := http.ListenAndServe(ADDR, nil); err != nil { 69 | log.Fatal("ListenAndServe:", err) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /room.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/alehano/wsgame/game" 5 | "github.com/alehano/wsgame/utils" 6 | "log" 7 | ) 8 | 9 | var allRooms = make(map[string]*room) 10 | var freeRooms = make(map[string]*room) 11 | var roomsCount int 12 | 13 | type room struct { 14 | name string 15 | 16 | // Registered connections. 17 | playerConns map[*playerConn]bool 18 | 19 | // Update state for all conn. 20 | updateAll chan bool 21 | 22 | // Register requests from the connections. 23 | join chan *playerConn 24 | 25 | // Unregister requests from connections. 26 | leave chan *playerConn 27 | } 28 | 29 | // Run the room in goroutine 30 | func (r *room) run() { 31 | for { 32 | select { 33 | case c := <-r.join: 34 | r.playerConns[c] = true 35 | r.updateAllPlayers() 36 | 37 | // if room is full - delete from freeRooms 38 | if len(r.playerConns) == 2 { 39 | delete(freeRooms, r.name) 40 | // pair players 41 | var p []*game.Player 42 | for k, _ := range r.playerConns { 43 | p = append(p, k.Player) 44 | } 45 | game.PairPlayers(p[0], p[1]) 46 | } 47 | 48 | case c := <-r.leave: 49 | c.GiveUp() 50 | r.updateAllPlayers() 51 | delete(r.playerConns, c) 52 | if len(r.playerConns) == 0 { 53 | goto Exit 54 | } 55 | case <-r.updateAll: 56 | r.updateAllPlayers() 57 | } 58 | } 59 | 60 | Exit: 61 | 62 | // delete room 63 | delete(allRooms, r.name) 64 | delete(freeRooms, r.name) 65 | roomsCount -= 1 66 | log.Print("Room closed:", r.name) 67 | } 68 | 69 | func (r *room) updateAllPlayers() { 70 | for c := range r.playerConns { 71 | c.sendState() 72 | } 73 | } 74 | 75 | func NewRoom(name string) *room { 76 | if name == "" { 77 | name = utils.RandString(16) 78 | } 79 | 80 | room := &room{ 81 | name: name, 82 | playerConns: make(map[*playerConn]bool), 83 | updateAll: make(chan bool), 84 | join: make(chan *playerConn), 85 | leave: make(chan *playerConn), 86 | } 87 | 88 | allRooms[name] = room 89 | freeRooms[name] = room 90 | 91 | // run room 92 | go room.run() 93 | 94 | roomsCount += 1 95 | 96 | return room 97 | } 98 | -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |Active games: {{.RoomsCount}}
46 | 47 | 48 | 49 | 50 |