作業#32 — 西洋棋盤八王后遊戲

練習 for in 迴圈、array 陣列、if else 條件設置。

作業來源:

Preview

這次的作業是練習西洋棋盤格,剛好朋友提到了 “八皇后挑戰”,就順便一起玩啦。

八皇后挑戰要求任兩個皇后都不能處於同一條橫行、縱行或斜線上,可以有多種解答。此次練習除了設計程式讓電腦自動生成解答外,也設置了可以讓人點選遊玩的棋盤介面。

Steps

I. 八王后問題

觀察棋盤規律,可以找出以下條件:

  1. 直排不重複:x 不能相等
  2. 橫排不重複:y 不能相等
  3. 右斜不重複:x + y 不能相等
  4. 左斜不重複:x -y 不能相等

故我們先設定四個 Array :

  1. x array:x 值(1~8)
  2. y array: y值(1~8)
  3. sum array: x+y 值 (空)
  4. differences array:x-y 值 (空)

從 x = 1 開始試,假設 y 為 1 ~ 8 間任意數,將 x + y 的值加入 sum array、將 x-y 的值加入 differences array、並將 y 值從 y array 中移除,避免重複使用。

接下來試 x = 2,y 為剩下的 7 個數字任選一,先檢驗 x + y 與 x — y 的值是否分別與 sum array 及 differences array 重複。若是重複,則須重選一個 y 值,若是沒有重複,則將 x + y 與 x — y 的值也加入 sum array 及 differences array 中。後續不停重複此步驟。

若試過剩餘所有的 y 值,都會與 sum array 及 differences array 重複的話,則此測驗會重頭來。若是所有的 x 值都測試完成,就表示成功找到 8 組座標彼此間不重複了。

//i, j array 分別儲存測試出來的 x, y 座標,會在其他地方使用到,故生成在 func 前。
var i :[Int] = []
var j :[Int] = []


func lookUpAnswers() {
//x 座標
var x = [1,2,3,4,5,6,7,8]
//y 座標
var y = [1,2,3,4,5,6,7,8]
//用以儲存 x+y 值
var sums:[Int] = []
//用以儲存 x-y 值
var differences:[Int] = []
//x座標的 index
var a = 0
//y座標的 index
var b = 0

//只要 x array 不為空之前會無限迴圈
while !x.isEmpty {

//因 b 設定的範圍是 in: 0..<y.count,避免出現 out of range 的情況(當 y=1 時會變成 "0..<0"),故特別設置此條件。
if x.count > 1 {
a = 0
b = Int.random(in: 0..<y.count)
} else {
a = 0
b = 0
}

//檢測每個 x+y 的值
let sumCheck = x[a]+y[b]
//檢測每個 x-y 的值
let differenceCheck = x[a]-y[b]

//如果 sum 及 difference 都沒有重複出現才可加入 array 中進行儲存。
if !sums.contains(sumCheck) && !differences.contains(differenceCheck) {
i.append(x[a]) //儲存測試除來的 x 座標
j.append(y[b]) //儲存測試除來的 y 座標
x.remove(at: a) // 在 x array 中移除以使用過的值(使得 x 值不重複)
y.remove(at: b) // 在 y array 中移除以使用過的值(使得 y 值不重複)
a += 1 //測驗完後 a+1,以檢驗下一個 x 值
sums.append(sumCheck) //將新的 x+y 值加入 sums array 中,用以檢驗未來的 x+y 值,以確保不重複
differences.append(differenceCheck) //將新的 x-y 值加入 differences array 中,用以檢驗未來的 x+y 值,以確保不重複

//如果同一個 x 與所有剩餘的 y 測試後,sum 或是 difference 都有重複的值,則重新開始此測驗(重設所有 array,x 從 1 開始再次檢驗)。
} else {
x = [1,2,3,4,5,6,7,8]
y = [1,2,3,4,5,6,7,8]
a = 0
i.removeAll()
j.removeAll()
sums.removeAll()
differences.removeAll()
}
}
}

2. 生成西洋棋盤格及八王后位置

上一步驟找出了八個座標後,將之分別儲存在 i array 及 j array 中。接下來要在畫面中生成西洋棋盤,並將此八個座標標記上去。

    func eightQueens() {
//要建立 8*8 的西洋棋盤
for row in 1...8 {
for column in 1...8 {
//以按鈕格式生成
let button = UIButton(type: .system)
//設置按鈕尺寸及位置
button.frame = CGRect(x: 3 + 48 * (row-1), y: 180 + 48 * (column-1), width:48 , height:48 )

//設置西洋棋盤交錯的顏色
if (row + column) % 2 == 0 {
button.backgroundColor = themeDarkBrown
} else {
button.backgroundColor = themLightBrown
}

//設置 index 變數用以對照 x, y 軸座標(同一組座標會儲存在 i array 及 j array 同一個位置)
//找出這一 row 上,column 所在的位置。在這個位置放上皇后符號。
let index = i.firstIndex(of: row)
if column == j[index!] {
button.setTitle("♛", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 48)
button.tintColor = UIColor.black
}
//將按鈕放上 view
view.addSubview(button)
}
}

}

3. 空白西洋棋盤格

除了生成答案外,也另外製作了空白的西洋棋盤格,讓人可以在上面玩八皇后遊戲。

主要功能為:點擊標記皇后位置、再點擊一次可以移除。若是與其他皇后棋行列/斜線有重複到,則會標記為紅色。最後找出 8 個座標即成功。

//answerRow, answerColumnarray 分別儲使用者選中的 x, y 座標。
var answerRow:[Int] = []
var answerColumn:[Int] = []

func makeCheckerboard() {
//正確皇后棋的變數(未與其他皇后棋重疊者)
var queenCount = 0
//所有皇后棋的變數(包含重疊者)
var moveCount = 0
var wrongStatus:Bool = false

//一樣要設置 8*8 棋盤格
for row in 1...8 {
for column in 1...8 {
let button = UIButton(type: .system)
button.addAction(UIAction(handler: { [self, weak button] _ in
//若按鈕為空(尚未被放置皇后棋),則放上皇后棋。正確皇后棋及所有皇后棋兩個變數都 +1
if button?.currentTitle == nil {
button?.setTitle("♛", for: .normal)
button?.tintColor = UIColor.black
button?.titleLabel?.font = UIFont.systemFont(ofSize: 48)
queenCount += 1
moveCount += 1
print("queen",queenCount,"move",moveCount)

//檢驗如果這個位置放置皇后棋,會與之前的皇后棋位置重疊 (x 值相同 or y 值相同 or x+y 值相同 or x-y 值相同)
for i in 0..<answerRow.count {
if (row + column == answerRow[i]+answerColumn[i]) || (row - column == answerRow[i]-answerColumn[i]) || (row == answerRow[i]) || (column == answerColumn[i]) {
//則將此 wrongStatus 標記為 true (預設為 false)
wrongStatus = true
}
}
//若在上一步驟檢驗時,wrongStatus 為 true
if wrongStatus == true {
//則此皇后棋以紅色顯示,"所有皇后棋"變數 +1,"正確皇后棋"變數不動,wrongStatus 改回 false
button?.tintColor = UIColor.red
queenCount -= 1
wrongStatus = false
}

//將此位置加入 answerRow 及 answerColumn array,用以檢驗未來的位置是否會有重疊
answerRow.append(row)
answerColumn.append(column)


//如果按鈕不為空(已被放上皇后棋),則清空按鈕
} else {
button?.setTitle(nil, for: .normal)
//如果“正確皇后棋”數量等於"所有皇后棋",則兩者皆減一
if queenCount == moveCount {
queenCount -= 1
moveCount -= 1
//如果"正確皇后棋"數量不等於"所有皇后棋",則只要“所有皇后棋”減一即可(被標記為紅色的皇后棋本身便沒有被計入)
} else {
moveCount -= 1
}
//將清空的按鈕座標從 answerRow 及 answerColumn 中移除
if answerRow.contains(row) {
//設定 index 找出最後 row 與 column 對應的位置
let index = answerRow.lastIndex(where: {$0 == row})
answerRow.remove(at: index!)
answerColumn.remove(at: index!)
}
}

//當"正確皇后棋"達到 8 個,表示遊戲成功!會出現 "You Win" 按鈕
if queenCount == 8 {
view.addSubview(winLabel)
winLabel.isHidden = false
}

}), for: .touchUpInside)

//設置按鈕尺寸及位置
button.frame = CGRect(x: 3 + 48 * (row-1), y: 180 + 48 * (column-1), width:48 , height:48 )
//設置西洋棋盤交錯的顏色
if (row + column) % 2 == 0 {
button.backgroundColor = themeDarkBrown
} else {
button.backgroundColor = themLightBrown
}
//將按鈕放上 view
view.addSubview(button)
}
}

}
<a href=”https://www.vecteezy.com/free-vector/chess-poster">Chess Poster Vectors by Vecteezy</a>

appetize.io 模擬器

*appetize.io 免費帳戶一個月只有 50 分鐘,如果出現 “Streaming for this account has been temporarily disabled” 就是這個月的額度被用完啦😆 要等下個月一號才會再重新開始計算。

完整程式碼

//
// ViewController.swift
// chess
//
// Created by 陳佩琪 on 2023/5/28.
//

import UIKit

class ViewController: UIViewController {

var i :[Int] = []
var j :[Int] = []

let themeDarkBrown = UIColor(red: 182/255, green: 136/255, blue: 100/255, alpha: 1)
let themLightBrown = UIColor(red: 241/255, green: 217/255, blue: 181/255, alpha: 1)
var buttons:[UIButton] = []

var answerRow:[Int] = []
var answerColumn:[Int] = []
let winLabel = UILabel()



override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.

makeCheckerboard()


let showAnswersButton = UIButton(type: .system,primaryAction: UIAction(handler: { [unowned self] action in
lookUpAnswers()
eightQueens()
}))
showAnswersButton.frame = CGRect(x: 215, y: 600, width: 140, height: 45)
showAnswersButton.setTitle("Show Answer(s)", for: .normal)
showAnswersButton.tintColor = UIColor.white
buttons.append(showAnswersButton)

let resetButton = UIButton(type: .system,primaryAction: UIAction(handler: { [unowned self] action in
makeCheckerboard()
}))
resetButton.frame = CGRect(x: 25, y: 600, width: 140, height: 45)
resetButton.setTitle("Restart", for: .normal)
buttons.append(resetButton)

for button in buttons {
button.backgroundColor = themeDarkBrown
button.tintColor = UIColor.white
button.layer.cornerRadius = 8
button.clipsToBounds = true
view.addSubview(button)
}

winLabel.text = "You\nWin\n👑"
winLabel.numberOfLines = 0
winLabel.textAlignment = .center
winLabel.font = UIFont.boldSystemFont(ofSize: 48)
winLabel.backgroundColor = UIColor(white: 1, alpha: 0.92)
winLabel.layer.cornerRadius = 40
winLabel.clipsToBounds = true
winLabel.frame = CGRect(x: 51, y: 228, width: 288, height: 288)
view.addSubview(winLabel)


let titleImageView = UIImageView()
titleImageView.image = UIImage(named: "title")
titleImageView.frame = CGRect(x: 48.75, y: 12, width: 292.5, height: 166.5)
view.addSubview(titleImageView)

let chessView = UIView()
let chessImage = UIImageView()
chessImage.image = UIImage(named: "chess")
chessImage.frame = CGRect(x: 0, y: 0, width: 195, height: 111)
chessView.frame = chessImage.frame
chessView.backgroundColor = themeDarkBrown
chessView.frame = CGRect(x: 102.5, y: 670, width: 195, height: 111)
chessView.addSubview(chessImage)
view.addSubview(chessView)
}

func lookUpAnswers() {

var x = [1,2,3,4,5,6,7,8]
var y = [1,2,3,4,5,6,7,8]
var sums:[Int] = []
var differences:[Int] = []
var numbering = 1
var a = 0
var b = 0
var loopTimes = 0



while !x.isEmpty {

if x.count > 1 {
a = 0
b = Int.random(in: 0..<y.count)
} else {
a = 0
b = 0
}

let sumCheck = x[a]+y[b]
let differenceCheck = x[a]-y[b]


if !sums.contains(sumCheck) && !differences.contains(differenceCheck) {
print("\(numbering).(\(x[a]),\(y[b]))")
i.append(x[a])
j.append(y[b])
x.remove(at: a)
y.remove(at: b)
a += 1
numbering += 1
sums.append(sumCheck)
differences.append(differenceCheck)
print(sums)
print(differences)

} else {
x = [1,2,3,4,5,6,7,8]
y = [1,2,3,4,5,6,7,8]
a = 0
numbering = 1
loopTimes += 1
i.removeAll()
j.removeAll()
sums.removeAll()
differences.removeAll()
print("loop",loopTimes)
}
}
}


func eightQueens() {
for row in 1...8 {
for column in 1...8 {
let button = UIButton(type: .system)
button.frame = CGRect(x: 3 + 48 * (row-1), y: 180 + 48 * (column-1), width:48 , height:48 )

if (row + column) % 2 == 0 {
button.backgroundColor = themeDarkBrown
} else {
button.backgroundColor = themLightBrown
}

let index = i.firstIndex(of: row)

if column == j[index!] {
button.setTitle("♛", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 48)
button.tintColor = UIColor.black
}

view.addSubview(button)
}
}
}


func makeCheckerboard() {

winLabel.isHidden = true
answerRow.removeAll()
answerColumn.removeAll()
var queenCount = 0
var moveCount = 0
var wrongStatus:Bool = false

for row in 1...8 {
for column in 1...8 {
let button = UIButton(type: .system)

button.addAction(UIAction(handler: { [self, weak button] _ in

if button?.currentTitle == nil {
button?.setTitle("♛", for: .normal)
button?.tintColor = UIColor.black
button?.titleLabel?.font = UIFont.systemFont(ofSize: 48)
queenCount += 1
moveCount += 1
print("queen",queenCount,"move",moveCount)

for i in 0..<answerRow.count {
if (row + column == answerRow[i]+answerColumn[i]) || (row - column == answerRow[i]-answerColumn[i]) || (row == answerRow[i]) || (column == answerColumn[i]) {
wrongStatus = true
}
}
if wrongStatus == true {
button?.tintColor = UIColor.red
queenCount -= 1
print("wrong move: queen",queenCount,"move",moveCount)
wrongStatus = false
}
answerRow.append(row)
answerColumn.append(column)
print("answerRow",answerRow)
print("answerColumn",answerColumn)


} else {
button?.setTitle(nil, for: .normal)

print("removed before: queen",queenCount,"move",moveCount)
if queenCount == moveCount {
queenCount -= 1
moveCount -= 1
} else {
moveCount -= 1
}
print("removed after: queen",queenCount,"move",moveCount)

if answerRow.contains(row) {
let index = answerRow.lastIndex(where: {$0 == row})
print("index to be removed",index)
answerRow.remove(at: index!)
print("removed answerRow",answerRow)
answerColumn.remove(at: index!)
print("removed answerColumn",answerColumn)
}

}

if queenCount == 8 {
view.addSubview(winLabel)
winLabel.isHidden = false
}

}), for: .touchUpInside)

button.frame = CGRect(x: 3 + 48 * (row-1), y: 180 + 48 * (column-1), width:48 , height:48 )

if (row + column) % 2 == 0 {
button.backgroundColor = themeDarkBrown
} else {
button.backgroundColor = themLightBrown
}

view.addSubview(button)
}
}

}
}

--

--