[Android] 안드로이드앱 개발 도전기

앱개발로그 2013. 3. 31. 22:02



    LifeGame 안드로이드 앱개발 도전기!





Google Play 마켓에서 보기




하루게 다르게 성장해 가는 안드로이드 시장! 

특히 국내에서는 9:1이라는 점유율로 아이폰과의 격차를 상당히 벌려놓았다. 





그 성장세에 이제 안드로이드를 준비해야할 때라는 생각이 들어 "안드로이드 학습"에 뛰어들었다.

무엇을 공부함에 있어서 가장 좋은 방법은 역시 "백문이 불여일행"!!

공부에서 그치지 말고 직접 앱을 만들고 google play 마켓에 출시해보면 훨씬 많은 것을 느낄 수 있는 것은 당연한 일!






먼저 개발하려는 앱의 주제 선정에 들어갔다. 

무엇을 만들까 -_-;; 아무리 생각해도 개발자에게 가장 어려운 질문인 것 같다. (나만 그런가 Otz...)

완료기간을 짧게 잡아 개발이 늘어지는 것을 피하기 위해 잘 아는 것을 주제로 잡기로 했다. 





고민끝에 아주 오래전에 만들었던 "The Game Of Life" 가 생각났다. 

예전에 자바를 사용하여 만든 데스크탑 스윙애플리케이션이다


The Game Of Life 에 대해 소개하면, 1970년대 영국의 수학자 "존 콘웨이"에 의해 만들어진 세포 자동자중 

하나이다.  세로 자동자라고 하니 어려운데... 쉽게 설명하면 "컴퓨터 혼자 하는 세균전" 이라고 억지로 풀어 볼 

수 있겠다.;;

격자로 이루어진 판위에 셀(세포)들의 위치가 결정된다. 살아있는 셀은 판위에 표시되고 죽어있는 셀은 

빈칸으로 표시된다. 셀은 대각선을 포함하여 상하좌우 8개의 이웃들을 갖게 되는데, 이때 이웃의 개수에 따라

다음턴에 탄생, 생존, 죽음이 결정된다.

턴을 거듭하면서 새로운 셀들이 생겨나고, 죽고, 유지되면서 기이한 패턴을 만들어 내게 되는데 

당시에 신기한 패턴을 발견한 사람에게는 상금을 걸어 대중에게 인기를 얻기도 했다고 한다. 


게임규칙에 대해 간단히 설명하면 


1. 정확히 3개의 이웃을 갖는 (죽은) 셀은 다음 턴에 살아난다. (탄생)










2.  2개 또는 3개의 이웃을 갖는 살아있는 셀은 다음 턴에도 유지된다. (생존)





 




3. 이웃 1개인 셀은 외로워서 죽고, 4개 이상인 셀은 질식해서 죽는다  (외로움과 질식)

















이 게임룰을 적용했을 때, 나타나는 패턴들은 크게 3가지  종류가 분류된다. 

전혀 변화가 없는 고정된 패턴(정물, still life), 


일정한 행동을 주기적으로 반복하는 패턴 (진동자, oscillator), 


한쪽 방향으로 계속 전진하는 패턴(우주선, spaceship).


Gasper 라는 사람이 발견한 글라이더 건(glider gun)  패턴은

마치 글라이더가 총알 처럼 지속적으로 발사되는 재미있는 형세를 이루고 있다. 



앱을 개발하면서 많은 고민에 부딪혔다. 사실 개발의 어려움에 관한 고민보다는 오히려 

이 앱은 과연 의미있는 앱인가? 이렇게 만들면 좋더 사용하기 편하고 보기 좋은가?와  같은 

정체성과 기획, 그리고 인터페이스에 대한 고민이었다.

무언가를 만들게 될 때 어디선가 타협점을 찾는 것! 굉장히 중요하다고 생각한다. 


이만 각설하고...Google Play 마켓에 업로드한 앱을 보자! 짜잔!!





<메인 인터페이스>




  <패턴을 로드하고 저장하는 기능>




<The Game of Life 소개>




<게임규칙 변경>





                     

                              [LifeGame 실행영상 - youtube]



물론 마켓에 올렸다고 끝이 아닌것 잘 알고 있다. 관심을 가져주는 분들이 있고, 


개선방향에 의견을 주시면 언제든지 반영할 준비가 되어있다.  


(수고했다는 의미로 광고한번 살포시 눌러주면 개발자에게 큰힘이 된답니다 ^^)


자. 이제 더 흥미로운 앱을 개발하기 위해 다시 고민할 때이다! 



LifeGame Facebook Page 가기


Google Play 마켓에서 보기





'앱개발로그' 카테고리의 다른 글

[Android] 안드로이드앱 개발 도전기  (2) 2013.03.31
2011년 스마트폰 사용자 통계  (0) 2012.11.12
BlackBerry Architecture  (0) 2011.04.26

[java] 라이프 게임 (life game)

언어로그/Java 2013. 2. 3. 00:44




스프링노트를 운영하던 시절 썼던 글인데 티스토리로 옮기면서 다시 읽어 보니 감회가 새롭네요


요즘엔 AWT/Swing 이 거의 쓰이지 않지만 이때 공부했던 GUI 프로그래밍이 이후에 플렉스와 아이폰을 


공부하면서 GUI에 적응하는데 밑거름이 되었던 것 같네요! 






1. 라이프 게임 개발을 시작하다


2008년 6월 25일 수요일 새벽 잠들기 전....


1년간 프로그래밍에 전혀 손 대지 않다가... 다시 프로그래밍을 하려고 하니 영~ 힘들다  


1년이란 시간이 매우 길긴 기나부다...그 많은 것을 잊어버렸다. 자바 Spring 프레임워크를 파보려고 하는데


읽어도 무슨말인지 모르겠고, 이클립스 등 개발환경 셋팅하는데만 하루가 걸렸다. ㅡㅡ;;


잊어버린 자바 관련 지식을 상기시키고자, 첫 번째 프로젝트에 들어갔다. 프로그램은 "콘웨이"에 '라이프 게임!'


게임 알고리즘이 간단해서 연습용 프로젝트에 딱인 것 같다.


그럼 라이프 게임이 무엇인지부터 알아보자!




2. 라이프 게임이란?


라이프 게임(Game of Life) 또는 생명 게임은 영국의 수학자 존 호튼 콘웨이가 고안해낸 세포 자동자의 

일종으로, 가장 널리 알려진 세포 자동자 가운데 하나이다. 미국의 과학잡지 사이언티픽 어메리칸 1970년 

10월호 중 마틴 가드너의 칼럼 〈Mathematical Games(수학 게임)란을 통해 처음으로 대중들에게 소개되어 

단순한 규칙 몇가지로 복잡한 패턴을 만들어낼 수 있다는 점 때문에 많은 관심과 반응을 불러일으켰다.


설명

이 ‘게임’은 사실 게임을 하는 사람이 자신의 의지로 게임의 진행을 결정하는 일반적인 게임과는 다르다. 

라이프 게임의 진행은 처음 입력된 초기값만으로 완전히 결정된다.


라이프 게임은 무한히 많은 사각형(혹은 ‘세포’)로 이루어진 격자 위에서 돌아간다. 각각의 세포 주위에는 

인접해 있는 여덟 개의 ‘이웃 세포’가 있으며, 또 각 세포는 ‘죽어’ 있거나 ‘살아’ 있는 두가지 상태중 

한가지 상태를 갖는다.  격자를 이루는 세포의 상태는 연속적이 아니라 이산적으로 변화한다. 즉, 현재 세대의 

세포들 전체의 상태가 다음 세대의 세포 전체의 상태를 결정한다.

...


패턴의 예

라이프 게임에는 전혀 변화가 없는 고정된 패턴(정물 靜物, still life), 일정한 행동을 주기적으로 반복하는 패턴

(진동자, oscillator), 한쪽 방향으로 계속 전진하는 패턴(우주선, spaceship) 등 여러 패턴이 존재한다.



‘block’과 ‘boat’는 정물이고, ‘blinker’와 ‘toad’는 진동자, 그리고 ‘글라이더(glider)’와 ‘경량급 우주선(lightweight spaceship — LWSS)’은 우주선에 속한다...



출처 Wikipedia

      




3. 게임 규칙


게임규칙을 요약하면 아래와 같다 


* 셀의 상,하,좌,우,각 대각선 8개의 인접한 셀을 이웃으로 한다.

* 셀은 세대를 거듭하며 살거나 죽는다.


1. 정확히 3개의 이웃이 살아있다면,  (죽어있는) 셀이 살아난다.

2. 2개의 이웃이 살아있다면 살아있는 셀은 다음세대에도 살아남든다

3. 1개 이하 또는 4개 이상의 이웃이 살아있다면, 살아있는 셀은 외로워서 또는 질식해서 죽는다.




4. 구현하기


프로그래밍을 하기 위하여 구현한 절차는 아래와 같다. 


먼저 모든 셀들을 순회하면서 살아있는 이웃셀을 카운트하고, 살아있는 이웃의 개수를 저장한다


이를 바탕으로 위 세가지 규칙을 적용하여 다음세대 살아있는 셀들을 결정한다.


간략하게 만들어 화면은 아래와 같다. 







4.1 게임판의 표현


게임판은 JPanel을 상속하는 Cell을 가로, 세로 size 개수 만큼의 요소로 갖는 이차원 배열로 표현하였다. 

LifeGame 클래스는 게임의 전체적인 흐름을 관리하는 메소드들을 갖는다. 

public class LifeGame extends JPanel  {
       ...
	private void init() {	
		setLayout(new GridLayout(size,size));
		cells = new Cell[size][size];
		
		for (int i=0; i < size; i++)
			for (int j=0; j < size; j++) {
				cells[i][j] = new Cell();
				cells[i][j].setBorder(BorderFactory.createLineBorder(Color.BLACK));
				add(cells[i][j]);
			}
		rule = new GameRule(cells);
	}
        ...
	public void transition() {
		rule.countAliveNeibor();
		rule.applyRule();
	}
        ...
}





4.2 셀의 표현과 셀의 상태 


Cell은 live 상태(true이면 살아있고, false 죽어있는 상태)와 살아있는 이웃의 개수에 대한 변수를 갖는다. 

live 상태에서 따라서 셀이 그려지거나 혹은 그려지지 않거나 한다.

public class Cell extends JPanel {
	private boolean live = false;
	private int neighborCount = 0;
	private Image img = null;
	private int w, h;
        ...
       @Override
	protected void paintComponent(Graphics g) {
	        ...
		if (live) {
			gg.drawImage(img, 0, 0, this);
		}
		else {
			gg.setColor(getBackground());
			gg.fillRect(0, 0, getWidth(), getHeight());
		}
		g.drawImage(image, 0, 0, this);
	}
}





4.3 게임규칙의 표현


턴마다 LifeGame 클래스의 transition()메소드가 호출되면, GameRule 클래스의 countAliveNeibor() 메소드가 

먼저 호출되고, 이웃하는 셀들에 개수를 모두 카운팅 한뒤, 살아있는 세대를 결정하기 위해 applyRule()메소드가 호출된다. 

public class GameRule {
    ...
     public void countAliveNeibor() {
         ...
     }
     public void applyRule() {
		int neighborCount;

		for (int r = 0; r < rows; r++) {
			for (int c = 0; c < cols; c++) {
				neighborCount = cells[r][c].getNeighborCount();

				if (cells[r][c].getLife()) {
					if (neighborCount <= 1 || neighborCount >= 4)
						cells[r][c].setLife(false);
				}
	
				else  {
					if (neighborCount == 3) 
						cells[r][c].setLife(true);
				}
			}
		} //for 
	}
}






5. 버려진 프로그램은 싫다! 리팩토링~


2009년 08월 04일


예전에 만든 라이프게임 UI가 허접해서 라이프게임 알고리즘은 그대로 드고, 드로잉부분만 수정해서 다시 만들어봤다. 


기존에는 메인패널 하나에 좌표를 가지고 Graphics 객체의 드로잉 메소드로 그렸지만, 이번에는 


셀하나가 JPanel을 상속하게 했고, 셀이 살아있으면, 이미지를 로딩해서 그리게 했다. 


빠른 드로잉을 위해 역시 더블 버퍼링을 사용한다


Timer로는 javax.util.Timer를 사용했다.


TimerTask 쓰레드를 정의해서, 2초마다 상태전이(transition) 후 드로잉하도록 했다.


pauser 기능은 wait() /  notify() 를 사용해서, TimerTask 쓰레드를 wait()로 대기상태로 만들고


notifyAll()로 다시 깨우도록 처리했다.





6. 개발하게 하면서 이런걸 알게되었다~


 1. AWT의 컴포넌트(Canvas)와 Swing의 컴포넌트(JMenu)를 함께 사용하면, Canvas에 가려 메뉴가 안보인다


    중량컨테이너(High Weight Container)인 AWT 와 경량컨테이너(Light Weight Container)인 Swing을 함께 


    사용해서 그렇다고 한다. 따라서 중량 컨테이너와 중량 컨테이너를 함께 사용하지 말아야 한다.  


    Swing에서는 Canvas대신 Panel을 사용한다


 2. Javax.swing.Timer 객체를 통해 타이머를 구현할 수 있다.

    Javax.swing.Timer timer = new Timer(int timeoutMil, ActionListener listener);

 


 3 . 더블버퍼링

      Image buffer = Component.createImage(int width, int height);    컴포넌트에 버퍼를 얻고,

      Graphics g = buffer.getGraphics(); 그래픽스 객체 g에 드로잉한다

      Componet.getGraphics().drawImage(buffer, posintX, positionY, Componet);   버퍼를 컴포넌트에 덮는다

 


 4. 이차원 배열의 사용

  Cell을 표현하는 이차원 배열의 생성은 다음과 같다

       Cell[][] cell = new Cell[size][size];

       for (int i=0; i < size; i++)

           for (int j=0; j < size; j++)

               cell[i][j] = new Cell();


주의할 점은 Cell 타입의 이차원배열을 생성하고, 그 이차원 배열을 순회하며 실제로 각 배열의 원소에 Cell 객체를 

생성하고 할당해야 한다는 것!




7. 만들면서 삽질하게 만든 요인들


첫째로, 

타이머 이벤트 발생할 때마다,  JPanel 클래스를 상속한 MyPanel에서 public void paint(Graphics g) 

메소드를 오버라이드 했는데, 해당좌표에 셀이 도무지 제대로 그려지지 않았다 --^; 

갖은 실수 끝에 실수로 알고리즘 메소드가 주석처리 되었었다는 사실을 깨닫고 수정했다. otz...


둘째로, 

이전 라이프셀이 지워지지 않는채, 계속 덮입혀져 그려진다. 이것은 더블버퍼링을 이용해서 해결! 

생각보다 자바의 드로잉은 속도가 느려서 더블버퍼링을 사용하지 않으면, 화면갱신이 드로잉을 따라가지 

못하는 것 같다. 


셋째로, 

AWT에서의 Canvas 대신, Swing에서는 JPanel을 사용하는데, 드로잉을 하는데 있어서 잘 생각해야 한다.

프로그램이 시작되자 마자 JPanel에 무엇이 그려져야 한다면, 더더욱 그렇다. 게임이 시작하자마자 셀라인을 

보이게 하려고 했다. JFrame의 생성자에 그리는 코드를 넣을게 아니라, public void paint(Graphics g) 메소드에서

처리하면 해결할 수 있다. 이때 그리는데 필요한 데이터값이 정확히 대입이 됐는지 잘 살펴야, 삽질을 피할수 있다.


어쨌든 이래저래 해서, 몸풀기 자바 프로그램을 완성했다. 요거 하면서 그나마 잊어버렸던 자바 지식들을 조금씩 

기억하게 되어 도움이 되었다.







8. 짜잔~ 최종적으로 만들어진 프로그램




소스 다운로드 

JDK_000_LifeGame.zip




다음은  LifeGame, GameRule, Cell 클래스 소스이다. 


package lifegame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;

import javax.swing.BorderFactory;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class LifeGame extends JPanel  {
	private Cell[][] cells;
	private int size; 
	private GameRule rule; 

	public LifeGame(int size) {
		this.size = size;
		init();
	}

	// 초기화 
	private void init() {	
		setLayout(new GridLayout(size,size));
		cells = new Cell[size][size];
		
		for (int i=0; i < size; i++)
			for (int j=0; j < size; j++) {
				cells[i][j] = new Cell();
				cells[i][j].setBorder(BorderFactory.createLineBorder(Color.BLACK));
				add(cells[i][j]);
			}
		rule = new GameRule(cells);
	}

	public void cellAlive(int r, int c) {
		cells[r][c].setLife(true);
	}
	
	public void cellDead(int r, int c) {
		cells[r][c].setLife(false);
	}

	public boolean getLifeCell(int r, int c) {
		return cells[r][c].getLife();
	}

	public void clearGame() {
		for (Cell[] cs : cells) 
			for (Cell c : cs)
				c.setLife(false);
	}

	public void transition() {
		rule.countAliveNeibor();
		rule.applyRule();
	}
	
	@Override
	protected void paintComponent(Graphics g) {
		for (Cell[] cs : cells)
			for (Cell c : cs)
				c.repaint();
	}
}

 

package lifegame;

public class GameRule {
	private Cell[][] cells;
	private static final int LEFT = -1, UP = -1;
	private static final int RIGHT = 1, DOWN = 1;
	private int rows, cols;
	
	public GameRule(Cell[][] cells) {
		this.cells = cells;
		rows = cells.length;
		cols = cells[0].length;
	}

	// 각 셀의 살아있는 이웃을 센다 
	public void countAliveNeibor() {
		int nNeighbor;

		for (int r = 0; r < rows; r++) {
			for (int c = 0; c < cols; c++) {
				
				nNeighbor = 0;
				if (r + UP >= 0)
					if(cells[r + UP][c].getLife())
						nNeighbor++;

				if (r + UP >= 0 && c + RIGHT < cols)
					if (cells[r + UP][c + RIGHT].getLife())
						nNeighbor++;
							
				if (c + RIGHT < cols)
					if (cells[r][c + RIGHT].getLife())
						nNeighbor++;

				if (r + DOWN < rows && c + RIGHT < cols)
					if (cells[r + DOWN][c + RIGHT].getLife())
						nNeighbor++; 
							
				if (r + DOWN < rows)
					if (cells[r + DOWN][c].getLife())
						nNeighbor++;

				if (r + DOWN < rows && c + LEFT >= 0)
					if (cells[r + DOWN][c + LEFT].getLife())
						nNeighbor++;				

				if (c + LEFT >= 0)
					if (cells[r][c + LEFT].getLife())
						nNeighbor++;

				if (r + UP >= 0 && c + LEFT >= 0)
					if (cells[r + UP][c + LEFT].getLife())
						nNeighbor++;

				cells[r][c].setNeighborCount(nNeighbor);
			}
		} // for

	}
	// 살아있는 셀은 이웃이 1명 이하, 4명 이상이면 죽는다
	// 죽어있는 셀은 이웃이  3명이면 살아난다
	// 이웃이 2,3명인 살아있는 셀은 계속 산다 
	public void applyRule() {
		int neighborCount;

		for (int r = 0; r < rows; r++) {
			for (int c = 0; c < cols; c++) {
				neighborCount = cells[r][c].getNeighborCount();

				if (cells[r][c].getLife()) {
					if (neighborCount <= 1 || neighborCount >= 4)
						cells[r][c].setLife(false);
				}
	
				else  {
					if (neighborCount == 3) 
						cells[r][c].setLife(true);
				}
			}
		} //for 
	}

}


package lifegame;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.BufferedInputStream;

import javax.swing.JPanel;
import javax.imageio.ImageIO;


@SuppressWarnings("serial")
public class Cell extends JPanel {
	private boolean live = false;
	private int neighborCount = 0;
	private Image img = null;
	private int w, h;
	
	
	public Cell() {
		try {
			img = ImageIO.read(new BufferedInputStream(Res.class.getResourceAsStream("../life.png")));
			w = img.getWidth(this);
			h = img.getHeight(this);
			setPreferredSize(new Dimension(w,h));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void setLife(boolean s) { 
		live = s;
	}
	
	public boolean getLife() {
		return live;
	}
	
	public void setNeighborCount(int n) {
		neighborCount = n;
	}
	
	public int getNeighborCount() {
		return neighborCount;
	}

	@Override
	protected void paintComponent(Graphics g) {
		// 더블 버퍼링 
		Image image = createImage(getWidth(), getHeight());
		Graphics gg = image.getGraphics();
		
		if (live) {
			gg.drawImage(img, 0, 0, this);
		}
		else {
			gg.setColor(getBackground());
			gg.fillRect(0, 0, getWidth(), getHeight());
		}
		g.drawImage(image, 0, 0, this);
	}
}






'언어로그 > Java' 카테고리의 다른 글

[java] 글 목록  (0) 2015.09.03
[Java] 자바란?  (0) 2013.02.16
[java] 라이프 게임 (life game)  (1) 2013.02.03
[java] 제13회 한국자바개발자 컨퍼런스  (0) 2013.01.30
Inner Class(내부 클래스)  (0) 2011.04.12
컬렉션(Collection)  (0) 2011.04.11
  • 2016.10.24 20:21 ADDR 수정/삭제 답글

    비밀댓글입니다