M main.go => main.go +2 -2
@@ 25,7 25,7 @@ var (
logger = log.Logger("tallyard")
protocolID = protocol.ID("/tallyard/0.0.0")
- candidates []ElectionOption
+ candidates []Candidate
optionsMerkle *merkletree.MerkleTree
rendezvousNonce Nonce
@@ 155,7 155,7 @@ func bootstrap() {
}
str = stripNewline(str)
if str != "" {
- candidates = append(candidates, ElectionOption(str))
+ candidates = append(candidates, Candidate(str))
fmt.Println(str)
}
if err == io.EOF {
M merkle.go => merkle.go +4 -4
@@ 11,9 11,9 @@ import (
"github.com/mr-tron/base58/base58"
)
-type ElectionOption string
+type Candidate string
-func (eo ElectionOption) CalculateHash() ([]byte, error) {
+func (eo Candidate) CalculateHash() ([]byte, error) {
h := sha256.New()
if _, err := h.Write([]byte(eo)); err != nil {
return nil, err
@@ 21,8 21,8 @@ func (eo ElectionOption) CalculateHash() ([]byte, error) {
return h.Sum(nil), nil
}
-func (eo ElectionOption) Equals(other merkletree.Content) (bool, error) {
- return eo == other.(ElectionOption), nil
+func (eo Candidate) Equals(other merkletree.Content) (bool, error) {
+ return eo == other.(Candidate), nil
}
type Nonce string
M ui.go => ui.go +70 -5
@@ 2,6 2,7 @@ package main
import (
"fmt"
+ "sort"
"strconv"
"strings"
"unicode"
@@ 37,7 38,7 @@ func createElection() {
rendezvousNonce = NewNonce()
content := []merkletree.Content{rendezvousNonce}
for i := 0; i < n-1; i++ {
- eo := ElectionOption(form.GetFormItem(i).(*tview.InputField).GetText())
+ eo := Candidate(form.GetFormItem(i).(*tview.InputField).GetText())
candidates = append(candidates, eo)
content = append(content, eo)
}
@@ 80,10 81,12 @@ func joinElection() {
}
}
-func vote(candidates []ElectionOption) []int {
- result := make([]int, len(candidates))
+// displays a voting UI to the user and returns the encoded ballot
+func vote(candidates []Candidate) []byte {
+ ranks := make([]int, len(candidates))
app := tview.NewApplication()
form := tview.NewForm()
+
for _, eo := range candidates {
// TODO: support more than 99 candidates
form.AddInputField(string(eo), "", 2,
@@ 91,6 94,7 @@ func vote(candidates []ElectionOption) []int {
return len(textToCheck) < 3 && unicode.IsDigit(lastChar)
}, nil)
}
+
form.AddButton("Submit", func() {
app.Stop()
for i := 0; i < len(candidates); i++ {
@@ 99,11 103,72 @@ func vote(candidates []ElectionOption) []int {
if err != nil {
panic(err)
}
- result[i] = rank
+ ranks[i] = rank
}
})
+
if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil {
panic(err)
}
- return result
+
+ return GetBallotFromRankings(ranks)
+}
+
+func GetBallotFromRankings(ranks []int) []byte {
+ n := len(ranks)
+ candidates := make([]int, n)
+
+ for i := 0; i < n; i++ {
+ candidates[i] = i
+ }
+
+ // sort candidates by ranking
+ cr := CandidateRanking{candidates, ranks}
+ sort.Sort(&cr)
+
+ // TODO: support more than 255 voters (limit from usage of byte)
+ prefMatrix := make([][]byte, n)
+
+ for len(candidates) > 0 {
+ r := ranks[candidates[0]]
+ i := 1
+ for ; i < len(candidates) && ranks[candidates[i]] == r; i++ {}
+ // i is now index of the first candidate with worse (i.e. higher
+ // in value) rank
+ row := make([]byte, n)
+ for j := i; j < len(candidates); j++ {
+ row[candidates[j]] = 1
+ }
+ for j := 0; j < i; j++ {
+ prefMatrix[candidates[j]] = row
+ }
+ candidates = candidates[i:]
+ }
+
+ // convert 2D array into 1D array
+ barray := make([]byte, 0, n*n)
+ for _, row := range prefMatrix {
+ barray = append(barray, row...)
+ }
+
+ return barray
+}
+
+type CandidateRanking struct {
+ candidates []int // becomes list of candidate IDs sorted by rank
+ ranks []int // maps candidate ID to rank
+}
+
+func (cr *CandidateRanking) Len() int {
+ return len(cr.ranks)
+}
+
+func (cr *CandidateRanking) Less(i, j int) bool {
+ return cr.ranks[cr.candidates[i]] < cr.ranks[cr.candidates[j]]
+}
+
+func (cr *CandidateRanking) Swap(i, j int) {
+ tmp := cr.candidates[i]
+ cr.candidates[i] = cr.candidates[j]
+ cr.candidates[j] = tmp
}