1
|
|
|
# TODO: Дописать правила игры в README |
2
|
|
|
# TODO: Написать аукцион (вызывается, если игрок не хочет или не может выкупить Собственность) |
3
|
|
|
# TODO: Написать управление собственностью (аукцион; строительство; залог) |
4
|
|
|
|
5
|
|
|
import random |
6
|
|
|
|
7
|
|
|
from termcolor import colored |
8
|
|
|
|
9
|
|
|
import yaml |
10
|
|
|
|
11
|
|
|
|
12
|
|
|
# Базовый класс, описывающий игровую сессию |
13
|
|
|
class Base: |
14
|
|
|
def __init__(self, field): |
15
|
|
|
self.field = field # Берет разметку игрового поля из файла data.py |
16
|
|
|
self.players = [] # Список с объектами игроков |
17
|
|
|
self.current_player = 0 # Номер текущего игрока |
18
|
|
|
self.cells = 36 # Количество ячеек на поле |
19
|
|
|
self.number_of_players = 0 # Количество игроков |
20
|
|
|
self.currency = '₩' # Символ валюты |
21
|
|
|
|
22
|
|
|
self.cp = None |
23
|
|
|
|
24
|
|
|
|
25
|
|
|
# Класс, описывающий игрока, его позицию на поле, баланс, нахождение в тюрьме |
26
|
|
|
class Player: |
27
|
|
|
def __init__(self, name: str): |
28
|
|
|
self.base_coord = 0 # Начальная координата |
29
|
|
|
self.last_coord = self.base_coord # Предыдущая координата |
30
|
|
|
self.cur_coord = self.base_coord # Текущая координата |
31
|
|
|
self.base_balance = 1500 # Начальный баланс |
32
|
|
|
self.cur_balance = self.base_balance # Текущий баланс |
33
|
|
|
|
34
|
|
|
self.property = {} |
35
|
|
|
|
36
|
|
|
self.name = name # Отображаемое имя |
37
|
|
|
|
38
|
|
|
self.start_score = sum(throw_a_die()) # Начальный бросок кубика |
39
|
|
|
self.takes = 0 # Дублей выпало |
40
|
|
|
|
41
|
|
|
self.in_jail = False # Игрок в тюрьме? |
42
|
|
|
self.left_in_jail = 0 # Ходов осталось провести в тюрьме |
43
|
|
|
self.escape_card = 0 # Карточки освобождения из тюрьмы ("Казна" или "Шанс") |
44
|
|
|
|
45
|
|
|
|
46
|
|
|
def start_new_game(): |
47
|
|
|
# Начало новой игры |
48
|
|
|
print('*********\nMONOPOLY\n*********\n') |
49
|
|
|
try: |
50
|
|
|
game.number_of_players = int(input('Введите количество игроков:\n> ')) # Спрашиваем количество игроков (ожидая |
51
|
|
|
# при этом целое число) |
52
|
|
|
except ValueError: |
53
|
|
|
print('Ожидается целое число') |
54
|
|
|
start_new_game() |
55
|
|
|
else: |
56
|
|
|
for i in range(game.number_of_players): # Создание профилей игроков в указанном количестве |
57
|
|
|
game.players.append(Player(name=input(f'Введите имя игрока {i + 1}: '))) |
58
|
|
|
# Сортировка игроков по убыванию стартовых очков (определение порядка хода, как в классической Монополии) |
59
|
|
|
for i in range(len(game.players)): |
60
|
|
|
# Исходно считаем наибольшим первый элемент |
61
|
|
|
index = i |
62
|
|
|
# Этот цикл перебирает несортированные элементы |
63
|
|
|
for j in range(i + 1, len(game.players)): |
64
|
|
|
if game.players[j].start_score > game.players[index].start_score: |
65
|
|
|
index = j |
66
|
|
|
# Самый большой элемент меняем с первым в списке |
67
|
|
|
game.players[i], game.players[index] = game.players[index], game.players[i] |
68
|
|
|
# Вывод порядка хода |
69
|
|
|
print('Порядок хода игроков: ', end='') |
70
|
|
|
for i in range(len(game.players)): |
71
|
|
|
if i != len(game.players) - 1: # Разделитель между игроками |
72
|
|
|
end = ', ' # Если сейчас выводится не последний игрок, тогда выведи после него запятую |
73
|
|
|
else: |
74
|
|
|
end = '\n' # Иначе – новую строку |
75
|
|
|
print(f'{game.players[i].name}', end=end) |
76
|
|
|
# Создание алиаса для текущего игрока (зд.: первого, с индексом 0) |
77
|
|
|
game.cp = game.players[game.current_player] |
78
|
|
|
|
79
|
|
|
|
80
|
|
|
def player_info(): |
81
|
|
|
if game.cp.in_jail and game.cp.cur_coord == 10: |
82
|
|
|
state = ' (как заключенный)' |
83
|
|
|
elif not game.cp.in_jail and game.cp.cur_coord == 10: |
84
|
|
|
state = ' (как посетитель)' |
85
|
|
|
else: |
86
|
|
|
state = '' |
87
|
|
|
print('====') |
88
|
|
|
print(f'{colored("Ход игрока", attrs=["bold"])} {colored(game.cp.name, "magenta")}.') |
89
|
|
|
print(f'{colored("Состояние счёта:", attrs=["bold"])} {colored(game.cp.cur_balance, "magenta")}' |
90
|
|
|
f'{colored(game.currency, "magenta")}.') |
91
|
|
|
print(f'{colored("Позиция:", attrs=["bold"])} {colored(game.field[game.cp.cur_coord]["name"], "magenta")}' |
92
|
|
|
f'{colored(state, "magenta")}.') |
93
|
|
|
print(f'====') |
94
|
|
|
|
95
|
|
|
|
96
|
|
|
def start_move(): |
97
|
|
|
bankrupt = False |
98
|
|
|
if game.cp.cur_balance <= 0: # Если баланс игрока меньше или равен нулю |
99
|
|
|
bankrupt = True # Тогда признаем его банкротом |
100
|
|
|
print(colored('Вы банкрот!', 'red', attrs=['bold', 'blink'])) |
101
|
|
|
if game.cp.in_jail: |
102
|
|
|
print('Вы находитесь в тюрьме и не можете ходиться.') |
103
|
|
|
if game.cp.escape_card: # Если карточек нет (то есть это свойство равно 0), то условие не сработает |
104
|
|
|
print('Вы можете использовать карточки побега.') # Можно получить среди карточек "Казна" или "Шанс" |
105
|
|
|
if prompt('Использовать?'): |
106
|
|
|
game.cp.escape_card -= 1 |
107
|
|
|
game.cp.in_jail = False |
108
|
|
|
game.cp.left_in_jail = 0 |
109
|
|
|
start_move() |
110
|
|
|
else: |
111
|
|
|
game.cp.left_in_jail -= 1 # Вычитаем один ход из его заключения |
112
|
|
|
if game.cp.left_in_jail != 0: # Если заключение еще не прошло |
113
|
|
|
print(f'Вам осталось провести в тюрьме {game.cp.left_in_jail} хода/ов.') |
114
|
|
|
complete_move() |
115
|
|
|
else: # Если время заключения прошло |
116
|
|
|
print('Вы вышли из тюрьмы.') |
117
|
|
|
game.cp.in_jail = False # Освобождаем игрока |
118
|
|
|
game.cp.left_in_jail = 0 |
119
|
|
|
start_move() # И даем возможность сходиться |
120
|
|
|
else: |
121
|
|
|
print('Выберите действие:') |
122
|
|
|
if not bankrupt: # И ограничиваем действия |
123
|
|
|
print('1 – Бросить кубик') |
124
|
|
|
print('2 – Управление недвижимостью') |
125
|
|
|
print('3 – Признать банкротство') |
126
|
|
|
try: |
127
|
|
|
move = int(input('> ')) |
128
|
|
|
except ValueError: |
129
|
|
|
print('Ожидается ввод числа') |
130
|
|
|
start_move() |
131
|
|
|
else: |
132
|
|
|
if not bankrupt: |
133
|
|
|
if move in [1, 2, 3]: |
134
|
|
|
if move == 1: # Бросить кубики и завершить ход |
135
|
|
|
make_move() |
136
|
|
|
complete_move() |
137
|
|
|
elif move == 2: # Начать управление недвижимостью |
138
|
|
|
manage_property() |
139
|
|
|
start_move() |
140
|
|
|
elif move == 3: # Сдаться |
141
|
|
|
# Сдаёмся и удаляем профайл игрока |
142
|
|
|
recognize_bankruptcy() |
143
|
|
|
else: # Неизвестная команда |
144
|
|
|
print('Введите число от 1 до 3!') |
145
|
|
|
start_move() |
146
|
|
|
else: |
147
|
|
|
if move in [2, 3]: |
148
|
|
|
if move == 2: # Начать управление недвижимостью |
149
|
|
|
manage_property() |
150
|
|
|
start_move() |
151
|
|
|
elif move == 3: # Сдаться |
152
|
|
|
pass |
153
|
|
|
else: # Неизвестная команда |
154
|
|
|
print('Введите число от 2 до 3!') |
155
|
|
|
start_move() |
156
|
|
|
|
157
|
|
|
|
158
|
|
|
def move_actions(): |
159
|
|
|
print(f'Вы находитесь на ячейке {game.field[game.cp.cur_coord]["name"]} (' |
160
|
|
|
f'{game.field[game.cp.cur_coord]["category"]})') |
161
|
|
|
if game.field[game.cp.cur_coord]["owned_by"] is None and game.cp.cur_coord not in [0, 2, 4, 7, 10, 17, 20, 29, 34, |
|
|
|
|
162
|
|
|
36]: |
163
|
|
|
# Если ячейка никому не принадлежит и если ячейка не служебная (т. е. её можно купить) |
164
|
|
|
if prompt(f'Предприятие никому не принадлежит. Хотите купить?\n' |
165
|
|
|
f'Её стоимость: {game.field[game.cp.cur_coord]["price"]}{game.currency}\n' |
166
|
|
|
f'У вас есть: {game.cp.cur_balance}{game.currency}'): # Предлагаем игроку купить эту карточку |
167
|
|
|
if game.field[game.cp.cur_coord]["price"] < game.cp.cur_balance: # Если у игрока достаточно денег |
168
|
|
|
game.field[game.cp.cur_coord]["owned_by"] = game.current_player # Покупаем |
169
|
|
|
game.cp.cur_balance -= game.field[game.players[ |
170
|
|
|
game.current_player].cur_coord]["price"] |
171
|
|
|
game.cp.property.setdefault(game.field[game.cp.cur_coord]['category']) |
172
|
|
|
try: |
173
|
|
|
_f = bool(len(game.cp.property.setdefault(game.field[game.cp.cur_coord]['category']))) |
174
|
|
|
except TypeError: |
175
|
|
|
game.cp.property[game.field[game.cp.cur_coord]['category']] = list() |
176
|
|
|
finally: |
177
|
|
|
game.cp.property[game.field[game.cp.cur_coord]['category']].append(game.field[game.cp.cur_coord]) |
178
|
|
|
print(f'Успех! Предприятие {game.field[game.cp.cur_coord]["name"]} теперь ваше!') |
179
|
|
|
else: |
180
|
|
|
print('У вас недостаточно средств для покупки этой карточки.') |
181
|
|
|
auction() |
182
|
|
|
else: |
183
|
|
|
auction() |
184
|
|
|
else: |
185
|
|
|
if game.field[game.cp.cur_coord]["owned_by"] is not None and game.field[game.cp.cur_coord]["owned_by"] != \ |
186
|
|
|
game.current_player: # Если ячейка принадлежит какому-то игроку |
187
|
|
|
print(f'Предприятие {game.field[game.cp.cur_coord]["name"]} принадлежит ' |
188
|
|
|
f'игроку ' |
189
|
|
|
f'{game.players[game.field[game.cp.cur_coord]["owned_by"]].name}') |
190
|
|
|
# Вычисляем ренту |
191
|
|
|
if game.field[game.cp.cur_coord]["houses"] is None and game.field[game.cp.cur_coord]["hotel"] is None: |
192
|
|
|
value = game.field[game.cp.cur_coord]["renta"] |
193
|
|
|
elif game.field[game.cp.cur_coord]["houses"] == 1 and game.field[game.cp.cur_coord]["hotel"] is None: |
194
|
|
|
value = game.field[game.cp.cur_coord]["one_house_renta"] |
195
|
|
|
elif game.field[game.cp.cur_coord]["houses"] == 2 and game.field[game.cp.cur_coord]["hotel"] is None: |
196
|
|
|
value = game.field[game.cp.cur_coord]["two_house_renta"] |
197
|
|
|
elif game.field[game.cp.cur_coord]["houses"] == 3 and game.field[game.cp.cur_coord]["hotel"] is None: |
198
|
|
|
value = game.field[game.cp.cur_coord]["three_house_renta"] |
199
|
|
|
elif game.field[game.cp.cur_coord]["houses"] == 4 and game.field[game.cp.cur_coord]["hotel"] is None: |
200
|
|
|
value = game.field[game.cp.cur_coord]["four_house_renta"] |
201
|
|
|
elif game.field[game.cp.cur_coord]["houses"] is None and game.field[game.cp.cur_coord]["hotel"] == 1: |
202
|
|
|
value = game.field[game.cp.cur_coord]["hotel_renta"] |
203
|
|
|
else: |
204
|
|
|
value = 0 |
205
|
|
|
print(f'Вы должны заплатить ему ренту в размере {value}{game.currency}.') # И переводим ее на счет хозяина |
206
|
|
|
game.cp.cur_balance -= value |
207
|
|
|
game.players[game.field[game.cp.cur_coord]["owned_by"]].cur_balance += value |
208
|
|
|
if game.cp.cur_coord in [2, 17]: |
209
|
|
|
print('chest.') |
210
|
|
|
get_event_card('chest') |
211
|
|
|
if game.cp.cur_coord in [7, 34]: |
212
|
|
|
print('chance.') |
213
|
|
|
get_event_card('chance') |
214
|
|
|
if game.cp.cur_coord == 29: |
215
|
|
|
go_to_jail() |
216
|
|
|
return None |
217
|
|
|
pay_taxes() # Заплотить нологе |
218
|
|
|
|
219
|
|
|
|
220
|
|
|
def complete_move(): |
221
|
|
|
print(f'Ход игрока {game.cp.name} завершен.') |
222
|
|
|
game.current_player += 1 |
|
|
|
|
223
|
|
|
if game.current_player == game.number_of_players: |
224
|
|
|
game.current_player = 0 |
225
|
|
|
game.cp = game.players[game.current_player] |
226
|
|
|
|
227
|
|
|
|
228
|
|
|
def make_move(): |
229
|
|
|
# Бросаем кубики |
230
|
|
|
f_die, s_die = throw_a_die() |
231
|
|
|
print(f'На кубиках выпало {f_die} и {s_die}.') |
232
|
|
|
# Смещаем фишку игрока |
233
|
|
|
game.cp.last_coord = game.cp.cur_coord |
|
|
|
|
234
|
|
|
game.cp.cur_coord += f_die + s_die |
235
|
|
|
if game.cp.cur_coord > game.cells: |
236
|
|
|
game.cp.cur_coord -= game.cells |
237
|
|
|
if game.cp.cur_coord == 0: |
238
|
|
|
pay_salary(400) |
239
|
|
|
else: |
240
|
|
|
pay_salary(200) |
241
|
|
|
# Выполняем действия карточки |
242
|
|
|
move_actions() |
243
|
|
|
if f_die == s_die: |
244
|
|
|
# Выпал дубль, делаем еще один ход |
245
|
|
|
print(f'Какая неожиданность! У вас выпал {colored("дубль", attrs=["bold"])}. Дубль дает право на еще один ход.') |
246
|
|
|
game.cp.takes += 1 # Добавляем единицу к счетчику |
247
|
|
|
if game.cp.takes >= 3: # Если количество дублей подряд достигло трех, то |
248
|
|
|
go_to_jail() # Отправляем игрока в тюрьму |
249
|
|
|
return None |
250
|
|
|
else: # Иначе |
251
|
|
|
make_move() # Даем сходится еще раз |
252
|
|
|
else: # Если цепчока дублей нарушилась, тогда обнуляем счетчик дублей |
253
|
|
|
game.cp.takes = 0 |
254
|
|
|
|
255
|
|
|
|
256
|
|
|
def manage_property(): |
257
|
|
|
if not game.cp.property: |
|
|
|
|
258
|
|
|
print('У вас нет предприятий в собственности!') |
259
|
|
|
else: |
260
|
|
|
for k, v in game.cp.property.items(): |
261
|
|
|
for i in range(len(v)): |
262
|
|
|
t = v[i]['name'] |
263
|
|
|
print(f'{k}: {t}') |
264
|
|
|
|
265
|
|
|
|
266
|
|
|
def recognize_bankruptcy(): |
267
|
|
|
# Признать банкротство |
268
|
|
|
if prompt('Вы действительно хотите сдаться?'): # Подтверждение |
269
|
|
|
print('Серьёзно? Ну хорошо, ваше право...') |
270
|
|
|
print(f'Удаление игрока {game.cp.name}.chr') # Отсылочка для знающих :) |
271
|
|
|
game.players.pop(game.current_player) |
|
|
|
|
272
|
|
|
for i in range(len(game.field)): |
273
|
|
|
if game.field[i]['owned_by'] == game.current_player: |
274
|
|
|
game.field[i]['owned_by'] = None |
275
|
|
|
print('Собственность ушедшего игрока теперь принадлежит Банку.') |
276
|
|
|
complete_move() |
277
|
|
|
else: |
278
|
|
|
print('Не пугайте меня так!') |
279
|
|
|
|
280
|
|
|
|
281
|
|
|
def go_to_jail(): |
282
|
|
|
# Отправляем игрока в тюрьму |
283
|
|
|
print('Вы попали в тюрьму и должны пропустить три своих хода.') |
284
|
|
|
game.cp.takes = 0 # Сбрасываем счетчик дублей |
|
|
|
|
285
|
|
|
game.cp.in_jail = True # Логически отправляем игрока в тюрьму |
286
|
|
|
game.cp.left_in_jail = 3 # Начисляем ходы в заключении |
287
|
|
|
game.cp.cur_coord = 10 # Физически отправляем игрока в тюрьму |
288
|
|
|
|
289
|
|
|
|
290
|
|
|
def pay_salary(value: int): |
291
|
|
|
# Зарплата за прохождение круга |
292
|
|
|
if game.cp.last_coord > game.cp.cur_coord: # Если игрок |
|
|
|
|
293
|
|
|
# закончил круг (предыдущая координата больше текущей) |
294
|
|
|
print(f'Вы завершили круг. Получите зарплату в размере {value}{game.currency}') |
295
|
|
|
game.cp.cur_balance += value |
296
|
|
|
|
297
|
|
|
|
298
|
|
|
def pay_taxes(): |
299
|
|
|
if game.cp.cur_coord == 4: # Если игрок попал на ячейку "Подоходный налог" |
|
|
|
|
300
|
|
|
print('Вы попали на клетку с налогами.') |
301
|
|
|
print(f'Подоходный налог. С вашего счета списано 200{game.currency}') |
302
|
|
|
game.cp.cur_balance -= 200 |
303
|
|
|
if game.cp.cur_coord == 38: # Если игрок попал на ячейку "Налог на роскошь" |
304
|
|
|
print('Вы попали на клетку с налогами.') |
305
|
|
|
print(f'Налог на роскошь. С вашего счета списано 100{game.currency}') |
306
|
|
|
game.cp.cur_balance -= 100 |
307
|
|
|
|
308
|
|
|
|
309
|
|
|
def get_event_card(_type: str): |
310
|
|
|
pass |
311
|
|
|
|
312
|
|
|
|
313
|
|
|
def auction(): |
314
|
|
|
print(colored('Аукцион!', 'yellow', attrs=['blink'])) |
315
|
|
|
|
316
|
|
|
|
317
|
|
|
def prompt(msg: str): |
318
|
|
|
resp = input(f'{msg} [Д/Н]:\n> ').lower() |
319
|
|
|
if resp == 'д': |
320
|
|
|
return True |
321
|
|
|
return False |
322
|
|
|
|
323
|
|
|
|
324
|
|
|
def throw_a_die(): |
325
|
|
|
# Бросить кубики |
326
|
|
|
return random.randint(1, 6), random.randint(1, 6) |
327
|
|
|
|
328
|
|
|
|
329
|
|
|
if __name__ == '__main__': |
330
|
|
|
try: # Пробуй |
331
|
|
|
field = yaml.full_load(open("field.yml", "r")) |
332
|
|
|
game = Base(field) # Создать экземпляр игры |
333
|
|
|
start_new_game() # Начать новую игру |
334
|
|
|
while len(game.players) > 1: # Пока количество игроков больше единицы |
335
|
|
|
player_info() # Выведи информацию о текущем игроке |
336
|
|
|
start_move() # Начинай новый ход |
337
|
|
|
except KeyboardInterrupt: # Если игра принудительно завершена на ^C |
338
|
|
|
print('\nИгра завершена без сохранения прогресса.') |
339
|
|
|
else: # Если игра завершилась без ручной остановки |
340
|
|
|
print(f'Игрок {game.players[0].name} одержал победу. Поздравляем!') # Поздравление |
341
|
|
|
|