Last active
April 9, 2016 14:18
-
-
Save AndreiDuma/3e1c1f357ce1b9e50b295a062cc74ba5 to your computer and use it in GitHub Desktop.
Schelet Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from abc import ABC, abstractmethod | |
| from math import sqrt, ceil | |
| from random import choice, randint, randrange | |
| from enum import Enum | |
| class Status(Enum): | |
| Playing = 0 | |
| Won = 1 | |
| Lost = 2 | |
| class State: | |
| # size - int | |
| # assume a square field, coordinates from (0, 0) to (size - 1, size - 1) | |
| # | |
| # traps - list of coordinate tuples | |
| # positions of all remaining traps | |
| # | |
| # trap_radius - int | |
| # how far from its origin a trap affects a predator | |
| # | |
| # predators - list of coordinate tuples | |
| # positions of all alive predators | |
| # | |
| # predator_speed - int | |
| # maximum predator speed | |
| # | |
| # predator_radius - int | |
| # how far a predator sees | |
| # | |
| # prey - coordinate tuple | |
| # position of prey | |
| # | |
| # prey_speed - int | |
| # maximum prey speed | |
| # | |
| # prey_radius - int | |
| # how far the prey sees | |
| # | |
| # collision_radius - int | |
| # how close a contact needs to be | |
| # | |
| def __init__(self, size, traps=[], trap_radius=0, | |
| predators=[], predator_speed=1, predator_radius=1, | |
| prey=(0, 0), prey_speed=1, prey_radius=1, | |
| collision_radius=0): | |
| self.size = size | |
| self.traps = traps | |
| self.trap_radius = trap_radius | |
| self.predators = predators | |
| self.predator_speed = predator_speed | |
| self.predator_radius = predator_radius | |
| self.prey = prey | |
| self.prey_speed = prey_speed | |
| self.prey_radius = prey_radius | |
| self.collision_radius = collision_radius | |
| self.status = Status.Playing | |
| # returns a string representing the current state map | |
| def __str__(self): | |
| map = [[[] for i in range(self.size)] for j in range(self.size)] | |
| for t in self.traps: | |
| map[t[1]][t[0]].append('T') | |
| for p in self.predators: | |
| map[p[1]][p[0]].append('P') | |
| map[self.prey[1]][self.prey[0]].append('@') | |
| s = '' | |
| for row in reversed(map): | |
| s += '|' | |
| for cell in row: | |
| s += '{:3}|'.format(''.join(cell)) | |
| s += '\n' | |
| s += 'Status: {}\n'.format(self.status) | |
| return s | |
| # creates a copy of the current state | |
| def clone(self): | |
| return State(self.size, self.traps[:], self.trap_radius, | |
| self.predators[:], self.predator_speed, | |
| self.predator_radius, self.prey, self.prey_speed, | |
| self.prey_radius, self.collision_radius) | |
| # computes the Manhattan distance between points `p` and `q` | |
| def distance(self, p, q): | |
| return abs(p[0] - q[0]) + abs(p[1] - q[1]) | |
| # computes the euclidian distance between points `p` and `q` | |
| def euclidian_distance(self, p, q): | |
| return sqrt((p[0]-q[0])**2 + (p[1] - q[1])**2) | |
| # checks whether two objects are colliding | |
| def is_collision(self, p, q): | |
| return self.distance(p, q) <= self.collision_radius | |
| # returns the trap active at `position` or None if there's no | |
| # trap close enough | |
| def get_trap(self, position): | |
| for t in self.traps: | |
| if self.distance(position, t) <= self.trap_radius: | |
| return t | |
| return None | |
| # checks whether `pos` is a valid position on the map | |
| def is_valid(self, pos): | |
| return pos[0] >= 0 and pos[0] < self.size \ | |
| and pos[1] >= 0 and pos[1] < self.size | |
| # returns all cell coordinates from `start` to `end` on the shortest path | |
| def path(self, start, end): | |
| if start == end: | |
| return [end] | |
| x, y = start | |
| options = [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)] | |
| next = min(options, key=lambda o: self.euclidian_distance(o, end)) | |
| rest = self.path(next, end) | |
| rest.insert(0, start) | |
| return rest | |
| # returns the farthest possible position along the path from `frm` to `to` | |
| # | |
| # makes no more than `max_speed` steps | |
| def walk(self, frm, to, max_speed=None): | |
| if max_speed is None: | |
| max_speed = self.predator_speed | |
| # go to destination directly if close enough | |
| if self.distance(frm, to) <= max_speed: | |
| return to | |
| return self.path(frm, to)[max_speed] | |
| # returns all locations an object can move to from `position` given | |
| # a `max_speed` maximum speed | |
| # | |
| # if must_move is True, staying still is not an option | |
| def all_actions(self, position, max_speed, must_move=False): | |
| actions = [] | |
| for x in range(-max_speed, max_speed + 1): | |
| _x = abs(x) | |
| for y in range(-(max_speed - _x), max_speed - _x + 1): | |
| # skip no-move action | |
| if must_move and x == 0 and y == 0: | |
| continue | |
| # shift to actual position | |
| pos_x, pos_y = position[0] + x, position[1] + y | |
| if self.is_valid((pos_x, pos_y)): | |
| actions.append((pos_x, pos_y)) | |
| return actions | |
| # Prey-specific methods | |
| # returns all possible actions for the prey | |
| def prey_actions(self): | |
| return self.all_actions(self.prey, self.prey_speed) | |
| # returns the predators the prey can see | |
| def prey_observable_predators(self): | |
| return [p for p in self.predators | |
| if self.distance(p, self.prey) <= self.prey_radius] | |
| # returns the predators the prey is colliding with | |
| def prey_colliding_predators(self): | |
| return [p for p in self.predators if self.is_collision(self.prey, p)] | |
| # returns the traps the prey can see | |
| def prey_observable_traps(self): | |
| return [t for t in self.traps | |
| if self.distance(t, self.prey) <= self.prey_radius] | |
| # Predator-specific methods | |
| # returns next action for the given predator | |
| def predator_action(self, predator): | |
| if predator not in self.predators: | |
| raise Exception('invalid predator') | |
| # if prey not visible, move randomly in search for it | |
| if not self.predator_observable_prey(predator): | |
| return choice(self.all_actions(predator, self.predator_speed, True)) | |
| other = self.predator_observable_predators(predator) | |
| # if prey visible and only predator, walk in direction of prey | |
| if len(other) == 0: | |
| return self.walk(predator, self.prey) | |
| # if prey visible and at least one other predator | |
| ordered = sorted(other + [predator], | |
| key=lambda p: self.distance(p, self.prey)) | |
| min_time = ceil(self.distance(ordered[1], self.prey) / | |
| self.predator_speed) | |
| if predator == ordered[0]: | |
| # only the closest predator needs to slow down | |
| speed = ceil(self.predator_speed / min_time) | |
| else: | |
| speed = self.predator_speed | |
| return self.walk(predator, self.prey, max_speed=speed) | |
| # checks if the prey is observable from the given predator | |
| def predator_observable_prey(self, predator): | |
| if predator not in self.predators: | |
| raise Exception('invalid predator') | |
| return self.distance(predator, self.prey) <= self.predator_radius | |
| # returns a list of other predators that can see the prey and can be seen | |
| # by the given predator. | |
| def predator_observable_predators(self, predator): | |
| if predator not in self.predators: | |
| raise Exception('invalid predator') | |
| return [p for p in self.predators if p != predator and | |
| self.distance(p, predator) <= self.predator_radius and | |
| self.distance(p, self.prey) <= self.predator_radius] | |
| def next_state(self, prey_action): | |
| next = self.clone() | |
| # update the prey position and status | |
| next.prey = prey_action | |
| colliding = next.prey_colliding_predators() | |
| if len(colliding) > 1: | |
| next.status = Status.Lost | |
| return next | |
| # kill zero or one colliding predators | |
| next.predators = [p for p in next.predators if p not in colliding] | |
| # update predator positions and status | |
| next.predators = [next.predator_action(p) for p in next.predators] | |
| colliding = next.prey_colliding_predators() | |
| if len(colliding) > 1: | |
| next.status = Status.Lost | |
| return next | |
| # kill zero or one colliding predators | |
| next.predators = [p for p in next.predators if p not in colliding] | |
| # activate traps and kill predators | |
| dead_predators = set() | |
| activated_traps = set() | |
| for p in next.predators: | |
| t = next.get_trap(p) | |
| if t: | |
| dead_predators.add(p) | |
| activated_traps.add(t) | |
| next.predators = [p for p in next.predators if p not in dead_predators] | |
| next.traps = [p for p in next.traps if p not in activated_traps] | |
| if len(next.predators) < 2: | |
| next.status = Status.Won | |
| return next | |
| class Strategy(ABC): | |
| # returns the action to take in the given state | |
| # | |
| # this method needs to be implemented by the strategies | |
| @classmethod | |
| @abstractmethod | |
| def next_action(cls, state): | |
| pass | |
| # basic strategy that chooses randomly from the available actions | |
| class RandomStrategy(Strategy): | |
| @classmethod | |
| def next_action(cls, state): | |
| return choice(state.prey_actions()) | |
| class Game: | |
| # initialize the game with an initial state and a strategy to use | |
| def __init__(self, initial_state, strategy): | |
| self.initial_state = initial_state | |
| self.state = initial_state | |
| self.strategy = strategy | |
| # status of the internal state | |
| @property | |
| def status(self): | |
| return self.state.status | |
| # runs the game starting from the initial state till one of the | |
| # stopping conditions occurs | |
| def run(self): | |
| while True: | |
| if self.status != Status.Playing: | |
| return | |
| action = self.strategy.next_action(self.state) | |
| self.state = self.state.next_state(action) | |
| # resets the internal state to the initial state | |
| def reset(self): | |
| self.state = self.initial_state | |
| # generate between `min` and `max` traps in the given state | |
| def gen_traps(state, min, max): | |
| number = randint(min, max) | |
| state.traps = [(randrange(state.size), randrange(state.size)) | |
| for i in range(number)] | |
| # generate between `min` and `max` predators in the given state | |
| def gen_predators(state, min, max): | |
| number = randint(min, max) | |
| state.predators = [(randrange(state.size), randrange(state.size)) | |
| for i in range(number)] | |
| # test the random strategy | |
| runs, won = 1000, 0 | |
| for it in range(runs): | |
| s = State(10, predator_radius=6, predator_speed=3, | |
| prey_radius=5, trap_radius=1) | |
| gen_traps(s, 3, 5) | |
| gen_predators(s, 5, 7) | |
| g = Game(s, RandomStrategy) | |
| g.run() | |
| if g.status == Status.Won: | |
| won += 1 | |
| g.reset() | |
| print('Won', won, 'out of', runs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Foarte bine făcut scheletul, doar 3 propuneri:
"# test the random strategy" ar fi misto să fie o funcție de test, care ia ca parametri runs, strategy si map,
iar game si state par initializate de prea multe ori. Și pentru sanity în testare merge un seed pentru generări de traps și prădători.
Este posibil să nu vreți să folosiți numele rezervat map, dar depinde de voi.