fork download
  1. from collections import defaultdict
  2. from random import sample, shuffle
  3. import time # clock
  4. from copy import deepcopy
  5.  
  6. class Cardinal:
  7. NORTH = 0
  8. EAST = 1
  9. SOUTH = 2
  10. WEST = 3
  11.  
  12. class CellType:
  13. BAD = -2
  14. GOOD = -1
  15. EMPTY = 0
  16. SNAKE_1 = 1
  17. SNAKE_2 = 2
  18. SNAKE_3 = 3
  19. SNAKE_4 = 4
  20.  
  21. def is_food(value):
  22. return value < EMPTY
  23. def is_snake(value):
  24. return value > EMPTY
  25.  
  26. class FoodType:
  27. RED = -2
  28. BLUE = 3
  29.  
  30. def value_by_cell(cell):
  31. if cell == CellType.GOOD:
  32. return BLUE
  33. else:
  34. return RED
  35.  
  36.  
  37. class ScoreTable:
  38.  
  39. def __init__(self, players):
  40. self.games = 0
  41. self.table = { player.id : self.__row(players) for player in players }
  42.  
  43. def __row(self, players):
  44. return { 'wins' : 0, 'deaths' : 0, 'kills' : [0 for p in players] }
  45.  
  46. def score_win(self, player):
  47. self.table[player]['wins'] += 1
  48.  
  49. def score_kill(self, killer, died):
  50. self.table[killer]['kills'][died] += 1
  51. self.table[died]['deaths'] += 1
  52.  
  53. def get_results(self):
  54. return sorted(self.table.items(),
  55. key = lambda (k,v):(v['wins'], sum(v['kills']) - v['deaths'],
  56. reverse = True)
  57.  
  58.  
  59. class Game:
  60.  
  61. SNAKE_MIN = 2
  62.  
  63. def __init__(self, game_id, speed, goodies, baddies, scoring, players):
  64. self.id = game_id
  65. self.speed = speed
  66. self.scoring = scoring
  67. self.players = players
  68. self.snakes = []
  69. self.food = []
  70.  
  71. self.board = Board(42, 42, goodies, baddies)
  72.  
  73. self.spawn_snakes()
  74. self.spawn_food()
  75.  
  76. # game loop
  77. while True:
  78. alive = [snake for snake in self.snakes if snake.alive]
  79.  
  80. if len(alive) > 1:
  81. self.tick(speed)
  82.  
  83. elif len(alive) == 1:
  84. snake = alive[0]
  85. # snake must stay alive for 10 more ticks
  86. for i in xrange(10):
  87. self.tick(speed)
  88. if not snake.alive:
  89. # no winner
  90. snake = None
  91. self.game_over(snake)
  92. break
  93.  
  94. else:
  95. self.game_over(None)
  96. break
  97.  
  98. def spawn_snakes(self):
  99. # snake parameters for each player (position, direction)
  100. p1 = ((self.width // 4, self.height // 2), Cardinal.EAST)
  101. p2 = ((self.width - p1[0], self.height - p1[1]), Cardinal.WEST)
  102. p3 = ((self.width // 2, self.height // 4), Cardinal.SOUTH)
  103. p4 = ((self.width - p3[0], self.height - p3[1]), Cardinal.NORTH)
  104.  
  105. # though there is no significant advantage, randomize starting positions anyway
  106. params = shuffle([p1, p2, p3, p4])
  107.  
  108. # spawn a snake for each player
  109. for i in xrange(len(self.players)):
  110. snake = Snake(i+1, self.players[i], *params[i])
  111.  
  112. # give snakes an initial boost of 3
  113. snake.feed(3)
  114. self.snakes.append(snake)
  115.  
  116. for snake in self.snakes:
  117. self.board.set_cell(snake.id, *snake.head())
  118.  
  119. def spawn_food(self):
  120. # subtract current food count from limit
  121. goodies = self.board.goodie_limit - len(self.board.dictionary[CellType.GOOD])
  122. baddies = self.board.baddie_limit - len(self.board.dictionary[CellType.BAD])
  123.  
  124. sample_size = (goodies + baddies)
  125.  
  126. if sample_size == 0:
  127. return
  128.  
  129. # get all empty cells
  130. empty = self.board.dictionary[CellType.EMPTY]
  131.  
  132. if sample_size > len(empty):
  133. # get the entire list of empty positions in random order
  134. sample_size = len(empty)
  135. positions = sample(empty, sample_size)
  136.  
  137. # target percentage of goodies
  138. ratio = self.board.goodie_limit / float(self.board.goodie_limit + self.board.baddie_limit)
  139.  
  140. # spawn food such that the target ratio is approached
  141. i = 0
  142. while i < sample_size:
  143. compare = cmp(goodies / float(goodies + baddies), baddies / float(goodies + baddies))
  144. if compare < 0:
  145. self.board.set_cell(FoodType.BLUE, *positions[i])
  146. goodies -= 1
  147. elif compare > 0:
  148. self.board.set_cell(FoodType.RED, *positions[i])
  149. baddies -= 1
  150. else:
  151. if randint(0,1) == 1:
  152. self.board.set_cell(FoodType.BLUE, *positions[i])
  153. goodies -= 1
  154. else:
  155. self.board.set_cell(FoodType.RED, *positions[i])
  156. baddies -= 1
  157. i += 1
  158.  
  159. else:
  160. positions = sample(empty, sample_size)
  161.  
  162. # spawn maximum amount of food
  163. for i in xrange(sample_size):
  164. if i < goodies:
  165. self.board.set_cell(FoodType.BLUE, *positions[i])
  166. else:
  167. self.board.set_cell(FoodType.RED, *positions[i])
  168.  
  169. def snake_choices(self):
  170. # read-only copy of the game board for player access
  171. _board = deepcopy(self.board)
  172.  
  173. for snake in self.snakes:
  174. time_taken = time.clock()
  175.  
  176. # call each snake's choice logic
  177. snake.choose_direction(_board, time_taken)
  178.  
  179. time_taken = time.clock() - time_taken
  180. if time_taken > 1.0:
  181. exit('Player %s exceeded the time limit, taking %s seconds.' % (snake.__class__.__name__, time_taken))
  182.  
  183. # TODO
  184. def move_snakes(self):
  185. # in the event that both snakes chose the same destination, one will move first,
  186. # then the other will have a head-to-head collision, killing both
  187. for snake in self.snakes:
  188. if not snake.alive:
  189. break
  190. head_pos = snake.head()
  191. move_to = self.board.get_coords(snake.direction, *head_pos)
  192. cell_value = self.board.get_cell(*move_to)
  193.  
  194. if CellType.is_snake(cell_value):
  195. # killed by enemy snake
  196. snake.die()
  197. enemy = self.snakes[cell_value - 1]
  198. self.scoring.score_kill(enemy.id, snake.id)
  199.  
  200. if enemy.alive and move_to == enemy.head():
  201. # head-to-head collision kills both
  202. enemy.die()
  203. self.scoring.score_kill(snake.id, enemy.id)
  204.  
  205. break
  206.  
  207. # did snake get food?
  208. food_value = 0
  209. if CellType.is_food(cell_value):
  210. food_value = FoodType.value_by_cell(cell_value)
  211.  
  212. # move snake tail if not growing
  213. if snake.growing <= 0:
  214. # move tail
  215. tail = snake.cells.pop()
  216. self.board.set_cell(CellType.EMPTY, *tail)
  217.  
  218. if snake.growing < 0:
  219. # shrink tail
  220. tail = snake.cells.pop()
  221. self.board.set_cell(CellType.EMPTY, *tail)
  222. self.growing += 1
  223.  
  224. # move snake head
  225. self.board.set_cell(snake.id, *move_to)
  226.  
  227. # feeding happens after moving so that growing isn't instant
  228. if food_value:
  229. snake.feed(food_value)
  230. # TODO: score for length/eaten?
  231.  
  232. # spawn replacement food
  233. self.spawn_food()
  234.  
  235. def tick(self):
  236. self.snake_choices()
  237. self.move_snakes()
  238.  
  239. # TODO: measure time taken?
  240. def game_over(self, winner):
  241. if winner:
  242. self.scoring.score_win(winner.id)
  243. print 'Game: %s, Winner: (%s) %s' % (self.id, winner.id, winner.__class__.__name__)
  244. else:
  245. print 'Game: %s, No winner' % self.id
  246.  
  247.  
  248. class Board:
  249.  
  250. def __init__(self, width, height, goodie_limit, baddie_limit, torus = True):
  251. self.width = width
  252. self.height = height
  253. self.goodie_limit = goodie_limit
  254. self.baddie_limit = baddie_limit
  255. # TODO: Not currently used
  256. self.torus = torus
  257.  
  258. # 2D list
  259. self.grid = [[CellType.EMPTY for x in xrange(self.width)] for y in xrange(self.height)]
  260.  
  261. # cells by type
  262. self.dictionary = defaultdict(list, {CellType.EMPTY : [(x,y) for y in xrange(self.height) for x in xrange(self.width)]})
  263.  
  264. def get_cell(self, x, y):
  265. return self.grid[y][x]
  266.  
  267. def set_cell(self, value, x, y):
  268. self.dictionary[self.get_cell(x,y)].remove( (x,y) )
  269. self.dictionary[value].push( (x,y) )
  270. self.grid[y][x] = value
  271.  
  272. def get_coords(self, direction, x, y):
  273. if direction == Cardinal.NORTH:
  274. return (x, (y-1) % self.height)
  275. if direction == Cardinal.EAST:
  276. return ((x+1) % self.width, y)
  277. if direction == Cardinal.SOUTH:
  278. return (x, (y+1) % self.height)
  279. if direction == Cardinal.WEST:
  280. return ((x-1) % self.width, y)
  281. raise ValueError('get_coords(%s, %s, %s)' % (direction, x, y))
  282.  
  283. def clear(self):
  284. for y in xrange(self.height):
  285. for x in xrange(self.width):
  286. self.set_cell(x, y, CellType.EMPTY)
  287.  
  288.  
  289. class Snake:
  290.  
  291. def __init__(self, snake_id, player_class, position, direction):
  292. self.id = snake_id
  293. self.player = player_class
  294. self.cells = [position]
  295. self.direction = direction
  296. self.alive = True
  297. self.growing = 0
  298.  
  299. def head(self):
  300. return self.cells[0]
  301.  
  302. def length(self):
  303. return len(self.cells)
  304.  
  305. def straight(self):
  306. pass
  307.  
  308. def left(self):
  309. self.direction = (self.direction - 1) % 4
  310.  
  311. def right(self):
  312. self.direction = (self.direction + 1) % 4
  313.  
  314. def choose_direction(self, _board, time_stamp):
  315. # Get move choice from self.player
  316. self.player.choose_direction(_board, time_stamp)
  317. assert(isinstance(player.direction, int))
  318. assert(0 <= player.direction < 4)
  319. self.direction = player.direction
  320.  
  321. def feed(self, value):
  322. # RED does nothing when min length
  323. if value < 0 and len(self.cells) <= Game.SNAKE_MIN:
  324. self.growing = 0
  325. return
  326. self.growing += value
  327.  
  328. def die(self):
  329. self.alive = False
  330. self.growing = 0
  331.  
  332.  
  333. # Essentially a read-only Snake class so that players may use the information
  334. class Snake_ReadOnly:
  335.  
  336. def __init__(self, snake):
  337. self.id = snake.id
  338. self.cells = snake.cells[:]
  339. self.direction = snake.direction
  340. self.alive = snake.alive
  341. self.growing = snake.growing
  342.  
  343. def head(self):
  344. return self.cells[0]
  345.  
  346. def length(self):
  347. return len(self.cells)
  348.  
  349. def straight(self):
  350. return self.direction
  351.  
  352. def left(self):
  353. return (self.direction - 1) % 4
  354.  
  355. def right(self):
  356. return (self.direction + 1) % 4
  357.  
  358.  
  359. class Player:
  360.  
  361. def __init__(self):
  362. self.direction = None
  363.  
  364. # @board - a copy of the Board class
  365. # @time_stamp - the time the clock started ticking for your move
  366. def choose_direction(self, board, time_stamp):
  367. pass
  368.  
  369.  
  370. class RandomBot(Player):
  371.  
  372. def __init__(self):
  373. from random import randint
  374.  
  375. def choose_direction(self, board, time_stamp):
  376. self.direction = [me.left, me.straight, me.right][randint(0,2)]()
  377.  
  378.  
  379. def main():
  380. return
  381.  
  382. # game options
  383. speed = 1
  384. goodies = 100
  385. baddies = 10
  386.  
  387. # instantiate player classes
  388. players = [RandomBot(), RandomBot()]
  389.  
  390. # create score table
  391. scoring = ScoreTable(players)
  392.  
  393. # TODO: Loop for multiple games
  394. game = Game(i, speed, goodies, baddies, scoring, players)
  395.  
  396.  
  397. main()
Success #stdin #stdout 0s 9024KB
stdin
Standard input is empty
stdout
1