Merge pull request '0.3.2' (#26) from 0.3.2 into main

Reviewed-on: #26
This commit is contained in:
2025-04-19 20:48:19 +02:00
14 changed files with 4 additions and 884 deletions

View File

@@ -1,5 +1,5 @@
import pygame import pygame
import pygameController as PC import pygameControls as PC
from actors.enums import Colors, PlayerDirection from actors.enums import Colors, PlayerDirection
from actors.pacman import ActorPacman from actors.pacman import ActorPacman
from actors.ghost import Blinky, Pinky, Inky, Clyde # adjust import path as needed from actors.ghost import Blinky, Pinky, Inky, Clyde # adjust import path as needed
@@ -7,7 +7,7 @@ from actors.ghost_mode_controller import GhostModeController
from hud import HUD from hud import HUD
from maze import Maze from maze import Maze
__version__ = "0.3.1" __version__ = "0.3.2"
def spawn_ghosts(center_position): def spawn_ghosts(center_position):

View File

@@ -1 +0,0 @@
from . import controller

View File

@@ -1,29 +0,0 @@
import pygame
from .controlsbase import ControlsBase
from .dualsense_controller import DualSenseController
from .dualsense_edge_controller import DualSenseEdgeController
from .logitech_f310_controller import LogitechF310Controller
from .logitech_f510_controller import LogitechF510Controller
from .logitech_f710_controller import LogitechF710Controller
from .xbox_series_x_controller import XboxSeriesXController
from .generic_controller import GenericController
from .logitech_dual_action_controller import LogitechDualActionController
CONTROLLERS = {
"DualSense Wireless Controller": DualSenseController,
"DualSense Edge Wireless Controller": DualSenseEdgeController,
"Logitech Gamepad F310": LogitechF310Controller,
"Logitech Gamepad F510": LogitechF510Controller,
"Logitech Gamepad F710": LogitechF710Controller,
"Logitech Dual Action": LogitechDualActionController,
"X box Series X Controller": XboxSeriesXController
}
class Controllers:
def __init__(self, joy):
self.controllers = []
if not joy.get_name() in CONTROLLERS:
self.controllers.append(GenericController(joy))
else:
self.controllers.append(CONTROLLERS[joy.get_name()](joy))

View File

@@ -1,33 +0,0 @@
"""
This is an abstract baseclass for the controls of snake.
"""
from abc import ABC, abstractmethod
class ControlsBase(ABC):
@abstractmethod
def handle_input(self, event):
pass
@abstractmethod
def left(self):
pass
@abstractmethod
def right(self):
pass
@abstractmethod
def up(self):
pass
@abstractmethod
def down(self):
pass
@abstractmethod
def pause(self):
pass
@abstractmethod
def rumble(self):
pass

View File

@@ -1,70 +0,0 @@
import os
import time
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
class DualSenseAudio:
def __init__(self):
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_device = self._detect_dualsense()
def _get_alsa_devices(self):
try:
cards = alsaaudio.cards()
return cards
except Exception as e:
print("ALSA detection failed:", e)
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
sinks = pulse.sink_list()
return sinks
except Exception as e:
print("PulseAudio detection failed:", e)
return []
def _detect_dualsense(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_device:
print("DualSense speaker not found.")
return
print(f"Playing tone on DualSense ({self.dualsense_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
if self.dualsense_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_device['name'])
elif self.dualsense_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
def list_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")

View File

@@ -1,74 +0,0 @@
from pygameController.controlsbase import ControlsBase
from pydualsense import *
BATTERY_STATE = {
"0": "Discharging",
"1": "Charging",
"2": "Full",
"11": "Not charging",
"15": "Error",
"10": "Temp or voltage out of range"
}
class DualSenseController(ControlsBase):
def __init__(self, joy):
self.device = pydualsense()
self.device.init()
self.name = self.device.device.get_product_string()
self.powerlevel = self.device.battery.Level
self.batterystate = BATTERY_STATE[str(self.device.battery.State)]
self.set_player_id(PlayerID.PLAYER_1)
print(f"{self.name} connected")
print(f"Power level: {self.powerlevel}")
print(f"Battery state: {self.batterystate}")
def handle_input(self, event):
pass
def set_led(self, red: int, green: int, blue: int):
self.device.light.setColorI(red, green, blue)
def set_player_id(self, playerid: PlayerID):
self.device.light.setPlayerID(playerid)
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 powerlevel(self) -> str:
return self._powerlevel
@powerlevel.setter
def powerlevel(self, lvl: str) -> None:
self._powerlevel = lvl
@property
def batterystate(self) -> int:
return self._batterystate
@batterystate.setter
def batterystate(self, state) -> None:
self._batterystate = state

View File

@@ -1,199 +0,0 @@
import time
import threading
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
from pydualsense import *
class DualSenseEdgeController:
def __init__(self):
# DualSense input/output interface
self.ds = pydualsense()
self.ds.init()
self._listening = False
self._bindings = {}
# Audio detection
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_audio_device = self._detect_dualsense_audio()
print("DualSense initialized.")
# ---------------------- Device Controls ----------------------
def set_rumble(self, small_motor: int, big_motor: int):
self.ds.setRumble(small_motor, big_motor)
def stop_rumble(self):
self.set_rumble(0, 0)
def set_led_color(self, r: int, g: int, b: int):
self.ds.setLightBarColor(r, g, b)
def set_trigger_effects(self, left_mode='Off', right_mode='Off', force=0):
left = getattr(TriggerModes, left_mode.upper(), TriggerModes.Off)
right = getattr(TriggerModes, right_mode.upper(), TriggerModes.Off)
self.ds.triggerL.setMode(left)
self.ds.triggerR.setMode(right)
if force > 0:
self.ds.triggerL.setForce(force)
self.ds.triggerR.setForce(force)
# ---------------------- Predefined Rumble Patterns ----------------------
def rumble_pattern(self, pattern: str, duration: float = 1.0):
patterns = {
"pulse": self._pulse_rumble,
"heartbeat": self._heartbeat_rumble,
"buzz": self._buzz_rumble,
"wave": self._wave_rumble,
"alarm": self._alarm_rumble,
}
if pattern in patterns:
threading.Thread(target=patterns[pattern], args=(duration,), daemon=True).start()
else:
print(f"Unknown rumble pattern: {pattern}")
def _pulse_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(50, 150)
time.sleep(0.2)
self.stop_rumble()
time.sleep(0.2)
def _heartbeat_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(200, 200)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.1)
self.set_rumble(100, 100)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.4)
def _buzz_rumble(self, duration):
self.set_rumble(80, 255)
time.sleep(duration)
self.stop_rumble()
def _wave_rumble(self, duration):
start = time.time()
while time.time() - start < duration:
for i in range(0, 256, 25):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
for i in reversed(range(0, 256, 25)):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
self.stop_rumble()
def _alarm_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(255, 0)
time.sleep(0.1)
self.set_rumble(0, 255)
time.sleep(0.1)
self.stop_rumble()
# ---------------------- Input Listener + Bindings ----------------------
def bind(self, button: str, action: callable):
"""Bind a button to a callable. Ex: controller.bind('cross', lambda: rumble_pattern('buzz'))"""
self._bindings[button] = action
def start_input_listener(self):
def listen():
while self._listening:
#self.ds.update()
for button, action in self._bindings.items():
if getattr(self.ds, button, False):
action()
self._listening = True
thread = threading.Thread(target=listen, daemon=True)
thread.start()
def stop_input_listener(self):
self._listening = False
# ---------------------- Audio Output ----------------------
def _get_alsa_devices(self):
try:
return alsaaudio.cards()
except Exception:
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
return pulse.sink_list()
except Exception:
return []
def _detect_dualsense_audio(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_audio_device:
print("DualSense speaker not detected.")
return
print(f"Playing tone on DualSense ({self.dualsense_audio_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
try:
if self.dualsense_audio_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_audio_device['name'])
elif self.dualsense_audio_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_audio_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
except Exception as e:
print("Failed to play tone:", e)
def list_audio_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")
# ---------------------- Cleanup ----------------------
def close(self):
self.ds.close()
if __name__ == "__main__":
controller = DualSenseController()
# Bind buttons to patterns
controller.bind("cross", lambda: controller.rumble_pattern("heartbeat", 1.5))
controller.bind("circle", lambda: controller.rumble_pattern("buzz", 0.5))
controller.bind("triangle", lambda: controller.rumble_pattern("pulse", 2))
controller.bind("square", lambda: controller.set_led_color(255, 0, 0))
# Start listening
controller.start_input_listener()

View File

@@ -1,50 +0,0 @@
import pygame
from pygameController.controlsbase import ControlsBase
class GenericController(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.guid = self.device.get_guid()
self.numaxis: int = self.device.get_numaxes()
self.axis: list = [self.device.get_axis(a) for a in range(self.numaxis)]
self.numhats: int = self.device.get_numhats()
self.hats: list = [self.device.get_hat(h) for h in range(self.numhats)]
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = [self.device.get_button(b) for b in range(self.numbuttons)]
print(f"{self.name} connected")
print("GUID:", self.guid)
print("Axis:", self.numaxis, self.axis)
print("Hats:", self.numhats, self.hats)
print("Buttons:", self.numbuttons, self.buttons)
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

View File

@@ -1,80 +0,0 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from pygameController.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class LogitechDualActionController(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.guid = self.device.get_guid()
self.powerlevel = self.device.get_power_level()
self.numaxis: int = self.device.get_numaxes()
self.axis: list = [self.device.get_axis(a) for a in range(self.numaxis)]
self.numhats: int = self.device.get_numhats()
self.hats: list = [self.device.get_hat(h) for h in range(self.numhats)]
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = [self.device.get_button(b) for b in range(self.numbuttons)]
self.input_mode = InputMode.DirectInput
print(f"{self.name} connected")
print("GUID:", self.guid)
print("Axis:", self.numaxis, self.axis)
print("Hats:", self.numhats, self.hats)
print("Buttons:", self.numbuttons, self.buttons)
print("Input mode:", self.input_mode)
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 input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode

View File

@@ -1,80 +0,0 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from pygameController.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class LogitechF310Controller(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.guid = self.device.get_guid()
self.powerlevel = self.device.get_power_level()
self.numaxis: int = self.device.get_numaxes()
self.axis: list = [self.device.get_axis(a) for a in range(self.numaxis)]
self.numhats: int = self.device.get_numhats()
self.hats: list = [self.device.get_hat(h) for h in range(self.numhats)]
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = [self.device.get_button(b) for b in range(self.numbuttons)]
self.input_mode = InputMode.XInput
print(f"{self.name} connected")
print("GUID:", self.guid)
print("Axis:", self.numaxis, self.axis)
print("Hats:", self.numhats, self.hats)
print("Buttons:", self.numbuttons, self.buttons)
print("Input mode:", self.input_mode)
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 input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode

View File

@@ -1,108 +0,0 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from pygameController.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class ConnectionType(Enum):
WIRED = 1
WIRELESS = 2
class LogitechF510Controller(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.input_mode: InputMode.DirectInput
self.input_connection: ConnectionType.WIRED
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 input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode
@property
def input_connection(self) -> int:
return self._input_connection
@input_connection.setter
def input_connection(self, conn: int) -> None:
self._input_connection = conn

View File

@@ -1,108 +0,0 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from pygameController.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class ConnectionType(Enum):
WIRED = 1
WIRELESS = 2
class LogitechF710Controller(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.input_mode: InputMode.DirectInput
self.input_connection: ConnectionType.WIRED
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 input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode
@property
def input_connection(self) -> int:
return self._input_connection
@input_connection.setter
def input_connection(self, conn: int) -> None:
self._input_connection = conn

View File

@@ -1,50 +0,0 @@
import time
import threading
class XboxSeriesXController:
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.guid = self.device.get_guid()
self.numaxis: int = self.device.get_numaxes()
self.axis: list = [self.device.get_axis(a) for a in range(self.numaxis)]
self.numhats: int = self.device.get_numhats()
self.hats: list = [self.device.get_hat(h) for h in range(self.numhats)]
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = [self.device.get_button(b) for b in range(self.numbuttons)]
print(f"{self.name} connected")
print("GUID:", self.guid)
print("Axis:", self.numaxis, self.axis)
print("Hats:", self.numhats, self.hats)
print("Buttons:", self.numbuttons, self.buttons)
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

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
pygame==2.6.1
pygameControls==0.0.4