프로젝트 다운로드 -벽돌 모양에 따라 색상 변경 기능 추가
안녕하세요. 언제나 휴일에 언휴예요.
이번 강의는 테트리스 프로젝트의 마지막 부분입니다.
이번 강의에서는 꽉 찬 라인을 지우는 기능과 종료 조건을 체크하는 부분을 구현합니다.
Board 형식 수정
Store 메서드에 라인이 꽉 찼는지 확인하는 메서드를 호출합니다. 이 때 주의할 점은 라인 체크는 아래부터 한다는 것입니다. 벽돌이 4X4공간에 배치하므로 벽돌이 있는 좌표에서 3칸 더 있다는 것을 고려하세요.
internal void Store(int bn, int turn, int x, int y) { for (int xx = 0; xx < 4; xx++) { for(int yy = 0; yy<4;yy++) { if(((x+xx)>=0)&&(x+xx<GameRule.BX)&&(y+yy>=0)&&(y+yy<GameRule.BY)) { board[x + xx, y + yy] += BlockValue.bvals[bn, turn, xx, yy]; } } } CheckLines(y+3); }
벽돌을 4X4 공간에 정의하였지만 일부는 마지막 라인이 모두 비어있을 때도 있습니다. 따라서 라인을 체크할 때 보드 공간을 벗어나는 라인은 체크하지 않습니다. 그리고 꽉 찬 라인은 라인을 지워줍니다. 주의할 점은 라인을 지우면 윗 부분이 한 칸씩 아래로 채워지므로 다시 같은 라인을 점검해야 합니다. 이 부분을 주의해서 구현하세요.
여기에서는 점검할 라인은 입력 인자로 받은 y값과 4개의 라인을 점검하기 위한 Loop 변수 yy를 뺀 값(y-yy)입니다. Loop변수 yy 값이 1 증가하는 대신 점검할 라인 번호도 같이 1 증가시키고 있어요. 결국 (y-yy)는 같은 값으로 같은 라인을 점검할 수 있어요.
private void CheckLines(int y) { int yy = 0; for(yy=0;(yy<4);yy++) { if(y-yy<GameRule.BY) { if (CheckLine(y - yy)) { ClearLine(y - yy); y++; } } } }
한 줄이 꽉 찼는지 확인하는 방법은 해당 라인에 모든 x좌표에 돌이 있을 때예요. 그리고 꽉 찼을 때 지우는 방법은 한 칸 위의 값으로 변경하는 것을 반복하는 거예요.
private void ClearLine(int y) { for (; y > 0; y--) { for (int xx = 0; xx < GameRule.BX; xx++) { board[xx, y] = board[xx, y - 1]; } } } private bool CheckLine(int y) { for(int xx= 0; xx<GameRule.BX;xx++) { if(board[xx,y]==0) { return false; } } return true; }
게임을 계속할 수 있게 보드를 초기화하는 메서드도 제공합시다.
internal void ClearBoard() { for(int xx=0;xx<GameRule.BX;xx++) { for(int yy=0;yy<GameRule.BY;yy++) { board[xx, yy] = 0; } } }
다음은 Board.cs 파일의 소스 코드입니다.
namespace 도형_이동 { class Board { internal static Board GameBoard { get; private set; } static Board() { GameBoard = new Board(); } Board() { } int[,] board = new int[GameRule.BX, GameRule.BY]; internal int this[int x,int y] { get { return board[x, y]; } } internal bool MoveEnable(int bn,int tn,int x,int y) { for(int xx=0;xx<4;xx++) { for(int yy=0;yy<4;yy++) { if(BlockValue.bvals[bn,tn,xx,yy]!=0) { if(board[x+xx,y+yy]!=0) { return false; } } } } return true; } internal void Store(int bn, int turn, int x, int y) { for (int xx = 0; xx < 4; xx++) { for(int yy = 0; yy<4;yy++) { if(((x+xx)>=0)&&(x+xx<GameRule.BX)&&(y+yy>=0)&&(y+yy<GameRule.BY)) { board[x + xx, y + yy] += BlockValue.bvals[bn, turn, xx, yy]; } } } CheckLines(y+3); } private void CheckLines(int y) { int yy = 0; for(yy=0;(yy<4);yy++) { if(y-yy<GameRule.BY) { if (CheckLine(y - yy)) { ClearLine(y - yy); y++; } } } } private void ClearLine(int y) { for (; y > 0; y--) { for (int xx = 0; xx < GameRule.BX; xx++) { board[xx, y] = board[xx, y - 1]; } } } private bool CheckLine(int y) { for(int xx= 0; xx<GameRule.BX;xx++) { if(board[xx,y]==0) { return false; } } return true; } internal void ClearBoard() { for(int xx=0;xx<GameRule.BX;xx++) { for(int yy=0;yy<GameRule.BY;yy++) { board[xx, yy] = 0; } } } } }
Game 형식 수정
Next 메서드에서 Reset 상태의 벽돌이 시작 지점에 배치할 수 있는지 체크합니다. 이를 통해 게임의 종료 여부를 결정합니다. 메서드의 반환 형식도 수정해야 합니다.
internal bool Next() { now.Reset(); return gboard.MoveEnable(now.BlockNum, Turn, now.X, now.Y); }
게임을 재시작하는 메서드를 제공합시다.
internal void ReStart() { gboard.ClearBoard(); }
다음은 Game.cs 소스 파일의 코드 내용입니다.
using System.Drawing; namespace 도형_이동 { class Game { Diagram now; Board gboard = Board.GameBoard; internal Point NowPosition { get { return new Point(now.X, now.Y); } } internal int BlockNum { get { return now.BlockNum; } } internal int Turn { get { return now.Turn; } } internal static Game Singleton { get; private set; } internal int this[int x,int y] { get { return gboard[x, y]; } } static Game() { Singleton = new Game(); } Game() { now = new Diagram(); } internal bool MoveLeft() { for(int xx=0;xx<4;xx++) { for(int yy=0;yy<4;yy++) { if(BlockValue.bvals[now.BlockNum,Turn, xx,yy]!=0) { if (now.X + xx <= 0) { return false; } } } } if (gboard.MoveEnable(now.BlockNum, Turn, now.X - 1, now.Y)) { now.MoveLeft(); return true; } return false; } internal bool MoveRight() { for (int xx = 0; xx < 4; xx++) { for (int yy = 0; yy < 4; yy++) { if (BlockValue.bvals[now.BlockNum, Turn, xx, yy] != 0) { if ((now.X + xx+1) >= GameRule.BX) { return false; } } } } if (gboard.MoveEnable(now.BlockNum, Turn, now.X + 1, now.Y)) { now.MoveRight(); return true; } return false; } internal bool MoveDown() { for (int xx = 0; xx < 4; xx++) { for (int yy = 0; yy < 4; yy++) { if (BlockValue.bvals[now.BlockNum, Turn, xx, yy] != 0) { if ((now.Y + yy + 1) >=GameRule.BY) { gboard.Store(now.BlockNum, Turn, now.X, now.Y); return false; } } } } if (gboard.MoveEnable(now.BlockNum, Turn, now.X, now.Y + 1)) { now.MoveDown(); return true; } gboard.Store(now.BlockNum, Turn, now.X, now.Y); return false; } internal bool MoveTurn() { for (int xx = 0; xx < 4; xx++) { for (int yy = 0; yy < 4; yy++) { if (BlockValue.bvals[now.BlockNum, (Turn+1)%4, xx, yy] != 0) { if (((now.X + xx) < 0)|| ((now.X + xx) >= GameRule.BX)||((now.Y+yy)>=GameRule.BY)) { return false; } } } } if (gboard.MoveEnable(now.BlockNum, (Turn + 1) % 4, now.X, now.Y)) { now.MoveTurn(); return true; } return false; } internal bool Next() { now.Reset(); return gboard.MoveEnable(now.BlockNum, Turn, now.X, now.Y); } internal void ReStart() { gboard.ClearBoard(); } } }
Form1 형식 수정
MoveDown 메서드에서 아래로 이동하지 못할 때 게임을 끝내야 하는지 체크하는 것으로 수정하세요.
private void MoveDown() { if(game.MoveDown()) { Region rg = MakeRegion(0, -1); Invalidate(rg); } else { EndingCheck(); } }
EndingCheck 메서드에서는 다음 도형이 가능한지 확인합니다. 만약 가능하지 않다면 종료 조건입니다. 타이머를 멈춘 후에 사용자에게 계속할 것인지 확인하고 이에 따라 다시 시작하거나 창을 닫습니다.
private void EndingCheck() { if (game.Next()) { Invalidate(); } else { timer_down.Enabled = false; if (DialogResult.Yes == MessageBox.Show("계속 하실건가요?", "계속 진행 확인 창", MessageBoxButtons.YesNo)) { game.ReStart(); timer_down.Enabled = true; Invalidate(); } else { this.Close(); } } }
Form1.cs 파일의 소스 코드입니다.
using System; using System.Drawing; using System.Windows.Forms; namespace 도형_이동 { public partial class Form1 : Form { Game game; int bx; int by; int bwidth; int bheight; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { game = Game.Singleton; bx = GameRule.BX; by = GameRule.BY; bwidth = GameRule.B_WIDTH; bheight = GameRule.B_HEIGHT; this.SetClientSizeCore(GameRule.BX * GameRule.B_WIDTH, GameRule.BY * GameRule.B_HEIGHT); } private void Form1_Paint(object sender, PaintEventArgs e) { DoubleBuffered = true; DrawGraduation(e.Graphics); DrawDiagram(e.Graphics); DrawBoard(e.Graphics); } private void DrawBoard(Graphics graphics) { for(int xx=0;xx<bx;xx++) { for(int yy=0; yy<by;yy++) { if(game[xx,yy]!=0) { Rectangle now_rt = new Rectangle(xx * bwidth + 2, yy * bheight + 2, bwidth - 4, bheight - 4); graphics.DrawRectangle(Pens.Green, now_rt); graphics.FillRectangle(Brushes.Red, now_rt); } } } } private void DrawDiagram(Graphics graphics) { Pen dpen = new Pen(Color.Red, 4); Point now = game.NowPosition; int bn = game.BlockNum; int tn = game.Turn; for(int xx=0;xx<4;xx++) { for(int yy=0;yy<4;yy++) { if(BlockValue.bvals[bn,tn,xx,yy]!=0) { Rectangle now_rt = new Rectangle((now.X+xx) * bwidth + 2, (now.Y+yy) * bheight + 2, bwidth - 4, bheight - 4); graphics.DrawRectangle(dpen, now_rt); } } } } private void DrawGraduation(Graphics graphics) { DrawHorizons(graphics); DrawVerticals(graphics); } private void DrawVerticals(Graphics graphics) { Point st = new Point(); Point et = new Point(); for (int cx = 0; cx < bx; cx++) { st.X = cx * bwidth; st.Y = 0; et.X = st.X; et.Y = by * bheight; graphics.DrawLine(Pens.Purple, st, et); } } private void DrawHorizons(Graphics graphics) { Point st = new Point(); Point et = new Point(); for (int cy = 0; cy < by; cy++) { st.X = 0; st.Y = cy * bheight; et.X = bx * bwidth; et.Y = cy * bheight; graphics.DrawLine(Pens.Green, st, et); } } private void Form1_KeyDown(object sender, KeyEventArgs e) { switch(e.KeyCode) { case Keys.Right: MoveRight(); return; case Keys.Left: MoveLeft(); return; case Keys.Space: MoveSSDown();return; case Keys.Up: MoveTurn();return; case Keys.Down: MoveDown(); return; } } private void MoveSSDown() { while (game.MoveDown()) { Region rg = MakeRegion(0, -1); Invalidate(rg); } EndingCheck(); } private void MoveTurn() { if(game.MoveTurn()) { Region rg = MakeRegion(); Invalidate(rg); } } private void MoveDown() { if(game.MoveDown()) { Region rg = MakeRegion(0, -1); Invalidate(rg); } else { EndingCheck(); } } private void EndingCheck() { if (game.Next()) { Invalidate(); } else { timer_down.Enabled = false; if (DialogResult.Yes == MessageBox.Show("계속 하실건가요?", "계속 진행 확인 창", MessageBoxButtons.YesNo)) { game.ReStart(); timer_down.Enabled = true; Invalidate(); } else { this.Close(); } } } private void MoveLeft() { if (game.MoveLeft()) { Region rg = MakeRegion(1, 0); Invalidate(rg); } } private Region MakeRegion(int cx, int cy) { Point now = game.NowPosition; int bn = game.BlockNum; int tn = game.Turn; Region region = new Region(); for (int xx = 0; xx < 4; xx++) { for (int yy = 0; yy < 4; yy++) { if (BlockValue.bvals[bn, tn, xx, yy] != 0) { Rectangle rect1 = new Rectangle((now.X + xx) * bwidth + 2, (now.Y + yy) * bheight + 2, bwidth - 4, bheight - 4); Rectangle rect2 = new Rectangle((now.X + cx+xx) * bwidth, (now.Y + cy+yy) * bheight, bwidth, bheight); Region rg1 = new Region(rect1); Region rg2 = new Region(rect2); region.Union(rg1); region.Union(rg2); } } } return region; } private Region MakeRegion() { Point now = game.NowPosition; int bn = game.BlockNum; int tn = game.Turn; int oldtn = (tn + 3) % 4; Region region = new Region(); for (int xx = 0; xx < 4; xx++) { for (int yy = 0; yy < 4; yy++) { if (BlockValue.bvals[bn, tn, xx, yy] != 0) { Rectangle rect1 = new Rectangle((now.X + xx) * bwidth + 2, (now.Y + yy) * bheight + 2, bwidth - 4, bheight - 4); Region rg1 = new Region(rect1); region.Union(rg1); } if (BlockValue.bvals[bn, oldtn, xx, yy] != 0) { Rectangle rect1 = new Rectangle((now.X + xx) * bwidth + 2, (now.Y + yy) * bheight + 2, bwidth - 4, bheight - 4); Region rg1 = new Region(rect1); region.Union(rg1); } } } return region; } private void MoveRight() { if (game.MoveRight()) { Region rg = MakeRegion(-1, 0); Invalidate(rg); } } private void timer_down_Tick(object sender, EventArgs e) { MoveDown(); } } }