Created
July 20, 2016 22:58
Star
You must be signed in to star a gist
Coder Radio Challenge #1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"github.com/fatih/color" | |
"github.com/gizak/termui" | |
) | |
const ( | |
// rowColorDecoration styles the currently-selected row for a selectableList | |
rowColorDecoration = "fg-white,bg-green" | |
// margin provides spacing between UI widgets so they don't collide | |
margin = 2 | |
) | |
// selectableList represents a termui.List with the added capability | |
// of being able to mark any row as 'selected' | |
type selectableList struct { | |
list *termui.List | |
restore string | |
activeRow int | |
} | |
// newSelectableList is a convenience constructor for the selectablsList struct. | |
// It returns a selectableList with default styling, and the first row preselected | |
func newSelectableList(items []string) selectableList { | |
list := termui.NewList() | |
list.Items = items | |
list.ItemFgColor = termui.ColorYellow | |
list.BorderLabel = "CHOOSE" | |
list.Height = len(items) + margin // give a little room at the bottom to make sure we don't cut anything off | |
// calculate width for list | |
longestItem := 10 // list should be at least 10 wide to avoid looking weird | |
for _, item := range items { | |
if len(item) > longestItem { | |
longestItem = len(item) | |
} | |
} | |
list.Width = longestItem + margin | |
list.Overflow = "wrap" | |
sl := selectableList{ | |
list: list, | |
restore: list.Items[0], | |
} | |
sl.selectRow(0) | |
return sl | |
} | |
// selectRow selects a row within the list, clipping it to within the bounds of the indices of the list | |
func (s *selectableList) selectRow(row int) { | |
if row < 0 || row >= len(s.list.Items) { | |
return | |
} | |
s.list.Items[s.activeRow] = s.restore | |
s.activeRow = row | |
s.restore = s.list.Items[s.activeRow] | |
s.list.Items[s.activeRow] = fmt.Sprintf("[%s](%s)", s.list.Items[s.activeRow], rowColorDecoration) | |
} | |
// up moves the selector one element up the list. If it was already at the top, nothing happens | |
func (s *selectableList) up() { | |
s.selectRow(s.activeRow - 1) | |
} | |
// down moves the selector one element down the list. If it was already at the bottom, nothing happens | |
func (s *selectableList) down() { | |
s.selectRow(s.activeRow + 1) | |
} | |
// question represents a singular question in a survey. | |
// It is a linked list, providing a pointer to the next question in the survey. | |
// It also keeps track of the answer the user selected | |
type question struct { | |
title string | |
list *selectableList | |
answer string | |
next *question | |
} | |
// newQuestion is a convenience constructor for the question struct. | |
func newQuestion(title string, items []string, next *question) question { | |
list := newSelectableList(items) | |
return question{ | |
title: title, | |
list: &list, | |
next: next, | |
} | |
} | |
// makeTitle creates a termui.Par (a text box essentially) | |
func makeTitle(s string) *termui.Par { | |
title := termui.NewPar(s) | |
title.Height = 3 | |
title.Width = len(s) + margin | |
title.TextFgColor = termui.ColorWhite | |
title.BorderFg = termui.ColorCyan | |
return title | |
} | |
// runSurvey walks through a linked list of questions, presenting them one by one | |
// and keeping track of the answers | |
func runSurvey(question *question) { | |
title := makeTitle(question.title) | |
current := question | |
current.list.list.Y = title.Height + margin // position list of answers underneath the question's title | |
help := makeTitle(":press 'q' to exit, ↑ and ↓ to change selection, <enter> to confirm") | |
help.X = termui.TermWidth() - help.Width - margin // right justify | |
var reRender = func() { | |
termui.Clear() | |
termui.Render(title, help, current.list.list) | |
} | |
reRender() | |
termui.Handle("/sys/kbd/<up>", func(termui.Event) { | |
current.list.up() | |
reRender() | |
}) | |
termui.Handle("/sys/kbd/<down>", func(termui.Event) { | |
current.list.down() | |
reRender() | |
}) | |
termui.Handle("/sys/kbd/<enter>", func(termui.Event) { | |
current.answer = current.list.restore | |
if current.next != nil { | |
current = current.next | |
current.list.list.Y = title.Height + margin | |
title = makeTitle(current.title) | |
current.list.list.Y = title.Height + margin | |
reRender() | |
} else { | |
termui.StopLoop() | |
} | |
}) | |
} | |
// createAnswerMap is a convenience function to construct the inner values of the 'answer' map | |
func createAnswerMap(d2, d3, d4, d5, d6, d7, d8, d9, d10, dA string) map[string]string { | |
return map[string]string{ | |
"2": d2, | |
"3": d3, | |
"4": d4, | |
"5": d5, | |
"6": d6, | |
"7": d7, | |
"8": d8, | |
"9": d9, | |
"10": d10, | |
"A": dA, | |
} | |
} | |
func main() { | |
err := termui.Init() | |
if err != nil { | |
panic(err) | |
} | |
// Codes if you are allowed to surrender | |
surrenderMap := map[string]string{ | |
"H": "Hit", | |
"S": "Stand", | |
"D": "Double if allowed, otherwise Stand", | |
"DS": "Double if allowed, otherwise Stand", | |
"SP": "Split", | |
"X/H": "Surrender", | |
"X/P": "Surrender", | |
"X/S": "Surrender", | |
} | |
// Codes if you are not allowed to surrender | |
normalMap := map[string]string{ | |
"H": "Hit", | |
"S": "Stand", | |
"D": "Double if allowed, otherwise Stand", | |
"DS": "Double if allowed, otherwise Stand", | |
"SP": "Split", | |
"X/H": "Hit", | |
"X/P": "Split", | |
"X/S": "Stand", | |
} | |
// adviceMap is a nested map which is indexed first by the player's hand | |
// and then by the dealer's hand. It returns a code which can be | |
// looked up in either surrenderMap or normalMap, depending on if surrendering | |
// is allowed. | |
adviceMap := map[string]map[string]string{ | |
"8": createAnswerMap("H", "H", "H", "H", "H", "H", "H", "H", "H", "H"), | |
"9": createAnswerMap("H", "D", "D", "D", "D", "H", "H", "H", "H", "H"), | |
"10": createAnswerMap("D", "D", "D", "D", "D", "D", "D", "D", "J", "H"), | |
"11": createAnswerMap("D", "D", "D", "D", "D", "D", "D", "D", "D", "D"), | |
"12": createAnswerMap("H", "H", "S", "S", "S", "H", "H", "H", "H", "H"), | |
"13": createAnswerMap("S", "S", "S", "S", "S", "H", "H", "H", "H", "H"), | |
"14": createAnswerMap("S", "S", "S", "S", "S", "H", "H", "H", "H", "H"), | |
"15": createAnswerMap("S", "S", "S", "S", "S", "H", "H", "H", "X/H", "X/H"), | |
"16": createAnswerMap("S", "S", "S", "S", "S", "H", "H", "X/H", "X/H", "X/H"), | |
"17": createAnswerMap("S", "S", "S", "S", "S", "S", "S", "S", "S", "X/S"), | |
"A,2": createAnswerMap("H", "H", "H", "D", "D", "H", "H", "H", "H", "H"), | |
"A,3": createAnswerMap("H", "H", "H", "D", "D", "H", "H", "H", "H", "H"), | |
"A,4": createAnswerMap("H", "H", "D", "D", "D", "H", "H", "H", "H", "H"), | |
"A,5": createAnswerMap("H", "H", "D", "D", "D", "H", "H", "H", "H", "H"), | |
"A,6": createAnswerMap("H", "D", "D", "D", "D", "H", "H", "H", "H", "H"), | |
"A,7": createAnswerMap("S", "DS", "DS", "DS", "DS", "S", "S", "H", "H", "H"), | |
"A,8": createAnswerMap("S", "S", "S", "S", "S", "S", "S", "S", "S", "S"), | |
"A,9": createAnswerMap("S", "S", "S", "S", "S", "S", "S", "S", "S", "S"), | |
"2,2": createAnswerMap("SP", "SP", "SP", "SP", "SP", "SP", "H", "H", "H", "H"), | |
"3,3": createAnswerMap("SP", "SP", "SP", "SP", "SP", "SP", "H", "H", "H", "H"), | |
"4,4": createAnswerMap("H", "H", "H", "SP", "SP", "H", "H", "H", "H", "H"), | |
"5,5": createAnswerMap("D", "D", "D", "D", "D", "D", "D", "D", "D", "H.H"), | |
"6,6": createAnswerMap("SP", "SP", "SP", "SP", "SP", "H", "H", "H", "H", "H"), | |
"7,7": createAnswerMap("SP", "SP", "SP", "SP", "SP", "SP", "H", "H", "H", "H"), | |
"8,8": createAnswerMap("SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "X/P"), | |
"9,9": createAnswerMap("SP", "SP", "SP", "SP", "SP", "S", "SP", "SP", "S", "S"), | |
"10,10": createAnswerMap("S", "S", "S", "S", "S", "S", "S", "S", "S", "S"), | |
"A,A": createAnswerMap("SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP", "SP"), | |
} | |
// build up a linked list of questions, starting with the last question and working toward the first: | |
// canSurrender? -> choose myHand -> choose dealerHand -> nil | |
dealerHand := newQuestion("Select Dealer's Hand", []string{"2", "3", "4", "5", "6", "7", "8", "9", "10", "A"}, nil) | |
myHand := newQuestion("Select Your Hand", []string{"8", "9", "10", "11", "12", "13", "14", | |
"15", "16", "17", "A,2", "A,3", "A,4", "A,5", "A,6", "A,7", "A,8", "A,9", | |
"2,2", "3,3", "4,4", "5,5", "6,6", "7,7", "8,8", "9,9", "10,10", "A,A"}, &dealerHand) | |
canSurrender := newQuestion("Surrender Allowed???", []string{"YES", "NO"}, &myHand) | |
// press 'q' to quit | |
termui.Handle("/sys/kbd/q", func(termui.Event) { | |
termui.StopLoop() | |
}) | |
runSurvey(&canSurrender) | |
termui.Loop() | |
// after we have run the survey, close out the terminal ui and print the advice | |
termui.Close() | |
// adviceMap will give an empty string in the case that the user exited early | |
// by pressing 'q'. Otherwise we are guaranteed an answer | |
if p := adviceMap[myHand.answer][dealerHand.answer]; p != "" { | |
adviceCode := adviceMap[myHand.answer][dealerHand.answer] | |
var verboseAnswer string | |
if canSurrender.answer == "YES" { | |
verboseAnswer = surrenderMap[adviceCode] | |
} else { | |
verboseAnswer = normalMap[adviceCode] | |
} | |
// print the advice in a pretty way | |
format := color.New(color.FgHiGreen).Add(color.Bold).Add(color.Underline).SprintFunc() | |
fmt.Printf("You should %s in this scenario. Good luck my friend!!\n", | |
format(verboseAnswer)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment