board.go
8222 bytes
1package game
2
3import (
4 "math"
5
6 "gitea.theedgeofrage.com/theedgeofrage/yeetris/elements"
7 "github.com/gen2brain/raylib-go/raylib"
8)
9
10type Board struct {
11 Board [10][20]*elements.Block
12
13 ActivePiece *elements.Piece
14 ActivePiecePos rl.Vector2
15 NextPiece *elements.Piece
16 HeldPiece *elements.Piece
17 AlreadyHeld bool
18}
19
20func InitBoard() *Board {
21 board := &Board{
22 NextPiece: elements.GetRandomPiece(),
23 }
24 _ = board.getNextPiece()
25
26 return board
27}
28
29func (b *Board) getNextPiece() error {
30 b.AlreadyHeld = false
31 b.ActivePiecePos = rl.NewVector2(5, 0)
32 b.ActivePiece = b.NextPiece
33 b.NextPiece = elements.GetRandomPiece()
34 for _, block := range b.ActivePiece.Blocks {
35 newX, newY := b.ActivePiecePos.X+block.Position.X, b.ActivePiecePos.Y+block.Position.Y
36 if newY >= 0 && b.Board[int(newX)][int(newY)] != nil {
37 return ErrGameOver
38 }
39 }
40 return nil
41}
42
43func (b *Board) setBlocksFromActivePiece() error {
44 for _, block := range b.ActivePiece.Blocks {
45 newX, newY := b.ActivePiecePos.X+block.Position.X, b.ActivePiecePos.Y+block.Position.Y
46 if newY < 0 {
47 return ErrGameOver
48 }
49 b.Board[int(newX)][int(newY)] = &elements.Block{
50 Position: rl.NewVector2(newX, newY),
51 Color: b.ActivePiece.Blocks[0].Color,
52 }
53 }
54
55 return nil
56}
57
58func (b *Board) checkHorizontalCollision(left bool) bool {
59 for _, activeBlock := range b.ActivePiece.Blocks {
60 xPos := int(b.ActivePiecePos.X + activeBlock.Position.X)
61 yPos := int(b.ActivePiecePos.Y + activeBlock.Position.Y)
62 if left {
63 if xPos == 0 {
64 return true
65 }
66 if yPos >= 0 && b.Board[xPos-1][yPos] != nil {
67 return true
68 }
69 } else {
70 if xPos == 9 {
71 return true
72 }
73 if yPos >= 0 && b.Board[xPos+1][yPos] != nil {
74 return true
75 }
76 }
77 }
78 return false
79}
80
81func (b *Board) descendActivePiece() (bool, error) {
82 b.ActivePiecePos.Y += 1
83 for _, activeBlock := range b.ActivePiece.Blocks {
84 if b.ActivePiecePos.Y+activeBlock.Position.Y == 20 ||
85 b.Board[int(b.ActivePiecePos.X+activeBlock.Position.X)][int(b.ActivePiecePos.Y+activeBlock.Position.Y)] != nil {
86 b.ActivePiecePos.Y -= 1
87 return true, b.setBlocksFromActivePiece()
88 }
89 }
90 return false, nil
91}
92
93// DescendActivePiece moves the piece down by one block. If the piece cannot move down, it will be placed on the board.
94// After that, a new piece will be generated. If the new piece cannot be placed, a game over error is returned.
95func (b *Board) DescendActivePiece() error {
96 placed, err := b.descendActivePiece()
97 if err != nil {
98 return err
99 }
100
101 if placed {
102 return b.getNextPiece()
103 }
104 return nil
105}
106
107// DropActivePiece moves the piece down until it cannot move down anymore. After that, a new piece will be generated.
108// If the new piece cannot be placed, a game over error is returned.
109func (b *Board) DropActivePiece() error {
110 for {
111 placed, err := b.descendActivePiece()
112 if err != nil {
113 return err
114 }
115 if placed {
116 break
117 }
118 }
119 return b.getNextPiece()
120}
121
122// MoveActivePiece moves the piece left or right. If the piece cannot move in the specified direction, nothing happens.
123func (b *Board) MoveActivePiece(direction int) {
124 if !b.checkHorizontalCollision(direction == -1) {
125 b.ActivePiecePos.X += float32(direction)
126 }
127}
128
129func getCollisionDepth(collisionDepth int, longBoi bool) int {
130 if longBoi || collisionDepth == 2 {
131 return 2
132 } else {
133 return 1
134 }
135}
136
137// RotateActivePiece rotates the piece clockwise or counter-clockwise. If the piece cannot rotate in the specified
138// direction, nothing happens.
139func (b *Board) RotateActivePiece(clockwise bool) {
140 if b.ActivePiece.Blocks[0].Color == elements.Yellow {
141 return
142 }
143
144 angle := rl.Deg2rad * 90
145 if !clockwise {
146 angle = -angle
147 }
148
149 tmpPiece := &elements.Piece{
150 Blocks: make([]*elements.Block, 4),
151 }
152 for i := 0; i < 4; i++ {
153 newPos := rl.Vector2Rotate(b.ActivePiece.Blocks[i].Position, float32(angle))
154 newPos.X = float32(math.Round(float64(newPos.X)))
155 newPos.Y = float32(math.Round(float64(newPos.Y)))
156 tmpPiece.Blocks[i] = &elements.Block{
157 Position: newPos,
158 Color: b.ActivePiece.Blocks[i].Color,
159 LongBoi: b.ActivePiece.Blocks[i].LongBoi,
160 }
161 }
162 collisionLeft := 0
163 collisionRight := 0
164 collisionBottom := 0
165 for _, block := range tmpPiece.Blocks {
166 newX, newY := b.ActivePiecePos.X+block.Position.X, b.ActivePiecePos.Y+block.Position.Y
167 if newY < 0 {
168 continue
169 }
170 if newX < 0 && collisionLeft == 0 {
171 collisionLeft = getCollisionDepth(collisionLeft, block.LongBoi)
172 }
173 if newX > 9 && collisionRight == 0 {
174 collisionRight = getCollisionDepth(collisionRight, block.LongBoi)
175 }
176 if newY > 19 && collisionBottom == 0 {
177 collisionBottom = getCollisionDepth(collisionBottom, block.LongBoi)
178 }
179 if collisionLeft != 0 || collisionRight != 0 || collisionBottom != 0 {
180 continue
181 }
182 if b.Board[int(newX)][int(newY)] != nil {
183 if newX < b.ActivePiecePos.X && collisionLeft == 0 {
184 collisionLeft = getCollisionDepth(collisionLeft, block.LongBoi)
185 }
186 if newX > b.ActivePiecePos.X && collisionRight == 0 {
187 collisionRight = getCollisionDepth(collisionRight, block.LongBoi)
188 }
189 if newY > b.ActivePiecePos.Y && collisionBottom == 0 {
190 collisionBottom = getCollisionDepth(collisionBottom, block.LongBoi)
191 }
192 }
193 }
194 if collisionLeft != 0 && collisionRight != 0 {
195 return
196 }
197
198 b.ActivePiecePos.Y -= float32(collisionBottom)
199 b.ActivePiecePos.X += float32(collisionLeft)
200 b.ActivePiecePos.X -= float32(collisionRight)
201 b.ActivePiece = tmpPiece
202}
203
204func (b *Board) HoldPiece() {
205 if b.AlreadyHeld {
206 return
207 }
208 b.AlreadyHeld = true
209 if b.HeldPiece == nil {
210 b.HeldPiece = b.ActivePiece
211 _ = b.getNextPiece()
212 return
213 }
214 b.HeldPiece, b.ActivePiece = b.ActivePiece, b.HeldPiece
215 b.ActivePiecePos = rl.NewVector2(5, 0)
216}
217
218// ClearLines clears all lines that are completely filled.
219func (g *Board) ClearLines() int {
220 clearedLines := 0
221 for y := 19; y >= 0; {
222 skip := false
223 for x := 0; x < 10; x++ {
224 if g.Board[x][y] == nil {
225 skip = true
226 break
227 }
228 }
229 if skip {
230 y--
231 continue
232 }
233 clearedLines++
234 for ty := y; ty >= 0; ty-- {
235 for x := 0; x < 10; x++ {
236 // always clear top line
237 if ty == 0 {
238 g.Board[x][ty] = nil
239 continue
240 }
241 // move all blocks above cleared line down
242 if g.Board[x][ty-1] != nil {
243 g.Board[x][ty] = g.Board[x][ty-1]
244 g.Board[x][ty].Position.Y++
245 } else {
246 g.Board[x][ty] = nil
247 }
248 }
249 }
250 }
251 return clearedLines
252}
253
254func (b *Board) DrawGrid(offset, scale rl.Vector2) {
255 for i := 0; i < 10; i++ {
256 rl.DrawLine(
257 int32(scale.X*float32(i)+offset.X),
258 int32(offset.Y),
259 int32(scale.X*float32(i)+offset.X),
260 int32(20*scale.Y+offset.Y),
261 elements.Gray,
262 )
263 }
264 for i := 0; i < 20; i++ {
265 rl.DrawLine(
266 int32(offset.X),
267 int32(scale.Y*float32(i)+offset.Y),
268 int32(10*scale.X+offset.X),
269 int32(scale.Y*float32(i)+offset.Y),
270 elements.Gray,
271 )
272 }
273}
274
275func (b *Board) drawUIPiece(piece *elements.Piece, position, scale rl.Vector2) {
276 centerOffset := rl.NewVector2(0, 0)
277 for _, block := range piece.Blocks {
278 centerOffset = rl.Vector2Add(
279 centerOffset,
280 rl.Vector2Add(block.Position, rl.NewVector2(0.5, 0.5)),
281 )
282 }
283 centerOffset = rl.Vector2Divide(centerOffset, rl.NewVector2(4, 4))
284 centerOffset = rl.Vector2Add(centerOffset, rl.NewVector2(0, 1))
285 centerOffset = rl.Vector2Multiply(centerOffset, scale)
286 for _, block := range piece.Blocks {
287 block.Draw(rl.NewVector2(0, 1), scale, rl.Vector2Subtract(position, centerOffset))
288 }
289}
290
291// DrawNextPiece draws the next piece in the UI at the specified position.
292func (b *Board) DrawNextPiece(position, scale rl.Vector2) {
293 b.drawUIPiece(b.NextPiece, position, scale)
294}
295
296// DrawHeldPiece draws the held piece in the UI at the specified position.
297func (b *Board) DrawHeldPiece(position, scale rl.Vector2) {
298 if b.HeldPiece == nil {
299 return
300 }
301 b.drawUIPiece(b.HeldPiece, position, scale)
302}
303
304// DrawBoard draws the board with placed blocks and the active piece.
305func (b *Board) DrawBoard(offset rl.Vector2, scale rl.Vector2) {
306 // b.DrawGrid(offset, scale)
307 for i := 0; i < 20; i++ {
308 for j := 0; j < 10; j++ {
309 if b.Board[j][i] == nil {
310 continue
311 }
312
313 b.Board[j][i].Draw(rl.NewVector2(0, 0), scale, offset)
314 }
315 }
316 b.ActivePiece.Draw(b.ActivePiecePos, scale, offset)
317}