Compare commits

...

3 Commits

8 changed files with 113 additions and 41 deletions

View File

@ -17,7 +17,7 @@ struct ContentView: View {
return c return c
}() }()
@State var timer: Timer? @State var timer: Timer?
@State var prevHorizTranslation: CGFloat? @State var initialXPosition: Int?
var body: some View { var body: some View {
GeometryReader { (geometry) in GeometryReader { (geometry) in
@ -27,7 +27,7 @@ struct ContentView: View {
.onAppear(perform: self.startTimer) .onAppear(perform: self.startTimer)
.onDisappear(perform: self.stopTimer) .onDisappear(perform: self.stopTimer)
.onTapGesture(perform: self.onTap) .onTapGesture(perform: self.onTap)
.gesture(self.horizDragGesture(geometry: geometry)) .gesture(self.horizDragGesture(geometry: geometry).simultaneously(with: self.verticalDragGesture))
// .gesture(ExclusiveGesture(horizDragGesture, verticalDragGesture)) // .gesture(ExclusiveGesture(horizDragGesture, verticalDragGesture))
// .gesture(horizDragGesture.simultaneously(with: verticalDragGesture)) // .gesture(horizDragGesture.simultaneously(with: verticalDragGesture))
@ -57,32 +57,25 @@ struct ContentView: View {
DragGesture(coordinateSpace: .global) DragGesture(coordinateSpace: .global)
.onChanged { (state) in .onChanged { (state) in
guard let currentPiece = self.controller.currentPiece else { return } guard let currentPiece = self.controller.currentPiece else { return }
// let position = (state.location.x) / geometry.size.width * 10 if self.initialXPosition == nil {
// let moved = currentPiece.moved(by: (Int(position.rounded()) - currentPiece.topLeft.0, 0)) self.initialXPosition = currentPiece.topLeft.0
// if !self.controller.overlapsAny(moved) {
// self.controller.currentPiece = moved
// }
if self.prevHorizTranslation == nil {
self.prevHorizTranslation = state.translation.width
} }
let delta = state.translation.width - self.prevHorizTranslation! var moved = currentPiece
let amount = Int((delta / 20).rounded()) let xPosition = self.initialXPosition! + Int((state.translation.width / (geometry.size.width / 10)).rounded())
let moved = currentPiece.moved(by: (amount, 0)) moved.topLeft = (xPosition, currentPiece.topLeft.1)
if !self.controller.overlapsAny(moved) { if !self.controller.overlapsAny(moved) {
self.controller.currentPiece = moved self.controller.currentPiece = moved
self.prevHorizTranslation = state.translation.width
} }
}.onEnded { (state) in }.onEnded { (state) in
self.prevHorizTranslation = nil self.initialXPosition = nil
} }
} }
var verticalDragGesture: some Gesture { var verticalDragGesture: some Gesture {
DragGesture() DragGesture()
.onEnded { (state) in .onEnded { (state) in
let deltaY = state.location.y - state.startLocation.y if abs(state.translation.height) > 80 {
if abs(deltaY) > 40 { if state.translation.height > 0 {
if deltaY > 0 {
self.onSwipeDown() self.onSwipeDown()
} else { } else {
self.onSwipeUp() self.onSwipeUp()

View File

@ -12,41 +12,43 @@ public struct GameBoard {
public let width: Int public let width: Int
public let height: Int public let height: Int
public internal(set) var tiles: [[Bool]] public internal(set) var tiles: [[Tetromino?]]
public init(width: Int, height: Int) { public init(width: Int, height: Int) {
self.width = width self.width = width
self.height = height self.height = height
tiles = (1...height).map { _ in tiles = (1...height).map { _ in
Array(repeating: false, count: width) Array(repeating: nil, count: width)
} }
} }
public subscript(x: Int, y: Int) -> Bool { public subscript(x: Int, y: Int) -> Bool {
return self.tiles[y][x] != nil
}
public subscript(x: Int, y: Int) -> Tetromino? {
return self.tiles[y][x] return self.tiles[y][x]
} }
mutating func set(piece: GamePiece) { public mutating func set(piece: GamePiece) {
let (left, top) = piece.topLeft let (left, top) = piece.topLeft
for y in 0..<piece.tiles.count where y + top < height { for y in 0..<piece.tiles.count where y + top < height {
for x in 0..<piece.tiles.first!.count where x + left < width { for x in 0..<piece.tiles.first!.count where x + left < width {
if piece.tiles[y][x] { if piece.tiles[y][x] {
self.tiles[y + top][x + left] = true self.tiles[y + top][x + left] = piece.tetromino
} }
} }
} }
} }
public mutating func set(tiles: [(Int, Int)]) { mutating func clear(tiles: [(Int, Int)]) {
for (x, y) in tiles { for (x, y) in tiles {
self.tiles[y][x] = true self.tiles[y][x] = nil
} }
} }
mutating func unset(tiles: [(Int, Int)]) { func rowFull(_ row: Int) -> Bool {
for (x, y) in tiles { return tiles[row].allSatisfy({ $0 != nil })
self.tiles[y][x] = false
}
} }
} }

View File

@ -14,6 +14,8 @@ public class GameController: ObservableObject {
public let width = 10 public let width = 10
public let height = 16 public let height = 16
var state: GameState = .waitingForStart
@Published public var board: GameBoard @Published public var board: GameBoard
@Published public var currentPiece: GamePiece? { @Published public var currentPiece: GamePiece? {
@ -33,6 +35,7 @@ public class GameController: ObservableObject {
} }
public func start() { public func start() {
state = .playing(.normal)
nextPiece() nextPiece()
} }
@ -45,24 +48,29 @@ public class GameController: ObservableObject {
self.board.set(piece: currentPiece!) self.board.set(piece: currentPiece!)
clearLines() clearLines()
nextPiece() nextPiece()
if case .playing(.dropped) = state {
state = .playing(.normal)
}
} }
func clearLines() { func clearLines() {
let fullRow = Array(repeating: true, count: width)
var row = height - 1 var row = height - 1
while row >= 0 { while row >= 0 {
if board.tiles[row] == fullRow { if board.rowFull(row) {
board.tiles.remove(at: row) board.tiles.remove(at: row)
} }
row -= 1 row -= 1
} }
for _ in 0..<height - board.tiles.count { for _ in 0..<height - board.tiles.count {
board.tiles.insert(Array(repeating: false, count: width), at: 0) board.tiles.insert(Array(repeating: nil, count: width), at: 0)
} }
} }
public func step() { public func step() {
guard let currentPiece = currentPiece else { return } guard case .playing(_) = state,
let currentPiece = currentPiece else { return }
let modifiedPiece = currentPiece.moved(by: (0, 1)) let modifiedPiece = currentPiece.moved(by: (0, 1))
if overlapsAny(modifiedPiece) { if overlapsAny(modifiedPiece) {
finalizePiece() finalizePiece()
@ -72,7 +80,9 @@ public class GameController: ObservableObject {
} }
public func rotate(direction: RotationDirection) { public func rotate(direction: RotationDirection) {
guard let currentPiece = currentPiece else { return } guard case .playing(_) = state,
let currentPiece = currentPiece else { return }
let modifiedPiece = currentPiece.rotated(direction) let modifiedPiece = currentPiece.rotated(direction)
if !overlapsAny(modifiedPiece) { if !overlapsAny(modifiedPiece) {
self.currentPiece = modifiedPiece self.currentPiece = modifiedPiece
@ -80,7 +90,8 @@ public class GameController: ObservableObject {
} }
public func left() { public func left() {
guard let currentPiece = currentPiece else { return } guard case .playing(.normal) = state,
let currentPiece = currentPiece else { return }
let modifiedPiece = currentPiece.moved(by: (-1, 0)) let modifiedPiece = currentPiece.moved(by: (-1, 0))
if !overlapsAny(modifiedPiece) { if !overlapsAny(modifiedPiece) {
self.currentPiece = modifiedPiece self.currentPiece = modifiedPiece
@ -88,7 +99,8 @@ public class GameController: ObservableObject {
} }
public func right() { public func right() {
guard let currentPiece = currentPiece else { return } guard case .playing(.normal) = state,
let currentPiece = currentPiece else { return }
let modifiedPiece = currentPiece.moved(by: (1, 0)) let modifiedPiece = currentPiece.moved(by: (1, 0))
if !overlapsAny(modifiedPiece) { if !overlapsAny(modifiedPiece) {
self.currentPiece = modifiedPiece self.currentPiece = modifiedPiece
@ -96,10 +108,12 @@ public class GameController: ObservableObject {
} }
public func drop() { public func drop() {
guard case .playing(.normal) = state else { return }
currentPiece = currentPieceAtDropPoint currentPiece = currentPieceAtDropPoint
state = .playing(.dropped)
} }
func updateCurrentPieceAtDropPoint() { private func updateCurrentPieceAtDropPoint() {
guard let currentPiece = currentPiece else { return } guard let currentPiece = currentPiece else { return }
var prev = currentPiece var prev = currentPiece
currentPieceAtDropPoint = currentPiece currentPieceAtDropPoint = currentPiece
@ -123,3 +137,13 @@ public class GameController: ObservableObject {
} }
} }
enum GameState {
case waitingForStart
case playing(PlayState)
}
enum PlayState {
case normal
case dropped
}

View File

@ -10,7 +10,7 @@ import Foundation
public struct GamePiece { public struct GamePiece {
public let tetromino: Tetromino public let tetromino: Tetromino
public internal(set) var topLeft: (Int, Int) public var topLeft: (Int, Int)
public internal(set) var tiles: [[Bool]] public internal(set) var tiles: [[Bool]]
public init(tetromino: Tetromino) { public init(tetromino: Tetromino) {

View File

@ -18,11 +18,21 @@ struct CurrentPieceView: View {
var body: some View { var body: some View {
GridView(rows: self.boardHeight, columns: self.boardWidth) { (col, row, size) in GridView(rows: self.boardHeight, columns: self.boardWidth) { (col, row, size) in
Rectangle() Rectangle()
.foregroundColor(self.currentPieceAt(col, row) ? Color.blue : self.droppedPieceAt(col, row) ? Color.gray : Color.clear) .foregroundColor(self.colorAt(col, row))
.frame(width: size, height: size) .frame(width: size, height: size)
} }
} }
func colorAt(_ col: Int, _ row: Int) -> Color {
if currentPieceAt(col, row) {
return self.currentPiece!.tetromino.color
} else if droppedPieceAt(col, row) {
return .gray
} else {
return .clear
}
}
func currentPieceAt(_ col: Int, _ row: Int) -> Bool { func currentPieceAt(_ col: Int, _ row: Int) -> Bool {
guard let currentPiece = self.currentPiece else { return false } guard let currentPiece = self.currentPiece else { return false }
let (left, top) = currentPiece.topLeft let (left, top) = currentPiece.topLeft

View File

@ -0,0 +1,41 @@
//
// Tetromino+Color.swift
// Tetris
//
// Created by Shadowfacts on 10/15/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import SwiftUI
import TetrisKit
fileprivate extension Color {
static let tetrominoAqua = Color(red: 0, green: 214/255, blue: 211/255)
static let tetrominoBlue = Color(red: 0, green: 79/255, blue: 214/255)
static let tetrominoOrange = Color(red: 255/255, green: 128/255, blue: 0)
static let tetrominoYellow = Color(red: 255/255, green: 229/255, blue: 0)
static let tetrominoGreen = Color(red: 0, green: 214/255, blue: 71/255)
static let tetrominoPurple = Color(red: 168/255, green: 0, blue: 214/255)
static let tetrominoRed = Color(red: 214/255, green: 0, blue: 61/255)
}
extension Tetromino {
var color: Color {
switch self {
case .i:
return .tetrominoAqua
case .o:
return .tetrominoYellow
case .t:
return .tetrominoPurple
case .j:
return .tetrominoOrange
case .l:
return .tetrominoBlue
case .s:
return .tetrominoRed
case .z:
return .tetrominoGreen
}
}
}

View File

@ -16,7 +16,7 @@ struct TilesView: View {
GridView(rows: board.height, columns: board.width) { (col, row, size) in GridView(rows: board.height, columns: board.width) { (col, row, size) in
Rectangle() Rectangle()
.frame(width: size, height: size) .frame(width: size, height: size)
.foregroundColor(self.board[col, row] ? Color.red : Color.black) .foregroundColor(self.board[col, row]?.color ?? Color.clear)
} }
} }
} }
@ -24,7 +24,9 @@ struct TilesView: View {
struct TilesView_Previews: PreviewProvider { struct TilesView_Previews: PreviewProvider {
@State static var board: GameBoard = { @State static var board: GameBoard = {
var b = GameBoard(width: 10, height: 16) var b = GameBoard(width: 10, height: 16)
b.set(tiles: [(0, 15), (1, 15), (1, 14), (2, 14)]) var piece = GamePiece(tetromino: .z)
piece.topLeft = (0, 14)
b.set(piece: piece)
return b return b
}() }()

View File

@ -82,7 +82,7 @@ extension TwoDimString {
setColumn(controller.width + 1, to: "+" + String(repeating: "|", count: controller.height) + "+") setColumn(controller.width + 1, to: "+" + String(repeating: "|", count: controller.height) + "+")
for y in 0..<controller.height { for y in 0..<controller.height {
for x in 0..<controller.width where controller.board.get(tile: (x, y)) { for x in 0..<controller.width where controller.board[x, y] {
self[x + 1, y + 1] = "X" self[x + 1, y + 1] = "X"
} }
} }
@ -99,7 +99,7 @@ extension TwoDimString {
} }
let controller = GameController() let controller = GameController()
controller.currentPiece = GamePiece(tetromino: .l) controller.start()
func readMove() { func readMove() {
print(TwoDimString(controller: controller)) print(TwoDimString(controller: controller))