반응형

스프링부트에서 소켓통신을 통한 채팅프로그램 만들기 2번째입니다.

 

이전까지의 소스는 아래 URL에서 참고해주세요.

https://myhappyman.tistory.com/100

 

SpringBoot - 스프링부트에서 채팅프로그램(소켓통신) 만들기-1

이번엔 소켓통신을 통하여 채팅프로그램을 스프링부트에서 만들어보겠습니다. 간단하게 프로젝트를 생성부터 채팅방 생성 및 채팅하는 과정까지 만들어보겠습니다. 소켓통신을 사용한 채팅프로그램 만들기 스프링..

myhappyman.tistory.com

 

 

 

1장에서는 단순하게 String 메시지 자체를 보냈는데, 이번엔 JSON형태로 메시지를 보내고 서버에서도 JSON형태의 메시지를 파싱하여 구분처리를 해보도록 하겠습니다.

추가적으로 내가보낸 메시지와 상대방을 구분해보겠습니다.

채팅구분처리


Server단

simple json 라이브러리 추가

pom.xml

<!-- json simple  -->
<dependency>
  <groupId>com.googlecode.json-simple</groupId>
  <artifactId>json-simple</artifactId>
  <version>1.1.1</version>
</dependency>
<!-- json simple  -->

pom.xml에 json파싱을 위해 json-simple 라이브러리를 추가합니다.

 

 

jsonToObjectParser 함수 추가

private static JSONObject JsonToObjectParser(String jsonStr) {
	JSONParser parser = new JSONParser();
	JSONObject obj = null;
	try {
		obj = (JSONObject) parser.parse(jsonStr);
	} catch (ParseException e) {
		e.printStackTrace();
	}
	return obj;
}

이전에 생성하였던 SocketHandler.java에 JSON파일이 들어오면 파싱해주는 함수를 추가하였습니다.

json형태의 문자열을 파라미터로 받아서 SimpleJson의 파서를 활용하여 JSONObject로 파싱처리를 해주는 함수입니다.

 

 

핸들러 로직 추가

SocketHandler.java

package com.psw.chating.handler;

import java.util.HashMap;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Component
public class SocketHandler extends TextWebSocketHandler {
	
	HashMap<String, WebSocketSession> sessionMap = new HashMap<>(); //웹소켓 세션을 담아둘 맵
	
	@Override
	public void handleTextMessage(WebSocketSession session, TextMessage message) {
		//메시지 발송
		String msg = message.getPayload();
		JSONObject obj = jsonToObjectParser(msg);
		for(String key : sessionMap.keySet()) {
			WebSocketSession wss = sessionMap.get(key);
			try {
				wss.sendMessage(new TextMessage(obj.toJSONString()));
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		//소켓 연결
		super.afterConnectionEstablished(session);
		sessionMap.put(session.getId(), session);
		JSONObject obj = new JSONObject();
		obj.put("type", "getId");
		obj.put("sessionId", session.getId());
		session.sendMessage(new TextMessage(obj.toJSONString()));
	}
	
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		//소켓 종료
		sessionMap.remove(session.getId());
		super.afterConnectionClosed(session, status);
	}
	
	private static JSONObject jsonToObjectParser(String jsonStr) {
		JSONParser parser = new JSONParser();
		JSONObject obj = null;
		try {
			obj = (JSONObject) parser.parse(jsonStr);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return obj;
	}
}

1. 소켓이 연결되면 동작하는 afterConnectionEstablished() 메소드부분에 로직이 추가되었습니다.

생성된 세션을 저장하면 발신메시지의 타입은 getId라고 명시 후 생성된 세션ID값을 클라이언트단으로 발송합니다.

클라이언트단에서는 type값을 통해 메시지와 초기 설정값을 구분할 예정입니다.

 

2. 메시지 전송시 JSON파싱을 위해 message.getPayload()를 통해 받은 문자열을 만든 함수 jsonToObjectParser에 넣어서 JSONObject값으로 받아서 강제 문자열 형태로 보내주는부분이 추가되었습니다.

 

 

Client 단

이제 발송하는 chat.jsp를 수정하겠습니다.

 

클라이언트단 뷰페이지 추가 및 json형태로 요청 및 파싱하기

chat.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<meta charset="UTF-8">
	<title>Chating</title>
	<style>
		*{
			margin:0;
			padding:0;
		}
		.container{
			width: 500px;
			margin: 0 auto;
			padding: 25px
		}
		.container h1{
			text-align: left;
			padding: 5px 5px 5px 15px;
			color: #FFBB00;
			border-left: 3px solid #FFBB00;
			margin-bottom: 20px;
		}
		.chating{
			background-color: #000;
			width: 500px;
			height: 500px;
			overflow: auto;
		}
		.chating .me{
			color: #F6F6F6;
			text-align: right;
		}
		.chating .others{
			color: #FFE400;
			text-align: left;
		}
		input{
			width: 330px;
			height: 25px;
		}
		#yourMsg{
			display: none;
		}
	</style>
</head>

<script type="text/javascript">
	var ws;

	function wsOpen(){
		ws = new WebSocket("ws://" + location.host + "/chating");
		wsEvt();
	}
		
	function wsEvt() {
		ws.onopen = function(data){
			//소켓이 열리면 동작
		}
		
		ws.onmessage = function(data) {
			//메시지를 받으면 동작
			var msg = data.data;
			if(msg != null && msg.trim() != ''){
				var d = JSON.parse(msg);
				if(d.type == "getId"){
					var si = d.sessionId != null ? d.sessionId : "";
					if(si != ''){
						$("#sessionId").val(si); 
					}
				}else if(d.type == "message"){
					if(d.sessionId == $("#sessionId").val()){
						$("#chating").append("<p class='me'>나 :" + d.msg + "</p>");	
					}else{
						$("#chating").append("<p class='others'>" + d.userName + " :" + d.msg + "</p>");
					}
						
				}else{
					console.warn("unknown type!")
				}
			}
		}

		document.addEventListener("keypress", function(e){
			if(e.keyCode == 13){ //enter press
				send();
			}
		});
	}

	function chatName(){
		var userName = $("#userName").val();
		if(userName == null || userName.trim() == ""){
			alert("사용자 이름을 입력해주세요.");
			$("#userName").focus();
		}else{
			wsOpen();
			$("#yourName").hide();
			$("#yourMsg").show();
		}
	}

	function send() {
		var option ={
			type: "message",
			sessionId : $("#sessionId").val(),
			userName : $("#userName").val(),
			msg : $("#chatting").val()
		}
		ws.send(JSON.stringify(option))
		$('#chatting').val("");
	}
</script>
<body>
	<div id="container" class="container">
		<h1>채팅</h1>
		<input type="hidden" id="sessionId" value="">
		
		<div id="chating" class="chating">
		</div>
		
		<div id="yourName">
			<table class="inputTable">
				<tr>
					<th>사용자명</th>
					<th><input type="text" name="userName" id="userName"></th>
					<th><button onclick="chatName()" id="startBtn">이름 등록</button></th>
				</tr>
			</table>
		</div>
		<div id="yourMsg">
			<table class="inputTable">
				<tr>
					<th>메시지</th>
					<th><input id="chatting" placeholder="보내실 메시지를 입력하세요."></th>
					<th><button onclick="send()" id="sendBtn">보내기</button></th>
				</tr>
			</table>
		</div>
	</div>
</body>
</html>

 

 

아래는 chat.jsp의 로직 설명입니다.


css부분은 따로 설정하진 않겠습니다.

1. html 소스부분은 현재의 세션값을 저장해놓기 위해 sessionId가 ID인 input태그를 추가하였습니다.

2. send() 함수의 발송하기전에 단순 String데이터가 아닌 obj값으로 값을 세팅하고 JSON형태로 발신처리로 변경되었습니다.

메시지를 보낼땐 type값을 message로 구분하여 발송합니다.

 

3. Socket.onmessage의 메시지 전달시 받는 로직이 변경되었습니다.

서버에서도 데이터를 JSON형태로 전달해주기 때문에 받은 데이터를 JSON.parse메소드를 활용하여 파싱을 합니다.

파싱한 객체의 type값을 확인하여 getId값이면 초기 설정된 값이므로 채팅창에 값을 입력하는게 아니라 추가한 태그 sessionId에 값을 세팅해줍니다.

id값은 소켓이 종료되기 전까지 자기자신을 구분할 수 있는 session값이 될 예정입니다.

 

4. type이 message인 경우엔 채팅이 발생한 경우입니다.

상대방과 자신을 구분하기 위해 여기서 sessionId값을 사용합니다.

최초 이름을 입력하고 연결되었을때, 발급받은 sessionId값을 비교하여 같다면 자기 자신이 발신한 메시지이므로 오른쪽으로 정렬하는 클래스를 처리하고 메시지를 출력합니다.

비교하여 같지 않다면 타인이 발신한 메시지이므로 왼쪽으로 정렬하는 클래스를 처리하고 메시지를 출력합니다.

 

 

다음장에서는 방구분 처리를 해보겠습니다.

 

 

반응형
반응형

이번엔 소켓통신을 통하여 채팅프로그램을 스프링부트에서 만들어보겠습니다.

 

프로젝트를 생성해서 단순한 채팅방과 추가적으로 방생성에 따른 채팅 구분 등의 과정까지 만들어보겠습니다.

 

 

소켓통신을 사용한 채팅프로그램 만들기

스프링부트 프로젝트 생성하기

먼저 프로젝트부터 만들어야겠죠?

Spring Starter Project로 생성합니다.

 

값을 채워넣었습니다.

프로젝트명, group, artifact, package명 등 위 내용으로 채워넣었습니다.

생성하고자 하는 프로젝트로 만드세요~

 

WebSocket 사용

라이브러리는 WebSocket을 사용할거니까 검색 후 추가해주고, 완료해주겠습니다.

 

스프링부트 프로젝트 생성!

 

부트 프로젝트 설정하기(pom.xml)

view페이지는 jsp를 사용할 예정입니다.

사용을 위해 pom.xml 설정을 하겠습니다. 아래의 dependency도 추가해주세요.

 

pom.xml

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- View JSP -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>
<!-- View JSP -->

 

 

다음은 properties의 설정값을 추가하겠습니다.

톰캣 포트를 80으로 변경하고, jsp를 바라볼 수 있도록 설정하겠습니다.

또한, 재시작없이 jsp가 적용되는 설정까지 하도록 하겠습니다.

 

application.properties

 

resources/application.properties

#Tomcat Server Setting
server.port=80

#JSP, HTML ModelAndView Path Setting
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

#JSP to Modify Not Restart Server
server.servlet.jsp.init-parameters.development=true

 

 

뷰 설정

디렉토리 생성 및 chat.jsp파일 생성

뷰 페이지 구조

뷰페이지를 생성합니다.

이제 컨트롤러를 구성하여 넘어가는 것을 보고 바로 소켓통신 설정을 진행해보겠습니다.

 

 

컨트롤러 추가

MainController 추가

controller패키지 추가 및 MainContoller.java파일을 생성합니다.

package com.psw.chating.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MainController {
	
	@RequestMapping("/chat")
	public ModelAndView chat() {
		ModelAndView mv = new ModelAndView();
		mv.setViewName("chat");
		return mv;
	}
}

chat파일을 넘겨주는 view컨트롤러를 생성 후 서버 구동하여 정상적으로 jsp페이지에 접근이 되는지 확인합니다.

 

127.0.0.1/chat 접근

localhost/chat으로 접근하니 정상적으로 chat.jsp페이지에 접근하는 모습을 볼 수 있습니다.

이제 본격적으로 WebSocket처리를 연결하고 통신 예제를 진행해보겠습니다.

 

WebSocket 설정

웹소켓 구현체와 구현체등록

웹소켓 구현체와 등록해주는 config파일을 생성해보겠습니다. 위 그림처럼 패키지와 자바파일을 생성합니다.

 

SocketHandler.java

@Component
public class SocketHandler extends TextWebSocketHandler {
	
	@Override
	public void handleTextMessage(WebSocketSession session, TextMessage message) {
		//메시지 발송
	}
	
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		//소켓 연결
	}
	
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		//소켓 종료
	}
}

구현체에 등록할 SocketHandler입니다.

afterConnectionEstablished 메소드는 웹소켓 연결이 되면 동작합니다.

acafterConnectionClosed 메소드는 반대로 웹소켓이 종료되면 동작합니다.

handleTextMessage 메소드는 메시지를 수신하면 실행됩니다. 상속받은 TextWebSocketHandler는 handleTextMessage를 실행시키며, 메시지 타입에따라 handleBinaryMessage또는 handleTextMessage가 실행됩니다.

 

소스를 완성해보겠습니다.

 

SocketHandler.java

package com.psw.chating.handler;

import java.util.HashMap;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Component
public class SocketHandler extends TextWebSocketHandler {
	
	HashMap<String, WebSocketSession> sessionMap = new HashMap<>(); //웹소켓 세션을 담아둘 맵
	
	@Override
	public void handleTextMessage(WebSocketSession session, TextMessage message) {
		//메시지 발송
		String msg = message.getPayload();
		for(String key : sessionMap.keySet()) {
			WebSocketSession wss = sessionMap.get(key);
			try {
				wss.sendMessage(new TextMessage(msg));
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		//소켓 연결
		super.afterConnectionEstablished(session);
		sessionMap.put(session.getId(), session);
	}
	
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		//소켓 종료
		sessionMap.remove(session.getId());
		super.afterConnectionClosed(session, status);
	}
}

 

 

 

구현체를 이제 등록해보겠습니다.

WebSocketConfig.java

package com.psw.chating.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import com.psw.chating.handler.SocketHandler;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{

	@Autowired
	SocketHandler socketHandler;
	
	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(socketHandler, "/chating");
	}
}

먼저 생성하였던 구현체를 등록하는 부분입니다.

 

마지막으로 chat.jsp를 완성하고 메시지가 주고 받아지는지 확인해보겠습니다.

 

chat.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<meta charset="UTF-8">
	<title>Chating</title>
	<style>
		*{
			margin:0;
			padding:0;
		}
		.container{
			width: 500px;
			margin: 0 auto;
			padding: 25px
		}
		.container h1{
			text-align: left;
			padding: 5px 5px 5px 15px;
			color: #FFBB00;
			border-left: 3px solid #FFBB00;
			margin-bottom: 20px;
		}
		.chating{
			background-color: #000;
			width: 500px;
			height: 500px;
			overflow: auto;
		}
		.chating p{
			color: #fff;
			text-align: left;
		}
		input{
			width: 330px;
			height: 25px;
		}
		#yourMsg{
			display: none;
		}
	</style>
</head>

<script type="text/javascript">
	var ws;

	function wsOpen(){
		ws = new WebSocket("ws://" + location.host + "/chating");
		wsEvt();
	}
		
	function wsEvt() {
		ws.onopen = function(data){
			//소켓이 열리면 초기화 세팅하기
		}
		
		ws.onmessage = function(data) {
			var msg = data.data;
			if(msg != null && msg.trim() != ''){
				$("#chating").append("<p>" + msg + "</p>");
			}
		}

		document.addEventListener("keypress", function(e){
			if(e.keyCode == 13){ //enter press
				send();
			}
		});
	}

	function chatName(){
		var userName = $("#userName").val();
		if(userName == null || userName.trim() == ""){
			alert("사용자 이름을 입력해주세요.");
			$("#userName").focus();
		}else{
			wsOpen();
			$("#yourName").hide();
			$("#yourMsg").show();
		}
	}

	function send() {
		var uN = $("#userName").val();
		var msg = $("#chatting").val();
		ws.send(uN+" : "+msg);
		$('#chatting').val("");
	}
</script>
<body>
	<div id="container" class="container">
		<h1>채팅</h1>
		<div id="chating" class="chating">
		</div>
		
		<div id="yourName">
			<table class="inputTable">
				<tr>
					<th>사용자명</th>
					<th><input type="text" name="userName" id="userName"></th>
					<th><button onclick="chatName()" id="startBtn">이름 등록</button></th>
				</tr>
			</table>
		</div>
		<div id="yourMsg">
			<table class="inputTable">
				<tr>
					<th>메시지</th>
					<th><input id="chatting" placeholder="보내실 메시지를 입력하세요."></th>
					<th><button onclick="send()" id="sendBtn">보내기</button></th>
				</tr>
			</table>
		</div>
	</div>
</body>
</html>

 

동작 확인

3개의 브라우저 생성

부트 어플리케이션을 구동하고 localhost/chat으로 접근하면 위와같은 뷰를 확인 할수 있습니다.

혼자서 3개의 브라우저를 열고 사용자명을 입력 후 테스트를 해보았습니다.

채팅 테스트

 

다음장에는 상대방과 자신을 구분을 처리해보겠습니다.

반응형
반응형

예전에 포스팅한 amchart에서 svg로 표현된 svg파일을 가져와서 svg코드를 수정거나 CSS 변경으로 적용했던 부분을 많이 보러 오시는걸 보고 한국지도를 찾는분들이 많이 계시는걸 알게 되었습니다.

 

이번에는 d3 라이브러리와 맵정보가 담긴 geoJson파일을 읽고 파싱하여 지도의 형태의 도형을 그려 넣을 것입니다. 그려 넣은 지도에 클릭 이벤트, 줌 이벤트 등을 추가할 수 있으며 이를 만들어보겠습니다.

추가적으로 이번엔 웹서버가 필요합니다.

was서버로 유명한 tomcat을 쓰셔도 좋고 간단하게 웹서버를 올릴 수 있는 http-server를 쓰셔도 됩니다. 

 

데이터 시각화를 지원하는 수많은 라이브러리 중에 d3라는 강력한 라이브러리가 있습니다.

데이터를 차트화도 시켜주고 이미지화 지도 등등 다양하게 표현이 가능합니다.

 

특히, 우리가 일반적으로 사용하는 차트형태가 아닌 topology차트와 같은 기하학적인 형태(트리형 표현, 노드 구성도 등)의 차트도 표현이 가능합니다.

 

이렇게 좋은 기능을 제공하는 d3이지만 개인적으로 안좋은 점(?), 힘든점으로 첫 번째 버전업에 따른 변경되는 메소드명, 파라미터등입니다.

버전이 올라감에 따라 과거버전에서 사용된 메소드가 사라지거나 이름이 바뀌는데... 버전 별로 어떤 기능을 쓸 때, 사용해야 하는 메소드명이라던지... 파라미터를 매번 다시 찾아봐야하는 점이 힘듭니다.

두번째는 기능이 많다보니 공식 API문서도 따로 공부를 해야할 정도로 양도 방대하고 영문으로 되어있습니다...(영어를 잘하는분들이 부럽습니다ㅜ.ㅜ) 엄청 깊게 사용해야할게 아니면 따로 공부보단 그때마다 사용해야할 메소드를 찾아서 적용하는편이 나을 것 같습니다.

 

개인적으로 느낀 d3에 대한 설명은 이정도로 하고 진행해보겠습니다.

 

 


d3를 활용한 한국 지도 만들기

 

한국 지도 gif...

만들어볼 한국지도의 UI 형태입니다.

카카오맵이나 구글지도, 오픈레이어스와 같은 형태가 아니니 참고바랍니다.

 

아래는 준비물입니다.

- d3(3.1.7 ver)

- was서버(http-server, node, tomcat등)

- 한국 지도의 데이터가 담긴 geoJSON파일

d3는 3.1.7버전으로 개발을 했었네요... 현재 최신 버전의 d3를 다운받아서 사용하시면 아마 메소드가 충돌나거나 에러가 발생하여 정상적으로 동작하지 않을겁니다.

 

geoJSON파일은 구글링을 하시면 많이 찾으실수 있는데 통계청을 통해 사용하셔도 되고, 아래 URL

https://github.com/vuski/admdongkor

 

vuski/admdongkor

대한민국 행정동 경계 파일. Contribute to vuski/admdongkor development by creating an account on GitHub.

github.com

이분의 github에서 받은 데이터를 QGIC3 툴을 활용하여 잘라서 사용하셔도 됩니다.

실제로 이번포스팅에는 없지만 지역별로 나눠야 하는 작업이 있어서 한국지도를 QGIC3툴로 잘라서 사용했습니다.

 

아래 2개 파일은 과거 d3버전이나, json파일 받는게 어려운 분들을 위해 따로 첨부합니다.

d3.js
0.29MB
korea.json
1.31MB

 


korea.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8" />
        <title>koreaMap</title>
        <link rel="stylesheet" href="css/korea.css" />
    </head>
    <script type="text/javascript" src="js/d3.js"></script>
    <script type="text/javascript" src="js/korea.js"></script>
    <body>
        <div id="container"></div>
    </body>
</html>

html은 간단합니다. container라는곳에 데이터를 파싱하여 한국 지도를 넣을겁니다.

css는 korea.css라는 파일에서 간단하게 정의할겁니다. hover와 같은 이벤트들~

 

이제 동작을 위해 d3라이브러리를 추가해야겠죠? 소스를 동작시킬 korea.js도 연결하였습니다.

 

korea.css, korea.js를 만들어보겠습니다.

 

korea.css

@charset "UTF-8";

#container {
	width: 700px;
	min-height: 700px;
	float: left;
	margin: 15px 35px;
}
#states path {
	fill: #585858;
	stroke: #000000;
	stroke-width: 1.5px;
}

#states path:hover {
	fill: #009300;
}

#states .active {
	fill: #00B700;
}

#states .activeDetail {
	fill: #00B700;
}

#states path {
	cursor: pointer;
}

#states text {
	cursor: pointer;
	font-size: 12px;
	fill: #fff;
}

지역별 색상과 hover 이벤트 글씨 색상등을 처리했습니다. 이번에도 svg로 그려지기때문에 fill, stroke등의 속성이 사용되었습니다.

 

korea.js

window.onload = function() {
    drawMap('#container');
};

//지도 그리기
function drawMap(target) {
    var width = 700; //지도의 넓이
    var height = 700; //지도의 높이
    var initialScale = 5500; //확대시킬 값
    var initialX = -11900; //초기 위치값 X
    var initialY = 4050; //초기 위치값 Y
    var labels;

    var projection = d3.geo
        .mercator()
        .scale(initialScale)
        .translate([initialX, initialY]);
    var path = d3.geo.path().projection(projection);
    var zoom = d3.behavior
        .zoom()
        .translate(projection.translate())
        .scale(projection.scale())
        .scaleExtent([height, 800 * height])
        .on('zoom', zoom);

    var svg = d3
        .select(target)
        .append('svg')
        .attr('width', width + 'px')
        .attr('height', height + 'px')
        .attr('id', 'map')
        .attr('class', 'map');

    var states = svg
        .append('g')
        .attr('id', 'states')
        .call(zoom);

    states
        .append('rect')
        .attr('class', 'background')
        .attr('width', width + 'px')
        .attr('height', height + 'px');

    //geoJson데이터를 파싱하여 지도그리기
    d3.json('json/korea.json', function(json) {
        states
            .selectAll('path') //지역 설정
            .data(json.features)
            .enter()
            .append('path')
            .attr('d', path)
            .attr('id', function(d) {
                return 'path-' + d.properties.name_eng;
            });

        labels = states
            .selectAll('text')
            .data(json.features) //라벨표시
            .enter()
            .append('text')
            .attr('transform', translateTolabel)
            .attr('id', function(d) {
                return 'label-' + d.properties.name_eng;
            })
            .attr('text-anchor', 'middle')
            .attr('dy', '.35em')
            .text(function(d) {
                return d.properties.name;
            });
    });

    //텍스트 위치 조절 - 하드코딩으로 위치 조절을 했습니다.
    function translateTolabel(d) {
        var arr = path.centroid(d);
        if (d.properties.code == 31) {
            //서울 경기도 이름 겹쳐서 경기도 내리기
            arr[1] +=
                d3.event && d3.event.scale
                    ? d3.event.scale / height + 20
                    : initialScale / height + 20;
        } else if (d.properties.code == 34) {
            //충남은 조금 더 내리기
            arr[1] +=
                d3.event && d3.event.scale
                    ? d3.event.scale / height + 10
                    : initialScale / height + 10;
        }
        return 'translate(' + arr + ')';
    }

    function zoom() {
        projection.translate(d3.event.translate).scale(d3.event.scale);
        states.selectAll('path').attr('d', path);
        labels.attr('transform', translateTolabel);
    }
}

d3사용으로 인해 여러 메소드들이 사용되었습니다.

d3의 메소드들을 다 설명하기엔 너무 길어지므로 공식 홈페이지의 API들이나 다른 예제들을 참조하여 추가해보시면 좋을것 같습니다.

 

지역명을 입력하는 label부분에서 서로 중간지점이 겹쳐서 글자를 제대로 알아보지 못하는 현상이 발생해서 

공통으로 사용하는 trnslateTolabel이라는 함수에서 약간의 커스텀이 처리되었습니다.

 

소스 작성이 완료되었으면 이제 실행을 해봐야겠죠?

웹 서버가 필요합니다!

톰캣같은 was로 개발하고 계신분들은 확인시 문제가 없겠지만 서버없이 html을 실행하게 되면 이런 에러가 발생할겁니다. 처음에 시작할 때 서버가 필요하다고 했었죠?

 

저는 http-server를 활용해서 실행하였습니다.

터미널 창을 열고 프로젝트 디렉토리로 이동해서 http-server를 입력합니다.

터미널에 http-server 입력!

 

enter입력! 서버가 실행되었습니다.

웹서버 실행

별다른 옵션없이 실행하여 기본포트인 8080으로 실행되었습니다.

 

이제 localhost:8080/korea.html을 입력해서 실행해보겠습니다. (127.0.0.1:8080/korea.html)

정상적으로 실행된 모습

 

http-server 설치법 및 사용법은 매우 간단하므로 구글링을 통해 보시고 사용하는것도 추천드립니다.

(추후에 포스팅하게 되면 연결해놓겠습니다.)

 

 

아래 URL의 github를 가시면 지금까지 작성한 소스를 받아보실수 있습니다.

https://github.com/myhappyman/d3_koreaMap

 

myhappyman/d3_koreaMap

Contribute to myhappyman/d3_koreaMap development by creating an account on GitHub.

github.com

반응형
반응형

https://myhappyman.tistory.com/94

 

HTML, JAVASCRIPT - 테트리스 만들기 - 3

https://myhappyman.tistory.com/93 HTML, JAVASCRIPT - 테트리스 만들기 - 2 https://myhappyman.tistory.com/92 HTML, JAVASCRIPT - 테트리스 만들기 - 1(table요소, 배열값으로 테트리스 만들기) 웹 개발을 해..

myhappyman.tistory.com

 

3장에서 키보드 입력 이벤트까지 처리해보았습니다.

 

이번 포스팅에서는 어떤 입력을 받거나 일정시간마다 내려가더라도 테트리스 블럭이 게임 맵을 뚫고 넘어가지 않는 함수를 작성하고 사용하도록 해보겠습니다.

 

isMove()라는 함수를 추가해서, 키 이벤트값이나 setInterval함수에 따라 진행이 되었을때, 벽인지 아닌지 체크 후 boolean값을 리턴하는 함수를 만들어보겠습니다.

 

움직임 제한 함수 추가 isMove()

function isMove(TYPE, TURN, GX, GY){
    TURN = TURN > 3 ? TURN % 4 : TURN;
    for(var i=0; i<4; i++){
        for(var j=0; j<4; j++){
            if(TETRIS[TYPE][TURN][i][j] == 1){ //테트리스의 블럭모양이 나왔는데
                if(MAP[GX+2+i][GY+2+j] != 0){ //닿을곳이 맵이 아니면
                    return false;
                }
            }
        }
    }
    return true;
}

이러한 함수를 추가할겁니다. 파라미터값들에 대해 설명을 하면 아래와 같습니다.

첫번째 파라미터(TYPE)에는 테트리스의 현재 타입값을 넣습니다.

두번째 파라미터(TURN)에는 테트리스의 현재 회전값을 넣습니다.

세번째 파라미터(GX)는 현재 테트리스가 위치한 위아래 위치값 넣습니다.

네번째 파라미터(GY)는 현재 테트리스가 위치한 좌우의 위치값을 넣습니다.

리턴값은 boolean값인 true, false값을 처리합니다.

 

그리고 맵 데이터에서 현재 이동이 가능한 값 0으로 된 데이터인지 체크를하고 0이 아니라면 움직이지 못하도록 false값을 리턴할겁니다.

모든 조건을 통과했을때만 true값이 나와서 동작을 시킵니다.

 

 

isMove()함수에 대해서 좀 더 알아보겠습니다.

숫자 1번키를 눌러서 왼쪽으로 이동한다!

숫자1번(또는 왼쪽으로 이동하는 키)을 눌러서 왼쪽으로 이동한다고 가정을 했을때,

MAP배열 데이터와 테트리스 데이터가 위 그림처럼 되어있을텐데, 여기서 계속해서 이동이 가능한 영역은 회색입니다. 현재 그림을 기준으로 왼쪽으로 2번까진 이동이 가능하겠죠. 테트리스 모양인 'ㄱ'자의 막대는 넘어가면 안되니까요.

 

즉, 현재의 테트리스의 타입과 회전모양을 값으로 받고 TETRIS배열의 0번째부터 끝까지 배열모양을 조사해서 1이라는건 테트리스의 모양이므로 입력한 동작이 움직일수 있는지 없는지 체크하는 조건문이 진행됩니다. 

if(TETRIS[TYPE][TURN][i][j] == 1)

 

전역변수 GX값, GY값은 MAP배열을 기준으로 2만큼씩 안으로 들어가 있기 때문에 MAP데이터를 검사할때 2만큼 추가하였고, 각각 닿는 부위를 체크하기 위해 위아래는 검사하는 GX축에 i값만큼 더하고, 좌우를 검사하는 GY부분에 j만큼 더하였습니다.

if(MAP[GX+2+i][GY+2+j] != 0)

와 같은 조건문이 만들어졌습니다. 0이 아니라는건 맵이 아니기 때문에 return false가 처리되면서 이동을 못하도록 하죠.

 

그럼 이제 실질적으로 isMove()를 써봐야겠죠 키이벤트를 정의했던 myFunction()을 수정해보겠습니다.

 

myFunction함수에 키이벤트에 따른 이동제한 처리

function myFunction(input){
    //그리기 전에 지우기
    erase(GX, TYPE, TURN);
    switch(input){
        case 53://회전
            if(isMove(TYPE, TURN+1, GX, GY)){
                TURN++;
                TURN = TURN % 4;
            }
            break;
        case 49://블럭왼쪽이동
            if(isMove(TYPE, TURN, GX, GY-1)){
                GY--;
            }
            break;
        case 51://블럭오른쪽이동
            if(isMove(TYPE, TURN, GX, GY+1)){
                GY++;
            }
            break;
        case 50://블럭아래로이동
            if(isMove(TYPE, TURN, GX+1, GY)){
                GX++;
            }
            break;
        case 32://스페이스
            while(isMove(TYPE, TURN, GX+1, GY)){
                GX++;
            }
            break;
    }
    //테트리스 블럭 그리기
    drawTetris(GX, TYPE, TURN);
}

3장에서 입력된 키보드 값을 처리할 때 위 방향키를 입력받으면 TURN값을 ++처리하여 증가하거나

왼쪽으로 이동하면 전역변수 GY값을 --시켰는데 이번 포스팅에서는 if문인 조건문이 추가되었습니다.

회전을 하게되면 파라미터값에 현재의 전역변수의 회전값에 +1을 처리한 값을 넣고 움직일 수 있는지 없는지 체크합니다. 조건이 만족되면 그때서야 TURN의 전역변수 값을 ++하여 증감해줍니다.

 

왼쪽으로 이동할땐, 좌우를 처리하는 GY값에 -1을 처리하고 왼쪽으로 이동이 가능한지 아닌지 체크합니다.

마찬가지로 조건이 만족되었을때만 GY--처리를 합니다.

 

스페이스값은 아래로 이동하는것을 더이상 진행이 불가할때까지 처리해주면 됩니다.

while문을 통해 처리하였고 이동을 못하면 false값이 나오니 자연스럽게 빠져오겠죠.

 

이제 마지막으로 action()함수의 setInterval부분을 추가하겠습니다.

일정시간마다 자연스럽게 내려가다보니 일정시간이 지나면 제한이 없어서 뚫고 지나가게 됩니다.

 

 

action() 함수의 일정시간마다 동작하는 setInterval() 이동제한 추가

function action(){
    //keyEvent
    document.addEventListener("keypress", function(){
        myFunction(event.keyCode);
    });

    var intervalID = setInterval(function(){
        erase(GX, TYPE, TURN); //생성한 전위치 블럭 삭제
        if(isMove(TYPE, TURN, GX+1, GY)){
            GX++; //한줄씩 내리기
        }
        drawTetris(GX, TYPE, TURN); //테트리스를 그린다.
    }, GAME_SPEED);
}

키 이벤트부분과 유사합니다.

먼저 현재의 블럭을 지워주고 아래로 이동하는 키값이 강제로 입력되었다고 생각하시면 됩니다.

아래 위값을 제어하는 GX값이 증가하였다고 생각하고 isMove()함수를 통해 체크합니다.

 

그러면 더이상 맵을 뚫고 블럭이 사라지는 일은 없을 겁니다.

더이상 벽을 뚫고 들어가지 않는다.

 

여기까지 동작하는 소스를 한번 올리도록 하겠습니다.

 

아래의 ZIP파일을 받아주시면 됩니다.

tetris_v3.zip
0.00MB

반응형
반응형

https://myhappyman.tistory.com/93

 

HTML, JAVASCRIPT - 테트리스 만들기 - 2

https://myhappyman.tistory.com/92 HTML, JAVASCRIPT - 테트리스 만들기 - 1(table요소, 배열값으로 테트리스 만들기) 웹 개발을 해보겠다고 예전에 학원을 다니면서 강사님과 같이 만들어봤던, 테트리스 예제가..

myhappyman.tistory.com

2장 이후 3장까지 왔습니다. 이번에는 키 이벤트를 처리해보겠습니다.

 

3. 키 이벤트에 따라 테트리스 제어하기

이번에는 키 이벤트를 받아서 테트리스를 제어해보겠습니다.

 

2장에서 만들었던 action 메소드에 keypress 이벤트를 추가하겠습니다.

keypress 처리

function action(){
    //keyEvent
    document.addEventListener("keypress", function(){
        myFunction(event.keyCode);
    });
}

특정 키값을 받을때마다 파라미터 값으로 myFunction에 파라미터로 넘겨주고 그에 따라 동작을 정의할 것입니다.

 

myFunction - 입력값에 따른 출력 메시지

function myFunction(input){
    switch(input){
        case 53://회전
            console.log("5번이 입력되었습니다.");
            break;
        case 49://블럭왼쪽이동                        
            console.log("1번이 입력되었습니다.");
            break;
        case 51://블럭오른쪽이동            
            console.log("3번이 입력되었습니다.");
            break;
        case 50://블럭아래로이동            
            console.log("2번이 입력되었습니다.");
            break;
        case 32://스페이스
            console.log("스페이스바가 입력되었습니다!");
            break;
    }
    //테트리스 블럭 그리기
    drawTetris(GX, TYPE, TURN);
}

 

action 함수에서 키이벤트를 추가했기 때문에 키보드 입력값이 들어오면 myFunction이 실행될텐데 이제 파라미터 input값에 따라 switch문에서 동작을 시켜줄겁니다.

저는 우측 키보드로 게임을 즐긴다고 생각하고 키 세팅을 했는데, 이 부분은 원하시는 키값으로 하시면 됩니다.

키 세팅

 

키 입력을 정상적으로 받아진다.

실행 후 키보드를 입력해보면 블럭은 움직이지 않지만 키보드 입력에 따라 콘솔로그값이 찍히는 모습을 볼 수 있습니다.

 

이제 입력값에 따라 실제로 이동이 되거나 블록을 회전시키거나 맨 아래로 이동시켜야겠죠?

바로 동작을 처리해보겠습니다.

회전, 스페이스바, 이동 시키기

function myFunction(ChangeValue){
    switch(ChangeValue){
        case 53://회전
            TURN++;
            TURN = TURN % 4;
            break;
        case 49://블럭왼쪽이동
            GY--;
            break;
        case 51://블럭오른쪽이동
            GY++;
            break;
        case 50://블럭아래로이동            
            GX++;
            break;
        case 32://스페이스
            GX=21;
            break;
    }
    //테트리스 블럭 그리기
    drawTetris(GX, TYPE, TURN);
}

53은 윗 방향을 누르는 키값으로 회전이 되어야 합니다. 즉, TURN의 값을 증가시키고 그리면 됩니다.

하지만 %연산자가 있는것을 볼 수 있는데, 존재하는 이유는 회전은 4가지의 모습이 있기 때문입니다.

 

맨 처음 시작할때 추가하였던 tetris_data.js에서 TETRIS 변수 중 ㄹ모양의 블럭을 예시로 볼 경우

ㄹ 모양 4가지 회전

이렇게 4가지 형태를 볼 수 있습니다.

이러한 이유때문에 모든 블럭을 4가지 형태로 넣었고, ㅁ모양의 블럭도 4가지인 이유입니다.

 

실행해서 해보면 일정시간마다 블럭이 내려가게 했을 때처럼 이전 블럭의 잔상이 남는 것을 볼 수 있는데, 눈치채신분들은 여기서 지우는 함수를 넣으면 된다는것을 알 수 있을겁니다.

키 이벤트에 따라 변경되지만 잔상이 남는다.

 

erase함수를 역시 처음에 추가해보겠습니다.

 

그럴싸한 myFunction - erase함수까지 추가

function myFunction(input){
    //그리기 전에 지우기 추가!!!
    erase(GX, TYPE, TURN);
    switch(input){
        case 53://회전
            TURN++;
            TURN = TURN % 4;
            break;
        case 49://블럭왼쪽이동
            GY--;
            break;
        case 51://블럭오른쪽이동
            GY++;
            break;
        case 50://블럭아래로이동            
            GX++;
            break;
        case 32://스페이스
            GX=21;
            break;
    }
    //테트리스 블럭 그리기
    drawTetris(GX, TYPE, TURN);
}

 

아직 완벽하진 않지만 그럴싸하게 키 이벤트도 받고 동작이 되는 모습을 볼 수 있습니다.

 

 

다음 포스팅에서는 마지막행에 닿으면 현재 블럭의 이벤트가 멈추고 다음블럭으로 넘어갈 수 있게 추가해보겠습니다.

 

반응형
반응형

https://myhappyman.tistory.com/92

 

HTML, JAVASCRIPT - 테트리스 만들기 - 1(table요소, 배열값으로 테트리스 만들기)

웹 개발을 해보겠다고 예전에 학원을 다니면서 강사님과 같이 만들어봤던, 테트리스 예제가 있어서 약간의 소스 정리 후 포스팅을 하면 좋을 것 같아 테트리스 만들기 예제를 시작합니다. 테트리스 만들기 예제들..

myhappyman.tistory.com

 

테트리스 만들기 1장 이후 2장입니다.

 

테트리스 구조를 만들고 맵을 만들었으니 이제 테트리스 다운 동작을 만들어야 할 것같습니다.

 

이번엔 index.js에 action()이라는 함수를 추가해서 일정 시간마다 자연스럽게 내려가는 동작을 추가해보겠습니다.

여기서 일정시간마다 동작한다. 라는 부분에서 떠오르는 함수가 있으신가요? setInterval 또는 setTimeout, animationFrame등이 생각 날 것 같습니다. 저는 setInterval을 활용해서 처음에 정의한 gamespeed의 변수값마다 동작하도록 작성했습니다.

 

2. 일정시간마다 블럭이 떨어지는 효과 만들기

index.js - action()

window.onload = function(){
    drawMap();
    drawNextTetris(Ntype);
    myFunction();
    action(); //action 함수 추가
}

/**
 * 동작 메소드
 * interval 함수로 입력시간마다 동작
 * (gameSpeed에 따라 떨어지는 속도가 변경)
 */
function action(){

    var intervalID = setInterval(function(){
        console.log("action!")
        GX++;
        drawTetris(GX, TYPE, TURN);
    }, GAME_SPEED);
}

window.onload 맨 끝에 action함수를 추가하여 페이지가 로드되면 action함수가 실행되도록 처리하였습니다.

 

action 함수 내부를 보니 Interval함수에 의해 GAME_SPEED값마다 콘솔로그값을 출력 후 GX전역 변수값을 증가시킨 후에 drawTetris 함수를 호출하는 것을 볼 수 있습니다.

 

 

drawTetris함수는 현재 선택된 테트리스의 값을 특정 위치에 그려주는 함수 입니다.

특정 위치를 그려주므로 일정 시간마다 우리는 위에서 아래로 떨어지는 모습을 위해 GX변수값을 증가시켜주면 자연스럽게 밑으로 계속해서 그려줄 것입니다.

 

 

 

하지만 계속 그려주게되면 이전 모습이 남아있어 이러한 형태로 잔상이 있겠죠?

이제 잔상을 지워주는 함수를 만들겠습니다.

 

 

erase 함수

한줄씩 내리면서 특정위치를 지우기전에 현재 위치의 테트리스 블럭값을 확인하고 지우는 erase 함수를 만들어보겠습니다.

//특정 지역을 map으로 초기화 (block값으로 되어있던걸 map으로)
function erase(GX, TYPE, TURN){
    for(var x=0; x<TETRIS[BLOCKTYPE][BLOCKROTATE].length; x++){
        for(var y=0; y<TETRIS[BLOCKTYPE][BLOCKROTATE][x].length; y++){
            if(TETRIS[TYPE][TURN][x][y]==1){
                ChangeColor(x+GX, y+GY, "map");
            }
        }
    }
}

function action(){
    var intervalID = setInterval(function(){
        erase(GX, TYPE, TURN); //생성한 전위치 블럭 삭제
        GX++; //한줄씩 내리기
        drawTetris(GX, TYPE, TURN); //테트리스를 그린다.
    }, GAME_SPEED);
}

 

현재의 테트리스 모양과 회전위치값을 가지고 와서 1이면 블럭모양이므로 1인지 체크 후에 블럭모양이라면 erase함수에서는 map이라는 클래스를 넣어 모두 맵으로 바꿔줍니다.

 

그럼 아래와 같이 지우면서 그리기 때문에 테트리스처럼 보이게 되죠.

 

지우면서 그려진다.

반응형
반응형

웹 개발을 해보겠다고 예전에 학원을 다니면서 강사님과 같이 만들어봤던, 테트리스 예제가 있어서 약간의 소스 정리 후 포스팅을 하면 좋을 것 같아 테트리스 만들기 예제를 시작합니다.

 

테트리스 만들기 예제들을 찾아보면 캔버스값으로 처리하는 예제들이 많던데, 아직 캔버스 사용이 쉽지는 않은터라 게임치고는 UI가 엄청 화려하게 변화는 없는 테트리스정도는 html의 간단한 요소들로 충분히 표현이 가능하고 jquery나 다른 화려한 플러그인 없이 순수 javascript만으로 로직을 구성해서 표현할 수 있는 테트리스를 선택하게 되었습니다.

크게 구분하자면 배열데이터를 조작하고 매번 배열의 데이터를 검사하여 table요소를 통해 UI를 표현하는 형태로 진행할 예정입니다.

 

 

 


1. 작성을 위해 페이지 구성 & 배열 데이터 만들기 맵 그리기

 

페이지별 구성도

이러한 형태로 페이지들을 구성할 예정입니다.

메인이될 index.html 페이지

css를 꾸며줄 index.css

동작처리를 위한 index.js

전체 맵데이터, 테트리스 모양데이터를 담아놓을 tetris_data.js 로 구성하여 소스를 작성하겠습니다.

 

각각 소스들을 보도록 하겠습니다.

 

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="./css/index.css">
    <title>Tetris</title>
    <script src="./js/tetris_data.js"></script>
    <script src="./js/index.js"></script>
</head>
<body>
    <div id="container"></div>
</body>
</html>

사용할 index.html은 1개입니다.

tetris_data.js라는곳에는 전역으로 처리할 맵 배열과 테트리스 모양배열을 담아두고 실질적으로 돌아가는 코드 동작은 index.js에서 처리할 예정입니다.

 

tetris_data.js

//맵
var MAP=[
    [4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4],       
    [4,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,2,2,2,2,2,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,2,2,2,2,2,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,2,2,2,2,2,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,2,2,2,2,2,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,2,2,2,2,2,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4],
    [4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]];

//테트리스 조각
var TETRIS=[
    [
        [//막대기
            [0,0,0,0],
            [1,1,1,1],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,0,0],
            [0,1,0,0],
            [0,1,0,0],
            [0,1,0,0]
        ],
        [
            [0,0,0,0],
            [1,1,1,1],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,0,0],
            [0,1,0,0],
            [0,1,0,0],
            [0,1,0,0]
        ]
    ],
    [
        [//ㄹ1
            [0,1,0,0],
            [0,1,1,0],
            [0,0,1,0],
            [0,0,0,0]
        ],
        [
            [0,0,1,1],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,0,0],
            [0,1,1,0],
            [0,0,1,0],
            [0,0,0,0]
        ],
        [
            [0,0,1,1],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0]
        ]
    ],
    [
        [//ㄹ2
            [0,0,1,0],
            [0,1,1,0],
            [0,1,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,1,0],
            [0,0,1,1],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,0,1,0],
            [0,1,1,0],
            [0,1,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,1,0],
            [0,0,1,1],
            [0,0,0,0],
            [0,0,0,0]
        ]
    ],
    [
        [//ㄱ
            [0,1,1,0],
            [0,0,1,0],
            [0,0,1,0],
            [0,0,0,0]
        ],
        [
            [0,0,0,1],
            [0,1,1,1],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,0,0],
            [0,1,0,0],
            [0,1,1,0],
            [0,0,0,0]
        ],
        [
            [0,1,1,1],
            [0,1,0,0],
            [0,0,0,0],
            [0,0,0,0]
        ]
    ],
    [
        [//ㄴ
            [0,1,1,0],
            [0,1,0,0],
            [0,1,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,0,0],
            [0,1,1,1],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,0,1,0],
            [0,0,1,0],
            [0,1,1,0],
            [0,0,0,0]
        ],
        [
            [0,1,1,1],
            [0,0,0,1],
            [0,0,0,0],
            [0,0,0,0]
        ]
    ],
    [
        [//ㅁ
            [0,1,1,0],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,1,0],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,1,0],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,1,1,0],
            [0,1,1,0],
            [0,0,0,0],
            [0,0,0,0]
        ]
    ],
    [
        [//ㅗ
            [0,0,1,0],
            [0,1,1,1],
            [0,0,0,0],
            [0,0,0,0]
        ],
        [
            [0,0,1,0],
            [0,0,1,1],
            [0,0,1,0],
            [0,0,0,0]
        ],
        [
            [0,0,0,0],
            [0,1,1,1],
            [0,0,1,0],
            [0,0,0,0]
        ],
        [
            [0,0,1,0],
            [0,1,1,0],
            [0,0,1,0],
            [0,0,0,0]
        ]
    ]
];

- 전역변수 MAP 배열에 테트리스 맵 데이터를 담아놓고 그릴 배열입니다.

  > 2차원 배열

  > 4의 값은 게임의 보드를 표현할 값입니다.

  > 3은 테트리스 가이드 라인으로 처리할 값입니다.

  > 2번은 다음 테트리스 블럭을 표현할 테트리스맵으로 검은 배경을 처리할 값입니다.

  > 0번은 테트리스 블럭이 돌아다니는 테트리스맵으로 검은 배경을 처리할 값입니다.

 

- 전역변수 TETRIS는 테트리스 게임의 블럭들을 담아놓는 배열입니다. ㅡ, ㄱ, ㄴ, ㄹ, ㅁ, ㅗ 형태의 블럭을 회전 4회의 모양별로 담아두었습니다.

  > 3차원 배열[첫번째][두번째][세번째]

  > 첫번째 인덱스의 값은 테트리스의 모양을 구분합니다.

  > 두번째 인덱스의 값은 테트리스의 회전을 구분합니다.

  > 세번째 인덱스의 값은 맵의값인지 블럭의값인지 구분합니다.

 

index.js

맵과 테트리스의 블럭들을 그려봐야겠죠?

실질적으로 동작을 정의할 자바스크립트입니다.

var GX = 0; //블럭을 생성할 초기 위치 X
var GY = 3; //블럭을 생서할 초기 위치 Y
var NGY = 17;
var TURN = 0;
var TYPE = Math.round(Math.random()*6);
var NTYPE = Math.round(Math.random()*6);
var BLOCKTYPE=6, BLOCKROTATE=3;
var GAME_SPEED = 2000;
 
window.onload = function(){
    drawMap();
    drawNextTetris(NTYPE);
    myFunction();
}

function drawMap(){
    var rowLen = MAP.length;
    var tag = "<table>";
    var x = 0;
    var y = 0;
    for(var i=0; i<rowLen; i++){
        tag += "<tr>"
        var colLen = MAP[i].length;
        for(var j=0; j<colLen; j++){
            var className = "";
            var idValue = "";
            if(MAP[i][j] == 0){
                className = "map";
                idValue = "x"+(i-2)+"y"+(j-2);
            }else if(MAP[i][j] == 2){
                className = "map";
                idValue = "xn"+(i-1)+"yn"+(j-16);
            }else if(MAP[i][j] == 3){
                className = "guard";
            }else if(MAP[i][j] == 4){
                className = "board";
            }

            if(idValue != ""){
                tag += "<td id="+idValue+" class="+className+"></td>"
            }else{
                tag += "<td class="+className+"></td>"
            }
            
        }
        tag += "</tr>"
    }
    document.getElementById("container").innerHTML = tag + "</table>";
};

function ChangeColor(x, y, cN){
    document.getElementById("x"+x+"y"+y).className = cN;
}

//다음에 그려질 방향의 테트리스 그리기
function drawNextTetris(NTYPE){
    for(var x=0; x<4; x++){
        for(var y=0; y<4; y++){
            if(TETRIS[NTYPE][0][x][y] == 1){
                ChangeColor("n"+(x+1), "n"+y, "block"+NTYPE);                
            }
        }
    }
}

//테트리스 그리기
function drawTetris(GX, TYPE, TURN){
    for(var x=0; x<TETRIS[BLOCKTYPE][BLOCKROTATE].length; x++){
        for(var y=0; y<TETRIS[BLOCKTYPE][BLOCKROTATE][x].length; y++){
            if(TETRIS[TYPE][TURN][x][y] == 1){
                ChangeColor(x+GX, y+GY, "block"+TYPE);
            }
        }
    }
}

//동작을 초기화하고 입력값에 따라 그려줄 함수
function myFunction(input){
    //테트리스 블럭 그리기
    drawTetris(GX, TYPE, TURN);
}

drawMap이라는 함수는 페이지가 로드되면 실행되면서 MAP배열의 값을 불러와서 table을 그리고 각각 td값에 알맞는 클래스값을 지정하여 테트리스 게임의 맵처럼 표출시키는 함수 입니다.

ChangeColor함수는 전달받은 x,y같에 따라 클래스명을 처리해줍니다.

drawNextTetris는 다음으로 그려질 블럭을 그리는 함수입니다.

drawTetris함수는 테트리스 블럭을 특정 위치에 그리는 함수 입니다.

myFunction은 현재 기능적으로 정의된게 없지만 추후에 동작 정의나 초기값 설정등을 처리할 함수입니다.

 

index.css

마지막으로 디자인을 입혀야 게임다운 느낌이 들겠죠? index.css입니다.

table{
    margin: auto;
    border-collapse: collapse;
}

td{
    width: 18px;
    height: 18px;
    text-align:center;
    vertical-align:middle;
    outline: 0.5px solid #EAEAEA;
}

.board{
    background-color: #5f3dc4;
}
.guard{
    background-color: #91a7ff;
}
.map{
    background-color: #495057;
}
.ghost{
    background-color: #495057;
}

.block0{
    background-color: #f5f100;
}
.block1{
    background-color: #e64980;
}
.block2{
    background-color: #ae3ec9;
}
.block3{
    background-color: #7048e8;
}
.block4{
    background-color: #4263eb;
}
.block5{
    background-color: #1098ad;
}
.block6{
    background-color: #37b24d;
}
.block7{
    background-color: #f59f00;
}

 

css까지 작성하시고 페이지를 열어보시면 이러한 기본 준비가 완성되어 있을겁니다.

 

 

다음장부터는 index.js에서 정의 동작을 수정하고 추가하면서 진행하다보면 어느새 완성된 테트리스를 만나볼 수 있을겁니다. 계속해서 진행해보겠습니다.

반응형
반응형

jQuery를 통해 radio, checkBox의 선택된 값을 가져오거나 강제로 선택되게 하는 방법을 알아보겠습니다.

 

radio 제어

예제로 이러한 smartPhone.html 있다고 가정하고 진행하겠습니다.

<div class="smartPhoneForm">
	<div>스마트폰</div>
	<div>
		통신사 :
		<input type="radio" name="telecom" value="skt"> SKT
		<input type="radio" name="telecom" value="kt"> KT
		<input type="radio" name="telecom" value="lgt"> LGT
	</div>
	<div>
		브랜드 :
		<input type="radio" name="brand" value="samsung"> 삼성
		<input type="radio" name="brand" value="apple"> Apple
		<input type="radio" name="brand" value="lg"> LG
	</div>
</div>

 

선택된 값 가져오기

$("input[name='radio의 name값']:checked").val();
//ex)$("input[name='telecom']:checked").val();

선택된 radio값을 가져옵니다.

 

 

 

값 강제로 선택시키기

$("input[name='radio의 name'][value='선택할 값']").prop("checked", true);
//$("input[name='telecom'][value='skt']").prop("checked", true);

 

 

 


checkbox 제어

checkbox 이벤트를 위한 checkbox.html 예제입니다.

<div class="favoriteBrand">
	<div>좋아하는 브랜드를 선택하세요</div>
	<div>
		<span><input type="checkBox" name="brand" value="acne"> 아크네 스튜디오</span>
		<span><input type="checkBox" name="brand" value="burbbery"> 버버리</span>
		<span><input type="checkBox" name="brand" value="louis_vuitton"> 루이비통</span>
	</div>
	<div>
		<span><input type="checkBox" name="brand" value="gucci"> 구찌</span>
		<span><input type="checkBox" name="brand" value="givenchy"> 지방시</span>
		<span><input type="checkBox" name="brand" value="off_white"> 오프화이트</span>
	</div>
</div>

 

 

체크박스 선택된 개수 가져오기

$("input[name='checkBox의 name값']:checked").length
//$("input[name='brand']:checked").length

선택된 개수

 

체크박스 선택된 값 가져오기

var len = $("input[name='brand']:checked").length;
if(len > 1){ //개수를 체크하고 2개부터는 each함수를 통해 각각 가져온다.
    $("input[name='brand']:checked").each(function(e){
        console.log($(this).val())
    })
}

선택된 데이터 가져오기

 

배열을 만들어 담아도 된다.

var len = $("input[name='brand']:checked").length;
var checkArr = [];
if(len > 1){ //개수를 체크하고 2개부터는 each함수를 통해 각각 가져온다.
    $("input[name='brand']:checked").each(function(e){
        var value = $(this).val();
        checkArr.push(value);        
    })
}

console.log(checkArr);

 

체크박스 강제로 선택시키기

$("input[name='checkBox의 name'][value='선택시킬 value값']").prop("checked", true);
//$("input[name='brand'][value='burbbery']").prop("checked", true);

선택시키기

 

특정 checkbox의 모든값 선택하기

$("input[name='brand']").each(function(e){
    $(this).prop("checked", true);
});
//brand로 설정된 모든 checkbox선택

각각 값에 접근하여 true처리하여 선택한다.

 

모두 선택

 

특정 checkbox의 모든값 해제하기

$("input[name='brand']").each(function(e){
    $(this).prop("checked", false); //false는 해제된다.
})

각각 값에 접근하여 false처리하여 해제한다.

 

반응형