2018年6月9日 星期六

Kivy PongGame adaption

Lately I try to do some side projects and I found an app might be great. So I start to look for some Python libraries as a GUI solution. I compared Tkinter, PyQt, WxPython,  Kivy. I want it free, modern, and have good API reference. Tkinter looks old, PyQt is not free and API of PySide is somewhat difficult for me to read. WxPython is good, but I like the UI of Kivy more.

Therefore I have gone through the tutorials and Development guide, and also the PongGame one. Following the steps is really easy and it did not take much time to complete. As the author said, the app was the bare minimum to understand the application development in Kivy, so I made some improvements, mostly to make it more naturally, and I think share it would be interesting.

Here are the improvements I did:

  1. Instead of Touch event( hard to play by PC users), I try to read the API and use Window.KeyBoard class.
  2. Moving the paddle was not smooth and it can stuck when you change direction promptly.

Here it is:

pong.kv

#:kivy 1.0.9

#step2
<pongball>:
 size_x:self.size_x
 size_y:self.size_y
 size: self.size_x, self.size_y
 canvas:
  Ellipse:
   pos: self.pos
   size: self.size
<pongpaddle>:
 size: 25, 200
 canvas:
  Rectangle:
   pos:self.pos
   size:self.size

<ponggame>:
 #ball id here
 ball: pong_ball
 p1: player_left
 p2: player_right
 canvas:
  Rectangle:
   pos: self.center_x - 5, 0
   size: 5, self.height
 Label:
  font_size: 70
  center_x: root.width / 4
  top: root.top - 5
  text: str(root.p1.score)
 Label:
  font_size: 70
  center_x: root.width * 3 / 4
  top: root.top - 5
  text: str(root.p2.score)
 PongBall:
  id: pong_ball
  center: self.parent.center
 PongPaddle:
  id: player_left
  x: root.x
  center_y: root.center_y
 PongPaddle:
  id: player_right
  x: root.width - self.width
  center_y: root.center_y

PongGame.py

#Original by the author of Kivy- inclement, Adapted by iamlockon on 2018-06-09
#More PongGame information from the author:
#https://kivy.org/docs/tutorials/pong.html

from kivy.app import App 
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock 
from kivy.core.window import Window


class PongPaddle(Widget):
 score = NumericProperty(0)

 #setting paddle speed
 vel_paddle_y = NumericProperty(0)
 #track the key states with mask
 key_mask = NumericProperty(0)
 def bounce_ball(self, ball):
  if self.collide_widget(ball):
   vx, vy = ball.velocity
   offset = (ball.center_y - self.center_y) / (self.height / 2)
   bounced = Vector(-1 * vx, vy)
   vel = bounced *1.1
   ball.velocity = vel.x, vel.y + offset
 def move(self):
  #12,3: both pressed ; 0: both unpressed --&gt; stop
  if self.key_mask in (12, 3, 0):
   self.vel_paddle_y = 0
  #8,2: pressing up
  if self.key_mask in (8, 2):
   self.vel_paddle_y = 8
  #4,1: pressing down
  if self.key_mask in (4, 1):
   self.vel_paddle_y = -8
  self.center_y = self.vel_paddle_y + self.center_y
#step2
class PongBall(Widget):
 size_x = NumericProperty(50)
 size_y = NumericProperty(50)
 velocity_x = NumericProperty(0)
 velocity_y = NumericProperty(0)
 velocity = ReferenceListProperty(velocity_x, velocity_y)

 def move(self):
  self.pos = Vector(*self.velocity) + self.pos



class PongGame(Widget):
 ball = ObjectProperty(None)
 p1 = ObjectProperty(None)
 p2 = ObjectProperty(None)
 def __init__(self, **kwargs):
  super(PongGame, self).__init__(**kwargs)
  #Get a keyboard instance
  self._keyboard = Window.request_keyboard(
   self._keyboard_closed, self, 'text')
  if self._keyboard.widget:
   pass
  #Binding key events to keyboard
  self._keyboard.bind(on_key_down=self._on_keyboard_down)
  self._keyboard.bind(on_key_up=self._on_keyboard_up)
 def _keyboard_closed(self):
  print('My kb has been closed!')

 def _on_keyboard_down(self, keyboard, keycode, *args):
  '''
  Setting the key_mask of paddles(players) respectively.
  You might need some bitwise operation knowledge to understand this.
  'c' for player 1 up, 'v' for player 1 down.
  'n' for player 2 up, 'm' for player 2 down.
  bit:  4 3 2 1
  key:  c v n m
  ex:   0 1 0 0 -&gt; 4, player 1 goes down
  ex:   1 1 0 1 -&gt; 13, player 1 stops, player 2 moves down

  '''
  if keycode[1] == 'c':
   self.p1.key_mask = self.p1.key_mask | 8
   return True
  if keycode[1] == 'v':
   self.p1.key_mask = self.p1.key_mask | 4
   return True
  if keycode[1] == 'n':
   self.p2.key_mask = self.p2.key_mask | 2
   return True
  if keycode[1] == 'm':
   self.p2.key_mask = self.p2.key_mask | 1
   return True

 def _on_keyboard_up(self, keyboard, keycode):

  if keycode[1] == 'c':
   self.p1.key_mask = self.p1.key_mask &amp; 7
   return True
  if keycode[1] == 'v':
   self.p1.key_mask = self.p1.key_mask &amp; 11
   return True
  if keycode[1] == 'n':
   self.p2.key_mask = self.p2.key_mask &amp; 13
   return True
  if keycode[1] == 'm':
   self.p2.key_mask = self.p2.key_mask &amp; 14
   return True

 def serve_ball(self, vel=(4, 0)):
  self.ball.center = self.center
  self.ball.velocity = vel
 def update(self, dt):
  self.ball.move() 
  #go outside the fringes, pull it back 2 pixels to make sure it can still move.
  if self.p1.top &gt; self.top:
   self.p1.top = self.top-2
  if self.p1.pos[1] &lt; self.x:
   self.p1.pos[1] = self.x+2
  self.p1.move()
  if self.p2.top &gt; self.top:
   self.p2.top = self.top-2
  if self.p2.pos[1] &lt; self.x:
   self.p2.pos[1] = self.x+2
  self.p2.move()
  self.p1.bounce_ball(self.ball)
  self.p2.bounce_ball(self.ball)

  if (self.ball.y &lt; self.y) or (self.ball.top &gt; self.top):
   self.ball.velocity_y *= -1

  if self.ball.x &lt; self.x:
   self.p2.score += 1
   self.serve_ball(vel=(4,0))
  if self.ball.x &gt; self.width:
   self.p1.score += 1
   self.serve_ball(vel=(-4,0))

class PongApp(App):
 def build(self):
  game = PongGame()
  game.serve_ball()
  Clock.schedule_interval(game.update, 1.0 / 60.0)
  return game

if __name__ == "__main__":
 PongApp().run()