Poly Bridge is a bridge-building simulation-puzzle game, developed by New Zealand-based indie studio Dry Cactus with music by Canadian composer Adrian Talens, where players build bridges for vehicles to cross. Poly Bridge was released for Microsoft Windows on July 12, 2016, iOS on June 13, 2017.Steel, wood, rope, and cable can be combined and used to strengthen a designed bridge. The game is made more difficult with the availability of different building materials of different prices. –- excerpted from Wikipedia
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.
Things you need to install beforehand and how to install them
Python3
pip install pygame
pip install anastruct
pip install vpython
pip install pymunk
Enter this in your termianl to download the game
git clone https://github.com/redxouls/Final_Project.git
Or go to the website tand find the button clone or download
main.py
, Structure.py
, Controller.py
, Car.py
, Truss.py
,Node.py
, `Bio.py and file for loading test).main.py
1. START! button:
Start the Game operation2. EXIT GAME button:
End the Game3. HELP button:
Get instructions4. RESUME Button:
Resume the game5. MENU Button:
Return to the menu1. "1" :
To set up "Road" Truss(which is black)2. "2" :
To set up "Wood" Truss(which is brown)3. "4" :
After each test press it to recover4. "Shiift" :
To stop or run the test5. "Space" :
Put a car onto the field6. "d" :
Press and Hold it as you grag to select an area with right click7. "e" :
Cancel the selection8. "Backspace" :
Delete trusses and nodes you just select9. "a" :
Move a node within range10. "s" :
Save your magnificent work into a file by entering filename in the terminal11. "l" :
Load the creation you saved by entering filename in the terminalmain.py
calls.main.py
import sys, pygame, copy, time
from pygame.locals import *
from anastruct import SystemElements
from vpython import *
from Node import *
from Truss import *
from Bio import *
from Ball import *
from Controller import *
from Structure import *
k = input("File name:")
x = int(input("width"))
y = int(input("height"))
initial_pos=[int(input("Initial x:")),int(input("Initial y:"))]
csize = int(input("cell size"))
root,sub1,sub2,,reset,cell1,cell2
Variables | Type | Usage |
---|---|---|
main_controler | Controller | Controll all other objects |
main_controler.structure | Structure | Store all trusses and nodes |
bg,screen | pygame object | Pygame Object to control the Gaming Screen |
clock | pygame object | Pygame Object to Control the screen's update speed |
Controller.py
import sys, pygame, time, copy, os, json
import numpy as np
from pygame.locals import *
from anastruct import SystemElements
from vpython import *
from Ball import *
from Node import *
from Truss import *
from Bio import *
from Structure import *
class Controller():
def __init__(self,structure,screen):
self.win = False
self.di = os.getcwd()
self.dir = os.path.join(self.di,'images')
path10 = os.path.join(self.dir,'esc_bg2.png')
self.background_size = (1280,960)
self.origin_game_bg = pygame.image.load(path10).convert_alpha()
self.game_bg = pygame.transform.scale(self.origin_game_bg,self.background_size)
#
self.structure = structure
self.screen = screen
self.balls = []
self.t = 0
Function: Import needed modules and declare numerous variblese for mouse control, mode switching, game status, temporarily saved coordinate.
Return: None
def findnode(self,mpos):
structure = self.structure
self.reltruss = []
nid = -1
screen,nodes,click,trusses = structure.screen, structure.nodes, self.click, structure.trusses
for i in range(len(nodes)):
check = nodes[i]
if check.clicked(mpos):
nid=i
break
for tru in trusses:
if tru.nodeA == nodes[nid] or tru.nodeB == nodes[nid]:
self.reltruss.append(tru)
return nid
Function: Given the mouse position,it finds the node id that is clicked by the mouse
Return: It will return the node id,if nothing is clicked,it returns -1.
def mvnode(self,mpos):
structure = self.structure
screen,nodes,click,trusses = structure.screen, structure.nodes, self.click, structure.trusses
npos = vector(mpos[0],mpos[1],0)
for tru in self.reltruss:
if mag(npos-tru.nodeA.pos) > self.tmpc[2] or mag(npos-tru.nodeB.pos) > self.tmpc[2]:
return
nodes[self.altednode].pos = npos
Function: Given the mouse position,the function determines whether the node movement is legal by checking the truss length,if so,the node will move.
Return: None
def delarea(self):
x1=self.dltcod[0][0]
x2=self.dltcod[1][0]
y1=self.dltcod[0][1]
y2=self.dltcod[1][1]
structure = self.structure
...
omitted
...
Function: It uses parametric equation to determine if the rectangle pulled out by the mouse includes the trusses or the node.If so,we add them into dltnode and dlttruss.
Return: None
def initial_platform(self,mapid):
structure = self.structure
if mapid == 1:
self.car_position = vector(60,650,0)
structure.truss_limit = 25
left_nodeA = structure.add_node(0,700)
left_nodeB = structure.add_node(200,700)
structure.add_truss(left_nodeA,left_nodeB,0)
if mapid == 2:
self.car_position = vector(60,150,0)
structure.truss_limit = 25
return
if mapid ==3:
left_nodeA = structure.add_node(0,700)
left_nodeB = structure.add_node(200,700)
structure.add_truss(left_nodeA,left_nodeB,0)
return
Function: Given map id,we create different scenarios for each map.Note that the third map contains obstacles
Return: None
def recov(self):
self.dltnode=[]
self.dlttruss=[]
Function: Clear the node to delete and truss to delete.
Return: None
def clear(self):
structure = self.structure
for nd in self.dltnode:
structure.nodes.remove(nd)
for tru in self.dlttruss:
structure.trusses.remove(tru)
if tru in self.structure.roadtrusses:
self.structure.roadtrusses.remove(tru)
self.recov()
Function: Eliminate the trusses and nodes that are selected.
Return: None
def game_start_interface(self):
def esc_interface(self):
def choose_map_interface(self):
def help_interface(self):
Function: Create different interfaces by using functions in pygame module such like blit, render, load, font.
Return: None
def click_start_button(self,downcod):
def click_exit_button(self,downcod):
def click_esc_resume_button(self,downcod):
def click_esc_restart_button(self,downcod):
def click_esc_exit_button(self,downcod):
def click_map1_button(self,downcod):
def click_map2_button(self,downcod):
def click_map3_button(self,downcod):
def click_help(self,downcod):
Function: Determing whether the mouse clicked on these buttons or not.
Return: Boolean Value
Car.py
def __init__(self,controller,pos):
self.controller = controller
self.structure = controller.structure
self.pos = pos
# Space
self._space = pymunk.Space()
self._space.gravity = (0.0, -900.0)
# Physics
# Time step
self._dt = 1.0 / 60.0
# Number of physics steps per screen frame
self._physics_steps_per_frame = 1
self._draw_options = pymunk.pygame_util.DrawOptions(self.structure.screen)
self.trusses = []
for i in self.structure.roadtrusses:
self.trusses.append([(int(i.nodeA.pos.x),960-int(i.nodeA.pos.y)),(int(i.nodeB.pos.x),960-int(i.nodeB.pos.y))])
# Static barrier walls (lines) that the balls bounce off of
self._add_static_scenery()
# Balls that exist in the world
self._balls = []
# Execution control and time until the next ball spawns
self._running = True
self._ticks_to_next_ball = 10
self._update_balls()
Function: Initializing car object with given controller
, pos
.After loading the structure and the car postion, we create a space and put all trusses into a space accordingly.
Return: None
def _create_ball(self,setpos=None):
"""
Create a ball.
:return:
"""
if setpos == None:
return
else :
pos = Vec2d(setpos.x,960-setpos.y)
wheel_color = 52,219,119
mass = 150
size = (50,30)
moment = pymunk.moment_for_box(mass, size)
chassi_b = pymunk.Body(mass, moment)
chassi_s = pymunk.Poly.create_box(chassi_b, size)
self._space.add(chassi_b, chassi_s)
wheel1_b.position = pos - (55,0)
self._space.add(
pymunk.PinJoint(wheel1_b, chassi_b, (0,0), (-25,-15)),
)
speed = -20
self._space.add(
pymunk.SimpleMotor(wheel1_b, chassi_b, speed),
pymunk.SimpleMotor(wheel2_b, chassi_b, speed)
)
Function: We first create two circles,we designate their masses and radius.Then we create a box and designate its size and mass.Later we add them to space,and use pin joints to connect them.Finally,we give the car its speed and angular velocity by SimpleMotor.(This function partially references the pymunk documentaion)
Return: None.
def _add_static_scenery(self):
"""
Create the static bodies.
:return: None
"""
static_body = self._space.static_body
self.trusses = []
for i in self.structure.roadtrusses:
self.trusses.append([(int(i.nodeA.pos.x),960-int(i.nodeA.pos.y)),(int(i.nodeB.pos.x),960-int(i.nodeB.pos.y))])
rt=self.trusses
static_lines=[]
...
omitted
...
self._space.add(static_lines)
Function: It collects the current trusses,and then remove the old trusses.Then,we add static lines to the space and give them elasticity and friction.
Return: None
def _update_balls(self):
"""
Create/remove balls as necessary. Call once per frame only.
:return: None
"""
self._create_ball(self.pos)
Function: It creates a car.
Return: None
Truss.py
def __init__(self,nodeA=None,nodeB=None,screen=None,*,pos=None,axis=None,radius=None):
self.nodeA = nodeA
self.nodeB = nodeB
if self.nodeA!= None and self.nodeB != None:
self.oril = mag(nodeA.pos-nodeB.pos)
self.screen = screen
self.maxforce = 100
self.collided = False
self.pos = pos
self.axis = axis
self.radius = radius
self.collapse = False
Function: Initialize the truss with given two nodes
Return: None
def draw_Truss(self,todel):
screen = self.screen
if todel:
pygame.draw.line(self.screen,(230,230,230), self.nodeA.to_int(), self.nodeB.to_int(), 17)
else:
pygame.draw.line(self.screen,(204,102,0), self.nodeA.to_int(), self.nodeB.to_int(), 17)
Function: It draws supportive trusses with different colors based on the given boolean variable 'todel',which indicates whether the truss is to be deleted
Return: None
def draw_marked_Truss(self,todel):
screen = self.screen
if todel:
pygame.draw.line(self.screen,(230,230,230), self.nodeA.to_int(), self.nodeB.to_int(), 17)
else:
pygame.draw.line(self.screen,(0,0,0), self.nodeA.to_int(), self.nodeB.to_int(), 17)
Function: It draws road trusses with different colors based on the given boolean variable 'todel',which indicates whether the truss is to be deleted
Return: None
def draw_obtruss(self):
screen = self.screen
pygame.draw.line(self.screen,(30,170,30), self.nodeA.to_int(), self.nodeB.to_int(), 17)
Function: It draws obstructive trusses.
Return: None
def length(self):
return mag(self.nodeA.pos-self.nodeB.pos)
Function: It calculates the length of the truss.
Return: length of the truss
def truss_touch(self,other):
px1 = (self.nodeA.pos.x,self.nodeB.pos.x-self.nodeA.pos.x)
py1 = (self.nodeA.pos.y,self.nodeB.pos.y-self.nodeA.pos.y)
px2 = (other.nodeA.pos.x,other.nodeB.pos.x-other.nodeA.pos.x)
py2 = (other.nodeA.pos.y,other.nodeB.pos.y-other.nodeA.pos.y)
...
omitted
...
delta = (a1*b2-a2*b1)
if delta == 0:
return True
deltax = (c1*b2-c2*b1)
deltay = (a1*c2-a2*c1)
if 0 <= deltax/delta <= 1 and 0 <= deltay/delta <= 1:
return True
return False
Function: It takes in two trusses and see if they intersect.
Return: Whether two trusses intersect
Node.py
def __init__(self,x=0,y =0,screen=None,*,pos=None,radius=None):
if pos == None:
self.pos = vector(x,y,0)
else:
self.pos = pos
self.radius = 15
self.maxforce = 0.000000002
self.screen = screen
Function: Initialize the node with given position
Return: None
def draw_node(self,todel):
screen = self.screen
if todel:
pygame.draw.circle(screen, (230, 230, 230), self.to_int(), self.radius, 0)
else:
pygame.draw.circle(screen, (96, 96, 96), self.to_int(), self.radius, 0)
return
Function: It draws nodes with different colors based on the given boolean variable 'todel',which indicates whether the truss is to be deleted
Return: None
def draw_obnode(self):
screen = self.screen
pygame.draw.circle(screen, (30, 170, 30), self.to_int(), self.radius, 0)
return
Function: It draws obstructive nodes
Return: None
def clicked(self,mouse_pos):
distance = ((self.pos.x-mouse_pos[0])**2+(self.pos.y-mouse_pos[1])**2)**0.5
if distance <= self.radius+10:
return True
else:
return False
Function: Determines whether the node is clicked by checking if 'mouse_pos' is in the circle of the node
Return: whether the node is clicked
def to_int(self):
return int(self.pos.x), int(self.pos.y)
Function: Since pygame uses integer coordinates,the function transfers float coordinates into integers
Return: integer coordinate tuple
Structure.py
def __init__(self,screen):
self.truss_limit = 25
self.trusses = []
self.nodes = []
self.click = False
self.lastc = 0
self.screen = screen
self.t = 0
self.Bios = []
self.loadid = []
self.roadtrusses=[]
self.collapse = False
self.dlt=False
self.dltcod=[(0,0),(0,0)]
self.dltnode=[]
self.dlttruss=[]
self.tmpc=[False,(0,0),120]
self.unstable = False
self.tempnodespos = []
self.obtruss = []
self.obnode = []
Function: Initialize our bridge structure,containing all information needed of the structure
Return: None
def add_node(self,x,y):
if len(self.trusses)> self.truss_limit +1 :
return
new_node = Node(x=int(x),y=int(y),screen=self.screen)
self.nodes.append(new_node)
return new_node
Function: It adds nodes to list 'self.nodes' with given position
Return: the new node that is created
def add_obtruss(self,nodeA,nodeB):
new_truss = Truss(nodeA,nodeB,self.screen)
self.obtruss.append(new_truss)
return new_truss
Function: It adds trusses to list 'self.trusses' with given nodes
Return: the new truss that is created
def add_obtruss(self,nodeA,nodeB):
new_truss = Truss(nodeA,nodeB,self.screen)
self.obtruss.append(new_truss)
return new_truss
Function: Similar to the 'add_truss' function,it adds obstructive trusses to list'obtruss'
Return: the new obstructive truss that is created
def add_obnode(self,x,y):
new_node = Node(x=int(x),y=int(y),screen=self.screen)
self.obnode.append(new_node)
return new_node
Function: Similar to the 'add_node' function,it adds obstructive nodes to list'obnode'
Return: the new obstructive node created
def set_orilen(self):
for truss in self.trusses:
truss.oril = truss.length()
for truss in self.roadtrusses:
truss.oril = truss.length()
Function: Set up the original length for each truss in order to facilitate the calculation of the collapse of truss
Return: None
Ball.py
class Ball:
def __init__(self,screen,label,pos=vector(0,0,0)):
self.label = label
self.screen = screen
self.radius = 10
self.g = 9.8
self.efficient = 0.01
self.colleffi = 0.8
self.power = 40.0
self.v = vector(0,0,0)
self.pos = pos
self.a = vector(0,self.g,0)
self.free = True
Function: Initiallize several physics parameters to track the movement of the two wheels of the car.
Return: None
def distance(self,*,node=None,truss=None):
if node != None:
return (self.pos[0]-node.cod[0]**2+(self.pos[1]-node.cod[1]**2)**0.5)
if truss != None:
return
Function: With given truss or node, it calculate the shortest distance between
Return: the distance calulated (float)
def nearest(self,structure):
for i in range(len(structure.roadtrusses)):
truss = structure.roadtrusses[i]
if truss.collapse:
continue
if self.pos.x>=truss.nodeA.pos.x and self.pos.x<truss.nodeB.pos.x:
return i
if self.pos.x<truss.nodeA.pos.x and self.pos.x>=truss.nodeB.pos.x:
return i
Function: It search the closest truss in roadtrusses and return the index of the neareset one
Return: index of the nearest truss(int)
def ground_distance(self,structure):
if self.nearest(structure) == None:
return
ground = structure.roadtrusses[self.nearest(structure)]
v1 = self.pos - vector(ground.nodeA.pos.x,ground.nodeA.pos.y,0)
v2 = vector(ground.nodeB.pos.x,ground.nodeB.pos.y,0) - vector(ground.nodeA.pos.x,ground.nodeA.pos.y,0)
theta = acos(v1.dot(v2)/(v1.mag*v2.mag))
distance = v1.mag*sin(theta)
return distance
Function: Utilizing the two function distance and nearest to calculate the distance between the car's wheel and the roadtrusses
Return: distance(float)
Bio.py
class Bio():
def __init__(self,*,pos=None, axis=None,screen=None,d=2.5*20,nodeA=None,nodeB=None):
if nodeA != None and nodeB!= None:
self.O = nodeA
self.C = nodeB
axis = nodeA.pos-nodeB.pos
d = axis.mag
else:
self.O = Node(pos=pos, radius=20)
self.C = Node(pos=pos+axis, radius=20)
self.bond = Truss(pos=pos, axis=axis, radius=20/2.0)
self.O.m = 20
self.C.m = 20
self.O.v = vector(0, 0, 0)
self.C.v = vector(0, 0, 0)
self.d = d
self.dt = 0.1
self.bond.k = 500.0
self.screen = screen
self.b = 0.05 * sqrt(self.bond.k*self.O.m)
self.gravity = vector(0,9.8,0)
Function: Initiallize physics parameters for falling trusses, and take advantage of both node and truss two class
Return: None
def bond_force_on_O(self): # return bond force acted on the O atom
return self.bond.k * (mag(self.bond.axis)-self.d) * norm(self.bond.axis) - self.b*(self.O.v-(self.O.v+self.C.v)/2)
Function: It define the way the two nodes connected to each others, and calculate the force accordingly
Return: force to keep this to nodes together(float)
def time_lapse(self): # by bond's force, calculate a, v and pos of C and O, and bond's pos and axis after dt
dt = self.dt
self.C.a = -self.bond_force_on_O() / self.C.m + self.gravity #
self.O.a = self.bond_force_on_O() / self.O.m + self.gravity #
self.C.v += self.C.a * dt
self.O.v += self.O.a * dt
self.C.pos += self.C.v * dt
self.O.pos += self.O.v * dt
self.bond.axis = self.C.pos - self.O.pos
self.bond.pos = self.O.pos
Function: The trusses move over time like a physics simulation, and this function update these objects position, velocity, and accerlation
Return: None
def ground_collision(self):
if self.O.pos.y > 900:
self.O.v.y*= -1
if self.C.pos.y > 900:
self.C.v.y*= -1
Function: If the trusses and nodes fall onto the ground, the ball collide with the ground.
Return: None
def collision(a1, a2):
v1prime = a1.v - 2 * a2.m/(a1.m+a2.m) *(a1.pos-a2.pos) * dot (a1.v-a2.v, a1.pos-a2.pos) / mag(a1.pos-a2.pos)**2
v2prime = a2.v - 2 * a1.m/(a1.m+a2.m) *(a2.pos-a1.pos) * dot (a2.v-a1.v, a2.pos-a1.pos) / mag(a2.pos-a1.pos)**2
return v1prime, v2prime
Function: This function takes in two nodes and handle collision between these two nodes
Return: None
def system_check_collision(self,COs):
N = len(COs)
for i in range(N-1):
for j in range(i+1,N):
if (COs[i].C.pos-COs[j].C.pos).mag<=2*COs[i].C.radius and dot(COs[i].C.pos-COs[j].C.pos,COs[i].C.v-COs[j].C.v)<0:
COs[i].C.v, COs[j].C.v = Bio.collision(COs[i].C,COs[j].C)
if (COs[i].C.pos-COs[j].O.pos).mag<=2*COs[i].C.radius and dot(COs[i].C.pos-COs[j].O.pos,COs[i].C.v-COs[j].O.v)<0:
COs[i].C.v, COs[j].O.v = Bio.collision(COs[i].C,COs[j].O)
if (COs[i].O.pos-COs[j].C.pos).mag<=2*COs[i].C.radius and dot(COs[i].O.pos-COs[j].C.pos,COs[i].O.v-COs[j].C.v)<0:
COs[i].O.v, COs[j].C.v = Bio.collision(COs[i].O,COs[j].C)
if (COs[i].O.pos-COs[j].O.pos).mag<=2*COs[i].C.radius and dot(COs[i].O.pos-COs[j].O.pos,COs[i].O.v-COs[j].O.v)<0:
COs[i].O.v, COs[j].O.v = Bio.collision(COs[i].O,COs[j].O)
Function: This function check if any nodes collide into one another and call collision function to change their speed
Return: None
def draw_Truss(self,screen):
pygame.draw.line(screen,(204,102,0), self.to_int(1), self.to_int(2), 17)
def draw_node(self,screen):
pygame.draw.circle(screen, (0, 127, 255), [int(self.O.pos.x), int(self.O.pos.y)], self.C.radius, 0)
pygame.draw.circle(screen, (0, 127, 255), [int(self.C.pos.x), int(self.C.pos.y)], self.C.radius, 0)
return
Function: Draw objects onto pygame screen
Return: None
def to_int(self, num):
if num ==1:
return [int(self.C.pos.x),int(self.C.pos.y)]
if num ==2:
return [int(self.O.pos.x),int(self.O.pos.y)]
Function: Convert vpython vector to two integer coordinate for pygame screen to draw objects
Return: coodinate of two objects(list)