~hokiegeek/tucana

f99f2e86687a474e4707c832fa4a240355fb9599 — HokieGeek 4 months ago c09d8fb v0.6.1
Corrected an error with scoring
2 files changed, 128 insertions(+), 106 deletions(-)

M cmd/tucana/main.go
M game.go
M cmd/tucana/main.go => cmd/tucana/main.go +35 -36
@@ 17,27 17,49 @@ func getInput(game *tucana.Game) error {
	if len(input.Text()) > 0 {
		inputs := strings.Split(input.Text(), " ")

		// TODO: only accepts village connections right now
		var points int
		for _, v := range inputs {
			switch {
			case strings.Contains(v, "-"):
				villages := strings.Split(strings.TrimSpace(v), "-")
				game.ConnectVillages(tucana.VillageConnection(villages[0]), tucana.VillageConnection(villages[1]))
				pts, bon := game.ConnectVillages(tucana.VillageConnection(villages[0]), tucana.VillageConnection(villages[1]))
				points += pts + bon
				_, _, bonuses, _ := game.Current()
				fmt.Println("Bonuses: ", bonuses.String())
				if bon > 0 {
					fmt.Println("Bonuses: ", bonuses.String())
				}
			case strings.ToLower(v) == "score":
				fmt.Println(game.Score().String())
			default: // Record a found sight
				if freeSegment := game.VisitSight(tucana.SightFromString(v)); freeSegment {
				var sight string
				switch v {
				case "o":
					sight = "obelisk"
				case "b":
					sight = "book"
				case "t":
					sight = "toucan"
				case "n":
					sight = "native"
				case "d":
					sight = "dragon"
				default:
					sight = v
				}
				pts, bon, freeSegment := game.VisitSight(tucana.SightFromString(sight))
				if freeSegment {
					fmt.Printf("Free segment!> ")
					if err := getInput(game); err != nil {
						return err
					}
					// FIXME: need to be able to enter a new Villages or sights here!
				}
				points += pts + bon
				// TODO: display remaining visits?
			}
		}
		if points > 0 {
			fmt.Printf("+%d\n", points)
		}
	}

	return nil


@@ 66,6 88,11 @@ func main() {

	fmt.Println(game.String())

	printScore := func() {
		fmt.Println("Score>")
		fmt.Print(game.Score().String())
	}

	var currentRound int
	for !game.Ended() {
		game.NextTurn()


@@ 73,7 100,7 @@ func main() {
		if currentRound != round {
			fmt.Printf("<Round %d>\n", round)
			fmt.Println("Bonuses: ", bonuses.String())
			fmt.Println("Score:\n", game.Score().String())
			printScore()
			currentRound = round
		}



@@ 82,37 109,9 @@ func main() {
		if err := getInput(game); err != nil {
			panic(err)
		}

		/*
			input := bufio.NewScanner(os.Stdin)
			input.Scan()

				if len(input.Text()) > 0 {
					inputs := strings.Split(input.Text(), " ")

					// TODO: only accepts village connections right now
					for _, v := range inputs {
						switch {
						case strings.Contains(v, "-"):
							villages := strings.Split(strings.TrimSpace(v), "-")
							game.ConnectVillages(tucana.VillageConnection(villages[0]), tucana.VillageConnection(villages[1]))
							_, _, bonuses, _ := game.Current()
							fmt.Println("Bonuses: ", bonuses.String())
						case strings.ToLower(v) == "score":
							fmt.Println(game.Score().String())
						default: // Record a found sight
							if freeSegment := game.VisitSight(tucana.SightFromString(v)); freeSegment {
								fmt.Println("Free segment!")
								// FIXME: need to be able to enter a new Villages or sights here!
							}
							// TODO: display remaining visits?
						}
					}

					// TODO: display score
				}
		*/
	}

	fmt.Println("Game Over!")
	fmt.Print("<Final ")
	printScore()
}

M game.go => game.go +93 -70
@@ 248,7 248,11 @@ func init() {
	BonusVillagesCards[VillagesConnectionDD] = []int{6, 4}
	BonusVillagesCards[VillagesConnectionEE] = []int{7, 5}

	// TODO: Bonus sights (variant): 6 points for each for a sight seen twice
	/* TODO: Bonus sights (variant): 6 points for each for a sight seen twice
	if variant is selected:
	2-4 players: 1 card  randomly selected
	5-8 players: 2 cards randomly selected
	*/

	SightScoring = make(map[Board]map[Sight][]int)
	SightScoring[IslaPetit] = make(map[Sight][]int)


@@ 272,11 276,11 @@ func init() {
	VillagesScoring[IslaPetit][VillagesConnectionDD] = 11
	VillagesScoring[IslaPetit][VillagesConnectionEE] = 10
	VillagesScoring[IslaGrande] = make(map[VillageConnection]int)
	VillagesScoring[IslaGrande][VillagesConnectionAA] = VillagesScoring[IslaPetit][VillagesConnectionAA] * 6
	VillagesScoring[IslaGrande][VillagesConnectionBB] = VillagesScoring[IslaPetit][VillagesConnectionBB] * 6
	VillagesScoring[IslaGrande][VillagesConnectionCC] = VillagesScoring[IslaPetit][VillagesConnectionCC] * 6
	VillagesScoring[IslaGrande][VillagesConnectionDD] = VillagesScoring[IslaPetit][VillagesConnectionDD] * 6
	VillagesScoring[IslaGrande][VillagesConnectionEE] = VillagesScoring[IslaPetit][VillagesConnectionEE] * 6
	VillagesScoring[IslaGrande][VillagesConnectionAA] = VillagesScoring[IslaPetit][VillagesConnectionAA] + 6
	VillagesScoring[IslaGrande][VillagesConnectionBB] = VillagesScoring[IslaPetit][VillagesConnectionBB] + 6
	VillagesScoring[IslaGrande][VillagesConnectionCC] = VillagesScoring[IslaPetit][VillagesConnectionCC] + 6
	VillagesScoring[IslaGrande][VillagesConnectionDD] = VillagesScoring[IslaPetit][VillagesConnectionDD] + 6
	VillagesScoring[IslaGrande][VillagesConnectionEE] = VillagesScoring[IslaPetit][VillagesConnectionEE] + 6
}

// Score captures the current score


@@ 303,27 307,29 @@ func (s Score) String() string {

// Game captures the state of a running game
type Game struct {
	InitiaSetup             InitialSetupCard
	board                   Board
	terrain                 TerrainDeck
	villageBonuses          BonusVillagesDeck
	numPlayers, round, turn int
	sightsVisited           [3]map[Sight]int
	villagesConnected       map[VillageConnection]bool
	bonuses                 []int
	InitiaSetup       InitialSetupCard           `json:"initialSetup"`
	Board             Board                      `json:"board"`
	Terrain           TerrainDeck                `json:"terrain"`
	VillageBonuses    BonusVillagesDeck          `json:"villageBonuses"`
	NumPlayers        int                        `json:"numPlayers"`
	Round             int                        `json:"round"`
	Turn              int                        `json:"turn"`
	SightsVisited     [3]map[Sight]int           `json:"sightsVisited"`
	VillagesConnected map[VillageConnection]bool `json:"villagesConnected"`
	BonusesEarned     []int                      `json:"bonusesEarned"`
}

func (g *Game) currentTerrain() []Terrain {
	if g.turn < 1 || g.turn > 13 {
	if g.Turn < 1 || g.Turn > 13 {
		return nil
	}
	pos := (g.turn - 1) * 2
	return g.terrain[pos : pos+2]
	pos := (g.Turn - 1) * 2
	return g.Terrain[pos : pos+2]
}

func (g *Game) reduceVillageBonuses() BonusVillagesDeck {
	villages := make([]VillageConnection, 0)
	for k := range g.villageBonuses {
	for k := range g.VillageBonuses {
		villages = append(villages, k)
	}
	rand.Seed(time.Now().UnixNano())


@@ 331,11 337,11 @@ func (g *Game) reduceVillageBonuses() BonusVillagesDeck {

	newVillageBonuses := make(map[VillageConnection][]int)
	for i, v := range villages {
		if g.numPlayers == 1 && i > 1 {
		if g.NumPlayers == 1 && i > 1 {
			continue
		}

		newVillageBonuses[v] = g.villageBonuses[v]
		newVillageBonuses[v] = g.VillageBonuses[v]
	}

	return newVillageBonuses


@@ 344,78 350,91 @@ func (g *Game) reduceVillageBonuses() BonusVillagesDeck {
// Current returns the currently played terrain cards
func (g *Game) Current() (int, int, BonusVillagesDeck, []Terrain) {
	bonuses := make(map[VillageConnection][]int)
	for k, v := range g.villageBonuses {
	for k, v := range g.VillageBonuses {
		bonuses[k] = v
	}

	return g.round, g.turn, bonuses, g.currentTerrain()
	return g.Round, g.Turn, bonuses, g.currentTerrain()
}

// NextTurn progresses the game by one turn
func (g *Game) NextTurn() {
	g.turn++
	g.Turn++

	// End round
	if g.turn > maxTurns {
		g.round++
		g.turn = 1
		g.terrain = TerrainCards.Shuffle()
		g.villageBonuses = g.reduceVillageBonuses()
	if g.Turn > maxTurns {
		g.Round++
		g.Turn = 1
		g.Terrain = TerrainCards.Shuffle()

		if g.NumPlayers == 1 && g.Round >= 2 {
			fmt.Println("might have to reduce!")
			switch {
			case g.Board == IslaPetit && g.Round == 2:
				fmt.Println("petit: yup!")
				fallthrough
			case g.Board == IslaGrande && g.Round == 3:
				fmt.Println("grande: yup!")
				g.VillageBonuses = g.reduceVillageBonuses()
			}
		}
	}
}

// Ended returns true if the game has ended
func (g *Game) Ended() bool {
	if g.round > maxRoundsBoard[g.board] {
	if g.Round > maxRoundsBoard[g.Board] {
		return true
	}

	if g.round == maxRoundsBoard[g.board] && g.turn >= maxTurns {
	if g.Round == maxRoundsBoard[g.Board] && g.Turn >= maxTurns {
		return true
	}
	return false
}

// ConnectVillages marks a set of villages which were connected
func (g *Game) ConnectVillages(a, b VillageConnection) bool {
func (g *Game) ConnectVillages(a, b VillageConnection) (int, int) {
	conn := VillageConnection(strings.ToUpper(fmt.Sprintf("%s-%s", a, b)))
	// Add the connection to the tracker
	g.villagesConnected[conn] = true
	g.VillagesConnected[conn] = true

	if bonuses, ok := g.villageBonuses[conn]; ok && len(bonuses) > 0 {
	var earnedBonus int
	if bonuses, ok := g.VillageBonuses[conn]; ok && len(bonuses) > 0 {
		// Score the bonus
		g.bonuses = append(g.bonuses, bonuses[0])
		g.BonusesEarned = append(g.BonusesEarned, bonuses[0])
		earnedBonus = g.BonusesEarned[len(g.BonusesEarned)-1]

		// Remove it from the deck
		g.villageBonuses[conn] = bonuses[1:]
		if len(g.villageBonuses[conn]) == 0 {
			delete(g.villageBonuses, conn)
		g.VillageBonuses[conn] = bonuses[1:]
		if len(g.VillageBonuses[conn]) == 0 {
			delete(g.VillageBonuses, conn)
		}
	}
	return false

	return VillagesScoring[g.Board][conn], earnedBonus
}

// VisitSight marks a sight as visited
func (g *Game) VisitSight(sight Sight) bool {
func (g *Game) VisitSight(sight Sight) (int, int, bool) {
	var numVisits int
	for _, sights := range g.sightsVisited {
	for _, sights := range g.SightsVisited {
		if s, ok := sights[sight]; ok {
			numVisits += s
		}
	}

	if numVisits == len(SightScoring[g.board][sight]) {
		fmt.Println("no more visits")
		return false
	if numVisits == len(SightScoring[g.Board][sight]) {
		// fmt.Println("no more visits")
		return 0, 0, false
	}

	if _, visited := g.sightsVisited[g.round-1][sight]; !visited {
		g.sightsVisited[g.round-1][sight] = 0
	if _, visited := g.SightsVisited[g.Round-1][sight]; !visited {
		g.SightsVisited[g.Round-1][sight] = 0
	}
	g.sightsVisited[g.round-1][sight]++
	// fmt.Println(sight, numVisits+1)
	g.SightsVisited[g.Round-1][sight]++

	return numVisits+1 == len(SightScoring[g.board][sight])
	return SightScoring[g.Board][sight][numVisits], 0, numVisits+1 == len(SightScoring[g.Board][sight])
}

// Score returns the currently accumulated score


@@ 423,13 442,17 @@ func (g *Game) Score() Score {
	var score Score

	// Sights
	score.Sights = make([]int, g.round)
	for i := 0; i < g.round; i++ {
	fmt.Println(g.SightsVisited)
	sightsVisits := make(map[Sight]int)
	score.Sights = make([]int, g.Round)
	for i := 0; i < g.Round; i++ {
		var roundSights int
		for sight, visited := range g.sightsVisited[i] {
		for sight, visited := range g.SightsVisited[i] {
			for j := 0; j < visited; j++ {
				roundSights += SightScoring[g.board][sight][j]
				fmt.Printf("Scoring %s (%d) on round %d\n", sight, SightScoring[g.Board][sight][sightsVisits[sight]+j], i)
				roundSights += SightScoring[g.Board][sight][sightsVisits[sight]+j]
			}
			sightsVisits[sight] += visited
		}
		if i > 0 {
			roundSights += score.Sights[i-1]


@@ 439,13 462,13 @@ func (g *Game) Score() Score {
	}

	// Villages
	for v := range g.villagesConnected {
		score.Villages += VillagesScoring[g.board][v]
	for v := range g.VillagesConnected {
		score.Villages += VillagesScoring[g.Board][v]
	}
	score.Total += score.Villages

	// Bonuses
	for _, b := range g.bonuses {
	for _, b := range g.BonusesEarned {
		score.Bonuses += b
	}
	score.Total += score.Bonuses


@@ 456,7 479,7 @@ func (g *Game) Score() Score {
func (g *Game) String() string {
	var buf bytes.Buffer

	switch g.board {
	switch g.Board {
	case IslaPetit:
		buf.WriteString("Isla Petit")
	case IslaGrande:


@@ 464,9 487,9 @@ func (g *Game) String() string {
	}

	buf.WriteString(" with ")
	buf.WriteString(fmt.Sprintf("%d", g.numPlayers))
	buf.WriteString(fmt.Sprintf("%d", g.NumPlayers))
	buf.WriteString(" player")
	if g.numPlayers > 1 {
	if g.NumPlayers > 1 {
		buf.WriteString("s")
	}
	buf.WriteString("\n")


@@ 479,41 502,41 @@ func (g *Game) String() string {
// New creates a new game state
func New(board Board, numPlayers int) (*Game, error) {
	g := new(Game)
	g.round = 1
	g.board = board
	g.numPlayers = numPlayers
	g.Round = 1
	g.Board = board
	g.NumPlayers = numPlayers

	// Get an initial setup card
	rand.Seed(time.Now().UnixNano())
	g.InitiaSetup = InitialSetupDeck[rand.Intn(len(InitialSetupDeck)-1)]

	// Clone the terrain deck and shuffle it
	g.terrain = TerrainCards.Shuffle()
	g.Terrain = TerrainCards.Shuffle()

	g.villageBonuses = make(map[VillageConnection][]int)
	g.VillageBonuses = make(map[VillageConnection][]int)
	for k, v := range BonusVillagesCards {
		var bonuses []int

		// #1 bonuses only excluded in solo mode
		if g.numPlayers != 1 {
		if g.NumPlayers != 1 {
			bonuses = append(bonuses, v[0])
		}

		// Solo mode only uses #2 bonuses
		// 5-8 players use #2 bonuses along with #1
		if g.numPlayers == 1 || g.numPlayers > 4 {
		if g.NumPlayers == 1 || g.NumPlayers > 4 {
			bonuses = append(bonuses, v[1])
		}

		g.villageBonuses[k] = bonuses
		g.VillageBonuses[k] = bonuses
	}

	g.sightsVisited[0] = make(map[Sight]int)
	g.sightsVisited[1] = make(map[Sight]int)
	g.SightsVisited[0] = make(map[Sight]int)
	g.SightsVisited[1] = make(map[Sight]int)
	// TODO: if g.board == IslaGrande {
	g.sightsVisited[2] = make(map[Sight]int)
	g.SightsVisited[2] = make(map[Sight]int)
	// }
	g.villagesConnected = make(map[VillageConnection]bool)
	g.VillagesConnected = make(map[VillageConnection]bool)

	return g, nil
}