Last active
December 21, 2015 13:39
-
-
Save mszegedy/1291879b59920c9567e0 to your computer and use it in GitHub Desktop.
Maxwell's Demon
This file contains 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
import math | |
from random import random,choice | |
from scene import * | |
# An explanation for anyone wondering what this is: this is a game I wrote for | |
# Pythonista for the iPhone on a boring plane ride from Budapest to Muenchen. | |
# The credit for the idea goes to my father, although he apparently doesn't | |
# remember coming up with it. You play as Maxwell's Demon, trying to get all | |
# the balls on the screen on one side of the barrier by manipulating a little | |
# gate. The controls are simple; just tap the screen to toggle the gate. | |
# Later, on the subsequent ride to Newark, I also added a couple circles you | |
# can touch to slow down and speed up the game. Each level, there are more | |
# balls, and they move faster. In later levels, red balls start spawning, which | |
# have half the mass of the others. The physical model of the game is not quite | |
# accurate; magnitude of momentum is conserved in collisions, but not | |
# direction. This is so that it doesn't matter if the balls are spawned with an | |
# initial bias towards one axis or the other (which can make the game annoying | |
# or impossible). | |
# This was developed entirely inside Pythonista on my iPhone 3GS. I used to | |
# feel so l33t for being able to type for a long time on my iPhone's tiny | |
# keyboard, and now, with my super ergonomic swiping keyboard on my Note 4 | |
# phablet, I can't for the life of me remember how I was able to bear it, since | |
# now I can hardly type on a regular phone keyboard. Oh well, that's one talent | |
# I hope I won't need again. | |
class MyScene (Scene): | |
def setup(self): | |
class Ball: | |
def __init__(self,pos_x,pos_y,vel_x,vel_y,fast=False): | |
self.pos_x = pos_x | |
self.pos_y = pos_y | |
self.vel_x = vel_x | |
self.vel_y = vel_y | |
self.fast = fast | |
def draw(self): | |
ellipse(int(self.pos_x)-10,int(self.pos_y)-10,20,20) | |
try: | |
self.difficult += 0 | |
except AttributeError: | |
self.difficult = 1 | |
self.balls = [] | |
for index in range(choice(range(7+self.difficult,15+self.difficult))): | |
self.balls.append(Ball(random()*self.size.w,random()*self.size.h,(0.2*self.difficult+1)*(random()*2-1),(0.2*self.difficult+1)*(random()*2-1))) | |
try: | |
for index in range(choice(range(self.difficult))): | |
self.balls.append(Ball(random()*self.size.w,random()*self.size.h,(0.2*self.difficult+1)*(2*random()-1),(0.2*self.difficult+1)*(2*random()-1),True)) | |
except IndexError: | |
pass | |
self.gate = True | |
self.gate_h = 180 | |
self.speed = 1.5 | |
self.win = False | |
def draw(self): | |
if not self.win: | |
self.win = (reduce(lambda x,y: x and y, [ball.pos_x<self.size.w/2 for ball in self.balls]) or reduce(lambda x,y: x and y, [ball.pos_x>self.size.w/2 for ball in self.balls])) and self.gate | |
if self.win: | |
background(0,0,0) | |
tint(0,1,0) | |
text('WIN',font_size=int(200.*(self.size.w/self.size.h)**(0.5)),x=self.size.w/2,y=self.size.h/2) | |
return | |
background(0, 0, 0) | |
stroke(1,1,1) | |
stroke_weight(1) | |
fill(1,1,1) | |
ellipse(10,10,15,15) | |
if self.speed < 1.5: | |
no_fill() | |
ellipse(30,10,15,15) | |
if self.speed < 9./4.: | |
no_fill() | |
ellipse(50,10,15,15) | |
if self.speed < 27./8.: | |
no_fill() | |
ellipse(70,10,15,15) | |
if self.speed < 81./16.: | |
no_fill() | |
ellipse(90,10,15,15) | |
no_stroke() | |
fill(1,1,1) | |
rect(self.size.w/2-3,(self.size.h+self.gate_h+6)/2,6,(self.size.h-(self.gate_h+6))/2) | |
rect(self.size.w/2-3,0,6,(self.size.h-(self.gate_h+6))/2) | |
if self.gate: | |
rect(self.size.w/2-3,(self.size.h-self.gate_h)/2,6,self.gate_h) | |
for index,ball in enumerate(self.balls): | |
if ball.fast: | |
fill(1,0,0) | |
ball.draw() | |
if ball.fast: | |
fill(1,1,1) | |
ball.pos_x += 2*self.speed*ball.vel_x | |
ball.pos_y += 2*self.speed*ball.vel_y | |
else: | |
ball.pos_x += self.speed*ball.vel_x | |
ball.pos_y += self.speed*ball.vel_y | |
if ball.pos_x < 10 or ball.pos_x > self.size.w-10: | |
ball.vel_x *= -1 | |
if ball.pos_x < 10: | |
ball.pos_x = 10 | |
elif ball.pos_x > self.size.w-10: | |
ball.pos_x = self.size.w-10 | |
if ball.pos_y < 10 or ball.pos_y > self.size.h-10: | |
ball.vel_y *= -1 | |
if ball.pos_y < 10: | |
ball.pos_y = 10 | |
elif ball.pos_y > self.size.h-10: | |
ball.pos_y = self.size.h-10 | |
if ball.pos_x > self.size.w/2-13 and ball.pos_x < self.size.w/2+13 and self.gate: | |
ball.vel_x *= -1 | |
if ball.pos_x < self.size.w/2: | |
ball.pos_x = self.size.w/2-13 | |
elif ball.pos_x > self.size.w/2: | |
ball.pos_x = self.size.w/2+13 | |
if ball.pos_x > self.size.w/2-13 and ball.pos_x < self.size.w/2+13 and not self.gate: | |
if ball.pos_y < (self.size.h-self.gate_h-2)/2+10 or ball.pos_y > (self.size.h+self.gate_h+2)/2-10: | |
ball.vel_x *= -1 | |
if ball.pos_x < self.size.w/2: | |
ball.pos_x = self.size.w/2-13 | |
elif ball.pos_x > self.size.w/2: | |
ball.pos_x = self.size.w/2+13 | |
for jndex,other_ball in enumerate(self.balls): | |
if jndex > index: | |
if (ball.pos_x-other_ball.pos_x)**2+(ball.pos_y-other_ball.pos_y)**2 <= 400: | |
if ball.pos_x-other_ball.pos_x > 0: | |
angle = math.atan((ball.pos_y-other_ball.pos_y)/(ball.pos_x-other_ball.pos_x)) | |
else: | |
if ball.pos_y-other_ball.pos_y > 0: | |
try: | |
angle = math.atan((ball.pos_y-other_ball.pos_y)/(ball.pos_x-other_ball.pos_x))+math.pi | |
except ZeroDivisionError: | |
angle = math.pi/2. | |
else: | |
try: | |
angle = math.atan((ball.pos_y-other_ball.pos_y)/(ball.pos_x-other_ball.pos_x))-math.pi | |
except ZeroDivisionError: | |
angle = -math.pi/2. | |
ball.pos_x = other_ball.pos_x+math.cos(angle)*20 | |
ball.pos_y = other_ball.pos_y+math.sin(angle)*20 | |
mean_vel = (math.sqrt(ball.vel_x**2+ball.vel_y**2)+math.sqrt(other_ball.vel_x**2+other_ball.vel_y**2))/2 | |
ball.vel_x = math.cos(angle)*mean_vel | |
other_ball.vel_x = -ball.vel_x | |
ball.vel_y = math.sin(angle)*mean_vel | |
other_ball.vel_y = -ball.vel_y | |
''' | |
for jndex,other_ball in enumerate(self.balls): | |
if jndex > index: | |
if ball.pos_x > other_ball.pos_x: | |
ball.vel_x += 4.**(-(ball.pos_x-other_ball.pos_x)**2) | |
other_ball.vel_x -= 4.**(-(ball.pos_x-other_ball.pos_x)**2) | |
elif ball.pos_x < other_ball.pos_x: | |
ball.vel_x -= 4.**(-(ball.pos_x-other_ball.pos_x)**2) | |
other_ball.vel_x += 4.**(-(ball.pos_x-other_ball.pos_x)**2) | |
if ball.pos_y > other_ball.pos_y: | |
ball.vel_y += 4.**(-(ball.pos_y-other_ball.pos_y)**2) | |
other_ball.vel_y -= 4.**(-(ball.pos_y-other_ball.pos_y)**2) | |
elif ball.pos_y < other_ball.pos_y: | |
ball.vel_y -= 4.**(-(ball.pos_y-other_ball.pos_y)**2) | |
other_ball.vel_y += 4.**(-(ball.pos_y-other_ball.pos_y)**2) | |
''' | |
def touch_began(self, touch): | |
if self.win: | |
self.difficult += 1 | |
self.setup() | |
return | |
loc = touch.location | |
if 0 <= loc.x and loc.x <= 110 and 0 <= loc.y and loc.y <= 20: | |
if 10 <= loc.x and loc.x < 30: | |
self.speed = 1 | |
elif 30 <= loc.x and loc.x < 50: | |
self.speed = 1.5 | |
elif 50 <= loc.x and loc.x < 70: | |
self.speed = 9./4. | |
elif 70 <= loc.x and loc.x < 90: | |
self.speed = 27./8. | |
elif 90 <= loc.x and loc.x <= 110: | |
self.speed = 81./16. | |
else: | |
self.gate = not self.gate | |
run(MyScene()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment