diff --git a/chat.py b/chat.py
new file mode 100644
index 0000000..6b09431
--- /dev/null
+++ b/chat.py
@@ -0,0 +1,14 @@
+import network, sys, time
+
+sock = network.Sock()
+port = 33077
+
+if sys.argv[1] == "s":
+	sock.listen(["0.0.0.0", port])
+	while True:
+		for d, a in sock.get():
+			print(a, d)
+		time.sleep(0.1)
+elif sys.argv[1] == "c":
+	while True:
+		sock.send(input(), ["192.168.0.255", port])
diff --git a/demo.py b/demo.py
index 4c79817..ec3615d 100644
--- a/demo.py
+++ b/demo.py
@@ -12,5 +12,5 @@ if sys.argv[1] == "s":
 		time.sleep(0.1)
 elif sys.argv[1] == "c":
 	while True:
-		sock.send(str(time.time()), (sys.argv[3], port))
+		sock.send(time.time(), (sys.argv[3], port))
 		time.sleep(1)
diff --git a/demo1.py b/demo1.py
new file mode 100644
index 0000000..d2754de
--- /dev/null
+++ b/demo1.py
@@ -0,0 +1,8 @@
+import network, sys, time
+
+sock = network.Sock()
+destination = ["192.168.0.255", 33077]
+
+while True:
+	message = input("envoyer: ")
+	sock.send(message, destination)
diff --git a/demo2.py b/demo2.py
new file mode 100644
index 0000000..f226594
--- /dev/null
+++ b/demo2.py
@@ -0,0 +1,10 @@
+import network, sys, time
+
+sock = network.Sock()
+address = ("0.0.0.0", 33077)
+
+sock.listen(address)
+while True:
+	for d, a in sock.get():
+		print(a, d)
+	time.sleep(0.1)
diff --git a/game.py b/game.py
index ac244ba..202232b 100644
--- a/game.py
+++ b/game.py
@@ -1,4 +1,4 @@
-import network, sys, json, pygame, random, xlog
+import network, sys, pygame, random, xlog, time, colorsys
 
 pygame.init()
 
@@ -7,26 +7,37 @@ w, h = 640, 480
 size = [w, h]
 black = [0, 0, 0]
 
+def random_color():
+	return [int(i*255) for i in colorsys.hsv_to_rgb(random.random(), 0.8, 1)]
+
+def normalize(n):
+	if n > 0:
+		return 1
+	if n < 0:
+		return -1
+	return 0
+
 def new_oid():
 	return random.randint(0,2147483648)
 
 class Obj:
 	def __init__(self, oid):
 		self.oid = oid
-	def damage(self, dmg): pass
+	def damage(self, dmg, author=None): pass
 	def update(self): pass
 	def draw(self, surface): pass
 	def print_debug(self, lines): pass
 
 class Player(Obj):
-	def __init__(self, oid, address, pos, color):
+	def __init__(self, oid, address, pos, color, life=100, score=0):
 		Obj.__init__(self, oid)
 		self.address = address
 		self.pos = pos
 		self.color = color
+		self.score = score
 		self.speed = [0, 0]
-		self.life = 100
-		self.direction = [1, 0]
+		self.life = life
+		self.direction = [0, 1]
 
 	def throw_energy_ball(self):
 		oid = new_oid()
@@ -34,7 +45,7 @@ class Player(Obj):
 		pos[0] += self.direction[0] * 16
 		pos[1] += self.direction[1] * 16
 		speed = [self.speed[0]+self.direction[0]*4, self.speed[1]+self.direction[1]*4]
-		objects[oid] = EnergyBall(oid, pos, speed)
+		objects[oid] = EnergyBall(oid, pos, speed, self.oid)
 	
 	def stab(self):
 		for oid in players:
@@ -42,30 +53,37 @@ class Player(Obj):
 				continue
 			player = players[oid]
 			if abs(player.pos[0]-self.pos[0]) < 32 and abs(player.pos[1]-self.pos[1]) < 32:
-				player.damage(10)
+				player.damage(10, self.oid)
 	
-	def damage(self, dmg):
+	def damage(self, dmg, author=None):
 		self.life -= dmg
 		if self.life <= 0:
-			print("die!")
+			self.speed = [0, 0]
+			self.pos = [16, 16]
+			self.life = 100
+			if author in players:
+				players[author].score += 1
 	
 	def update(self):
+		if self.speed[0] != 0 or self.speed[1] != 0:
+			self.direction = [normalize(self.speed[0]), normalize(self.speed[1])]
 		self.pos[0] += self.speed[0]
 		self.pos[1] += self.speed[1]
 	
 	def draw(self, surface):
 		pygame.draw.rect(surface, self.color, (self.pos[0]-8, self.pos[1]-8, 16, 16))
-		lifebar = int(32*self.life/100)
-		pygame.draw.rect(surface, (64,255,64), (self.pos[0]-lifebar//2+8, self.pos[1]+6, lifebar, 4))
+		lifebar = int(16*self.life/100)
+		pygame.draw.rect(surface, (64,255,64), (self.pos[0]-8, self.pos[1]-16, lifebar, 4))
 	
 	def print_debug(self, lines):
-		lines.append("Player {}: {}".format(self.oid, obj.address))
+		lines.append("Player {}: {} / score {}".format(self.oid, obj.address, obj.score))
 
 class EnergyBall(Obj):
-	def __init__(self, oid, pos, speed):
+	def __init__(self, oid, pos, speed, thrower):
 		Obj.__init__(self, oid)
 		self.pos = pos
 		self.speed = speed
+		self.thrower = thrower
 	
 	def update(self):
 		self.pos[0] += self.speed[0]
@@ -75,14 +93,14 @@ class EnergyBall(Obj):
 				continue
 			obj = objects[oid]
 			if abs(self.pos[0]-obj.pos[0])<8 and abs(self.pos[1]-obj.pos[1])<8:
-				obj.damage(10)
+				obj.damage(10, self.thrower)
 				removable.append(self.oid)
 				return
 	
 	def draw(self, surface):
 		pygame.draw.rect(surface, (128,255,255), (self.pos[0]-2, self.pos[1]-2, 4, 4))
 	
-	def damage(self, dmg):
+	def damage(self, dmg, author=None):
 		if dmg > 0:
 			removable.append(self.oid)
 	
@@ -95,15 +113,29 @@ clock = pygame.time.Clock()
 sock = network.Sock()
 sock.listen(address)
 
-me = Player(new_oid(), address, [10, 10], [255, 0, 0])
+me = Player(new_oid(), address, [random.randint(16, w-16), random.randint(16, h-16)], random_color())
 objects = {me.oid: me}
 players = {me.oid: me}
 removable = []
 
 def send_all(msg):
-	msg = json.dumps(msg)
 	for oid in players:
-		sock.send(msg, objects[oid].address)
+		if oid != me.oid:
+			sock.send(msg, objects[oid].address)
+
+def make_recap():
+	pl = []
+	for oid in players:
+		player = players[oid]
+		pl.append({
+			"oid": oid,
+			"pos": player.pos,
+			"color": player.color,
+			"address": player.address,
+			"life": player.life,
+			"score": player.score
+		})
+	return {"type":"recap", "players":pl, "oid":me.oid}
 
 def remove_objects():
 	for oid in removable:
@@ -113,8 +145,11 @@ def remove_objects():
 			players.pop(oid)
 	removable.clear()
 
-sock.send(json.dumps({"type":"join","pos":me.pos,"color":me.color,"port":address[1],"oid":me.oid}), ["192.168.0.255", 33033])
-sock.send(json.dumps({"type":"join","pos":me.pos,"color":me.color,"port":address[1],"oid":me.oid}), ["127.0.0.1", 33033])
+sock.send({"type":"join","pos":me.pos,"color":me.color,"port":address[1],"oid":me.oid}, ["192.168.0.255", 33033])
+sock.send({"type":"join","pos":me.pos,"color":me.color,"port":address[1],"oid":me.oid}, ["192.168.0.255", 33033])
+sock.send({"type":"join","pos":me.pos,"color":me.color,"port":address[1],"oid":me.oid}, ["127.0.0.1", 33033])
+
+last_recap = 0
 
 while True:
 	for event in pygame.event.get():
@@ -125,19 +160,15 @@ while True:
 				send_all({"type":"quit", "oid":me.oid})
 				sys.exit()
 			if event.key == pygame.K_RIGHT:
-				me.direction = [1, 0]
 				me.speed[0] = 2
 				send_all({"type":"move", "oid":me.oid, "speed":me.speed})
 			elif event.key == pygame.K_LEFT:
-				me.direction = [-1, 0]
 				me.speed[0] = -2
 				send_all({"type":"move", "oid":me.oid, "speed":me.speed})
 			elif event.key == pygame.K_DOWN:
-				me.direction = [0, 1]
 				me.speed[1] = 2
 				send_all({"type":"move", "oid":me.oid, "speed":me.speed})
 			elif event.key == pygame.K_UP:
-				me.direction = [0, -1]
 				me.speed[1] = -2
 				send_all({"type":"move", "oid":me.oid, "speed":me.speed})
 			elif event.key == pygame.K_SPACE:
@@ -160,16 +191,14 @@ while True:
 				me.speed[1] = 0
 				send_all({"type":"move", "oid":me.oid, "speed":me.speed})
 	for (r, c) in sock.get():
-		r = json.loads(r.decode())
 		if r["oid"] == me.oid:
 			continue
 		if r["type"] == "join":
-			pl = []
-			for oid in players:
-				player = players[oid]
-				pl.append({"oid":oid, "pos":player.pos, "color":player.color, "address":player.address})
-			sock.send(json.dumps({"type":"recap", "players":pl, "oid":me.oid}), [c[0], r["port"]])
-			players[r["oid"]] = Player(r["oid"], [c[0], r["port"]], r["pos"], r["color"])
+			sock.send(make_recap(), [c[0], r["port"]])
+			sock.send(make_recap(), [c[0], r["port"]])
+			new_player = Player(r["oid"], [c[0], r["port"]], r["pos"], r["color"])
+			players[r["oid"]] = new_player
+			objects[r["oid"]] = new_player
 		elif r["type"] == "move":
 			players[r["oid"]].speed = r["speed"]
 		elif r["type"] == "recap":
@@ -178,12 +207,21 @@ while True:
 					address = player["address"]
 					if address[0] == "0.0.0.0":
 						address = [c[0], c[1]]
-					objects[player["oid"]] = Player(player["oid"], address, player["pos"], player["color"])
-					players[player["oid"]] = objects[player["oid"]]
+					new_player = Player(player["oid"], address, player["pos"], player["color"],
+						life=player["life"],
+						score=player["score"]
+					)
+					objects[player["oid"]] = new_player
+					players[player["oid"]] = new_player
+				elif c[0] == player["address"][0]:
+					players[player["oid"]].pos = player["pos"]
+					players[player["oid"]].speed = player["speed"]
 		elif r["type"] == "quit":
 			removable.append(r["oid"])
 		elif r["type"] == "stab":
 			objects[r["oid"]].stab()
+		elif r["type"] == "throw":
+			objects[r["oid"]].throw_energy_ball()
 
 	remove_objects()
 	
@@ -200,5 +238,9 @@ while True:
 	
 	xlog.display(screen, lines)
 	
+	if time.time() > last_recap + 5:
+		send_all(make_recap())
+		last_recap = time.time() + 5
+	
 	pygame.display.flip()
 	clock.tick(30)
diff --git a/network.py b/network.py
index f62af0c..01aea39 100644
--- a/network.py
+++ b/network.py
@@ -1,4 +1,4 @@
-import socket, os, sys
+import socket, os, sys, json
 from threading import Thread, Lock
 
 NETTRACE = str(os.environ.get("NETTRACE", "0")) == "1"
@@ -12,25 +12,44 @@ class Sock:
 		self.queue_lock = Lock()
 		self.sock_thread = None
 	
-	def send(self, message, address):
+	def send_raw(self, message, address):
 		if type(message) == str:
 			message = message.encode()
 		if NETTRACE:
 			print("Send to", tuple(address), ":", message, file=sys.stderr)
 		self.sock.sendto(message, tuple(address))
 	
+	def send(self, message, address):
+		message = json.dumps(message)
+		if NETTRACE:
+			print("Send to", tuple(address), ":", message, file=sys.stderr)
+		self.sock.sendto(message.encode(), tuple(address))
+	
 	def listen(self, address, length=65535):
 		self.sock_thread = SockThread(self, tuple(address), length)
 		self.sock_thread.setDaemon(True)
 		self.sock_thread.start()
 	
-	def get(self):
+	def get_raw(self):
 		if len(self.queue) == 0:
 			return []
 		with self.queue_lock:
 			queue = self.queue
 			self.queue = []
 		return queue
+	
+	def get(self):
+		if len(self.queue) == 0:
+			return []
+		queue = []
+		with self.queue_lock:
+			for r in self.queue:
+				try:
+					queue.append([json.loads(r[0].decode()), r[1]])
+				except:
+					pass
+			self.queue.clear()
+		return queue
 
 class SockThread(Thread):
 	def __init__(self, sock, address, length):
diff --git a/template.py b/template.py
new file mode 100644
index 0000000..1c94ded
--- /dev/null
+++ b/template.py
@@ -0,0 +1,108 @@
+import network, sys, pygame, random, xlog, time, colorsys
+
+pygame.init()
+
+w, h = 640, 480 # Taille de la fenêtre
+size = [w, h]
+black = [0, 0, 0] # Couleur noire
+
+# Créer une couleur aléatoire
+def random_color():
+	return [int(i*255) for i in colorsys.hsv_to_rgb(random.random(), 0.8, 1)]
+
+# Créer un identifiant d'objet aléatoire
+def new_oid():
+	return random.randint(0,2147483648)
+
+# Définit les méthodes par défaut d'un objet
+class Obj:
+	def __init__(self, oid):
+		self.oid = oid
+	def update(self): pass
+	def draw(self, surface): pass
+	def print_debug(self, lines): pass
+
+# Définit un joueur
+class Player(Obj):
+	def __init__(self, oid, pos, color):
+		Obj.__init__(self, oid)
+		self.pos = pos
+		self.speed = [0, 0]
+		self.color = color
+	
+	# Mise à jour de l'état du joueur
+	def update(self):
+		self.pos[0] += self.speed[0]
+		self.pos[1] += self.speed[1]
+	
+	# Dessiner le joueur
+	def draw(self, surface):
+		pygame.draw.rect(surface, self.color, (self.pos[0]-8, self.pos[1]-8, 16, 16))
+	
+	# Afficher le texte d'information
+	def print_debug(self, lines):
+		lines.append("Player {}".format(self.oid))
+
+screen = pygame.display.set_mode(size) # définir la taille de la fenêtre
+clock = pygame.time.Clock()
+
+# Créer notre propre joueur
+me = Player(new_oid(), [random.randint(16, w-16), random.randint(16, h-16)], random_color())
+objects = {me.oid: me} # dictionnaire de tous les objets
+players = {me.oid: me} # dictionnaire de tous les joueurs (qui sont aussi des objets)
+removable = [] # identifiants des objets à supprimer
+
+# Supprimer les objets à supprimer
+def remove_objects():
+	for oid in removable:
+		if oid in objects:
+			objects.pop(oid)
+		if oid in players:
+			players.pop(oid)
+	removable.clear()
+
+# Boucle principale
+while True:
+	# Lecture des événements
+	for event in pygame.event.get():
+		if event.type == pygame.QUIT:
+			sys.exit()
+		if event.type == pygame.KEYDOWN:
+			if event.key == pygame.K_ESCAPE:
+				sys.exit()
+			if event.key == pygame.K_RIGHT:
+				me.speed[0] = 2
+			elif event.key == pygame.K_LEFT:
+				me.speed[0] = -2
+			elif event.key == pygame.K_DOWN:
+				me.speed[1] = 2
+			elif event.key == pygame.K_UP:
+				me.speed[1] = -2
+		elif event.type == pygame.KEYUP:
+			if event.key == pygame.K_RIGHT:
+				me.speed[0] = 0
+			elif event.key == pygame.K_LEFT:
+				me.speed[0] = 0
+			elif event.key == pygame.K_DOWN:
+				me.speed[1] = 0
+			elif event.key == pygame.K_UP:
+				me.speed[1] = 0
+
+	remove_objects()
+	
+	screen.fill(black) # colorier le fond en noir
+	
+	# Mettre à jour tous les objets
+	lines = []
+	for oid in objects:
+		obj = objects[oid]
+		obj.update()
+		obj.draw(screen)
+		obj.print_debug(lines)
+
+	remove_objects()
+	
+	xlog.display(screen, lines) # afficher le texte d'information
+	
+	pygame.display.flip() # mettre à jour l'écran
+	clock.tick(30) # attendre un peu