finish budgetchat

This commit is contained in:
Evan Burkey 2023-09-12 14:19:14 -07:00
parent 0499ea30fe
commit 8f0f04e2f2
5 changed files with 167 additions and 123 deletions

96
cmd/budgetchat/client.go Normal file
View File

@ -0,0 +1,96 @@
package main
import (
"errors"
"fmt"
"log"
"net"
"regexp"
"strings"
)
type client struct {
c net.Conn
name string
}
func isValidName(name string) bool {
pattern := "^[a-zA-Z0-9]+$"
nameReg, err := regexp.Compile(pattern)
if err != nil {
log.Fatalln(err)
}
return nameReg.MatchString(name)
}
func cleanString(buf []byte) string {
s := string(buf)
idx := strings.Index(s, "\n")
if idx != -1 {
s = s[:idx]
}
return s
}
func newClient(c net.Conn) (*client, error) {
cl := &client{
c: c,
name: "",
}
cl.sendMessageString("Welcome to budgetchat! What shall I call you?")
r := make(chan string, 1)
var err error = nil
go func() {
for {
buf := make([]byte, 32)
_, err = c.Read(buf)
if err != nil {
c.Close()
r <- ""
return
}
name := cleanString(buf)
// Check legality
if !isValidName(name) {
cl.sendMessageString("The name %s is invalid", string(name))
err = errors.New("name invalid")
c.Close()
r <- name
return
}
// Check for duplicate name
for _, cls := range room {
if cls.name == name {
cl.sendMessageString("The name %s is already taken", string(name))
err = errors.New("name already taken")
c.Close()
r <- name
return
}
}
r <- name
return
}
}()
cl.name = <-r
if err != nil {
return nil, err
}
return cl, nil
}
func (cl *client) sendMessage(m []byte) {
_, err := cl.c.Write(m)
if err != nil {
log.Fatalln(err)
}
}
func (cl *client) sendMessageString(m string, vars ...any) {
cl.sendMessage([]byte(fmt.Sprintf(m, vars...) + "\n"))
}

View File

@ -3,53 +3,83 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"log"
"net" "net"
"protohackers/pkg/conn" "protohackers/pkg/conn"
) )
type message struct {
msg []byte
client *client
}
func msg(s string, c *client) message {
return message{[]byte(s), c}
}
var (
room = make([]*client, 0)
messages = make(chan message, 32)
)
func main() { func main() {
l, err := net.Listen(conn.Type, conn.Port) go broadcast()
err := conn.StartSimple(newUserHandler)
if err != nil { if err != nil {
panic(err) log.Fatalln(err)
}
defer l.Close()
fmt.Println(fmt.Sprintf("Listening on port %s", conn.Port))
r := createRoom()
for {
c, err := l.Accept()
if err != nil {
break
}
go handleConn(r, c)
} }
} }
func handleConn(r *room, c net.Conn) { func broadcast() {
u := newUser(c) for {
defer c.Close() select {
go u.sendMessage() case m := <-messages:
for _, cl := range room {
u.write("Welcome to budgetchat! What shall I call you?") if m.client == cl {
continue
s := bufio.NewScanner(u.conn)
for s.Scan() {
input := s.Text()
if !u.online {
r.sendToRest(r.joinMsg(u), u)
u.write(r.connectMsg())
r.joinUser(u)
} else {
r.sendToRest(msgWrap(input, u), u)
} }
if s.Err() != nil { cl.sendMessage(m.msg)
}
}
}
}
func userList() []byte {
m := "* The room contains: "
if len(room) > 0 {
for _, c := range room {
m += fmt.Sprintf("%s, ", c.name)
}
m = m[:len(m)-2]
}
return []byte(m + "\n")
}
func newUserHandler(c net.Conn) {
cl, err := newClient(c)
if err != nil {
return return
} }
cl.sendMessage(userList())
fmt.Printf("New client %s\n", cl.name)
messages <- msg(fmt.Sprintf("* %s has entered the room\n", cl.name), cl)
room = append(room, cl)
input := bufio.NewScanner(cl.c)
for input.Scan() {
m := fmt.Sprintf("[%s] %s\n", cl.name, input.Text())
messages <- msg(m, cl)
}
for i, p := range room {
if p == cl {
name := cl.name
cl.c.Close()
room = append(room[:i], room[i+1:]...)
fmt.Printf("%s has left", cl.name)
messages <- msg(fmt.Sprintf("* %s has left the room\n", name), cl)
break
}
} }
} }
func msgWrap(msg string, u *user) string {
return fmt.Sprintf("[%s] %s", u.nick, msg)
}

View File

@ -1,59 +0,0 @@
package main
import (
"fmt"
"strings"
)
type room struct {
users []*user
}
func createRoom() *room {
return &room{
users: make([]*user, 16),
}
}
func (r *room) joinUser(u *user) {
for _, usr := range r.users {
if usr.nick == u.nick {
}
}
r.users = append(r.users, u)
u.online = true
}
func (r *room) sendToAll(msg string) {
for _, u := range r.users {
if u.online {
u.write(msg)
}
}
}
func (r *room) sendToRest(msg string, skip *user) {
for _, u := range r.users {
if u.online && u != skip {
u.write(msg)
}
}
}
func (r *room) connected() (nicks []string) {
for _, u := range r.users {
if u.online {
nicks = append(nicks, u.nick)
}
}
return nicks
}
func (r *room) joinMsg(u *user) string {
return fmt.Sprintf("* %s has joined the channel", u.nick)
}
func (r *room) connectMsg() string {
return fmt.Sprintf("* The room contains: %s", strings.Join(r.connected(), ", "))
}

View File

@ -1,27 +0,0 @@
package main
import "net"
type user struct {
conn net.Conn
nick string
online bool
output chan string
}
func newUser(conn net.Conn) *user {
return &user{
conn: conn,
output: make(chan string),
}
}
func (u *user) write(s string) {
u.output <- s + "\n"
}
func (u *user) sendMessage() {
for line := range u.output {
u.conn.Write([]byte(line))
}
}

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=