Add basic command parsing and handling
This commit is contained in:
parent
a74df0d5d2
commit
17056eb77e
7 changed files with 260 additions and 37 deletions
|
@ -1,7 +1,5 @@
|
|||
package bgammon
|
||||
|
||||
type Client interface {
|
||||
ReadCommand() (Command, error)
|
||||
WriteEvent() (Event, error)
|
||||
Terminate() error
|
||||
Terminate(reason string) error
|
||||
}
|
||||
|
|
6
cmd/bgammon-server/account.go
Normal file
6
cmd/bgammon-server/account.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package main
|
||||
|
||||
type account struct {
|
||||
id int
|
||||
username string
|
||||
}
|
14
cmd/bgammon-server/client.go
Normal file
14
cmd/bgammon-server/client.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package main
|
||||
|
||||
import "code.rocket9labs.com/tslocum/bgammon"
|
||||
|
||||
type serverClient struct {
|
||||
id int
|
||||
name []byte
|
||||
account int
|
||||
connected int64
|
||||
lastActive int64
|
||||
commands <-chan []byte
|
||||
events chan<- []byte
|
||||
bgammon.Client
|
||||
}
|
55
cmd/bgammon-server/client_tcp.go
Normal file
55
cmd/bgammon-server/client_tcp.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
)
|
||||
|
||||
var _ bgammon.Client = &socketClient{}
|
||||
|
||||
type socketClient struct {
|
||||
conn net.Conn
|
||||
events <-chan []byte
|
||||
commands chan<- []byte
|
||||
}
|
||||
|
||||
func newSocketClient(conn net.Conn, commands chan<- []byte, events <-chan []byte) *socketClient {
|
||||
c := &socketClient{
|
||||
conn: conn,
|
||||
events: events,
|
||||
commands: commands,
|
||||
}
|
||||
go c.readCommands()
|
||||
go c.writeEvents()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *socketClient) readCommands() {
|
||||
var scanner = bufio.NewScanner(c.conn)
|
||||
for scanner.Scan() {
|
||||
log.Printf("READ COMMAND %s", scanner.Text())
|
||||
|
||||
buf := make([]byte, len(scanner.Bytes()))
|
||||
copy(buf, scanner.Bytes())
|
||||
c.commands <- buf
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socketClient) writeEvents() {
|
||||
var event []byte
|
||||
for event = range c.events {
|
||||
c.conn.Write(event)
|
||||
c.conn.Write([]byte("\n"))
|
||||
log.Printf("write event %s", event)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socketClient) Terminate(reason string) error {
|
||||
c.conn.Write([]byte(fmt.Sprintf("Connection closed: %s\n", reason)))
|
||||
c.conn.Close()
|
||||
return nil
|
||||
}
|
|
@ -1,19 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.rocket9labs.com/tslocum/bgammon"
|
||||
)
|
||||
|
||||
type serverClient struct {
|
||||
id int
|
||||
connected int64
|
||||
lastActive int64
|
||||
bgammon.Client
|
||||
}
|
||||
|
||||
type serverGame struct {
|
||||
id int
|
||||
created int64
|
||||
|
@ -23,14 +21,28 @@ type serverGame struct {
|
|||
bgammon.Game
|
||||
}
|
||||
|
||||
type serverCommand struct {
|
||||
client *serverClient
|
||||
command []byte
|
||||
}
|
||||
|
||||
type server struct {
|
||||
clients []*serverClient
|
||||
games []*serverGame
|
||||
listeners []net.Listener
|
||||
clients []*serverClient
|
||||
games []*serverGame
|
||||
listeners []net.Listener
|
||||
newClientIDs chan int
|
||||
commands chan serverCommand
|
||||
}
|
||||
|
||||
func newServer() *server {
|
||||
return &server{}
|
||||
const bufferSize = 10
|
||||
s := &server{
|
||||
newClientIDs: make(chan int),
|
||||
commands: make(chan serverCommand, bufferSize),
|
||||
}
|
||||
go s.handleNewClientIDs()
|
||||
go s.handleCommands()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *server) listen(network string, address string) {
|
||||
|
@ -55,4 +67,154 @@ func (s *server) handleListener(listener net.Listener) {
|
|||
|
||||
func (s *server) handleConnection(conn net.Conn) {
|
||||
log.Printf("new conn %+v", conn)
|
||||
|
||||
const bufferSize = 8
|
||||
commands := make(chan []byte, bufferSize)
|
||||
events := make(chan []byte, bufferSize)
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
c := &serverClient{
|
||||
id: <-s.newClientIDs,
|
||||
account: -1,
|
||||
connected: now,
|
||||
lastActive: now,
|
||||
commands: commands,
|
||||
events: events,
|
||||
Client: newSocketClient(conn, commands, events),
|
||||
}
|
||||
log.Println("socket client", c)
|
||||
|
||||
s.handleClientCommands(c)
|
||||
}
|
||||
|
||||
func (s *server) handleClientCommands(c *serverClient) {
|
||||
var command []byte
|
||||
for command = range c.commands {
|
||||
s.commands <- serverCommand{
|
||||
client: c,
|
||||
command: command,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) handleNewClientIDs() {
|
||||
clientID := 1
|
||||
for {
|
||||
s.newClientIDs <- clientID
|
||||
clientID++
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) randomUsername() []byte {
|
||||
// TODO check if username is available
|
||||
i := 100 + rand.Intn(900)
|
||||
return []byte(fmt.Sprintf("Guest%d", i))
|
||||
}
|
||||
|
||||
func (s *server) sendWelcome(c *serverClient) {
|
||||
c.events <- []byte(fmt.Sprintf("welcome %s there are %d clients playing %d games", c.name, len(s.clients), len(s.games)))
|
||||
}
|
||||
|
||||
func (s *server) handleCommands() {
|
||||
var cmd serverCommand
|
||||
for cmd = range s.commands {
|
||||
if cmd.client == nil {
|
||||
log.Panicf("nil client with command %s", cmd.command)
|
||||
}
|
||||
|
||||
cmd.command = bytes.TrimSpace(cmd.command)
|
||||
|
||||
firstSpace := bytes.IndexByte(cmd.command, ' ')
|
||||
var keyword string
|
||||
var startParameters int
|
||||
if firstSpace == -1 {
|
||||
keyword = string(cmd.command)
|
||||
startParameters = len(cmd.command)
|
||||
} else {
|
||||
keyword = string(cmd.command[:firstSpace])
|
||||
startParameters = firstSpace + 1
|
||||
}
|
||||
if keyword == "" {
|
||||
continue
|
||||
}
|
||||
keyword = strings.ToLower(keyword)
|
||||
|
||||
log.Printf("server client %+v command %s with keyword %s", cmd.client, cmd.command, keyword)
|
||||
|
||||
params := bytes.Fields(cmd.command[startParameters:])
|
||||
log.Printf("params %+v", params)
|
||||
|
||||
// Require users to send login command first.
|
||||
if cmd.client.account == -1 {
|
||||
if keyword == bgammon.CommandLogin {
|
||||
var username []byte
|
||||
var password []byte
|
||||
switch len(params) {
|
||||
case 0:
|
||||
username = s.randomUsername()
|
||||
case 1:
|
||||
username = params[0]
|
||||
default:
|
||||
username = params[0]
|
||||
password = bytes.Join(params[1:], []byte(" "))
|
||||
}
|
||||
|
||||
if len(password) > 0 {
|
||||
cmd.client.account = 1
|
||||
} else {
|
||||
cmd.client.account = 0
|
||||
}
|
||||
cmd.client.name = username
|
||||
|
||||
s.sendWelcome(cmd.client)
|
||||
|
||||
log.Printf("login as %s - %s", username, password)
|
||||
continue
|
||||
} else {
|
||||
cmd.client.Terminate("You must login before using other commands.")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch keyword {
|
||||
case bgammon.CommandSay:
|
||||
if len(params) == 0 {
|
||||
continue
|
||||
}
|
||||
case bgammon.CommandList:
|
||||
case bgammon.CommandCreate:
|
||||
sendUsage := func() {
|
||||
cmd.client.events <- []byte("notice To create a public game specify whether it is public or private.")
|
||||
cmd.client.events <- []byte("notice When creating a private game, a password must be set.")
|
||||
}
|
||||
if len(params) == 0 {
|
||||
sendUsage()
|
||||
continue
|
||||
}
|
||||
var gamePassword []byte
|
||||
gameType := bytes.ToLower(params[0])
|
||||
var gameName []byte
|
||||
switch {
|
||||
case bytes.Equal(gameType, []byte("public")):
|
||||
gameName = bytes.Join(params[1:], []byte(" "))
|
||||
case bytes.Equal(gameType, []byte("private")):
|
||||
if len(params) < 2 {
|
||||
sendUsage()
|
||||
continue
|
||||
}
|
||||
gamePassword = params[1]
|
||||
gameName = bytes.Join(params[2:], []byte(" "))
|
||||
default:
|
||||
sendUsage()
|
||||
continue
|
||||
}
|
||||
log.Printf("create game (password %s) name: %s", gamePassword, gameName)
|
||||
case bgammon.CommandJoin:
|
||||
case bgammon.CommandRoll:
|
||||
case bgammon.CommandMove:
|
||||
default:
|
||||
log.Printf("unknown command %s", keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
22
command.go
22
command.go
|
@ -2,16 +2,14 @@ package bgammon
|
|||
|
||||
// commands are always sent TO the server
|
||||
|
||||
type Command struct {
|
||||
Type int
|
||||
}
|
||||
type Command string
|
||||
|
||||
type CommandChat struct {
|
||||
Command
|
||||
Message string
|
||||
}
|
||||
|
||||
type CommandMove struct {
|
||||
Event
|
||||
Spaces []int // One or more sets of moves from A->B as A,B,A,B,A,B...
|
||||
}
|
||||
const (
|
||||
CommandLogin = "login" // Log in with username and password, or as a guest.
|
||||
CommandSay = "say" // Send chat message.
|
||||
CommandList = "list" // List available games.
|
||||
CommandCreate = "create" // Create game.
|
||||
CommandJoin = "join" // Join game.
|
||||
CommandRoll = "roll" // Roll dice.
|
||||
CommandMove = "move" // Move checkers.
|
||||
)
|
||||
|
|
12
event.go
12
event.go
|
@ -3,16 +3,6 @@ package bgammon
|
|||
// events are always received FROM the server
|
||||
|
||||
type Event struct {
|
||||
Type int
|
||||
Player int
|
||||
}
|
||||
|
||||
type EventChat struct {
|
||||
Event
|
||||
Message string
|
||||
}
|
||||
|
||||
type EventMove struct {
|
||||
Event
|
||||
Spaces []int // One or more sets of moves from A->B as A,B,A,B,A,B...
|
||||
Command
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue