Merge pull request '0.0.5' (#4) from 0.0.5 into main

Reviewed-on: #4
This commit is contained in:
2025-03-22 22:33:24 +01:00
64 changed files with 562 additions and 4216 deletions

1
.gitignore vendored
View File

@@ -168,3 +168,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
testing/

View File

@@ -1,4 +1,4 @@
![PyGame](./pygame_logo.png)
![SnakeGame](resources/gfx/snake_banner.png)
# Snake

View File

@@ -1,69 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Gamepad settings
gamepadType = Gamepad.PS4
buttonHappy = 'CROSS'
buttonBeep = 'CIRCLE'
buttonExit = 'PS'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
pollInterval = 0.1
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
global running
running = True
speed = 0.0
steering = 0.0
# Create some callback functions for single events
def happyButtonPressed():
print(':)')
def happyButtonReleased():
print(':(')
def exitButtonPressed():
global running
print('EXIT')
running = False
# Start the background updating
gamepad.startBackgroundUpdates()
# Register the callback functions
gamepad.addButtonPressedHandler(buttonHappy, happyButtonPressed)
gamepad.addButtonReleasedHandler(buttonHappy, happyButtonReleased)
gamepad.addButtonPressedHandler(buttonExit, exitButtonPressed)
# Joystick events handled in the background
try:
while running and gamepad.isConnected():
# Check if the beep button is held
if gamepad.isPressed(buttonBeep):
print('BEEP')
# Update the joystick positions
# Speed control (inverted)
speed = -gamepad.axis(joystickSpeed)
# Steering control (not inverted)
steering = gamepad.axis(joystickSteering)
print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))
# Sleep for our polling interval
time.sleep(pollInterval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()

View File

@@ -1,61 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Gamepad settings
gamepadType = Gamepad.PS4
buttonHappy = 'CROSS'
buttonBeep = 'CIRCLE'
buttonExit = 'PS'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
pollInterval = 0.1
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
speed = 0.0
steering = 0.0
# Start the background updating
gamepad.startBackgroundUpdates()
# Joystick events handled in the background
try:
while gamepad.isConnected():
# Check for the exit button
if gamepad.beenPressed(buttonExit):
print('EXIT')
break
# Check for happy button changes
if gamepad.beenPressed(buttonHappy):
print(':)')
if gamepad.beenReleased(buttonHappy):
print(':(')
# Check if the beep button is held
if gamepad.isPressed(buttonBeep):
print('BEEP')
# Update the joystick positions
# Speed control (inverted)
speed = -gamepad.axis(joystickSpeed)
# Steering control (not inverted)
steering = gamepad.axis(joystickSteering)
print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))
# Sleep for our polling interval
time.sleep(pollInterval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()

View File

@@ -1,428 +0,0 @@
# coding: utf-8
"""
Standard gamepad mappings.
Pulled in to Gamepad.py directly.
"""
class PS3(Gamepad):
fullName = 'PlayStation 3 controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'L2',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'R2'
}
self.buttonNames = {
0: 'CROSS',
1: 'CIRCLE',
2: 'TRIANGLE',
3: 'SQUARE',
4: 'L1',
5: 'R1',
6: 'L2',
7: 'R2',
8: 'SELECT',
9: 'START',
10: 'PS',
11: 'L3',
12: 'R3',
13: 'DPAD-UP',
14: 'DPAD-DOWN',
15: 'DPAD-LEFT',
16: 'DPAD-RIGHT'
}
self._setupReverseMaps()
# PS3 controller settings for older Raspbian versions
#class PS3(Gamepad):
# fullName = 'PlayStation 3 controller'
#
# def __init__(self, joystickNumber = 0):
# Gamepad.__init__(self, joystickNumber)
# self.axisNames = {
# 0: 'LEFT-X',
# 1: 'LEFT-Y',
# 2: 'RIGHT-X',
# 3: 'RIGHT-Y',
# 4: 'roll-1',
# 5: 'pitch',
# 6: 'roll-2',
# 8: 'DPAD-UP',
# 9: 'DPAD-RIGHT',
# 10: 'DPAD-DOWN',
# 11: 'DPAD-LEFT',
# 12: 'L2',
# 13: 'R2',
# 14: 'L1',
# 15: 'R1',
# 16: 'TRIANGLE',
# 17: 'CIRCLE',
# 18: 'CROSS',
# 19: 'SQUARE'
# }
# self.buttonNames = {
# 0: 'SELECT',
# 1: 'L3',
# 2: 'R3',
# 3: 'START',
# 4: 'DPAD-UP',
# 5: 'DPAD-RIGHT',
# 6: 'DPAD-DOWN',
# 7: 'DPAD-LEFT',
# 8: 'L2',
# 9: 'R2',
# 10: 'L1',
# 11: 'R1',
# 12: 'TRIANGLE',
# 13: 'CIRCLE',
# 14: 'CROSS',
# 15: 'SQUARE',
# 16: 'PS'
# }
# self._setupReverseMaps()
class PS4(Gamepad):
fullName = 'PlayStation 4 controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'L2',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'R2',
6: 'DPAD-X',
7: 'DPAD-Y'
}
self.buttonNames = {
0: 'CROSS',
1: 'CIRCLE',
2: 'TRIANGLE',
3: 'SQUARE',
4: 'L1',
5: 'R1',
6: 'L2',
7: 'R2',
8: 'SHARE',
9: 'OPTIONS',
10: 'PS',
11: 'L3',
12: 'R3'
}
self._setupReverseMaps()
# PS4 controller settings for older Raspbian versions
#class PS4(Gamepad):
# fullName = 'PlayStation 4 controller'
#
# def __init__(self, joystickNumber = 0):
# Gamepad.__init__(self, joystickNumber)
# self.axisNames = {
# 0: 'LEFT-X',
# 1: 'LEFT-Y',
# 2: 'RIGHT-X',
# 3: 'L2',
# 4: 'R2',
# 5: 'RIGHT-Y',
# 6: 'DPAD-X',
# 7: 'DPAD-Y'
# }
# self.buttonNames = {
# 0: 'SQUARE',
# 1: 'CROSS',
# 2: 'CIRCLE',
# 3: 'TRIANGLE',
# 4: 'L1',
# 5: 'R1',
# 6: 'L2',
# 7: 'R2',
# 8: 'SHARE',
# 9: 'OPTIONS',
# 10: 'L3',
# 11: 'R3',
# 12: 'PS',
# 13: 'PAD'
# }
# self._setupReverseMaps()
class PS5(Gamepad):
fullName = 'PlayStation 5 controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'L2',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'R2',
6: 'DPAD-X',
7: 'DPAD-Y'
}
self.buttonNames = {
0: 'CROSS',
1: 'CIRCLE',
2: 'TRIANGLE',
3: 'SQUARE',
4: 'L1',
5: 'R1',
6: 'L2',
7: 'R2',
8: 'SHARE',
9: 'OPTIONS',
10: 'PS',
11: 'L3',
12: 'R3'
}
self._setupReverseMaps()
# PS4 controller settings for older Raspbian versions
#class PS5(Gamepad):
# fullName = 'PlayStation 5 controller'
#
# def __init__(self, joystickNumber = 0):
# Gamepad.__init__(self, joystickNumber)
# self.axisNames = {
# 0: 'LEFT-X',
# 1: 'LEFT-Y',
# 2: 'RIGHT-X',
# 3: 'L2',
# 4: 'R2',
# 5: 'RIGHT-Y',
# 6: 'DPAD-X',
# 7: 'DPAD-Y'
# }
# self.buttonNames = {
# 0: 'SQUARE',
# 1: 'CROSS',
# 2: 'CIRCLE',
# 3: 'TRIANGLE',
# 4: 'L1',
# 5: 'R1',
# 6: 'L2',
# 7: 'R2',
# 8: 'SHARE',
# 9: 'OPTIONS',
# 10: 'L3',
# 11: 'R3',
# 12: 'PS',
# 13: 'PAD'
# }
# self._setupReverseMaps()
class Xbox360(Gamepad):
fullName = 'Xbox 360 controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'LT',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'RT'
}
self.buttonNames = {
0: 'A',
1: 'B',
2: 'X',
3: 'Y',
4: 'LB',
5: 'RB',
6: 'BACK',
7: 'START',
8: 'XBOX',
9: 'LA',
10: 'RA'
}
self._setupReverseMaps()
class XboxONE(Gamepad):
fullName = 'Xbox ONE controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LAS -X', #Left Analog Stick Left/Right
1: 'LAS -Y', #Left Analog Stick Up/Down
2: 'RAS -X', #Right Analog Stick Left/Right
3: 'RAS -Y', #Right Analog Stick Up/Down
4: 'RT', #Right Trigger
5: 'LT', #Left Trigger
6: 'DPAD -X', #D-Pad Left/Right
7: 'DPAD -Y' #D-Pad Up/Down
}
self.buttonNames = {
0: 'A', #A Button
1: 'B', #B Button
3: 'X', #X Button
4: 'Y', #Y Button
6: 'LB', #Left Bumper
7: 'RB', #Right Bumper
11: 'START', #Hamburger Button
12: 'HOME', #XBOX Button
13: 'LASB', #Left Analog Stick button
14: 'RASB' #Right Analog Stick button
}
self._setupReverseMaps()
class Steam(Gamepad):
fullName = 'Steam controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'AS -X', #Analog Stick Left/Right
1: 'AS -Y', #Analog Stick Up/Down
2: 'RTP -X', #Right Track Pad Left/Right
3: 'RTP -Y', #Right Track Pad Up/Down
4: 'LTP -Y', #Left Track Pad Up/Down
5: 'LTP -X', #Left Track Pad Left/Right
6: 'RTA', #Right Trigger Axis
7: 'LTA' #Left Trigger Axis
}
self.buttonNames = {
0: 'LPTBUTTON', #Left TrackPad button
1: 'RTPBUTTON', #Right TrackPad button
2: 'A', #A Button
3: 'B', #B Button
4: 'X', #X Button
5: 'Y', #Y Button
6: 'LB', #Left Bumper
7: 'RB', #Right Bumper
8: 'LT', #Left Trigger
9: 'RT', #Right Trigger
10: 'SELECT', #Select Button <
11: 'START', #Start button >
12: 'HOME', #Steam Button
13: 'STICKBUTTON', #Analog Stick button
15: 'LG', #Left Grip
16: 'RG', #Right Grip
17: 'LTP -DUP', #Left TrackPad D-PAD Up
18: 'LTP -DDOWN', #Left TrackPad D-PAD Down
19: 'LTP -DLEFT', #Left TrackPad D-PAD Left
20: 'LTP -DRIGHT', #Left TrackPad D-PAD Right
}
self._setupReverseMaps()
class MMP1251(Gamepad):
fullName = "ModMyPi Raspberry Pi Wireless USB Gamepad"
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'L2',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'R2',
6: 'DPAD-X',
7: 'DPAD-Y'
}
self.buttonNames = {
0: 'A',
1: 'B',
2: 'X',
3: 'Y',
4: 'L1',
5: 'R1',
6: 'SELECT',
7: 'START',
8: 'HOME',
9: 'L3',
10: 'R3'
}
self._setupReverseMaps()
class GameHat(Gamepad):
fullName = "WaveShare rpi GameHat "
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y'
}
self.buttonNames = {
0: 'A',
1: 'B',
2: 'X',
3: 'Y',
4: 'TR',
5: 'TL',
6: 'SELECT',
7: 'START'
}
self._setupReverseMaps()
class PG9099(Gamepad):
fullName = 'ipega PG-9099 Bluetooth Controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LAS -X', #Left Analog Stick Left/Right
1: 'LAS -Y', #Left Analog Stick Up/Down
2: 'RAS -X', #Right Analog Stick Left/Right
3: 'RAS -Y', #Right Analog Stick Up/Down
4: 'RT', #Right Trigger
5: 'LT', #Left Trigger
6: 'DPAD -X', #D-Pad Left/Right
7: 'DPAD -Y' #D-Pad Up/Down
}
self.buttonNames = {
0: 'A', #A Button
1: 'B', #B Button
3: 'X', #X Button
4: 'Y', #Y Button
6: 'LB', #Left Bumper
7: 'RB', #Right Bumper
10: 'SELECT', #Select Button
11: 'START', #Hamburger Button
13: 'LASB', #Left Analog Stick button
14: 'RASB' #Right Analog Stick button
}
self._setupReverseMaps()
class example(Gamepad):
# This class must have self.axisNames with a map
# of numbers to capitalised strings. Follow the
# conventions the other classes use for generic
# axes, make up your own names for axes unique
# to your device.
# self.buttonNames needs the same treatment.
# Use python Gamepad.py to get the event mappings.
fullName = 'Enter the human readable name of the device here'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'AXIS0',
1: 'AXIS1',
2: 'AXIS2'
}
self.buttonNames = {
0: 'BUTTON0',
1: 'BUTTON1',
2: 'BUTTON2'
}
self._setupReverseMaps()

View File

@@ -1,91 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Make our own custom gamepad
# The numbers can be figured out by running the Gamepad script:
# ./Gamepad.py
# Press ENTER without typing a name to get raw numbers for each
# button press or axis movement, press CTRL+C when done
class CustomGamepad(Gamepad.Gamepad):
def __init__(self, joystickNumber = 0):
Gamepad.Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'RIGHT-Y',
3: 'RIGHT-X',
4: 'DPAD-X',
5: 'DPAD-Y'
}
self.buttonNames = {
0: '1',
1: '2',
2: '3',
3: '4',
4: 'L1',
5: 'L2',
6: 'R1',
7: 'R2',
8: 'SELECT',
9: 'START',
10: 'L3',
11: 'R3'
}
self._setupReverseMaps()
# Gamepad settings
gamepadType = CustomGamepad
buttonHappy = '3'
buttonBeep = 'L3'
buttonExit = 'START'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
speed = 0.0
steering = 0.0
# Handle joystick updates one at a time
while gamepad.isConnected():
# Wait for the next event
eventType, control, value = gamepad.getNextEvent()
# Determine the type
if eventType == 'BUTTON':
# Button changed
if control == buttonHappy:
# Happy button (event on press and release)
if value:
print(':)')
else:
print(':(')
elif control == buttonBeep:
# Beep button (event on press)
if value:
print('BEEP')
elif control == buttonExit:
# Exit button (event on press)
if value:
print('EXIT')
break
elif eventType == 'AXIS':
# Joystick changed
if control == joystickSpeed:
# Speed control (inverted)
speed = -value
elif control == joystickSteering:
# Steering control (not inverted)
steering = value
print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 260 KiB

View File

@@ -1,120 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Gamepad settings
gamepadType = Gamepad.PS5
buttonHappy = 'CROSS'
buttonBeep = 'CIRCLE'
buttonExit = 'PS'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
dpadX = 'DPAD-X'
dpadY = 'DPAD-Y'
pollInterval = 0.2
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
global running
global beepOn
global speed
global steering
global direction
global dirChange
running = True
beepOn = False
speed = 0.0
steering = 0.0
direction = ""
dirChange = False
# Create some callback functions
def happyButtonPressed():
print(':)')
def happyButtonReleased():
print(':(')
def beepButtonChanged(isPressed):
global beepOn
beepOn = isPressed
def exitButtonPressed():
global running
print('EXIT')
running = False
def speedAxisMoved(position):
global speed
speed = -position # Inverted
def steeringAxisMoved(position):
global steering
steering = position # Non-inverted
def dpadXMoved(dir):
global direction
global dirChange
print("X direction: ", dir)
match dir:
case -1.0:
direction = 'LEFT'
dirChange = True
case 1.0:
direction = 'RIGHT'
dirChange = True
def dpadYMoved(dir):
global direction
global dirChange
print("Y direction: ", dir)
match dir:
case -1.0:
direction = 'UP'
dirChange = True
case 1.0:
direction = 'DOWN'
dirChange = True
# Start the background updating
gamepad.startBackgroundUpdates()
# Register the callback functions
gamepad.addButtonPressedHandler(buttonHappy, happyButtonPressed)
gamepad.addButtonReleasedHandler(buttonHappy, happyButtonReleased)
gamepad.addButtonChangedHandler(buttonBeep, beepButtonChanged)
gamepad.addButtonPressedHandler(buttonExit, exitButtonPressed)
gamepad.addAxisMovedHandler(joystickSpeed, speedAxisMoved)
gamepad.addAxisMovedHandler(joystickSteering, steeringAxisMoved)
gamepad.addAxisMovedHandler(dpadX, dpadXMoved)
gamepad.addAxisMovedHandler(dpadY, dpadYMoved)
# Keep running while joystick updates are handled by the callbacks
try:
while running and gamepad.isConnected():
# Show the current speed and steering
# print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))
# Display the beep if held
if beepOn:
print('BEEP')
if dirChange:
print(direction)
dirChange = False
# Sleep for our polling interval
time.sleep(pollInterval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()

View File

@@ -1,617 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
"""
This module is designed to read inputs from a gamepad or joystick.
See Controllers.py the names which can be used with specific gamepad types.
For basic use see the following examples:
AsyncExample.py - Updates read in the background.
EventExample.py - Updates passed to callback functions.
PollingExample.py - Reading updates one at a time.
AsyncAndEventExample.py - Mixing callbacks and background updates.
"""
import os
import sys
import struct
import time
import threading
import inspect
def available(joystickNumber = 0):
"""Check if a joystick is connected and ready to use."""
joystickPath = '/dev/input/js' + str(joystickNumber)
return os.path.exists(joystickPath)
class Gamepad:
EVENT_CODE_BUTTON = 0x01
EVENT_CODE_AXIS = 0x02
EVENT_CODE_INIT_BUTTON = 0x80 | EVENT_CODE_BUTTON
EVENT_CODE_INIT_AXIS = 0x80 | EVENT_CODE_AXIS
MIN_AXIS = -32767.0
MAX_AXIS = +32767.0
EVENT_BUTTON = 'BUTTON'
EVENT_AXIS = 'AXIS'
fullName = 'Generic (numbers only)'
class UpdateThread(threading.Thread):
"""Thread used to continually run the updateState function on a Gamepad in the background
One of these is created by the Gamepad startBackgroundUpdates function and closed by stopBackgroundUpdates"""
def __init__(self, gamepad):
threading.Thread.__init__(self)
if isinstance(gamepad, Gamepad):
self.gamepad = gamepad
else:
raise ValueError('Gamepad update thread was not created with a valid Gamepad object')
self.running = True
def run(self):
try:
while self.running:
self.gamepad.updateState()
self.gamepad = None
except:
self.running = False
self.gamepad = None
raise
def __init__(self, joystickNumber = 0):
self.joystickNumber = str(joystickNumber)
self.joystickPath = '/dev/input/js' + self.joystickNumber
retryCount = 5
while True:
try:
self.joystickFile = open(self.joystickPath, 'rb')
break
except IOError as e:
retryCount -= 1
if retryCount > 0:
time.sleep(0.5)
else:
raise IOError('Could not open gamepad %s: %s' % (self.joystickNumber, str(e)))
self.eventSize = struct.calcsize('IhBB')
self.pressedMap = {}
self.wasPressedMap = {}
self.wasReleasedMap = {}
self.axisMap = {}
self.buttonNames = {}
self.buttonIndex = {}
self.axisNames = {}
self.axisIndex = {}
self.lastTimestamp = 0
self.updateThread = None
self.connected = True
self.pressedEventMap = {}
self.releasedEventMap = {}
self.changedEventMap = {}
self.movedEventMap = {}
def __del__(self):
try:
self.joystickFile.close()
except AttributeError:
pass
def _setupReverseMaps(self):
for index in self.buttonNames:
self.buttonIndex[self.buttonNames[index]] = index
for index in self.axisNames:
self.axisIndex[self.axisNames[index]] = index
def _getNextEventRaw(self):
"""Returns the next raw event from the gamepad.
The return format is:
timestamp (ms), value, event type code, axis / button number
Throws an IOError if the gamepad is disconnected"""
if self.connected:
try:
rawEvent = self.joystickFile.read(self.eventSize)
except IOError as e:
self.connected = False
raise IOError('Gamepad %s disconnected: %s' % (self.joystickNumber, str(e)))
if rawEvent is None:
self.connected = False
raise IOError('Gamepad %s disconnected' % self.joystickNumber)
else:
return struct.unpack('IhBB', rawEvent)
else:
raise IOError('Gamepad has been disconnected')
def _rawEventToDescription(self, event):
"""Decodes the raw event from getNextEventRaw into a formatted string."""
timestamp, value, eventType, index = event
if eventType == Gamepad.EVENT_CODE_BUTTON:
if index in self.buttonNames:
button = self.buttonNames[index]
else:
button = str(index)
if value == 0:
return '%010u: Button %s released' % (timestamp, button)
elif value == 1:
return '%010u: button %s pressed' % (timestamp, button)
else:
return '%010u: button %s state %i' % (timestamp, button, value)
elif eventType == Gamepad.EVENT_CODE_AXIS:
if index in self.axisNames:
axis = self.axisNames[index]
else:
axis = str(index)
position = value / Gamepad.MAX_AXIS
return '%010u: Axis %s at %+06.1f %%' % (timestamp, axis, position * 100)
elif eventType == Gamepad.EVENT_CODE_INIT_BUTTON:
if index in self.buttonNames:
button = self.buttonNames[index]
else:
button = str(index)
if value == 0:
return '%010u: Button %s initially released' % (timestamp, button)
elif value == 1:
return '%010u: button %s initially pressed' % (timestamp, button)
else:
return '%010u: button %s initially state %i' % (timestamp, button, value)
elif eventType == Gamepad.EVENT_CODE_INIT_AXIS:
if index in self.axisNames:
axis = self.axisNames[index]
else:
axis = str(index)
position = value / Gamepad.MAX_AXIS
return '%010u: Axis %s initially at %+06.1f %%' % (timestamp, axis, position * 100)
else:
return '%010u: Unknown event %u, Index %u, Value %i' % (timestamp, eventType, index, value)
def getNextEvent(self, skipInit = True):
"""Returns the next event from the gamepad.
The return format is:
event name, entity name, value
For button events the event name is BUTTON and value is either True or False.
For axis events the event name is AXIS and value is between -1.0 and +1.0.
Names are string based when found in the button / axis decode map.
When not available the raw index is returned as an integer instead.
After each call the internal state used by getPressed and getAxis is updated.
Throws an IOError if the gamepad is disconnected"""
self.lastTimestamp, value, eventType, index = self._getNextEventRaw()
skip = False
eventName = None
entityName = None
finalValue = None
if eventType == Gamepad.EVENT_CODE_BUTTON:
eventName = Gamepad.EVENT_BUTTON
if index in self.buttonNames:
entityName = self.buttonNames[index]
else:
entityName = index
if value == 0:
finalValue = False
self.wasReleasedMap[index] = True
for callback in self.releasedEventMap[index]:
callback()
else:
finalValue = True
self.wasPressedMap[index] = True
for callback in self.pressedEventMap[index]:
callback()
self.pressedMap[index] = finalValue
for callback in self.changedEventMap[index]:
callback(finalValue)
elif eventType == Gamepad.EVENT_CODE_AXIS:
eventName = Gamepad.EVENT_AXIS
if index in self.axisNames:
entityName = self.axisNames[index]
else:
entityName = index
finalValue = value / Gamepad.MAX_AXIS
self.axisMap[index] = finalValue
for callback in self.movedEventMap[index]:
callback(finalValue)
elif eventType == Gamepad.EVENT_CODE_INIT_BUTTON:
eventName = Gamepad.EVENT_BUTTON
if index in self.buttonNames:
entityName = self.buttonNames[index]
else:
entityName = index
if value == 0:
finalValue = False
else:
finalValue = True
self.pressedMap[index] = finalValue
self.wasPressedMap[index] = False
self.wasReleasedMap[index] = False
self.pressedEventMap[index] = []
self.releasedEventMap[index] = []
self.changedEventMap[index] = []
skip = skipInit
elif eventType == Gamepad.EVENT_CODE_INIT_AXIS:
eventName = Gamepad.EVENT_AXIS
if index in self.axisNames:
entityName = self.axisNames[index]
else:
entityName = index
finalValue = value / Gamepad.MAX_AXIS
self.axisMap[index] = finalValue
self.movedEventMap[index] = []
skip = skipInit
else:
skip = True
if skip:
return self.getNextEvent()
else:
return eventName, entityName, finalValue
def updateState(self):
"""Updates the internal button and axis states with the next pending event.
This call waits for a new event if there are not any waiting to be processed."""
self.lastTimestamp, value, eventType, index = self._getNextEventRaw()
if eventType == Gamepad.EVENT_CODE_BUTTON:
if value == 0:
finalValue = False
self.wasReleasedMap[index] = True
for callback in self.releasedEventMap[index]:
callback()
else:
finalValue = True
self.wasPressedMap[index] = True
for callback in self.pressedEventMap[index]:
callback()
self.pressedMap[index] = finalValue
for callback in self.changedEventMap[index]:
callback(finalValue)
elif eventType == Gamepad.EVENT_CODE_AXIS:
finalValue = value / Gamepad.MAX_AXIS
self.axisMap[index] = finalValue
for callback in self.movedEventMap[index]:
callback(finalValue)
elif eventType == Gamepad.EVENT_CODE_INIT_BUTTON:
if value == 0:
finalValue = False
else:
finalValue = True
self.pressedMap[index] = finalValue
self.wasPressedMap[index] = False
self.wasReleasedMap[index] = False
self.pressedEventMap[index] = []
self.releasedEventMap[index] = []
self.changedEventMap[index] = []
elif eventType == Gamepad.EVENT_CODE_INIT_AXIS:
finalValue = value / Gamepad.MAX_AXIS
self.axisMap[index] = finalValue
self.movedEventMap[index] = []
def startBackgroundUpdates(self, waitForReady = True):
"""Starts a background thread which keeps the gamepad state updated automatically.
This allows for asynchronous gamepad updates and event callback code.
Do not use with getNextEvent"""
if self.updateThread is not None:
if self.updateThread.running:
raise RuntimeError('Called startBackgroundUpdates when the update thread is already running')
self.updateThread = Gamepad.UpdateThread(self)
self.updateThread.start()
if waitForReady:
while not self.isReady() and self.connected:
time.sleep(1.0)
def stopBackgroundUpdates(self):
"""Stops the background thread which keeps the gamepad state updated automatically.
This may be called even if the background thread was never started.
The thread will stop on the next event after this call was made."""
if self.updateThread is not None:
self.updateThread.running = False
def isReady(self):
"""Used with updateState to indicate that the gamepad is now ready for use.
This is usually after the first button press or stick movement."""
return len(self.axisMap) + len(self.pressedMap) > 1
def waitReady(self):
"""Convenience function which waits until the isReady call is True."""
self.updateState()
while not self.isReady() and self.connected:
time.sleep(1.0)
self.updateState()
def isPressed(self, buttonName):
"""Returns the last observed state of a gamepad button specified by name or index.
True if pressed, False if not pressed.
Status is updated by getNextEvent calls.
Throws ValueError if the button name or index cannot be found."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
return self.pressedMap[buttonIndex]
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def beenPressed(self, buttonName):
"""Returns True if the button specified by name or index has been pressed since the last beenPressed call.
Used in conjunction with updateState.
Throws ValueError if the button name or index cannot be found."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if self.wasPressedMap[buttonIndex]:
self.wasPressedMap[buttonIndex] = False
return True
else:
return False
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def beenReleased(self, buttonName):
"""Returns True if the button specified by name or index has been released since the last beenReleased call.
Used in conjunction with updateState.
Throws ValueError if the button name or index cannot be found."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if self.wasReleasedMap[buttonIndex]:
self.wasReleasedMap[buttonIndex] = False
return True
else:
return False
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def axis(self, axisName):
"""Returns the last observed state of a gamepad axis specified by name or index.
Throws a ValueError if the axis index is unavailable.
Status is updated by getNextEvent calls.
Throws ValueError if the button name or index cannot be found."""
try:
if axisName in self.axisIndex:
axisIndex = self.axisIndex[axisName]
else:
axisIndex = int(axisName)
return self.axisMap[axisIndex]
except KeyError:
raise ValueError('Axis %i was not found' % axisIndex)
except ValueError:
raise ValueError('Axis name %s was not found' % axisName)
def availableButtonNames(self):
"""Returns a list of available button names for this gamepad.
An empty list means that no button mapping has been provided."""
return self.buttonIndex.keys()
def availableAxisNames(self):
"""Returns a list of available axis names for this gamepad.
An empty list means that no axis mapping has been provided."""
return self.axisIndex.keys()
def isConnected(self):
"""Returns True until reading from the device fails."""
return self.connected
def addButtonPressedHandler(self, buttonName, callback):
"""Adds a callback for when a specific button specified by name or index is pressed.
This callback gets no parameters passed."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback not in self.pressedEventMap[buttonIndex]:
self.pressedEventMap[buttonIndex].append(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def removeButtonPressedHandler(self, buttonName, callback):
"""Removes a callback for when a specific button specified by name or index is pressed."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback in self.pressedEventMap[buttonIndex]:
self.pressedEventMap[buttonIndex].remove(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def addButtonReleasedHandler(self, buttonName, callback):
"""Adds a callback for when a specific button specified by name or index is released.
This callback gets no parameters passed."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback not in self.releasedEventMap[buttonIndex]:
self.releasedEventMap[buttonIndex].append(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def removeButtonReleasedHandler(self, buttonName, callback):
"""Removes a callback for when a specific button specified by name or index is released."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback in self.releasedEventMap[buttonIndex]:
self.releasedEventMap[buttonIndex].remove(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def addButtonChangedHandler(self, buttonName, callback):
"""Adds a callback for when a specific button specified by name or index changes.
This callback gets a boolean for the button pressed state."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback not in self.changedEventMap[buttonIndex]:
self.changedEventMap[buttonIndex].append(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def removeButtonChangedHandler(self, buttonName, callback):
"""Removes a callback for when a specific button specified by name or index changes."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback in self.changedEventMap[buttonIndex]:
self.changedEventMap[buttonIndex].remove(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def addAxisMovedHandler(self, axisName, callback):
"""Adds a callback for when a specific axis specified by name or index changes.
This callback gets the updated position of the axis."""
try:
if axisName in self.axisIndex:
axisIndex = self.axisIndex[axisName]
else:
axisIndex = int(axisName)
if callback not in self.movedEventMap[axisIndex]:
self.movedEventMap[axisIndex].append(callback)
except KeyError:
raise ValueError('Button %i was not found' % axisIndex)
except ValueError:
raise ValueError('Button name %s was not found' % axisName)
def removeAxisMovedHandler(self, axisName, callback):
"""Removes a callback for when a specific axis specified by name or index changes."""
try:
if axisName in self.axisIndex:
axisIndex = self.axisIndex[axisName]
else:
axisIndex = int(axisName)
if callback in self.movedEventMap[axisIndex]:
self.movedEventMap[axisIndex].remove(callback)
except KeyError:
raise ValueError('Button %i was not found' % axisIndex)
except ValueError:
raise ValueError('Button name %s was not found' % axisName)
def removeAllEventHandlers(self):
"""Removes all event handlers from all axes and buttons."""
for index in self.pressedEventMap.keys():
self.pressedEventMap[index] = []
self.releasedEventMap[index] = []
self.changedEventMap[index] = []
self.movedEventMap[index] = []
def disconnect(self):
"""Cleanly disconnect and remove any threads and event handlers."""
self.connected = False
self.removeAllEventHandlers()
self.stopBackgroundUpdates()
del self.joystickFile
###########################
# Import gamepad mappings #
###########################
scriptDir = os.path.dirname(os.path.realpath(__file__))
controllerScript = os.path.join(scriptDir, "Controllers.py")
exec(open(controllerScript).read())
# Generate a list of available gamepad types
moduleDict = globals()
classList = [moduleDict[a] for a in moduleDict.keys() if inspect.isclass(moduleDict[a])]
controllerDict = {}
deviceNames = []
for gamepad in classList:
controllerDict[gamepad.__name__.upper()] = gamepad
deviceNames.append(gamepad.__name__)
deviceNames.sort()
##################################################################
# When this script is run it provides testing code for a gamepad #
##################################################################
if __name__ == "__main__":
# Python 2/3 compatibility
try:
input = raw_input
except NameError:
pass
# ANSI colour code sequences
GREEN = '\033[0;32m'
CYAN = '\033[0;36m'
BLUE = '\033[1;34m'
RESET = '\033[0m'
# Ask for the gamepad to use
print('Gamepad axis and button events...')
print('Press CTRL+C to exit')
print('')
print('Available device names:')
formatString = ' ' + GREEN + '%s' + RESET + ' - ' + CYAN + '%s' + RESET
for device in deviceNames:
print(formatString % (device, controllerDict[device.upper()].fullName))
print('')
print('What device name are you using (leave blank if not in the list)')
device = input('? ' + GREEN).strip().upper()
print(RESET)
# Wait for a connection
if not available():
print('Please connect your gamepad...')
while not available():
time.sleep(1.0)
print('Gamepad connected')
# Pick the correct class
if device in controllerDict:
print(controllerDict[device].fullName)
gamepad = controllerDict[device]()
elif device == '':
print('Unspecified gamepad')
print('')
gamepad = Gamepad()
else:
print('Unknown gamepad')
print('')
sys.exit()
# Display the event messages as they arrive
while True:
eventType, index, value = gamepad.getNextEvent()
print(BLUE + eventType + RESET + ',\t ' +
GREEN + str(index) + RESET + ',\t' +
CYAN + str(value) + RESET)
time.sleep(1.0)

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Arron Churchill
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
print('Gamepad connected')
# Pick the gamepad type
#gamepad = Gamepad.Gamepad() #Generic unnamed controls
#gamepad = Gamepad.PS3()
gamepad = Gamepad.PS4()
# Show the selected gamepad type
print('Gamepad type: ' + gamepad.__class__.__name__)
# Display axis names
axisNames = gamepad.availableAxisNames()
if axisNames:
print('Available axis names:')
for axis in axisNames:
print(' ' + str(axis))
else:
print('No axis name mappings configured')
print('')
# Display button names
buttonNames = gamepad.availableButtonNames()
if buttonNames:
print('Available button names:')
for button in buttonNames:
print(' ' + str(button))
else:
print('No button name mappings configured')

View File

@@ -1,59 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Gamepad settings
gamepadType = Gamepad.PS4
buttonHappy = 'CROSS'
buttonBeep = 'CIRCLE'
buttonExit = 'PS'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
speed = 0.0
steering = 0.0
# Handle joystick updates one at a time
while gamepad.isConnected():
# Wait for the next event
eventType, control, value = gamepad.getNextEvent()
# Determine the type
if eventType == 'BUTTON':
# Button changed
if control == buttonHappy:
# Happy button (event on press and release)
if value:
print(':)')
else:
print(':(')
elif control == buttonBeep:
# Beep button (event on press)
if value:
print('BEEP')
elif control == buttonExit:
# Exit button (event on press)
if value:
print('EXIT')
break
elif eventType == 'AXIS':
# Joystick changed
if control == joystickSpeed:
# Speed control (inverted)
speed = -value
elif control == joystickSteering:
# Steering control (not inverted)
steering = value
print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))

View File

@@ -1,168 +0,0 @@
# PiBorg Gamepad Library
![Polling flowchart](Diagrams/gamepad-logo.svg)
The Gamepad library provides a simple way of getting inputs from joysticks, gamepads, and other game controllers.
Both buttons and axes / joysticks can be referenced by names as well as their raw index numbers. These can be easily customised and new controllers can easily be added as well.
It is designed and tested for the Raspberry Pi, but it should work with an Linux install and any device which shows up as a ```/dev/input/js?``` device.
Gamepad only uses imports which are part of the standard Python install and works with both Python 2 and 3.
Multiple controllers are also supported, just create more than one ```Gamepad``` instance with different joystick IDs.
See our other projects on GitHub [here](https://github.com/piborg) :)
# Installing the Library
```
cd ~
git clone https://github.com/piborg/Gamepad
```
That's it. The library does not need installing, it just needs to be downloaded.
# The Gamepad Library
The library provides three basic modes for getting updates from your controller:
1. Polling - we ask the library for the next controller update one at a time.
2. Asynchronous - the controller state is updated in the background.
3. Event - callbacks are made when the controller state changes.
See the examples further down for an explanation of how each mode is used.
The library itself is contained in just two scripts:
## ```Gamepad.py```
The main library, this script contains all of the code which reads input from the controller. It contains the ```Gamepad``` class which has all of the logic used to decode the joystick events and store the current state.
It works with both the raw axis / joystick and button numbers and easy to read names. It also contains the threading code needed to handle the background updating used in the asynchronous and event modes.
This script can be run directly using ```./Gamepad.py``` to check your controller mappings are correct or work out the mapping numbers for your own controller.
## ```Controllers.py```
This script contains the layouts for the different controllers. Each controller is represented by its own class inheriting from the main ```Gamepad``` class.
If the mapping is not right for you the layout for both the axis / joystick names and the button names by editing these classes. Adding your own controller is also simple, just make your own class based on the example at the bottom :)
Any button or axis without a name can still be used by the raw number if needed. This also means the ```Gamepad``` class can be used directly if you are only using the raw numbers.
This script is not run directly, instead it is read by ```Gamepad.py``` so that all of the devices are available when Gamepad is imported.
# Examples
## Polling mode - ```PollingExample.py```
The polling mode is probably the simplest and uses the Gamepad library to decode controller events one at a time.
![Polling flowchart](Diagrams/Polling.svg)
It works by repeatedly calling the ```getNextEvent()``` function to get the next update from the controller in the order they occurred. Each call returns three things:
1. The event type. This is either ```'BUTTON'``` for button presses or ```'AXIS'``` for axis / joystick movements.
2. The control which changed. This is a string with the name if available, or the index number if no name was available.
3. The new value for the control. For buttons this is ```True``` or ```False```, for axis / joystick movement it is a position between -1.0 and +1.0.
For example if the circle button on your controller was just pressed you would get ```'BUTTON', 'CIRCLE', True``` as the result.
Polling mode cannot be used at the same time as the asynchronous or event modes as they read the controller events for you.
## Asynchronous mode - ```AsyncExample.py```
Asynchronous mode works by reading the controller events in a background thread and updating the objects state to match the controller.
![Asynchronous flowchart](Diagrams/Asynchronous.svg)
It is started by calling the ```startBackgroundUpdates()``` function and should be stopped at the end of your script by calling the ```disconnect()``` function.
The current controller state can be queried using the following functions:
* ```isConnected()``` - check if the controller is still connected.
* ```isPressed(X)``` - check if a button is currently pressed.
* ```beenPressed(X)``` - see if a button was pressed since the last check. Only returns ```True``` once per press.
* ```beenReleased(X)``` - see if a button was released since the last check. Only returns ```True``` once per release.
* ```axis(X)``` - read the latest axis / joystick position. This is a float number between -1.0 and +1.0.
In all cases ```X``` can either be the string based name or the raw index number (e.g. ```'CIRCLE'``` or ```1```).
Asynchronous mode cannot be used at the same time as the polling mode as it reads the controller events for you, but it can be used with event mode.
## Event mode - ```EventExample.py```
Event mode works by reading the controller events in a background thread and calling functions in your script when changes occur.
![Event flowchart](Diagrams/Event.svg)
It is started by calling the ```startBackgroundUpdates()``` function and should be stopped at the end of your script by calling the ```disconnect()``` function. The ```isConnected()``` function will return ```False``` if the controller is disconnected while running.
Once started you can register your functions to controller events using these calls:
* ```addButtonPressedHandler(X, F)``` - called when a button is pressed, no values passed.
* ```addButtonReleasedHandler(X, F)``` - called when a button is released, no values passed.
* ```addButtonChangedHandler(X, F)``` - called when a button changes state, boolean passed with ```True``` for pressed or ```False``` for released.
* ```addAxisMovedHandler(X, F)``` - called when an axis / joystick is moved, a float number between -1.0 and +1.0 is passed.
In all cases ```X``` can either be the string based name or the raw index number (e.g. ```'CIRCLE'``` or ```1```). ```F``` is the function which gets called when the event occurs.
The same function can be registered with multiple events. You can also register multiple functions with the same event.
You can also remove an already registered event using these calls if needed:
* ```removeButtonPressedHandler(X, F)``` - removes a callback added by ```addButtonPressedHandler```.
* ```removeButtonReleasedHandler(X, F)``` - removes a callback added by ```addButtonReleasedHandler```.
* ```removeButtonChangedHandler(X, F)``` - removes a callback added by ```addButtonChangedHandler```.
* ```removeAxisMovedHandler(X, F)``` - removes a callback added by ```addAxisMovedHandler```.
* ```removeAllEventHandlers()``` - removes all added callbacks for this controller.
Event mode cannot be used at the same time as the polling mode as it reads the controller events for you, but it can be used with asynchronous mode.
## Asynchronous and event mode - ```AsyncAndEventExample.py```
This is not really a mode, but an example of how the asynchronous and event modes can be used at the same time. This is generally my preferred option as event mode is often easier to understand for button presses and asynchronous mode works well with axis / joystick movements.
![Asynchronous and event flowchart](Diagrams/Asynchronous-and-event.svg)
The example script here is really a hybrid between the ```AsyncExample.py``` and ```EventExample.py``` examples. Button callbacks are registered in the event style, then the loop only checks one button and the joystick positions.
In this style you are free to mix and match what you see as events and what you read the state of directly.
## Getting the available names - ```ListNames.py```
This example is just a helpful utility to print out all of the axis and button names for a controller type. You can change the controller type by looking for this line:
```
gamepad = Gamepad.PS4()
```
and changing ```PS4``` to any available device name.
The list of device names is shown when you run ```./Gamepad.py``` directly.
## Custom controller in your own script - ```CustomGamepadExample.py```
This example shows how you can create a controller mapping in your own script without changing ```Controllers.py```. This can be useful if you need to use different names in just one script, or if you want to keep all of your changes in your own code.
In this case you make your own class inheriting from ```Gamepad.Gamepad``` in the same way as they are written in ```Controllers.py```. You do not have to set the ```fullName``` value.
## RockyBorg example - ```rockyJoy.py```
Here we have an actual use of the Gamepad library, controlling a [RockyBorg](https://www.piborg.org/rockyborg-white) robot :)
It works exactly like the old Pygame version of the ```rbJoystick.py``` example script, but with the addition of a button to end the script.
The controller and button layout is all specified towards the top of the script and the standard [RockyBorg library](https://github.com/piborg/rockyborg) is used to control the motors and servo.
## MonsterBorg example - ```monsterJoy.py```
Here we have another real-world use of the Gamepad library, controlling a [MonsterBorg](https://www.piborg.org/monsterborg) robot :)
This provides the same control scheme we usually use for our robots, plus buttons to toggle the LEDs and end the script.
The controller and button layout is all specified towards the top of the script and the standard [ThunderBorg](https://www.piborg.org/thunderborg) library is used to control the motors.
# Using Gamepad in your own project
If you are using ```Gamepad``` in your own script it will need access to both the ```Gamepad.py``` and ```Controllers.py``` scripts. This can be done in a few ways:
1. Write your script in the Gamepad directory.
2. Copy the two scripts into your project's directory.
3. Add the Gamepad directory to your PYTHONPATH environment variable.
4. Add these lines to your script before importing the library: ```import sys``` and ```sys.path.insert(0, "/home/pi/Gamepad")```, where ```/home/pi/Gamepad``` is the Gamepad directory.
# Troubleshooting
For troubleshooting and further help please post questions on our [forum](http://forum.piborg.org/forum).
# Reporting Issues
If you find a bug with the RockyBorg code, please feel free to report it as an issue. When reporting issues with this library please fill in the issue report with as much information as you can to help us help you.
Please supply as much information as you can so we can replicate the bug locally. :)

View File

@@ -1,166 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# Load the libraries
import sys
import Gamepad
import time
sys.path.insert(0, "/home/pi/thunderborg")
import ThunderBorg
# Settings for the gamepad
gamepadType = Gamepad.PS4 # Class for the gamepad (e.g. Gamepad.PS3)
joystickSpeed = 'LEFT-Y' # Joystick axis to read for up / down position
joystickSpeedInverted = True # Set this to True if up and down appear to be swapped
joystickSteering = 'RIGHT-X' # Joystick axis to read for left / right position
joystickSteeringInverted = False # Set this to True if left and right appear to be swapped
buttonSlow = 'L2' # Joystick button for driving slowly whilst held
slowFactor = 0.5 # Speed to slow to when the drive slowly button is held, e.g. 0.5 would be half speed
buttonFastTurn = 'R2' # Joystick button for turning fast
buttonExit = 'PS' # Joystick button to end the program
buttonFullBeamToggle = 'CROSS' # Joystick button to toggle the LEDs between battery mode and fully white
interval = 0.05 # Time between motor updates in seconds, smaller responds faster but uses more processor time
# Power settings
voltageIn = 1.2 * 10 # Total battery voltage to the ThunderBorg
voltageOut = 12.0 * 0.95 # Maximum motor voltage, we limit it to 95% to allow the RPi to get uninterrupted power
# Setup the power limits
if voltageOut > voltageIn:
maxPower = 1.0
else:
maxPower = voltageOut / float(voltageIn)
# Setup the ThunderBorg
TB = ThunderBorg.ThunderBorg()
#TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address
TB.Init()
if not TB.foundChip:
boards = ThunderBorg.ScanForThunderBorg()
if len(boards) == 0:
print('No ThunderBorg found, check you are attached :)')
else:
print('No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress))
for board in boards:
print(' %02X (%d)' % (board, board))
print('If you need to change the I²C address change the setup line so it is correct, e.g.')
print('TB.i2cAddress = 0x%02X' % (boards[0]))
sys.exit()
# Ensure the communications failsafe has been enabled!
failsafe = False
for i in range(5):
TB.SetCommsFailsafe(True)
failsafe = TB.GetCommsFailsafe()
if failsafe:
break
if not failsafe:
print('Board %02X failed to report in failsafe mode!' % (TB.i2cAddress))
sys.exit()
# Show battery monitoring settings
battMin, battMax = TB.GetBatteryMonitoringLimits()
battCurrent = TB.GetBatteryReading()
print('Battery monitoring settings:')
print(' Minimum (red) %02.2f V' % (battMin))
print(' Half-way (yellow) %02.2f V' % ((battMin + battMax) / 2))
print(' Maximum (green) %02.2f V' % (battMax))
print('')
print(' Current voltage %02.2f V' % (battCurrent))
print('')
# Setup the state shared with callbacks
global running
global fullBeam
running = True
fullBeam = False
# Create the callback functions
def exitButtonPressed():
global running
print('EXIT')
running = False
def fullBeamButtonPressed():
global fullBeam
fullBeam = not fullBeam
if fullBeam:
TB.SetLedShowBattery(False)
TB.SetLeds(1, 1, 1)
else:
TB.SetLedShowBattery(True)
# Wait for a connection
TB.MotorsOff()
TB.SetLedShowBattery(False)
TB.SetLeds(0, 0, 1)
waitingToggle = True
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(interval * 4)
waitingToggle = not waitingToggle
if waitingToggle:
TB.SetLeds(0, 0, 1)
else:
TB.SetLeds(0, 0, 0)
print('Gamepad connected')
gamepad = gamepadType()
# Start the background updating
gamepad.startBackgroundUpdates()
TB.SetLedShowBattery(True)
# Register the callback functions
gamepad.addButtonPressedHandler(buttonExit, exitButtonPressed)
gamepad.addButtonPressedHandler(buttonFullBeamToggle, fullBeamButtonPressed)
# Keep running while joystick updates are handled by the callbacks
try:
while running and gamepad.isConnected():
# Read the latest speed and steering
if joystickSpeedInverted:
speed = -gamepad.axis(joystickSpeed)
else:
speed = +gamepad.axis(joystickSpeed)
if joystickSteeringInverted:
steering = -gamepad.axis(joystickSteering)
else:
steering = +gamepad.axis(joystickSteering)
# Work out the adjusted speed and steering
if gamepad.isPressed(buttonSlow):
finalSpeed = speed * slowFactor
else:
finalSpeed = speed
if gamepad.isPressed(buttonFastTurn):
finalSteering = steering * 2.0
else:
finalSteering = steering
# Determine the drive power levels
driveLeft = finalSpeed
driveRight = finalSpeed
if finalSteering < -0.05:
# Turning left
driveLeft *= 1.0 + finalSteering
elif finalSteering > +0.05:
# Turning right
driveRight *= 1.0 - finalSteering
# Set the motors to the new speeds
TB.SetMotor1(driveRight * maxPower)
TB.SetMotor2(driveLeft * maxPower)
# Sleep for our motor change interval
time.sleep(interval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()
# Turn the motors off
TB.MotorsOff()
# Set the LED to a dim red to indicate we have finished
TB.SetCommsFailsafe(False)
TB.SetLedShowBattery(False)
TB.SetLeds(0.2, 0, 0)

View File

@@ -1,131 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
# Load the libraries
import sys
import Gamepad
import time
sys.path.insert(0, "/home/pi/rockyborg")
import RockyBorg
# Settings for the gamepad
gamepadType = Gamepad.PS4 # Class for the gamepad (e.g. Gamepad.PS3)
joystickSpeed = 'LEFT-Y' # Joystick axis to read for up / down position
joystickSpeedInverted = True # Set this to True if up and down appear to be swapped
joystickSteering = 'RIGHT-X' # Joystick axis to read for left / right position
joystickSteeringInverted = False # Set this to True if left and right appear to be swapped
buttonSlow = 'L2' # Joystick button for driving slowly whilst held
slowFactor = 0.5 # Speed to slow to when the drive slowly button is held, e.g. 0.5 would be half speed
buttonExit = 'PS' # Joystick button to end the program
interval = 0.05 # Time between motor updates in seconds, smaller responds faster but uses more processor time
# Power settings
voltageIn = 1.2 * 8 # Total battery voltage to the RockyBorg
voltageOut = 6.0 # Maximum motor voltage
# Setup the power limits
if voltageOut > voltageIn:
maxPower = 1.0
else:
maxPower = voltageOut / float(voltageIn)
# Setup the RockyBorg
RB = RockyBorg.RockyBorg()
#RB.i2cAddress = 0x21 # Uncomment and change the value if you have changed the board address
RB.Init()
if not RB.foundChip:
boards = RockyBorg.ScanForRockyBorg()
if len(boards) == 0:
print('No RockyBorg found, check you are attached :)')
else:
print('No RockyBorg at address %02X, but we did find boards:' % (RB.i2cAddress))
for board in boards:
print(' %02X (%d)' % (board, board))
print('If you need to change the I²C address change the setup line so it is correct, e.g.')
print('RB.i2cAddress = 0x%02X' % (boards[0]))
sys.exit()
# Enable the motors and disable the failsafe
RB.SetCommsFailsafe(False)
RB.MotorsOff()
RB.SetMotorsEnabled(True)
# Setup the state shared with callbacks
global running
running = True
# Create the callback functions
def exitButtonPressed():
global running
print('EXIT')
running = False
# Wait for a connection
RB.MotorsOff()
RB.SetLed(True)
waitingToggle = True
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(interval * 4)
waitingToggle = not waitingToggle
if waitingToggle:
RB.SetLed(False)
else:
RB.SetLed(True)
print('Gamepad connected')
gamepad = gamepadType()
# Start the background updating
gamepad.startBackgroundUpdates()
RB.SetLed(True)
# Register the callback functions
gamepad.addButtonPressedHandler(buttonExit, exitButtonPressed)
# Keep running while joystick updates are handled by the callbacks
try:
while running and gamepad.isConnected():
# Read the latest speed and steering
if joystickSpeedInverted:
speed = -gamepad.axis(joystickSpeed)
else:
speed = +gamepad.axis(joystickSpeed)
if joystickSteeringInverted:
steering = -gamepad.axis(joystickSteering)
else:
steering = +gamepad.axis(joystickSteering)
# Work out the adjusted speed
if gamepad.isPressed(buttonSlow):
finalSpeed = speed * slowFactor
else:
finalSpeed = speed
# Determine the drive power levels based on steering angle
servoPosition = steering
driveLeft = finalSpeed
driveRight = finalSpeed
if steering < -0.05:
# Turning left
driveLeft *= 1.0 + (0.5 * steering)
elif steering > +0.05:
# Turning right
driveRight *= 1.0 - (0.5 * steering)
# Set the motors to the new speeds and tilt the servo to steer
RB.SetMotor1(-driveLeft * maxPower)
RB.SetMotor2(driveRight * maxPower)
RB.SetServoPosition(servoPosition)
# Sleep for our motor change interval
time.sleep(interval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()
# Turn the motors off
RB.MotorsOff()
# Turn the LED off indicate we have finished
RB.SetLed(False)

View File

@@ -1,3 +1,2 @@
from . import gamepad
from . import joystick
from . import controller
from . import keyboard

76
controls/controller.py Normal file
View File

@@ -0,0 +1,76 @@
import pygame
from controls.controlsbase import ControlsBase
class ControllerControls(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.power_level: str = ""
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def power_level(self) -> str:
return self._power_level
@power_level.setter
def power_level(self) -> None:
self._power_level = self.device.get_power_level()

View File

@@ -5,17 +5,5 @@ from abc import ABC, abstractmethod
class ControlsBase(ABC):
@abstractmethod
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
def handle_input(self, event):
pass

View File

@@ -1,25 +0,0 @@
"""
Class for grabbing gamepad signals
"""
from controls.controlsbase import ControlsBase
import pygame
import gamepad
class GamePadControls(ControlsBase):
def __init__(self):
gamapadType = gamepad.ps5
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass

View File

@@ -1,44 +0,0 @@
"""
Class for grabbing joystick signals
"""
from controls.controlsbase import ControlsBase
import pygame
import pyjoystick
from pyjoystick.pygame import Key, Joystick, run_event_loop
class JoystickControls(ControlsBase):
def __init__(self):
mngr = pyjoystick.ThreadEventManager(event_loop=run_event_loop,
handle_key_event=self.handle_key_event)
mngr.start()
def handle_key_event(self, key):
if key.keytype != Key.HAT:
if key.value != pygame.K_P:
return
if key.value == Key.HAT_UP:
self.up()
elif key.value == Key.HAT_DOWN:
self.down()
if key.value == Key.HAT_LEFT:
self.left()
elif key.value == Key.HAT_RIGHT:
self.right()
elif key.value == Key.KEY_P:
self.pause()
def left(self):
print("Joystick moved left")
def right(self):
print("Joystick moved right")
def up(self):
print("Joystick moved up")
def down(self):
print("Joystick moved down")
def pause(self):
print("Paused")

View File

@@ -7,6 +7,9 @@ class KeyboardControls(ControlsBase):
def __init__(self):
pass
def handle_input(self, event):
pass
def left(self):
pass
@@ -20,4 +23,7 @@ class KeyboardControls(ControlsBase):
pass
def pause(self):
pass
pass
def rumble(self):
return

BIN
gamepad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

BIN
joystick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
keyboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
logitech-t310.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

78
main.py
View File

@@ -1,8 +1,80 @@
import pygame
import time
import controls
from screens.startscreen import StartScreen
from tilemap.playground import PlayGround
if __name__ == "__main__":
pg = PlayGround(geometry = (1, 3))
class Snake:
def __init__(self):
self.STATES = {"starting": 0, "playing": 1, "pausing": 2, "ending": 3, "setting": 4, "highscore": 5}
self.running = True
self.numplayers: int = 1
self.playground: PlayGround = None
self.controls = None
self.players: list = []
self.windowed: bool = True
self.width: int = 800
self.height: int =600
self.startscreen = None
self.icon = pygame.image.load("snake.webp")
pygame.display.set_icon(self.icon)
self.set_state("starting")
self.set_windowed(self.width, self.height)
# self.set_fullscreen(self.width, self.height)
self.mainloop()
def mainloop(self):
while self.running:
match self.state:
case 0:
self.startscreen = StartScreen(self)
case 1:
pass
case 2:
pass
case 3:
pass
case 4:
pass
case 5:
pass
pygame.display.flip()
time.sleep(5)
self.running = False
pygame.quit()
def set_state(self, state: str) -> None:
self.state = self.STATES[state]
def set_windowed(self, width:int, height: int) -> None:
self.screen = pygame.display.set_mode((width, height))
self.windowed = True
pygame.display.set_caption("Snake")
def set_fullscreen(self, width: int, height: int) -> None:
self.screen = pygame.display.set_mode((width, height), flags=pygame.FULLSCREEN)
self.windowed = False
def get_width(self) -> int:
return self.width
@property
def numplayers(self) -> int:
return self._numplayers
@numplayers.setter
def numplayers(self, pl: int):
if pl <= 0 or pl >= 3:
raise ValueError("Numplayers must be a positive value of 1 or 2")
else:
self._numplayers = pl
def main():
pygame.init()
pass
snake = Snake()
if __name__ == "__main__":
main()

5
player/player.py Normal file
View File

@@ -0,0 +1,5 @@
class Player:
def __init__(self):
self.controls = None

BIN
ps5-dualsense.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

BIN
ps5-dualsense.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -0,0 +1,38 @@
1001Fonts Free For Personal Use License (FFP)
Preamble
In this license, 'Fun Blob' refers to the given .zip file, which may contain one or numerous fonts. These fonts can be of any type (.ttf, .otf, ...) and together they form a 'font family' or in short a 'typeface'.
1. Copyright
Fun Blob is the intellectual property of its respective author, provided it is original, and is protected by copyright laws in many parts of the world.
2. Personal Use
Fun Blob may be downloaded and used free of charge for personal use, as long as the usage is not racist or illegal. Personal use refers to all usage that does not generate financial income in a business manner, for instance:
- personal scrapbooking for yourself
- recreational websites and blogs for friends and family
- prints such as flyers, posters, t-shirts for churches, charities, and non-profit organizations
3. Commercial Use
Commercial use is not allowed without prior written permission from the respective author. Please contact the author to ask for commercial licensing. Commercial use refers to usage in a business environment, including:
- business cards, logos, advertising, websites, mobile apps for companies
- t-shirts, books, apparel that will be sold for money
- flyers, posters for events that charge admission
- freelance graphic design work
- anything that will generate direct or indirect income
4. Modification
Fun Blob may not be modified, altered, adapted or built upon without written permission by its respective author. This pertains all files within the downloadable font zip-file.
5. Conversion
Fun Blob may be converted to other formats such as WOFF, SVG or EOT webfonts, as long as the font is not modified in any other way, such as changing names or altering individual glyphs.
6. Distribution
While Fun Blob may freely be copied and passed along to other individuals for private use as its original downloadable zip-file, it may not be sold or published without written permission by its respective author.
7. Embedding
Fun Blob may be embedded into an application such as a web- or mobile app, as long as the application is of personal use and does not distribute Fun Blob, such as offering it as a download.
8. Disclaimer
Fun Blob is offered 'as is' without any warranty. 1001fonts.com and the respective author of Fun Blob shall not be liable for any damage derived from using this typeface. By using Fun Blob you agree to the terms of this license.

Binary file not shown.

BIN
resources/fonts/FunBlob.otf Normal file

Binary file not shown.

BIN
resources/fonts/FunBlob.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,34 @@
This font is a DEMO version of the font and the characters in it contain the standard version. Read the description below for more details.
By installing or using this font, you agree to the Product Use Agreement:
1. This font is for PERSONAL USE. No commercial use allowed!
2. If you want to use it for commercial purposes, you can download it via the following link and you will get the full version and complete characters:
https://teenagefoundry.com/product/teenage-popular/
3. If you don't mind, give me a cup of coffee to get even more excited!
Donate click here
paypal.me/teenagefoundry
4. For inquiries, you can contact me at: teenagefoundry@gmail.com
Thank you
Teenage Foundry
INDONESIAN VERSION:
1. Font demo ini hanya untuk penggunaan pribadi, tidak untuk komersial/yang menghasilkan profit
atau keuntungan dari hasil menggunakan font ini. Baik itu untuk Pribadi, Agensi Desain Grafis, Youtube, Tv, Percetakan, Perusahaan dll.
(APABILA ANDA MELANGGAR DAN MENGGUNAKAN TANPA MEMBELI LISENSI TERLEBIH DAHULU AKAN DIKENAKAN DENDA SEBESAR 10X LIPAT HARGA LISENSI SESUAI PENGGUNAAN).
2. Jika ingin menggunakan secara komersial silahkan membeli lisensi melalui link di bawah ini:
https://teenagefoundry.com/product/teenage-popular/
3. Untuk pertanyaan, Anda dapat menghubungi saya di: teenagefoundry@gmail.com
Terimakasih
Teenage Foundry

View File

@@ -0,0 +1,37 @@
This font is a DEMO version of the font and the characters in it contain the standard version. Read the description below for more details.
By installing or using this font, you agree to the Product Use Agreement:
1. This font is for PERSONAL USE. No commercial use allowed!
2. If you want to use it for commercial purposes, you can download it via the following link and you will get the full version and complete characters:
https://timurtype.com/product/chewy-noodles/
3. If you don't mind, give me a cup of coffee to get even more excited!
Donate click here
https://paypal.me/timurtypestd
4. Please visit our store for more amazing fonts :
https://timurtype.com
5. For inquiries, you can contact me at: timurtype.studio@gmail.com
Thank you
Timurtype Studio
INDONESIAN VERSION:
1. Font demo ini hanya untuk penggunaan pribadi, tidak untuk komersial/yang menghasilkan profit atau keuntungan dari hasil menggunakan font ini. Baik itu untuk Pribadi, Agensi Desain Grafis, Youtube, Tv, Percetakan, Perusahaan dll.
(APABILA ANDA MELANGGAR DAN MENGGUNAKAN TANPA MEMBELI LISENSI TERLEBIH DAHULU AKAN DIKENAKAN DENDA SEBESAR 10X LIPAT HARGA LISENSI SESUAI PENGGUNAAN).
2. Jika ingin menggunakan secara komersial silahkan membeli lisensi melalui link di bawah ini:
https://timurtype.com/product/chewy-noodles/
3. Untuk pertanyaan, Anda dapat menghubungi saya di: timurtype.studio@gmail.com
Terimakasih
Timurtype Studio

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View File

@@ -0,0 +1,5 @@
The fonts included in this archive are released under a <20>no rights reserved<65> Creative Commons Zero license. Please do not ask permission to do anything with these fonts. Whatever you want to do with this font, the answer will be yes. Please read about the CC0 Public Domain license before contacting me.
https://creativecommons.org/publicdomain/zero/1.0/
To the extent possible under law, Raymond Larabie has waived all copyright and related or neighboring rights to the fonts in this archive. This work is published from: Japan.

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

View File

0
screens/gamescreen.py Normal file
View File

View File

View File

0
screens/splitscreen.py Normal file
View File

11
screens/startscreen.py Normal file
View File

@@ -0,0 +1,11 @@
import pygame
class StartScreen:
def __init__(self, parent):
self.parent = parent
self.banner = pygame.image.load("resources/gfx/snake_banner.png")
self.banner_width = int(self.parent.get_width())
self.banner = pygame.transform.smoothscale(self.banner, (self.banner_width, int(self.banner_width*0.4)))
self.parent.screen.blit(self.banner, (0, 0))

BIN
snake.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

View File

@@ -1,5 +0,0 @@
import pygame
class StartScreen:
def __init__(self):
pass

119
testing/controls.py Normal file
View File

@@ -0,0 +1,119 @@
import pygame
from abc import ABC, abstractmethod
# Initialize pygame
pygame.init()
# Screen settings
WIDTH, HEIGHT = 600, 400
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Select Control Method")
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
# Font
font = pygame.font.Font(None, 36)
# Load images (replace with actual image paths)
def load_image(path, max_size):
image = pygame.image.load(path).convert_alpha() # Use convert_alpha for transparency
image_rect = image.get_rect()
scale_factor = min(max_size[0] / image_rect.width, max_size[1] / image_rect.height)
new_size = (int(image_rect.width * scale_factor), int(image_rect.height * scale_factor))
return pygame.transform.smoothscale(image, new_size)
keyboard_image = load_image("keyboard.png", (100, 100))
#joystick_image = load_image("ps5-dualsense.png", (100, 100))
joystick_image = load_image("gamepad.png", (100, 100))
# Abstract Control Class
class Control(ABC):
@abstractmethod
def handle_input(self, event):
pass
class KeyboardControl(Control):
def handle_input(self, event):
if event.type == pygame.KEYDOWN:
print("Keyboard key pressed")
class JoystickControl(Control):
def __init__(self, joystick_id):
self.joystick = pygame.joystick.Joystick(joystick_id)
self.joystick.init()
def handle_input(self, event):
if event.type == pygame.JOYBUTTONDOWN and event.joy == self.joystick.get_id():
print(f"Joystick {self.joystick.get_id()} button pressed")
# Options
options = ["Keyboard"]
option_images = {"Keyboard": keyboard_image}
pygame.joystick.init()
joystick_count = pygame.joystick.get_count()
joystick_controls = {}
for i in range(joystick_count):
joystick = pygame.joystick.Joystick(i)
joystick.init()
joystick_name = joystick.get_name()
options.append(joystick_name)
joystick_controls[joystick_name] = JoystickControl(i)
option_images[joystick_name] = joystick_image
selected_index = 0
# Control mapping
control_classes = {"Keyboard": KeyboardControl}
control_classes.update(joystick_controls)
selected_control = KeyboardControl()
running = True
while running:
screen.fill(WHITE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_DOWN and selected_index < len(options) - 1:
selected_index += 1
elif event.key == pygame.K_UP and selected_index > 0:
selected_index -= 1
elif event.key == pygame.K_RETURN:
selected_control = control_classes[options[selected_index]]() if options[selected_index] == "Keyboard" else joystick_controls[options[selected_index]]
elif event.type == pygame.MOUSEBUTTONDOWN:
x, y = event.pos
for i in range(len(options)):
if 100 <= x <= 300 and 100 + i * 40 <= y <= 140 + i * 40:
selected_index = i
selected_control = control_classes[options[selected_index]]() if options[selected_index] == "Keyboard" else joystick_controls[options[selected_index]]
break
# Pass input to selected control method
selected_control.handle_input(event)
# Render label
label = font.render("Controls:", True, BLACK)
screen.blit(label, (50, 50))
# Render list
for i, option in enumerate(options):
color = RED if i == selected_index else BLACK
text = font.render(option, True, color)
screen.blit(text, (100, 100 + i * 40))
# Render selected item image
selected_option = options[selected_index]
if selected_option in option_images:
image = option_images[selected_option]
image_rect = image.get_rect(center=(500, 200)) # Keep aspect ratio
screen.blit(image, image_rect.topleft)
pygame.display.flip()
pygame.quit()

151
testing/joystick.py Normal file
View File

@@ -0,0 +1,151 @@
import pygame
pygame.init()
# This is a simple class that will help us print to the screen.
# It has nothing to do with the joysticks, just outputting the
# information.
class TextPrint:
def __init__(self):
self.reset()
self.font = pygame.font.Font(None, 25)
def tprint(self, screen, text):
text_bitmap = self.font.render(text, True, (0, 0, 0))
screen.blit(text_bitmap, (self.x, self.y))
self.y += self.line_height
def reset(self):
self.x = 10
self.y = 10
self.line_height = 15
def indent(self):
self.x += 10
def unindent(self):
self.x -= 10
def main():
# Set the width and height of the screen (width, height), and name the window.
screen = pygame.display.set_mode((500, 700))
pygame.display.set_caption("Joystick example")
# Used to manage how fast the screen updates.
clock = pygame.time.Clock()
# Get ready to print.
text_print = TextPrint()
# This dict can be left as-is, since pygame will generate a
# pygame.JOYDEVICEADDED event for every joystick connected
# at the start of the program.
joysticks = {}
done = False
while not done:
# Event processing step.
# Possible joystick events: JOYAXISMOTION, JOYBALLMOTION, JOYBUTTONDOWN,
# JOYBUTTONUP, JOYHATMOTION, JOYDEVICEADDED, JOYDEVICEREMOVED
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True # Flag that we are done so we exit this loop.
if event.type == pygame.JOYBUTTONDOWN:
print("Joystick button pressed.")
if event.button == 0:
joystick = joysticks[event.instance_id]
if joystick.rumble(0, 0.7, 500):
print(f"Rumble effect played on joystick {event.instance_id}")
if event.type == pygame.JOYBUTTONUP:
print("Joystick button released.")
# Handle hotplugging
if event.type == pygame.JOYDEVICEADDED:
# This event will be generated when the program starts for every
# joystick, filling up the list without needing to create them manually.
joy = pygame.joystick.Joystick(event.device_index)
joysticks[joy.get_instance_id()] = joy
print(f"Joystick {joy.get_instance_id()} connencted")
if event.type == pygame.JOYDEVICEREMOVED:
del joysticks[event.instance_id]
print(f"Joystick {event.instance_id} disconnected")
# Drawing step
# First, clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
screen.fill((255, 255, 255))
text_print.reset()
# Get count of joysticks.
joystick_count = pygame.joystick.get_count()
text_print.tprint(screen, f"Number of joysticks: {joystick_count}")
text_print.indent()
# For each joystick:
for joystick in joysticks.values():
jid = joystick.get_instance_id()
text_print.tprint(screen, f"Joystick {jid}")
text_print.indent()
# Get the name from the OS for the controller/joystick.
name = joystick.get_name()
text_print.tprint(screen, f"Joystick name: {name}")
guid = joystick.get_guid()
text_print.tprint(screen, f"GUID: {guid}")
power_level = joystick.get_power_level()
text_print.tprint(screen, f"Joystick's power level: {power_level}")
# Usually axis run in pairs, up/down for one, and left/right for
# the other. Triggers count as axes.
axes = joystick.get_numaxes()
text_print.tprint(screen, f"Number of axes: {axes}")
text_print.indent()
for i in range(axes):
axis = joystick.get_axis(i)
text_print.tprint(screen, f"Axis {i} value: {axis:>6.3f}")
text_print.unindent()
buttons = joystick.get_numbuttons()
text_print.tprint(screen, f"Number of buttons: {buttons}")
text_print.indent()
for i in range(buttons):
button = joystick.get_button(i)
text_print.tprint(screen, f"Button {i:>2} value: {button}")
text_print.unindent()
hats = joystick.get_numhats()
text_print.tprint(screen, f"Number of hats: {hats}")
text_print.indent()
# Hat position. All or nothing for direction, not a float like
# get_axis(). Position is a tuple of int values (x, y).
for i in range(hats):
hat = joystick.get_hat(i)
text_print.tprint(screen, f"Hat {i} value: {str(hat)}")
text_print.unindent()
text_print.unindent()
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 30 frames per second.
clock.tick(30)
if __name__ == "__main__":
main()
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit()