some-automoderation/rooms/room.go

295 lines
9.2 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 %s", p, r.Name)
// 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", nextSpeakerGesture.String(), currentSpeakerGesture.String())
for gesture := HandGesture(0); gesture < nextSpeakerGesture; gesture++ {
_, alreadySet := r.Marks[gesture]
if !alreadySet {
r.Marks[gesture] = p
}
}
}
// also we need to remove marks from top to 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
}
gestureIteration:
for _, gesture := range GesturesHighToLow {
log.Printf("searching for gesture %s", gesture.String())
startIndex := r.gestureSearchStartIndex(gesture, currentSpeakerGesture)
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
}
// find place to start - separate function
// current speaker level, searched gesture level, room marks
// if search level >= current speaker start from speaker
// if search level < current speaekr look for mark, start from mark of that level,
//
// if no mark - from speaker
func (r *Room) gestureSearchStartIndex(gesture, curSpeakerGesture HandGesture) int {
if r.CurrentSpeaker == PersonId(0) {
// nobody is actully a speaker
return 0
}
var personFromWhichToStartSearch PersonId = r.CurrentSpeaker
// if searched guesture is higher or same as current speaker, start from speaker
if gesture >= curSpeakerGesture {
personFromWhichToStartSearch = r.CurrentSpeaker
}
// if searched gesture is of lower priority from current speaker, check marks to return to, or use speaker level
if gesture < curSpeakerGesture {
gestureMark, found := r.Marks[gesture]
if found {
personFromWhichToStartSearch = gestureMark
}
// if no mark found - count from the speaker
}
log.Printf("> selecting person from which to start. cur speaker %d with gesture %s, for gestrue %s, got person %d",
r.CurrentSpeaker, curSpeakerGesture.String(), gesture.String(), personFromWhichToStartSearch)
indexFromWhichToStart := slices.Index(r.Paricipants, personFromWhichToStartSearch)
return indexFromWhichToStart
}
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
}