Parent directory

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}