socket.io, svelte 및 node를 사용하여 멀티플레이어 영화 퀴즈/퀴즈 게임을 만들어 봅시다. 데브로그 #6

사진은 그들이 말하는 천 단어보다 더 많은 것을 말해줍니다..



그럼, 사진 몇 장으로 시작해 보세요..

모바일용 시작 화면



따라서 이름과 아바타 시드를 입력해야 합니다.
이것은 로컬 저장소에 저장되므로 페이지를 다시 방문할 때 기억됩니다.

모바일용 로비



나중에 CSS 작업이 더 필요합니다. 지금은 충분합니다.

게임 시작을 위한 카운트다운이 있는 일반 로비




의문.



지금은 백엔드에 하드 코딩된 질문이 있습니다.

라운드 결과



라운드 결과를 보여줍니다. DNA는 응답하지 않았다는 의미이며, 플레이어는 시간이 다 되기 전에 응답하지 않았습니다. 정답은 ✔로, 오답은 ❌로 표시합니다.


css는 결코 완성되지 않았습니다.. 지금은 단지 텍스트보다 더 재미있는 것을 표시할 수 있도록 빠르게 함께 던지는 것입니다... ;)

암호



그래서 마지막 로그에서 게임의 다양한 상태 동안 연결 해제 처리를 제외하고 이전에 만든 회로도를 구현했다고 언급했습니다. 이것은 이제 수정되었습니다.
핸들러에서 필요한 코드를 함수로 간단히 추출하고 플레이어가 연결을 끊을 때 게임 상태에 따라 적절한 함수가 실행됩니다.

핸들러는 함수를 호출합니다.

//use disconnecting instead of discconnect so still have access to room of socket n stuff
        socket.on('disconnecting', () => {
            disconnecting(io, socket, games); 
        });


그리고 여기에 기능이 있습니다

function disconnecting(io, socket, games) {
    //check if player is in a game and if so remove them from the game..
    if (socket.rooms.size > 1) {
        for (const room of socket.rooms) {
            //seems stuff can be undefined for some time so added a check for this too.
            if (room !== socket.id && games.get(room) !== undefined) {
                const game = games.get(room);
                game?.leave(socket.id);

                //delete room if empty
                if (game?.players.size === 0) games.delete(room);
                else {
                    //notify the other players that the player has left the game
                    io.to(game.id).emit('player-left', socket.id);
                    //-----chek the state of the game and run needed function
                    switch (game.status) {
                        case 'waiting-for-start':
                            shouldGameStart(io, game);
                            break;
                        case 'waiting-for-ready':
                            shouldStartRound(io, game);
                            break;
                        case 'waiting-for-answer':
                            shouldEndRound(io, game);
                            break;
                    }
                }
                break;
            }
        }
    }
}


지금은 실제 질문이 없습니다. 테스트를 위해 일부 하드 코딩된 것입니다.

나중에 질문에 대한 일반적인 구조를 파악해야 합니다.

module.exports = async function generateQuestions(no) {
    const questions = [
        { question: 'What is the capital of the United States?', answers: ['Washington', 'New York', 'Los Angeles'], correctAnswer: 'Washington', type: 'pick-one' },
        { question: 'What is the capital of the United Kingdom?', answers: ['London', 'Manchester', 'Liverpool'], correctAnswer: 'London', type: 'pick-one' },
        { question: 'What is the capital of the Sweden?', answers: ['Stockholm', 'Oslo', 'Madrid'], correctAnswer: 'Stockholm', type: 'pick-one' },
    ];

    //select no random questions from the array
    const selectedQuestions = [];
    for (let i = 0; i < no; i++) {
        const randomIndex = Math.floor(Math.random() * questions.length);
        selectedQuestions.push(questions[randomIndex]);
    }
    console.log('q', selectedQuestions);
    return selectedQuestions;
};



Svelte 구성 요소는 여전히 단순합니다.
Start.svelte.. 먼저 표시되는 것, 여기서 u는 이름 n 아바타 n 원하는 작업(예: 게임 생성 또는 참여)을 선택합니다.

<script>
    import { onMount } from 'svelte';

    import { activeComponent, players, gameProps, playerId } from '../lib/stores';
    export let socket;

    let playername = '';
    let seed = '';

    //get player name and seed from localstorage
    onMount(() => {
        let player = localStorage.getItem('player');
        if (player !== null) {
            player = JSON.parse(player);
            // @ts-ignore
            playername = player?.name || ''; //these safeguards are really not needed i guess
            // @ts-ignore
            seed = player?.seed || '';
        }
    });

    //set player name and seed to localstorage when it chages
    $: {
        if (playername || seed) localStorage.setItem('player', JSON.stringify({ name: playername, seed: seed }));
    }

    function createGame() {
        let data = { name: playername, avatar: seed };
        socket.emit('create-game', data, (response) => {
            console.log(response.status);
            if (response.status === 'ok') {
                //set all the other response data in store.. playerId and gameData
                players.set(response.players);
                gameProps.set(response.gameData);
                playerId.set(response.playerId);
                //move to lobby
                activeComponent.set('lobby');
            }
        });
    }

    /// if find open game will join it
    function quickPlay() {
        socket.emit('quick-play', (response) => {
            if (response.gameId) {
                socket.emit('join-game', { gameId: response.gameId, name: playername, avatar: seed }, (response) => {
                    if (response.status === 'ok') {
                        //set all the other response data in store.. playerId and gameData
                        players.set(response.players);
                        gameProps.set(response.gameData);
                        playerId.set(response.playerId);
                        //move to lobby
                        activeComponent.set('lobby');
                    }
                });
            } else alert('no game found');
        });
    }

    function test() {
        socket.emit('test');
    }
</script>

<div class="wrapper">
    <h1>Let's Play!</h1>
    <img src={`https://avatars.dicebear.com/api/avataaars/:${seed}.svg`} alt="avatar" />

    <input type="text" placeholder="Enter name" bind:value={playername} />
    <input type="text" placeholder="Avatar Seed" bind:value={seed} />
    <div class="buttons" class:disabled={!seed || !playername}>
        <button on:click={createGame}>Create Game</button>
        <button on:click={quickPlay}>Quickplay</button>
        <button on:click={test}>List Games</button>
    </div>
</div>



Lobby.svelte .. 게임을 만들거나 참가한 후 끝나는 곳입니다.

<script>
    import { players, gameProps } from '../lib/stores';
    export let socket;
    export let currentCount;

    let clicked = false;

    function playerReady() {
        clicked = true;
        socket.emit('player-ready', $gameProps.id);
    }
</script>

<div class="component-wrapper">
    <h1>Lobby</h1>
    <h2>{$gameProps.id}</h2>

    <div class="player-wrapper">
        {#each $players as player}
            <div>
                <img src={player.avatar} alt="avatar" />
                <p>{player.name}</p>
            </div>
        {/each}
    </div>

    <button on:click|once={playerReady} disabled={clicked}>Ready</button>
    <progress value={currentCount} max={$gameProps.waitBetweenRound} />

    <h2>{currentCount > -1 ? currentCount : 'Waiting'}</h2>
</div>


질문 구성 요소는 게시할 가치조차 없으며 누를 때 서버에 내보낼 수 있는 다양한 답변이 있는 버튼을 생성할 뿐입니다.

RoundResult.svelte는 라운드 결과만 표시합니다.

<script>
    export let currentCount;
    export let roundResults;
    import { players } from '../lib/stores';
</script>

<h2>Round Results</h2>
<div class="player-wrapper">
    {#each $players as player}
        <div>
            <img src={player.avatar} alt="avatar" />
            <p>{player.name}</p>
            <p>{roundResults.find((x) => x.id == player.id).answer} <span>{roundResults.find((x) => x.id == player.id).answerCorrect ? '' : ''} </span></p>
        </div>
    {/each}
</div>
{#if currentCount > 0}
    <h2>{currentCount}</h2>
{/if}


엔딩 노트



아바타를 생성하려면 https://avatars.dicebear.com을 사용합니다. 멋진 물건!

pico.css을 가져왔기 때문에 모의를 하거나 모든 것을 직접 스타일링할 필요가 없어 시간이 절약됩니다.

이제 누락된 항목을 계속 구현하면 됩니다. 점수 시스템, 최종 게임 결과, 질문 생성기, 질문 구성 요소 등.. 지금은 괜찮다고 생각하지만 모든 논리가 의도한 대로 작동하는 것 같습니다.

끝났어.. 이제 진정하고 한국 시리즈 좀 봐..

좋은 웹페이지 즐겨찾기