Tic tac toe finding win? - swift

I am making a tic tac toe game in Swift for 2 players. Now I know I can find a winner by making outlets for the 9 buttons and then writing for all the 3 in row cases, but I was wondering if there is a better way to do this? And is there a better way to write my switch statement?
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
#IBOutlet weak var button3: UIButton!
#IBOutlet weak var button4: UIButton!
#IBOutlet weak var button5: UIButton!
#IBOutlet weak var button6: UIButton!
#IBOutlet weak var button7: UIButton!
#IBOutlet weak var button8: UIButton!
#IBOutlet weak var button9: UIButton!
var player = 1
#IBAction func playersTurn(sender: UIButton){
switch player{
case 1: sender.setTitle("X", forState: UIControlState.Normal); player = 2;break;
case 2: sender.setTitle("O", forState: UIControlState.Normal); player = 1;break;
default:exit(0); break
}
}

I would set the tag property on your buttons using values 0-9 in order from top to bottom, left to right, like so:
[ 0 ][ 1 ][ 2 ]
[ 3 ][ 4 ][ 5 ]
[ 6 ][ 7 ][ 8 ]
Then create a model to represent each square and give it a corresponding index value, as well as a reference to the player who owns it. Then you can handle you game logic with those models and update the UI by getting references to your buttons with viewForTag(_:). Here's my version:
class Square {
var owningPlayer: Player?
let index: Int
init( index: Int ) {
self.index = index
}
}
class Player {
let symbol: String
init( symbol: String ) {
self.symbol = symbol
}
}
class ViewController: UIViewController {
var squares = [Square]()
var players = [Player]()
var currentPlayer: Player?
override func viewDidLoad() {
super.viewDidLoad()
self.players = [ Player(symbol: "X"), Player(symbol: "O") ]
self.currentPlayer = self.players[0]
// Create squares
for i in 0..<9 {
self.squares.append( Square(index: i) )
}
}
#IBAction func buttonPressed( button: UIButton ) {
guard let player = self.currentPlayer else {
fatalError( "Don't have a current player!" )
}
if let square = self.squares.filter({ $0.index == button.tag }).first {
// Update model
square.owningPlayer = player
// Update UI
button.setTitle( player.symbol, forState: .Normal )
}
if self.checkWin() {
print( "\(player.symbol) wins!" )
}
else {
self.currentPlayer = self.players.filter({ $0 !== player }).first
}
}
func checkWin() -> Bool {
guard let player = self.currentPlayer else {
fatalError( "Don't have a current player!" )
}
let winningSequences = [
// Horizontal rows
[ 0, 1, 2 ],
[ 3, 4, 5 ],
[ 6, 7, 8 ],
// Diagonals
[ 0, 4, 8 ],
[ 2, 4, 6 ],
// Vertical rows
[ 0, 3, 6 ],
[ 1, 4, 7 ],
[ 2, 5, 8 ],
]
// Get indexes owned by this player
let selectedIndexes = self.squares.filter({ $0.owningPlayer === player }).map { $0.index }
// Change the sequence arrays into sets for accurate comparison using `contains(_:)`
if winningSequences.map({ Set<Int>($0) }).contains( Set<Int>(selectedIndexes) ) {
return true
}
return false
}
}

Although #Patrick Lynch's answer is excellent I found that it didn't match a win should the game go beyond 3 moves.
This is because he is only checking if any of the winningSequences contains a current move sequence (selectedIndexes). As the winningSequences map only has a max of 3 items it'll never contain a set of 4 moves.
To resolve this I changed contains to subset. i.e is a winningSequence (0, 1, 2) a subset of the selectedIndexes (0, 5, 1, 2)
Replace
if winningSequences.map({ Set<Int>($0) }).contains( Set<Int>(selectedIndexes) ) {
return true
}
with
for seq in winningSequences.map( { Set<Int>($0)}) {
if seq.isSubset(of: selectedIndexes) {
return true
}
}
I'm still new to swift so if I'm wrong or anyone has a better idea then I'd be happy to hear it.

Related

Calculate a price with 3 steppers

I have created an application that displays the prices of bus tickets.
I use 3 steppers to increment the prices. There are the teenagers prices at $0.75, the seniors prices at $2.00, and the adults prices at $3.00. The goal is to be able to add and calculate teen, senior, and adult prices in the same label.
My current code only allows me to calculate for one category at a time. But I can't combine and calculate all 3 price categories in the same label.
Here is my code:
#IBOutlet weak var seniorsTicketsStepper: UIStepper!
#IBOutlet weak var adultsTicketsStepper: UIStepper!
#IBOutlet weak var teenagersTicketsStepper: UIStepper!
#IBOutlet weak var numOfTickets: UILabel!
#IBOutlet weak var ticketsPrices: UILabel!
var totalPrice: Double = 0.00
#IBAction func seniorsTicketsTapped(_ sender: UIStepper) {
updateTicketsCount(stepper: sender)
updateTicketsPrices(stepper: sender)
}
#IBAction func adultsTicketsTapped(_ sender: UIStepper) {
updateTicketsCount(stepper: sender)
updateTicketsPrices(stepper: sender)
}
#IBAction func teenagersTicketsTapped(_ sender: UIStepper) {
updateTicketsCount(stepper: sender)
updateTicketsPrices(stepper: sender)
}
func updateTicketsCount(stepper: UIStepper) {
stepper.maximumValue = 10
let summe: Int = Int(seniorsTicketsStepper.value + adultsTicketsStepper.value + teenagersTicketsStepper.value)
if stepper.value < 1 {
numOfTickets.text = summe.description
validateButton.isEnabled = false
validateButton.backgroundColor = UIColor.init(red: 170/250, green: 170/250, blue: 170/250, alpha: 1)
} else {
numOfTickets.text = summe.description
validateButton.isEnabled = true
validateButton.backgroundColor = UIColor.init(red: 0/250, green: 194/250, blue: 166/250, alpha: 1)
}
}
func updateTicketsPrices(stepper: UIStepper) {
var price = totalPrice
if teenagersTicketsStepper.value != 0 {
price += 0.75
}
if seniorsTicketsStepper.value != 0 {
price += 2.00
}
if adultsTicketsStepper.value != 0 {
price += 3.00
}
price = price * stepper.value
ticketsPrices.text = "$ \(price.description)"
}
But I can't combine and calculate all 3 price categories in the same label.
Because you don't have three price categories. In fact, you have no prices at all. You have only interface objects. You've completely omitted to give your view controller a place to store data.
Basically, you're storing your data in the interface, which is totally wrong. The interface is for displaying data. Storing data is something your code (meaning your objects) must do.
Take some time and think about what your program actually does — not what the user does or sees, but what the program's reasoning would be even if there were no user interface at all. What information does it manipulate? Once you've got instance properties for all the information you're trying to keep track of, the answer to your problem will fall into your lap.
A further hint: code like this is completely wrong:
if teenagersTicketsStepper.value != 0 {
price += 0.75
}
if seniorsTicketsStepper.value != 0 {
price += 2.00
}
if adultsTicketsStepper.value != 0 {
price += 3.00
}
Once you've got your categories, perhaps even expressed as an object type, that sort of "choice" will happen without magic numbers being embedded in tedious logic.

Swift refactoring assistance

I have a meter laid out on a storyboard that is comprised of ten segments. There are an additional ten segments on top of the original segments that are colored to simulate a lighted segment - these are outlets. (See image below.) Currently I am using a switch statement to hide and unhide each outlet/segment based on a constantly varying input level. But, as you can see in the code below, it's not pretty. I keep reading that polymorphism is often the way to improve a switch statement, but I can't see how that would help here.
switch input {
case 0...9:
seg1.hidden = false
seg2.hidden = true
seg3.hidden = true
seg4.hidden = true
seg5.hidden = true
seg6.hidden = true
seg7.hidden = true
seg8.hidden = true
seg9.hidden = true
seg10.hidden = true
case 10...19:
seg1.hidden = false
seg2.hidden = false
seg3.hidden = true
seg4.hidden = true
seg5.hidden = true
seg6.hidden = true
seg7.hidden = true
seg8.hidden = true
seg9.hidden = true
seg10.hidden = true
...
and on and on for eight more levels.
//You can have these segments in an array like this-
let segments = [seg1, seg2, seg3, seg4, seg5,seg6, seg7, seg8, seg9, seg10]
// Function that will setup the segments based on input
func setUpSegmentsForInputValue(segments:[UIView], value:Int) {
for (index, segment) in segments.enumerate() {
segment.hidden = (value/10) != index
}
}
// Call the function with whatever input values
setUpSegmentsForInputValue(segments: segments, value: someValue)
You could add all of your outlets to an array. Map the input to an "index range" and then decide in a for loop if outlet is in "index range". Then set hidden property accordingly.
#IBOutlet weak var seg1 = UIView()
#IBOutlet weak var seg2 = UIView()
#IBOutlet weak var seg3 = UIView()
#IBOutlet weak var seg4 = UIView()
...
let segs = [seg1, seg2, seg3, seg4]
let input = 19
let range = 0 ... input / 10
for (index, seg) in segs.enumerate() {
if range ~= index {
seg.hidden = false
}else {
seg.hidden = true
}
}
Alternative (instead of the for loop):
#IBOutlet weak var seg1 = UIView()
#IBOutlet weak var seg2 = UIView()
#IBOutlet weak var seg3 = UIView()
#IBOutlet weak var seg4 = UIView()
...
let segs = [seg1, seg2, seg3, seg4]
let input = 19
let range = 0 ... input / 10
segs.enumerate().forEach { index, seg in
seg.hidden = !(range ~= index)
}
Hope this helps.

When using < operator swift goes over specified number before exiting

I am currently learning swift/iOS programing. When I use a < operator and do something like: x < y, x will go past y before exiting. Is there a way around this so it stops as it hits y rather than going over?
Any and all help is greatly appreciated!
Thanks!
So when I press my AddBtnPressed button it adds firstNum and secondNum storing it in sumNum. Say firstNum = 995, secondNum = 15, when I hit my button it will hit 1010 instead of exiting and then if I hit my button again after that it will then exit after its already gone over.
Hope this makes sense!
The user inputs a number hits play which takes you to a second screen that says "press add to add" you press the Add button and it reveals an equation of 0 + (user input number) = x. and every time you press the Add button it adds the user number to the previous sum until it hits 999 then its supposed to take you back to the main screen.
import UIKit
class ViewController: UIViewController {
var firstNum = 0
var secondNum = 0
var sumNum = 0
var maxNum = 900
// First Screen
#IBOutlet weak var logo: UIImageView!
#IBOutlet weak var userNum: UITextField!
#IBOutlet weak var play: UIButton!
// Second Screen
#IBOutlet weak var pressAddToAdd: UILabel!
#IBOutlet weak var AddBtn: UIButton!
// Thrird Screen
#IBOutlet weak var ContSum: UILabel!
#IBOutlet weak var addUserNum: UILabel!
#IBOutlet weak var sum: UILabel!
#IBOutlet weak var mathSymbols: UILabel!
#IBAction func playBtnPressed(sender: UIButton) {
if userNum.text != nil && userNum.text != "" {
userNum.hidden = true
logo.hidden = true
play.hidden = true
pressAddToAdd.hidden = false
AddBtn.hidden = false
}
}
#IBAction func AddBtnPressed(sender: UIButton) {
if sumNum <= maxNum {
// Hide 2nd screen
pressAddToAdd.hidden = true
// Reveal 3rd screen
ContSum.hidden = false
addUserNum.hidden = false
sum.hidden = false
mathSymbols.hidden = false
// Variables
firstNum = sumNum
secondNum = Int (userNum.text!)!
sumNum = firstNum + secondNum
// Imput Variables into textFields
addUserNum.text = "\(secondNum)"
sum.text = "\(sumNum)"
ContSum.text = "\(sumNum - secondNum)"
} else {
// Restart
firstNum = 0
secondNum = 0
sumNum = 0
ContSum.hidden = true
addUserNum.hidden = true
sum.hidden = true
mathSymbols.hidden = true
AddBtn.hidden = true
userNum.hidden = false
logo.hidden = false
play.hidden = false
}
}
Evaluation your total outside of your conditional
sumNum += Int(userNum.text!)!
if sumNum <= 999 {
...
}
operator < is nothing but less than. less than can be true or false. expression x < y is evaluated as value of type Bool
// "do something like: x < y"
2 < 3 // true
"a" < "b" // true
var x = 0
var y = 1
x++ < y // true
print(x, y) // 1 1
--x < y // true
print(x, y) // 0 1

Check text field Live

I have found this answer How to check text field input at real time?
This is what I am looking for. However I am having trouble actually implementing this code. Also my current geographical location makes googling almost impossible.
I want to be able to change the background color of the next text field if the correct number is entered into the previous text field. textfieldTwo background color will change to green if the correct value is entered in textFieldOne. If the value is incorrect then nothing will happen. Please help me out. I have two text fields called textFieldOne and textFieldTwo and nothing else in the code.
Just pop this in your main view controller in an empty project (try using iphone 6 on the simulator)
import UIKit
class ViewController: UIViewController {
var txtField:UITextField!
var txtFieldTwo:UITextField!
var rightNumber = 10
override func viewDidLoad() {
super.viewDidLoad()
//txtFieldOne
var txtField = UITextField()
txtField.frame = CGRectMake(100, 100, 200, 40)
txtField.borderStyle = UITextBorderStyle.None
txtField.backgroundColor = UIColor.blueColor()
txtField.layer.cornerRadius = 5
self.view.addSubview(txtField)
//txtFieldTwo
var txtFieldTwo = UITextField()
txtFieldTwo.frame = CGRectMake(100, 150, 200, 40)
txtFieldTwo.borderStyle = UITextBorderStyle.None
txtFieldTwo.backgroundColor = UIColor.blueColor()
txtFieldTwo.layer.cornerRadius = 5
self.view.addSubview(txtFieldTwo)
txtField.addTarget(self, action: "checkForRightNumber", forControlEvents: UIControlEvents.AllEditingEvents)
self.txtField = txtField
self.txtFieldTwo = txtFieldTwo
}
func checkForRightNumber() {
let number:Int? = self.txtField.text.toInt()
if number == rightNumber {
self.txtFieldTwo.backgroundColor = UIColor.greenColor()
} else {
self.txtFieldTwo.backgroundColor = UIColor.blueColor()
}
}
}
EDIT: Adding a version with IBOutlets and IBActions
Note that in this example the IBAction is connected to txtFieldOne on Sent Events / Editing Changed
Also, make sure your Text Fields border colors are set to None. In the storyboard, the way to do this is to choose the left most option with the dashed border around it. That's so you can color the backgrounds. You can use layer.cornerRadius to set the roundness of the border's edges.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var txtField: UITextField!
#IBOutlet weak var txtFieldTwo: UITextField!
var rightNumber = 10
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func checkForRightNumber(sender: AnyObject) {
let number:Int? = self.txtField.text.toInt()
if number == rightNumber {
self.txtFieldTwo.backgroundColor = UIColor.greenColor()
} else {
self.txtFieldTwo.backgroundColor = UIColor.blueColor()
}
}
}

Weird NSButton creation

I have a simple AppDelegate like this:
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var field: NSView!
let button = NSButton()
let buttons = [NSButton(), NSButton(), NSButton()]
func applicationDidFinishLaunching(aNotification: NSNotification) {
button.frame = NSMakeRect(30, 60, 80, 25)
button.title = "ok"
field.addSubview(button)
for index in 0...2 {
let buttonI = buttons[index]
buttonI.frame = NSMakeRect(30+CGFloat(index)*90, 30, 80, 25)
buttonI.title = "not ok:\(index)"
field.addSubview(buttonI)
}
}
}
(You can test the above by connecting field with a custom view inside the content view of the window).
When I run this I get the following button appearance:
Why is it that the single button looks ok while those from the array are not?
You just need to set the bezelStyle:
button.bezelStyle = .RoundedBezelStyle
buttonI.bezelStyle = .RoundedBezelStyle