forked from asweigart/the-big-book-of-small-python-projects
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathroyalgameofur.py
256 lines (217 loc) · 8.72 KB
/
royalgameofur.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
"""The Royal Game of Ur, by Al Sweigart [email protected]
A 5,000 year old board game from Mesopotamia. Two players knock each
other back as they race for the goal.
More info https://en.wikipedia.org/wiki/Royal_Game_of_Ur
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, board game, game, two-player
"""
import random, sys
X_PLAYER = 'X'
O_PLAYER = 'O'
EMPTY = ' '
# Set up constants for the space labels:
X_HOME = 'x_home'
O_HOME = 'o_home'
X_GOAL = 'x_goal'
O_GOAL = 'o_goal'
# The spaces in left to right, top to bottom order:
ALL_SPACES = 'hgfetsijklmnopdcbarq'
X_TRACK = 'HefghijklmnopstG' # (H stands for Home, G stands for Goal.)
O_TRACK = 'HabcdijklmnopqrG'
FLOWER_SPACES = ('h', 't', 'l', 'd', 'r')
BOARD_TEMPLATE = """
{} {}
Home Goal
v ^
+-----+-----+-----+--v--+ +--^--+-----+
|*****| | | | |*****| |
|* {} *< {} < {} < {} | |* {} *< {} |
|****h| g| f| e| |****t| s|
+--v--+-----+-----+-----+-----+-----+-----+--^--+
| | | |*****| | | | |
| {} > {} > {} >* {} *> {} > {} > {} > {} |
| i| j| k|****l| m| n| o| p|
+--^--+-----+-----+-----+-----+-----+-----+--v--+
|*****| | | | |*****| |
|* {} *< {} < {} < {} | |* {} *< {} |
|****d| c| b| a| |****r| q|
+-----+-----+-----+--^--+ +--v--+-----+
^ v
Home Goal
{} {}
"""
def main():
print('''The Royal Game of Ur, by Al Sweigart
This is a 5,000 year old game. Two players must move their tokens
from their home to their goal. On your turn you flip four coins and can
move one token a number of spaces equal to the heads you got.
Ur is a racing game; the first player to move all seven of their tokens
to their goal wins. To do this, tokens must travel from their home to
their goal:
X Home X Goal
v ^
+---+---+---+-v-+ +-^-+---+
|v<<<<<<<<<<<<< | | ^<|<< |
|v | | | | | | ^ |
+v--+---+---+---+---+---+---+-^-+
|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>^ |
|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>v |
+^--+---+---+---+---+---+---+-v-+
|^ | | | | | | v |
|^<<<<<<<<<<<<< | | v<<<< |
+---+---+---+-^-+ +-v-+---+
^ v
O Home O Goal
If you land on an opponent's token in the middle track, it gets sent
back home. The **flower** spaces let you take another turn. Tokens in
the middle flower space are safe and cannot be landed on.''')
input('Press Enter to begin...')
gameBoard = getNewBoard()
turn = O_PLAYER
while True: # Main game loop.
# Set up some variables for this turn:
if turn == X_PLAYER:
opponent = O_PLAYER
home = X_HOME
track = X_TRACK
goal = X_GOAL
opponentHome = O_HOME
elif turn == O_PLAYER:
opponent = X_PLAYER
home = O_HOME
track = O_TRACK
goal = O_GOAL
opponentHome = X_HOME
displayBoard(gameBoard)
input('It is ' + turn + '\'s turn. Press Enter to flip...')
flipTally = 0
print('Flips: ', end='')
for i in range(4): # Flip 4 coins.
result = random.randint(0, 1)
if result == 0:
print('T', end='') # Tails.
else:
print('H', end='') # Heads.
if i != 3:
print('-', end='') # Print separator.
flipTally += result
print(' ', end='')
if flipTally == 0:
input('You lose a turn. Press Enter to continue...')
turn = opponent # Swap turns to the other player.
continue
# Ask the player for their move:
validMoves = getValidMoves(gameBoard, turn, flipTally)
if validMoves == []:
print('There are no possible moves, so you lose a turn.')
input('Press Enter to continue...')
turn = opponent # Swap turns to the other player.
continue
while True:
print('Select move', flipTally, 'spaces: ', end='')
print(' '.join(validMoves) + ' quit')
move = input('> ').lower()
if move == 'quit':
print('Thanks for playing!')
sys.exit()
if move in validMoves:
break # Exit the loop when a valid move is selected.
print('That is not a valid move.')
# Perform the selected move on the board:
if move == 'home':
# Subtract tokens at home if moving from home:
gameBoard[home] -= 1
nextTrackSpaceIndex = flipTally
else:
gameBoard[move] = EMPTY # Set the "from" space to empty.
nextTrackSpaceIndex = track.index(move) + flipTally
movingOntoGoal = nextTrackSpaceIndex == len(track) - 1
if movingOntoGoal:
gameBoard[goal] += 1
# Check if the player has won:
if gameBoard[goal] == 7:
displayBoard(gameBoard)
print(turn, 'has won the game!')
print('Thanks for playing!')
sys.exit()
else:
nextBoardSpace = track[nextTrackSpaceIndex]
# Check if the opponent has a tile there:
if gameBoard[nextBoardSpace] == opponent:
gameBoard[opponentHome] += 1
# Set the "to" space to the player's token:
gameBoard[nextBoardSpace] = turn
# Check if the player landed on a flower space and can go again:
if nextBoardSpace in FLOWER_SPACES:
print(turn, 'landed on a flower space and goes again.')
input('Press Enter to continue...')
else:
turn = opponent # Swap turns to the other player.
def getNewBoard():
"""
Returns a dictionary that represents the state of the board. The
keys are strings of the space labels, the values are X_PLAYER,
O_PLAYER, or EMPTY. There are also counters for how many tokens are
at the home and goal of both players.
"""
board = {X_HOME: 7, X_GOAL: 0, O_HOME: 7, O_GOAL: 0}
# Set each space as empty to start:
for spaceLabel in ALL_SPACES:
board[spaceLabel] = EMPTY
return board
def displayBoard(board):
"""Display the board on the screen."""
# "Clear" the screen by printing many newlines, so the old
# board isn't visible anymore.
print('\n' * 60)
xHomeTokens = ('X' * board[X_HOME]).ljust(7, '.')
xGoalTokens = ('X' * board[X_GOAL]).ljust(7, '.')
oHomeTokens = ('O' * board[O_HOME]).ljust(7, '.')
oGoalTokens = ('O' * board[O_GOAL]).ljust(7, '.')
# Add the strings that should populate BOARD_TEMPLATE in order,
# going from left to right, top to bottom.
spaces = []
spaces.append(xHomeTokens)
spaces.append(xGoalTokens)
for spaceLabel in ALL_SPACES:
spaces.append(board[spaceLabel])
spaces.append(oHomeTokens)
spaces.append(oGoalTokens)
print(BOARD_TEMPLATE.format(*spaces))
def getValidMoves(board, player, flipTally):
validMoves = [] # Contains the spaces with tokens that can move.
if player == X_PLAYER:
opponent = O_PLAYER
track = X_TRACK
home = X_HOME
elif player == O_PLAYER:
opponent = X_PLAYER
track = O_TRACK
home = O_HOME
# Check if the player can move a token from home:
if board[home] > 0 and board[track[flipTally]] == EMPTY:
validMoves.append('home')
# Check which spaces have a token the player can move:
for trackSpaceIndex, space in enumerate(track):
if space == 'H' or space == 'G' or board[space] != player:
continue
nextTrackSpaceIndex = trackSpaceIndex + flipTally
if nextTrackSpaceIndex >= len(track):
# You must flip an exact number of moves onto the goal,
# otherwise you can't move on the goal.
continue
else:
nextBoardSpaceKey = track[nextTrackSpaceIndex]
if nextBoardSpaceKey == 'G':
# This token can move off the board:
validMoves.append(space)
continue
if board[nextBoardSpaceKey] in (EMPTY, opponent):
# If the next space is the protected middle space, you
# can only move there if it is empty:
if nextBoardSpaceKey == 'l' and board['l'] == opponent:
continue # Skip this move, the space is protected.
validMoves.append(space)
return validMoves
if __name__ == '__main__':
main()