본문 바로가기
AVR

[AVR] 점핑 게임 Jumping Game - LCD로 만드는 게임

by hseoy 2020. 7. 30.
반응형

[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;
}

 

반응형

댓글