1. Snake game 이란?
Snake game은 뱀을 조종하여 먹이를 먹어 길이를 늘리는 게임입니다.
게임의 규칙은 간단합니다.
1. 먹이를 먹으면 뱀의 길이가 증가한다.
2. 뱀의 머리가 뱀의 몸이나 벽에 부딪히면 종료된다.
이를 Java GUI를 통해 구현해보았습니다.
2. Java GUI 코드
코드는 아래와 같이 세 가지 파일로 구성됩니다.
1. Main.java
2. GamePanel.java
3. GameFrame.java
1. Main.java
public class SnakeMain {
public static void main(String[] args) {
new GameFrame();
}
}
2. GameFrame.java
import javax.swing.JFrame;
public class GameFrame extends JFrame{
GameFrame() {
this.add(new GamePanel());
this.setTitle("Snake Game");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.pack();
this.setVisible(true);
this.setLocationRelativeTo(null);
}
}
3. GamePanel.java
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import java.util.Random;
public class GamePanel extends JPanel implements ActionListener{
static final int SCREEN_WIDTH = 400;
static final int SCREEN_HEIGHT = 400;
static final int UNIT_SIZE = 20;
static final int GAME_UNIT = (SCREEN_WIDTH*SCREEN_HEIGHT)/UNIT_SIZE;
static final int DELAY = 100; // 0.1초마다 갱신
final int x[] = new int[GAME_UNIT]; // 뱀 형상 x 값
final int y[] =new int[GAME_UNIT]; // 뱀 형상 y 값
int bodyParts = 6; //뱀의 길이
int foodAte; // 먹이를 먹은 횟수
int foodX; //음식의 x값
int foodY; //음식의 r값
char direction = 'R'; //뱀 방향
boolean running = false; //게임 시작 조건 플래그
Timer timer;
Random random;
GamePanel(){
random = new Random();
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.black); //게임 배경색
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
startGame();
}
public void startGame() {
newFood(); //먹이 생성
running = true; // 플래그값, 종료조건시 false로 변경
timer = new Timer(DELAY, this);
timer.start();
}
//게임 보드 구현
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g) {
if (running) { // 게임이 실행중이면
for(int i=0; i<SCREEN_HEIGHT/UNIT_SIZE;i++){
g.drawLine(i*UNIT_SIZE, 0, i*UNIT_SIZE, SCREEN_HEIGHT);
g.drawLine(0, i*UNIT_SIZE, SCREEN_WIDTH, i*UNIT_SIZE);
}
g.setColor(Color.ORANGE); // 먹이색깔
g.fillOval(foodX, foodY, UNIT_SIZE, UNIT_SIZE); // 먹이 원모양으로 좌표만큼
for(int i = 0; i<bodyParts; i++){ // 뱀 형상
if(i == 0){ // 뱀 머리 부분
g.setColor(Color.GREEN);
g.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
}else{ // 나머지 부분
g.setColor(new Color(45, 100, 0));
g.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
}
}
//먹이를 얼마나 먹었는지 score를 표현하기 위함
g.setColor(Color.WHITE);
g.setFont(new Font("Ink FREE", Font.BOLD, 30));
FontMetrics metrics =getFontMetrics(g.getFont());
g.drawString("SCORE: "+foodAte,
(SCREEN_WIDTH-metrics.stringWidth("SCORE: "+foodAte))/2,
g.getFont().getSize());
}
else{
gameOver(g); //게임 오버 창 표시
}
}
public void move() {
for(int i = bodyParts; i>0; i--){
x[i] = x[i-1];
y[i] = y[i-1];
}
switch(direction){
case 'U':
y[0] = y[0] - UNIT_SIZE;
break;
case 'D':
y[0] = y[0] + UNIT_SIZE;
break;
case 'L':
x[0] = x[0] - UNIT_SIZE;
break;
case 'R':
x[0] = x[0] + UNIT_SIZE;
break;
}
}
public void newFood(){
foodX =random.nextInt((int)(SCREEN_WIDTH/UNIT_SIZE))*UNIT_SIZE;
foodY =random.nextInt((int)(SCREEN_HEIGHT/UNIT_SIZE))*UNIT_SIZE;
}
public void checkFood(){
if((x[0]==foodX)&&(y[0]==foodY)){
bodyParts++;
foodAte++;
newFood();
}
}
public void checkCollision() {
//뱀 머리가 몸통과 충돌하는지 체크
for(int i= bodyParts; i>0; i--){
if((x[0] == x[i])&&y[0]==y[i]){
running = false;
}
}
//뱀 머리가 경계선 벗어났는지 체크
if(x[0]<0){
running = false;
}
if(x[0]>SCREEN_WIDTH){
running = false;
}
if(y[0]<0){
running =false;
}
if(y[0]>SCREEN_HEIGHT){
running =false;
}
if (!running) {
timer.stop();
}
}
public void gameOver(Graphics g) {
g.setColor(Color.WHITE);
g.setFont(new Font("Ink FREE", Font.BOLD, 75));
FontMetrics metrics =getFontMetrics(g.getFont());
g.drawString("GAME OVER", (SCREEN_WIDTH-metrics.stringWidth("Game Over"))/2, SCREEN_HEIGHT/2);
}
@Override
public void actionPerformed(ActionEvent e) {
if(running){
move();
checkFood();
checkCollision();
}
repaint();
}
public class MyKeyAdapter extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if(direction!='R'){
direction = 'L';
}
break;
case KeyEvent.VK_RIGHT:
if(direction!='L'){
direction = 'R';
}
break;
case KeyEvent.VK_UP:
if(direction!='D'){
direction = 'U';
}
case KeyEvent.VK_DOWN:
if(direction!='U'){
direction = 'D';
}
default:
break;
}
}
}
}
3. 관련 알고리즘 문제 풀이
백준 3190. 뱀
문제
https://www.acmicpc.net/problem/3190
소스코드
import java.util.*;
public class B3190_뱀 {
static int N, time;
static int direction;
static int head_r, head_c;
static int[][] board;
static int[] dr = {0,1,0,-1};
static int[] dc = {1,0,-1,0};
static Map<Integer, String> map = new HashMap<>();
public static void main(String[] args) {
input();
play();
System.out.println(time+1);
}
static void play(){
Queue<int[]> snakeBody = new LinkedList<>(); //head
head_r = 0;
head_c = 0;
time = 0;
direction = 0; // 0 오 / 1 아 / 2 왼 / 3 위
board[0][0] = 1;
snakeBody.add(new int[]{0,0});
while(true) {
if(map.containsKey(time)){
if(map.get(time).equals("L")) {
direction = direction-1;
if(direction<0) direction = 4+direction;
}
if(map.get(time).equals("D")){
direction = direction+1;
if(direction>=4) direction = direction-4;
}
}
head_r += dr[direction];
head_c += dc[direction];
//다음 머리
if(head_r<0||head_c<0||head_r>=N||head_c>=N) break;
if(board[head_r][head_c]==1) break;
if(board[head_r][head_c]==0){
board[head_r][head_c] = 1;
snakeBody.add(new int[]{head_r,head_c});
int[] tail = snakeBody.poll();
board[tail[0]][tail[1]] = 0;
}
if(board[head_r][head_c]==2){
board[head_r][head_c] = 1;
snakeBody.add(new int[]{head_r,head_c});
}
time++;
}
}
static void input(){
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
board = new int[N][N];
int K = sc.nextInt();
for(int i = 0; i<K;i++){
int r = sc.nextInt()-1;
int c = sc.nextInt()-1;
board[r][c] = 2;
}
int L = sc.nextInt();
for(int i =0;i<L;i++){
int sec = sc.nextInt();
String C = sc.next(); //L: 왼쪽 / D: 오른쪽
map.put(sec,C);
}
}
}