-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogic.py
377 lines (284 loc) · 11.3 KB
/
logic.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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
##########
### Game logic for actually running a game
##########
from uuid import uuid4
import random
from copy import copy
import communication
import config
from bottle import abort
import thread
from threading import Timer
import pusher
pusher.app_id = config.PUSHER_APP_ID
pusher.key = config.PUSHER_KEY
pusher.secret = config.PUSHER_SECRET
def push(game, subject, content):
game['pushes'].append([subject, content])
p = pusher.Pusher()
p['game-' + game['id']].trigger(subject, content)
def log_action(game, player, action, data={}):
data['action'] = action
for p in game['players'].values():
if not p['id'] == player['id']:
# Log it for this user
if not player['id'] in p['actions']:
p['actions'][player['id']] = []
p['actions'][player['id']].append(data)
def setup_player(player):
in_game_player = {
"id": uuid4().hex,
"secret": uuid4().hex,
"player": player['id'],
"name": player['id'],
"endpoint": player['endpoint'],
"generators": copy(config.DEFAULT_GENERATORS),
"improved_generators": copy(config.DEFAULT_GENERATORS),
"resources": copy(config.DEFAULT_RESOURCES),
"pr": 0,
"customers": 2,
"actions": {}
}
return in_game_player
def run_generators(players):
awarded = {}
def award(player, generator, amount=1):
generated = config.GENERATORS[generator]
player['resources'][generated] += 1
if not player['id'] in awarded:
awarded[player['id']] = {"name": player['player'], "resources": {}}
if not generated in awarded[player['id']]["resources"]:
awarded[player['id']]["resources"][generated] = 0
awarded[player['id']]["resources"][generated] += amount
for player in players.values():
for generator in player['generators']:
for i in range(player['generators'][generator]):
if random.randint(1, 10) == 1:
award(player, generator)
for generator in player['improved_generators']:
for i in range(player['improved_generators'][generator]):
if random.randint(1, 10) == 1:
award(player, generator, 2)
return awarded
def start_game(db, players):
print "Starting game"
game_id = uuid4().hex
game = {
"id": game_id,
"players": {},
"player_order": [],
"turn": len(players),
"turn_id": None,
"round": 0,
"pushes": []
}
used_names =[]
for player in players:
p = setup_player(player)
i = 0
while p['name'] in used_names:
i += 1
p['name'] = p['player'] + ' %i' % i
used_names.append(p['name'])
game['players'][p['id']] = p
game['player_order'].append(p['id'])
generators_to_use = copy(config.GENERATORS.keys())
random.shuffle(generators_to_use)
p = 0
max_generators = 0
# First pass out all generators as evenly as possible
while (len(generators_to_use) > 0):
game['players'][game['player_order'][p]]['generators'][generators_to_use.pop()] += 1
total_generators = sum(game['players'][game['player_order'][p]]['generators'].values())
if total_generators > max_generators:
max_generators = total_generators
p += 1
if p == len(game['player_order']):
p = 0
# Now ensure everyone has an equal amount
generators_to_use = copy(config.GENERATORS.keys())
random.shuffle(generators_to_use)
for p in game['players']:
while sum(game['players'][p]['generators'].values()) < max_generators:
game['players'][p]['generators'][generators_to_use.pop()] += 1
started_players = []
for player in game['players'].values():
response, data = communication.request(player, "game/%s" % (player['id']), {"player": player, "endpoint": "http://localhost:8080/game/%s" % game_id}, 'PUT')
if response.status != 200:
for p in started_players:
communication.request(player, "game/%s/cancel" % (player['id']), {"player": player})
return False
started_players.append(player)
db.save(game)
next_turn(db, game)
return game_id
def game_is_over(game):
if game['round'] >= config.MAX_ROUNDS:
return True
for player in game['players'].values():
if player['customers'] >= config.MAX_POINTS:
return True
return False
def next_turn(db, game):
turn_taken = False
while not turn_taken: # Find the next player ready to make a move
game['turn'] = game['turn'] + 1
if game['turn'] >= len(game['player_order']):
# Next round
game['round'] += 1
game['turn'] = 0
if game_is_over(game):
return end_game(game)
print "Starting round %i" % game['round']
game['player_order'].reverse()
generated = run_generators(game['players'])
if len(generated.keys()) > 0:
push(game, 'new-round', {'round': game['round'], 'players': copy(game['players']), "generated": generated})
else:
push(game, 'new-round', {'round': game['round'], 'players': copy(game['players'])})
player = game['players'][game['player_order'][game['turn']]] # Wow - nice line
response, data = communication.request(player, "game/%s/start_turn" % player['id'])
if response.status == 200:
turn_id = uuid4().hex
game['turn_id'] = turn_id
player['actions'] = {}
db.save(game)
turn_taken = True
push(game, 'start-turn', {'player': player, 'turn': game['turn'], 'round': game['round']})
def force_turn_end():
g = db.get(game['id'])
if g['turn_id'] == turn_id:
# The turn hasn't changed
print "Out of time"
end_turn(db, game, player, forced=True)
turn_timeout = Timer(config.TURN_TIMEOUT, force_turn_end)
turn_timeout.start()
else:
db.save(game)
log_action(game, player, 'turn-skipped')
push(game, 'turn-skipped', {'player': player, 'turn': game['turn'], 'round': game['round']})
def require_player_turn(f):
def inner_func(db, game, player, *args, **kwargs):
if player['id'] != game['player_order'][game['turn']]:
abort(400, 'It is not your turn')
return f(db, game, player, *args, **kwargs)
return inner_func
def has_enough_resources(player, resources):
for resource in resources:
if player['resources'][resource] < resources[resource]:
return False
return True
def require_resources(resources):
def require_resources_inner(f):
def inner_func(db, game, player, *args, **kwargs):
if not has_enough_resources(player, resources):
abort(400, 'Not enough resources')
return f(db, game, player, *args, **kwargs)
return inner_func
return require_resources_inner
def charge_resources(player, resources):
for resource in resources:
player['resources'][resource] -= resources[resource]
@require_player_turn
def end_turn(db, game, player, forced=False):
def run_end_turn():
next_turn(db, game)
print "Ended turn"
game['turn_id'] = None
db.save(game)
if forced:
push(game, 'timeout', {'player': player, 'turn': game['turn'], 'round': game['round']})
communication.request(player, "game/%s/end_turn" % player['id'])
thread.start_new_thread(run_end_turn, ())
return {"status": "success"}
def end_game(game):
def sort_players(player_id):
return (int(game['players'][player_id]['customers']), sum(game['players'][player_id]['resources'].values()))
game['player_order'] = sorted(game['player_order'], key=sort_players, reverse=True)
push(game, 'end', {"players": [game['players'][p] for p in game['player_order']]})
for player in game['players'].values():
communication.request(player, "game/%s" % player['id'], method="DELETE")
@require_player_turn
@require_resources(config.PR_COST)
def purchase_pr(db, game, player):
max_pr = reduce(lambda m, p: p['pr'] if p['pr'] > m else m , game['players'].values(), 0)
if max_pr <= (player['pr'] + 1):
for p in game['players'].values(): # Take away the bonuses
if p['pr'] == max_pr:
p['customers'] -= 2
charge_resources(player, config.PR_COST)
player['pr'] += 1
if max_pr <= (player['pr']):
for p in game['players'].values(): # Reapply bonus
if p['pr'] == player['pr']:
p['customers'] += 2
db.save(game)
log_action(game, player, 'purchase-pr')
push(game, 'purchase-pr', {"round": game['round'], "turn": game['turn'], "player": player})
return {"player": player, "highest_pr": (max_pr <= player['pr'])}
@require_player_turn
@require_resources(config.GENERATOR_COST)
def purchase_generator(db, game, player):
if sum(player['generators'].values()) >= config.MAX_RESOURCE_GENERATORS:
abort(400, "You can't build any more generators")
charge_resources(player, config.GENERATOR_COST)
generator = random.choice(config.GENERATORS.keys())
player['generators'][generator] += 1
player['customers'] += 1
db.save(game)
log_action(game, player, 'purchase-generator', {"generator_type": generator})
push(game, 'purchase-generator', {"round": game['round'], "turn": game['turn'], "player": player, 'generator_type': generator})
return {"player": player, 'generator_type': generator}
@require_player_turn
@require_resources(config.GENERATOR_IMPROVEMENT_COST)
def upgrade_generator(db, game, player, generator_type):
if sum(player['improved_generators'].values()) >= config.MAX_IMPROVED_RESOURCE_GENERATORS:
abort(400, "You can't build any more generators")
if player['generators'][generator_type] < 1:
abort(400, "You don't have enough %s" % generator_type)
charge_resources(player, config.GENERATOR_IMPROVEMENT_COST)
player['generators'][generator_type] -= 1
player['improved_generators'][generator_type] += 1
player['customers'] += 1
db.save(game)
log_action(game, player, 'upgrade-generator', {"generator_type": generator_type})
push(game, 'upgrade-generator', {"round": game['round'], "turn": game['turn'], "player": player, 'generator_type': generator_type})
return {"player": player, 'generator_type': generator_type}
@require_player_turn
def trade(db, game, player, offering, requesting):
if not has_enough_resources(player, offering):
abort(400, "You don't have enough stuff!")
players = [game['players'][p] for p in game['player_order'] if not p == player['id']]
random.shuffle(players) # Don't give one person first refusal
print "Player ", player['id'], " offering ", offering, " for ", requesting
trade_id = uuid4().hex
push(game, 'trade', {"round": game['round'], "turn": game['turn'], "player": player, 'offering': offering, 'requesting': requesting, "trade_id": trade_id})
if sum(offering.values()) >= (sum(requesting.values()) * config.BANK_TRADE_RATE):
# The bank will take the trade
charge_resources(player, offering)
for resource in requesting:
player['resources'][resource] += requesting[resource]
log_action(game, player, 'bank-trade', {"offer": offering, "request": requesting})
push(game, 'trade-bank-accepted', {"trade_id": trade_id})
db.save(game)
return {"player": player, 'accepted_by': 'bank'}
for p in players:
if has_enough_resources(p, requesting):
response, data = communication.request(p, "game/%s/trade" % player['id'], {"player": player['id'], "offering": offering, "requesting": requesting})
if response.status == 200:
charge_resources(player, offering)
charge_resources(p, requesting)
for resource in offering:
p['resources'][resource] += offering[resource]
for resource in requesting:
player['resources'][resource] += requesting[resource]
log_action(game, player, 'trade', {"offer": offering, "request": requesting, "traded_with": p['id']})
push(game, 'trade-accepted', {"trade_id": trade_id, "player": p})
db.save(game)
return {"player": player, 'accepted_by': p['id']}
log_action(game, player, 'trade-rejected', {"offer": offering, "request": requesting})
push(game, 'trade-rejected', {"trade_id": trade_id})
abort(500, "No bites")
@require_player_turn
def log(db, game, player, message):
push(game, 'log', {'player': player, 'message': message})