Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not pull!! quick and dirty proof of concept asteroids with eval'ed user script #19

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 70 additions & 47 deletions turtlepower/asteroids.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,47 @@ def callback(self):
self.forward(self.speed)


script = """
def run_from(asteroids):
pass

def attack(asteroid):
diff = ship.turn_towards(ship.towards(asteroid), 6)

if distance(ship, asteroid) < ship.range:
if diff < 1:
ship.fire()
else:
ship.forward(1)

def update():
asteroids = get_asteroids()

dangerous = [a for a in asteroids if distance(ship, a) < 70]
if dangerous:
run_from(dangerous)
else:
asteroids.sort(key=lambda a: distance(ship, a))
attack(asteroids[0])

ship.on_update(update)
"""


class Proxy(object):
""" Hide some methods but not really
"""
def __init__(self, base, methods):
self._base = base
self._methods = methods

def __getattr__(self, key):
if key in self._methods:
return getattr(self._base, key)
else:
raise AttributeError(key)


class Ship(PowerTurtle):
type = 'ship'

Expand All @@ -52,8 +93,23 @@ def setup(self):
self.setheading(90)
self.dead = False
self.rocket = None
self.__range = 200
self.state = "shooting"
self.range = 200

self._update_callback = None

ship = Proxy(self, {
'fire', 'range',
'turn_towards', 'towards', 'forward',
'xcor', 'ycor', 'distance',
'on_update',
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so the idea is to only expose limited apis?

Not sure I get this. The student is supposed to write the ship method. This example is just how I wrote it in an hour or so, there's other ways to do it. True, the teacher might choose to provide a rocket class or similar, but I'd want them to be able to build it all from scratch. E.g. first, an asteroid turtle, then a ship turtle, then a rocket turtle. We won't know in advance what methods they will write.

Also, the turn_towards, xcor, ycor, etc methods are part of the stdlib turtle interface, not specific to this game, and I would want them on every single turtle, not per world. It's up for the student to decide what they need to do.

Although, I do think update() is a bit better that callback() as a name.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a string just above called script which contains the students
code. The proxy object just limits the api so that you can keep things
competitive! There is no reason why the next level can't be making your
own game.
On 26 Sep 2013 20:35, "Simon Davy" [email protected] wrote:

In turtlepower/asteroids.py:

@@ -52,8 +93,23 @@ def setup(self):
self.setheading(90)
self.dead = False
self.rocket = None

  •    self.__range = 200
    
  •    self.state = "shooting"
    
  •    self.range = 200
    
  •    self._update_callback = None
    
  •    ship = Proxy(self, {
    
  •        'fire', 'range',
    
  •        'turn_towards', 'towards', 'forward',
    
  •        'xcor', 'ycor', 'distance',
    
  •        'on_update',
    
  •    })
    

Ok, so the idea is to only expose limited apis?

Not sure I get this. The student is supposed to write the ship method.
This example is just how I wrote it in an hour or so, there's other ways to
do it. True, the teacher might choose to provide a rocket class or similar,
but I'd want them to be able to build it all from scratch. E.g. first, an
asteroid turtle, then a ship turtle, then a rocket turtle. We won't know in
advance what methods they will write.

Also, the turn_towards, xcor, ycor, etc methods are part of the stdlib
turtle interface, not specific to this game, and I would want them on every
single turtle, not per world. It's up for the student to decide what they
need to do.

Although, I do think update() is a bit better that callback() as a name.


Reply to this email directly or view it on GitHubhttps://github.com//pull/19/files#r6611819
.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I saw the string - I was commenting ion the 'fire' method you provided.

Regards keeping it competitive, I think it may be confusing that the current implementation in turtlepower is incorrect. setposition/setx/sety do not teleport you in the stdlib module - they animate your path to that coord. That is TurtlePower will do too (very soon, once we start using NinjaTurtle as out turtle impl). So there is no way to teleport, at least - that's an artifact of current implementation that is incorrect.

Regards, other forms of cheating - that would be enforce by change the methods of the base turtle type for that world. e.g. for a grid world with only 90 deg changes allowed, it would coerce them to the nearest 90.

e.g.

class GridTurtle(PowerTurtle):
    def left(self, angle=90):
        angle = angle % 360
        if angle is None or angle <=90
            angle = 90
        elif angle <= 180:
            angle = 180
        elif angle <= 270:
            angle = 270
        else:
            return
        super(GridTurtle).left(angle)


exec(script, {
'ship': ship,
'get_asteroids': lambda: [t for t in self.world.turtles
if t.type == 'asteroid'],
'distance': lambda a, b: a.distance(b),
})

def callback(self):
self.penup()
Expand All @@ -63,61 +119,28 @@ def callback(self):
self.write("I WIN, PUNY HUMAN")
sleep(10)
sys.exit(0)
if self.state != 'shooting':
self.runaway()
else:
distances = [
(self.distance(a) - a.radius, a) for a in asteroids
]
distances.sort()
dangerous = [(d, a) for d, a in distances if d < 70]
if dangerous:
self.run(dangerous)
else:
self.shoot(distances[0][0], distances[0][1])

def shoot(self, distance, a):
diff = self.turn_towards(self.towards(a), 6)
if distance < self.__range:
if diff < 1:
self.fire()
else:
self.forward(1)

elif self._update_callback:
self._update_callback()

def on_update(self, callback):
self._update_callback = callback

def die(self):
if not self.dead:
self.write("GAME OVER")
self.dead = True

def fire(self):
if self.rocket is None:
self.rocket = Rocket(w)
self.rocket.init(
self.heading() + (random() * 4) - 2,
self.pos(),
self.__range
self.range
)
self.world.add_turtle(self.rocket)

def die(self):
if not self.dead:
self.write("GAME OVER")
self.dead = True

def run(self, dangerous):
d, a = dangerous[0]
lr = -90 if random() > 0.5 else 90
self._running_heading = self.towards(a) + lr
self._running_distance = 50
self.state = "turning"
self.runaway()

def runaway(self):
if self.state == "turning":
diff = self.turn_towards(self._running_heading, 6)
if diff < 1.0 or diff > 359.0:
self.state = "running"
elif self.state == "running":
self.forward(2.0)
self._running_distance -= 2.0
if self._running_distance <= 1.0:
self.state = 'shooting'


class Rocket(PowerTurtle):
type = 'rocket'
Expand Down