~edwargix/tallyard

80d91f5f23aaf506b085929eac5f2e10d7a4063b — David Florness 4 years ago 1ebfb2c
Allow user to see results of past elections
5 files changed, 42 insertions(+), 33 deletions(-)

M cmd/tallyard/main.go
M election/election.go
M election/msg.go
M election/result.go
M ui/tui.go
M cmd/tallyard/main.go => cmd/tallyard/main.go +6 -7
@@ 111,12 111,12 @@ func main() {
	if err != nil {
		panic(err)
	}
	if el == nil || el.LocalVoter == nil {
	if el == nil {
		// no election selected; user likely hit C-c
		return
	}

	// wait for election to start
	// wait for election to start if needed
	err = ui.ElectionWaitTUI(client, el, elections.EventStore)
	if err != nil {
		panic(err)


@@ 128,15 128,14 @@ func main() {

	// vote if we need to (user may have voted in previous tallyard
	// invocation)
	if el.LocalVoter.Poly == nil {
		candidates := el.Candidates
		ballot := ui.VoteTUI(candidates)
	if el.LocalVoter != nil && el.LocalVoter.Poly == nil {
		ballot := ui.VoteTUI(el.Candidates)
		el.LocalVoter.Poly = math.NewRandomPoly(uint(len(*el.FinalJoinIDs)-1), 1024, ballot)
		el.Save()
	}

	// send evals if needed
	if el.LocalVoter.EvalsID == nil {
	if el.LocalVoter != nil && el.LocalVoter.EvalsID == nil {
		err = el.SendEvals(client, elections.EventStore)
		if err != nil {
			panic(err)


@@ 144,7 143,7 @@ func main() {
	}

	// send sum if needed
	if el.LocalVoter.SumID == nil {
	if el.LocalVoter != nil && el.LocalVoter.SumID == nil {
		fmt.Println("waiting for evals...")
		err = el.SendSum(client, elections.EventStore)
		if err != nil {

M election/election.go => election/election.go +1 -0
@@ 17,6 17,7 @@ type Election struct {
	FinalJoinIDs *[]id.EventID         `json:"final_voters,omitempty"`
	Joins        map[id.EventID]*Voter `json:"joins"`
	LocalVoter   *LocalVoter           `json:"local_voter,omitempty"`
	Result       *[]byte               `json:"result,omitempty"`
	RoomID       id.RoomID             `json:"room_id"`
	StartID      *id.EventID           `json:"start_id,omitempty"`
	Title        string                `json:"title"`

M election/msg.go => election/msg.go +12 -0
@@ 503,5 503,17 @@ func (elections *ElectionsMap) onSumMessage(evt *event.Event) (success bool) {
	voter.SumID = &evt.ID
	voter.Sum = new(big.Int).SetBytes(bytes)

	// once we've recieved all sums, calculate the election result!
	receivedAllSums := true
	for _, voterID := range *el.FinalJoinIDs {
		if el.Joins[voterID].SumID == nil {
			receivedAllSums = false
			break
		}
	}
	if receivedAllSums {
		el.CalculateResult()
	}

	return true
}

M election/result.go => election/result.go +10 -17
@@ 4,29 4,15 @@ import (
	"fmt"
	"math/big"
	"sort"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"
	"maunium.net/go/mautrix/id"
	"tallyard.xyz/math"
)

func (el *Election) PrintResults() {
	var sumIDs []id.EventID
	var wg sync.WaitGroup
	for _, voterJoinId := range *el.FinalJoinIDs {
		wg.Add(1)
		go func(voter *Voter) {
			for voter.SumID == nil {
				time.Sleep(time.Millisecond * 100)
			}
			sumIDs = append(sumIDs, *voter.SumID)
			wg.Done()
		}(el.Joins[voterJoinId])
	}
	wg.Wait()

func (el *Election) CalculateResult() {
	// this assumes all sums have been received and successfully validated
	// and processed
	M := constructPolyMatrix(el)
	M.RREF()
	constant := M[0][len(M[0])-1]


@@ 37,7 23,14 @@ func (el *Election) PrintResults() {
	// number of bytes we need to insert at the front since they're zero
	diff := (len(el.Candidates) * len(el.Candidates)) - len(result)
	result = append(make([]byte, diff), result...)
	el.Result = &result
}

func (el *Election) PrintResults() {
	for el.Result == nil {
		time.Sleep(time.Millisecond * 200)
	}
	result := *el.Result
	candidates := el.Candidates

	log.Debugf("result: %v", result)

M ui/tui.go => ui/tui.go +13 -9
@@ 130,12 130,16 @@ func RoomTUI(client *mautrix.Client, roomID id.RoomID, elections *election.Elect
			el = L[i - 1]
			// don't need to lock because this goroutine controls LocalVoter
			if el.LocalVoter == nil {
				// ask user if s/he wants to join election
				if joinElectionConfirmation(el) {
					err = el.JoinElection(client, elections.EventStore)
				} else {
					// user needs to select a different election
				// ask user if s/he wants to join election (or
				// display results if it's already started)
				if !electionConfirmation(el) {
					// user needs to select a different
					// election
					el, err = RoomTUI(client, roomID, elections)
				} else if el.StartID == nil {
					// user selected yes on joining the
					// election
					err = el.JoinElection(client, elections.EventStore)
				}
			}
			return


@@ 162,7 166,7 @@ func RoomTUI(client *mautrix.Client, roomID id.RoomID, elections *election.Elect
	return
}

func joinElectionConfirmation(el *election.Election) (shouldJoin bool) {
func electionConfirmation(el *election.Election) (shouldJoin bool) {
	app := newTallyardApplication()

	var buttons []string


@@ 171,8 175,8 @@ func joinElectionConfirmation(el *election.Election) (shouldJoin bool) {
	el.RLock()
	// TODO: handle when election starts while in modal
	if el.StartID != nil {
		buttons = []string{"Ok"}
		text = "Election has already started, sorry"
		buttons = []string{"Yes", "No"}
		text = "Election has already started; display results?"
	} else {
		buttons = []string{"Yes", "No"}
		text = "Join election?"


@@ 250,7 254,7 @@ func ElectionWaitTUI(client *mautrix.Client, el *election.Election, eventStore *
	frame.SetTitle(el.Title).SetBorder(true)
	app := newTallyardApplication()
	el.RLock()
	if el.CreateEvt.Sender == el.LocalVoter.JoinEvt.Sender {
	if el.LocalVoter != nil && el.CreateEvt.Sender == el.LocalVoter.JoinEvt.Sender {
		frame.AddText("Press enter to start the election", false, tview.AlignCenter, tcell.ColorWhite)
		app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
			if event.Key() == tcell.KeyEnter {