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()) r.Marks[currentSpeakerGesture] = 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 }