Position class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tetris
{
//임에서 특정 위치를 나타내는 클래스
public class Position
{
// 행 위치를 나타내는 속성 (y 좌표와 유사)
public int Row { get; set; }
// 열 위치를 나타내는 속성 (x 좌표와 유사)
public int Column { get; set; }
// 행과 열을 매개변수로 받아 Position 객체를 초기화하는 생성자
public Position(int row, int column)
{
Row = row; // 입력된 행 값을 Row 속성에 할당
Column = column; // 입력된 열 값을 Column 속성에 할당
}
}
}
- 이 Position 클래스는 테트리스 게임에서 각 블록이나 타일의 위치를 표현하기 위해 사용됩니다.
- Row와 Column 속성은 각각 위치의 세로(y 좌표)와 가로(x 좌표)를 나타냅니다.
- 생성자는 Position 객체를 생성할 때 초기 행과 열 값을 설정하는 역할을 합니다.
- 이 클래스는 간단하게 블록이 게임 보드에서 위치할 좌표를 추적하는 데 사용됩니다.
Block class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tetris
{
// 테트리스의 블록을 나타내는 추상 클래스
public abstract class Block
{
// 블록의 회전 상태를 나타내는 2차원 배열, 각 상태는 Position 배열로 구성
protected abstract Position[][] Tiles { get; }
// 블록의 초기 위치를 나타내는 추상 속성
protected abstract Position StartOffset { get; }
// 블록의 고유 식별자
public abstract int Id { get; }
// 현재 회전 상태를 나타내는 변수
private int rotationState;
// 블록의 현재 위치를 나타내는 오프셋
private Position offset;
// 생성자: 블록의 초기 위치를 설정
public Block()
{
offset = new Position(StartOffset.Row, StartOffset.Column);
}
// 블록의 현재 타일 위치들을 반환
public IEnumerable<Position> TilePositions()
{
foreach (Position p in Tiles[rotationState]) // 현재 회전 상태에 따라 타일을 가져옴
{
yield return new Position(p.Row + offset.Row, p.Column + offset.Column); // 오프셋을 적용하여 반환
}
}
// 시계 방향으로 블록을 회전
public void RotateCW()
{
rotationState = (rotationState + 1) % Tiles.Length; // 회전 상태를 순환
}
// 반시계 방향으로 블록을 회전
public void RotateCCW()
{
rotationState = (rotationState - 1 + Tiles.Length) % Tiles.Length; // 음수가 되지 않도록 보정
}
// 반시계 방향으로 블록을 회전 (대체 구현)
public void TotateCCW()
{
if (rotationState == 0)
{
rotationState = Tiles.Length - 1; // 현재 상태가 0이면 마지막 상태로 설정
}
else
{
rotationState--; // 그렇지 않으면 회전 상태 감소
}
}
// 블록을 지정된 행과 열만큼 이동
public void Move(int rows, int columns)
{
offset.Row += rows;
offset.Column += columns;
}
// 블록의 회전 상태와 위치를 초기 상태로 재설정
public void Reset()
{
rotationState = 0; // 회전 상태 초기화
offset.Row = StartOffset.Row; // 행 오프셋 초기화
offset.Column = StartOffset.Column; // 열 오프셋 초기화
}
}
}
- 이 Block 클래스는 테트리스의 각 블록을 추상적으로 정의하는 기반 클래스입니다.
- Tiles: 블록의 회전 상태를 나타내는 배열로, 각 회전 상태는 Position 배열로 표현됩니다.
- StartOffset: 블록의 초기 위치를 정의합니다.
- TilePositions(): 현재 블록의 모든 타일 위치를 계산하여 반환합니다.
- RotateCW() 및 RotateCCW(): 블록을 시계 또는 반시계 방향으로 회전시킵니다.
- Move(): 블록을 특정 행과 열만큼 이동합니다.
- Reset(): 블록을 초기 상태로 되돌립니다.
이 클래스는 테트리스 게임에서 블록의 회전, 이동 및 초기화 기능을 처리하는 데 필요한 메서드와 속성을 정의합니다.
ZBlock class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tetris
{
// 테트리스 게임의 'Z' 블록을 나타내는 클래스
public class ZBlock : Block
{
// 'Z' 블록의 다양한 회전 상태를 정의하는 2차원 Position 배열
private readonly Position[][] tiles = new Position[][]
{
// 기본 상태
new Position[] { new(0, 0), new(0, 1), new(1, 1), new(1, 2) },
// 시계 방향으로 90도 회전한 상태
new Position[] { new(0, 2), new(1, 1), new(1, 2), new(2, 1) },
// 180도 회전한 상태
new Position[] { new(1, 0), new(1, 1), new(2, 1), new(2, 2) },
// 시계 방향으로 270도 회전한 상태
new Position[] { new(0, 1), new(1, 0), new(1, 1), new(2, 0) }
};
// 블록의 식별자. 테트리스 게임에서 'Z' 블록을 구분하는 값으로 사용
public override int Id => 7;
// 블록의 초기 위치를 나타냄. 게임 보드의 위쪽 중앙에서 시작하도록 설정
protected override Position StartOffset => new Position(0, 3);
// 'Tiles' 속성은 'tiles' 배열을 반환하여 블록의 회전 상태를 제공
protected override Position[][] Tiles => tiles;
}
}
- tiles: Position[][] 배열로, Z 블록의 네 가지 회전 상태를 정의합니다. 각 Position[] 배열은 Z 블록이 각 회전 상태에서 차지하는 상대적 좌표를 지정합니다.
- Id 속성: Z 블록을 다른 블록과 구분하기 위해 고유 식별자로 7을 사용합니다.
- StartOffset 속성: 블록이 게임 보드에서 처음 나타나는 위치를 설정합니다. (0, 3)은 보드의 위쪽 중간에서 시작하도록 합니다.
Tiles 속성: Block 클래스에서 사용하기 위해 tiles 배열을 반환합니다. 이 배열은 블록의 현재 회전 상태에 따라 블록의 타일 위치를 제공합니다.
이하 다른 블록의 클래스도 좌표를 제외한 나머지 코드는 동일하므로 생략합니다.
BlockQueue class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tetris
{
// 블록의 큐를 관리하는 클래스. 다음 블록을 미리 선택하고 업데이트하는 역할을 함
class BlockQueue
{
// 게임에 사용할 블록 배열. 다양한 블록의 인스턴스를 포함
private readonly Block[] blocks = new Block[]
{
new IBlock(),
new JBlock(),
new LBlock(),
new OBlock(),
new SBlock(),
new TBlock(),
new ZBlock(),
};
// 블록을 무작위로 선택하기 위한 Random 객체
private readonly Random random = new Random();
// 큐에서 사용할 다음 블록을 나타내는 속성
public Block NextBlock { get; private set; }
// 생성자: 처음에 랜덤으로 블록을 선택하여 초기화
public BlockQueue()
{
NextBlock = RandomBlock(); // 초기 블록 설정
}
// 블록 배열에서 랜덤으로 하나의 블록을 반환하는 메서드
private Block RandomBlock()
{
return blocks[random.Next(blocks.Length)]; // 블록 배열의 인덱스를 무작위로 선택
}
// 현재 블록을 반환하고 다음 블록을 업데이트하는 메서드
public Block GetAndUpdate()
{
Block block = NextBlock; // 현재 블록 저장
// 다음 블록이 현재 블록과 다를 때까지 무작위로 선택
do
{
NextBlock = RandomBlock();
}
while (block.Id == NextBlock.Id); // 같은 블록이 연속으로 나오지 않도록 방지
return block; // 현재 블록 반환
}
}
}
- blocks 배열: 다양한 테트리스 블록 인스턴스를 포함하며, 무작위로 선택됩니다.
- random 객체: 무작위 블록을 선택하기 위해 사용됩니다.
- NextBlock 속성: 다음에 사용할 블록을 나타내며, GetAndUpdate() 메서드가 호출될 때마다 갱신됩니다.
- RandomBlock() 메서드: blocks 배열에서 무작위로 하나의 블록을 반환합니다.
- GetAndUpdate() 메서드:
- 현재 NextBlock을 반환한 후, 새로운 블록을 무작위로 선택하여 NextBlock에 할당합니다.
- 연속으로 동일한 블록이 나오지 않도록 조건(block.Id == NextBlock.Id)을 사용해 반복적으로 선택합니다.
이 클래스는 테트리스 게임에서 다음 블록을 예측하고 미리 준비하는 역할을 담당합니다.
GameState class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tetris
{
// 테트리스 게임의 상태를 관리하는 클래스
class GameState
{
// 현재 활성 블록
private Block currentBlock;
// 현재 활성 블록을 가져오거나 설정하는 속성. 블록이 설정될 때 초기화 및 이동 처리
public Block CurrentBlock
{
get => currentBlock;
private set
{
currentBlock = value;
currentBlock.Reset(); // 블록 상태 초기화
// 초기 위치에서 블록을 두 번 아래로 이동하여 보드에 맞추려 시도
for (int i = 0; i < 2; i++)
{
currentBlock.Move(1, 0);
if (!BlockFits())
{
currentBlock.Move(-1, 0); // 블록이 맞지 않으면 원위치
}
}
}
}
// 게임 보드 상태를 나타내는 그리드
public GameGrid GameGrid { get; }
// 블록 큐로 다음 블록을 관리
public BlockQueue BlockQueue { get; }
// 게임 종료 상태를 나타내는 속성
public bool GameOver { get; private set; }
// 현재 게임 점수
public int Score { get; private set; }
// 홀드된 블록
public Block HeldBlock { get; private set; }
// 블록 홀드 가능 여부를 나타내는 속성
public bool CanHold { get; private set; }
// 생성자: 초기 상태를 설정
public GameState()
{
GameGrid = new GameGrid(22, 10); // 22행 x 10열 그리드 생성
BlockQueue = new BlockQueue(); // 블록 큐 초기화
CurrentBlock = BlockQueue.GetAndUpdate(); // 첫 블록 설정
}
// 현재 블록이 그리드에 맞는지 검사하는 메서드
private bool BlockFits()
{
foreach (Position p in CurrentBlock.TilePositions())
{
if (!GameGrid.IsEmpty(p.Row, p.Column))
{
return false; // 블록이 비어 있지 않은 공간에 위치하면 false 반환
}
}
return true; // 모든 위치가 비어 있으면 true 반환
}
// 블록을 홀드하는 기능
public void HoldBlock()
{
if (!CanHold)
{
return; // 홀드 불가능하면 메서드 종료
}
if (HeldBlock == null)
{
HeldBlock = currentBlock; // 현재 블록을 홀드
CurrentBlock = BlockQueue.GetAndUpdate(); // 새로운 블록 가져옴
}
else
{
Block tmp = CurrentBlock;
CurrentBlock = HeldBlock; // 기존 홀드 블록과 현재 블록을 교체
HeldBlock = tmp;
}
CanHold = false; // 홀드 후에는 재홀드 불가
}
// 블록을 시계 방향으로 회전시키는 메서드
public void RotateBlockCW()
{
currentBlock.RotateCW();
if (!BlockFits())
{
CurrentBlock.RotateCCW(); // 맞지 않으면 회전을 되돌림
}
}
// 블록을 반시계 방향으로 회전시키는 메서드
public void RotateBlockCCW()
{
CurrentBlock.RotateCCW();
if (!BlockFits())
{
CurrentBlock.RotateCW(); // 맞지 않으면 회전을 되돌림
}
}
// 블록을 왼쪽으로 이동시키는 메서드
public void MoveBlockLeft()
{
CurrentBlock.Move(0, -1);
if (!BlockFits())
{
CurrentBlock.Move(0, 1); // 맞지 않으면 원위치
}
}
// 블록을 오른쪽으로 이동시키는 메서드
public void MoveBlockRight()
{
CurrentBlock.Move(0, 1);
if (!BlockFits())
{
CurrentBlock.Move(0, -1); // 맞지 않으면 원위치
}
}
// 게임 오버 상태를 검사하는 메서드
private bool IsGameOver()
{
return !(GameGrid.IsRowEmpty(0) && GameGrid.IsRowEmpty(1)); // 상단 두 줄이 비어있지 않으면 게임 오버
}
// 블록을 현재 위치에 고정시키고 점수를 갱신하는 메서드
private void PlaceBlock()
{
foreach (Position p in CurrentBlock.TilePositions())
{
GameGrid[p.Row, p.Column] = CurrentBlock.Id; // 블록 위치를 보드에 고정
}
Score += GameGrid.ClearFullRows(); // 꽉 찬 줄을 지우고 점수 갱신
if (IsGameOver())
{
GameOver = true; // 게임 오버 상태 설정
}
else
{
CurrentBlock = BlockQueue.GetAndUpdate(); // 새로운 블록 설정
CanHold = true; // 블록 홀드 가능 상태로 설정
}
}
// 블록을 아래로 한 칸 이동시키는 메서드
public void MoveBlockDown()
{
CurrentBlock.Move(1, 0);
if (!BlockFits())
{
CurrentBlock.Move(-1, 0); // 맞지 않으면 원위치
PlaceBlock(); // 블록 고정
}
}
// 특정 위치에서 블록이 떨어질 수 있는 거리 계산
private int TileDropDistance(Position p)
{
int drop = 0;
while (GameGrid.IsEmpty(p.Row + drop + 1, p.Column))
{
drop++; // 떨어질 수 있는 만큼 거리 증가
}
return drop; // 최대 떨어질 거리 반환
}
// 현재 블록이 떨어질 수 있는 최소 거리 계산
public int BlockDropDistance()
{
int drop = GameGrid.Rows; // 초기값을 최대 행 수로 설정
foreach (Position p in CurrentBlock.TilePositions())
{
drop = System.Math.Min(drop, TileDropDistance(p)); // 최소 거리 계산
}
return drop; // 최소 거리 반환
}
// 블록을 즉시 바닥으로 떨어뜨리고 고정하는 메서드
public void DropBlock()
{
CurrentBlock.Move(BlockDropDistance(), 0); // 블록을 최대로 떨어뜨림
PlaceBlock(); // 블록 고정
}
}
}
- CurrentBlock 속성: 현재 활성화된 블록을 나타내며, 새로운 블록 설정 시 초기화 및 위치 조정을 수행합니다.
- BlockFits(): 현재 블록이 보드에 맞는지 검사합니다.
- HoldBlock(): 블록을 홀드하고, 필요한 경우 홀드된 블록과 현재 블록을 교체합니다.
- PlaceBlock(): 현재 블록을 보드에 고정하고 줄을 지우며 점수를 갱신합니다.
- MoveBlockDown(), MoveBlockLeft(), MoveBlockRight(): 블록을 해당 방향으로 이동시키고, 맞지 않으면 원위치로 되돌립니다.
- RotateBlockCW(), RotateBlockCCW(): 블록을 회전시키고, 보드에 맞지 않으면 회전을 되돌립니다.
- IsGameOver(): 게임 오버 조건을 검사합니다.
- BlockDropDistance() 및 TileDropDistance(): 블록이 떨어질 수 있는 최대 거리를 계산합니다.
- DropBlock(): 블록을 즉시 바닥으로 떨어뜨리고 고정합니다.
Mainwindow xaml
<Window x:Class="Tetris.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Tetris"
mc:Ignorable="d"
Title="Tetris" Height="600" Width="800"
MinWidth="600" MinHeight="600"
Foreground="white"
FontFamily="Segoe UI Light" FontSize="28"
KeyDown="Window_KeyDown">
<!-- Window의 기본 설정: 클래스, 네임스페이스, 크기, 스타일 및 키 입력 이벤트 설정 -->
<Grid>
<!-- 전체 레이아웃을 위한 Grid 정의 -->
<Grid.RowDefinitions>
<RowDefinition Height="auto"/> <!-- 첫 번째 행은 콘텐츠에 맞춤 -->
<RowDefinition Height="*"/> <!-- 두 번째 행은 남은 공간 전체 차지 -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <!-- 첫 번째 열: 남은 공간 전체 -->
<ColumnDefinition Width="auto"/> <!-- 두 번째 열: 콘텐츠에 맞춤 -->
<ColumnDefinition Width="*"/> <!-- 세 번째 열: 남은 공간 전체 -->
</Grid.ColumnDefinitions>
<Grid.Background>
<ImageBrush ImageSource="Assets/Background.png"/>
<!-- 배경 이미지 설정 -->
</Grid.Background>
<!-- 게임 캔버스를 포함하는 Viewbox -->
<Viewbox Grid.Row="1" Grid.Column="1" Margin="0,0,0,20">
<Canvas x:Name="GameCanvas"
Background="#101010" <!-- 어두운 배경색 -->
Width="250"
Height="510"
ClipToBounds="True"
Loaded="GameCanvas_Loaded"/>
<!-- 게임 캔버스 로드 이벤트 설정 -->
</Viewbox>
<!-- 점수를 표시하는 텍스트 -->
<TextBlock x:Name="ScoreText"
Grid.Row="0"
Grid.Column="1"
Text="Score: "
Margin="0, 10"
TextAlignment="Center"/>
<!-- Hold 블록 영역 -->
<StackPanel Grid.Row="1" Grid.Column="0"
VerticalAlignment="Center"
HorizontalAlignment="Right">
<TextBlock Text="Hold" TextAlignment="Center"/>
<Image x:Name="HoldImage" Margin="20" Width="125"/>
</StackPanel>
<!-- Next 블록 영역 -->
<StackPanel Grid.Row="1" Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Left">
<TextBlock Text="Next" TextAlignment="Center"/>
<Image x:Name="NextImage" Margin="20" Width="125"/>
</StackPanel>
<!-- 게임 오버 메뉴 -->
<Grid x:Name="GameOverMenu"
Background="#CC000000" <!-- 반투명한 검은색 배경 -->
Grid.RowSpan="2" <!-- 전체 행에 걸쳐 표시 -->
Grid.ColumnSpan="3" <!-- 전체 열에 걸쳐 표시 -->
Visibility="Hidden"> <!-- 초기 상태는 숨김 -->
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="Game Over" FontSize="48" TextAlignment="Center"/>
<TextBlock x:Name="FinalScoreText" Text="Score: " FontSize="36" TextAlignment="Center"/>
<Button Content="Play Again"
Background="LightGreen"
Margin="0,20,0,0"
Padding="5"
Click="PlayAgain_Click"/>
<!-- 'Play Again' 버튼 클릭 이벤트 -->
</StackPanel>
</Grid>
</Grid>
</Window>
- <Window>: 테트리스 애플리케이션의 메인 윈도우로, XAML 코드를 통해 UI의 구조와 스타일을 정의합니다.
- <Grid>: 전체 UI 레이아웃을 구성하는 컨테이너입니다. 행과 열을 정의하여 UI 요소를 배치합니다.
- <Viewbox>: GameCanvas를 포함하여 캔버스의 크기를 동적으로 조정할 수 있도록 합니다.
- <TextBlock>: 점수와 게임 오버 메시지를 표시하는 텍스트 요소입니다.
- <StackPanel>: Hold 및 Next 블록을 보여주는 컨테이너로, 세로 방향으로 요소를 정렬합니다.
- <Grid x:Name="GameOverMenu">: 게임 오버 상태일 때 표시되는 메뉴입니다. 처음에는 Visibility가 Hidden으로 설정되어 숨겨져 있습니다.
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Threading.Tasks;
namespace Tetris
{
/// <summary>
/// MainWindow.xaml의 코드 비하인드 클래스
/// 테트리스 게임의 주요 로직과 화면 렌더링을 담당
/// </summary>
public partial class MainWindow : Window
{
// 블록의 타일 이미지를 저장하는 배열
private readonly ImageSource[] tileImages = new ImageSource[]
{
new BitmapImage(new Uri("Assets/TileEmpty.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/TileCyan.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/TileBlue.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/TileOrange.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/TileYellow.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/TileGreen.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/TilePurple.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/TileRed.png", UriKind.Relative)),
};
// 다음 블록과 홀드 블록 이미지를 저장하는 배열
private readonly ImageSource[] blockImages = new ImageSource[]
{
new BitmapImage(new Uri("Assets/Block-Empty.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/Block-I.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/Block-J.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/Block-L.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/Block-O.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/Block-S.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/Block-T.png", UriKind.Relative)),
new BitmapImage(new Uri("Assets/Block-Z.png", UriKind.Relative))
};
// 그리드의 각 셀에 대한 이미지 컨트롤을 저장하는 2차원 배열
private readonly Image[,] imageControls;
// 게임 진행 속도를 조절하기 위한 지연 시간 설정
private readonly int maxDelay = 1000;
private readonly int minDelay = 75;
private readonly int delayDecrease = 25;
// 게임 상태를 관리하는 객체
private GameState gameState = new GameState();
// 생성자: 윈도우를 초기화하고 게임 캔버스를 설정
public MainWindow()
{
InitializeComponent();
imageControls = SetupGameCanvas(gameState.GameGrid);
}
// 게임 캔버스를 설정하고 이미지 컨트롤 배열을 반환하는 메서드
private Image[,] SetupGameCanvas(GameGrid grid)
{
Image[,] imageControls = new Image[grid.Rows, grid.Columns];
int cellSize = 25;
for (int r = 0; r < grid.Rows; r++)
{
for (int c = 0; c < grid.Columns; c++)
{
Image imageControl = new Image
{
Width = cellSize,
Height = cellSize
};
// 이미지의 위치 설정
Canvas.SetTop(imageControl, (r - 2) * cellSize + 10);
Canvas.SetLeft(imageControl, c * cellSize);
GameCanvas.Children.Add(imageControl);
imageControls[r, c] = imageControl;
}
}
return imageControls;
}
// 게임 보드를 그리는 메서드
private void DrawGrid(GameGrid grid)
{
for (int r = 0; r < grid.Rows; r++)
{
for (int c = 0; c < grid.Columns; c++)
{
int id = grid[r, c];
imageControls[r, c].Opacity = 1;
imageControls[r, c].Source = tileImages[id];
}
}
}
// 현재 블록을 그리는 메서드
private void DrawBlock(Block block)
{
foreach (Position p in block.TilePositions())
{
imageControls[p.Row, p.Column].Opacity = 1;
imageControls[p.Row, p.Column].Source = tileImages[block.Id];
}
}
// 다음 블록 이미지를 그리는 메서드
private void DrawNextBlock(BlockQueue blockQueue)
{
Block next = blockQueue.NextBlock;
NextImage.Source = blockImages[next.Id];
}
// 홀드된 블록 이미지를 그리는 메서드
private void DrawHeldBlock(Block heldBlock)
{
if (heldBlock == null)
{
HoldImage.Source = blockImages[0]; // 빈 블록 이미지
}
else
{
HoldImage.Source = blockImages[heldBlock.Id];
}
}
// 블록의 고스트(예상 낙하 위치)를 그리는 메서드
private void DrawGhostBlock(Block block)
{
int dropDistance = gameState.BlockDropDistance();
foreach (Position p in block.TilePositions())
{
imageControls[p.Row + dropDistance, p.Column].Opacity = 0.25;
imageControls[p.Row + dropDistance, p.Column].Source = tileImages[block.Id];
}
}
// 전체 게임 상태를 그리는 메서드
private void Draw(GameState gameState)
{
DrawGrid(gameState.GameGrid);
DrawGhostBlock(gameState.CurrentBlock);
DrawBlock(gameState.CurrentBlock);
DrawNextBlock(gameState.BlockQueue);
ScoreText.Text = $"Score: {gameState.Score}";
DrawHeldBlock(gameState.HeldBlock);
}
// 게임 루프를 비동기로 실행하는 메서드
private async Task GameLoop()
{
Draw(gameState);
while (!gameState.GameOver)
{
int delay = Math.Max(minDelay, maxDelay - (gameState.Score * delayDecrease));
await Task.Delay(delay); // 지정된 시간만큼 대기
gameState.MoveBlockDown();
Draw(gameState); // 블록 이동 후 화면 갱신
}
// 게임 오버 시 메뉴 표시
GameOverMenu.Visibility = Visibility.Visible;
FinalScoreText.Text = $"Score: {gameState.Score}";
}
// 키 입력을 처리하는 메서드
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (gameState.GameOver)
{
return; // 게임 오버 상태면 입력 무시
}
switch (e.Key)
{
case Key.Left:
gameState.MoveBlockLeft();
break;
case Key.Right:
gameState.MoveBlockRight();
break;
case Key.Down:
gameState.MoveBlockDown();
break;
case Key.Up:
gameState.RotateBlockCW();
break;
case Key.Z:
gameState.RotateBlockCCW();
break;
case Key.C:
gameState.HoldBlock();
break;
case Key.Space:
gameState.DropBlock();
break;
default:
return; // 지원되지 않는 키는 무시
}
Draw(gameState); // 키 입력 후 화면 갱신
}
// 게임 캔버스가 로드될 때 게임 루프를 시작하는 메서드
private async void GameCanvas_Loaded(object sender, RoutedEventArgs e)
{
await GameLoop();
}
// "Play Again" 버튼 클릭 시 게임을 재시작하는 메서드
private async void PlayAgain_Click(object sender, RoutedEventArgs e)
{
gameState = new GameState(); // 새 게임 상태 생성
GameOverMenu.Visibility = Visibility.Hidden; // 게임 오버 메뉴 숨김
await GameLoop(); // 새 게임 루프 시작
}
}
}
- tileImages 및 blockImages: 각 타일과 블록의 이미지를 ImageSource 배열로 저장해 게임에서 사용.
- GameLoop(): 게임의 주요 루프를 비동기로 실행해 일정 간격마다 블록을 아래로 이동시키고 화면을 갱신.
- Window_KeyDown(): 사용자의 키 입력을 처리해 블록 이동, 회전, 낙하 등을 수행.
- Draw() 메서드: 게임 보드, 현재 블록, 다음 블록, 홀드 블록, 고스트 블록 등을 화면에 그리는 역할.
- GameCanvas_Loaded(): 게임 캔버스 로드 시 게임 루프를 시작.
- PlayAgain_Click(): "Play Again" 버튼 클릭 시 새로운 게임을 시작
'C#' 카테고리의 다른 글
네트워크 프로그래밍 (0) | 2024.11.04 |
---|---|
Task (0) | 2024.11.03 |
대리자와 이벤트 (0) | 2024.11.01 |
프로그래밍 (0) | 2024.11.01 |
배열과 컬렉션 그리고 인덱서 (0) | 2024.11.01 |
댓글