Last active
February 15, 2025 12:51
-
-
Save leonmak/f38fb76a4f3787d548fad0f14221e211 to your computer and use it in GitHub Desktop.
multi-threaded board game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import threading | |
import time | |
num_p = 10 | |
m, n = 5, 5 | |
# position (y,x) - lock, players in pos | |
board_locks = {(i//m, i % n): threading.Lock() for i in range(m*n)} | |
board_players = {(i//m, i % n): set() for i in range(m*n)} | |
class Player(threading.Thread): | |
def __init__(self, id, turns=10): | |
super().__init__() | |
self.id = id | |
self.turns = turns | |
self.pos_lock = threading.Lock() # locks all attr | |
self.pos = (random.randint(0, m-1), random.randint(0, n-1)) | |
self.strength = 0 | |
self.alive = True # can be set by other player | |
def run(self): | |
for _ in range(self.turns): | |
time.sleep(random.randint(0, 50)/10) # sleep 0-5s | |
if not self.alive: # other init and kiled you | |
return | |
other_ps = self.move() | |
for p in other_ps: | |
you_died = self.battle(p) # you init | |
if you_died: | |
self.alive = False | |
return | |
def battle(self, other): | |
# check other is alive | |
if not other.alive: | |
return | |
(lock1, lock2) = (self.pos_lock, other.pos_lock) if self.id < other.id else ( | |
other.pos_lock, self.pos_lock) | |
# prevent deadlock if both acquire their own pos_lock, then try acquire the other | |
with lock1: | |
with lock2: # prevent player from moving (and fighting other) | |
print(f"===\nP{self.id} fights P{other.id} at {self.pos}") | |
if self.strength < other.strength: | |
self.remove_pos() | |
self.alive = False | |
print(f"P{self.id} has died\n") | |
return True # you died | |
elif self.strength > other.strength: | |
other.remove_pos() | |
other.alive = False | |
print(f"P{other.id} has died") | |
print(f"P{self.id} gained 4 str\n") | |
self.strength += 4 | |
return False | |
return False # did not die | |
def remove_pos(self): # from other / self | |
with board_locks[self.pos]: | |
board_players[self.pos].discard(self.id) | |
def move(self): # locked self pos | |
# get lock to remove prev | |
with board_locks[self.pos]: | |
board_players[self.pos].discard(self.id) | |
# increase str when move | |
# set new pos, return players there | |
new_pos = (random.randint(0, m-1), random.randint(0, n-1)) | |
dist = abs(new_pos[0]-self.pos[0]) + abs(new_pos[1]-self.pos[1]) | |
self.strength += dist | |
if dist > 0: | |
print(f"P{self.id} gained {dist} str to {self.strength}") | |
# acq lock to set new pos | |
with board_locks[new_pos]: | |
if not self.alive: # someone killed you before you moved | |
return [] | |
self.pos = new_pos | |
other_ps = [players[i] for i in board_players[self.pos]] | |
board_players[self.pos].add(self.id) | |
return other_ps | |
players = {i: Player(i) for i in range(num_p)} | |
for p in players.values(): | |
p.start() # run | |
for p in players.values(): | |
p.join() | |
for p in players.values(): | |
print(f'P{p.id} (str: {p.strength}) is {"alive" if p.alive else "dead"}') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment