diff --git a/client/src/app/pages/game/game-page/game-page.component.html b/client/src/app/pages/game/game-page/game-page.component.html
index 2f8a1777cb67fa1ca9d81afd6f2d5bbc27af96bd..c17a68c9019526383bb588bc61b1070869d62c92 100644
--- a/client/src/app/pages/game/game-page/game-page.component.html
+++ b/client/src/app/pages/game/game-page/game-page.component.html
@@ -3,7 +3,7 @@
     <div id="game-info">
         <app-radial-timer size="65"></app-radial-timer>
         <div class="score-board">
-            <span class="name">{{ player.name }}</span>
+            <span class="name">{{ player.username }}</span>
             <span class="score">{{ player.score }}pts</span>
         </div>
         <app-chat-box id="chat-box"></app-chat-box>
diff --git a/client/src/app/pages/game/game-page/game-page.component.ts b/client/src/app/pages/game/game-page/game-page.component.ts
index 41049c155f7ef4d1c0d201e2ddc2159428f19ea7..9ac58e64d3d5d2f0730c45a4ddcb0025d411160f 100644
--- a/client/src/app/pages/game/game-page/game-page.component.ts
+++ b/client/src/app/pages/game/game-page/game-page.component.ts
@@ -18,7 +18,7 @@ export const DEFAULT_POINTS = 8;
     styleUrls: ['./game-page.component.scss'],
 })
 export class GamePageComponent implements OnInit {
-    player: Player = { name: 'Garry', score: 0 };
+    player: Player = { username: 'Gary', score: 0 };
     constructor(private playerService: PlayerService) {}
 
     ngOnInit(): void {
diff --git a/common/events/game.events.ts b/common/events/game.events.ts
index a8bafeab4b9a2870ec319604624b880c4ef96296..93af3d60611001e3075e7dd4418f30aa9190fb53 100644
--- a/common/events/game.events.ts
+++ b/common/events/game.events.ts
@@ -2,11 +2,16 @@ export enum GameServerEvent {
     Exception = 'exception',
     RedirectToWaitingRoom = 'redirect-to-waiting-room',
     StartDemo = 'start-demo',
+    NextQuestion = 'next-question',
+    TimesUp = 'times-up',
+    Results = 'results',
+    EndGame = 'end-game',
 }
 
 export enum GameClientEvent {
     Create = 'create',
     Demo = 'demo',
+    Answer = 'answer',
 }
 
 export type GameEvent = GameServerEvent | GameClientEvent;
diff --git a/common/player.ts b/common/player.ts
index 55fd72aaf6189da4638f63095c8cea44e1588ccd..aad64fd32b043bb769b2a7daf6dfa5e199aa45f4 100644
--- a/common/player.ts
+++ b/common/player.ts
@@ -1,4 +1,4 @@
 export type Player = {
-    name: string;
+    username: string;
     score: number;
 };
diff --git a/common/question.ts b/common/question.ts
index 2dc0d1eda83b97062d230062e3192f325d1af6f2..3f8f22b7e1bdf09f1ecde106694068dd32aa1925 100644
--- a/common/question.ts
+++ b/common/question.ts
@@ -33,11 +33,16 @@ export type Qcm = ConcreteQuestionType<CreateQcm>;
 export type Qrl = ConcreteQuestionType<CreateQrl>;
 export type Question = Qcm | Qrl;
 
-export type PlayableQcm = Omit<Qcm, 'choices'>;
-export type PlayableQrl = Qrl;
-export type PlayableQuestion = PlayableQcm | PlayableQrl;
-
 type StandaloneQuestionType<T extends Question> = T & { lastModification: Date };
 export type StandaloneQcm = StandaloneQuestionType<Qcm>;
 export type StandaloneQrl = StandaloneQuestionType<Qrl>;
 export type StandaloneQuestion = StandaloneQcm | StandaloneQrl;
+
+export type PlayableQcm = Omit<Qcm, 'choices'>;
+export type PlayableQrl = Qrl;
+export type PlayableQuestion = PlayableQcm | PlayableQrl;
+
+type GameQuestionType<T extends PlayableQuestion> = T & { index: number };
+export type GameQcmQuestion = GameQuestionType<PlayableQuestion> & { choices: string[] };
+export type GameQrlQuestion = GameQuestionType<PlayableQuestion>;
+export type GameQuestion = GameQcmQuestion | GameQrlQuestion;
diff --git a/common/results.ts b/common/results.ts
new file mode 100644
index 0000000000000000000000000000000000000000..985f17edc613baa82824a498bd04c8168fb2f8ff
--- /dev/null
+++ b/common/results.ts
@@ -0,0 +1,17 @@
+import { Player } from './player';
+import { QuestionType } from './question';
+
+interface BaseResults {
+    players: { [id: string]: Player };
+}
+
+export interface QcmResults extends BaseResults {
+    type: QuestionType.Qcm;
+    solution: boolean[];
+}
+
+export interface QrlResults extends BaseResults {
+    type: QuestionType.Qrl;
+}
+
+export type Results = QcmResults | QrlResults;
diff --git a/server/app/game/classes/room.class.constants.ts b/server/app/game/classes/room.class.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4233811f0aadb13b15316eb8164dcd436ff9e9ea
--- /dev/null
+++ b/server/app/game/classes/room.class.constants.ts
@@ -0,0 +1,3 @@
+export const BONUS_FACTOR = 1.2;
+export const DELAY_BETWEEN_QUESTIONS_MS = 3000;
+export const S_TO_MS = 1000;
diff --git a/server/app/game/classes/room.class.spec.ts b/server/app/game/classes/room.class.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4e1dfdf922e47b3f51b7b480075eacc498591ef1
--- /dev/null
+++ b/server/app/game/classes/room.class.spec.ts
@@ -0,0 +1,275 @@
+import { Game } from '@common/game';
+import { Room } from './room.class';
+import { Quiz } from '@app/quiz/schemas/quiz.schema';
+import { Server, Socket, BroadcastOperator } from 'socket.io';
+import type { DefaultEventsMap } from 'socket.io/dist/typed-events';
+import * as sinon from 'sinon';
+import { QuestionType } from '@common/question';
+import { WsException } from '@nestjs/websockets';
+
+jest.useFakeTimers();
+
+describe('Room', () => {
+    let server: sinon.SinonStubbedInstance<Server>;
+    let serverRoom: sinon.SinonStubbedInstance<Socket>;
+
+    beforeEach(() => {
+        server = sinon.createStubInstance<Server>(Server);
+        serverRoom = sinon.createStubInstance<Socket>(Socket);
+        server.to.callsFake(() => serverRoom as unknown as BroadcastOperator<DefaultEventsMap, unknown>);
+    });
+
+    it('should be defined', () => {
+        const room = new Room({} as Game, {} as Quiz, server);
+        expect(room).toBeDefined();
+    });
+
+    describe('addPlayer', () => {
+        it('should add a player', () => {
+            const room = new Room({} as Game, {} as Quiz, server);
+
+            const client = sinon.createStubInstance(Socket);
+
+            room.addPlayer(client, 'player1');
+
+            expect(client.join.calledOnceWithExactly(room.id)).toBe(true);
+
+            const players = room['players'];
+            expect(players.size).toBe(1);
+            expect(players.get(client.id)).toEqual({ username: 'player1', score: 0 });
+        });
+    });
+
+    describe('start', () => {
+        it('should start the game after 3 seconds', () => {
+            const room = new Room({} as Game, {} as Quiz, server);
+            const nextSpy = sinon.stub(room, 'next');
+
+            room.start();
+            expect(nextSpy.calledOnce).toBe(false);
+
+            // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+            jest.advanceTimersByTime(3000);
+
+            expect(nextSpy.calledOnce).toBe(true);
+        });
+    });
+
+    describe('next', () => {
+        let room: Room;
+        let timesUpStub: sinon.SinonStub;
+
+        beforeEach(() => {
+            room = new Room(
+                { id: 'gameid' } as Game,
+                {
+                    questions: [
+                        {
+                            type: QuestionType.Qcm,
+                            choices: [
+                                { text: 'choice1', isCorrect: true },
+                                { text: 'choice2', isCorrect: false },
+                            ],
+                        },
+                    ],
+                } as unknown as Quiz,
+                server,
+            );
+            room['currentQuestionIndex'] = -1;
+
+            timesUpStub = sinon.stub(room, 'timesUp');
+        });
+
+        it('should end the game if there are no more questions', () => {
+            room['currentQuestionIndex'] = 0;
+
+            const endSpy = sinon.stub(room, 'end');
+
+            room.next();
+            expect(endSpy.calledOnce).toBe(true);
+        });
+
+        it('should emit the next question', () => {
+            room['quiz'].questions[0] = { type: QuestionType.Qrl, _id: '', text: '', points: 0 };
+            room.next();
+            expect(server.to.calledOnceWith('gameid')).toBe(true);
+
+            expect(serverRoom.emit.calledOnce).toBe(true);
+            expect(serverRoom.emit.calledOnceWith('next-question')).toBe(true);
+            const gameQuestion = serverRoom.emit.firstCall.args[1];
+
+            expect(gameQuestion).toMatchObject({ index: 0, type: QuestionType.Qrl });
+        });
+
+        it('should emit the next QCM question with choices', () => {
+            room.next();
+            expect(server.to.calledOnceWith('gameid')).toBe(true);
+
+            expect(serverRoom.emit.calledOnce).toBe(true);
+            expect(serverRoom.emit.calledOnceWith('next-question')).toBe(true);
+            const gameQuestion = serverRoom.emit.firstCall.args[1];
+
+            expect(gameQuestion).toMatchObject({ index: 0, type: QuestionType.Qcm, choices: ['choice1', 'choice2'] });
+        });
+
+        it('should set a timeout for the answer period', () => {
+            room['quiz'].duration = 5;
+
+            room.next();
+            expect(room['currentQuestionTimeout']).toBeDefined();
+            expect(timesUpStub.called).toBe(false);
+
+            // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+            jest.advanceTimersByTime(4500);
+            expect(timesUpStub.called).toBe(false);
+
+            // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+            jest.advanceTimersByTime(500);
+            expect(timesUpStub.called).toBe(true);
+        });
+    });
+
+    describe('answer', () => {
+        let room: Room;
+        let sendResultsStub: sinon.SinonStub;
+
+        beforeEach(() => {
+            room = new Room(
+                {} as Game,
+                { questions: [{ type: QuestionType.Qcm, points: 100, choices: [{ isCorrect: true }] }] } as unknown as Quiz,
+                server,
+            );
+            room['currentQuestionIndex'] = 0;
+            room['players'] = new Map([['playerid', { username: 'player1', score: 0 }]]);
+            sendResultsStub = sinon.stub(room, 'sendResults');
+        });
+
+        it('should add points to the player if the answer is correct', () => {
+            room.answer('playerid', [true]);
+
+            // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+            expect(room['players'].get('playerid')?.score).toEqual(120);
+        });
+
+        it('should call sendResults after', () => {
+            room.answer('playerid', [true]);
+
+            expect(sendResultsStub.calledOnce).toBe(true);
+        });
+
+        it('should clear the timeout', () => {
+            const spy = sinon.spy();
+            // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+            room['currentQuestionTimeout'] = setTimeout(() => spy, 1000);
+
+            room.answer('playerid', [false]);
+
+            // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+            jest.advanceTimersByTime(1000);
+            expect(spy.called).toBe(false);
+        });
+
+        it('should throw if the player is not in the room', () => {
+            expect(() => room.answer('unknownplayerid', [false])).toThrowError(WsException);
+        });
+
+        it('should throw if the question is not a QCM', () => {
+            room['quiz'].questions[0].type = QuestionType.Qrl;
+            expect(() => room.answer('playerid', [false])).toThrowError(WsException);
+        });
+    });
+
+    describe('timesUp', () => {
+        it('should emit TimesUp', () => {
+            const room = new Room({} as Game, {} as Quiz, server);
+
+            room['currentQuestionIndex'] = 0;
+
+            room.timesUp();
+            expect(server.to.calledOnceWith(room.id)).toBe(true);
+            expect(serverRoom.emit.calledOnceWith('times-up')).toBe(true);
+        });
+    });
+
+    describe('sendResults', () => {
+        let room: Room;
+        let nextStub: sinon.SinonStub;
+
+        beforeEach(() => {
+            room = new Room(
+                {} as Game,
+                {
+                    questions: [{ type: QuestionType.Qcm, points: 100, choices: [{ isCorrect: true }, { isCorrect: false }, { isCorrect: true }] }],
+                } as unknown as Quiz,
+                server,
+            );
+            room['currentQuestionIndex'] = 0;
+            room['players'] = new Map([
+                ['playerid1', { username: 'player1', score: 10 }],
+                ['playerid2', { username: 'player2', score: 20 }],
+            ]);
+
+            nextStub = sinon.stub(room, 'next');
+        });
+
+        it('should emit the solution with a QCM', () => {
+            room.sendResults();
+
+            expect(server.to.calledOnceWith(room.id)).toBe(true);
+            expect(serverRoom.emit.calledOnce).toBe(true);
+            expect(serverRoom.emit.calledOnceWith('results')).toBe(true);
+
+            const results = serverRoom.emit.firstCall.args[1];
+            expect(results).toEqual({
+                type: QuestionType.Qcm,
+                players: {
+                    playerid1: { username: 'player1', score: 10 },
+                    playerid2: { username: 'player2', score: 20 },
+                },
+                solution: [true, false, true],
+            });
+        });
+
+        it('should only emit the score with a QRL', () => {
+            room['quiz'].questions[0].type = QuestionType.Qrl;
+
+            room.sendResults();
+
+            expect(server.to.calledOnceWith(room.id)).toBe(true);
+            expect(serverRoom.emit.calledOnce).toBe(true);
+            expect(serverRoom.emit.calledOnceWith('results')).toBe(true);
+
+            const results = serverRoom.emit.firstCall.args[1];
+            expect(results).toEqual({
+                type: QuestionType.Qrl,
+                players: {
+                    playerid1: { username: 'player1', score: 10 },
+                    playerid2: { username: 'player2', score: 20 },
+                },
+            });
+        });
+
+        it('should call next after 3 seconds', () => {
+            room.sendResults();
+            expect(nextStub.called).toBe(false);
+
+            // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+            jest.advanceTimersByTime(2500);
+            expect(nextStub.called).toBe(false);
+
+            // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+            jest.advanceTimersByTime(500);
+            expect(nextStub.calledOnce).toBe(true);
+        });
+    });
+
+    describe('end', () => {
+        it('should emit EndGame', () => {
+            const room = new Room({} as Game, {} as Quiz, server);
+
+            room.end();
+            expect(server.to.calledOnceWith(room.id)).toBe(true);
+            expect(serverRoom.emit.calledOnceWith('end-game')).toBe(true);
+        });
+    });
+});
diff --git a/server/app/game/classes/room.class.ts b/server/app/game/classes/room.class.ts
new file mode 100644
index 0000000000000000000000000000000000000000..13ffe34c2a440c3cca7bc9cd4608d42ec9074c81
--- /dev/null
+++ b/server/app/game/classes/room.class.ts
@@ -0,0 +1,132 @@
+import { Quiz } from '@common/quiz';
+import { Socket, Server } from 'socket.io';
+import { Question, QuestionType, GameQuestion } from '@common/question';
+import { Game } from '@common/game';
+import { Results } from '@common/results';
+import { Player } from '@common/player';
+import { GameServerEvent } from '@common/events/game.events';
+import { DELAY_BETWEEN_QUESTIONS_MS, S_TO_MS, BONUS_FACTOR } from './room.class.constants';
+import { WsException } from '@nestjs/websockets';
+
+export class Room {
+    readonly id: string;
+
+    private players: Map<string, Player> = new Map();
+
+    private currentQuestionIndex: number;
+    private currentQuestionTimeout?: NodeJS.Timeout;
+
+    constructor(
+        readonly game: Game,
+        private readonly quiz: Quiz,
+        private readonly server: Server,
+    ) {
+        this.id = game.id;
+    }
+
+    addPlayer(client: Socket, username: string) {
+        client.join(this.game.id);
+
+        this.players.set(client.id, { username, score: 0 });
+    }
+
+    start() {
+        this.currentQuestionIndex = -1;
+
+        setTimeout(() => {
+            this.next();
+        }, DELAY_BETWEEN_QUESTIONS_MS);
+    }
+
+    next() {
+        if (this.currentQuestionIndex === this.quiz.questions.length - 1) {
+            this.end();
+            return;
+        }
+
+        const question: Question = this.quiz.questions[++this.currentQuestionIndex];
+
+        let gameQuestion: GameQuestion;
+
+        if (question.type === QuestionType.Qcm) {
+            gameQuestion = {
+                ...question,
+                index: this.currentQuestionIndex,
+                type: question.type,
+                choices: question.choices.map((choice) => choice.text),
+            };
+        } else {
+            gameQuestion = {
+                ...question,
+                index: this.currentQuestionIndex,
+                type: question.type,
+            };
+        }
+
+        this.toAll().emit(GameServerEvent.NextQuestion, gameQuestion);
+
+        this.currentQuestionTimeout = setTimeout(() => {
+            this.timesUp();
+        }, this.quiz.duration * S_TO_MS);
+    }
+
+    answer(userId: string, answers: boolean[]) {
+        // FIXME: refactor when more than one player can answer
+        clearTimeout(this.currentQuestionTimeout);
+
+        const player = this.players.get(userId);
+
+        if (!player) {
+            throw new WsException('Player not found');
+        }
+
+        const question = this.quiz.questions[this.currentQuestionIndex];
+
+        if (question.type !== QuestionType.Qcm) {
+            throw new WsException('Not implemented');
+        }
+
+        const correct = question.choices.every((choice, index) => choice.isCorrect === answers[index]);
+
+        if (correct) {
+            player.score += question.points * BONUS_FACTOR;
+        }
+
+        this.sendResults();
+    }
+
+    timesUp() {
+        this.toAll().emit(GameServerEvent.TimesUp);
+    }
+
+    sendResults() {
+        const question = this.quiz.questions[this.currentQuestionIndex];
+
+        const players = Object.fromEntries(this.players);
+        const results: Results =
+            question.type === QuestionType.Qcm
+                ? {
+                      type: question.type,
+                      players,
+                      solution: question.choices.map((choice) => choice.isCorrect),
+                  }
+                : {
+                      type: question.type,
+                      players,
+                  };
+
+        this.toAll().emit(GameServerEvent.Results, results);
+
+        setTimeout(() => {
+            this.next();
+        }, DELAY_BETWEEN_QUESTIONS_MS);
+    }
+
+    end() {
+        this.toAll().emit(GameServerEvent.EndGame);
+    }
+
+    private toAll() {
+        return this.server.to(this.game.id);
+    }
+}
diff --git a/server/app/game/game-room.service.spec.ts b/server/app/game/game-room.service.spec.ts
index 4a2bbb1621cb80fa5a232d5d124c2976fe1354b5..6736e9bdac3c965f0ff3eecdf1f2cee13180b0e0 100644
--- a/server/app/game/game-room.service.spec.ts
+++ b/server/app/game/game-room.service.spec.ts
@@ -1,13 +1,19 @@
 import { Test, TestingModule } from '@nestjs/testing';
 import { GameRoomService } from './game-room.service';
 import { Quiz, QuizDocument } from '@app/quiz/schemas/quiz.schema';
-import { Game, GameType } from '@common/game';
-import { GameWithQuiz } from './interfaces/game.interface';
+import { GameType } from '@common/game';
+import * as sinon from 'sinon';
+import { Server } from 'socket.io';
+import * as roomModule from './classes/room.class';
+
+const roomConstructor = sinon.stub(roomModule, 'Room');
 
 describe('GameRoomService', () => {
     let service: GameRoomService;
 
     beforeEach(async () => {
+        roomConstructor.reset();
+
         const module: TestingModule = await Test.createTestingModule({
             providers: [GameRoomService],
         }).compile();
@@ -20,24 +26,29 @@ describe('GameRoomService', () => {
     });
 
     describe('create', () => {
+        let server: sinon.SinonStubbedInstance<Server>;
         let plainQuiz: Quiz;
         let quiz: QuizDocument;
-
-        let game: Game;
+        let room: roomModule.Room;
 
         beforeEach(() => {
+            server = sinon.createStubInstance<Server>(Server);
             plainQuiz = { title: 'foo', duration: 10 } as Quiz;
-
             quiz = {
                 title: plainQuiz.title,
                 duration: plainQuiz.duration,
                 toObject: () => quiz,
             } as unknown as QuizDocument;
-
-            game = service.create(quiz, GameType.Normal);
+            room = service.create(server, quiz, GameType.Normal);
         });
 
         it('should create a room', () => {
+            expect(roomConstructor.calledOnce).toBe(true);
+
+            const args = roomConstructor.getCall(0).args;
+
+            const game = args[0];
+
             expect(game.id).toContain('game-');
             // eslint-disable-next-line @typescript-eslint/no-magic-numbers
             expect(game.pin).toHaveLength(4);
@@ -45,30 +56,49 @@ describe('GameRoomService', () => {
             expect(game.title).toBe('foo');
             // eslint-disable-next-line @typescript-eslint/no-magic-numbers
             expect(game.duration).toBe(10);
+
+            expect(args[1]).toMatchObject(plainQuiz);
+            expect(args[2]).toBe(server);
         });
 
         it('should save the room', () => {
-            expect(service['rooms'].get(game.id)).toMatchObject({
-                quiz: plainQuiz,
-                type: GameType.Normal,
-                title: 'foo',
-                duration: 10,
-            });
+            const game = roomConstructor.getCall(0).args[0];
+            expect(service['rooms'].get(game.id)).toBe(room);
         });
 
         it('should create two different rooms for the same quiz', () => {
-            const game1 = service.create(quiz, GameType.Normal);
-            const game2 = service.create(quiz, GameType.Normal);
+            service.create(server, quiz, GameType.Normal);
+
+            const game1 = roomConstructor.getCall(0).args[0];
+            const game2 = roomConstructor.getCall(1).args[0];
 
             expect(game1.id).not.toEqual(game2.id);
         });
     });
 
+    describe('get', () => {
+        let room: roomModule.Room;
+
+        beforeEach(() => {
+            room = {} as roomModule.Room;
+
+            service['rooms'].set('foo', room);
+        });
+
+        it('should get a room', () => {
+            expect(service.get(new Set(['bar', 'foo']))).toBe(room);
+        });
+
+        it('should return undefined if the room does not exist', () => {
+            expect(service.get(new Set(['bar']))).toBeUndefined();
+        });
+    });
+
     describe('delete', () => {
         it('should delete a room', () => {
             const roomId = 'foo';
 
-            service['rooms'].set(roomId, {} as GameWithQuiz);
+            service['rooms'].set(roomId, {} as roomModule.Room);
 
             service.delete(roomId);
 
diff --git a/server/app/game/game-room.service.ts b/server/app/game/game-room.service.ts
index 2832498b88b8ff2b2f0152fc4f342fdca37a5ca0..9880647681804181a01e2b335f9944210855ab04 100644
--- a/server/app/game/game-room.service.ts
+++ b/server/app/game/game-room.service.ts
@@ -1,27 +1,38 @@
 import { QuizDocument } from '@app/quiz/schemas/quiz.schema';
-import { Game, GameType } from '@common/game';
+import { GameType } from '@common/game';
 import { GAME_ROOM_ID_END, GAME_ROOM_ID_START, GAME_ROOM_PREFIX } from './game-room.service.constants';
 import { Injectable } from '@nestjs/common';
-import { GameWithQuiz } from './interfaces/game.interface';
+import { Room } from './classes/room.class';
+import { Server } from 'socket.io';
 
 @Injectable()
 export class GameRoomService {
-    private rooms = new Map<string, GameWithQuiz>();
+    private rooms = new Map<string, Room>();
 
-    create(quiz: QuizDocument, type: GameType): Game {
+    create(server: Server, quiz: QuizDocument, type: GameType): Room {
         const pin = this.generatePin();
         const roomId = this.buildId(pin);
 
-        const game: Game = {
+        const game = {
             id: roomId,
             pin,
             type,
             title: quiz.title,
             duration: quiz.duration,
         };
-        this.rooms.set(roomId, { ...game, quiz: quiz.toObject() });
 
-        return game;
+        const room = new Room(game, quiz.toObject(), server);
+        this.rooms.set(roomId, room);
+
+        return room;
+    }
+
+    get(rooms: Set<string>): Room | undefined {
+        for (const roomId of rooms) {
+            if (this.rooms.has(roomId)) {
+                return this.rooms.get(roomId);
+            }
+        }
     }
 
     delete(roomId: string): void {
diff --git a/server/app/game/game.gateway.constants.ts b/server/app/game/game.gateway.constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0b70e02196d69b26b62bd8347179978c91e29069
--- /dev/null
+++ b/server/app/game/game.gateway.constants.ts
@@ -0,0 +1 @@
+export const GAME_CREATOR_USERNAME = 'Organisateur';
diff --git a/server/app/game/game.gateway.spec.ts b/server/app/game/game.gateway.spec.ts
index 70005bf6c478ac04ea4f11b2b2be067adcdd6b89..26e75469dbf59000825c02714f9e014f5aa64b21 100644
--- a/server/app/game/game.gateway.spec.ts
+++ b/server/app/game/game.gateway.spec.ts
@@ -1,19 +1,20 @@
 import { Test, TestingModule } from '@nestjs/testing';
 import { GameGateway } from './game.gateway';
 import * as sinon from 'sinon';
-import { QuizService } from '@app/quiz/quiz.service';
 import { Socket, Server, BroadcastOperator } from 'socket.io';
-import { Game, GameType } from '@common/game';
+import { GameType } from '@common/game';
 import { QuizDocument } from '@app/quiz/schemas/quiz.schema';
 import { Logger } from '@nestjs/common';
 import { GameRoomService } from './game-room.service';
 import { DefaultEventsMap } from 'socket.io/dist/typed-events';
 import { Writeable } from '@app/core/types/testing';
+import { Room } from './classes/room.class';
+import { AvailableQuizPipe } from './pipes/available-quiz.pipe';
+import { WsException } from '@nestjs/websockets';
 
 describe('GameGateway', () => {
     let gateway: GameGateway;
     let gameRoomService: sinon.SinonStubbedInstance<GameRoomService>;
-    let quizService: sinon.SinonStubbedInstance<QuizService>;
     let logger: sinon.SinonStubbedInstance<Logger>;
 
     let socket: sinon.SinonStubbedInstance<Socket>;
@@ -21,7 +22,6 @@ describe('GameGateway', () => {
 
     beforeEach(async () => {
         gameRoomService = sinon.createStubInstance<GameRoomService>(GameRoomService);
-        quizService = sinon.createStubInstance<QuizService>(QuizService);
         logger = sinon.createStubInstance<Logger>(Logger);
 
         socket = sinon.createStubInstance<Socket>(Socket);
@@ -34,12 +34,11 @@ describe('GameGateway', () => {
                     provide: GameRoomService,
                     useValue: gameRoomService,
                 },
-                {
-                    provide: QuizService,
-                    useValue: quizService,
-                },
             ],
-        }).compile();
+        })
+            .overridePipe(AvailableQuizPipe)
+            .useValue({ transform: (quiz: QuizDocument) => quiz })
+            .compile();
 
         gateway = module.get<GameGateway>(GameGateway);
         gateway['logger'] = logger;
@@ -51,46 +50,69 @@ describe('GameGateway', () => {
     });
 
     describe('create', () => {
-        it('should create a normal room and join the client', () => {
+        let room: sinon.SinonStubbedInstance<Room>;
+
+        beforeEach(() => {
             const quiz = { id: 'foo' } as QuizDocument;
-            const roomId = 'bar';
-            gameRoomService.create.withArgs(quiz, GameType.Normal).returns({ id: roomId } as Game);
+            room = sinon.createStubInstance(Room);
+            (room as Writeable<Room>).id = 'bar';
+            gameRoomService.create.withArgs(server, quiz, GameType.Normal).returns(room);
 
             gateway.create(quiz, socket);
+        });
 
-            expect(socket.join.calledWith(roomId)).toBe(true);
+        it('should create a room and join the client', () => {
+            expect(gameRoomService.create.calledOnce).toBe(true);
+            expect(room.addPlayer.calledWith(socket, 'Organisateur')).toBe(true);
         });
 
         it('should redirect the client to the waiting room', () => {
-            const quiz = { id: 'foo' } as QuizDocument;
-            const game = { id: 'bar' } as Game;
-            gameRoomService.create.withArgs(quiz, GameType.Normal).returns(game);
-
-            gateway.create(quiz, socket);
-
-            expect(socket.emit.calledWith('redirect-to-waiting-room', game)).toBe(true);
+            expect(socket.emit.calledWith('redirect-to-waiting-room', room.game)).toBe(true);
         });
     });
 
     describe('demo', () => {
-        it('should create a demo room and join the client', () => {
+        let room: sinon.SinonStubbedInstance<Room>;
+
+        beforeEach(() => {
             const quiz = { id: 'foo' } as QuizDocument;
-            const roomId = 'bar';
-            gameRoomService.create.withArgs(quiz, GameType.Demo).returns({ id: roomId } as Game);
+            room = sinon.createStubInstance(Room);
+            (room as Writeable<Room>).id = 'bar';
+            gameRoomService.create.withArgs(server, quiz, GameType.Demo).returns(room);
 
             gateway.demo(quiz, socket);
+        });
 
-            expect(socket.join.calledWith(roomId)).toBe(true);
+        it('should create a room and join the client', () => {
+            expect(gameRoomService.create.calledOnce).toBe(true);
+            expect(room.addPlayer.calledWith(socket, 'Organisateur')).toBe(true);
         });
 
         it('should start the demo for the client', () => {
-            const quiz = { id: 'foo' } as QuizDocument;
-            const game = { id: 'bar' } as Game;
-            gameRoomService.create.withArgs(quiz, GameType.Demo).returns(game);
+            expect(socket.emit.calledWith('start-demo', room.game)).toBe(true);
+        });
 
-            gateway.demo(quiz, socket);
+        it('should start the room', () => {
+            expect(room.start.calledOnce).toBe(true);
+        });
+    });
+
+    describe('answer', () => {
+        it('should get the room and answer', () => {
+            const room = sinon.createStubInstance(Room);
+            sinon.stub(socket, 'rooms').value(new Set(['foo']));
+            gameRoomService.get.withArgs(socket.rooms).returns(room);
+
+            gateway.answer([true, false], socket);
+
+            expect(room.answer.calledWith(socket.id, [true, false])).toBe(true);
+        });
+
+        it('should throw if the room is not found', () => {
+            sinon.stub(socket, 'rooms').value(new Set(['foo']));
+            gameRoomService.get.withArgs(socket.rooms).returns(undefined);
 
-            expect(socket.emit.calledWith('start-demo', game)).toBe(true);
+            expect(() => gateway.answer([true, false], socket)).toThrowError(WsException);
         });
     });
 
diff --git a/server/app/game/game.gateway.ts b/server/app/game/game.gateway.ts
index 5191d9ba85c59dbdbd6f4d9e57ac92b9b2fa5203..158e40b395cde9839e65d09c671d213fc403fef6 100644
--- a/server/app/game/game.gateway.ts
+++ b/server/app/game/game.gateway.ts
@@ -7,6 +7,7 @@ import {
     SubscribeMessage,
     WebSocketGateway,
     WebSocketServer,
+    WsException,
 } from '@nestjs/websockets';
 import { Socket, Server } from 'socket.io';
 import { GameClientEvent, GameServerEvent } from '@common/events/game.events';
@@ -14,6 +15,7 @@ import { AvailableQuizPipe } from './pipes/available-quiz.pipe';
 import { QuizDocument } from '@app/quiz/schemas/quiz.schema';
 import { GameType } from '@common/game';
 import { GameRoomService } from './game-room.service';
+import { GAME_CREATOR_USERNAME } from './game.gateway.constants';
 
 @WebSocketGateway({ cors: true, namespace: 'game' })
 export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
@@ -25,20 +27,33 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
 
     @SubscribeMessage(GameClientEvent.Create)
     create(@MessageBody(AvailableQuizPipe) quiz: QuizDocument, @ConnectedSocket() client: Socket) {
-        const game = this.gameRoomService.create(quiz, GameType.Normal);
-        client.join(game.id);
-        this.logger.log(`Game with quiz ${quiz.id} created: ${game.id}`);
+        const room = this.gameRoomService.create(this.server, quiz, GameType.Normal);
+        room.addPlayer(client, GAME_CREATOR_USERNAME);
 
-        client.emit(GameServerEvent.RedirectToWaitingRoom, game);
+        this.logger.log(`Game with quiz ${quiz.id} created: ${room.id}`);
+
+        client.emit(GameServerEvent.RedirectToWaitingRoom, room.game, GAME_CREATOR_USERNAME);
     }
 
     @SubscribeMessage(GameClientEvent.Demo)
     demo(@MessageBody(AvailableQuizPipe) quiz: QuizDocument, @ConnectedSocket() client: Socket) {
-        const game = this.gameRoomService.create(quiz, GameType.Demo);
-        client.join(game.id);
-        this.logger.log(`Demo game with quiz ${quiz.id} created: ${game.id}`);
+        const room = this.gameRoomService.create(this.server, quiz, GameType.Demo);
+        room.addPlayer(client, GAME_CREATOR_USERNAME);
+
+        this.logger.log(`Demo game with quiz ${quiz.id} created: ${room.id}`);
+
+        client.emit(GameServerEvent.StartDemo, room.game, GAME_CREATOR_USERNAME);
+        room.start();
+    }
+
+    @SubscribeMessage(GameClientEvent.Answer)
+    answer(@MessageBody() answers: boolean[], @ConnectedSocket() client: Socket) {
+        const room = this.gameRoomService.get(client.rooms);
+        if (!room) {
+            throw new WsException('Room not found');
+        }
 
-        client.emit(GameServerEvent.StartDemo, game);
+        room.answer(client.id, answers);
     }
 
     handleConnection(client: Socket) {
diff --git a/server/app/game/interfaces/game.interface.ts b/server/app/game/interfaces/game.interface.ts
deleted file mode 100644
index 4db0f7a901712b28048a4581428f4c84f8b1a179..0000000000000000000000000000000000000000
--- a/server/app/game/interfaces/game.interface.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { Quiz } from '@app/quiz/schemas/quiz.schema';
-import { Game } from '@common/game';
-
-export interface GameWithQuiz extends Game {
-    quiz: Quiz;
-}