HTML5 Tetris 게임 만들기 - (1) 뼈대 만들기

|

Tetris 게임 뼈대 구현

테트리스 게임을 만들기 전에 먼저 HTML5 canvas 위에 작은 박스를 그려보고, Keyboard 화살표 입력을 이용해서 이동을 할 수 있는 코드를 작성해보도록 하겠습니다.


tetris.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>;Tetris</title>

    <script src="/js/tetris.js"></script>
</head>
<body>
    <canvas id="canvas" width="240" height="480">
        이 브라우저는 HTML5 Canvas를 지원하지 않습니다.
    </canvas>

    <br>

    <script>
        window.onload = function() {
            init();

            draw();
        }
    </script>
</body>
</html>


js/tetris.js

var canvas;
var ctx;

var x, y;

function init() {
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');

    x = canvas.width / 2;
    y = 20;
}

function draw() {
    drawBackground();
    drawBlock();
}

function drawBackground() {
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function drawBlock() {
    ctx.fillStyle = 'orange';
    ctx.fillRect(x-10, y-10, 20, 20);
}

여기까지 코드를 작성하면 화면에 캔버스를 그리고 검정색 백그라운드에 오렌지색 박스를 하나 그리게 됩니다.


키보드 입력 이벤트 리스너 등록

function addKeyEventListener() {
    addEventListener('keydown', function(event) {
        switch(event.key) {
            case 'ArrowLeft':
                console.log("Left");
                x -=20;

                draw();
                break;

            case 'ArrowRight':
                console.log("Right");
                x += 20;

                draw();
                break;

            case 'ArrowUp':
                console.log("Up");
                y -= 20;

                draw();
                break;

            case 'ArrowDown':
                console.log("Down");
                y += 20;

                draw();
                break;
        }
    });
}

Kotlin Ktor 복수 클라이언트를 지원하는 WebSocket 예제

|

복수 클라이언트를 지원하는 WebSocket 예제


Main.kt

package com.snowdeer

import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.http.cio.websocket.DefaultWebSocketSession
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readText
import io.ktor.http.content.resources
import io.ktor.http.content.static
import io.ktor.response.respondFile
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.File
import java.time.Duration
import java.util.*
import kotlin.collections.LinkedHashSet

private val wsConnections = Collections.synchronizedSet(LinkedHashSet<DefaultWebSocketSession>())

fun Application.main() {

    install(WebSockets) {
        pingPeriod = Duration.ofSeconds(60) // Disabled (null) by default
        timeout = Duration.ofSeconds(15)
        maxFrameSize = Long.MAX_VALUE // Disabled (max value). The connection will be closed if surpassed this length.
        masking = false
    }

    routing {
        static("/static") {
            resources("static")
        }

        get("/") {
            val file = File("src/main/resources/static/index.html")
            call.respondFile(file)
        }

        webSocket("/chat") {
            println("[snowdeer] chat starts")
            wsConnections += this
            try {
                while (true) {
                    when (val frame = incoming.receive()) {
                        is Frame.Text -> {
                            val text = frame.readText()

                            for (conn in wsConnections) {
                                conn.outgoing.send(Frame.Text(text))
                            }
                        }
                    }
                }
            } finally {
                wsConnections -= this
            }

            println("[snowdeer] chat is finished")
        }
    }

    startCoroutine()
}

private fun startCoroutine() {
    println("[snowdeer] startCoroutine()")

    GlobalScope.launch {
        var count = 0L
        while (true) {
            println("Coroutine is running...")

            count++
            for (conn in wsConnections) {
                conn.outgoing.send(Frame.Text("hello($count)"))
            }

            delay(1000)
        }
    }
}

Kotlin Ktor HTML 파일 리턴하기

|

Ktor HTML 파일 리턴하기

앞서 작업했던 WebSocket 예제를 확장해서 사용합니다.


Main.kt

package com.snowdeer

import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.http.cio.websocket.DefaultWebSocketSession
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readText
import io.ktor.http.content.resources
import io.ktor.http.content.static
import io.ktor.response.respondFile
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket
import java.io.File
import java.time.Duration
import java.util.*
import kotlin.collections.LinkedHashSet

fun Application.main() {

    install(WebSockets) {
        pingPeriod = Duration.ofSeconds(60) // Disabled (null) by default
        timeout = Duration.ofSeconds(15)
        maxFrameSize = Long.MAX_VALUE // Disabled (max value). The connection will be closed if surpassed this length.
        masking = false
    }

    routing {
        static("/static") {
            resources("static")
        }

        get("/") {
            val file = File("src/main/resources/static/index.html")
            call.respondFile(file)
        }

        webSocket("/chat") {
            println("[snowdeer] chat starts")
            while (true) {
                when (val frame = incoming.receive()) {
                    is Frame.Text -> {
                        val text = frame.readText()
                        println("[snowdeer] text: $text")
                        outgoing.send(Frame.Text("$text from Server"))
                    }
                }
            }

            println("[snowdeer] chat is finished")
        }
    }
}


index.html

resources/static/index.html 파일을 생성하고 아래와 같은 내용을 채웁니다.

<!DOCTYPE HTML>

<html>
   <head>
      
      <script type = "text/javascript">
         function startWebSocket() {
            
            if ("WebSocket" in window) {
               alert("You can use WebSocket.");
               
               var ws = new WebSocket("ws://" + location.host + "/chat");
				
               ws.onopen = function() {
                  alert("WebSocket is opened.");
                  ws.send("Hello");
                  alert("Send message to Server(Hello).");
               };
				
               ws.onmessage = function (evt) { 
                  var msg = evt.data;
                  alert("Message is received(" + msg + ")");
               };
				
               ws.onclose = function() { 
                  alert("WebSocket is closed."); 
               };
            } else {
               alert("Your browser does not support WebSocket !!!");
            }
         }
      </script>
		
   </head>
   
   <body>
      <a href = "javascript:startWebSocket()">Start WebSocket</a>
   </body>
</html>

로컬 서버 주소는 var ws = new WebSocket("ws://localhost:8080/chat");으로 사용해도 되고, var ws = new WebSocket("ws://" + location.host + "/chat");으로 사용해도 됩니다.

Kotlin Ktor WebSocket 사용하기

|

Ktor WebSocket 사용하기

WebSocket를 이용하면 실시간 양방향 통신을 할 수 있습니다.


build.gradle

먼저 build.gradle에 다음 라이브러리를 추가해줍니다.

dependencies {
    implementation "io.ktor:ktor-websockets:$ktor_version"
}


Main.kt

routing은 다음 코드처럼 할 수 있으며, incoming.receive() 메소드와 outgoing.send(Frame.Text(text) 메소드는 블럭킹(Blocking) 메소드입니다.

package com.snowdeer

import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readText
import io.ktor.http.content.resources
import io.ktor.http.content.static
import io.ktor.routing.routing
import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket
import java.time.Duration

fun Application.main() {

    install(WebSockets) {
        pingPeriod = Duration.ofSeconds(60) // Disabled (null) by default
        timeout = Duration.ofSeconds(15)
        maxFrameSize = Long.MAX_VALUE // Disabled (max value). The connection will be closed if surpassed this length.
        masking = false
    }

    routing {
        static("/static") {
            resources("static")
        }

        webSocket("/chat") {
            println("[snowdeer] chat starts")
            while (true) {
                when (val frame = incoming.receive()) {
                    is Frame.Text -> {
                        val text = frame.readText()
                        println("[snowdeer] text: $text")
                        outgoing.send(Frame.Text("$text from Server"))
                    }
                }
            }

            println("[snowdeer] chat is finished")
        }
    }
}


client.html

다음 코드를 브라우저에서 실행해서 WebSocket 통신이 잘 되는지 확인할 수 있습니다.

<!DOCTYPE HTML>

<html>
   <head>
      
      <script type = "text/javascript">
         function startWebSocket() {
            
            if ("WebSocket" in window) {
               alert("You can use WebSocket.");
               
               var ws = new WebSocket("ws://localhost:8080/chat");
				
               ws.onopen = function() {
                  alert("WebSocket is opened.");
                  ws.send("Hello");
                  alert("Send message to Server(Hello).");
               };
				
               ws.onmessage = function (evt) { 
                  var msg = evt.data;
                  alert("Message is received(" + msg + ")");
               };
				
               ws.onclose = function() { 
                  alert("WebSocket is closed."); 
               };
            } else {
               alert("Your browser does not support WebSocket !!!");
            }
         }
      </script>
		
   </head>
   
   <body>
      <a href = "javascript:startWebSocket()">Start WebSocket</a>
   </body>
</html>

Kotlin Ktor CORS 설정

|

CORS 설정

fun Application.module() {
    install(CORS) {
        method(HttpMethod.Options)
        method(HttpMethod.Get)
        method(HttpMethod.Post)
        method(HttpMethod.Put)
        method(HttpMethod.Delete)
        method(HttpMethod.Patch)
        header(HttpHeaders.Authorization)
        allowCredentials = true
        anyHost()
    }
    // ...
}