Qt 로 만들자: 숫자 야구

어릴 때 한 번 쯤은 누구나 숫자 야구를 해 보았을 것이다. 이번에는 이 숫자야구를 구현해보자.

1. 요구 사항

숫자 야구의 규칙은 다음과 같다.

  • 공격수는 0 부터 9 까지 숫자 중에 3 개의 숫자를 고른다
  • 숫자와 위치가 모두 맞으면 스트라이크
  • 숫자는 맞고 위치가 다르면 볼
  • 스트라이크만 3개면 종료
  • 수비수의 숫자 3 개는 겹치지 않는다

2. 구현

2.1 프로젝트 생성

  • 프로젝트 이름 : Baseball
  • 메인 클래스 이름 : Baseball
  • 메인 클래스 유형 : QMainWindow

2.1 사용할 위젯

  • 3 개의 숫자를 입력받기 위한 3 개의 QLineEdit 위젯
  • 입력을 완료하기 위한 버튼
  • 숫자 야구를 새로 하기 위한 버튼
  • 그동안 입력한 숫자와 그에 대한 판정을 기록할 리스트

2.2 헤더 파일(baseball.h)

다음은 이를 구현한 헤더 파일의 내용이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef BASEBALL_H
#define BASEBALL_H

#include <QMainWindow>
#include <QtWidgets>

class Baseball : public QMainWindow
{
    Q_OBJECT

public:
    Baseball(QWidget *parent = 0);
    ~Baseball();

private:
    static const int MAX_ANSWER_COUNT = 3;

    QList<QLineEdit *> _numLineList;
    QPushButton *_enterPush;
    QPushButton *_newPush;

    QListWidget *_historyList;

    QList<int> _answerList;

    int _tryCount;

    void initWidgets();
    void initActions();
    void initAnswer();

private slots:
    void checkAnswer();
    void newBaseball();
};

#endif // BASEBALL_H


5 번째 줄에서 QtWidgets 헤더가 포함되었다.

16 번째 줄에서 최대 입력받을 숫자의 갯수를 지정했다.

18 번째 줄에서 QList<T> 형태의 자료형이 등장했는데, QList<T> 는 T 자료형에 대한 리스트이다. 배열이나 리스트를 쓰고자 한다면, QList 가 제격이다. 비슷한 자료형으로 QVector 나 QLinkedList 가 있으나, Qt 에서는 QList 를 추천한다. 이에 대해서는 도움말을 참고하자.

22 번째 줄에서 QListWidget 클래스가 나오는데, 이 클래스는 목록을 나타내주는 편의 클래스이다.

33 ~ 34 번째 줄의 private slots 는 각각 [입력] 버튼과 [새로] 버튼에 연결될 슬롯들이다.

2.3 소스 파일(baseball.cpp)

2.3.1 생성자와 소멸자

생성자와 소멸자는 지난번과 크게 다르지 않다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Baseball::Baseball(QWidget *parent)
    : QMainWindow(parent),
      _tryCount(0)
{
    initWidgets();
    initActions();

    newBaseball();
}

Baseball::~Baseball()
{
}


생성자에서 사용할 위젯들을 초기화하고, 시그널과 슬롯을 연결한다. 그리고 숫자 야구를 준비한다. 소멸자에서 할 일은 없다. 메인 클래스 자체가 소멸할 때 소속되어 있는 자식 위젯들도 함께 소멸하기 때문이다.

2.3.2 initWidgets()

위젯들을 초기화하고, 적절하게 배치해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void Baseball::initWidgets()
{
    // 0..9 만 옳다
    QIntValidator *numValidator = new QIntValidator(0, 9, this);

    while (_numLineList.count() < MAX_ANSWER_COUNT)
    {
        QLineEdit *lineEdit = new QLineEdit;

        lineEdit->setMaxLength(1);              // 최대 1 문자
        lineEdit->setValidator(numValidator);   // 0..9 만 허용

        _numLineList.append(lineEdit);
    }

    _enterPush = new QPushButton(tr("입력(&E)"));
    _newPush = new QPushButton(tr("새로(&N)"));

    // 수평으로 나열
    QHBoxLayout *hboxLayout = new QHBoxLayout;
    hboxLayout->addWidget(_numLineList.at(0));
    hboxLayout->addStretch();
    hboxLayout->addWidget(_numLineList.at(1));
    hboxLayout->addStretch();
    hboxLayout->addWidget(_numLineList.at(2));
    hboxLayout->addStretch();
    hboxLayout->addWidget(_enterPush);
    hboxLayout->addWidget(_newPush);

    _historyList = new QListWidget;

    // 수직으로 나열
    QVBoxLayout *vboxLayout = new QVBoxLayout;
    vboxLayout->addLayout(hboxLayout);
    vboxLayout->addWidget(_historyList);

    // 센트럴 위젯 설정
    setCentralWidget(new QWidget);

    // 레이아웃 설정
    centralWidget()->setLayout(vboxLayout);
}


4 번째 줄에, QIntValidator 클래스가 등장하는데, 이는 QValidator 클래스를 상속한 클래스로 정수형 자료만 받아들이고 싶을 때 사용하는 클래스이다. 최솟값과 최댓값을 결정할 수 있고, 이 밖의 값들은 잘못된 것으로 판단하다. 여기에서는 0 부터 9 까지만 허용한다.

아울러, 마지막에 this 를 넘겨준다. 이는 QIntValidator 인스턴스의 부모를 this 로 한다는 뜻이다. 이렇게 하면 별도로 QIntValidator 인스턴스를 소멸시키지 않더라도 this 곧 메인 클래스가 소멸될 때 QIntValidator 인스턴스가 함께 소멸된돠.

6 번째 줄에 count() 가 나오는데, 이는 QList<T> 에 들어있는 자료의 갯수를 알려주는 QList 클래스의 멤버 함수이다. 만일 count(T) 형태로 쓰인다면 T 가 몇 개 있는지 알려준다.

최대 MAX_ANSWER_COUNT 만큼 QLineEdit 위젯을 만들고, 이 위젯의 특성을 설장한다. 10 번째 줄의 setMaxLength() 는 QLineEdit 위젯이 받아들일 최대 문자의 갯수를 지정하고, 11 번째 줄의 setValidator() 는 QLineEdit 위젯이 받아들이는 문자의 종류를 결정한다. 여기에서는 0 부터 9 까지만 입력받도록 한다.

20 번째 줄을 보면 QHBoxLayout 클래스가 나오는데, 이는 Qt 에서 제공하는 레이아웃 클래스의 하나로 포함된 위젯들을 수평 방향으로 나열한다.

출처: Qt 도움말(QHBoxLayout)


따라서 QLineEdit 위젯 3 개와 QPushButton 두 개가 한 줄에 수평 방향으로 나열된다.

33 번째 QVBoxLayout 은 QHBoxLayout 과는 반대로 포함된 위젯들을 수직으로 나열한다.

출처 : Qt 도움말(QVBoxLayout)


따라서 윗쪽에는 수평으로 나열된 5 개의 위젯이 있고, 아랫쪽에는 리스트 위젯 하나가 있다. 다음은 이렇게 완성된 메인 윈도우의 모습이다.


2.3.2 initActions()

이제 시그널과 슬롯을 연결해보자.

1
2
3
4
5
6
7
void Baseball::initActions()
{
    // 사용자가 "입력" 버튼을 클릭하면 checkAnswer() 호출
    connect(_enterPush, SIGNAL(clicked(bool)), this, SLOT(checkAnswer()));
    // 사용자가 "새로" 버튼을 클릭하면 newBaseball() 호출
    connect(_newPush, SIGNAL(clicked(bool)), this, SLOT(newBaseball()));
}


간단하다. _enterPush[입력] 버튼과 _newPush[새로]  버튼이 클릭되면 각각 checkAnswer() 와 newBaseball() 슬롯을 호출한다.

2.3.3 newBaseball()

이 함수는 숫자 야구를 초기화하는 역할을 한다. 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Baseball::newBaseball()
{
    // 입력 숫자 지우기
    Q_FOREACH (QLineEdit *lineEdit, _numLineList)
        lineEdit->clear();

    // 입력 목록 지우기
    _historyList->clear();

    // 촛점 이동
    _numLineList.at(0)->setFocus();

    // 시도 횟수 초기화
    _tryCount = 0;

    // 숫자 생성
    initAnswer();
}


4 번째 줄의 Q_FOREACH() 또는 foreach() 는 컨테이너 클래스의 전체 내용을 순환하는 Qt 매크로/키워드이다. foreach() 가 기존의 이름공간(namespace)과 충돌하는 경우 Q_FOREACH() 를 사용하자. 그리고 Qt 전용이라는 것을 나타내기 위해서라도 Q_FOREACH() 를 쓰는 것도 좋다. Q_FOREACH()/foreach() 의 용법은 다음과 같다.

Q_FOREACH(변수, 컨테이너)

변수는 위 코드에 나타나 있듯이 Q_FOREACH() 내에서 선언해도 되고, 외부에 선언되어 있는 변수를 써도 된다.

8 번째 줄에는 있는 clear() 는 QListWidget 클래스 위젯의 내용을 지우는 함수로, QListWidget 클래스의 멤버함수이다.

11 번째 줄에는 있는 at() 은 QList<T> 변수를 인덱스 형태로 접근하는 함수이다. at() 대신에 [] 를 쓸 수 있지만, at() 이 더 빠르다고 한다. 되도록 at() 을 쓰자.

그리고 setFocus() 는 입력 촛점을 해당 위젯으로 바꾸는 함수이다. QWidget 클래스의 멤버 함수이다.

2.3.4 initAnswer()

이 함수는 수비자의 숫자를 생성한다. 우리가 이 숫자를 맞혀야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Baseball::initAnswer()
{
    // _answerList 초기화
    _answerList.clear();

    // random seed 초기화
    qsrand(QTime::currentTime().msecsSinceStartOfDay());

    // 숫자 생성
    while (_answerList.count() < MAX_ANSWER_COUNT)
    {
        int num;

        do
        {
            num = qrand() * 9 / RAND_MAX;           // 0..9 숫자 생성
        } while (_answerList.indexOf(num) != -1);   // 같은 숫자가 있으면

        // 숫자 저장
        _answerList.append(num);
    }
}


4 번째 줄에 있는 clear() 는 QList<T> 변수에 저장되어 있는 내용을 지우는 함수이다. QList 클래스의 멤버 함수.

7 번째 줄에 있는 qsrand() 는 C++ srand() 함수의 thread-safe 판이다. QTime::currentTime() 은 현재 시간을 알려주는 QTime 클래스의 정적 멤버 함수이다. msecsSinceStartOfDay() 는 하루가 시작되고 나서 경과한 시간을 밀리초 단위로 알려주는 QTime 클래스의 멤버 함수이다.

16 번째 줄의 qrand() 은 C++ rand() 함수의 thread-safe 판이다. 그리고 이를 이용해서 0 에서 9 까지 난수를 발생시킨다. 정해진 범위에서 난수를 발생시키는 방법으로 이것말고 나머지 연산을 이용하는 것이 있다. 다음으로 대체할 수도 있다.

num = qrand() % 10

하지만, 나머지 연산보다는 비례 관계를 이용하는 것이 난수를 더 고르게 분포시킨다고 알려져 있다.

17 번째 줄의 indexOf() 는 주어진 값의 인덱스를 찾는 함수이다. 주어진 값이 없을 때는 -1 을 돌려준다. QList 클래스의 멤버 함수이다.

20 번째 줄의 append() 는 주어진 값을 리스트의 끝에 추가한다.

2.3.5 checkAnswer()

사용자가 [입력] 버튼을 누르면 답을 확인한다. 이 때, 숫자가 빠져 있는지, 겹친 숫자가 있는지 따위를 확인한다. 그리고 스트라이크와 볼을 판단하여 목록에 추가하고, 성공한 경우 성공했다는 메세지를 보여준 후, 숫자 야구를 새롭게 시작한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
void Baseball::checkAnswer()
{
    int emptyIndex = -1;

    // 비어 있는 것이 있는지 확인
    Q_FOREACH (QLineEdit *lineEdit, _numLineList)
    {
        if (lineEdit->text().isEmpty())
        {
            emptyIndex = _numLineList.indexOf(lineEdit);
            break;
        }
    }

    // 비어 있는 것이 있으면
    if (emptyIndex != -1)
    {
        QMessageBox::warning(this, qApp->applicationName(),
                             tr("%1번째 숫자가 입력되지 않았습니다.")
                                .arg(emptyIndex + 1));

        // 입력 촛점 설정
        _numLineList.at(emptyIndex)->setFocus();
        // 텍스트 전체 선택
        _numLineList.at(emptyIndex)->selectAll();

        return;
    }

    QList<int> inputNumList;

    // 입력된 숫자를 저장
    Q_FOREACH (QLineEdit *lineEdit, _numLineList)
        inputNumList.append(lineEdit->text().toInt());

    // 겹친 숫자가 있는지 확인
    Q_FOREACH (int num, inputNumList)
    {
        if (inputNumList.count(num) > 1)
        {
            QMessageBox::warning(this, qApp->applicationName(),
                                 tr("겹친 숫자(%1)가 있습니다.")
                                 .arg(num));

            int index = inputNumList.indexOf(num);
            // 입력 촛점 설정
            _numLineList.at(index)->setFocus();
            // 텍스트 전체 선택
            _numLineList.at(index)->selectAll();

            return;
        }
    }

    int strike = 0;
    int ball = 0;

    // 스트라이크, 볼 판단
    Q_FOREACH (int num, inputNumList)
    {
        int answerIndex = _answerList.indexOf(num);

        // 답이 있나 ?
        if (answerIndex != -1)
            answerIndex == inputNumList.indexOf(num) ?
                        strike++ :  // 같은 위치면 스트라이크
                        ball++;     // 아니면 볼
    }

    QString result;

    // 시도 횟수
    result.append(tr("%1 번째(").arg(++_tryCount));

    // 입력한 숫자들
    Q_FOREACH (QLineEdit *lineEdit, _numLineList)
        result.append(lineEdit->text()).append(" ");
    result.chop(1);     // 마지막 한 문자(빈칸) 삭제

    // 판정 결과
    result.append(tr(") : %1s %2b").arg(strike).arg(ball));

    // 입력 목록 첫번째 줄에 삽입
    _historyList->insertItem(0, result);

    if (strike == MAX_ANSWER_COUNT)
    {
        QString msg;

        msg.append(tr("%1 번째만에 맞혔습니다.\n").arg(_tryCount));
        msg.append(tr("정답: "));
        Q_FOREACH (QLineEdit *lineEdit, _numLineList)
            msg.append(lineEdit->text()).append(" ");
        msg.chop(1);

        QMessageBox::information(this, qApp->applicationName(), msg);

        newBaseball();
    }

    // 첫번째 숫자로 촛점 이동
    _numLineList.at(0)->setFocus();
    // 텍스트 전체 선택
    _numLineList.at(0)->selectAll();
}


8번째 줄의 isEmpty() 는 QString 문자열이 비어 있는지 확인하는 QString 클래스의 멤버 함수이다.

18 번째 줄의 QMessageBox 클래스는 메세지를 담은 대화상자를 사용자에게 보여준다. warning() 은 경고 아이콘을 메세지와 함께 보여주는 정적 멤버 함수이다.
qApp 은 main.cpp 에서 만드는 QApplication 인스턴스를 가리키는 전역 포인터 변수이다. applicationName() 은 사용자가 따로 지정하지 않으면 실행 파일의 이름을 돌려주는 QCoreApplication 클래스의 멤버 함수이다.

25 번째 줄의 selectAll() 은 모든 텍스트를 선택하는 QLineEdit 클래스의 멤버 함수이다.

34 번째 줄의 toInt() 는 문자열을 정수로 바꾸는 QString 클래스의 멤버 함수이다.

78 번째 줄의 chop() 는 문자열 끝에서부터 주어진 갯수만큼 문자를 지운다.

84  번째 줄의 insertItem() 은 주어진 위치에 아이템을 삽입하는 QListWidget 클래스의 멤버 함수이다.

3. 마무리하면서...

이번에는 숫자 야구를 만들어 보았다. 하지만 기본적인 기능만 있을 뿐, 사용자 편의성의 측면에서는 많이 부족하다. 특히 숫자를 입력하면 다음 칸으로 이동한다든지, 엔터키를 누르면 자동으로 [입력] 버튼으로 연결된다든지 하는 소소한 기능들을 추가하면 더 좋을 것이다.

그리고 새로운 함수가 등장하면 주저하지 말고 반드시 도움말을 확인하자. 여기에 사용된 용법말고 여러가지 오버로딩된 함수들이 있으니 말이다.

// ----- 2015/07/31
숫자 야구 전체 소스는 다음 링크에서 확인할 수 있다.


다음은 baseball.h 와 baseball.cpp 의 완성된 소스이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/****************************************************************************
**
** Baseball, Number baseball game
**
** Copyright (C) 2015 by KO Myung-Hun
** All rights reserved.
** Contact: KO Myung-Hun (komh@chollian.net)
**
** This file is part of K Alarm.
**
** $BEGIN_LICENSE$
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** $END_LICENSE$
**
****************************************************************************/


#ifndef BASEBALL_H
#define BASEBALL_H

#include <QMainWindow>
#include <QtWidgets>

class Baseball : public QMainWindow
{
    Q_OBJECT

public:
    Baseball(QWidget *parent = 0);
    ~Baseball();

private:
    static const int MAX_ANSWER_COUNT = 3;

    QList<QLineEdit *> _numLineList;
    QPushButton *_enterPush;
    QPushButton *_newPush;

    QListWidget *_historyList;

    QList<int> _answerList;

    int _tryCount;

    void initWidgets();
    void initActions();
    void initAnswer();

private slots:
    void checkAnswer();
    void newBaseball();
};

#endif // BASEBALL_H


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/****************************************************************************
**
** Baseball, Number baseball game
**
** Copyright (C) 2015 by KO Myung-Hun
** All rights reserved.
** Contact: KO Myung-Hun (komh@chollian.net)
**
** This file is part of K Alarm.
**
** $BEGIN_LICENSE$
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** $END_LICENSE$
**
****************************************************************************/


#include "baseball.h"

#include <QtWidgets>

Baseball::Baseball(QWidget *parent)
    : QMainWindow(parent),
      _tryCount(0)
{
    initWidgets();
    initActions();

    newBaseball();
}

Baseball::~Baseball()
{
}

void Baseball::initWidgets()
{
    // 0..9 만 옳다
    QIntValidator *numValidator = new QIntValidator(0, 9, this);

    while (_numLineList.count() < MAX_ANSWER_COUNT)
    {
        QLineEdit *lineEdit = new QLineEdit;

        lineEdit->setMaxLength(1);              // 최대 1 문자
        lineEdit->setValidator(numValidator);   // 0..9 만 허용

        _numLineList.append(lineEdit);
    }

    _enterPush = new QPushButton(tr("입력(&E)"));
    _newPush = new QPushButton(tr("새로(&N)"));

    // 수평으로 나열
    QHBoxLayout *hboxLayout = new QHBoxLayout;
    hboxLayout->addWidget(_numLineList.at(0));
    hboxLayout->addStretch();
    hboxLayout->addWidget(_numLineList.at(1));
    hboxLayout->addStretch();
    hboxLayout->addWidget(_numLineList.at(2));
    hboxLayout->addStretch();
    hboxLayout->addWidget(_enterPush);
    hboxLayout->addWidget(_newPush);

    _historyList = new QListWidget;

    // 수직으로 나열
    QVBoxLayout *vboxLayout = new QVBoxLayout;
    vboxLayout->addLayout(hboxLayout);
    vboxLayout->addWidget(_historyList);

    // 센트럴 위젯 설정
    setCentralWidget(new QWidget);

    // 레이아웃 설정
    centralWidget()->setLayout(vboxLayout);
}

void Baseball::initActions()
{
    // 사용자가 "입력" 버튼을 클릭하면 checkAnswer() 호출
    connect(_enterPush, SIGNAL(clicked(bool)), this, SLOT(checkAnswer()));
    // 사용자가 "새로" 버튼을 클릭하면 newBaseball() 호출
    connect(_newPush, SIGNAL(clicked(bool)), this, SLOT(newBaseball()));
}

void Baseball::initAnswer()
{
    // _answerList 초기화
    _answerList.clear();

    // random seed 초기화
    qsrand(QTime::currentTime().msecsSinceStartOfDay());

    // 숫자 생성
    while (_answerList.count() < MAX_ANSWER_COUNT)
    {
        int num;

        do
        {
            num = qrand() * 9 / RAND_MAX;           // 0..9 숫자 생성
        } while (_answerList.indexOf(num) != -1);   // 같은 숫자가 있으면

        // 숫자 저장
        _answerList.append(num);
    }
}

void Baseball::checkAnswer()
{
    int emptyIndex = -1;

    // 비어 있는 것이 있는지 확인
    Q_FOREACH (QLineEdit *lineEdit, _numLineList)
    {
        if (lineEdit->text().isEmpty())
        {
            emptyIndex = _numLineList.indexOf(lineEdit);
            break;
        }
    }

    // 비어 있는 것이 있으면
    if (emptyIndex != -1)
    {
        QMessageBox::warning(this, qApp->applicationName(),
                             tr("%1번째 숫자가 입력되지 않았습니다.")
                                .arg(emptyIndex + 1));

        // 입력 촛점 설정
        _numLineList.at(emptyIndex)->setFocus();
        // 텍스트 전체 선택
        _numLineList.at(emptyIndex)->selectAll();

        return;
    }

    QList<int> inputNumList;

    // 입력된 숫자를 저장
    Q_FOREACH (QLineEdit *lineEdit, _numLineList)
        inputNumList.append(lineEdit->text().toInt());

    // 겹친 숫자가 있는지 확인
    Q_FOREACH (int num, inputNumList)
    {
        if (inputNumList.count(num) > 1)
        {
            QMessageBox::warning(this, qApp->applicationName(),
                                 tr("겹친 숫자(%1)가 있습니다.")
                                 .arg(num));

            int index = inputNumList.indexOf(num);
            // 입력 촛점 설정
            _numLineList.at(index)->setFocus();
            // 텍스트 전체 선택
            _numLineList.at(index)->selectAll();

            return;
        }
    }

    int strike = 0;
    int ball = 0;

    // 스트라이크, 볼 판단
    Q_FOREACH (int num, inputNumList)
    {
        int answerIndex = _answerList.indexOf(num);

        // 답이 있나 ?
        if (answerIndex != -1)
            answerIndex == inputNumList.indexOf(num) ?
                        strike++ :  // 같은 위치면 스트라이크
                        ball++;     // 아니면 볼
    }

    QString result;

    // 시도 횟수
    result.append(tr("%1 번째(").arg(++_tryCount));

    // 입력한 숫자들
    Q_FOREACH (QLineEdit *lineEdit, _numLineList)
        result.append(lineEdit->text()).append(" ");
    result.chop(1);     // 마지막 한 문자(빈칸) 삭제

    // 판정 결과
    result.append(tr(") : %1s %2b").arg(strike).arg(ball));

    // 입력 목록 첫번째 줄에 삽입
    _historyList->insertItem(0, result);

    if (strike == MAX_ANSWER_COUNT)
    {
        QString msg;

        msg.append(tr("%1 번째만에 맞혔습니다.\n").arg(_tryCount));
        msg.append(tr("정답: "));
        Q_FOREACH (QLineEdit *lineEdit, _numLineList)
            msg.append(lineEdit->text()).append(" ");
        msg.chop(1);

        QMessageBox::information(this, qApp->applicationName(), msg);

        newBaseball();
    }

    // 첫번째 숫자로 촛점 이동
    _numLineList.at(0)->setFocus();
    // 텍스트 전체 선택
    _numLineList.at(0)->selectAll();
}

void Baseball::newBaseball()
{
    // 입력 숫자 지우기
    Q_FOREACH (QLineEdit *lineEdit, _numLineList)
        lineEdit->clear();

    // 입력 목록 지우기
    _historyList->clear();

    // 촛점 이동
    _numLineList.at(0)->setFocus();

    // 시도 횟수 초기화
    _tryCount = 0;

    // 숫자 생성
    initAnswer();
}


// -----



댓글

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷