#!/usr/bin/env python3
# coding: utf-8
'''
Siltanosturi: Perusdynamiikka toimii, mutta taakka ei reagoi esteisiin.
- Siirrä vaunua vasen ja oikea nuolinäppäimillä
- Nosta tai laske kuormaa ylä- ja alanuolella
- Kytke kuorman korkeuden säätö päälle ja pois s-näppäimellä
'''
import pygame
import json
from math import cos, sin
from dxdt import fdxdt, Flukko
from rk4 import rk4
from opt_params import par
g = 9.81
Musta = (0, 0, 0) # Mustassa ei ole mitään valoa
Valk = (255, 255, 255) # valkoisessa on kaikenväristä valoa
Sin = (0, 0, 255) # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Seina = (200, 200, 200)
Vaunuvari = (0, 200, 200)
Puskurivari = (250, 0, 100)
Kiskovari = (50, 200, 100)
# Kuorman paikka xy-koordinaatistossa vaijerin
# L ja kulman funktiona
def rw_xy(L, kulma):
return (L * sin(kulma), -L * cos(kulma))
# Voimanuolien piirtäminen
# p0: alkupiste;
def nuoli(p0, Voima):
(ix0, iy0) = p0
scale = 10.0
(Fx, Fy) = Voima
p1 = (ix0 + scale * Fx, iy0 - scale * Fy)
pygame.draw.line(screen, Vihr, p0, p1, 4)
# referenssivoima, jolla kuorma pysyisi paikallaan
def nuolir(p0, Voima):
(ix0, iy0) = p0
scale = 10.0
(Fx, Fy) = Voima
p1 = (ix0 + scale * Fx, iy0 - scale * Fy)
pygame.draw.line(screen, Pun, p0, p1, 4)
# Pelin ja simuloinnin ajoitus
class cl_Ajat:
def __init__(self):
self.Nayttotaajuus = 48
self.Dt = 1.0 / self.Nayttotaajuus
# Pelimaailman kuvaaminen näytölle peli-ikkunaan.
# Luokka Kamera periytyy toisesta ohjelmasta, jossa kuvakulmaa
# saattoi vaihtaa.
class Cl_Kamera:
def __init__(self, Wxnaytto, Wynaytto):
self.Wxnaytto = Wxnaytto
self.Wynaytto = Wynaytto
kuvasuhde = self.Wynaytto/self.Wxnaytto
self.Wx = 20.0
self.Wy = kuvasuhde * self.Wx
self.Wx0 = 0.0
self.Wy0 = -0.9 * self.Wy
# Lasketaan mitä pikseliä näytöllä vastaa pelimaailman piste.
# Oivallinen geometrian harjoitus miettiä, mihin tämä perustuu
def xy_naytolla(self, xy):
scale = self.Wxnaytto / self.Wx
(x, y) = xy
xpikseli = int((x - self.Wx0) * scale)
ypikseli = int(self.Wynaytto - (y - self.Wy0) * scale)
return (xpikseli, ypikseli)
def xy_skaalaus(self, w, h):
scale = self.Wxnaytto / self.Wx
xpikseli = int(w * scale)
ypikseli = int(h * scale)
return (xpikseli, ypikseli)
def skaalaus(self, x):
scale = self.Wxnaytto / self.Wx
xpikseli = int(x * scale)
return xpikseli
# piirretään muutama laatikko markkeeraamaan taloja, joiden katolle
# kuorma pitäisi laskea
# Ohjelmoimatta: Kuorman ja rakennusten vuorovaikutus
class Cl_tyomaa:
def __init__(self, Talot):
self.talot = Talot
def piirra(self):
for talo in self.talot:
topleft = Kamera.xy_naytolla(talo)
w = Kamera.skaalaus(1.0)
h = Kamera.skaalaus(4.0)
dim = (w, h)
pygame.draw.rect(screen, Seina, (topleft, dim), 0)
# Siltanosturin vaunu. Kulkee katonrajassa vaakasuoraa kiskoa pitkin
class Cl_vaunu:
def __init__(self):
self.M = 1.0 # Vaunun massa
self.x = 15.0 # 0.1*Kamera.Wx # vaunun paikka
self.v = 0.0 # vaunun nopeus
self.F = 0.0 # vaunua liikuttava voima
self.Cf = par.cfr_car # kitkakerroin
def piirra(self):
(xi, yi) = Kamera.xy_naytolla((self.x, 0.0))
pygame.draw.line(screen, Vaunuvari, (xi - 20, yi), (xi + 20, yi), 20)
nuoli((xi, yi), (self.F, 0.0))
(xb0, yb0) = Kamera.xy_naytolla((0.1 * Kamera.Wx, 0.0))
(xb1, yb1) = Kamera.xy_naytolla((0.9 * Kamera.Wx, 0.0))
pygame.draw.line(screen, Puskurivari, (0, yi - 5),
(xb0 - 20, yi - 5), 40)
pygame.draw.line(screen, Puskurivari, (xb1 + 20, yi - 5),
(Wxnaytto, yi - 5), 40)
pygame.draw.line(screen, Kiskovari, (0, yi + 10),
(Wxnaytto, yi + 10), 10)
# Taakse/vasemmalle
def taakse(self):
self.F = -5.0
# print(self.F)
def eteen(self):
self.F = 5.0
# print(self.F)
# Kiskon päissä on joustavat puskurit,
# jotka työntävät vaunua poispäin puskurista
# Puskuritkin voisi piirtää
def puskurit(self):
dx1 = self.x - 19.0 # 0.9*Kamera.Wx
dx2 = -self.x + 1.0 # 0.1*Kamera.Wx
if dx1 > 0:
self.F = -20.0 * dx1
elif dx2 > 0:
self.F = 20.0 * dx2
class Cl_kuorma:
def __init__(self):
self.M = 3.0 # Massa
self.theta = 0.0 # vaijerin kulma
self.w = 0.0 # vaijerin kulman muutosnopeus
self.Lr = 4.0 # vaijerin pituuden asetusarvo
self.L = self.Lr # vaijerin pituus
self.Lv = 0.0 # vaijerin pituuden muutosnopeus
self.F0 = 9.81 * self.M
self.F = self.F0 # vaijerin jännitys
self.Cf = par.cfr_load # ilmanvastuksen kerroin
self.Ckela = par.cfr_kela
self.lukittu = True
# Piirretään kuorma, vaijeri ja vaijerin pituuden asetusarvo
def piirra(self):
(x, y) = rw_xy(self.L, self.theta)
(xi, yi) = Kamera.xy_naytolla((x + vaunu.x, y))
pygame.draw.circle(screen, Kelt, (xi, yi), 10, 0)
(rxi, ryi) = Kamera.xy_naytolla(
(x + vaunu.x, -self.Lr * cos(self.theta)))
if Saato:
pygame.draw.line(screen, Vihr, (rxi - 50, ryi), (rxi + 50, ryi), 1)
vp = Kamera.xy_naytolla((vaunu.x, 0.0))
pygame.draw.line(screen, Sin, vp, (xi, yi), 4)
F = rw_xy(-self.F, self.theta)
F0 = rw_xy(-self.F0, self.theta)
nuolir((xi + 2, yi), F0)
nuoli((xi - 2, yi), F)
# lasketaan asetusarvoa
def alas(self):
self.lukittu = False
if Saato:
self.Lr += 0.02
else:
self.F -= 0.1
def ylos(self):
self.lukittu = False
if Saato:
self.Lr -= 0.02
if self.Lr > 2.0:
self.Lr -= 0.02
else:
self.F += 0.1
# Kuormaa oli hyvin vaikea pysäyttää tietylle korkeudelle voimaa säätelemällä
# Kun F = F0 ja vaunu täysin levossa, kuorma pysähtyy, mutta muuten se
# vaeltaa ylös tai alas.
# Tein yksinkertaisen takaisinkytketyn säädön pysäyttämään kuorma halutulle
# korkeudelle
# F0 on hyvä arvaus voimalle. Korjataan sitä vaijerin pituuden poikkeamaan
# verrannollisesti . Korjataan vielä nopeuteen verrannollisesti.
# Säätötekniikka on oma vaativa tieteenalansa, mutta kokeilemalla pääsee alkuun
# Ehkä kannattaisi tehdä säätimestä sellainen, että sitä voisi virittää
# on-line
# Säätimestä voisi tehdä sellaisenkin, että se pyrkii pitämään kuorman
# vakiokorkeudella.
def saato(self):
self.F = self.F0 - 5.0 * (self.Lr - self.L) + 2.0 * self.Lv
# self.F = min(1.5*self.F0, max(0.5*self.F0, F))
class Cl_opt:
def __init__(self):
with open('uopt.dat', 'r') as fil:
self.uu_opt = json.load(fil)
self.otim = -50
self.maxotim = len(self.uu_opt)
self.opti = False
vaunu.x = 3.0
vaunu.v = 0.0
vaunu.F = 0.0
kuorma.theta = 0.0 # vaijerin kulma
kuorma.w = 0.0 # vaijerin kulman muutosnopeus
kuorma.Lr = 8.0 # vaijerin pituuden asetusarvo
kuorma.L = kuorma.Lr # vaijerin pituus
kuorma.Lv = 0.0 # vaijerin pituuden muutosnopeus
kuorma.F = kuorma.F0
def optu(self):
if (self.otim <= self.maxotim + 10):
self.otim = self.otim + 1
if (self.otim >= 0 and self.otim < self.maxotim):
[F_vaunu, F_kuorma] = self.uu_opt[self.otim]
vaunu.F = F_vaunu
kuorma.F = F_kuorma
if (self.otim >= self.maxotim):
vaunu.F = 0.0
kuorma.F = kuorma.F0
if (self.otim > self.maxotim + 10):
self.opti = False
kuorma.Lr = kuorma.L
# Ohjelmoin yleispätevän Runge-Kutta algoritmin erilliseksi moduliksi rk4.py
# sille annetaan yhdeksi parametriksi järjestelmän tilamuuttujien derivaatat
# laskevan funktion nimi (dxdt). Systeemiyhtälöt johdetaan ja funktio dxdt
# generoidaan sympyllä.
def liike():
yy = [vaunu.v, kuorma.w, kuorma.Lv, vaunu.x, kuorma.theta, kuorma.L]
uu = [
vaunu.F, kuorma.F, vaunu.M, kuorma.M,
vaunu.Cf, kuorma.Cf, kuorma.Ckela
]
if (kuorma.lukittu):
uu[1] = Flukko(yy, uu)
kuorma.F = uu[1]
yy[2] = 0.9 * yy[2]
yy = rk4(fdxdt, yy, uu, Aika.Dt)
[vaunu.v, kuorma.w, kuorma.Lv, vaunu.x, kuorma.theta, kuorma.L] = yy
# ====================================================================
#
# Pääohjelma alkaa tästä
# ====================================================================
pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wxnaytto = D_Info.current_w
Wynaytto = D_Info.current_h
screen = pygame.display.set_mode((Wxnaytto, Wynaytto))
pygame.display.set_caption("Nosturi")
pygame.init()
clock = pygame.time.Clock()
Kamera = Cl_Kamera(Wxnaytto, Wynaytto)
Aika = cl_Ajat()
vaunu = Cl_vaunu()
kuorma = Cl_kuorma()
tyomaa = Cl_tyomaa([(2.5, -8.2), (14.5, -4.2)])
ohj = Cl_opt()
loppu = False
MuistaVanhat = False
Saato = False
font_ohj = pygame.font.SysFont('Arial', 22)
ohje = "nuolinäppäimet: liiku; s: korkeuden säätö; o: optimiohjaus; \
l: lukitse korkeus (aukeaa ylä/ala-nuolella); F1: näytä liikerata"
rend_ohje = font_ohj.render(ohje, True, (0, 255, 0), (0, 0, 0))
oix = int(0.2 * Wxnaytto)
oiy = int(0.9 * Wynaytto)
while not loppu:
vaunu.F = 0.0
if not MuistaVanhat:
screen.fill(Musta) # pyyhitään peli-ikkuna tyhjäksi
screen.blit(rend_ohje, (oix, oiy))
for event in pygame.event.get():
# Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
if event.type == pygame.QUIT:
loppu = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_F1:
MuistaVanhat = not MuistaVanhat
if event.key == pygame.K_s:
Saato = not Saato
if event.key == pygame.K_o:
Saato = False
kuorma.lukittu = False
ohj.opti = True
# ohj.init_opt()
if event.key == pygame.K_l:
kuorma.lukittu = True
Saato = False
if event.key == pygame.K_ESCAPE:
loppu = True
pressed = pygame.key.get_pressed()
if pressed[pygame.K_UP]:
kuorma.ylos()
if pressed[pygame.K_DOWN]:
kuorma.alas()
if pressed[pygame.K_LEFT]:
vaunu.taakse()
if pressed[pygame.K_RIGHT]:
vaunu.eteen()
vaunu.puskurit()
if Saato:
kuorma.lukittu = False
kuorma.saato()
if ohj.opti:
ohj.optu()
liike()
vaunu.piirra()
kuorma.piirra()
tyomaa.piirra()
pygame.display.flip()
clock.tick(Aika.Nayttotaajuus)
pygame.quit()