#!/usr/bin/perl ############################################################################ # # tkballs.pl 1.1 - Object-oriented Animation of a bunch of Balls in Perl/Tk. # # Copyright (C) 2015, 2017, Hauke Lubenow, Germany. # # License: GNU GPL (version 3 or above): # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ############################################################################ use warnings; use strict; use Tk; package BallWindow { sub new { my $classname = shift; my $self = {screenx => 640, screeny => 480, speed => 5, running => 0}; return bless($self, $classname); } sub runApplication { my $self = shift; $self->initBalls(); $self->showWindow(); } sub initBalls { my $self = shift; $self->{balls} = [Ball->new(name => "Ball1", x => $self->toX(40), y => $self->toY(30), color => "red", direction => "11"), Ball->new(name => "Ball2", x => $self->toY(1.5), y => $self->toY(1.5), color => "blue"), Ball->new(name => "Ball3", x => $self->toX(2.5), y => $self->toY(2.5), color => "green"), Ball->new(name => "Ball4", x => $self->toX(2), y => $self->toY(10), color => "cyan"), Ball->new(name => "Ball5", x => $self->toX(5), y => $self->toY(5), color => "yellow")]; } sub toX { my $self = shift; return $self->{screenx} / shift; } sub toY { my $self = shift; return $self->{screeny} / shift; } sub showWindow { my $self = shift; $self->{mw} = MainWindow->new(); $self->{mw}->optionAdd("*font", "Arial 12 normal"); $self->{mw}->title("TkBalls"); my $geomstr = int($self->{screenx} * 1.25); $geomstr .= "x"; $geomstr .= int($self->{screeny} * 1.25); $geomstr .= "+108+64"; $self->{mw}->geometry($geomstr); $self->{mw}->bind('', sub { $self->{mw}->destroy() }); $self->{cv} = $self->{mw}->Canvas(bg => "white", width => $self->{screenx}, height => $self->{screeny}); $self->{cv}->pack(-padx => 20, -pady => 20); $self->{fr1} = $self->{mw}->Frame(); $self->{btn_start} = $self->{fr1}->Button(-text => "Start", -command => sub { $self->startMoving() }); $self->{btn_start}->focus(); $self->{btn_stop} = $self->{fr1}->Button(-text => "Stop", -command => sub { $self->stopMoving() }); $self->{btn_exit} = $self->{mw}->Button(-text => "Exit", -command => sub { $self->{mw}->destroy() }); $self->{btn_start}->pack(-side => "left", -padx => 50); $self->{btn_stop}->pack(-side => "left", -padx => 50); $self->{fr1}->pack(); $self->{btn_exit}->pack(-side => "right", -padx => 5, -pady => 5); $self->moveBalls(); $self->{mw}->MainLoop(); } sub moveBalls { my $self = shift; my @balls = @{$self->{balls}}; my $i; foreach $i (@balls) { $i->clearDirChange(); $self->checkCollisions(); $i->moveBall($self->{screenx}, $self->{screeny}); $self->{mw}->after($self->{speed}, sub { $self->drawBall($i) }); } if ($self->{running} == 1) { $self->{mw}->after($self->{speed}, sub { $self->moveBalls() }); } } sub startMoving { my $self = shift; if ($self->{running} == 1) { return; } $self->{running} = 1; $self->moveBalls(); } sub stopMoving { my $self = shift; $self->{running} = 0; } sub checkCollisions { my $self = shift; my @balls = @{$self->{balls}}; my $i; my $u; my @checkedballs = (); foreach $i (@balls) { foreach $u (@balls) { if ($i->getName() eq $u->getName()) { next; } if ($self->alreadyChecked($u, @checkedballs) == 1) { next; } $i->checkCollision($u, $self->{screenx}, $self->{screeny}); } push(@checkedballs, $i); } } sub alreadyChecked { my $self = shift; my $ball = shift; my @checkedballs = @_; my $i; foreach $i (@checkedballs) { if($ball->getName() eq $i->getName()) { return 1; } } return 0; } sub drawBall { my $self = shift; my $ball = shift; my $id = $ball->getId(); if ($id != -1) { $self->{cv}->delete($id); } my $x = $ball->getX(); my $y = $ball->getY(); my $color = $ball->getColor(); my $size = $ball->getSize(); $id = $self->{cv}->createOval($x - $size, $y - $size, $x + $size, $y + $size, -fill => $color); $ball->setId($id); } } package Ball { sub new { my $classname = shift; my $args = {@_}; my $self = {name => $args->{name} || "Ball1", x => $args->{x} || 0, y => $args->{y} || 0, color => $args->{color} || "black", size => $args->{size} || 15, id => $args->{id} || -1, direction => $args->{direction} || "xx", dirchanged => 0}; if ($self->{direction} eq "xx") { my @directions = ("00", "01", "10", "11"); $self->{direction} = $directions[int(rand(4))]; } return bless($self, $classname); }; sub getX { my $self = shift; return $self->{x}; } sub setX { my $self = shift; $self->{x} = shift; } sub getY { my $self = shift; return $self->{y}; } sub setY { my $self = shift; $self->{y} = shift; } sub getColor { my $self = shift; return $self->{color}; } sub getName { my $self = shift; return $self->{name}; } sub getId { my $self = shift; return $self->{id}; } sub getSize { my $self = shift; return $self->{size}; } sub setId { my $self = shift; $self->{id} = shift; } sub moveBall { my $self = shift; my $screenx = shift; my $screeny = shift; my $xdir = substr($self->{direction}, 0, 1); my $newxdir = $xdir; my $ydir = substr($self->{direction}, 1, 1); my $newydir = $ydir; if ($xdir == 0) { if ($self->{x} <= $self->{size}) { $newxdir = 1; } else { $self->{x}--; } } if ($xdir == 1) { if ($self->{x} >= $screenx - $self->{size}) { $newxdir = 0; } else { $self->{x}++; } } if ($ydir == 0) { if ($self->{y} <= $self->{size}) { $newydir = 1; } else { $self->{y}--; } } if ($ydir == 1) { if ($self->{y} >= $screeny - $self->{size}) { $newydir = 0; } else { $self->{y}++; } } $self->{direction} = "$newxdir$newydir"; } sub checkCollision { my $self = shift; if ($self->{dirchanged} == 1) { return; } my $otherball = shift; my $screenx = shift; my $screeny = shift; my $otherx = $otherball->getX(); my $othery = $otherball->getY(); my $othersize = $otherball->getSize(); if ($otherx - $othersize < $self->{x} + $self->{size} && $otherx + $othersize > $self->{x} - $self->{size} && $othery - $othersize < $self->{y} + $self->{size} && $othery + $othersize > $self->{y} - $self->{size}) { $self->changeDirection(); # If the balls are too close , they keep changing directions # and "stick" to each other. So at least on ball needs # to be moved away a bit: $self->moveBall($screenx, $screeny); $otherball->changeDirection(); } } sub changeDirection { my $self = shift; my $newdir = ""; if (substr($self->{direction}, 0, 1) eq "0") { $newdir .= "1"; } else { $newdir .= "0"; } if (substr($self->{direction}, 1, 1) eq "0") { $newdir .= "1"; } else { $newdir .= "0"; } $self->{direction} = $newdir; $self->{dirchanged} = 1; } sub clearDirChange { my $self = shift; $self->{dirchanged} = 0; } } my $bw = BallWindow->new(); $bw->runApplication();