Skip to content

Instantly share code, notes, and snippets.

@chad-russell
Created July 20, 2016 22:58
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save chad-russell/f00fc05900e1912c3329850e0ab42bc3 to your computer and use it in GitHub Desktop.
Coder Radio Challenge #1
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