[Jumping Game - LCD로 만드는 게임]
방학 시즌이 다가오면서 학교에서도 수행평가와 시험에 대한 마무리가 진행되었다. 이 글에서는 마이크로프로세서 제어 과목에서 내가 1학기 개인과제로 제출한 LCD로 만든 게임 - Jumping Game 프로젝트에 대해서 기록하고자 한다.
일단 거두절미하고 실행영상부터 보여주고자 한다.
이 프로젝트는 LCD를 이용해서 Jumping Game을 구현한다. 하트를 먹으면 100점을 얻으며 장애물에 걸리거나 9900점을 얻으면 게임이 끝난다. 점수 9900점 미만의 경우 모두 게임에 진 것으로 판단한다. 이렇게 얻은 점수를 기준으로 A,B,C,... 등급으로 게임 성적이 표현되며 이것은 6개의 LED로 확인할 수 있다. LED의 색깔이 바뀔 경우 아이템(장애물, 하트)이 날아오는 속도가 빨라진다. 3번 스위치를 클릭하면 게임을 정지시키고 점수와 등급을 확인할 수 있다. 또한 게임을 정지시킨 상태에서 2번 스위치를 클릭하면 게임 중간에 게임을 초기화 시킬 수 있다. 게임이 끝나고 2번 스위치를 클릭하면 게임을 다시 시작할 수 있다.
기능을 부품을 기준으로 요약하면 아래와 같다.
승리 조건 : 점수 9900점 - Special
- SW0 : 플레이어 위치를 LCD 1번 줄로 이동
- SW1 : 플레이어 위치를 LCD 2번 줄로 이동
- SW2 : 게임 리셋
- SW3 : 게임을 정지시키고 점수와 등급 표시
- 1digit FND : 게임 성적을 A, B, C, ... 대문자로 표시
- 4 digt FND : 0~ 9900점까지의 점수를 표시
- 6개의 LED : A부터 F까지의 게임성적을 표현하며 색깔이 변할 때마다 아이템이 날아오는 속도가 빨라지며 게임의 난이도가 높아진다.
많은 시간을 틈틈히 투자하며 최대한 높은 수준을 만들고자 노력했다. 그러나 아쉬운 부분도 있는 거 같다.
먼저 FND에 대한 부분이 살짝 아쉽다. 맨 처음에는 게임 중간 중간에 점수와 성적이 FND로 표시되도록 했었다. 그러나 이 과정에서 발생한 delay로 인해서 LCD에서 아이템이 날라오는 시간이 느려졌고 이것은 결과적으로 게임이 재미가 없어졌다. 총 3단계의 난이도가 있고 2번의 speed up이 발생하는 게임의 흐름상 이것은 맨 처음 속도가 매우 매우 느려져서 게임 그 자체를 재미 없게 만들어 심각한 문제였다. 게임은 재미있어야 한다. 나는 그렇게 게임이라는 것의 정체성을 정의했고 결론적으로 나는 게임 중간중간의 FND 출력을 없앴다. 대신 게임 정지 기능을 추가해서 게임을 정지시켰을 때 점수와 성적을 확인할 수 있도록 하고, 게임이 끝났을 때 점수와 성적을 확인할 수 있도록 하여 최대한 게임의 재미를 살리면서 게임의 점수 및 성적을 확인할 수 있도록 하였다.
두 번째로는 LCD에서 날아오는 아이템들이 잘 보이지 않는다는 것이다. 게임의 재미를 위해서 아이템이 날아오는 속도를 높였더니 아이템들이 LCD에 표시될 때 필요한 충분한 시간이 충족되지 않아 중간에 표시되다 이동되면서 잔상만 희미하게 보이게 되었다. 이는 게임의 난이도를 높이는 또다른 요소가 되어 어떻게 보면 재미 요소일 수도 있겠지만 확실하게 보였다면 더 좋았을 것 같다. 이는 하드웨어의 한계인듯해서 나중에 LCD가 아닌 다른 방법으로 구현해야 할 듯하다.
이제 구현에 대해서 이야기 하고자 한다. 먼저 내가 공을 들인 부분 중 하나는 아이템 랜덤 생성이다. 랜덤 함수를 사용할 수는 있으나 seed 값을 계속 바꿔주지 않는다면 실행할 때마다 계속 같은 수가 나올 수밖에 없으므로 사용자에게 하여금 랜덤으로 나온다는 느낌을 줄 수 없다. 따라서 seed값을 계속 랜덤하게 바꿔줘야 하는 데 일반적인 경우 time(NULL)을 호출하여 현재 시간을 seed 값으로 자주 사용하지만 ATmega128에서는 자체적으로 현재시간을 가져올 수 있는 방법이 없다. 따라서 다른 방법을 사용해야 하는 데 나는 아래와 같은 코드를 통해서 유사한 랜덤 아이템 생성 기능을 구현했다. 아래의 방법은 엄밀히 말하면 완벽한 랜덤은 아니지만 사용자에게 랜덤으로 나오는 것 같은 느낌을 준다.
int item_seed = 9425;
int item_seed_cnt = 0;
int main()
{
// ... 생략
srand(item_seed);
while(1)
{
// ... 생략
item_seed++;
if (item_seed == 9425) {
item_seed = rand() % 9425;
}
if (item_seed_cnt == 9425) {
item_seed_cnt = 0;
}
if (item_seed_cnt % 6 == 0) {
srand(item_seed);
}
// ... 생략
}
}
LCD와 관련된 모든 기능은 전에 만들어둔 LCD 라이브러리(lcd1602a_h68 - 이 글 참고)를 사용하여 구현했다. 자세한 코드에 대한 설명은 주석으로 설명하는 것이 좋을 것 같아 주석을 달아 놨으니 참고하길 바란다.
/* 커스텀 폰트 번호 */
#define HEART_CHAR 4
#define PLAYER_CHAR 5
/* 전역 변수들 - 아이템(장애물, 하트)과 플레이어에 대한 커스텀 폰트 패턴 값 */
uint8_t obstacle_pattern[5][8] = {
{ 0x00, 0x04, 0x04, 0x06, 0x0E, 0x0F, 0x1F, 0x00 },
{ 0x00, 0x00, 0x04, 0x0C, 0x0E, 0x1F, 0x1F, 0x00 },
{ 0x1F, 0x1F, 0x0E, 0x06, 0x06, 0x04, 0x04, 0x00 },
{ 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x04, 0x04, 0x00 },
};
uint8_t heart_pattern[8] = { 0x00, 0x0A, 0x15, 0x11, 0x11, 0x0A, 0x04, 0x00 };
uint8_t player_pattern[8] = { 0x0E, 0x0E, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x00 };
/* 플레이어 점프 여부 */
// 0일 경우 y 좌표는 1으로 두 번째 줄에 표시
// 1일 경우 y 좌표는 0으로 첫 번째 줄에 표시
uint8_t player_is_up = 0;
/* 현재 LCD에 보여지고 있는 아이템에 대한 정보 */
uint8_t item_type = -1;
uint8_t item_x = LCD_ROWS_MAX - 1;
uint8_t item_y = 1;
/* 게임 점수 */
int game_score = 0;
// 생략
/* LCD 초기화 */
void lcd_set()
{
struct lcd1602a_port lcd_port = {&DDRC, &DDRC, &PORTC, &PORTC};
set_lcd_port(lcd_port);
set_lcd_bit(4);
lcd_init(LCD_ROWS_MAX, LCD_COLS_MAX);
}
/* 아이템(장애물, 하트) 및 플레이어 커스텀 폰트 생성 */
void lcd_item_set()
{
int i;
for (i = 0; i < 4; i++) {
fnds_on(game_score);
lcd_create_char(i, obstacle_pattern[i]);
}
lcd_create_char(i, heart_pattern);
lcd_create_char(i+1, player_pattern);
}
/* 플레이어의 위치를 0, 1으로 세팅하고 LCD에 플레이어를 그림 */
void lcd_player_set()
{
player_is_up = 0;
lcd_player_draw();
}
/* LCD에 플레이어를 그린다.*/
void lcd_player_draw()
{
// 점프 여부에 따라 위치를 다르게 함.
// 점프 여부가 1이면 첫 번째 줄에 플레이어를 표시하고
// 두 번째 줄에 공백을 출력해 비움. 0이면 반대.
lcd_move(0, player_is_up);
lcd_putc(' ');
lcd_move(0, !player_is_up);
lcd_putc(PLAYER_CHAR);
}
/* 플레이어를 점프시킴 - 첫 번째 줄에 플레이어 표시 */
void lcd_player_up()
{
if (player_is_up) return;
player_is_up = 1;
lcd_player_draw();
}
/* 점프한 플레이어를 원래 위치로 - 두 번째 줄에 플레이어 표시 */
void lcd_player_down()
{
if (!player_is_up) return;
player_is_up = 0;
lcd_player_draw();
}
/* 아이템(장애물, 하트) 생성*/
int lcd_create_item()
{
// 이전 아이템을 지우고
lcd_move(item_x, item_y);
lcd_putc(' ');
// 아이템 타입을 랜덤하게 가져온다.
// 아이템의 종류는 장애물 4개 하트 1개이지만
// 하트가 나오는 비중을 높이기 위해 랜덤값 0~7까지 가져오고
// 4이상일 경우 하트로 설정한다.
item_type = rand() % 8;
// 아이템의 좌표는 오른쪽 끝으로 설정
item_x = LCD_ROWS_MAX - 1;
item_y = LCD_COLS_MAX - 1;
// 만약 아이템의 형태가 장애물이고 위에 붙어 있어야 하는 형태면 y좌표를 0으로 고정
if (item_type == 2 || item_type == 3) {
item_y = 0;
}
// 4이상일 경우 아이템 타입을 하트로 설정
// 이는 하트가 나오는 빈도를 높게 하기 위해서임
if (item_type >= 4) {
item_y = rand() % 2;
item_type = 4;
}
// 설정한 좌표로 커서를 이동시키고 아이템 출력
lcd_move(item_x, item_y);
lcd_putc(item_type);
return 0;
}
/* 아이템 이동 */
int lcd_item_shift()
{
if (item_type == -1)
return -1;
// 이전 좌표에 공백을 출력해
// 아이템을 지우고
lcd_move(item_x, item_y);
lcd_putc(' ');
// X좌표를 -1시키고
item_x--;
// 다시 아이템을 좌표에 출력하여
// 아이템이 오른쪽에서 왼쪽으로 이동한 거 같은 효과를 준다.
lcd_move(item_x, item_y);
lcd_putc(item_type);
return 0;
}
/* 점수 표시 */
int lcd_game_score()
{
lcd_putc(game_score / 1000 + '0');
lcd_putc((game_score / 100) % 10 + '0');
lcd_putc((game_score / 10) % 10 + '0');
lcd_putc(game_score % 10 + '0');
return 0;
}
/* 성적 표시 */
int lcd_game_grade(uint8_t game_grade, uint8_t grade_option)
{
switch (game_grade)
{
case 1:
lcd_putc('A');
break;
case 2:
lcd_putc('B');
break;
case 3:
lcd_putc('C');
break;
case 4:
lcd_putc('D');
break;
case 5:
lcd_putc('E');
break;
default:
lcd_putc('F');
}
switch (grade_option)
{
case 1:
lcd_putc('+');
break;
case 2:
lcd_putc('-');
break;
}
return 0;
}
// 생략
그 다음은 FND에 대한 함수들이다. 위에서 설명했다시피 나는 게임 성적(4자리)을 출력하기 위한 4자리 FND 하나와 A~F까지의 성적을 출력하기 위한 큰 한 자리 FND 하나를 사용했다. 각각에 대한 8개 LED 핀은 공유를 하고 있다. 여기서 PORTE가 FND에 대한 LED 출력 포트이고 PORTB가 어떤 FND에 숫자를 출력할 지 결정하는 포트다.
#define BV(n) (1 << n)
// 생략
/* FND 출력 포트 설정 */
void fnds_set()
{
DDRE = 0xFF;
PORTE = 0x00;
DDRB = 0xFF;
PORTB = 0x00;
}
/* 7세그먼트 i번째 FND에 num이라는 숫자를 출력함 */
void fnd_on(unsigned char i, unsigned char num)
{
const unsigned char SegAnode[10] = {
0x03, // 0
0x9F, // 1
0x25, // 2
0x0D, // 3
0x99, // 4
0x49, // 5
0x41, // 6
0x1F, // 7
0x01, // 8
0x09, // 9
};
PORTE = SegAnode[num % 10];
PORTB &= ~(PORTB & 0x1F);
PORTB |= (BV(i) & 0x1F);
}
/* fnd_on()함수를 이용하여 4자리 FND에 4자리 숫자 출력 */
void fnds_on(unsigned int num)
{
if (num > 9999) num = 0;
else if (num < 0) num = 9999;
fnd_on(0, num / 1000);
_delay_ms(2);
fnd_on(1, (num / 100) % 10);
_delay_ms(2);
fnd_on(2, (num / 10) % 10);
_delay_ms(2);
fnd_on(3, num % 10);
_delay_ms(2);
}
/* 한자리 FND에 A~F 성적 출력 */
void fnd_game_grade(uint8_t grade)
{
if (grade <= 0 || grade > 6) return;
uint8_t fnd_grade_alphabet[] = {
0x11, // A
0x01, // B
0x63, // C
0x03, // D
0x61, // E
0x71, // F
};
PORTE = fnd_grade_alphabet[grade - 1];
PORTB &= ~(PORTB & 0x1F);
PORTB |= BV(4);
_delay_ms(2);
}
함수에 대한 설명은 어느 정도 마무리 되가는 듯하다. 이것만 하면 내가 별도로 만든 함수들에 대한 설명은 끝이다. 아래 함수들은 게임 그 자체에 대한 함수들이다. 게임을 초기화하고 정지하고 게임을 끝났을 때 LCD에 결과를 출력하거나 난이도를 올리는 등에 대한 함수들이 아래에 있다.
#define SW_DDR DDRA
#define SW_PIN PINA
#define BV(n) (1 << n)
#define CHECK_SW(n) (~SW_PIN & BV(n)) // 버튼 눌림 여부 판별
/* 전역 변수 - 게임 난이도 */
uint8_t game_difficulty = 6;
/* 버튼 입력을 위한 포트 설정 */
void button_set()
{
DDRA = 0x00;
}
/* 게임 시작 화면을 LCD에 출력 및 게임 초기화 */
void game_start()
{
// LCD에 게임 시작 화면을 출력
lcd_clear();
lcd_move(0,0);
lcd_puts("JUMPING GAME");
lcd_move(0,1);
lcd_puts("-START-");
// 2번 버튼이 눌릴 때까지 대기
while(CHECK_SW(2))_delay_ms(1);
while(!CHECK_SW(2))_delay_ms(1);
while(CHECK_SW(2))_delay_ms(1);
// 2번 버튼이 눌리면 게임을 시작함
lcd_clear();
lcd_player_set();
game_set();
}
/* 게임 종료 및 게임 결과 표시 */
void game_finish(uint8_t is_win)
{
uint8_t game_grade = get_game_grade();
uint8_t grade_option = get_grade_option();
// 게임 결과 표시
lcd_clear();
lcd_puts("GAME FINISH");
lcd_move(0, 1);
if (is_win) {
lcd_puts("WIN - SPECIAL!");
} else {
lcd_puts("LOSE-");
lcd_putc('(');
lcd_game_score();
lcd_putc(',');
lcd_game_grade(game_grade, grade_option);
lcd_putc(')');
}
// 2번 버튼이 눌릴 때까지 대기
while(!CHECK_SW(2))
{
fnds_on(game_score);
if (!is_win) {
fnd_game_grade(game_grade);
}
}
// 게임 재시작
lcd_clear();
lcd_player_set();
game_set();
game_start();
}
/* 게임 초기화 */
void game_set()
{
// 전역 변수 값들을 초기값으로 설정
player_is_up = 0;
item_type = -1;
item_x = LCD_ROWS_MAX - 1;
item_y = 1;
item_seed = rand() % 9425;
item_speed = 50;
game_score = 0;
fnds_set();
// 초기 난이도 설정
game_difficulty_led_set();
game_difficulty_up();
}
/* 게임 정지 및 중간 게임 성적 출력 */
void game_pause()
{
uint8_t game_grade = get_game_grade();
uint8_t grade_option = get_grade_option();
// 게임 정지 시 현재까지의 성적 출력
lcd_clear();
lcd_move(0,0);
lcd_puts("GAME PAUSE");
lcd_move(0,1);
lcd_putc('(');
lcd_game_score();
lcd_putc(',');
lcd_game_grade(game_grade, grade_option);
lcd_putc(')');
while(CHECK_SW(3))_delay_ms(1);
while(!CHECK_SW(3)) {
_delay_ms(1);
// 만약 2번 버튼이 눌리면 게임 초기화
if (CHECK_SW(2)) {
game_start();
return;
}
}
// 만약 3번 버튼이 눌리면 게임 정지 해제
while(CHECK_SW(3))_delay_ms(1);
lcd_clear();
lcd_player_draw();
}
/* 난이도 및 LED 초기 설정 */
void game_difficulty_led_set()
{
DDRD = 0x3F;
PORTD = 0x00;
game_difficulty = 6;
}
/* 난이도를 올리고 난이도에 맞게 LED 출력 */
void game_difficulty_up()
{
game_difficulty--;
if (game_difficulty < 0) game_difficulty = 6;
PORTD |= BV(game_difficulty);
}
/* 게임 점수대를 성적대로 변환함 */
uint8_t get_game_grade()
{
switch(game_score / 1000)
{
case 9:
case 8:
return 1;
case 7:
case 6:
return 2;
case 5:
case 4:
return 3;
case 3:
case 2:
return 4;
case 1:
return 5;
case 0:
return 6;
}
return 6;
}
/* X+ 또는 X- 와 같은 게임 성적 +- 여부를 반환 */
uint8_t get_grade_option()
{
if (
game_score > 9500 ||
(game_score < 8000 && game_score > 7500) ||
(game_score < 6000 && game_score > 5500) ||
(game_score < 4000 && game_score > 3500) ||
(game_score < 2000 && game_score > 1800) ||
(game_score < 1000 && game_score > 800)
) return 1;
else if (
(game_score < 8500 && game_score >= 8000) ||
(game_score < 6500 && game_score >= 6000) ||
(game_score < 4500 && game_score >= 4000) ||
(game_score < 2500 && game_score >= 2000) ||
(game_score < 1200 && game_score >= 1000) ||
(game_score < 200 && game_score >= 0)
) return 2;
return 0;
}
드디어 메인 로직이다. 모든 설정이 끝나고 계속해서 동작하는 while(1) {} 안의 내용을 가져와봤다. 이 역시 주석으로 설명할테니 천천히 살펴보길 바란다. while(1) {} 안의 내용은 아래와 같다.
while (1)
{
// 버튼 클릭 여부 확인
if (CHECK_SW(0)) {
// 0번 버튼이 눌리면 점프
lcd_player_up();
} else if (CHECK_SW(1)) {
// 1번 버튼이 눌리면 원래 위치로
lcd_player_down();
} else if (CHECK_SW(2)) {
// 2번 버튼이 눌리면 리셋
game_start();
} else if (CHECK_SW(3)) {
// 3번 버튼이 눌리면 정지
game_pause();
}
// 아이템 랜덤 생성을 위한 사전 세팅
item_seed++;
if (item_seed == 9425) {
item_seed = rand() % 9425;
}
if (item_seed_cnt == 9425) {
item_seed_cnt = 0;
}
if (item_seed_cnt % 6 == 0) {
srand(item_seed);
}
// 아이템이 왼쪽 끝 지점에 도달하여 다시 처음 지점 자표에 있으면
// 새로운 아이템 생성
if (item_x == LCD_ROWS_MAX - 1) {
lcd_create_item();
item_seed_cnt++;
}
// 아이템을 오른쪽에서 왼쪽으로 한 칸 이동 시킴
lcd_item_shift();
// 만약에 아이템과 플레이어가 만났다면
if (item_x == 0 && item_y == !player_is_up) {
// 아이템 위에 플레이어가 나타나도록 플레이어를 다시 출력
lcd_player_draw();
// 아이템이 만약 하트였다면
if (item_type == 4) {
// 100점 추가
game_score += 100;
// 이전 성적과 다르다면 난이도 업
if (before_grade != get_game_grade()) {
game_difficulty_up();
if (get_game_grade() % 2 == 0 && get_game_grade() < 6) {
item_speed -= 20;
}
}
before_grade = get_game_grade();
// 만약 9900점이 되었다면 게임 종료
if (game_score >= 9900) {
// 게임 승리 화면 출력 후 재시작
game_finish(1);
game_set();
before_grade = get_game_grade();
continue;
}
} else {
// 만난 아이템이 하트가 아니라 장애물이라면
// 게임 패배 화면 출력 후 재시작
game_finish(0);
game_set();
before_grade = get_game_grade();
continue;
}
}
// 최저 속도는 0
if (item_speed < 0) item_speed = 0;
// 아이템 속도만큼 delay시켜 아이템이 이동하는 속도를 조절
for (i = 0; i < item_speed; i++) {
_delay_ms(1);
// delay 중 버튼 체크
if (CHECK_SW(0)) {
lcd_player_up();
} else if (CHECK_SW(1)) {
lcd_player_down();
}
}
// 만약 아이템이 왼쪽 끝지점에 도달했다면
if (item_x == 0) {
if (item_y == player_is_up) {
// 만약 플레이어와 아이템이 만나지 않았다면
// 공백을 아이템 좌표에 출력에 아이템을 지움
lcd_move(item_x, item_y);
lcd_putc(' ');
}
// 아이템의 x좌표를 초기 지점으로 설정
item_x = LCD_ROWS_MAX - 1;
}
}
지금 다시 코드를 살펴보니 조금 아쉽지만 그래도 기간 내 개발 완료해서 다행이라는 생각이 든다. 다음에는 더 깔끔하게 짜보고 싶다. 아래는 전체 코드이다. lcd1602a_h68 라이브러리의 코드의 경우에는 이 글을 참고. 깃허브 Repo는 여기에 있다.
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include "lcd1602a_h68.h"
#define SW_DDR DDRA
#define SW_PIN PINA
#define HEART_CHAR 4
#define PLAYER_CHAR 5
#define BV(n) (1 << n)
#define CHECK_SW(n) (~SW_PIN & BV(n))
void lcd_set();
void lcd_item_set();
void lcd_player_set();
void lcd_player_up();
void lcd_player_down();
void lcd_player_draw();
int lcd_create_item();
int lcd_item_shift();
void fnds_set();
void fnd_on(unsigned char i, unsigned char num);
void fnds_on(unsigned int num);
void fnd_game_grade(uint8_t grade);
void button_set();
void game_start();
void game_finish(uint8_t is_win);
void game_set();
void game_pause();
void game_difficulty_led_set();
void game_difficulty_up();
uint8_t get_grade_option();
uint8_t get_game_grade();
uint8_t obstacle_pattern[5][8] = {
{ 0x00, 0x04, 0x04, 0x06, 0x0E, 0x0F, 0x1F, 0x00 },
{ 0x00, 0x00, 0x04, 0x0C, 0x0E, 0x1F, 0x1F, 0x00 },
{ 0x1F, 0x1F, 0x0E, 0x06, 0x06, 0x04, 0x04, 0x00 },
{ 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x04, 0x04, 0x00 },
};
uint8_t heart_pattern[8] = { 0x00, 0x0A, 0x15, 0x11, 0x11, 0x0A, 0x04, 0x00 };
uint8_t player_pattern[8] = { 0x0E, 0x0E, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x00 };
uint8_t player_is_up = 0;
uint8_t item_type = -1;
uint8_t item_x = LCD_ROWS_MAX - 1;
uint8_t item_y = 1;
int item_seed = 9425;
int item_seed_cnt = 0;
int item_speed = 50;
int game_score = 0;
uint8_t game_difficulty = 6;
int main(void)
{
int i = 0;
int before_grade = get_game_grade();
lcd_set();
fnds_set();
lcd_item_set();
lcd_player_set();
button_set();
game_set();
game_start();
srand(item_seed);
while (1)
{
if (CHECK_SW(0)) {
lcd_player_up();
} else if (CHECK_SW(1)) {
lcd_player_down();
} else if (CHECK_SW(2)) {
game_start();
} else if (CHECK_SW(3)) {
game_pause();
}
item_seed++;
if (item_seed == 9425) {
item_seed = rand() % 9425;
}
if (item_seed_cnt == 9425) {
item_seed_cnt = 0;
}
if (item_seed_cnt % 6 == 0) {
srand(item_seed);
}
if (item_x == LCD_ROWS_MAX - 1) {
lcd_create_item();
item_seed_cnt++;
}
lcd_item_shift();
if (item_x == 0 && item_y == !player_is_up) {
lcd_player_draw();
if (item_type == 4) {
game_score += 100;
if (before_grade != get_game_grade()) {
game_difficulty_up();
if (get_game_grade() % 2 == 0 && get_game_grade() < 6) {
item_speed -= 20;
}
}
before_grade = get_game_grade();
if (game_score >= 9900) {
game_finish(1);
game_set();
before_grade = get_game_grade();
continue;
}
} else {
game_finish(0);
game_set();
before_grade = get_game_grade();
continue;
}
}
if (item_speed < 0) item_speed = 0;
for (i = 0; i < item_speed; i++) {
_delay_ms(1);
if (CHECK_SW(0)) {
lcd_player_up();
} else if (CHECK_SW(1)) {
lcd_player_down();
}
}
if (item_x == 0) {
if (item_y == player_is_up) {
lcd_move(item_x, item_y);
lcd_putc(' ');
}
item_x = LCD_ROWS_MAX - 1;
}
}
}
void lcd_set()
{
struct lcd1602a_port lcd_port = {&DDRC, &DDRC, &PORTC, &PORTC};
set_lcd_port(lcd_port);
set_lcd_bit(4);
lcd_init(LCD_ROWS_MAX, LCD_COLS_MAX);
}
void lcd_item_set()
{
int i;
for (i = 0; i < 4; i++) {
fnds_on(game_score);
lcd_create_char(i, obstacle_pattern[i]);
}
lcd_create_char(i, heart_pattern);
lcd_create_char(i+1, player_pattern);
}
void lcd_player_set()
{
player_is_up = 0;
lcd_player_draw();
}
void lcd_player_draw()
{
lcd_move(0, player_is_up);
lcd_putc(' ');
lcd_move(0, !player_is_up);
lcd_putc(PLAYER_CHAR);
}
void lcd_player_up()
{
if (player_is_up) return;
player_is_up = 1;
lcd_player_draw();
}
void lcd_player_down()
{
if (!player_is_up) return;
player_is_up = 0;
lcd_player_draw();
}
int lcd_create_item()
{
lcd_move(item_x, item_y);
lcd_putc(' ');
item_type = rand() % 8;
item_x = LCD_ROWS_MAX - 1;
item_y = LCD_COLS_MAX - 1;
if (item_type == 2 || item_type == 3) {
item_y = 0;
}
if (item_type >= 4) {
item_y = rand() % 2;
item_type = 4;
}
lcd_move(item_x, item_y);
lcd_putc(item_type);
return 0;
}
int lcd_item_shift()
{
if (item_type == -1)
return -1;
lcd_move(item_x, item_y);
lcd_putc(' ');
item_x--;
lcd_move(item_x, item_y);
lcd_putc(item_type);
return 0;
}
int lcd_game_score()
{
lcd_putc(game_score / 1000 + '0');
lcd_putc((game_score / 100) % 10 + '0');
lcd_putc((game_score / 10) % 10 + '0');
lcd_putc(game_score % 10 + '0');
return 0;
}
int lcd_game_grade(uint8_t game_grade, uint8_t grade_option)
{
switch (game_grade)
{
case 1:
lcd_putc('A');
break;
case 2:
lcd_putc('B');
break;
case 3:
lcd_putc('C');
break;
case 4:
lcd_putc('D');
break;
case 5:
lcd_putc('E');
break;
default:
lcd_putc('F');
}
switch (grade_option)
{
case 1:
lcd_putc('+');
break;
case 2:
lcd_putc('-');
break;
}
return 0;
}
void fnds_set()
{
DDRE = 0xFF;
PORTE = 0x00;
DDRB = 0xFF;
PORTB = 0x00;
}
void fnd_on(unsigned char i, unsigned char num)
{
const unsigned char SegAnode[10] = {
0x03, // 0
0x9F, // 1
0x25, // 2
0x0D, // 3
0x99, // 4
0x49, // 5
0x41, // 6
0x1F, // 7
0x01, // 8
0x09, // 9
};
PORTE = SegAnode[num % 10];
PORTB &= ~(PORTB & 0x1F);
PORTB |= (BV(i) & 0x1F);
}
void fnds_on(unsigned int num)
{
if (num > 9999) num = 0;
else if (num < 0) num = 9999;
fnd_on(0, num / 1000);
_delay_ms(2);
fnd_on(1, (num / 100) % 10);
_delay_ms(2);
fnd_on(2, (num / 10) % 10);
_delay_ms(2);
fnd_on(3, num % 10);
_delay_ms(2);
}
void fnd_game_grade(uint8_t grade)
{
if (grade <= 0 || grade > 6) return;
uint8_t fnd_grade_alphabet[] = {
0x11, // A
0x01, // B
0x63, // C
0x03, // D
0x61, // E
0x71, // F
};
PORTE = fnd_grade_alphabet[grade - 1];
PORTB &= ~(PORTB & 0x1F);
PORTB |= BV(4);
_delay_ms(2);
}
void button_set()
{
DDRA = 0x00;
}
void game_start()
{
lcd_clear();
lcd_move(0,0);
lcd_puts("JUMPING GAME");
lcd_move(0,1);
lcd_puts("-START-");
while(CHECK_SW(2))_delay_ms(1);
while(!CHECK_SW(2))_delay_ms(1);
while(CHECK_SW(2))_delay_ms(1);
lcd_clear();
lcd_player_set();
game_set();
}
void game_finish(uint8_t is_win)
{
uint8_t game_grade = get_game_grade();
uint8_t grade_option = get_grade_option();
lcd_clear();
lcd_puts("GAME FINISH");
lcd_move(0, 1);
if (is_win) {
lcd_puts("WIN - SPECIAL!");
} else {
lcd_puts("LOSE-");
lcd_putc('(');
lcd_game_score();
lcd_putc(',');
lcd_game_grade(game_grade, grade_option);
lcd_putc(')');
}
while(!CHECK_SW(2))
{
fnds_on(game_score);
if (!is_win) {
fnd_game_grade(game_grade);
}
}
lcd_clear();
lcd_player_set();
game_set();
game_start();
}
void game_set()
{
player_is_up = 0;
item_type = -1;
item_x = LCD_ROWS_MAX - 1;
item_y = 1;
item_seed = rand() % 9425;
item_speed = 50;
game_score = 0;
fnds_set();
game_difficulty_led_set();
game_difficulty_up();
}
void game_pause()
{
uint8_t game_grade = get_game_grade();
uint8_t grade_option = get_grade_option();
lcd_clear();
lcd_move(0,0);
lcd_puts("GAME PAUSE");
lcd_move(0,1);
lcd_putc('(');
lcd_game_score();
lcd_putc(',');
lcd_game_grade(game_grade, grade_option);
lcd_putc(')');
while(CHECK_SW(3))_delay_ms(1);
while(!CHECK_SW(3)) {
_delay_ms(1);
if (CHECK_SW(2)) {
game_start();
return;
}
}
while(CHECK_SW(3))_delay_ms(1);
lcd_clear();
lcd_player_draw();
}
void game_difficulty_led_set()
{
DDRD = 0x3F;
PORTD = 0x00;
game_difficulty = 6;
}
void game_difficulty_up()
{
game_difficulty--;
if (game_difficulty < 0) game_difficulty = 6;
PORTD |= BV(game_difficulty);
}
uint8_t get_game_grade()
{
switch(game_score / 1000)
{
case 9:
case 8:
return 1;
case 7:
case 6:
return 2;
case 5:
case 4:
return 3;
case 3:
case 2:
return 4;
case 1:
return 5;
case 0:
return 6;
}
return 6;
}
uint8_t get_grade_option()
{
if (
game_score > 9500 ||
(game_score < 8000 && game_score > 7500) ||
(game_score < 6000 && game_score > 5500) ||
(game_score < 4000 && game_score > 3500) ||
(game_score < 2000 && game_score > 1800) ||
(game_score < 1000 && game_score > 800)
) return 1;
else if (
(game_score < 8500 && game_score >= 8000) ||
(game_score < 6500 && game_score >= 6000) ||
(game_score < 4500 && game_score >= 4000) ||
(game_score < 2500 && game_score >= 2000) ||
(game_score < 1200 && game_score >= 1000) ||
(game_score < 200 && game_score >= 0)
) return 2;
return 0;
}
'AVR' 카테고리의 다른 글
[AVR] ATmega128 RGB LED 제어 (16bit Timer, PyQT) (0) | 2020.11.29 |
---|---|
[AVR] LCD1602A를 ATmega128에서 쉽게 사용하기 with lcd1602a_h68 라이브러리 (+ 커스텀 문자 출력, Display Shift) (1) | 2020.06.27 |
[AVR] ATmega8 브레드보드 테스트 - 스위치 & LED (0) | 2020.06.11 |
[AVR] ATmega8 브레드 보드에서 사용하기 - 퓨즈Fuse 비트를 잘못 변경하면 생기는 일 (0) | 2020.06.10 |
[AVR] LED 실습 6. LED 순차 점등 제어(for 문 이용) (2) | 2020.06.09 |
댓글