Skip to content

Instantly share code, notes, and snippets.

@AndreiDuma
Last active April 9, 2016 14:18
Show Gist options
  • Save AndreiDuma/3e1c1f357ce1b9e50b295a062cc74ba5 to your computer and use it in GitHub Desktop.
Save AndreiDuma/3e1c1f357ce1b9e50b295a062cc74ba5 to your computer and use it in GitHub Desktop.
Schelet Python
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)
@adrian-nitu-92
Copy link

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.

    def test(runs, strategy, map):
        won = 0
        # test the random strategy
        g = Game(map, strategy)
        for it in range(runs):
            g.run()
            if g.status == Status.Won:
                won += 1
            g.reset()
        return won
seed(args.seed)
map = State(10, predator_radius=6, predator_speed=3,
          prey_radius=5, trap_radius=1)
gen_traps(map, 3, 5)
gen_predators(map, 5, 7)

wr = test(args.runs, RandomStrategy, map)
print('For RandomStrategy Won', wr, 'out of', args.runs)

Este posibil să nu vreți să folosiți numele rezervat map, dar depinde de voi.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment