TTRPlayer.py
import collections
from TTRBoard import Board
class Player(object):
def __init__(self,
startingHand,
startingTickets,
playerBoard,
playerPosition,
numTrains
):
"""orderNumber: int
startingHand: list
startingTickets: list
playerBoard: PlayerBoard object from the TTRBoard module
playerPosition: int
"""
self.name = '' #ask for them to enter it on first turn
self.isComputerPlayer = False
#implimented as a collection to avoid O(n) hand.remove(x)
self.hand = collections.Counter(startingHand)
self.tickets = {x:False for x in startingTickets}
self.numTrains = numTrains
self.points = 0
self.useDijkstra = False
self.playerPosition = playerPosition
self.board = Board()
self.wayAvailable = []
self.results = []
#custom board to represent
self.playerBoard = playerBoard
def removeCardsFromHand(self, color, numColor):
"""removes one ore more cards from hand
assumes all cards are in hand, error if not
cards: list
"""
assert self.hand[color] >= numColor
self.hand[color] -= numColor
#add card to hand
def addCardToHand(self, card):
"""adds a single card to hand
assumes card is a valid choice
card: String
"""
if card != None:
self.hand[card] += 1
#add ticket to hand
def addTicket(self, ticket):
"""adds a single ticket to tickets
ticket: tuple(city1, city2, value)
"""
self.tickets[ticket] = False
def completeTicket(self, ticket):
"""updates the value in the tickets dict to True for key: ticket
ticket: tuple(city1, city2, value)
"""
assert ticket in self.tickets
self.tickets[ticket] = True
def getHand(self):
return self.hand
def addPoints(self, numPoints):
self.points += numPoints
def subtractPoints(self, numPoints):
self.points -= numPoints
def getPoints(self):
return self.points
def getTickets(self):
return self.tickets
def getNumTrains(self):
return self.numTrains
def playNumTrains(self, numTrains):
assert numTrains <= self.numTrains
self.numTrains -= numTrains
def setPlayerName(self, name):
"""sets playerName to name
name: string
"""
self.name = name
def getName(self):
return self.name
def getConnectCityList(self):
cityList = []
for ticket in self.getTickets():
if self.useDijkstra:
shortest_path = self.board.dijkstraShortestPath(ticket[0], ticket[1])
else:
shortest_path = self.board.findAnyPath(ticket[0], ticket[1])
cityList.append(shortest_path)
return cityList
def hasTrainsConnectTicket(self, ways):
for way in ways:
for citys in self.getConnectCityList():
if way[0] in citys and way[1] in citys:
self.wayAvailable = [way[0], way[1]]
return True
return False
def getNeedColors(self):
colors = []
for cities in self.getConnectCityList():
self.results = [[cities[i], cities[i + 1]] for i in range(len(cities) - 1)]
for result in self.results:
colors += self.board.getEdgeColors(result[0], result[1])
return colors
TTRGameSim.py
# this version no longer takes input from user.
# Designed for computer OR human player (mainly comp v comp sim)
import random
# All values and methods associated with the Original Ticket to Ride board game
import TTRBoard
import TTRCards
import TTRPlayer
import collections
import pprint
class Game(object):
def __init__(self, numPlayers):
self.sizeDrawPile = 5
self.numTicketsDealt = 3
self.sizeStartingHand = 4
self.maxWilds = 3
self.endingTrainCount = 3 # ending condition to trigger final round
self.pointsForLongestRoute = 10
self.startingNumOfTrains = 10 # 45
self.deck = TTRCards.Cards(self.sizeDrawPile, self.maxWilds)
self.board = TTRBoard.Board()
self.numPlayers = numPlayers
self.players = []
self.posToMove = 0
# point values for tracks of different lengths
self.routeValues = {1: 1, 2: 2, 3: 4, 4: 7, 5: 10, 6: 15}
for position in range(numPlayers):
startingHand = self.deck.dealCards(self.sizeStartingHand)
startingTickets = [] # self.deck.dealTickets(self.numTicketsDealt)
# this is now done in initialize method below
# occurs before first player's first move
playerBoard = TTRBoard.PlayerBoard()
player = TTRPlayer.Player(startingHand,
startingTickets,
playerBoard,
position,
self.startingNumOfTrains
)
self.players.append(player)
def printSepLine(self, toPrint):
print(toPrint)
def advanceOnePlayer(self):
"""Updates self.posToMove"""
self.posToMove += 1
self.posToMove %= self.numPlayers
def getCurrentPlayer(self):
return self.players[self.posToMove]
def doesPlayerHaveCardsForEdge(self, player, city1, city2):
if player.playerBoard.hasEdge(city1, city2):
return False
routeDist = self.board.getEdgeWeight(city1, city2)
routeColors = self.board.getEdgeColors(city1, city2)
for col in routeColors:
if col == 'grey':
if max([x for x in player.hand.values() if x != 'wild']) \
+ player.hand['wild'] >= routeDist:
return True
else:
routeDist = self.board.getEdgeWeight(city1, city2)
if player.hand[col] + player.hand['wild'] >= routeDist:
return True
return False
def checkEndingCondition(self, player):
return player.getNumTrains() < self.endingTrainCount
def initialize(self):
"""Before game turns starts, enter names and pick destination tickets
"""
for player in self.players:
# pick desination tickets
if len(player.getName()) == 0:
player.setPlayerName(input("Enter your name: "))
if input("Human(0) or Computer(1) ? ") == '1':
player.isComputerPlayer = True
if input("Use Dijkstra(1) or not(0)?") == '1':
player.useDijkstra = True
self.pickTickets(player, 2)
self.advanceOnePlayer()
def scorePlayerTickets(self, player):
"""returns None.
Scores player's destination tickets and
adds/subtracts from player's points
"""
for ticket in player.tickets:
city1 = ticket[0]
city2 = ticket[1]
value = ticket[2]
posNodes = player.playerBoard.getNodes()
if city1 not in posNodes or city2 not in posNodes:
player.subtractPoints(value)
continue
if player.playerBoard.hasPath(city1, city2):
player.addPoints(value)
player.completeTicket(ticket)
def scoreLongestPath(self):
"""determines which player has the longest route and
adjusts their score accordingly
players: list of players
adds self.pointsForLongestRoute to player with longest route
"""
scores = {x: (0, ()) for x in self.players}
longestPath = 0
for player in scores:
for city in player.playerBoard.getCities():
pathInfo = player.playerBoard.longestPath(city)
if pathInfo[0] > scores[player][0]:
scores[player] = pathInfo
if scores[player][0] > longestPath:
longestPath = scores[player][0]
print(scores)
for player in scores:
if scores[player][0] == longestPath:
player.addPoints(self.pointsForLongestRoute)
# does not return anthing
def printAllPlayerData(self):
"""prints out all of the non method attributes values for all players
"""
for player in self.players:
print(player.name)
print("------------------------------")
for x in player.__dict__:
print(x, player.__dict__[x])
print(player.playerBoard.iterEdges())
print("==============================")
def playTurn(self, player):
"""player chooses 'cards', 'trains', 'tickets'
player: player object
"""
choice = ""
if player.isComputerPlayer:
print("Please type: cards, trains or tickets: ")
ways = [x for x in sorted(self.board.iterEdges()) if self.doesPlayerHaveCardsForEdge(player, x[0], x[1])]
if player.hasTrainsConnectTicket(ways):
choice = 'trains'
else:
choice = 'cards'
print(choice)
else:
choice = input("Please type: cards, trains or tickets: ")
count = 0 # a way out of the loop if 5 invalid responses
while choice not in ['cards', 'trains', 'tickets'] and count < 5:
choice = input("Invalid repsonse. Please select either cards, "
+ "trains or tickets: ")
count += 1
displayMap = input("Display map? y/n: ")
if displayMap == 'y':
pauseTime = input("For how many seconds? (between 1 and 30): ")
if int(pauseTime) not in range(1, 31):
pass
else:
self.board.showBoard(self.board.G, int(pauseTime))
#Depricated?
if count >= 5:
return "Move complete"
if choice == 'cards':
self.pickCards(player)
return "Move complete"
elif choice == 'trains':
self.placeTrains(player)
return "Move complete"
else:
self.pickTickets(player)
return "Move complete"
def pickCards(self, player):
count = 0 # a way out of the loop if 5 invalid responses
print("Your hand consists of: ")
self.printSepLine(player.getHand())
print("Draw pile consists of: ")
self.printSepLine(self.deck.getDrawPile())
choice1 = ""
if player.isComputerPlayer:
print("Please type a card from the above list or "
+ "type 'drawPile': ")
if 'wild' in self.deck.getDrawPile():
choice1 = 'wild'
else:
colors = player.getNeedColors()
for card in self.deck.getDrawPile():
if card in colors:
choice1 = card
break
if choice1 == '':
choice1 = 'drawPile'
else:
choice1 = input("Please type a card from the above list or "
+ "type 'drawPile': ")
while choice1 not in self.deck.getDrawPile() + ['drawPile'] \
and count < 5:
choice1 = input("Invalid repsonse. Please type either from "
+ str(self.deck.getDrawPile())
+ " or type 'drawPile' "
)
count += 1
# add card to player's hand
# remove it from drawPile or cards and
# add new card to drawPile
if count >= 5:
pass
elif choice1 == 'drawPile':
chosenCard = self.deck.pickFaceDown()
print("You selected: " + str(chosenCard))
player.addCardToHand(chosenCard)
else:
player.addCardToHand(self.deck.pickFaceUpCard(choice1))
# start second card selection
if choice1 == 'wild':
print("Your hand now consists of: ")
self.printSepLine(player.getHand())
return "Move complete"
count = 0
self.printSepLine(self.deck.getDrawPile())
choice2 = ""
if player.isComputerPlayer:
print("Please type a card from the above list or "
+ "type 'drawPile': ")
colors = player.getNeedColors()
for card in self.deck.getDrawPile():
if card in colors:
choice2 = card
break
if choice2 == '':
choice2 = 'drawPile'
else:
choice2 = input("Please type another card from the above list or "
+ "type 'drawPile': ")
while choice2 == 'wild' \
or (choice2 not in self.deck.getDrawPile() + ['drawPile'] \
and count < 5):
choice2 = input("Invalid repsonse. Please type either from "
+ str(self.deck.getDrawPile())
+ " or type 'drawPile' \
NOTE: second choice cannot be 'wild' "
)
count += 1
# add card to player's hand
# remove it from drawPile or cards and
# add new card to drawPile
if count >= 5:
return "Move complete"
elif choice2 == 'drawPile':
chosenCard = self.deck.pickFaceDown()
print("You selected: " + str(chosenCard))
player.addCardToHand(chosenCard)
else:
player.addCardToHand(self.deck.pickFaceUpCard(choice2))
print("Your hand now consists of: ")
self.printSepLine(player.getHand())
return "Move complete"
def placeTrains(self, player):
count = 0
print("Available cities:")
# only print routes that are legal given the players cards
# sort alphabetically
self.printSepLine([x for x in sorted(self.board.iterEdges())
if self.doesPlayerHaveCardsForEdge(player, x[0], x[1])])
print("Your hand consists of: ")
self.printSepLine(player.getHand())
city1 = ''
city2 = ''
if player.isComputerPlayer:
print("Please type the start city of desired route: ", end='')
city1 = player.wayAvailable[0]
print(city1)
print("Please type the destination city to go to from "
+ str(city1)
+ " : ", end='')
city2 = player.wayAvailable[1]
print(city2)
else:
city1 = input("Please type the start city of desired route: ")
while city1 not in self.board.getCities() and count < 5:
city1 = input("Invalid response. "
+ "Please select from the above city list: "
)
count += 1
if count >= 5:
return "Move complete"
if len([x for x in self.board.G.neighbors(city1)
if self.board.hasEdge(city1, x)]) == 0:
print("You have a selected a city with no legal destination")
return "Move complete"
# start city2
count = 0
print("Available destination cities: " \
+ str([x for x in self.board.G.neighbors(city1)
if self.doesPlayerHaveCardsForEdge(player, city1, x)]))
city2 = input("Please type the destination city to go to from "
+ str(city1)
+ " : "
)
while not self.board.hasEdge(city1, city2) and count < 5:
city2 = input("Invalid response. "
+ "Please type one of the following cities "
+ "(without quotes): \n"
+ str([x for x in self.board.G.neighbors(city1)
if self.board.hasEdge(city1, x)])
+ " : "
)
count += 1
if count >= 5:
return "Move complete"
# start exchange cards and place trains
routeDist = self.board.getEdgeWeight(city1, city2)
spanColors = self.board.getEdgeColors(city1, city2)
if len(spanColors) == 0:
# a little harsh but will updated later to players start over
print("You have a selected two cities with no legal route")
return "Move complete"
print("\n This route is of length: ")
self.printSepLine(routeDist)
print("Your hand consists of: ")
self.printSepLine(player.getHand())
color = ""
if len(spanColors) == 1:
color = spanColors[0] # use first element, getEdgeColors returns list
print("This route is: " + str(color))
else:
color = ""
if player.isComputerPlayer:
num = 0
for i in spanColors:
if player.getHand()[i] > num:
num = player.getHand()[i]
color = i
if color == 'wild':
color = random.choice(spanColors)
else:
color = input("which color track would you like to claim? ("
+ str(spanColors)
+ " available): "
)
if color not in spanColors:
print("Invalid Color")
return "Move complete"
# check to see if player has appropriate cards to play route
# (edge weight, color/wild)
if not self.doesPlayerHaveCardsForEdge(player, city1, city2):
print("You do not have sufficient cards to play this route")
return "Move complete"
if color == 'grey':
availColor = max(x for x in player.hand.values())
else:
availColor = player.hand[color]
availWild = player.hand['wild']
if color == 'grey':
if player.isComputerPlayer:
print(("Which color would you like to play "
+ "on this grey route? "
+ "(pick a color, not 'wild'): "
), end='')
num = 0
for i in player.getHand().keys():
if player.getHand()[i] > num and i != 'wild':
num = player.getHand()[i]
color = i
print(color)
else:
color = input("Which color would you like to play "
+ "on this grey route? "
+ "(pick a color, not 'wild'): "
)
if color not in self.deck.possibleColors:
print("Invalid Color")
return "Move complete"
availColor = player.hand[color]
if player.isComputerPlayer:
if availColor > routeDist:
numColor = routeDist
else:
numColor = availColor
else:
numColor = input("How many " + str(color)
+ " cards would you like to play? ("
+ str(availColor)
+ " available): "
)
else:
if player.isComputerPlayer:
if availColor > routeDist:
numColor = routeDist
else:
numColor = availColor
else:
numColor = input("How many "
+ str(color)
+ " cards would you like to play? ("
+ str(availColor)
+ " available) "
)
if str(numColor) not in [str(x) for x in range(routeDist + 1)]:
print("Invalid Entry")
return "Move complete"
numColor = int(numColor) # change raw string to int
if numColor not in range(0, availColor + 1):
print("You do not have that many")
return "Move complete"
if numColor < routeDist: # span distance
if player.isComputerPlayer:
numWild = routeDist - numColor
else:
numWild = input("How many wild cards would you like to play? ("
+ str(availWild)
+ " available) "
)
numWild = int(numWild)
if numWild not in range(0, availWild + 1):
print("You do not have that many")
return "Move complete"
else:
numWild = 0
# verify that this is a legal move
if numWild + numColor != routeDist:
print("Selected cards do not properly span that route")
return "Move complete"
# claim route for player (see dedicated method within Game class)
player.playerBoard.addEdge(city1, city2, routeDist, color)
# remove route from main board
self.board.removeEdge(city1, city2, color)
# calculate points
player.addPoints(self.routeValues[routeDist])
# remove cards from player's hand
player.removeCardsFromHand(color, numColor)
player.removeCardsFromHand('wild', numWild)
# add cards to discard pile
self.deck.addToDiscard([color for x in range(numColor)]
+ ['wild' for x in range(numWild)]
)
# remove trains from players numTrains
player.playNumTrains(routeDist)
print("Number of trains left to play: ")
self.printSepLine(player.getNumTrains())
return "Move complete"
def pickTickets(self, player, minNumToSelect=1):
count = 0
tickets = self.deck.dealTickets(self.numTicketsDealt)
# assign a number to each ticket to make it easier to choose
tickets = {x[0]: x[1] for x in zip(range(len(tickets)), tickets)}
print("Please select at least " + str(minNumToSelect) + ": ")
self.printSepLine(tickets)
choices = set()
if player.isComputerPlayer:
choices.add(tickets[0])
choices.add(tickets[1])
choices.add(tickets[2])
else:
choice = input("Select a number corresponding to the above tickets,"
+ " type done when finished: ")
while (choice != 'done' and count < 7) \
or len(choices) < minNumToSelect:
try:
choices.add(tickets[int(choice)])
choice = input("Select the number corresponding to the "
+ "above tickets, type 'done' when finished: "
)
except:
choice = input("Invalid Choice: Select the number "
+ "corresponding to the above tickets, "
+ "type 'done' when finished: "
+ " (must select at least "
+ str(minNumToSelect)
+ ") "
)
count += 1
print("You selected: ")
for ticket in choices:
player.addTicket(ticket)
print(ticket)
# add tickets that weren't chosen to the ticketDiscardPile
notChosen = set(range(len(tickets))).difference(choices)
for i in notChosen:
self.deck.addToTicketDiscard(tickets[i])
print("All of your tickets: ")
self.printSepLine(player.getTickets())
return "Move complete"
def playTTR():
# before first turn, select 1, 2 or 3 destination tickets
print("\n Welcome to Ticket to Ride! \n")
numPlayers = input("How many players will be playing today? "
+ "1,2,3,4,5 or 6? ")
count = 0
while int(numPlayers) not in range(1, 7) and count < 5:
if numPlayers == 'exit': return "Thanks for playing!"
numPlayers = input("Please enter either 1,2,3,4,5 or 6: ")
count += 1
if count >= 5:
print("Default player count has been set to 2")
numPlayers = 2
game = Game(int(numPlayers))
game.initialize()
player = game.players[game.posToMove]
# main game loop
while True:
print("\n_________________NEW PLAYER'S TURN_________________ \n")
print("It's your turn " + str(player.getName()) + "! ")
game.playTurn(player)
# condition to break out of loop
if game.checkEndingCondition(player):
game.advanceOnePlayer()
player = game.getCurrentPlayer()
break
game.advanceOnePlayer()
player = game.getCurrentPlayer()
print("\n This is the last round! Everyone has one more turn! \n")
for i in range(len(game.players)):
print("\n_________________NEW PLAYER'S TURN_________________ \n")
print("This is your LAST TURN " + str(player.getName()) + "! ")
game.playTurn(player)
game.advanceOnePlayer()
player = game.getCurrentPlayer()
for player in game.players:
game.scorePlayerTickets(player)
game.scoreLongestPath()
scores = []
for player in game.players:
print(str(player.getName())
+ " had "
+ str(player.getPoints())
+ " points!"
)
score = player.getPoints()
scores.append(score)
winners = [x.getName() for x in game.players
if x.getPoints() == max(scores)]
if len(winners) == 1:
print("The winner is " + str(winners[0]))
else:
print("The winners are " + ' and '.join(winners))
print("\n =========== Data =========== \n")
game.printAllPlayerData()
print("\n =========== fin =========== \n")
if __name__ == "__main__":
playTTR()