앞 시간에서는 게임내에서 주로 무언가 입력을 받는 그런 다이얼로그들을 구현해 보았었죠.

이번 시간에는 게임내에서 사용자에게 어떤 메시지를 전달하기 위한 다이얼로그들을 구현해 보겠습니다.


뭐, 메시지를 전달하기 위한 다이얼로그라고 해봤자 별건 아니구요,

쪽, 뻑, 따닥, 쓸 과 같은 메시지를 화면에 출력해 주는 그런 내용입니다. 이전시간과 전반적인 흐름은 같습니다.


먼저 순서대로 쪽 부터 구현을 해보도록 하지요.








case 2: // 손에서 낸 카드와, 뒤집어 낸 카드 둘뿐이라면,
    /* 쪽! */
    curViewState = State.GAME_SHOWMESSAGE;
    curMsgState = CyDialog.MSG_JJOK;
    repaints();
    Sleep.wait(this, 300);

    curViewState = State.GAME_RUNNING;
    repaints();

    // 쪽일때에는, 우선 두장 다 먹고,
    room.addPae(curTurn, room.getMonth(handCardIdx)[0]);
    room.addPae(curTurn, room.getMonth(handCardIdx)[1]);
    room.getMonth(handCardIdx)[0] = room.CARD_NULL;
    room.getMonth(handCardIdx)[1] = room.CARD_NULL;

    // 상대방의 피를 하나 가져온다.
    room.robPee(curTurn);

    repaints();
    break;


전체 함수를 모두 올리는건 지면상 오버헤드가 큰것 같아서 쪽 부분만을 따로 떼어 내었습니다.

예전의 소스와 별 다를 것이 없지만, 앞 부분에 몇가지 부분이 추가 되었습니다.


먼저, State 클래스에 GAME_SHOWMESSAGE 라는 상태 상수가 추가 되었지요.

curViewState가 GAME_SHOWMESSAGE 상태일때에는 어떤 메시지를 보여주고 있는 상태를 뜻합니다.


현재 보여주고 있는 메시지의 종류는 아래 보이는 curMsgState 라는 변수에 저장을 하고 관리합니다.

지금 이 상태에서는 CyDialog 클래스의 MSG_JJOK 이라는 상태 상수가 저장되었으므로,

현재 보여지는 메시지가 "쪽"과 관련된 메시지 라는 것을 알 수 있겠죠.

메시지를 일정 시간 보여주고 난 뒤에는 다시 게임으로 돌아와서, 화면을 다시 그려주면 되겠지요.


< 쪽이 발생했을때의 메시지 >


다음으로 뻑일때의 메시지 출력을 구현해 보도록 하겠습니다.









case 3: // 손에서 낸 카드와, 뒤집어 낸 카드와 원래 카드 한장이라면,
    room.incCntPuck(curTurn); // 뻑 횟수 +1.

    curViewState = State.GAME_SHOWMESSAGE;
    curMsgState = CyDialog.MSG_PPUCK;
    repaints();
    Sleep.wait(this, 300);

    curViewState = State.GAME_RUNNING;
    repaints();

    // 3번 뻑이라면,
    if(room.getCntPuck(curTurn) >= 3)
    {
        // 7점에 해당하는 금액을 상대방에게 받고,
        room.setPoint(curTurn, 7); // 플레이어 점수를 7점으로 세팅.
        // 게임 종료.
        curViewState = State.GAME_RESULT;

        repaints();

        // 게임 끝.
        curViewState = State.GAME_RESULT;
        repaints();

        return;
    }

    break;

뻑 역시 마찬가지입니다.

curViewState를 GAME_SHOWMESSAGE로 변경하고,

curMsgState에 MSG_PPUCK이 저장되었으므로, "뻑" 메시지와 관련된 이미지를 출력해 줍니다.

뻑이 나면, 강렬한 응가 이미지로 뻑이 났다는걸 플레이어가 좀더 쉽게 인식할 수 있게 해주지요^^



< 뻑이 났음을 알려주는 메시지 >


다음 따닥 부분을 보도록 하죠.






☆
☆
☆
☆

☆
☆
else
{
    /* 카드가 슬롯에 가득 차 있다면, 따닥! */
    if(i == 3)
    {
        /* 따닥! */
        curViewState = State.GAME_SHOWMESSAGE;
        curMsgState = CyDialog.MSG_DDADAK;
        repaints();
        Sleep.wait(this, 300);

        curViewState = State.GAME_RUNNING;
        repaints();

        // 모두 먹고,
        for(j=0; j<4; j++)
        {
            room.addPae(curTurn, room.getMonth(handCardIdx)[j]);
            room.getMonth(handCardIdx)[j] = room.CARD_NULL;
        }

        // 상대방의 피를 하나 가져온다.
        room.robPee(curTurn);
    }
}

역시 마찬가지죠. curMsgState가 MSG_DDADAK으로 변경된것 뿐이군요.


< 따닥이 발생했을때의 메시지 >


쓸이 발생했을때에는 처리해 줄 것이 조금 있죠.

쓸이라는건, 플레이어가 패를 낸 후, 먹을 수있는 패를 다 먹었을때, 바닥에 패가 한장도 없다면, 쓸이죠.

따라서, 턴이 변경되기 바로 전에, 쓸을 체크 하는 것이 좋겠네요.


좀더 편하게 작업하기 위해서, 턴을 변경 하는 것을 따로 함수로 만들었습니다.


/**
 * 턴을 변경한다.
 */
private void changeTurn()
{
    // 쓸 체크.
    if(checkSseul() == true)
    {
        curViewState = State.GAME_SHOWMESSAGE;
        curMsgState = CyDialog.MSG_SSEUL;
        repaints();
        Sleep.wait(this, 300);

        curViewState = State.GAME_RUNNING;
        repaints();
    }

    // 턴 변경.
    curTurn = (curTurn==User.PLAYER)? User.COMPUTER : User.PLAYER;
}

단순히 턴을 변경하는 것이 아니라, 턴을 변경 하기 전에 checkSseul() 함수를 이용해서 쓸을 체크한후,

쓸이라면, 쓸 메시지를 일정시간 보여준 후, 턴을 변경해 줍니다.


쓸을 체크 하는 함수는 다음과 같습니다.


/**
 * 쓸의 여부 체크.
 */
private boolean checkSseul()
{
    for(byte i=0; i<12; i++)
    {
        // 한군데의 월이라도 카드가 있다면, 쓸이 아님.
        if(room.getMonth(i)[0] != room.CARD_NULL)
            return false;
    }

    // 쓸.
    return true;
}


12개의 월을 루프를 돌면서 검사를 해서, 카드가 없다면, true를 리턴, 있다면 false를 리턴합니다.

카드가 없어야 쓸이니, true가 리턴이 되면 쓸이라는 의미가 되겠죠.


그리고 앞 시간의 결과 메시지를 출력하는 부분에서 그냥 단순히 결과 메시지만을 출력하고 끝났었죠.

그러나, 그렇게 되면, 아무런 키 입력도 먹지를 않기 때문에, 플레이어는 다운이라도 된줄 알겠죠.

결과 창에서, 확인키를 눌렀을 때 보여줄 메뉴 상자를 하나 더 추가해줍니다.


이 메시지 상자는 게임이 끝난 후, 게임을 계속 할 것인지를 묻는 의미가 강하기 때문에,

ContinueGame이라고 이름 지었습니다.

ContinueGame 메뉴 상자를 그려주는 함수는 drawContinueGame() 함수이며, 다음과 같습니다.


/**
 * 게임중의 메뉴화면을 그려준다.
 * @param g
 */
public void drawContinueGame(Graphics g)
{
    if(preViewState == State.GAME_WAIT)
        drawWaitingGame(g);
    else
        drawRunningGame(g);

    dialog.drawBorder(g, CyDialog.CONTINUEGAME);
}

curViewState가 State.GAME_CONTINUEGAME 이라면, 이 ContinueGame 상자가 화면에 뜹니다.

그러나 이 ContinueGame 메뉴 상자는 게임이 끝났을 때에만 나오는 것이 아니라,?

게임 중간에도 취소 키나 메뉴 키를 누르면, 이 메뉴 상자가 뜨도록 했습니다.


그래서, ContinueGame 메뉴 상자 출력 전에는 현재 상태를 preViewState라는 곳에 저장을 해야 합니다.


또, preViewState가 GAME_WAIT인 상태에서 취소키를 누르면, 배경을 drawRunningGame()으로 그려주는것이아니라.

drawWaitingGame(g)으로 그려주어야 제대로 된 화면 위에 메뉴 상자가 출력이 되겠죠.



< ContinueGame 메뉴 상자 >

drawContinueGame() 함수를 호출하면, 위와 같은 메뉴 상자가 출력 되겠죠.

그러나 출력은 되었다고 해도 키 입력은 아무런 처리도 안했기 때문에, 그냥 단순히 저 화면만 뜨겠죠?


이제 키 입력을 처리해 보도록 하겠습니다.


else if(curViewState == State.GAME_CONTINUEGAME)
{
    if(numKey == 1) /* 계속 */
    {
        if(preViewState == State.GAME_RESULT)
        {
            room.gameOver();
            init();
            curViewState = State.GAME_WAIT;
        }
        else
            curViewState = preViewState;
    }
    else if(numKey == 2) /* 나가기 */
    {
        enterMainMenu();
    }
    else if(numKey == 3) /* 겔러리로 가기 */
    {
        gallery = new Gallery(midlet);

        gallery.savePreCanvas(this);
        midlet.setCurrentDisplay(gallery);

        gallery = null;
        System.gc();
    }

    repaints();
}

위 부분은 키입력을 받는 switch()문 안의 lsdefault: 블럭 안에 삽입된 내용입니다.


1을 누르면, 계속 이므로, GAME_RESULT에서 발생했다면, 게임을 다시 시작하고,

다른 곳에서 발생했다면, 사용자가 취소키나 메뉴키를 눌러서 불러낸 것이므로, 원래 상태로 돌아갑니다.


2번을 누르면 enterMainMenu() 함수를 통해서 메인 메뉴로 빠져나갈수 있도록 해주면 되겠죠.

아직 제대로 구현은 되어 있지 않지만, 게임에서 딴 게임머니로 겔러리에서 그림을 구입할수 있습니다.


3번을 누르면 겔러리 화면을 보여줍니다.

Midlet 클래스에서 gallery를 화면에 보여주는것과 같이 겔러리에서 돌아갈 현재 캔버스를 저장하고,

현재 display를 겔러리로 설정해 주면, 겔러리 화면이 화면에 그려지겠죠?


이걸로 어느정도 디스플레이 요소는 모두 추가를 시켜 주었네요.

 

Posted by maysent
:

이번 시간에는 게임에 각종 디스플레이 요소를 씌워보는 작업을 하도록 하겠습니다.

이번 시간을 거쳐서 이제 진짜 그럴듯한 게임으로 거듭나는 것을 감상하실 수 있습니다.


가장 먼저 게임 내에서 디스플레이 요소를 출력하기 위한 CyDialog 클래스를 구현해보도록 하지요.

* CyDialog.java.
package common;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;

import com.skt.m.Graphics2D;

/**
 * 다이얼로그와 관련된 기능을 모아놓은 클래스.
 */

public class CyDialog {
    public final static byte SELECTTWOCARD = 0; // 두장의 카드 선택.
    public final static byte SELECTGOOKJIN = 1; // 국진 선택.
    public final static byte SELECTGOSTOP = 2; // 고/스톱 선택.
    public final static byte SELECTSHAKE = 3; // 흔듬 선택(플레이어).
    public final static byte SELECTBOMB = 4; // 폭탄 선택(플레이어).
    public final static byte RESULT = 5; // 결과 확인.
    public final static byte CONTINUEGAME = 6; // 게임 메뉴.

    public final static byte MSG_GO = 7; // 고.
    public final static byte MSG_STOP = 8; // 스톱.
    public final static byte MSG_PPUCK = 9; // 뻑.
    public final static byte MSG_JJOK = 10; // 쪽.
    public final static byte MSG_DDADAK = 11; // 따닥.
    public final static byte MSG_SSEUL = 12; // 쓸.
    public final static byte MSG_SHAKE = 13; // 흔듬(컴퓨터 턴).
    public final static byte MSG_BOMB = 14; // 폭탄(컴퓨터 턴).

    private Canvas parentCanvas;
    private short width, height;

    /*
     * Border Images.
     */
    private Image imgSelectTwoCard; 
    private Image imgSelectGookjin;
    private Image imgSelectGoStop;
    private Image imgSelectShake;
    private Image imgSelectBomb;
    private Image imgResult;
    private Image imgContinueGame; // 게임 중의 메뉴 화면.

    /*
     * Message Images.
     */
    private Image imgMsgGo; // 고를 선택했을때의 메시지.
    private Image imgMsgStop; // 스톱을 선택했을때의 메시지.
    private Image imgMsgPpuck; // 뻑을 당했을때의 메시지.
    private Image imgMsgJjok; // 쪽을 했을때의 메시지.
    private Image imgMsgDdadak; // 따닥 했을때의 메시지.
    private Image imgMsgSseul; // 쓸을 했을때의 메시지.
    private Image imgMsgShake; // 흔들었을때의 메시지.
    private Image imgMsgBomb; // 폭탄일대의 메시지.

    /**
     * Constructor.
     * @param parentCanvas
     */
    public CyDialog(Canvas parentCanvas)
    {
        this.parentCanvas = parentCanvas;

        width = (short) parentCanvas.getWidth();
        height = (short) (parentCanvas.getHeight()+16);

        loadImages(); // 이미지를 읽어옴.
    }

    /**
     * 다이얼로그에 필요한 각종 이미지를 로드한다.
     */
    private void loadImages()
    {
        try {
            /*
             * Load Border Images.
             */
            // 두장의 카드 선택 border.
            imgSelectTwoCard = Image.createImage("/image/gasktwocard.lbm");
            // 두장의 카드 선택 border.
            imgSelectGookjin = Image.createImage("/image/gaskgook.lbm");
            // 고/스톱 선택 border.
            imgSelectGoStop = Image.createImage("/image/gaskgostop.lbm");
            // 흔들기 선택 border.
            imgSelectShake = Image.createImage("/image/gaskshake.lbm");
            // 폭탄 선택 border.
            imgSelectBomb = Image.createImage("/image/gaskbomb.lbm");
            // 결과 화면.
            imgResult = Image.createImage("/image/resultbg.lbm");
            // 게임 메뉴 화면.
imgContinueGame = Image.createImage("/image/continuegame.lbm"); /* * Load Message Images. */ // 고를 선택했을때의 메시지. imgMsgGo = Image.createImage("/image/anigo.lbm"); // 스톱을 선택했을때의 메시지. imgMsgStop = Image.createImage("/image/anistop.lbm"); // 뻑을 당했을때의 메시지. imgMsgPpuck = Image.createImage("/image/anippuk.lbm"); // 쪽을 했을때의 메시지. imgMsgJjok = Image.createImage("/Image/anijjok.lbm"); // 따닥 했을때의 메시지. imgMsgDdadak = Image.createImage("/Image/aniddadak.lbm"); // 쓸을 했을때의 메시지. imgMsgSseul = Image.createImage("/image/aniclear.lbm"); // 흔들었을때의 메시지. imgMsgShake = Image.createImage("/image/anishake.lbm"); // 폭탄일때의 메시지. imgMsgBomb = Image.createImage("/image/anibomb.lbm"); } catch(Exception e) {} } /** * 다이얼로그의 border를 그려줌. * @param g * @param borderType */ public void drawBorder(Graphics g, byte borderType) { switch(borderType) { case SELECTTWOCARD: // 두장의 카드를 선택 중일때, g.drawImage(imgSelectTwoCard, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case SELECTGOOKJIN: // 국진을 선택 중일때, g.drawImage(imgSelectGookjin, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case SELECTGOSTOP: // 고/스톱을 선택 중일때, g.drawImage(imgSelectGoStop, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case SELECTSHAKE: // 흔들기를 선택 중일때, g.drawImage(imgSelectShake, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case SELECTBOMB: // 폭탄을 선택 중일때, g.drawImage(imgSelectBomb, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case RESULT: // 결과 화면을 보는 중일때, g.drawImage(imgResult, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break;
case CONTINUEGAME: // 게임 중의 메뉴 화면.
g.drawImage(imgContinueGame, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); } } /** * 메시지를 출력한다. */ public void drawMessage(Graphics g, byte messageType, byte cntGo) { switch(messageType) { case MSG_GO: // 고. Graphics2D g2d = Graphics2D.getGraphics2D(g); g2d.drawImage((width-38)/2, (height-18)/2, imgMsgGo, (cntGo-1)*38, 0, 38, 18, Graphics2D.DRAW_COPY); break; case MSG_STOP: // 스톱. g.drawImage(imgMsgStop, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case MSG_PPUCK: // 뻑. g.drawImage(imgMsgPpuck, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case MSG_JJOK: // 쪽. g.drawImage(imgMsgJjok, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case MSG_SSEUL: // 쓸. g.drawImage(imgMsgSseul, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case MSG_SHAKE: // 흔듬. g.drawImage(imgMsgShake, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; case MSG_BOMB: // 폭탄. g.drawImage(imgMsgBomb, width/2, height/2, Graphics.HCENTER|Graphics.VCENTER); break; } } /** * 자원 해제. */ public void cleanUp() { imgSelectTwoCard = null; imgSelectGookjin = null; imgSelectGoStop = null; imgSelectShake = null; imgSelectBomb = null; imgResult = null; imgMsgGo = null; imgMsgStop = null; imgMsgPpuck = null; imgMsgJjok = null; imgMsgDdadak = null; imgMsgSseul = null; imgMsgShake = null; imgMsgBomb = null; System.gc(); } }

역시나 별건 없죠? 예전과 동일합니다.

처음에 생성자에서 Canvas를 저장하고, 화면 사이즈를 초기화 한 후, 모든 이미지를 로드합니다.


그리고 drawBorder() 함수는, 단순히 화면 중앙에 Border 형식의 다이얼로그를 출력해줍니다.


다음에 나오는 drawMessage() 역시 마찬가지 함수입니다.

이 drawMessage()는 메시지 형식의 다이얼로그를 출력해준다는 것 만이 다르죠.


여태까지의 게임에서는 g.drawString() 함수를 이용해서, 모든 메시지를 처리했죠.

게임 종료 처리도 구현이 안되어 있었구요. 이번시간에 그걸 해보도록 하겠습니다.


먼저 두장의 카드중 하나를 선택하는 부분을 수정해보도록 할까요?

/**
 * 카드 둘중 하나를 선택할때...
 * @param g
 */
private void drawSelectTwoCards(Graphics g)
{
    byte index;

    drawRunningGame(g);
    /* 카드 두장 중 하나를 선택한다. */
    dialog.drawBorder(g, CyDialog.SELECTTWOCARD);

    // 두 종류의 카드를 보여줌.
    if(room.slot[0] != room.CARD_NULL)
        index = 0;
    else if(room.slot[1] != room.CARD_NULL)
        index = 1;
    else return;

    drawCard(40, 66, room.getMonth(room.slot[index])[0]);
    drawCard(65, 66, room.getMonth(room.slot[index])[1]);
}

지난시간과 거의 다름이 없지만, g.drawString() 이 함수가 삭제되고,

dialog.drawBorder(g, CyDialog.SELECTTWOCARD);

위 부분이 추가 되었죠.

그리고 이전에는 단순히 문자열만이 출력되어서, 어떤 카드중에 하나를 선택하라는 건지 알수가 없었죠.

이제는 맨 아래의 drawCard() 함수 두개가 선택해야 하는 카드를 화면에 그려주어,

어떤 카드 중에 선택을 하라는 것인지 확실히 알 수가 있습니다.


< 두장의 카드를 선택하는 화면 >


이제, 흔들기/폭탄시의 다이얼로그를 출력 해 보도록 할까요?

/**
 * 흔듬/폭탄 여부를 선택 중일때...
 * @param g
 */
private void drawSelectShake(Graphics g)
{
    drawRunningGame(g);
if(shakeType == SHAKE) // 흔듬 선택 중일때, dialog.drawBorder(g, CyDialog.SELECTSHAKE); else /* if(shakeType == BOMB) */ // 폭탄 선택 중일때, dialog.drawBorder(g, CyDialog.SELECTBOMB); }

흔들기/폭탄 선택 다이얼로그는 단순히, 이미지 한장을 출력해 주면 됩니다.

단지, 흔들기 일때의 이미지와, 폭탄일때의 이미지가 다르므로, 그것만 구분해서 출력해 주면 되겠죠.


< 흔듬 여부를 선택하는 화면 >


다음에 나오는 국진 선택화면이나, 고/스톱 선택화면은 모두 화면 중앙에 이미지를 출력하는것 뿐입니다.

/**
 * 국진의 사용을 선택 중일때,
 * @param g
 */
private void drawSelectGookjin(Graphics g)
{
    drawRunningGame(g);
    dialog.drawBorder(g, CyDialog.SELECTGOOKJIN);
}

/**
 * 고/스톱을 선택 중일때...
 * @param g
 */
private void drawSelectGoStop(Graphics g)
{
    drawRunningGame(g);
    dialog.drawBorder(g, CyDialog.SELECTGOSTOP);
}

그냥 현재 게임 화면을 먼저 그려준 후 , (drawRunningGame())

그 위에 각 상황에 맞는 이미지를 하나 출력 해 준 것 뿐이죠.

아래는 각각의 상황의 출력 결과입니다.



< 국진의 선택 화면 >

< 고/스톱 선택 화면 >


이번에는 결과 화면을 출력해 주도록 하겠습니다.

예전에는 그냥 g.drawString() 함수를 이용해서, "플레이어 승!", "컴퓨터 승!" 혹은 "나가리!" 와 같이,

단순히 문자열만 출력해주는 아~주 초라했던 인터페이스를 가지고 있었죠.


drawResult() 함수를 CyDialog 클래스를 이용해서 좀더 수정해 보았습니다.















☆

☆

☆







☆

☆

☆
/**
 * 결과 화면을 그려준다.
 * @param g
 */
private void drawResult(Graphics g)
{
    drawRunningGame(g);

    // 결과 창을 그려줌.
    dialog.drawBorder(g, CyDialog.RESULT);

    // 나가리라면...
    if(room.getNagari() == true)
    {
        if(gameEnd == false)
            gameEnd = true;

        g.setColor(0);
        g.drawString("나가리", width/2, 73, Graphics.HCENTER|Graphics.TOP);
    }
    else
    {
        /* 벌칙. */
        byte goBak; // 고박.
        byte peeBak; // 피박.
        byte kwangBak; // 광박.
        byte mungDda; // 멍따.

        int obtainedMoney=0; // 획득 머니.

        switch(curTurn)
        {
        case User.PLAYER: // 플레이어의 턴일때,
            g.setColor(0);
            g.drawString("플레이어 승", width/2, 73, 
                                Graphics.HCENTER|Graphics.TOP);

            // 상대방이 고를 한번 이상 했었다면, - 고박일경우 돈 두배 획득.
            goBak = (room.getCntGo(User.COMPUTER) >= 1)? 
                                (byte)2 : (byte)1; 
// 상대방의 피가 7장 이하라면, 피박. - 피박일경우 돈 두배 획득. peeBak = (room.getNumOfPee(User.COMPUTER) < 7)? (byte)2 : (byte)1;
// 상대방의 광이 없다면, 광박. - 광박일경우, 돈 두배 획득. kwangBak = (room.getNumOfKwang(User.COMPUTER) == 0)? (byte)2 : (byte)1;
// 상대방의 열이 7장 이상이라면, 멍따.
- 멍따일 경우, 돈 두배. mungDda = (room.getNumOfYeul(User.COMPUTER) >= 7)? (byte)2 : (byte)1; if(gameEnd == false) { gameEnd = true; obtainedMoney = room.MONEY_PER_POINT *room.getRealPoint(User.PLAYER) *goBak*kwangBak*peeBak*mungDda; // 플레이어의 돈 증가. room.addMoney(curTurn, obtainedMoney); // 컴퓨터의 돈 감소. room.addMoney(curTurn, -obtainedMoney); } g2d.drawImage(18, 45, sprWinLose, 0, 0, 15, 12, Graphics2D.DRAW_COPY); // 승. g.drawString("" + room.getCntGo(curTurn), 30, 59, Graphics.LEFT|Graphics.TOP); // 고 횟수. g.drawString("" + room.getRealPoint(curTurn), 80, 47, Graphics.LEFT|Graphics.TOP); // 점수. g.drawString("" + room.getCntShake(curTurn), 80, 59, Graphics.LEFT|Graphics.TOP); // 흔듬 횟수. g.drawString("획득 금액 +" + obtainedMoney, width/2, 89, Graphics.HCENTER|Graphics.TOP); break; case User.COMPUTER: // 컴퓨터의 턴일때, g.setColor(0); g.drawString("컴퓨터 승", width/2, 73, Graphics.HCENTER|Graphics.TOP); // 상대방이 고를 한번 이상 했었다면, goBak = (room.getCntGo(User.COMPUTER) >= 1)? (byte)2 : (byte)1; // 고박일경우 돈 두배 획득. // 상대방의 피가 7장 이하라면, 피박. peeBak = (room.getNumOfPee(User.PLAYER) < 7)? (byte)2 : (byte)1; // 피박일경우 돈 두배 획득. // 상대방의 광이 없다면, kwangBak = (room.getNumOfKwang(User.PLAYER) == 0)? (byte)2: (byte)1; // 광박일경우, 돈 두배 획득. // 상대방의 열이 7장 이상이라면, 멍따. mungDda = (room.getNumOfYeul(User.COMPUTER) >= 7)? (byte)2 : (byte)1; // 멍따일 경우, 돈 두배. if(gameEnd == false) { gameEnd = true; obtainedMoney = room.MONEY_PER_POINT *room.getRealPoint(User.COMPUTER) *goBak*kwangBak*peeBak*mungDda; // 플레이어의 돈 감소. room.addMoney(curTurn, -obtainedMoney); // 컴퓨터의 돈 증가. room.addMoney(curTurn, obtainedMoney); } g2d.drawImage(18, 45, sprWinLose, 15, 0, 15, 12, Graphics2D.DRAW_COPY); // 패. g.drawString("" + room.getCntGo(curTurn), 30, 59, Graphics.LEFT|Graphics.TOP); // 고 횟수. g.drawString("" + room.getRealPoint(curTurn), 80, 47, Graphics.LEFT|Graphics.TOP); // 점수. g.drawString("" + room.getCntShake(curTurn), 80, 59, Graphics.LEFT|Graphics.TOP); // 흔듬 횟수. g.drawString("획득 금액 +" + obtainedMoney, width/2, 89, Graphics.HCENTER|Graphics.TOP); } } }

파란색 별은 새롭게 추가된 부분, 주황색 별은 수정된 부분을 의미합니다.

소스가 조금 길긴 하지만, 자세히 보면, 이미지랑 문자열 출력해주는것 밖에 추가 된 것이 없죠^^;



가장 윗 부분을 보시면, 이제까지와 마찬가지로,

CyDialog 클래스의 drawBorder() 함수를 이용해서, 결과 화면을 그려주었습니다.

그리고 주황색 별()로 표시된 부분은 지난시간과 똑같지만, 문자열의 좌표가 약간 변경되었습니다.


그 다음에 추가된 부분들은 모두, 결과창 이미지의 좌표에 맞게 문자열을 출력해주는 부분들일 뿐입니다.

문자열들의 좌표는 포토샾에서 계산을 해서 집적 숫자 값으로 넘겨 주었습니다.


< 게임이 끝난후의 결과 화면 >

 

Posted by maysent
:
3뻑/쪽/따닥.

우리가 그냥 넘어가긴 했지만, 지난시간에 구현했던 내용에 이미 쪽과 따닥이 구현되어 있습니다.

그 곳에 쪽과 따닥이 발생 시, 상대방의 피를 한장 가져오는 것만 추가를 해주면 되겠죠.


한판에 3번 뻑이 나면, 게임이 끝나며, 7점에 해당되는 금액을 상대방에게획득하고, 승리합니다.

또, 쪽이란 먹을 수 있는 패가 없어서 아무 패나 냈는데 뒤집은 패가 같은 무늬인 경우이죠.

쪽이 발생했을 경우에는, 패를 먹음과 동시에 상대방의 피 한 장을 가져옵니다.

따닥은 바닥에 같은 무늬 패 두 장이 있고 그걸 먹기 위해 패를 냈는데, 뒤집은 패도 같은 무늬일 경우,

해당 카드를 네 장 모두 먹음과 동시에 상대방의 피 한 장을 가져옵니다.


지난 시간에 뻑을 구현 했는데, 그 안에 쪽과 따닥에 관한 내용도 이미 포함이 되어 있습니다.

// 손에서 냈던 카드와 뒤집어서 낸 카드가 같은 슬롯에 있다면,
if(handCardIdx == reverseCardIdx)
{
    int i, j;

    // 뻑 체크.
    for(i=0; i<4; i++)
    {
        if(room.getMonth(handCardIdx)[i] == room.CARD_NULL)
        {
            switch(i)
            {
            case 2: // 손에서 낸 카드와, 뒤집어 낸 카드 둘뿐이라면,
              /* 쪽! */
                // 쪽일때에는, 우선 두장 다 먹고,
                room.addPae(curTurn, room.getMonth(handCardIdx)[0]);
                room.addPae(curTurn, room.getMonth(handCardIdx)[1]);
                room.getMonth(handCardIdx)[0] = room.CARD_NULL;
                room.getMonth(handCardIdx)[1] = room.CARD_NULL;

              // 상대방의 피를 하나 가져온다.
              room.robPee(curTurn);

                repaints();
                break;
            case 3: // 손에서 낸 카드와, 뒤집어 낸 카드와 원래 카드 한장이라면,
                // System.out.println(" # Msg : 뻑!!");
                room.incCntPuck(curTurn); // 뻑 횟수 +1.

              // 3번 뻑이라면,
              if(room.getCntPuck(curTurn) >= 3)
              {
                  // 7점에 해당하는 금액을 상대방에게 받고,
                  room.setPoint(curTurn, 7); // 플레이어 점수를 7점으로 세팅.
                  // 게임 종료.
                  curViewState = State.GAME_RESULT;

                  repaints();

                  // 게임 끝.
                  curViewState = State.GAME_RESULT;
                  repaints();

                  return;
              }

                break;
            }
        } // end if(room.getMonth(handCardIdx)[i] == room.CARD_NULL).
        else
        {
          /* 카드가 슬롯에 가득 차 있다면, 따닥! */
            if(i == 3)
            {
                // 모두 먹고,
                for(j=0; j<4; j++)
                {
                    room.addPae(curTurn, room.getMonth(handCardIdx)[j]);
                    room.getMonth(handCardIdx)[j] = room.CARD_NULL;
                }

              // 상대방의 피를 하나 가져온다.
              room.robPee(curTurn);
            }
        }
    }
}

앞에
와 같은 별이 달려있는 Line이 새로 추가된 줄입니다.

뻑이 3번 발생하면, 뻑이 3번 발생한 플레이어의 점수를 무조건 7점으로 만들고 게임을 종료 합니다.

왜냐하면, 게임이 끝났을때, 플레이어의 점수에 따라 상대방에게서 금액을 획득하므로,

점수를 7점으로 세팅해 주면, 7점에 해당하는 금액을 받아오게 되겠지요.


그리고 쪽과 따닥은 지난 시간까지의 내용에 상대방의 피를 하나 가져오는 부분만을 추가한 것이죠.

상대방에게서 피를 한장 뺏어오는 robPee() 함수는 twogo1p.Room 클래스에 정의 되어 있습니다.

/**
 * 다른 사용자로부터, 피를 한장 가져온다.
 * @param userType
 * @return
 */
public byte robPee(byte userType)
{
    byte userLastIdx = -1;
    byte enemyLastIdx = -1;
    byte enemyType;
    byte userPeeIdx = CARD_NULL, enemyPeeIdx = CARD_NULL;
    byte userSsangPeeIdx = CARD_NULL, enemySsangPeeIdx = CARD_NULL;

    // 상대방의 타입 설정.
    enemyType = (userType==COMPUTER)? PLAYER : COMPUTER;

    // 상대방의 피에 1점자리 피가 있는지 찾는다. 
    for(i=0; i<MAX_PEE; i++)
    {
        // 플레이어의 카드가 없다면,
        if(cardsPee[userType][i] == CARD_NULL)
            userLastIdx = (byte)(i-1);
// 상대방의 카드가 없다면, if(cardsPee[enemyType][i] == CARD_NULL) enemyLastIdx = (byte)(i-1);
// 둘다 검사가 끝났다면, if(userLastIdx != -1 && enemyLastIdx != -1) break;
// 상대방으로부터 가져올 1점 자리 패를 찾았다면, if(enemyPeeIdx == CARD_NULL && gCard.getType(cardsPee[enemyType][i]) == CARDTYPE_PEE)
{ enemyPeeIdx = (byte)i; } // 상대방으로부터 가져올 쌍피를 찾았다면, if(enemyPeeIdx == CARD_NULL && gCard.getType(cardsPee[enemyType][i]) == CARDTYPE_SSANG_PEE) { enemySsangPeeIdx = (byte)i; } // 사용자의 패에서, 1점 자리 패를 찾았다면, if(userPeeIdx == CARD_NULL && gCard.getType(cardsPee[enemyType][i]) == CARDTYPE_PEE) { userPeeIdx = (byte)i; } // 사용자의 패에서, 쌍피를 찾았다면, if(userPeeIdx == CARD_NULL && gCard.getType(cardsPee[enemyType][i]) == CARDTYPE_SSANG_PEE) { userSsangPeeIdx = (byte)i; } } // for(i=0; i<MAX_PEE; i++). // 상대방의 패에서 1점 자리 패를 찾았다면, if(enemyPeeIdx != CARD_NULL) { // 사용자의 피를 한장 추가 하고, addPae(userType, cardsPee[enemyType][enemyPeeIdx]); // 빈 슬롯을 매꿔 준다. cardsPee[enemyType][enemyPeeIdx] = cardsPee[enemyType][enemyLastIdx]; cardsPee[enemyType][enemyLastIdx] = CARD_NULL; } else if(userPeeIdx != CARD_NULL) // 사용자의 패에서 1점 자리 패를 찾았다면, { byte Temp; // 상대방의 마지막 패가 쌍피라면, if(gCard.getType(cardsPee[enemyType][enemyLastIdx]) == CARDTYPE_SSANG_PEE) { // 상대방으로부터 쌍피를 받고, Temp = cardsPee[enemyType][enemyLastIdx]; // 1점 짜리 피를 넘겨줌. cardsPee[enemyType][enemyLastIdx] = cardsPee[userType][userPeeIdx]; cardsPee[userType][userPeeIdx] = Temp; } else if(userSsangPeeIdx != CARD_NULL) // 사용자가 쌍피가 있다면, { // 상대방의 마지막 패가 쓰리피라면, if(gCard.getType(cardsPee[enemyType][enemyLastIdx])==CARDTYPE_THREE_PEE) { // 상대방으로부터 쓰리피를 받고, Temp = cardsPee[enemyType][enemyLastIdx]; // 쌍피를 넘겨줌. cardsPee[enemyType][enemyLastIdx] = cardsPee[userType][userSsangPeeIdx]; cardsPee[userType][userSsangPeeIdx] = Temp; } } } return 1; } // end public byte robPee(byte userType).

함수가 조금 길긴 하지만, 별건 아닙니다.

먼저, 0부터 MAX_PEE(36) 까지 루프를 돌면서, 각각의 플레이어의 피에서 쌍피와 1점자리 일반피를 찾아냅니다.

만약에 상대방의 피에서 1점 자리 일반 피를 찾았다면, 그 피만 받고 끝나면 되겠죠.


그러나, 상대방의 피에 1점자리 피가 없다면, 쌍피가 있는지 보고,

플레이어가 1점자리 피가 있다면, 쌍피를 받고 1점자리 피를 상대에게 넘겨줍니다.


상대방이 쓰리피가 있다면, 플레이어에게 쌍피가 있는지 보고,

플레이어가 쌍피가 있다면, 쓰리피를 받고 쌍피를 넘겨 줍니다.

이렇게 하면 피 한장을 받을 수 있겠죠?


고/스톱 관리.

맞고 역시 1:1 고스톱이나 마찬 가지이므로, 고와 스톱이 존재하죠.

투고 까지는 1고는 1점, 2고는 2점. 과 같이 더해지지만,

3고부터는 점수의 2배, 4고는 점수의 4배, 5고는 점수의 8배, .... 이런식으로 늘어납니다.


고/스톱 을 관리 해 주는 checkGoStop() 함수는 다음과 같이 작성해 줍니다.

private void checkGoStop()
{ if(room.getCntGo(curTurn) == 0 && room.getRealPoint(curTurn) >= LIMITPOINT_GOSTOP) { // 고를 한번도 하지 않았을때, 일정 점수를 초과하면, prePoint[curTurn] = room.getPoint(curTurn); // 현재 점수 저장. // 고스톱 선택 다이얼로그를 보여준다. curViewState = State.GAME_SELECTGOSTOP; room.incCntGo(curTurn); // 고 횟수 +1. repaints(); return; } else if(room.getCntGo(curTurn) >= 1 && room.getRealPoint(curTurn)-prePoint[curTurn] >= 1) { // 고를 한번이상 한 뒤, 1점이라도 내면, prePoint[curTurn] = room.getPoint(curTurn); // 현재 점수 저장. // 고스톱 선택 다이얼로그를 보여준다. curViewState = State.GAME_SELECTGOSTOP; room.incCntGo(curTurn); // 고 횟수 +1. repaints(); return; }
}

위에서 별로 헷갈릴만한 내용은 없을 거라고 봅니다.

고를 한번도 하지 않은 상태에서는 LIMITPOINT_GOSTOP(GameView에 7로 정의되어있음)을 초과하면,

고/스톱을 물어보지만, 일단 한번 고를 한 상태에서는 추가로 점수가 발생할 때마다 고/스톱을 물어보죠.


이 checkGoStop() 함수는추가로 점수가 발생 하는 곳에다가 모두 삽입을 해주어야 하겠지요.

카드를 냈을때에만, 점수가 발생하는 것이 아니니까요.

흔들었을 때에도, 점수가 2배가 되므로, 고/스톱을 물어 보아야 하고,

국진을 쌍피로 선택 했을때에도, 추가 점수가 발생하므로, 고/스톱을 물어보아야 하고,

일반 적인 경우에도, 점수가 발생했다면, 고/스톱을 체크해야하겠죠.


그런데 이 checkGoStop() 함수에 보면, getRealPoint() 라는 함수를 이용해서 점수를 얻어 오고 있는데,

지난 시간에 우리가 작성했던 점수를 얻어오는 함수는 getPoint() 함수였었죠?


getRealPoint() 함수는, 각종 룰의 공식에 의해 실제적으로 얻어지는 실제 점수를 리턴해 줍니다.

getRealPoint() 함수는 gostop.GameRoom 클래스에 정의되어 있습니다.

/**
 * 실제 점수를 리턴한다.
 * @param userType
 * @return
 */
public int getRealPoint(byte userType)
{
    int realPoint=getPoint(userType);

    // 원고나 투고 라면,
    if(cntGo[userType] == 1
        ||  cntGo[userType] == 2)
    {
        // 원고면 1점, 투고면 2점을 더해줌.
        realPoint += cntGo[userType];
    }
    else if(cntGo[userType] >= 3) // 쓰리고 이상이라면,
    {
        // 3고 부터는, 2의 (고횟수-2)승으로 계산.
        realPoint *= Etc.pow(2, cntGo[userType]-2);
    }

    // 흔들기도, 2의 흔든 횟수승으로 계산.
    realPoint *= Etc.pow(2, cntShake[userType]);

    return realPoint;
}

고나 흔들기가 발생하면, 점수에 두배를 곱해야 하는데,

현재 점수의 두배가 아니라 최종 점수의 두배이므로, 현재 점수를 함부로 건드려선 안되겠죠.


그래서, 고와 흔들기에 의한 변경된 점수는 getRealPoint() 함수를 통해서 얻어 옵니다.

여기서 3고 부터는 2의 (고횟수-2)승 이라는 것까지는 이해가 가시겠죠?

그런데 여기서 제곱승을 구하는 함수가 Etc.pow() 함수가 쓰였습니다.

제곱승을 구해오는 pow() 함수는 표기 된 데로 common.Etc 클래스에 정의되어 있습니다.

/**
 * a의 n제곱승을 리턴.
 * @param a
 * @param n
 * @return a의 n승.
 */
public static int pow(int a, int n)
{
    byte i;
    short nPowOfa=1;

    for(i=0; i<n; i++)
    {
        nPowOfa *= a;
    }

    return nPowOfa;
}

그냥 단순히 인자로 받은 a의 n승을 리턴해 주는 함수입니다.

for 루프를 n번 돌아서 1에 a를 곱해 주면 a의 n승을 구해 올 수 있겠죠.

나가리.

나가리란, 두명의 사용자가 모든 카드를 다 냈는데도, 게임이 끝나지 않았을때,

즉, 그 판에서 아무도 7점을 내지 못하거나 상대방이 고를 했는데 추가 점수를 내지 못했을 경우를 말합니다.

나가리 시에는 게임을 끝내고 결과를 보여 주어야 하겠죠.

/**
 * 나가리를 체크.
 * @return 나가리 여부.
 */
private boolean checkNagari()
{
    // 플레이어의 카드와 컴퓨터의 카드가 없다면,
    if(room.numOfPlayerCard == 0 && room.numOfComputerCard == 0 )
    {
        repaints();

        // 나가리.
        room.setNagari(true);
        // 게임 끝.
        curViewState = State.GAME_RESULT;
        repaints();

        return false;
    }

    return true;
}

플레이어의 카드와 컴퓨터의 카드가 한장도 없는데, 게임이 끝나질 않았다면, 나가리로 처리하고,

결과 화면을 보여줍니다.

결과 출력.

이제 그럼 결과 화면을 출력해 보도록 하겠습니다.

/**
 * 결과 화면을 그려준다.
 * @param g
 */
private void drawResult(Graphics g)
{
    drawRunningGame(g);

    // 나가리라면...
    if(room.getNagari() == true)
    {
        if(gameEnd == false)
            gameEnd = true;

        g.drawString("나가리!", 0, 0, Graphics.LEFT|Graphics.TOP);
    }
    else
    {
        /* 벌칙. */
        byte goBak; // 고박.
        byte peeBak; // 피박.
        byte kwangBak; // 광박.
        byte mungDda; // 멍따.

        switch(curTurn)
        {
        case User.PLAYER: // 플레이어의 턴일때,
            g.drawString("플레이어 승!", 0, 0, Graphics.LEFT|Graphics.TOP);

            // 상대방이 고를 한번 이상 했었다면, 고박. (고박일경우 돈 두배 획득)
            goBak = (room.getCntGo(User.COMPUTER) >= 1)? 
                    (byte)2 : (byte)1;
// 상대방의 피가 7장 이하라면, 피박. (피박일경우 돈 두배 획득) peeBak = (room.getNumOfPee(User.COMPUTER) < 7)? (byte)2 : (byte)1; // 상대방의 광이 없다면, 광박. (광박일경우, 돈 두배 획득) kwangBak = (room.getNumOfKwang(User.COMPUTER) == 0)? (byte)2 : (byte)1;
// 상대방의 열이 7장 이상이라면, 멍따. (멍따일 경우, 돈 두배 획득) mungDda = (room.getNumOfYeul(User.COMPUTER) >= 7)? (byte)2 : (byte)1; if(gameEnd == false) { gameEnd = true; // 플레이어의 돈 증가. room.addMoney(curTurn, room.MONEY_PER_POINT *room.getRealPoint(User.PLAYER) *goBak*kwangBak*peeBak*mungDda); // 컴퓨터의 돈 감소. room.addMoney(curTurn, -room.MONEY_PER_POINT *room.getRealPoint(User.COMPUTER) *goBak*kwangBak*peeBak*mungDda); } break; case User.COMPUTER: // 컴퓨터의 턴일대, g.drawString("컴퓨터 승!", 0, 0, Graphics.LEFT|Graphics.TOP); // 상대방이 고를 한번 이상 했었다면, 고박. (고박일경우 돈 두배 획득) goBak = (room.getCntGo(User.COMPUTER) >= 1)? (byte)2 : (byte)1;
// 상대방의 피가 7장 이하라면, 피박. (피박일경우 돈 두배 획득) peeBak = (room.getNumOfPee(User.COMPUTER) < 7)? (byte)2 : (byte)1; // 상대방의 광이 없다면, 광박. (광박일경우, 돈 두배 획득) kwangBak = (room.getNumOfKwang(User.COMPUTER) == 0)? (byte)2 : (byte)1;
// 상대방의 열이 7장 이상이라면, 멍따. (멍따일 경우, 돈 두배 획득) mungDda = (room.getNumOfYeul(User.COMPUTER) >= 7)? (byte)2 : (byte)1; if(gameEnd == false) { gameEnd = true; // 플레이어의 돈 감소. room.addMoney(curTurn, -room.MONEY_PER_POINT *room.getRealPoint(User.PLAYER) *goBak*kwangBak*peeBak*mungDda); // 컴퓨터의 돈 증가. room.addMoney(curTurn, room.MONEY_PER_POINT *room.getRealPoint(User.COMPUTER) *goBak*kwangBak*peeBak*mungDda); } } } }

만약에 나가리로 게임이 끝났다면, 화면에 그냥 나가리라고 출력해 줍니다.

만약에 나가리가 아니라면, 현재 턴의 플레이어가 스톱을 했을테니 현재 턴의 플레이어가 이겼겠죠.


이곳에서 고박, 피박, 광박, 멍따 등의 벌칙 룰을 적용해 줍니다.

// 상대방이 고를 한번 이상 했었다면, 고박. (고박일경우 돈 두배 획득)
goBak = (room.getCntGo(User.COMPUTER) >= 1)? (byte)2 : (byte)1;

// 상대방의 피가 7장 이하라면, 피박. (피박일경우 돈 두배 획득)

peeBak = (room.getNumOfPee(User.COMPUTER) < 7)? (byte)2 : (byte)1;

// 상대방의 광이 없다면, 광박. (광박일경우, 돈 두배 획득)

kwangBak = (room.getNumOfKwang(User.COMPUTER) == 0)? (byte)2 : (byte)1;

// 상대방의 열이 7장 이상이라면, 멍따. (멍따일 경우, 돈 두배 획득)

mungDda = (room.getNumOfYeul(User.COMPUTER) >= 7)? (byte)2 : (byte)1;

상대방이 고를 한번이라도 했다면, 고박으로 돈을 두배로 획득하게 해주죠.

이때 goBak에 대입해주는 정수를 곱하므로, 고박이라면 획득 금액에 2를 곱해주고, 아니면 1을 곱하겠죠.

나머지도 모두 마찬가겠죠?


따로 gameEnd라는 boolean 변수를 두어서 gameEnd가 false일때에만, 플레이어의 돈이 증감하게 합니다.

그래야 drawResult() 함수가 중복 호출 되었을때, 돈이 이중으로 빠져나가지 않게 할 수 있겠죠.


이번 시간까지의 내용으로 전반적인 맞고 게임의 틀은 모두 갖춘것 같네요.

이번 시간에 작성한 게임의 스크린 샷은 지난 시간과 별다를게 없으므로 생략하도록 하겠습니다.

 

Posted by maysent
: