use level select vote messages to determine if peers are playing versus and sync random seed for level
This commit is contained in:
parent
a085eaff6d
commit
e7846d4826
144
NS.py
144
NS.py
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# This is the main file containing all the Pygame code.
|
||||
|
||||
import argparse, pathlib, operator, subprocess, sys, os, socket, select, time
|
||||
import argparse, pathlib, operator, subprocess, sys, os, socket, select, time, random
|
||||
|
||||
# Auto-detect GPIO library
|
||||
try:
|
||||
|
@ -18,7 +18,6 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
from random import randint, choice, random
|
||||
from math import pi
|
||||
from copy import copy
|
||||
from glob import iglob
|
||||
|
@ -154,6 +153,7 @@ class NS(Game, Animation):
|
|||
result = None
|
||||
versus = False
|
||||
level = None
|
||||
seed = None
|
||||
|
||||
def __init__(self, address, port):
|
||||
self.address = address
|
||||
|
@ -210,7 +210,7 @@ class NS(Game, Animation):
|
|||
},
|
||||
"network":
|
||||
{
|
||||
"int": ["port", "diagnostics-size"],
|
||||
"int": ["port", "diagnostics-size", "join-time-limit"],
|
||||
"bool": "diagnostics",
|
||||
"path": "diagnostics-font",
|
||||
"list": "peers"
|
||||
|
@ -234,7 +234,7 @@ class NS(Game, Animation):
|
|||
"system":
|
||||
{
|
||||
"bool": ["minimize-load-time", "enable-level-select", "optimize-title-screen"],
|
||||
"int": ["lives-boss-rush-mode", "lives-level-select-mode"]
|
||||
"int": ["lives-boss-rush-mode", "lives-level-select-mode", "max-seed"]
|
||||
},
|
||||
"pads":
|
||||
{
|
||||
|
@ -373,15 +373,16 @@ class NS(Game, Animation):
|
|||
self.pop_up_font = pygame.font.Font(self.get_resource(Dialogue.FONT_PATH), self.get_configuration("pop-up", "size"))
|
||||
self.pop_up_text = ""
|
||||
|
||||
# Initialize networking
|
||||
# Initialize networking. Include self as a peer located at "localhost".
|
||||
self.server = socket.create_server(("", self.get_configuration("network", "port")))
|
||||
self.peers = {}
|
||||
self.player_count = 1
|
||||
self.peers = {"localhost": NS.Peer("localhost", self.get_configuration("network", "port"))}
|
||||
self.peers["localhost"].versus = True
|
||||
print(f"Added peer 'localhost'")
|
||||
if self.get_configuration("network", "peers"):
|
||||
for peer in self.get_configuration("network", "peers"):
|
||||
# Store peers in a dictionary where the key is the peer address
|
||||
self.peers[peer] = NS.Peer(peer, self.get_configuration("network", "port"))
|
||||
print(f"Added peer {peer}")
|
||||
print(f"Added peer '{peer}'")
|
||||
# Launch separate threads for listing and posting to peers
|
||||
self.listen_thread = Thread(target=self.listen_to_peers, daemon=True)
|
||||
self.listen_thread.start()
|
||||
|
@ -470,21 +471,37 @@ class NS(Game, Animation):
|
|||
# Determine this game's status
|
||||
if self.title.active:
|
||||
status = "title"
|
||||
message = status
|
||||
elif self.level_select.active and self.level_select.level_index_selected is None:
|
||||
status = "level select"
|
||||
elif not self.boss.battle_finished:
|
||||
status = f"playing {self.level_select.level_index_selected}"
|
||||
message = status
|
||||
elif self.level_select.active and not self.level_select.level_launched:
|
||||
status = "voted"
|
||||
level = self.level_select.level_index_selected
|
||||
self.peers["localhost"].level = level
|
||||
message = f"{status} {level} {self.peers['localhost'].seed}"
|
||||
elif self.level_select.active or not self.boss.battle_finished:
|
||||
status = "playing"
|
||||
level = self.level_select.level_index_selected
|
||||
self.peers["localhost"].level = level
|
||||
message = f"{status} {level}"
|
||||
elif self.boss.player_defeated:
|
||||
status = "lost"
|
||||
message = status
|
||||
else:
|
||||
status = f"complete {self.most_recent_score.milliseconds}"
|
||||
status = "complete"
|
||||
result = self.most_recent_score.milliseconds
|
||||
self.peers["localhost"].result = result
|
||||
message = f"{status} {result}"
|
||||
self.peers["localhost"].status = status
|
||||
|
||||
# Connect and send status message to each peer. If sending fails, pass and wait until the next iteration.
|
||||
for peer in self.peers.values():
|
||||
try:
|
||||
socket.create_connection((peer.address, peer.port)).send(str.encode(status))
|
||||
except:
|
||||
pass
|
||||
if peer.address != "localhost":
|
||||
try:
|
||||
socket.create_connection((peer.address, peer.port)).send(str.encode(message))
|
||||
except:
|
||||
pass
|
||||
|
||||
# Send status every 1/2 second
|
||||
time.sleep(0.5)
|
||||
|
@ -502,6 +519,8 @@ class NS(Game, Animation):
|
|||
peer = self.peers[incoming[1][0]]
|
||||
# All messages are less than 64 characters
|
||||
message = incoming[0].recv(64).decode()
|
||||
if message.startswith("title") or message.startswith("level select"):
|
||||
peer.versus = False
|
||||
if message.startswith("complete"):
|
||||
try:
|
||||
peer.result = int(message.split()[-1])
|
||||
|
@ -514,20 +533,36 @@ class NS(Game, Animation):
|
|||
peer.level = int(message.split()[-1])
|
||||
peer.status = "playing"
|
||||
except:
|
||||
# Improperly formatted message received
|
||||
pass
|
||||
elif message.startswith("voted"):
|
||||
try:
|
||||
status, level, seed = message.split()
|
||||
peer.level = int(level)
|
||||
peer.seed = int(seed)
|
||||
peer.status = status
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
peer.status = message
|
||||
|
||||
def count_players(self):
|
||||
"""
|
||||
@return count of peers playing versus against this
|
||||
@return count of peers committed to a match with this peer
|
||||
"""
|
||||
count = 1
|
||||
count = 0
|
||||
for peer in self.peers.values():
|
||||
count += peer.versus
|
||||
return count
|
||||
|
||||
def count_lobby(self):
|
||||
"""
|
||||
@return count of peers at the level select screen
|
||||
"""
|
||||
count = 0
|
||||
for peer in self.peers.values():
|
||||
count += peer.status == "level select" or peer.status == "voted"
|
||||
return count
|
||||
|
||||
def reset(self, leave_wipe_running=False):
|
||||
self.idle_elapsed = 0
|
||||
self.suppressing_input = False
|
||||
|
@ -693,6 +728,7 @@ class NS(Game, Animation):
|
|||
if y > 0:
|
||||
sprite = Sprite(self)
|
||||
sprite.add_frame(full_surface)
|
||||
sprite.set_alpha(200)
|
||||
if self.get_configuration("pop-up", "center"):
|
||||
sprite.location.center = self.get_display_surface().get_rect().center
|
||||
sprite.update()
|
||||
|
@ -706,9 +742,14 @@ class NS(Game, Animation):
|
|||
surface = font.render(
|
||||
f"{peer.address} {peer.status} [PvP {peer.versus}, lvl {peer.level}, result {peer.result}]",
|
||||
True, (255, 255, 255), (0, 0, 0))
|
||||
surface.set_alpha(200)
|
||||
y -= surface.get_height()
|
||||
self.get_display_surface().blit(surface, (0, y))
|
||||
|
||||
surface = font.render(f"players: {self.count_players()} lobby: {self.count_lobby()}", True, (255, 255, 255), (0, 0, 0))
|
||||
surface.set_alpha(200)
|
||||
y -= surface.get_height()
|
||||
self.get_display_surface().blit(surface, (0, y))
|
||||
|
||||
# Reset the game when idle
|
||||
self.idle_elapsed += self.time_filter.get_last_frame_duration()
|
||||
if self.idle_elapsed >= self.IDLE_TIMEOUT:
|
||||
|
@ -744,7 +785,7 @@ class LevelSelect(Animation):
|
|||
def __init__(self, parent):
|
||||
Animation.__init__(self, parent)
|
||||
self.subscribe(self.respond, KEYDOWN)
|
||||
self.register(self.timeout)
|
||||
self.register(self.timeout, self.force_launch)
|
||||
y = 250
|
||||
indent = 10
|
||||
dsr = self.get_display_surface().get_rect()
|
||||
|
@ -812,6 +853,8 @@ class LevelSelect(Animation):
|
|||
def reset(self):
|
||||
self.deactivate()
|
||||
self.level_index_selected = None
|
||||
self.level_launched = False
|
||||
self.launch_forced = False
|
||||
self.zoom = 1.0
|
||||
self.grow_sound_channel = None
|
||||
for level_index in range(3):
|
||||
|
@ -859,6 +902,9 @@ class LevelSelect(Animation):
|
|||
"""
|
||||
self.get_game().wipe.start(self.get_game().reset, leave_wipe_running=True)
|
||||
|
||||
def force_launch(self):
|
||||
self.launch_forced = True
|
||||
|
||||
def update(self):
|
||||
if self.active:
|
||||
Animation.update(self)
|
||||
|
@ -870,11 +916,21 @@ class LevelSelect(Animation):
|
|||
for level_index, platform in enumerate(self.platforms):
|
||||
if platform.get_glowing_edge() == self.get_game().platform.get_edge_pressed():
|
||||
if self.get_game().platform.press_elapsed > self.get_configuration("time", "level-select-press-length"):
|
||||
# This will cause the level to launch
|
||||
# This will cause a vote to be cast to peers if there are any. If there are others in the lobby,
|
||||
# the game will wait for other votes to be cast or the lobby to clear. Otherwise, the level will
|
||||
# launch.
|
||||
self.level_index_selected = level_index
|
||||
if self.grow_sound_channel is not None:
|
||||
self.grow_sound_channel.stop()
|
||||
self.grow_sound_channel = None
|
||||
self.get_game().peers["localhost"].seed = random.randint(0, self.get_configuration("system", "max-seed"))
|
||||
self.play(self.force_launch, delay=self.get_configuration("network", "join-time-limit"))
|
||||
# Wipe away other levels and zoom selected
|
||||
for level_index in range(3):
|
||||
if level_index != self.level_index_selected:
|
||||
self.platforms[level_index].view.play(self.platforms[level_index].view.wipe_out)
|
||||
self.previews[level_index].play(self.previews[level_index].wipe_out, interval=100)
|
||||
self.get_audio().play_sfx("complete_pattern_3")
|
||||
break
|
||||
else:
|
||||
if self.grow_sound_channel is None:
|
||||
|
@ -890,15 +946,34 @@ class LevelSelect(Animation):
|
|||
if offset < angle:
|
||||
pygame.draw.arc(self.get_display_surface(), (255, 255, 255), rect, offset, angle, 14)
|
||||
offset += .01
|
||||
if self.level_index_selected is not None:
|
||||
# Launch the level
|
||||
for level_index in range(3):
|
||||
if level_index != self.level_index_selected:
|
||||
self.platforms[level_index].view.play(self.platforms[level_index].view.wipe_out)
|
||||
self.previews[level_index].play(self.previews[level_index].wipe_out, interval=100)
|
||||
self.get_audio().play_sfx("complete_pattern_3")
|
||||
|
||||
# Check if peers are still deciding
|
||||
elif not self.level_launched:
|
||||
|
||||
# Launch if time is up or the lobby is empty
|
||||
if all(peer.status != "level select" or peer.address == "localhost" for peer in self.get_game().peers.values()) or \
|
||||
self.launch_forced:
|
||||
seed = self.get_game().peers["localhost"].seed
|
||||
for peer in self.get_game().peers.values():
|
||||
if peer.address != "localhost" and peer.status == "voted" and peer.level == self.level_index_selected:
|
||||
peer.versus = True
|
||||
seed = (seed + peer.seed) % self.get_configuration("system", "max-seed")
|
||||
random.seed(seed)
|
||||
self.halt(self.force_launch)
|
||||
self.get_game().pop_up("", clear=True)
|
||||
self.level_launched = True
|
||||
|
||||
# Update displayed wait message
|
||||
else:
|
||||
self.get_game().pop_up(
|
||||
f"Waiting {self.accounts[self.force_launch].delay // 1000 + 1}s for players to join", clear=True)
|
||||
|
||||
# Second half of launch animation
|
||||
elif not self.get_game().wipe.is_playing() and any(preview.is_hidden() for preview in self.previews):
|
||||
|
||||
# Final animation before game will launch, launch is attached to the animation and will be triggered automatically
|
||||
self.get_game().wipe.start(self.launch_selected_index)
|
||||
|
||||
for platform in self.platforms:
|
||||
platform.update()
|
||||
if self.level_index_selected is not None:
|
||||
|
@ -1099,7 +1174,7 @@ class Tony(Sprite):
|
|||
Sprite.shift_frame(self)
|
||||
frameset = self.get_current_frameset()
|
||||
if frameset.name == "board" and frameset.current_index == 1:
|
||||
self.get_audio().play_sfx(choice(self.taunts))
|
||||
self.get_audio().play_sfx(random.choice(self.taunts))
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
|
@ -1196,9 +1271,9 @@ class Video(Sprite):
|
|||
|
||||
def shift_frame(self):
|
||||
Sprite.shift_frame(self)
|
||||
if random() < self.next_video_chance:
|
||||
if random.random() < self.next_video_chance:
|
||||
while True:
|
||||
selection = choice(range(0, len(self.gif_frames_scaled)))
|
||||
selection = random.choice(range(0, len(self.gif_frames_scaled)))
|
||||
if selection != self.gif_index:
|
||||
self.gif_index = selection
|
||||
self.load_selection()
|
||||
|
@ -2707,6 +2782,7 @@ class Boss(Animation):
|
|||
def brandish(self):
|
||||
self.queue = []
|
||||
platform = self.get_game().platform
|
||||
choice = random.choice
|
||||
if self.level_index == 0:
|
||||
if self.health.amount > 90:
|
||||
first = choice(platform.get_steps_from_edge(self.last_attack))
|
||||
|
@ -2859,7 +2935,7 @@ class Boss(Animation):
|
|||
length = 8
|
||||
while len(self.queue) < length:
|
||||
while True:
|
||||
orientation = randint(0, 5)
|
||||
orientation = random.randint(0, 5)
|
||||
if (not self.queue and orientation != self.last_attack) or \
|
||||
(len(self.queue) > 0 and orientation != self.queue[-1]):
|
||||
self.queue.append(orientation)
|
||||
|
@ -2876,7 +2952,7 @@ class Boss(Animation):
|
|||
|
||||
def choose_new_edge(self, edges):
|
||||
while True:
|
||||
edge = choice(edges)
|
||||
edge = random.choice(edges)
|
||||
if edge != self.last_attack:
|
||||
return edge
|
||||
|
||||
|
@ -3468,7 +3544,7 @@ class Ending(Animation):
|
|||
self.deactivate()
|
||||
self.halt()
|
||||
self.text_index = 0
|
||||
self.angle = choice((pi / 4, 3 * pi / 4, 5 * pi / 4, 7 * pi / 4))
|
||||
self.angle = random.choice((pi / 4, 3 * pi / 4, 5 * pi / 4, 7 * pi / 4))
|
||||
self.slime_bag.reset()
|
||||
|
||||
def deactivate(self):
|
||||
|
|
3
config
3
config
|
@ -40,12 +40,13 @@ enable-level-select = yes
|
|||
lives-boss-rush-mode = 3
|
||||
lives-level-select-mode = 1
|
||||
optimize-title-screen = no
|
||||
max-seed = 2147483647
|
||||
|
||||
[network]
|
||||
peers =
|
||||
port = 8080
|
||||
delimeter = |
|
||||
timeout = 10.0
|
||||
join-time-limit = 5999
|
||||
diagnostics = no
|
||||
diagnostics-font = resource/BPmono.ttf
|
||||
diagnostics-size = 15
|
||||
|
|
Loading…
Reference in New Issue