diff --git a/README.md b/README.md new file mode 100644 index 0000000..b052e4a --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Flappy Bird Automation Game + +This repository contains the Flappy Bird game modified to be controlled with ROS. + +## Game description + + +Flappy Bird is in trouble again! This time it went into space and landed in an asteroid belt. If FlappyBird collides with the rocks it is game over. Luckily Flappy remembered his laser scanner that provides distance measurements. It will give you its velocity and the laserscans in return for an acceleration input. FlappyBird only asks for 60 seconds of your guidance. Help FlappyBird go through as many asteroid lines as possible before the time runs out! + +![Flappy](flappy_cover.png) + +## Getting Started + +This game has been tested with Ubuntu 16.04 running ROS Kinetic and Python 2.7. + +There are two recommended options for running the game. Either download the VirtualBox image that comes with a complete Ubuntu 16.04 setup or add the packages provided from this repository to your own ROS workspace. + +### Option 1 - VirtualBox +First download VirtualBox from here [[VirtualBox link]](https://www.virtualbox.org/wiki/Downloads) and install it on your system. Then download the Ubuntu image that we have preconfigured with the game from here [[Image link]](https://www.virtualbox.org/wiki/Downloads). Once downloaded add the image to VirtualBox and boot up Ubuntu. +The username and password are both **FlyaTest** . + +### Option 2 - Adding as ROS package +If you already have Ubuntu you can install ROS and setup a workspace as covered here [[ROS install guide]](http://wiki.ros.org/ROS/Tutorials/InstallingandConfiguringROSEnvironment). +To add the automation game to your workspace clone the automation game repository to the source folder. + +If you followed the tutorial it will look like this, +``` +cd ~/catkin_ws/src/ +``` +Clone the automation game, +``` +git clone https://github.com/JohsBL/flappy_automation_test.git +``` +Make sure you have Pygame installed +``` +sudo apt-get install python-pygame +``` + +Then run the catkin_make command +``` +cd ~/catkin_ws/ +catkin_make +``` + +## Running the game + +To run the game we use the roslaunch command. There are two ROS launch files depending on if you want to run the game with C++ or python automation code (whatever you prefer). + +For python open a terminal and run, +``` +roslaunch flappy_automation_code flappy_automation_code_py.launch +``` +For C++ open a terminal and run, +``` +roslaunch flappy_automation_code flappy_automation_code_cpp.launch +``` +A GUI will become visible with the game start screen. For now the automation code does not do anything, so to start the game and move the bird press the arrow keys. Tapping the up key ↑ will start the game. You can then add velocity in a wanted direction by pressing the arrow keys ←↑↓→. Notice that it is not possible to go backwards and if hitting an obstacle the bird will crash. + +## Automate FlappyBird +Now that we have gotten familiar with the game we want to control FlappyBird. To do this a python and C++ template has been provided. + +### Modifying the code + +The templates are located in the **flappy_automation_code** folder. + +For using python modify the file **flappy_automation_code_node.py** in the **scripts** folder. + +For using C++ modify the files **flappy_automation_code.cpp** in the **src** folder and **flappy_automation_code.hpp** in the **include** folder. + +Take your pick. + +To get the state of the bird velocity readings and laserscans are published and an acceleration command can be given for actuating the FlappyBird. In the code callbacks for these topics and examples of how to read the returned messages are provided. + +### Build and run +Once the code has been modified run the catkin_make command again, +``` +cd ~/catkin_ws/ +catkin_make +``` + +Then launch the game as before. + +Python, +``` +roslaunch flappy_automation_code flappy_automation_code_py.launch +``` +C++, +``` +roslaunch flappy_automation_code flappy_automation_code_cpp.launch +``` + +### Other info +Scaling: 1 pixel = 0.01 meter +Game and sensor update rates: 30 fps +Max acceleration x: 0.1 m/s^2 +Max acceleration y: 2.0 m/s^2 +Axis convention: x →, y ↑ +[LaserScan message definition](http://docs.ros.org/api/sensor_msgs/html/msg/LaserScan.html) + +| Value | Unit | Topic | +| ------------- |:-------------:| :-----:| +| Velocity | m/s | /flappy_vel | +| Acceleration | m/s^2 | /flappy_acc | +| LaserScan | Radians, meters | /flappy_laser_scan | diff --git a/flappy_automation_code/CMakeLists.txt b/flappy_automation_code/CMakeLists.txt new file mode 100644 index 0000000..170cc3b --- /dev/null +++ b/flappy_automation_code/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 2.8.3) +project(flappy_automation_code) + +find_package(catkin REQUIRED COMPONENTS + roscpp + rospy + std_msgs +) + +catkin_package( + INCLUDE_DIRS include + CATKIN_DEPENDS roscpp rospy std_msgs +) + +include_directories( + include + ${catkin_INCLUDE_DIRS} +) + +add_executable(${PROJECT_NAME}_node src/flappy_automation_code.cpp) + +target_link_libraries(${PROJECT_NAME}_node + ${catkin_LIBRARIES} +) diff --git a/flappy_automation_code/include/flappy_automation_code/flappy_automation_code.hpp b/flappy_automation_code/include/flappy_automation_code/flappy_automation_code.hpp new file mode 100644 index 0000000..3d30399 --- /dev/null +++ b/flappy_automation_code/include/flappy_automation_code/flappy_automation_code.hpp @@ -0,0 +1,21 @@ +#ifndef FLAPPY_AUTOMATION_CODE_H_ +#define FLAPPY_AUTOMATION_CODE_H_ + +#include +#include "sensor_msgs/LaserScan.h" +#include "geometry_msgs/Vector3.h" + +//Ros nodehandle +ros::NodeHandle* nh_= NULL; +//Publisher for acceleration command +ros::Publisher pub_acc_cmd; +//Subscriber for velocity +ros::Subscriber sub_vel; +//Subscriber for laser scan +ros::Subscriber sub_laser_scan; + +void initNode(); +void velCallback(const geometry_msgs::Vector3::ConstPtr& msg); +void laserScanCallback(const sensor_msgs::LaserScan::ConstPtr& msg); + +#endif diff --git a/flappy_automation_code/launch/flappy_automation_code_cpp.launch b/flappy_automation_code/launch/flappy_automation_code_cpp.launch new file mode 100644 index 0000000..1cd4f68 --- /dev/null +++ b/flappy_automation_code/launch/flappy_automation_code_cpp.launch @@ -0,0 +1,6 @@ + + + + + + diff --git a/flappy_automation_code/launch/flappy_automation_code_py.launch b/flappy_automation_code/launch/flappy_automation_code_py.launch new file mode 100644 index 0000000..0d96e6f --- /dev/null +++ b/flappy_automation_code/launch/flappy_automation_code_py.launch @@ -0,0 +1,6 @@ + + + + + + diff --git a/flappy_automation_code/package.xml b/flappy_automation_code/package.xml new file mode 100644 index 0000000..8cb80a4 --- /dev/null +++ b/flappy_automation_code/package.xml @@ -0,0 +1,25 @@ + + + flappy_automation_code + 0.0.0 + The flappy_automation_code package + + johannes + + TODO + + catkin + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + + + + + diff --git a/flappy_automation_code/scripts/flappy_automation_code_node.py b/flappy_automation_code/scripts/flappy_automation_code_node.py new file mode 100755 index 0000000..8ccf551 --- /dev/null +++ b/flappy_automation_code/scripts/flappy_automation_code_node.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +import rospy +import numpy as np +from sensor_msgs.msg import LaserScan +from geometry_msgs.msg import Vector3 + +# Publisher for sending acceleration commands to flappy bird +pub_acc_cmd = rospy.Publisher('/flappy_acc', Vector3, queue_size=1) + +def initNode(): + # Here we initialize our node running the automation code + rospy.init_node('flappy_automation_code', anonymous=True) + + # Subscribe to topics for velocity and laser scan from Flappy Bird game + rospy.Subscriber("/flappy_vel", Vector3, velCallback) + rospy.Subscriber("/flappy_laser_scan", LaserScan, laserScanCallback) + + # Ros spin to prevent program from exiting + rospy.spin() + +def velCallback(msg): + # msg has the format of geometry_msgs::Vector3 + # Example of publishing acceleration command on velocity velCallback + x = 0 + y = 0 + pub_acc_cmd.publish(Vector3(x,y,0)) + +def laserScanCallback(msg): + # msg has the format of sensor_msgs::LaserScan + # print laser angle and range + print "Laser range: {}, angle: {}".format(msg.ranges[0], msg.angle_max) + +if __name__ == '__main__': + try: + initNode() + except rospy.ROSInterruptException: + pass diff --git a/flappy_automation_code/src/flappy_automation_code.cpp b/flappy_automation_code/src/flappy_automation_code.cpp new file mode 100644 index 0000000..c08f5e4 --- /dev/null +++ b/flappy_automation_code/src/flappy_automation_code.cpp @@ -0,0 +1,42 @@ +#include "ros/ros.h" +#include "flappy_automation_code/flappy_automation_code.hpp" +#include "sensor_msgs/LaserScan.h" +#include "geometry_msgs/Vector3.h" + +void initNode() +{ + //Initialization of nodehandle + nh_ = new ros::NodeHandle(); + //Init publishers and subscribers + pub_acc_cmd = nh_->advertise("/flappy_acc",1); + sub_vel = nh_->subscribe("/flappy_vel", 1, velCallback); + sub_laser_scan = nh_->subscribe("/flappy_laser_scan", 1, laserScanCallback); +} + +void velCallback(const geometry_msgs::Vector3::ConstPtr& msg) +{ + // msg has the format of geometry_msgs::Vector3 + // Example of publishing acceleration command on velocity velCallback + geometry_msgs::Vector3 acc_cmd; + + acc_cmd.x = 0; + acc_cmd.y = 0; + pub_acc_cmd.publish(acc_cmd); +} + +void laserScanCallback(const sensor_msgs::LaserScan::ConstPtr& msg) +{ + //msg has the format of sensor_msgs::LaserScan + //print laser angle and range + ROS_INFO("Laser range: %f, angle: %f", msg->ranges[0], msg->angle_max); +} + +int main(int argc, char **argv) +{ + ros::init(argc,argv,"flappy_automation_code"); + initNode(); + + // Ros spin to prevent program from exiting + ros::spin(); + return 0; +} diff --git a/flappy_cover.png b/flappy_cover.png new file mode 100644 index 0000000..87fb29c Binary files /dev/null and b/flappy_cover.png differ diff --git a/flappy_main_game/CMakeLists.txt b/flappy_main_game/CMakeLists.txt new file mode 100644 index 0000000..6704c12 --- /dev/null +++ b/flappy_main_game/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.8.3) +project(flappy_main_game) + +find_package(catkin REQUIRED COMPONENTS + roscpp + rospy + std_msgs +) +include_directories( + ${catkin_INCLUDE_DIRS} +) diff --git a/flappy_main_game/assets/audio/die.ogg b/flappy_main_game/assets/audio/die.ogg new file mode 100644 index 0000000..3d4f87c Binary files /dev/null and b/flappy_main_game/assets/audio/die.ogg differ diff --git a/flappy_main_game/assets/audio/die.wav b/flappy_main_game/assets/audio/die.wav new file mode 100644 index 0000000..9b79fbd Binary files /dev/null and b/flappy_main_game/assets/audio/die.wav differ diff --git a/flappy_main_game/assets/audio/hit.ogg b/flappy_main_game/assets/audio/hit.ogg new file mode 100644 index 0000000..72d821a Binary files /dev/null and b/flappy_main_game/assets/audio/hit.ogg differ diff --git a/flappy_main_game/assets/audio/hit.wav b/flappy_main_game/assets/audio/hit.wav new file mode 100644 index 0000000..9d9b77c Binary files /dev/null and b/flappy_main_game/assets/audio/hit.wav differ diff --git a/flappy_main_game/assets/audio/point.ogg b/flappy_main_game/assets/audio/point.ogg new file mode 100644 index 0000000..efb2d99 Binary files /dev/null and b/flappy_main_game/assets/audio/point.ogg differ diff --git a/flappy_main_game/assets/audio/point.wav b/flappy_main_game/assets/audio/point.wav new file mode 100644 index 0000000..9cf19fe Binary files /dev/null and b/flappy_main_game/assets/audio/point.wav differ diff --git a/flappy_main_game/assets/audio/swoosh.ogg b/flappy_main_game/assets/audio/swoosh.ogg new file mode 100644 index 0000000..f483cd6 Binary files /dev/null and b/flappy_main_game/assets/audio/swoosh.ogg differ diff --git a/flappy_main_game/assets/audio/swoosh.wav b/flappy_main_game/assets/audio/swoosh.wav new file mode 100644 index 0000000..bcae63e Binary files /dev/null and b/flappy_main_game/assets/audio/swoosh.wav differ diff --git a/flappy_main_game/assets/audio/wing.ogg b/flappy_main_game/assets/audio/wing.ogg new file mode 100644 index 0000000..76e3a2a Binary files /dev/null and b/flappy_main_game/assets/audio/wing.ogg differ diff --git a/flappy_main_game/assets/audio/wing.wav b/flappy_main_game/assets/audio/wing.wav new file mode 100644 index 0000000..9ae2c67 Binary files /dev/null and b/flappy_main_game/assets/audio/wing.wav differ diff --git a/flappy_main_game/assets/sprites/0.png b/flappy_main_game/assets/sprites/0.png new file mode 100644 index 0000000..4501590 Binary files /dev/null and b/flappy_main_game/assets/sprites/0.png differ diff --git a/flappy_main_game/assets/sprites/1.png b/flappy_main_game/assets/sprites/1.png new file mode 100644 index 0000000..469da94 Binary files /dev/null and b/flappy_main_game/assets/sprites/1.png differ diff --git a/flappy_main_game/assets/sprites/2.png b/flappy_main_game/assets/sprites/2.png new file mode 100644 index 0000000..c08acfc Binary files /dev/null and b/flappy_main_game/assets/sprites/2.png differ diff --git a/flappy_main_game/assets/sprites/3.png b/flappy_main_game/assets/sprites/3.png new file mode 100644 index 0000000..c5b3234 Binary files /dev/null and b/flappy_main_game/assets/sprites/3.png differ diff --git a/flappy_main_game/assets/sprites/4.png b/flappy_main_game/assets/sprites/4.png new file mode 100644 index 0000000..dd61841 Binary files /dev/null and b/flappy_main_game/assets/sprites/4.png differ diff --git a/flappy_main_game/assets/sprites/5.png b/flappy_main_game/assets/sprites/5.png new file mode 100644 index 0000000..3199558 Binary files /dev/null and b/flappy_main_game/assets/sprites/5.png differ diff --git a/flappy_main_game/assets/sprites/6.png b/flappy_main_game/assets/sprites/6.png new file mode 100644 index 0000000..26bfa8c Binary files /dev/null and b/flappy_main_game/assets/sprites/6.png differ diff --git a/flappy_main_game/assets/sprites/7.png b/flappy_main_game/assets/sprites/7.png new file mode 100644 index 0000000..ec88df4 Binary files /dev/null and b/flappy_main_game/assets/sprites/7.png differ diff --git a/flappy_main_game/assets/sprites/8.png b/flappy_main_game/assets/sprites/8.png new file mode 100644 index 0000000..cc714fb Binary files /dev/null and b/flappy_main_game/assets/sprites/8.png differ diff --git a/flappy_main_game/assets/sprites/9.png b/flappy_main_game/assets/sprites/9.png new file mode 100644 index 0000000..94b67d6 Binary files /dev/null and b/flappy_main_game/assets/sprites/9.png differ diff --git a/flappy_main_game/assets/sprites/background-night.png b/flappy_main_game/assets/sprites/background-night.png new file mode 100644 index 0000000..f16bd88 Binary files /dev/null and b/flappy_main_game/assets/sprites/background-night.png differ diff --git a/flappy_main_game/assets/sprites/base.png b/flappy_main_game/assets/sprites/base.png new file mode 100644 index 0000000..8443847 Binary files /dev/null and b/flappy_main_game/assets/sprites/base.png differ diff --git a/flappy_main_game/assets/sprites/gameover.png b/flappy_main_game/assets/sprites/gameover.png new file mode 100644 index 0000000..02c3bbb Binary files /dev/null and b/flappy_main_game/assets/sprites/gameover.png differ diff --git a/flappy_main_game/assets/sprites/message.png b/flappy_main_game/assets/sprites/message.png new file mode 100644 index 0000000..89debbc Binary files /dev/null and b/flappy_main_game/assets/sprites/message.png differ diff --git a/flappy_main_game/assets/sprites/pipe-red.png b/flappy_main_game/assets/sprites/pipe-red.png new file mode 100644 index 0000000..37c7363 Binary files /dev/null and b/flappy_main_game/assets/sprites/pipe-red.png differ diff --git a/flappy_main_game/assets/sprites/redbird-downflap.png b/flappy_main_game/assets/sprites/redbird-downflap.png new file mode 100644 index 0000000..21a6d72 Binary files /dev/null and b/flappy_main_game/assets/sprites/redbird-downflap.png differ diff --git a/flappy_main_game/assets/sprites/redbird-midflap.png b/flappy_main_game/assets/sprites/redbird-midflap.png new file mode 100644 index 0000000..cbd0c77 Binary files /dev/null and b/flappy_main_game/assets/sprites/redbird-midflap.png differ diff --git a/flappy_main_game/assets/sprites/redbird-upflap.png b/flappy_main_game/assets/sprites/redbird-upflap.png new file mode 100644 index 0000000..84a7548 Binary files /dev/null and b/flappy_main_game/assets/sprites/redbird-upflap.png differ diff --git a/flappy_main_game/package.xml b/flappy_main_game/package.xml new file mode 100644 index 0000000..9595854 --- /dev/null +++ b/flappy_main_game/package.xml @@ -0,0 +1,25 @@ + + + flappy_main_game + 0.0.0 + The flappy_main_game package + + johannes + + TODO + + catkin + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + + + + + diff --git a/flappy_main_game/scripts/flappy.py b/flappy_main_game/scripts/flappy.py new file mode 100755 index 0000000..418ac70 --- /dev/null +++ b/flappy_main_game/scripts/flappy.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python +from itertools import cycle +import random +import sys +from laser import Laser +import numpy as np +import pygame.surfarray as surfarray +import matplotlib.pyplot as plt +import pygame +from pygame.locals import * + +#rospy +import rospkg +import rospy +from geometry_msgs.msg import Vector3 + +DEBUG = 0 +FPS = 30 +SCREENWIDTH = 432 +SCREENHEIGHT = 512 + +# laser specs +LASERFOV = 90 +LASERRES = 9 +LASERHZ = 30 +ROTATING = 0 + +# scale pixels to meters +SCALING = 0.01 +ACCXLIMIT = 0.1 / SCALING +ACCYLIMIT = 2.0 / SCALING +VELLIMIT = 10.0 / (SCALING*FPS) + +PIPESPACING = 192 +# amount by which base can maximum shift to left +PIPEGAPSIZE = 50 # gap between upper and lower part of pipe +BASEY = SCREENHEIGHT * 0.79 +# image, sound and hitmask dicts +IMAGES, SOUNDS, HITMASKS = {}, {}, {} + +rospack = rospkg.RosPack() +PATHTOFLAPPY = rospack.get_path('flappy_main_game') + +# list of all possible players (tuple of 3 positions of flap) +PLAYERS_LIST = ( + # red bird + ( + PATHTOFLAPPY + '/assets/sprites/redbird-upflap.png', + PATHTOFLAPPY + '/assets/sprites/redbird-midflap.png', + PATHTOFLAPPY + '/assets/sprites/redbird-downflap.png', + ), +) + +# list of backgrounds +BACKGROUNDS_LIST = ( + PATHTOFLAPPY + '/assets/sprites/background-night.png', +) + +# list of pipes +PIPES_LIST = ( + PATHTOFLAPPY + '/assets/sprites/pipe-red.png', +) + +playerAccX = 0 +playerAccY = 0 + +try: + xrange +except NameError: + xrange = range + + +def main(): + # init ros node + rospy.init_node('main_game', anonymous=True) + #define subscribers + rospy.Subscriber("flappy_acc", Vector3, controlCallback) + #define shutdown + rospy.on_shutdown(shutdownHoook) + + global SCREEN, FPSCLOCK + pygame.init() + FPSCLOCK = pygame.time.Clock() + SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT)) + pygame.display.set_caption('Flappy Bird') + + # numbers sprites for score display + IMAGES['numbers'] = ( + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/0.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/1.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/2.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/3.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/4.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/5.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/6.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/7.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/8.png').convert_alpha(), + pygame.image.load(PATHTOFLAPPY + '/assets/sprites/9.png').convert_alpha() + ) + + # game over sprite + IMAGES['gameover'] = pygame.image.load(PATHTOFLAPPY + '/assets/sprites/gameover.png').convert_alpha() + # message sprite for welcome screen + IMAGES['message'] = pygame.image.load(PATHTOFLAPPY + '/assets/sprites/message.png').convert_alpha() + # base (ground) sprite + IMAGES['base'] = pygame.image.load(PATHTOFLAPPY + '/assets/sprites/base.png').convert_alpha() + + # sounds + if 'win' in sys.platform: + soundExt = '.wav' + else: + soundExt = '.ogg' + + SOUNDS['die'] = pygame.mixer.Sound(PATHTOFLAPPY + '/assets/audio/die' + soundExt) + SOUNDS['hit'] = pygame.mixer.Sound(PATHTOFLAPPY + '/assets/audio/hit' + soundExt) + SOUNDS['point'] = pygame.mixer.Sound(PATHTOFLAPPY + '/assets/audio/point' + soundExt) + SOUNDS['swoosh'] = pygame.mixer.Sound(PATHTOFLAPPY + '/assets/audio/swoosh' + soundExt) + SOUNDS['wing'] = pygame.mixer.Sound(PATHTOFLAPPY + '/assets/audio/wing' + soundExt) + + while True: + # select random background sprites + randBg = 0; + IMAGES['background'] = pygame.image.load(BACKGROUNDS_LIST[randBg]).convert() + + # select random player sprites + randPlayer = 0; + IMAGES['player'] = ( + pygame.image.load(PLAYERS_LIST[randPlayer][0]).convert_alpha(), + pygame.image.load(PLAYERS_LIST[randPlayer][1]).convert_alpha(), + pygame.image.load(PLAYERS_LIST[randPlayer][2]).convert_alpha(), + ) + + # select random pipe sprites + pipeindex = 0 + IMAGES['pipe'] = ( + pygame.transform.rotate( + pygame.image.load(PIPES_LIST[pipeindex]).convert_alpha(), 180), + pygame.image.load(PIPES_LIST[pipeindex]).convert_alpha(), + ) + + # hismask for pipes + HITMASKS['pipe'] = ( + getHitmask(IMAGES['pipe'][0]), + getHitmask(IMAGES['pipe'][1]), + ) + + # hitmask for player + HITMASKS['player'] = ( + getHitmask(IMAGES['player'][0]), + getHitmask(IMAGES['player'][1]), + getHitmask(IMAGES['player'][2]), + ) + + movementInfo = showWelcomeAnimation() + crashInfo = mainGame(movementInfo) + showGameOverScreen(crashInfo) + + +def showWelcomeAnimation(): + """Shows welcome screen animation of flappy bird""" + # index of player to blit on screen + playerIndex = 0 + playerIndexGen = cycle([0, 1, 2, 1]) + # iterator used to change playerIndex after every 5th iteration + loopIter = 0 + + playerx = int(SCREENWIDTH*0.13) + playery = int((SCREENHEIGHT - IMAGES['player'][0].get_height()) / 2) + + messagex = int((SCREENWIDTH - IMAGES['message'].get_width()) / 2) + messagey = int(SCREENHEIGHT * 0.12) + + basex = 0 + # amount by which base can maximum shift to left + baseShift = IMAGES['base'].get_width() - IMAGES['background'].get_width() + + # player shm for up-down motion on welcome screen + playerShmVals = {'val': 0, 'dir': 1} + + while True: + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + pygame.quit() + sys.exit() + if event.type == KEYDOWN and (event.key == K_UP): + # make first flap sound and return values for mainGame + #SOUNDS['wing'].play() + return { + 'playery': playery + playerShmVals['val'], + 'basex': basex, + 'playerIndexGen': playerIndexGen, + } + + # adjust playery, playerIndex, basex + if (loopIter + 1) % 5 == 0: + playerIndex = next(playerIndexGen) + loopIter = (loopIter + 1) % 30 + basex = -((-basex ) % baseShift) + playerShm(playerShmVals) + + # draw sprites + SCREEN.blit(IMAGES['background'], (0,0)) + SCREEN.blit(IMAGES['player'][playerIndex], + (playerx, playery + playerShmVals['val'])) + SCREEN.blit(IMAGES['message'], (messagex, messagey)) + SCREEN.blit(IMAGES['base'], (basex, BASEY)) + + pygame.display.update() + FPSCLOCK.tick(FPS) + + +def mainGame(movementInfo): + global playerAccX + global playerAccY + #define publishers + pub_velocity = rospy.Publisher('flappy_vel', Vector3, queue_size=10) + # create laser + laser = Laser(LASERFOV,LASERRES,LASERHZ,SCALING,ROTATING) + score = playerIndex = loopIter = 0 + playerIndexGen = movementInfo['playerIndexGen'] + playerx, playery = int(SCREENWIDTH * 0.13), movementInfo['playery'] + + #create timer and counter for timer countdown + pygame.time.set_timer(pygame.USEREVENT, 1000) + countdown = 60 + + # + basex = movementInfo['basex'] + baseShift = IMAGES['base'].get_width() - IMAGES['background'].get_width() + + # get 2 new pipes to add to upperPipes lowerPipes list + newPipe1 = getRandomPipe() + newPipe2 = getRandomPipe() + + # list of upper pipes + upperPipes = [ + {'x': SCREENWIDTH + 200, 'y': newPipe1[0]['y']}, + {'x': SCREENWIDTH + 200 + PIPESPACING, 'y': newPipe2[0]['y']}, + ] + + # list of lowerpipe + lowerPipes = [ + {'x': SCREENWIDTH + 200, 'y': newPipe1[1]['y']}, + {'x': SCREENWIDTH + 200 + PIPESPACING, 'y': newPipe2[1]['y']}, + ] + + # player velocity, max velocity, downward accleration, accleration on flap + pipeVelX = 0 + playerVelY = 0 # player's velocity along Y + deltaVel = 0.8 + betweenPipes = 0 + + while True: + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + pygame.quit() + sys.exit() + if event.type == KEYDOWN and (event.key == K_UP): + #if playery > -2 * IMAGES['player'][0].get_height(): + playerVelY -= deltaVel + #playerAccY -= deltaAcc + if event.type == KEYDOWN and (event.key == K_DOWN): + playerVelY += deltaVel + #playerAccY += deltaAcc + if event.type == KEYDOWN and (event.key == K_LEFT): + pipeVelX += deltaVel + #playerAccX += deltaAcc + if event.type == KEYDOWN and (event.key == K_RIGHT): + pipeVelX -= deltaVel + #playerAccX -= deltaAcc + if event.type == pygame.USEREVENT and score > 0: + if countdown > 0: + countdown -= 1 + else: + return { + 'y': playery, + 'groundCrash': crashTest[1], + 'basex': basex, + 'upperPipes': upperPipes, + 'lowerPipes': lowerPipes, + 'score': score, + 'playerVelY': playerVelY, + 'timeRanOut': 1, + } + + #update velocity + pipeVelX += playerAccX/FPS + playerVelY += playerAccY/FPS + + #limit velocity + playerVelY = limitVel(playerVelY,1) + pipeVelX = limitVel(pipeVelX,0) + + #publish pub_velocity + pub_velocity.publish(Vector3(-SCALING*FPS*pipeVelX,-SCALING*FPS*playerVelY,0)) + + # check for crash here + crashTest = checkCrash({'x': playerx, 'y': playery, 'index': playerIndex}, + upperPipes, lowerPipes) + if crashTest[0]: + return { + 'y': playery, + 'groundCrash': crashTest[1], + 'basex': basex, + 'upperPipes': upperPipes, + 'lowerPipes': lowerPipes, + 'score': score, + 'playerVelY': playerVelY, + 'timeRanOut': 0, + } + + # check for score + playerMidPos = playerx + IMAGES['player'][0].get_width() / 2 + pipeCounter = 0 + for pipe in upperPipes: + pipeMidPos = pipe['x'] + IMAGES['pipe'][0].get_width() / 2 + if pipeMidPos <= playerMidPos < (pipeMidPos + IMAGES['pipe'][0].get_width() / 2): + pipeCounter += 1 + if betweenPipes == 0: + score += 1 + betweenPipes = 1 + SOUNDS['point'].play() + if pipeCounter == 0: + betweenPipes = 0 + # playerIndex basex change + if (loopIter + 1) % 3 == 0: + playerIndex = next(playerIndexGen) + loopIter = (loopIter + 1) % 30 + basex = -((-basex - pipeVelX) % baseShift) + + playerHeight = IMAGES['player'][playerIndex].get_height() + playery += min(playerVelY, BASEY - playery - playerHeight) + + # move pipes to left + for uPipe, lPipe in zip(upperPipes, lowerPipes): + uPipe['x'] += pipeVelX + lPipe['x'] += pipeVelX + + # add new pipe when first pipe each ? pixels + if (SCREENWIDTH + 200) > upperPipes[-1]['x']: + newPipe = getRandomPipe() + newPipe[0]['x'] = PIPESPACING + upperPipes[-1]['x'] + newPipe[1]['x'] = PIPESPACING + upperPipes[-1]['x'] + upperPipes.append(newPipe[0]) + lowerPipes.append(newPipe[1]) + + # remove first pipe if its out of the screen + if upperPipes[0]['x'] < -IMAGES['pipe'][0].get_width(): + upperPipes.pop(0) + lowerPipes.pop(0) + + # draw sprites + SCREEN.blit(IMAGES['background'], (0,0)) + + for uPipe, lPipe in zip(upperPipes, lowerPipes): + SCREEN.blit(IMAGES['pipe'][0], (uPipe['x'], uPipe['y'])) + SCREEN.blit(IMAGES['pipe'][1], (lPipe['x'], lPipe['y'])) + + SCREEN.blit(IMAGES['base'], (basex, BASEY)) + ################################################################### + # get bitmap of obstacles + bitmap = getBitmap(upperPipes,lowerPipes,(basex, BASEY)) + # do raytracing with Laser + playerMiddle = (playerx + IMAGES['player'][0].get_width() / 2,playery + IMAGES['player'][0].get_height() / 2) + laserPoints = laser.scan(playerMiddle,bitmap,pygame.time.get_ticks()) + + # display + if DEBUG == 1: + # display obstacles and ray tracing + bitmap = pygame.surfarray.make_surface(bitmap) + SCREEN.blit(bitmap, (0, 0)) + + for i in range(0,len(laserPoints)): + if laserPoints[i][2] == 1: + pygame.draw.circle(SCREEN,(0,255,0),laserPoints[i][0:2],3,0) + pygame.draw.aaline(SCREEN,(0,255,0),playerMiddle,laserPoints[i][0:2],1) + else : + pygame.draw.aaline(SCREEN,(0,140,0),playerMiddle,laserPoints[i][0:2],1) + ################################################################### + + # print score so player overlaps the score + showScore(score) + if score > 0: + showCounter(countdown) + playerSurface = pygame.transform.rotate(IMAGES['player'][playerIndex], 0) + SCREEN.blit(playerSurface, (playerx, playery)) + + pygame.display.update() + FPSCLOCK.tick(FPS) + + +def showGameOverScreen(crashInfo): + """crashes the player down ans shows gameover image""" + score = crashInfo['score'] + playerx = SCREENWIDTH * 0.13 + playery = crashInfo['y'] + playerHeight = IMAGES['player'][0].get_height() + playerVelY = crashInfo['playerVelY'] + playerAccY = 2 + + basex = crashInfo['basex'] + + upperPipes, lowerPipes = crashInfo['upperPipes'], crashInfo['lowerPipes'] + + # play hit and die sounds + if not crashInfo['timeRanOut']: + SOUNDS['hit'].play() + if not crashInfo['groundCrash']: + SOUNDS['die'].play() + + while True: + for event in pygame.event.get(): + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + pygame.quit() + sys.exit() + if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP): + if playery + playerHeight >= BASEY - 1: + return + + # player y shift + if playery + playerHeight < BASEY - 1: + playery += min(playerVelY, BASEY - playery - playerHeight) + + # player velocity change + if playerVelY < 15: + playerVelY += playerAccY + + # draw sprites + SCREEN.blit(IMAGES['background'], (0,0)) + + for uPipe, lPipe in zip(upperPipes, lowerPipes): + SCREEN.blit(IMAGES['pipe'][0], (uPipe['x'], uPipe['y'])) + SCREEN.blit(IMAGES['pipe'][1], (lPipe['x'], lPipe['y'])) + + SCREEN.blit(IMAGES['base'], (basex, BASEY)) + showScore(score) + + playerSurface = pygame.transform.rotate(IMAGES['player'][1], -45) + SCREEN.blit(playerSurface, (playerx,playery)) + + FPSCLOCK.tick(FPS) + pygame.display.update() + + +def playerShm(playerShm): + """oscillates the value of playerShm['val'] between 8 and -8""" + if abs(playerShm['val']) == 8: + playerShm['dir'] *= -1 + + if playerShm['dir'] == 1: + playerShm['val'] += 1 + else: + playerShm['val'] -= 1 + + +def getRandomPipe(): + """returns a randomly generated pipe""" + # y of gap between upper and lower pipe + gapY = random.randrange(0, int(BASEY * 0.6 - PIPEGAPSIZE)) + gapY += int(BASEY * 0.2) + pipeHeight = IMAGES['pipe'][0].get_height() + pipeX = SCREENWIDTH + 10 + + return [ + {'x': pipeX, 'y': gapY - pipeHeight}, # upper pipe + {'x': pipeX, 'y': gapY + PIPEGAPSIZE}, # lower pipe + ] + + +def showScore(score): + """displays score in center of screen""" + scoreDigits = [int(x) for x in list(str(score))] + totalWidth = 0 # total width of all numbers to be printed + + for digit in scoreDigits: + totalWidth += IMAGES['numbers'][digit].get_width() + + Xoffset = (SCREENWIDTH - totalWidth) / 2 + + for digit in scoreDigits: + SCREEN.blit(IMAGES['numbers'][digit], (Xoffset, SCREENHEIGHT * 0.1)) + Xoffset += IMAGES['numbers'][digit].get_width() + +def showCounter(counter): + """displays score in center of screen""" + scoreDigits = [int(x) for x in list(str(counter))] + totalWidth = 0 # total width of all numbers to be printed + + for digit in scoreDigits: + totalWidth += IMAGES['numbers'][digit].get_width() + + Xoffset = (SCREENWIDTH - totalWidth) / 2 + + for digit in scoreDigits: + SCREEN.blit(IMAGES['numbers'][digit], (Xoffset, SCREENHEIGHT * 0.85)) + Xoffset += IMAGES['numbers'][digit].get_width() + +def checkCrash(player, upperPipes, lowerPipes): + """returns True if player collders with top, base or pipes.""" + pi = player['index'] + player['w'] = IMAGES['player'][0].get_width() + player['h'] = IMAGES['player'][0].get_height() + + # if player crashes into ground + if player['y'] + player['h'] >= BASEY - 1: + return [True, True] + elif player['y'] <= 0: + return [True, True] + else: + + playerRect = pygame.Rect(player['x'], player['y'], + player['w'], player['h']) + pipeW = IMAGES['pipe'][0].get_width() + pipeH = IMAGES['pipe'][0].get_height() + + for uPipe, lPipe in zip(upperPipes, lowerPipes): + # upper and lower pipe rects + uPipeRect = pygame.Rect(uPipe['x'], uPipe['y'], pipeW, pipeH) + lPipeRect = pygame.Rect(lPipe['x'], lPipe['y'], pipeW, pipeH) + + # player and upper/lower pipe hitmasks + pHitMask = HITMASKS['player'][pi] + uHitmask = HITMASKS['pipe'][0] + lHitmask = HITMASKS['pipe'][1] + + # if bird collided with upipe or lpipe + uCollide = pixelCollision(playerRect, uPipeRect, pHitMask, uHitmask) + lCollide = pixelCollision(playerRect, lPipeRect, pHitMask, lHitmask) + + if uCollide or lCollide: + return [True, False] + + return [False, False] + +def pixelCollision(rect1, rect2, hitmask1, hitmask2): + """Checks if two objects collide and not just their rects""" + rect = rect1.clip(rect2) + + if rect.width == 0 or rect.height == 0: + return False + + x1, y1 = rect.x - rect1.x, rect.y - rect1.y + x2, y2 = rect.x - rect2.x, rect.y - rect2.y + + for x in xrange(rect.width): + for y in xrange(rect.height): + if hitmask1[x1+x][y1+y] and hitmask2[x2+x][y2+y]: + return True + return False + +def getHitmask(image): + """returns a hitmask using an image's alpha.""" + mask = [] + for x in xrange(image.get_width()): + mask.append([]) + for y in xrange(image.get_height()): + mask[x].append(bool(image.get_at((x,y))[3])) + return mask + +def getBitmap(upperPipes,lowerPipes,base): + obstacleSurface = pygame.Surface((SCREENWIDTH, SCREENHEIGHT),flags=pygame.SRCALPHA) + obstacleSurface.fill((0,0,0,0)) + #Add obstacles + for uPipe, lPipe in zip(upperPipes, lowerPipes): + obstacleSurface.blit(IMAGES['pipe'][0], (uPipe['x'], uPipe['y'])) + obstacleSurface.blit(IMAGES['pipe'][1], (lPipe['x'], lPipe['y'])) + obstacleSurface.blit(IMAGES['base'], base) + + #Copy alpha values + #bitmap = pygame.mask.from_surface(obstacleSurface,255) + bitmap = surfarray.array_alpha(obstacleSurface) + return bitmap + +def controlCallback(data): + global playerAccX + global playerAccY + + playerAccX = limitAcceleration(-data.x/SCALING,ACCXLIMIT) + playerAccY = limitAcceleration(-data.y/SCALING,ACCYLIMIT) + #print(playerAccY) + +def limitAcceleration(accUser,limit): + if accUser > limit: + accUser = limit + elif accUser < -limit: + accUser = -limit + return accUser + +def limitVel(velUser,direction): + if velUser > VELLIMIT and direction == 1: + velUser = VELLIMIT + elif velUser > 0 and direction == 0: + velUser = 0 + elif velUser < -VELLIMIT: + velUser = -VELLIMIT + return velUser + +def shutdownHoook(): + print("Shutting flappy down!!!") + pygame.quit() + sys.exit() + +if __name__ == '__main__': + try: + main() + except rospy.ROSInterruptException: + pass diff --git a/flappy_main_game/scripts/laser.py b/flappy_main_game/scripts/laser.py new file mode 100644 index 0000000..45e8884 --- /dev/null +++ b/flappy_main_game/scripts/laser.py @@ -0,0 +1,122 @@ +import math +import rospy +from sensor_msgs.msg import LaserScan +from math import hypot +#from std.msg import Float32 + +class Laser: + def __init__(self,fov,resolution,hz,scaling,rotating): + self.fov = fov #degrees + self.angle_max = math.radians(fov/2.0) # radians + self.angle_min = -math.radians(fov/2.0) # radians + self.angle_increment = math.radians(fov/(resolution-1.0)) # radians + self.resolution = resolution + self.sampleTime = 1000*1.0/hz + self.range = 355 + self.time = 0 + self.laser_scan_publisher = rospy.Publisher("/flappy_laser_scan", LaserScan, queue_size=10) + self.scaling = scaling + # For rotating Lidar + self.rotating = rotating + self.ray_counter = 0 + + def scan(self,startPoint,bitmap,time): + # update with indicated hz + #time_diff = time-self.time + #if time_diff < self.sampleTime: + # return [] + #self.time = time-(time_diff % self.sampleTime) + pointcloud = [] + if self.rotating: + raysToCast = [self.ray_counter] + self.ray_counter += 1 + if self.ray_counter >= self.resolution: + self.ray_counter = 0 + else: + raysToCast = xrange(self.resolution) + + for i in raysToCast: + # calc endpoint from angle and range + angle = self.angle_max-(i*self.angle_increment) + endPoint = (int(math.cos(angle)*self.range + startPoint[0]), + int(-math.sin(angle)*self.range + startPoint[1])) + #endpoint + pointcloud.append(self._raycast(startPoint,endPoint,bitmap)) + #publish the scan + self._publish_laser_scan(pointcloud,startPoint) + return pointcloud + + def _raycast(self,(x0,y0),(x1,y1),bitmap): + x0 = int(x0) + y0 = int(y0) + x1 = int(x1) + y1 = int(y1) + #calculate end point from angle and bitmap + laserCollision = () + bitmapSize = bitmap.size + #bresenhams algorithm + dx = x1 - x0 + dy = y1 - y0 + + xsign = 1 if dx > 0 else -1 + ysign = 1 if dy > 0 else -1 + + dx = abs(dx) + dy = abs(dy) + + if dx > dy: + xx, xy, yx, yy = xsign, 0, 0, ysign + else: + dx, dy = dy, dx + xx, xy, yx, yy = 0, ysign, xsign, 0 + + D = 2*dy - dx + y = 0 + + for x in range(dx + 1): + pixelPos = (x0 + x*xx + y*yx, y0 + x*xy + y*yy) + # break if out of bounds + if not (0 <= pixelPos[0] < 432) or not (0 <= pixelPos[1] < 512): + return laserCollision + (1,) + # save pixelVal + pixelVal = bitmap[pixelPos[0]][pixelPos[1]] + #print pixelVal + if pixelVal > 127: + return laserCollision + (1,) + # save val + laserCollision = pixelPos + if D >= 0: + y += 1 + D -= 2*dx + D += 2*dy + return laserCollision + (0,) + + def _publish_laser_scan(self,pointcloud,startPoint): + scan = LaserScan() + scan.header.stamp = rospy.Time.now() + scan.header.frame_id = 'laser_frame' + + scan.range_min = 0.0 + scan.range_max = self.range*self.scaling + + if self.rotating: + scan.angle_min = self.angle_max-(self.ray_counter*self.angle_increment) + scan.angle_max = scan.angle_min + scan.angle_increment = 0 + else: + scan.angle_min = self.angle_min + scan.angle_max = self.angle_max + scan.angle_increment = self.angle_increment + + + scan.ranges = [] + scan.intensities = [] + for i in range(0,len(pointcloud)): + if pointcloud[i][2] == 1: + dist = math.hypot(pointcloud[i][0]-startPoint[0], pointcloud[i][1]-startPoint[1]) + scan.ranges.append(self.scaling*dist) + scan.intensities.append(pointcloud[i][2]) + else: + scan.ranges.append(scan.range_max) + scan.intensities.append(pointcloud[i][2]) + self.laser_scan_publisher.publish(scan) diff --git a/flappy_main_game/scripts/laser.pyc b/flappy_main_game/scripts/laser.pyc new file mode 100644 index 0000000..c3f9bed Binary files /dev/null and b/flappy_main_game/scripts/laser.pyc differ