일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 알고리즘
- c#
- c언어
- 언제나 휴일
- 유튜브 동영상 강의
- 프로젝트
- 무료 동영상 강의
- 산책하기 좋은 곳
- 실습으로 다지는 c#
- 소켓 통신
- 안드로이드 앱 개발
- 졸업 작품 소재
- 언제나휴일
- Windows Forms
- 클래스 다이어그램
- 캡슐화
- 충남 천안
- 파이썬
- 네트워크 프로그래밍
- 실습
- 표준 입출력
- 추천
- 강의
- 동영상
- 표준 라이브러리 함수
- C++
- 소스 코드
- 동영상 강의
- 졸업 작품
- 원격 제어 프로그램
- Today
- Total
프로그래밍 언어 및 기술 [언제나휴일]
[C#] 테트리스 만들기 – Part 4(Final). 꽉 찬 줄 지우기, Ending 본문
프로젝트 다운로드 -벽돌 모양에 따라 색상 변경 기능 추가
1. 유튜브 동영상 강의
안녕하세요. 언제나 휴일에 언휴예요.
이번 강의는 테트리스 프로젝트의 마지막 부분입니다.
이번 강의에서는 꽉 찬 라인을 지우는 기능과 종료 조건을 체크하는 부분을 구현합니다.
2. 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;
}
}
}
}
}
3. 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();
}
}
}
4. 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();
}
}
}
'프로젝트 > C# 테트리스' 카테고리의 다른 글
[C#] 테트리스 만들기 – Part 3. 벽돌 쌓기 (0) | 2024.01.05 |
---|---|
[C#] 테트리스 만들기 – Part 2. 테트리스 도형 정의하기, 도형 회전하기 (0) | 2024.01.05 |
[C#] 테트리스 만들기 – Part 1. 키보드로 도형 제어하기, 타이머로 도형 아래로 이동 (0) | 2024.01.05 |