269 lines
8.1 KiB
Go
269 lines
8.1 KiB
Go
package rooms
|
|
|
|
import (
|
|
"html/template"
|
|
"log"
|
|
"maps"
|
|
"slices"
|
|
)
|
|
|
|
type Room struct {
|
|
Name string // will be unique ID
|
|
AdminIds []PersonId
|
|
PasswordHash string
|
|
CurrentSpeaker PersonId // i guess let's set to zero value when it's noone from the room
|
|
// all people that were visiting room before, spectating now or being a participant
|
|
// used to check person password against the name
|
|
AllKnownPeople map[PersonId]Person
|
|
// person ids of people who are currently participating in the discussion at the table
|
|
// TODO for now peson in seated at join and removed at logout in routes
|
|
Paricipants []PersonId
|
|
ParticipantHands map[PersonId]HandGesture
|
|
Marks map[HandGesture]PersonId
|
|
}
|
|
|
|
func (r *Room) InitMaps() {
|
|
if r.ParticipantHands == nil {
|
|
r.ParticipantHands = make(map[PersonId]HandGesture)
|
|
}
|
|
if r.Marks == nil {
|
|
r.Marks = make(map[HandGesture]PersonId)
|
|
}
|
|
}
|
|
|
|
// if you have no hand raised - select any hand
|
|
// if you are not speaking and have a hand raise - just exchange
|
|
// if you are speaking - change nothing
|
|
// if nobody is speaking, set this person as a first speaker
|
|
func (r *Room) RaiseHand(p PersonId, gesture HandGesture) Room {
|
|
if r.CurrentSpeaker == p {
|
|
// if person already speaking, should first end speaking
|
|
return *r
|
|
}
|
|
r.ParticipantHands[p] = gesture
|
|
if r.CurrentSpeaker == PersonId(0) {
|
|
r.CurrentSpeaker = p
|
|
}
|
|
return *r
|
|
}
|
|
|
|
// releasing the hand - removing the hand gesture currently being held by p PersonId
|
|
// if p is not current speaker: just removing the signal is ok
|
|
// when p is current speaker:
|
|
// - we should find next speaker
|
|
// - assign room.CurrentSpeaker to next speaker or to PersonId(0) to indicate that noone is speaking
|
|
// - if next speaker has gesture of higher priority - set Mark to current speaker for their gesture level
|
|
func (r *Room) ReleaseHand(p PersonId) {
|
|
// releasing a hand of a current speaker should result in selection of a new speaker
|
|
log.Printf("about to release hand of %d in %+v", p, r)
|
|
// keeping hand in room until end of funcion in case we'll need it to determine next speaker
|
|
defer delete(r.ParticipantHands, p)
|
|
|
|
handReleaseGesture, handReleaserFound := r.ParticipantHands[p]
|
|
if !handReleaserFound {
|
|
return
|
|
}
|
|
|
|
// if not a current speaker, no complicated logic required to release a hand
|
|
if r.CurrentSpeaker != p {
|
|
return
|
|
}
|
|
|
|
// if a current speaker raises their hand
|
|
// - in addition to removing the hand, we need to find next speaker
|
|
nextSpeakerIndex, nextSpeakerFound, _ := r.NextSpeakerIndex()
|
|
|
|
if !nextSpeakerFound {
|
|
log.Printf("there is not next speaker, that's ok")
|
|
r.CurrentSpeaker = PersonId(0)
|
|
} else {
|
|
// searching for the next speaker
|
|
currentSpeakerGesture := handReleaseGesture
|
|
nextSpeakerId := r.Paricipants[nextSpeakerIndex]
|
|
nextSpeakerGesture := r.ParticipantHands[nextSpeakerId]
|
|
log.Printf("found next speaker %+v", nextSpeakerId)
|
|
|
|
if nextSpeakerGesture > currentSpeakerGesture {
|
|
// raising the level of the speaker, need to save mark
|
|
log.Printf("we do have nextSpeaker of priority %s higher than current %s. setting marks between them",
|
|
nextSpeakerGesture.String(),
|
|
currentSpeakerGesture.String())
|
|
for gesture := currentSpeakerGesture; gesture < nextSpeakerGesture; gesture++ {
|
|
log.Printf(">>>>> iterating in setting gesture for %s. marks %+v", gesture.String(), r)
|
|
_, alreadySet := r.Marks[gesture]
|
|
if !alreadySet {
|
|
r.Marks[gesture] = p
|
|
}
|
|
}
|
|
}
|
|
|
|
// also we need to remove marks from top to new current speaker level
|
|
for _, gesture := range GesturesHighToLow {
|
|
if gesture < nextSpeakerGesture {
|
|
break
|
|
}
|
|
delete(r.Marks, gesture)
|
|
}
|
|
|
|
r.CurrentSpeaker = nextSpeakerId
|
|
}
|
|
}
|
|
|
|
// fully remove p PersonId from participants
|
|
func (r *Room) PersonToStandUpFromTable(p PersonId) {
|
|
personGesture, found := r.ParticipantHands[p]
|
|
if found {
|
|
r.ReleaseHand(p)
|
|
if r.Marks[personGesture] == p {
|
|
// the leaving person is marked
|
|
// TODO get better logic, maybe assign mark to a neighbor
|
|
delete(r.Marks, personGesture)
|
|
}
|
|
}
|
|
if slices.Contains(r.Paricipants, p) {
|
|
updated := slices.DeleteFunc(r.Paricipants, func(sittingPerson PersonId) bool {
|
|
return sittingPerson == p
|
|
})
|
|
r.Paricipants = updated
|
|
}
|
|
}
|
|
|
|
// find index of next speaker
|
|
// for the room with current speaker and their gesture present
|
|
// also returning index from which the person was counted:
|
|
// - if the gesture of next speaker is of same of higher priority
|
|
// should be counted from current speaker
|
|
// - if the gesture is of lover priority and there is no mark
|
|
// should be counted from current speaker
|
|
// - if the gesture is of lover priority and there is a mark for the gesture level
|
|
// should be counted from the mark
|
|
func (r *Room) NextSpeakerIndex() (nextSpeakerIndex int, found bool, countedFromIndex int) {
|
|
// if a current speaker - before removing the hand, we need to find next speaker
|
|
// from highest hand gesture to lowest, until one is found
|
|
|
|
currentSpeakerGesture, currentSpeakerFound := r.ParticipantHands[r.CurrentSpeaker]
|
|
if !currentSpeakerFound {
|
|
log.Printf("> cur speaker gesture %s and found %t", currentSpeakerGesture.String(), currentSpeakerFound)
|
|
return -1, false, -1
|
|
}
|
|
startIndex := slices.Index(r.Paricipants, r.CurrentSpeaker)
|
|
|
|
gestureIteration:
|
|
for _, gesture := range GesturesHighToLow {
|
|
log.Printf("searching for gesture %s", gesture.String())
|
|
levelMark, levelHasMark := r.Marks[gesture]
|
|
if levelHasMark {
|
|
startIndex = slices.Index(r.Paricipants, levelMark)
|
|
}
|
|
participantsCount := len(r.Paricipants)
|
|
inGestureParticipantIteration:
|
|
for i := 1; i <= participantsCount; i++ {
|
|
checkIndex := (startIndex + i) % participantsCount
|
|
checkPerson := r.Paricipants[checkIndex]
|
|
if checkPerson == r.CurrentSpeaker {
|
|
// current speaker still has thair gesture up
|
|
continue inGestureParticipantIteration
|
|
}
|
|
checkGesture, isFound := r.ParticipantHands[checkPerson]
|
|
if isFound && checkGesture == gesture {
|
|
nextSpeakerIndex = slices.Index(r.Paricipants, checkPerson)
|
|
countedFromIndex = startIndex
|
|
found = true
|
|
break gestureIteration
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
log.Printf("there is not next speaker, that's ok")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (r *Room) Equal(other *Room) bool {
|
|
if r == other {
|
|
return true
|
|
}
|
|
if r.Name != other.Name || r.PasswordHash != other.PasswordHash || r.CurrentSpeaker != other.CurrentSpeaker {
|
|
return false
|
|
}
|
|
if !slices.Equal(r.AdminIds, other.AdminIds) || !slices.Equal(r.Paricipants, other.Paricipants) {
|
|
return false
|
|
}
|
|
if !maps.Equal(r.ParticipantHands, other.ParticipantHands) ||
|
|
!maps.Equal(r.Marks, other.Marks) ||
|
|
!maps.Equal(r.AllKnownPeople, other.AllKnownPeople) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// sooooooo. i need hand types. are there enums in go?
|
|
type HandGesture uint8
|
|
|
|
const (
|
|
ChangeTopic HandGesture = iota
|
|
ProbingQ
|
|
Expand
|
|
ClarifyingQ
|
|
Meta
|
|
)
|
|
|
|
var GesturesHighToLow = [...]HandGesture{4, 3, 2, 1, 0}
|
|
|
|
func GestureFromInt(num int) (HandGesture, bool) {
|
|
if num >= int(ChangeTopic) && num <= int(Meta) {
|
|
return HandGesture(num), true
|
|
}
|
|
return HandGesture(0), false
|
|
}
|
|
|
|
// String() method to print the name of the days
|
|
func (g HandGesture) String() string {
|
|
return [...]string{"Change Topic", "Probing Quesion", "Expand", "Clarifying Quesion", "Meta"}[g]
|
|
}
|
|
|
|
type GestureInfo struct {
|
|
Name string
|
|
Color, ColorDark template.CSS
|
|
IconUrl template.URL
|
|
}
|
|
|
|
func (g HandGesture) GetGestureInfo() GestureInfo {
|
|
result := GestureInfo{}
|
|
switch g {
|
|
case ChangeTopic:
|
|
result = GestureInfo{
|
|
Color: "--change-topic-color",
|
|
ColorDark: "--change-topic-dark",
|
|
IconUrl: "/static/images/change-topic.png",
|
|
}
|
|
case ProbingQ:
|
|
result = GestureInfo{
|
|
Color: "--probing-q-color",
|
|
ColorDark: "--probing-q-dark",
|
|
IconUrl: "/static/images/probing-q.png",
|
|
}
|
|
case Expand:
|
|
result = GestureInfo{
|
|
Color: "--expand-color",
|
|
ColorDark: "--expand-dark",
|
|
IconUrl: "/static/images/expand.png",
|
|
}
|
|
case ClarifyingQ:
|
|
result = GestureInfo{
|
|
Color: "--clarifying-q-color",
|
|
ColorDark: "--clarifying-q-dark",
|
|
IconUrl: "/static/images/clarifying-q.png",
|
|
}
|
|
case Meta:
|
|
result = GestureInfo{
|
|
Color: "--meta-color",
|
|
ColorDark: "--meta-dark",
|
|
IconUrl: "/static/images/meta.png",
|
|
}
|
|
}
|
|
result.Name = g.String()
|
|
return result
|
|
}
|