diff --git a/tp8/bataille-carte/apl1test.py b/tp8/bataille-carte/apl1test.py
new file mode 100644
index 0000000000000000000000000000000000000000..8533ccaca5d99e7cfb83d6d86aa9334bb6a73a40
--- /dev/null
+++ b/tp8/bataille-carte/apl1test.py
@@ -0,0 +1,89 @@
+import thonnycontrib
+from thonnycontrib.backend.evaluator import Evaluator
+import thonnycontrib.backend.l1test_backend
+from thonny.plugins.cpython_backend.cp_back import MainCPythonBackend
+import thonnycontrib.backend.doctest_parser 
+from thonnycontrib.backend.doctest_parser import ExampleWithExpected, ExampleWithoutExpected
+import thonnycontrib.backend.ast_parser
+from thonnycontrib.backend.ast_parser import L1DocTest
+import thonnycontrib.backend.verdicts
+from thonnycontrib.backend.verdicts.ExceptionVerdict import ExceptionVerdict 
+
+import inspect
+import tempfile
+import os
+import sys
+        
+class MockBackend(MainCPythonBackend):
+    """
+    Fake backend.
+    """
+    def __init__(self):
+        ...
+
+    def send_message(self, msg) -> None:
+        ...
+
+# register backend
+thonnycontrib.backend.l1test_backend.BACKEND = MockBackend()
+
+def l1test_to_org(filename: str, source: str=""):
+    """
+    Return an org abstract of the tests presents in `filename` file.
+    """
+    abstract = {'total': 0,
+                'success': 0,
+                'failures': 0,
+                'errors': 0,
+                'empty': 0}
+
+    if source == "":
+        with open(filename, 'rt') as fin:
+            source = fin.read()
+    evaluator = Evaluator(filename=filename,
+                          source=source)
+    tests = evaluator.evaluate()
+    n = len(tests)
+    abstract['total'] = n
+    res = ""
+    for test in tests:
+        examples = test.get_examples()
+        res_examples = ""
+        nb_test, nb_test_ok = 0, 0
+        empty = True
+        for example in examples:
+            verdict = test.get_verdict_from_example(example)
+            if isinstance(example, ExampleWithExpected):
+                nb_test += 1
+                if verdict.isSuccess():
+                    nb_test_ok += 1
+                    abstract['success'] += 1
+                else:
+                    abstract['failures'] += 1
+                empty = False
+            if isinstance(verdict, ExceptionVerdict):
+                abstract['errors'] += 1
+                empty = False
+            res_examples += f"** {verdict}\n\n"
+            if not verdict.isSuccess():
+                res_examples += f"   {verdict.get_details()}\n\n"
+        if not empty: 
+            res += f"* {test.get_name()} ~ {nb_test_ok}/{nb_test} réussis\n\n"
+        else:
+            abstract['empty'] += 1
+            res += f"* {test.get_name()}\n\n Aucun test trouvé !\n\n"
+        res += res_examples
+    res = f"Tests exécutés : {abstract['total']}\nSuccès: {abstract['success']}, \
+Echecs: {abstract['failures']}, Erreurs: {abstract['errors']}, \
+Vide: {abstract['empty']}\n\n" + res
+    return res
+
+
+def testmod(modulename: str):
+    """
+    mimic the doctest.testmod function
+    for `modulename` module
+    """
+    print(l1test_to_org(modulename))
+
+
diff --git a/tp8/bataille-carte/apqueue.py b/tp8/bataille-carte/apqueue.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdc44f7d34f0c344ae262e89fb6af1a93e633d08
--- /dev/null
+++ b/tp8/bataille-carte/apqueue.py
@@ -0,0 +1,104 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+:mod:`apqueue` module
+
+:author: `FIL - Faculté des Sciences et Technologies -
+          Univ. Lille <http://portail.fil.univ-lille1.fr>`_
+
+:date: 2015, september
+:last revision: 2024, March
+
+A module for queue data structure.
+
+:Provides:
+
+* class ApQueue
+
+and methods
+
+* `enqueue`
+* `dequeue`
+* `is_empty`
+"""
+from typing import TypeVar
+T = TypeVar('T')
+
+class ApQueueEmptyError(Exception):
+    """
+    Exception for empty stacks
+    """
+    def __init__(self, msg):
+        self.message = msg
+
+
+class ApQueue():
+    """
+    $$$ ap_queue = ApQueue()
+    $$$ ap_queue.is_empty()
+    True
+    $$$ ap_queue.enqueue(1)
+    $$$ ap_queue.is_empty()
+    False
+    $$$ ap_queue.enqueue(2)
+    $$$ str(ap_queue)
+    '→2|1→'
+    $$$ ap_queue.dequeue()
+    1
+    $$$ ap_queue.dequeue()
+    2
+    $$$ ap_queue.is_empty()
+    True
+    $$e ap_queue.dequeue()
+    ApQueueEmptyError
+    """
+    ARROW = chr(0x2192)
+
+    def __init__(self):
+        """
+        build  a new empty queue
+        precondition: none
+        """
+        self.__content = []
+
+    def enqueue(self, elt: T):
+        """
+        insert an element at the begining of the queue
+        precondition: none
+        """
+        self.__content.insert(0, elt)
+
+    def dequeue(self) -> T:
+        """
+        return the element on top of self
+        Side effect: self contains an element less
+        precondition: self must be non empty
+        """
+        if len(self.__content) > 0:
+            res = self.__content.pop()
+        else:
+            raise ApQueueEmptyError('empty queue, nothing to dequeue')
+        return res
+
+    def is_empty(self) -> bool:
+        """
+        return:
+          * ``True`` if s is empty
+          * ``False`` otherwise
+        precondition: none
+        """
+        return self.__content == []
+
+    def __str__(self) -> str:
+        """
+        return the string representation of this queue.
+        """
+        return ApQueue.ARROW + \
+            "|".join(str(el) for el in self.__content) + \
+            ApQueue.ARROW
+
+
+if __name__ == '__main__':
+    import apl1test
+    apl1test.testmod('apqueue.py')
diff --git a/tp8/bataille-carte/apstack.py b/tp8/bataille-carte/apstack.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9a512110ce56dc25f67de8dd915988c88af22de
--- /dev/null
+++ b/tp8/bataille-carte/apstack.py
@@ -0,0 +1,126 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+:mod:`stack` module
+
+:author: `FIL - Faculté des Sciences et Technologies - 
+          Univ. Lille <http://portail.fil.univ-lille1.fr>`_
+
+:date: 2015, september
+:last revision: 2017, october
+
+A module for stack data structure.
+
+:Provides:
+
+* class ApStack
+
+and methods
+
+* `push`
+* `pop`
+* `top`
+* `is_empty`
+
+:Examples:
+"""
+from typing import TypeVar
+T = TypeVar('T')
+
+class ApStackEmptyError(Exception):
+    """
+    Exception for empty stacks
+    """
+    def __init__(self, msg):
+        self.message = msg
+
+
+class ApStack():
+    """
+    $$$ stak = ApStack()
+    $$$ stak.is_empty()
+    True
+    $$$ stak.push(1)
+    $$$ stak.is_empty()
+    False
+    $$$ stak.push(2)
+    $$$ stak.top()
+    2
+    $$$ stak.pop()
+    2
+    $$$ stak.top()
+    1
+    $$$ stak.pop()
+    1
+    $$$ stak.is_empty()
+    True
+    $$e stak.pop()
+    ApStackEmptyError
+    """
+    
+    def __init__(self):
+        """
+        build a new empty stack
+        précondition : none
+        """
+        self.__content = []
+        
+    def push(self, el: T):
+        """
+        add el on top of the stack.
+        précondition : none
+        """
+        self.__content.append(el)
+
+    def pop(self) -> T:
+        """
+        return the element on top of self
+        
+        Side effect: self contains an element less
+        
+        précondition : self must be non empty
+        """
+        if len(self.__content) == 0:
+            raise ApStackEmptyError('empty stack, nothing to pop')
+        return self.__content.pop()
+
+    def top(self) -> T:
+        """
+        return the element on top of self without removing it
+        
+        précondition : self must be non empty
+        """
+        if len(self.__content) == 0:
+            raise ApStackEmptyError('empty stack, nothing to pop')
+        return self.__content[-1]
+
+    def is_empty(self) -> bool:
+        """
+        return:
+           * ``True`` if s is empty
+           * ``False`` otherwise
+                précondition : none
+        """
+        return self.__content == []
+
+    def __str__(self) -> str:
+        """
+        return a stack representation 
+        """
+        mlen = 1
+        if not self.is_empty():
+            mlen = max(len(str(el)) for el in self.__content)
+        res = []
+        for el in self.__content:
+            pad = mlen - len(str(el))
+            left = pad // 2
+            right = pad - left
+            res.insert(0, "|" + " " * left + str(el) + " " * right + "|")
+        res.append("+" + "-" * mlen + "+")
+        return "\n".join(res)
+
+
+if (__name__ == '__main__'):
+    import apl1test
+    apl1test.testmod('apstack.py')
diff --git a/tp8/bataille-carte/card.py b/tp8/bataille-carte/card.py
new file mode 100755
index 0000000000000000000000000000000000000000..04c1532f95d2f3f75c6270c5fc844ccf7156faeb
--- /dev/null
+++ b/tp8/bataille-carte/card.py
@@ -0,0 +1,168 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+:mod:`card` module 
+
+:author: `FIL - Faculté des Sciences et Technologies - 
+         Univ. Lille <http://portail.fil.univ-lille1.fr>`_
+
+:date: 2017, september.
+:last revision: 2024, march
+
+"""
+from __future__ import annotations
+import random
+
+
+
+class Card(object):
+    """
+    Cards are defined by a value and a color.
+    Possible values and colors are listed in ``Card.VALUES`` and ``Card.COLORS``.
+
+    $$$ c1 = Card("Ace", "heart")
+    $$$ c1.color
+    'heart'
+    $$$ c1.value
+    'Ace'
+    $$$ repr(c1)
+    'Card("Ace", "heart")'
+    $$$ c2 = Card("King", "spade")
+    $$$ c2.value in Card.VALUES
+    True
+    $$$ c2.color in Card.COLORS
+    True
+    $$$ c1 == c1
+    True
+    $$$ c1 != c1
+    False
+    $$$ c1 < c1
+    False
+    $$$ c1 <= c1
+    True
+    """ 
+    # tuple of possible values and colors in ascending order
+    VALUES = ("Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Knight", "Queen", "King")
+    COLORS = ("spade", "heart", "diamond", "club")
+
+    def __init__(self, value: str, color: str):
+        """
+        creates a card with value and color
+
+        précondition : value in VALUES and color in COLORS
+        """
+        if value in Card.VALUES:
+            self.value = value
+        if color in Card.COLORS:
+            self.color = color
+
+    def __hash__(self) -> int:
+        """
+        Renvoie un haché de self.
+        """
+        return hash((self.color,self.value))
+
+    def __repr__(self) -> str:
+        """
+        return a string representation of the card
+    
+        $$$ repr(Card('Ace', 'heart'))
+        'Card("Ace", "heart")'
+        """
+        return f"(Card("f"{self.value}, "f"{self.color})"
+
+    def compare(self, card: Card) -> int:
+        """
+        compares cards.
+
+        Order on cards is defined  by order on values
+
+        return: 
+          
+           * a positive number if self is greater than card
+           * a negative number if self is lower than card
+           * 0 if self is the same than card
+
+        précondition: none
+        exemples: 
+
+        $$$ c1 = Card('Ace', 'heart')
+        $$$ c2 = Card('King', 'heart')
+        $$$ c3 = Card('Ace','spade')
+        $$$ c1bis = Card('Ace','heart')
+        $$$ c1.compare(c2) < 0
+        True
+        $$$ c2.compare(c1) > 0
+        True
+        $$$ c1.compare(c3) == 0
+        True
+        """
+        ...
+
+    @staticmethod
+    def deck(n_card: int) -> list[Card]:
+        """
+        return a list of `n_card` randomly chosen cards
+
+        precondition: n_card > 0 and n_card <= 4*13
+
+        Exemples:
+
+        $$$ cartes = Card.deck( 10 )
+        $$$ len(cartes) == 10
+        True
+        $$$ all( isinstance(c, Card) for c in cartes)
+        True
+        $$$ len(set(cartes))
+        len(cartes)
+        """
+        ...
+
+    def __eq__(self, card: Card) -> bool:
+        """
+        return True if self equals card
+               False otherwise
+        """
+        ...
+
+    def __neq__(self, card: Card) -> bool:
+        """
+        return True if self don't equal card
+               False otherwise
+        """
+        ...
+
+    def __lt__(self, card: Card) -> bool:
+        """
+        return True if self is strictly inferior to card
+               False otherwise
+        """
+        ...
+
+    def __le__(self, card: Card) -> bool:
+        """
+        return True if self is inferior or equal to card
+               False otherwise
+        """
+        ...
+
+    def __gt__(self, card: Card) -> bool:
+        """
+        return True if self is strictly superior to card
+               False otherwise
+        """
+        ...
+
+    def __ge__(self, card: Card) -> bool:
+        """
+        return True if self is superior or equal to card
+               False otherwise
+        """
+        ...
+
+
+if __name__ == '__main__':
+    import apl1test
+    apl1test.testmod('card.py')
+
diff --git a/tp8/bataille-carte/war.py b/tp8/bataille-carte/war.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1acaccd3e8c30fe38cf8862e58886e2ce5459e8
--- /dev/null
+++ b/tp8/bataille-carte/war.py
@@ -0,0 +1,91 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+:mod:`war` game
+
+:author: `FIL - Faculté des Sciences et Technologies -
+         Univ. Lille <http://portail.fil.univ-lille1.fr>`
+
+:date: 2021, april.
+:last revision: 2024, march.
+"""
+
+from card import Card
+from apqueue import *
+from apstack import *
+
+
+def distribute(n_card: int) -> tuple[ApQueue, ApQueue]:
+    """
+    renvoie un couple (m1, m2) constitué de deux files,
+    contenant pour chacune `n_card` cartes
+
+    precondition : n_card > 0
+    exemples :
+
+    $$$ m1, m2 = distribute( 4 )
+    $$$ len(m1) == 4
+    True
+    $$$ len(m2) == 4
+    True
+    $$$ type(m1) == ApQueue
+    True
+    $$$ type(m2) == ApQueue
+    True
+    $$$ carte = m1.dequeue()
+    $$$ isinstance(carte, Card)
+    True
+    """
+    ...
+
+def gather_stack(main: ApQueue, pile: ApStack) -> None:
+    """
+    ajoute les carte de la pile dans la main
+
+    exemples :
+
+    $$$ cartes = Card.deck(4)
+    $$$ main = ApQueue()
+    $$$ pile = ApStack()
+    $$$ for c in cartes:
+    ...     pile.push(c)
+    $$$ gather_stack( main, pile )
+    $$$ len( main ) == 4
+    True
+    $$$ all( main.dequeue() == cartes[ 3 - i ] for i in range(3))
+    True
+    """
+    ...
+
+def play_one_round(m1: ApQueue, m2: ApQueue, pile: ApStack) -> None:
+    """
+    Simule une étape du jeu :
+    `j1`` et ``j2`` prennent la première carte de leur
+    main. On compare les deux cartes :
+
+    * Si la carte de ``j1`` est supérieure à celle de ``j2``, alors
+    ``j1`` remporte toutes les cartes de la pile ;
+    * Si la carte de ``j1`` est inférieure à celle de ``j2``, alors
+    ``j2`` remporte toutes les cartes de la pile ;
+    * Si les cartes sont égales, alors elles sont *empilées* sur la
+      pile.
+
+    precondition : m1 et m2 ne sont pas vides
+    """
+    ...
+
+def play(n_card: int, n_round: int) -> None:
+    """
+    simule une partie de bataille
+
+    n_card: le nombre de cartes à distribuer à chaque joueur.
+    n_round: le nombre maximal de tours
+    """
+    ...
+
+
+if __name__ == "__main__":
+    import apl1test
+    apl1test.testmod("war.py")
+