Skip to content

Instantly share code, notes, and snippets.

@mszegedy
Last active December 21, 2015 13:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mszegedy/1291879b59920c9567e0 to your computer and use it in GitHub Desktop.
Save mszegedy/1291879b59920c9567e0 to your computer and use it in GitHub Desktop.
Maxwell's Demon
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