Compare commits
3 Commits
master
...
enha/sse-t
Author | SHA1 | Date | |
---|---|---|---|
acc3f11ee3 | |||
9fc36eb7ea | |||
8f9865db3f |
@ -2,13 +2,12 @@ body{
|
|||||||
background-color: #0C1616FF;
|
background-color: #0C1616FF;
|
||||||
color: #8896b2;
|
color: #8896b2;
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
margin: 2em 2em !important;
|
margin: 2em auto !important;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: Open Sans,Arial;
|
font-family: Open Sans,Arial;
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -23,6 +22,18 @@ tr{
|
|||||||
#usertable{
|
#usertable{
|
||||||
display: block ruby;
|
display: block ruby;
|
||||||
}
|
}
|
||||||
|
.actiontable{
|
||||||
|
display: inline flow-root;
|
||||||
|
margin-inline: 10px;
|
||||||
|
}
|
||||||
|
.action_name{
|
||||||
|
border: none;
|
||||||
|
display: inline;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: none;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
#errorbox{
|
#errorbox{
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
background-color: darkorange;
|
background-color: darkorange;
|
||||||
|
BIN
assets/style.css.gz
Normal file
BIN
assets/style.css.gz
Normal file
Binary file not shown.
@ -4,12 +4,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// the amount of time to wait when pushing a message to
|
// the amount of time to wait when pushing a message to
|
||||||
// a slow client or a client that closed after `range clients` started.
|
// a slow client or a client that closed after `range clients` started.
|
||||||
const patience time.Duration = time.Millisecond * 500
|
// const patience time.Duration = time.Second * 1
|
||||||
|
|
||||||
type (
|
type (
|
||||||
NotificationEvent struct {
|
NotificationEvent struct {
|
||||||
@ -20,22 +21,18 @@ type (
|
|||||||
Broker struct {
|
Broker struct {
|
||||||
// Events are pushed to this channel by the main events-gathering routine
|
// Events are pushed to this channel by the main events-gathering routine
|
||||||
Notifier NotifierChan
|
Notifier NotifierChan
|
||||||
// New client connections
|
log *slog.Logger
|
||||||
newClients chan NotifierChan
|
|
||||||
// Closed client connections
|
|
||||||
closingClients chan NotifierChan
|
|
||||||
// Client connections registry
|
|
||||||
clients map[NotifierChan]struct{}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBroker() (broker *Broker) {
|
func NewBroker() (broker *Broker) {
|
||||||
// Instantiate a broker
|
// Instantiate a broker
|
||||||
return &Broker{
|
return &Broker{
|
||||||
Notifier: make(NotifierChan, 1),
|
Notifier: make(NotifierChan, 100),
|
||||||
newClients: make(chan NotifierChan),
|
log: slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
closingClients: make(chan NotifierChan),
|
Level: slog.LevelDebug,
|
||||||
clients: make(map[NotifierChan]struct{}),
|
AddSource: true,
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +41,6 @@ var Notifier *Broker
|
|||||||
// for use in different packages
|
// for use in different packages
|
||||||
func init() {
|
func init() {
|
||||||
Notifier = NewBroker()
|
Notifier = NewBroker()
|
||||||
go Notifier.Listen()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (broker *Broker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (broker *Broker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -59,68 +55,39 @@ func (broker *Broker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
messageChan := make(NotifierChan, 10) // Buffered channel
|
|
||||||
broker.newClients <- messageChan
|
|
||||||
defer func() { broker.closingClients <- messageChan }()
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
// browser can close sse on its own; ping every 2s to prevent
|
// browser can close sse on its own; ping every 2s to prevent
|
||||||
heartbeat := time.NewTicker(2 * time.Second)
|
heartbeat := time.NewTicker(8 * time.Second)
|
||||||
defer heartbeat.Stop()
|
defer heartbeat.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
broker.log.Debug("broker: got ctx done")
|
||||||
// Client disconnected
|
// Client disconnected
|
||||||
return
|
return
|
||||||
case event := <-messageChan:
|
case event := <-broker.Notifier:
|
||||||
_, err := fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.EventName, event.Payload)
|
broker.log.Debug("got event", "event", event)
|
||||||
if err != nil {
|
for i := 0; i < 10; i++ { // Repeat 3 times
|
||||||
fmt.Println(err)
|
_, err := fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.EventName, event.Payload)
|
||||||
// Client disconnected
|
if err != nil {
|
||||||
return
|
broker.log.Error("write failed", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
// Short delay between sends (non-blocking)
|
||||||
|
select {
|
||||||
|
case <-time.After(20 * time.Millisecond): // Adjust delay as needed
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
case <-heartbeat.C:
|
case <-heartbeat.C:
|
||||||
// Send SSE heartbeat comment
|
// Send SSE heartbeat comment
|
||||||
if _, err := fmt.Fprint(w, ":\n\n"); err != nil {
|
if _, err := fmt.Fprint(w, ":\n\n"); err != nil {
|
||||||
|
broker.log.Error("failed to write heartbeat", "error", err)
|
||||||
return // Client disconnected
|
return // Client disconnected
|
||||||
}
|
}
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for new notifications and redistribute them to clients
|
|
||||||
func (broker *Broker) Listen() {
|
|
||||||
slog.Info("Broker listener started")
|
|
||||||
for {
|
|
||||||
slog.Info("Broker waiting for event")
|
|
||||||
select {
|
|
||||||
case s := <-broker.newClients:
|
|
||||||
// A new client has connected.
|
|
||||||
// Register their message channel
|
|
||||||
broker.clients[s] = struct{}{}
|
|
||||||
slog.Info("Client added", "clients listening", len(broker.clients))
|
|
||||||
case s := <-broker.closingClients:
|
|
||||||
// A client has dettached and we want to
|
|
||||||
// stop sending them messages.
|
|
||||||
delete(broker.clients, s)
|
|
||||||
slog.Info("Client removed", "clients listening", len(broker.clients))
|
|
||||||
case event := <-broker.Notifier:
|
|
||||||
slog.Info("Received new event", "event", event.EventName, "payload", event.Payload)
|
|
||||||
// We got a new event from the outside!
|
|
||||||
// Send event to all connected clients
|
|
||||||
slog.Info("Broadcasting event to clients", "client_count", len(broker.clients))
|
|
||||||
for clientMessageChan := range broker.clients {
|
|
||||||
slog.Info("Sending event to client", "client", clientMessageChan)
|
|
||||||
select {
|
|
||||||
case clientMessageChan <- event:
|
|
||||||
slog.Info("Successfully sent event to client", "client", clientMessageChan)
|
|
||||||
case <-time.After(patience):
|
|
||||||
delete(broker.clients, clientMessageChan)
|
|
||||||
slog.Warn("Client timed out, removed", "client", clientMessageChan, "clients listening", len(broker.clients))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slog.Info("Finished broadcasting event")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -13,6 +13,12 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="ancestor" hx-ext="sse" sse-connect="/sub/sse">
|
<div id="ancestor" hx-ext="sse" sse-connect="/sub/sse">
|
||||||
|
<script type="text/javascript">
|
||||||
|
document.body.addEventListener('htmx:sseError', function (e) {
|
||||||
|
// do something before the event data is swapped in
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
<div id="main-content">
|
<div id="main-content">
|
||||||
{{template "main" .}}
|
{{template "main" .}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,46 +3,7 @@
|
|||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2">
|
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2">
|
||||||
{{range .Cards}}
|
{{range .Cards}}
|
||||||
{{if .Revealed}}
|
{{template "cardword" .}}
|
||||||
{{if eq .Color "amber"}}
|
|
||||||
<div id="card-{{.Word}}" class="bg-{{.Color}}-100 border-8 border-stine-400 p-4 min-w-[100px] text-center text-white cursor-pointer"
|
|
||||||
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div id="card-{{.Word}}" class="bg-{{.Color}}-400 border-8 border-stone-400 p-4 min-w-[100px] text-center text-white cursor-pointer"
|
|
||||||
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{else if or (.Mime) ($.IsOver)}}
|
|
||||||
{{if eq .Color "amber"}}
|
|
||||||
<div id="card-{{.Word}}" class="bg-{{.Color}}-100 border border-stone-400 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer"
|
|
||||||
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div id="card-{{.Word}}" class="bg-{{.Color}}-400 border border-stone-400 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer"
|
|
||||||
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{else}}
|
|
||||||
<div id="card-{{.Word}}" class="bg-stone-400 border border-gray-500 rounded-lg min-w-[100px] cursor-pointer flex flex-col h-full">
|
|
||||||
<div class="flex-grow text-center p-4 flex items-center justify-center text-white"
|
|
||||||
style="text-shadow: 0 2px 4px rgba(0,0,0,0.8);"
|
|
||||||
hx-get="/word/show-color?word={{.Word}}" hx-trigger="click" hx-swap="outerHTML transition:true swap:.05s">
|
|
||||||
{{.Word}}
|
|
||||||
</div>
|
|
||||||
<div class="h-6 bg-stone-600 rounded-b flex items-center justify-center text-white text-sm cursor-pointer"
|
|
||||||
hx-get="/mark-card?word={{.Word}}" hx-trigger="click" hx-swap="outerHTML transition:true swap:.05s">
|
|
||||||
{{range .Marks}}
|
|
||||||
{{ $length := len .Username }}
|
|
||||||
{{ if lt $length 3 }}
|
|
||||||
<span class="mx-0.5">{{.Username}}</span>
|
|
||||||
{{else}}
|
|
||||||
<span class="mx-0.5">{{slice .Username 0 3}}</span>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
|
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else if or (.Mime) }}
|
{{else if .Mime}}
|
||||||
{{if eq .Color "amber"}}
|
{{if eq .Color "amber"}}
|
||||||
<div id="card-{{.Word}}" class="bg-{{.Color}}-100 border border-stone-400 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer"
|
<div id="card-{{.Word}}" class="bg-{{.Color}}-100 border border-stone-400 p-4 rounded-lg min-w-[100px] text-center text-white cursor-pointer"
|
||||||
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
|
style="text-shadow: 0 2px 4px rgba(0,0,0,0.9);"> {{.Word}}
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
Create a room <br/>
|
Create a room <br/>
|
||||||
or<br/>
|
or<br/>
|
||||||
<button button class="justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" hx-get="/room/hideform" hx-target=".create-room-div" >Hide Form</button>
|
<button button class="justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" hx-get="/room/hideform" hx-target=".create-room-div" >Hide Form</button>
|
||||||
<form hx-post="/room-create" hx-target="#main-content" class="space-y-4">
|
<form hx-post="/room-create" hx-target="#main-content">
|
||||||
<label For="game_time">Turn Seconds:</label><br/>
|
<label For="game_time">Turn Seconds:</label><br/>
|
||||||
<input type="number" id="game_time" name="game_time" class="text-center text-white" value="120"/><br/>
|
<input type="number" id="game_time" name="game_time" class="text-center text-white" value="300"/><br/>
|
||||||
<label For="language">Language:</label><br/>
|
<label For="language">Language:</label><br/>
|
||||||
<div>
|
<div>
|
||||||
<select class="form-select text-white text-center bg-gray-900" id="languages" name="language">
|
<select class="form-select text-white text-center bg-gray-900" id="languages" name="language">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{{define "error"}}
|
{{define "error"}}
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<div id=errorbox class="bg-orange-100 border-l-4 border-black text-black p-4" role="alert">
|
<div id=errorbox class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4" role="alert">
|
||||||
<p class="font-bold">An error from server</p>
|
<p class="font-bold">An error from server</p>
|
||||||
<p>{{.}}</p>
|
<p>{{.}}</p>
|
||||||
<p>Click this banner to return to main page.</p>
|
<p>Click this banner to return to main page.</p>
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
{{template "roomlist" .List}}
|
{{template "roomlist" .List}}
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
<div id="sse-listener" sse-connect="/sub/sse" hx-trigger="sse:roomupdate_{{.State.RoomID}}" hx-get="/room" hx-target="#room-interier" hx-swap="none" style="display:none;"></div>
|
||||||
<div id="room">
|
<div id="room">
|
||||||
{{template "room" .}}
|
{{template "room" .}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
{{define "journal"}}
|
|
||||||
<div id="systembox" class="overflow-y-auto max-h-96 border-2 border-gray-300 p-4 rounded-lg space-y-2">
|
|
||||||
bot journal: <br>
|
|
||||||
<ul>
|
|
||||||
{{range .LogJournal}}
|
|
||||||
<li>{{.Username}}: {{.Entry}}</li>
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
|||||||
{{define "login"}}
|
{{define "login"}}
|
||||||
<div id="logindiv">
|
<div id="logindiv">
|
||||||
<form hx-post="/login" hx-target="#main-content" class="space-y-4">
|
<form class="space-y-6" hx-post="/login" hx-target="#main-content">
|
||||||
<div>
|
<div>
|
||||||
<label For="username" class="text-sm text-center font-medium leading-6 text-white-900">tell us your username (signup|login)</label>
|
<label For="username" class="block text-sm font-medium leading-6 text-white-900">tell us your username (signup|login)</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input id="username" name="username" hx-target="#login_notice" hx-swap="outerHTML" hx-post="/check/name" hx-trigger="input changed delay:400ms" autocomplete="username" required class="text-center rounded-md border-0 bg-white py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-center"/>
|
<input id="username" name="username" hx-target="#login_notice" hx-swap="outerHTML" hx-post="/check/name" hx-trigger="input changed delay:400ms" autocomplete="username" required class="block w-full rounded-md border-0 bg-white py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-center"/>
|
||||||
</div>
|
</div>
|
||||||
<div id="login_notice">this name looks available</div>
|
<div id="login_notice">this name looks available</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label For="password" class="text-sm font-medium text-center leading-6 text-white-900">password</label>
|
<label For="password" class="block text-sm font-medium leading-6 text-white-900">password</label>
|
||||||
<div class="mt-2">
|
<input id="password" name="password" type="password" class="block w-full rounded-md border-0 bg-white py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-center"/>
|
||||||
<input id="password" name="password" type="password" class="rounded-md border-0 bg-white py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-center"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button type="submit" class="justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Sign in</button>
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Sign in</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{{define "room"}}
|
{{define "room"}}
|
||||||
<div id="interier" hx-get="/" hx-trigger="sse:roomupdate_{{.State.RoomID}}" class=space-y-2>
|
<div id="room-interier" class=space-y-2>
|
||||||
<div id="headwrapper" class="grid grid-cols-1 md:grid-cols-5 md:gap-4">
|
<div id="headwrapper" class="grid grid-cols-1 md:grid-cols-5 md:gap-4">
|
||||||
<div id="meta" class="md:col-span-1 border-2 rounded-lg text-center space-y-2">
|
<div id="meta" class="md:col-span-1 border-2 rounded-lg text-center space-y-2">
|
||||||
<p>Hello {{.State.Username}};</p>
|
<p>Hello {{.State.Username}};</p>
|
||||||
@ -53,15 +53,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-5 md:gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-5 md:gap-4">
|
||||||
<div class="md:col-span-1">
|
<div hx-get="/actionhistory" class="md:col-span-1">
|
||||||
{{template "actionhistory" .Room.ActionHistory}}
|
{{template "actionhistory" .Room.ActionHistory}}
|
||||||
</div>
|
</div>
|
||||||
<div id="cardtable" class="md:col-span-3">
|
<div id="cardtable" class="md:col-span-3">
|
||||||
{{template "cardtable" .Room}}
|
{{template "cardtable" .Room}}
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:block md:col-span-1">
|
<div class="hidden md:block md:col-span-1"></div> <!-- Spacer -->
|
||||||
{{template "journal" .Room}}
|
</div>
|
||||||
</div> <!-- Spacer -->
|
<div id="systembox" class="overflow-y-auto max-h-96 border-2 border-gray-300 p-4 rounded-lg space-y-2">
|
||||||
|
bot thought: <br>
|
||||||
|
<ul>
|
||||||
|
{{range .Room.LogJournal}}
|
||||||
|
<li>{{.Username}}: {{.Entry}}</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{if .Room.IsRunning}}
|
{{if .Room.IsRunning}}
|
||||||
|
@ -334,3 +334,19 @@ func HandleRemoveBot(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
notify(models.NotifyRoomUpdatePrefix+fi.Room.ID, "")
|
notify(models.NotifyRoomUpdatePrefix+fi.Room.ID, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HandleGetRoom(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fi, err := getFullInfoByCtx(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
abortWithError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmpl, err := template.ParseGlob("components/*.html")
|
||||||
|
if err != nil {
|
||||||
|
abortWithError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := tmpl.ExecuteTemplate(w, "room", fi); err != nil {
|
||||||
|
log.Error("failed to execute template", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -106,9 +106,8 @@ func HandleExit(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := repo.RoomDeleteByID(r.Context(), fi.Room.ID); err != nil {
|
if err := repo.RoomDeleteByID(r.Context(), fi.Room.ID); err != nil {
|
||||||
log.Error("failed to remove room", "error", err)
|
log.Error("failed to remove room", "error", err)
|
||||||
}
|
}
|
||||||
notify(models.NotifyRoomListUpdate, "") // why is it needed?
|
notify(models.NotifyRoomListUpdate, "")
|
||||||
} else {
|
} else {
|
||||||
notify(models.NotifyRoomUpdatePrefix, "")
|
|
||||||
// if regular player leaves, just exit room
|
// if regular player leaves, just exit room
|
||||||
if err := repo.PlayerExitRoom(r.Context(), fi.State.Username); err != nil {
|
if err := repo.PlayerExitRoom(r.Context(), fi.State.Username); err != nil {
|
||||||
log.Error("failed to exit room", "error", err)
|
log.Error("failed to exit room", "error", err)
|
||||||
|
@ -12,7 +12,7 @@ func StartTurnTimer(roomID string, timeLeft uint32) {
|
|||||||
logger := slog.Default().With("room_id", roomID)
|
logger := slog.Default().With("room_id", roomID)
|
||||||
|
|
||||||
onTurnEnd := func(ctx context.Context, roomID string) {
|
onTurnEnd := func(ctx context.Context, roomID string) {
|
||||||
room, err := repo.RoomGetExtended(context.Background(), roomID)
|
room, err := repo.RoomGetByID(context.Background(), roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("failed to get room by id", "error", err)
|
logger.Error("failed to get room by id", "error", err)
|
||||||
return
|
return
|
||||||
@ -23,7 +23,7 @@ func StartTurnTimer(roomID string, timeLeft uint32) {
|
|||||||
if err := repo.RoomUpdate(context.Background(), room); err != nil {
|
if err := repo.RoomUpdate(context.Background(), room); err != nil {
|
||||||
logger.Error("failed to save room", "error", err)
|
logger.Error("failed to save room", "error", err)
|
||||||
}
|
}
|
||||||
// notify(models.NotifyTurnTimerPrefix+room.ID, strconv.FormatUint(uint64(room.Settings.RoundTime), 10))
|
notify(models.NotifyTurnTimerPrefix+room.ID, strconv.FormatUint(uint64(room.Settings.RoundTime), 10))
|
||||||
notifyBotIfNeeded(room)
|
notifyBotIfNeeded(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,10 +31,9 @@ func StartTurnTimer(roomID string, timeLeft uint32) {
|
|||||||
notify(models.NotifyTurnTimerPrefix+roomID, strconv.FormatUint(uint64(currentLeft), 10))
|
notify(models.NotifyTurnTimerPrefix+roomID, strconv.FormatUint(uint64(currentLeft), 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.StartTurnTimer(context.Background(), roomID, int32(timeLeft), onTurnEnd, onTick, logger)
|
timer.StartTurnTimer(context.Background(), roomID, timeLeft, onTurnEnd, onTick, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StopTurnTimer(roomID string) {
|
func StopTurnTimer(roomID string) {
|
||||||
timer.StopTurnTimer(roomID)
|
timer.StopTurnTimer(roomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +173,6 @@ func (p *openRouterParser) MakePayload(prompt string) io.Reader {
|
|||||||
model := models[int(p.modelIndex)%len(models)]
|
model := models[int(p.modelIndex)%len(models)]
|
||||||
strPayload := fmt.Sprintf(`{
|
strPayload := fmt.Sprintf(`{
|
||||||
"model": "%s",
|
"model": "%s",
|
||||||
"max_tokens": 300,
|
|
||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
|
@ -13,7 +13,7 @@ func (b *Bot) StartTurnTimer(timeLeft uint32) {
|
|||||||
logger := b.log.With("room_id", b.RoomID)
|
logger := b.log.With("room_id", b.RoomID)
|
||||||
|
|
||||||
onTurnEnd := func(ctx context.Context, roomID string) {
|
onTurnEnd := func(ctx context.Context, roomID string) {
|
||||||
room, err := repos.RP.RoomGetExtended(context.Background(), roomID)
|
room, err := repos.RP.RoomGetByID(context.Background(), roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("failed to get room by id", "error", err)
|
logger.Error("failed to get room by id", "error", err)
|
||||||
return
|
return
|
||||||
@ -24,10 +24,10 @@ func (b *Bot) StartTurnTimer(timeLeft uint32) {
|
|||||||
if err := repos.RP.RoomUpdate(context.Background(), room); err != nil {
|
if err := repos.RP.RoomUpdate(context.Background(), room); err != nil {
|
||||||
logger.Error("failed to save room", "error", err)
|
logger.Error("failed to save room", "error", err)
|
||||||
}
|
}
|
||||||
// broker.Notifier.Notifier <- broker.NotificationEvent{
|
broker.Notifier.Notifier <- broker.NotificationEvent{
|
||||||
// EventName: models.NotifyTurnTimerPrefix + room.ID,
|
EventName: models.NotifyTurnTimerPrefix + room.ID,
|
||||||
// Payload: strconv.FormatUint(uint64(room.Settings.RoundTime), 10),
|
Payload: strconv.FormatUint(uint64(room.Settings.RoundTime), 10),
|
||||||
// }
|
}
|
||||||
// notifyBotIfNeeded(room)
|
// notifyBotIfNeeded(room)
|
||||||
if botName := room.WhichBotToMove(); botName != "" {
|
if botName := room.WhichBotToMove(); botName != "" {
|
||||||
SignalChanMap[botName] <- true
|
SignalChanMap[botName] <- true
|
||||||
@ -41,10 +41,9 @@ func (b *Bot) StartTurnTimer(timeLeft uint32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.StartTurnTimer(context.Background(), b.RoomID, int32(timeLeft), onTurnEnd, onTick, logger)
|
timer.StartTurnTimer(context.Background(), b.RoomID, timeLeft, onTurnEnd, onTick, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) StopTurnTimer() {
|
func (b *Bot) StopTurnTimer() {
|
||||||
timer.StopTurnTimer(b.RoomID)
|
timer.StopTurnTimer(b.RoomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
main.go
1
main.go
@ -92,6 +92,7 @@ func ListenToRequests(port string) *http.Server {
|
|||||||
mux.HandleFunc("GET /add-bot", handlers.HandleAddBot)
|
mux.HandleFunc("GET /add-bot", handlers.HandleAddBot)
|
||||||
mux.HandleFunc("GET /remove-bot", handlers.HandleRemoveBot)
|
mux.HandleFunc("GET /remove-bot", handlers.HandleRemoveBot)
|
||||||
mux.HandleFunc("GET /mark-card", handlers.HandleMarkCard)
|
mux.HandleFunc("GET /mark-card", handlers.HandleMarkCard)
|
||||||
|
mux.HandleFunc("GET /room", handlers.HandleGetRoom)
|
||||||
// special
|
// special
|
||||||
mux.HandleFunc("GET /renotify-bot", handlers.HandleRenotifyBot)
|
mux.HandleFunc("GET /renotify-bot", handlers.HandleRenotifyBot)
|
||||||
// sse
|
// sse
|
||||||
|
@ -410,22 +410,14 @@ func (r *Room) RevealSpecificWord(word string) uint32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Room) SetGameOverToCards(isover bool) {
|
|
||||||
for i := range r.Cards {
|
|
||||||
r.Cards[i].IsOver = isover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type WordCard struct {
|
type WordCard struct {
|
||||||
ID uint32 `json:"id" db:"id"`
|
ID uint32 `json:"id" db:"id"`
|
||||||
RoomID string `json:"room_id" db:"room_id"`
|
RoomID string `json:"room_id" db:"room_id"`
|
||||||
Word string `json:"word" db:"word"`
|
Word string `json:"word" db:"word"`
|
||||||
Color WordColor `json:"color" db:"color"`
|
Color WordColor `json:"color" db:"color"`
|
||||||
Revealed bool `json:"revealed" db:"revealed"`
|
Revealed bool `json:"revealed" db:"revealed"`
|
||||||
// pain; but at the end of the game players should see color of unopen cards
|
Mime bool `json:"mime" db:"mime_view"` // user who sees that card is mime
|
||||||
IsOver bool
|
Marks []CardMark `json:"marks" db:"-"`
|
||||||
Mime bool `json:"mime" db:"mime_view"` // user who sees that card is mime
|
|
||||||
Marks []CardMark `json:"marks" db:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// table: settings
|
// table: settings
|
||||||
|
@ -14,12 +14,12 @@ type TurnEndCallback func(ctx context.Context, roomID string)
|
|||||||
type TickCallback func(ctx context.Context, roomID string, timeLeft uint32)
|
type TickCallback func(ctx context.Context, roomID string, timeLeft uint32)
|
||||||
|
|
||||||
type RoomTimer struct {
|
type RoomTimer struct {
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
done chan bool
|
done chan bool
|
||||||
roomID string
|
roomID string
|
||||||
onTurnEnd TurnEndCallback
|
onTurnEnd TurnEndCallback
|
||||||
onTick TickCallback
|
onTick TickCallback
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -28,7 +28,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// StartTurnTimer initializes and starts a new turn timer for a given room.
|
// StartTurnTimer initializes and starts a new turn timer for a given room.
|
||||||
func StartTurnTimer(ctx context.Context, roomID string, timeLeft int32, onTurnEnd TurnEndCallback, onTick TickCallback, logger *slog.Logger) {
|
func StartTurnTimer(ctx context.Context, roomID string, timeLeft uint32, onTurnEnd TurnEndCallback, onTick TickCallback, logger *slog.Logger) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
@ -41,12 +41,12 @@ func StartTurnTimer(ctx context.Context, roomID string, timeLeft int32, onTurnEn
|
|||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
|
|
||||||
rt := &RoomTimer{
|
rt := &RoomTimer{
|
||||||
ticker: ticker,
|
ticker: ticker,
|
||||||
done: done,
|
done: done,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
onTurnEnd: onTurnEnd,
|
onTurnEnd: onTurnEnd,
|
||||||
onTick: onTick,
|
onTick: onTick,
|
||||||
log: logger,
|
log: logger,
|
||||||
}
|
}
|
||||||
timers[roomID] = rt
|
timers[roomID] = rt
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ func StartTurnTimer(ctx context.Context, roomID string, timeLeft int32, onTurnEn
|
|||||||
StopTurnTimer(roomID)
|
StopTurnTimer(roomID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rt.onTick(ctx, roomID, uint32(currentLeft))
|
rt.onTick(ctx, roomID, currentLeft)
|
||||||
currentLeft--
|
currentLeft--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
todos.md
5
todos.md
@ -93,8 +93,3 @@
|
|||||||
- journal still does not work; +
|
- journal still does not work; +
|
||||||
- lose/win game; then exit room (while being the creator), then press to stats -> cannot find session in db, although cookie in place and session in db; +
|
- lose/win game; then exit room (while being the creator), then press to stats -> cannot find session in db, although cookie in place and session in db; +
|
||||||
- exit endpoints delets player from db; +
|
- exit endpoints delets player from db; +
|
||||||
- timer end did not update the page;
|
|
||||||
|
|
||||||
- timers conflict; stop timers;
|
|
||||||
- clue snatching;
|
|
||||||
- llm resp token amount limit;
|
|
||||||
|
Reference in New Issue
Block a user