Skip to content

Instantly share code, notes, and snippets.

@kmckinley
Last active August 29, 2015 14:27
Show Gist options
  • Save kmckinley/33dc47f4e682b4e375aa to your computer and use it in GitHub Desktop.
Save kmckinley/33dc47f4e682b4e375aa to your computer and use it in GitHub Desktop.
/*
Unlimited Tic Tac Toe!
This algorithm determines if a game has a winner, in progress, or finished with a tied game.
There is no limit to the size of the game as long as the size is a perfect square root e.g., 4,9,16,25,ect
*/
class TicTacToe {
let wins = "win!!"
func start(sequence: String) -> String {
let charSize = count(sequence)
let testGameSize = Double(charSize)%sqrt(Double(charSize))
if testGameSize != 0 {
return "The size of the sequence needs to be a perfect square bro!!!"
}else {
return gameStatus(sequence)
}
}
func gameStatus(sequence: String) -> String {
// convert string into a char array
let charArray = Array(sequence)
// Determine the divisor inorder to create an algorithm that doesn't rely on
// an explicilty defined data structure.
let divisor = Int(sqrt(Double(charArray.count)))
for var i=0; i<charArray.count; ++i {
let char = charArray[i]
if i < divisor {
// First check first row, all columns and all diagonals
if i == 0 {
// Top row, first column, first diagonal
if rowTest(charArray, index: i, divisor: divisor) {
return "\(char) \(wins)"
}
if columnTest(charArray, index: i, divisor: divisor) {
return "\(char) \(wins)"
}
if diagonalTest(charArray, index: i, conditionNum: charArray.count, increment: divisor+1) {
return "\(char) \(wins)"
}
}else if i == divisor-1 {
// Last column, second diagonal
if columnTest(charArray, index: i, divisor: divisor) {
return "\(char) \(wins)"
}
// I don't like how I came up with the conditionNum, but I couldn't think of anything else
// If I don't subtract 2 from the array count it will check the last position of the char
// array. This is not an issue when checking the other diagonal.
if diagonalTest(charArray, index: i, conditionNum: charArray.count-2, increment: divisor-1) {
return "\(char) \(wins)"
}
}else {
// All columns other than the first and last
if columnTest(charArray, index: i, divisor: divisor) {
return "\(char) \(wins)"
}
}
} else if ((i%divisor) == 0) {
// All remaining rows
if rowTest(charArray, index: i, divisor: divisor) {
return "\(char) \(wins)"
}
}
}
if sequence.rangeOfString("-") != nil {
return "Game still in progress"
}else {
return "Tie Game"
}
}
// Test the entire row based on the divisor and index position. Only call this method
// when you are at the first column of a row.
func rowTest(charArray: Array<Character>, index: Int, divisor: Int) -> Bool {
let char = charArray[index]
var match = true
for var j=index; j<divisor+index && match; ++j {
if char == "-" || char != charArray[j] {
match = false
}
}
return match
}
// Test the entire column based on the divisor and index position. Only call this method
// when you are at the first row of a column.
func columnTest(charArray: Array<Character>, index: Int, divisor: Int) -> Bool {
let char = charArray[index]
var match = true
for var j=index; j<charArray.count && match; j += divisor {
if char == "-" || char != charArray[j] {
match = false
}
}
return match
}
// Test diagonal line. Only called when at first column of first row, or last column of first row.
// conditionNum is used to ensure that we don't loop through the last position of the charArray when
// we are testing the diagonal from the last column first row
// increment is different depending on which diagonal we are testing.
func diagonalTest(charArray: Array<Character>, index: Int, conditionNum: Int, increment: Int) -> Bool {
let char = charArray[index]
var match = true
for var j=index; j<=conditionNum && match; j += increment {
if char == "-" || char != charArray[j] {
match = false
}
}
return match
}
}
// Lets see it in action
let ttt = TicTacToe()
ttt.start("xxx-o-o-o") // "x wins!!"
ttt.start("xo-ox-o-x") // "x wins!!"
ttt.start("x-o-o-oxx") // "o wins!!"
ttt.start("x-xooo-x-") // "o wins!!"
ttt.start("ooxxxooxx") // "Tie Game"
ttt.start("oxooxoxox") // "Tie Game"
ttt.start("---------") // "Game still in progress"
ttt.start("xox---oxo") // "Game still in progress"
ttt.start("----------------") // "Game still in progress"
ttt.start("x----x----x----x") // "x wins!!"
ttt.start("-o---o---o---o--") // "o wins!!"
ttt.start("---------------------------------------------------------------------------------"); // "Game still in progress"
ttt.start("------x--------x--------x--------x--------x--------x--------x--------x--------x--"); // "x wins!!"
ttt.start("--------x-------x-------x-------x-------x-------x-------x-------x-------x--------"); // "x wins!!"
ttt.start("------------------------------------------------------------------------ooooooooo"); // "o wins!!"
ttt.start("---------------------------------------------------------------ooooooooo---------"); // "o wins!!"
@paulevans
Copy link

Haha! You allowed for extreme Terranaova tic-tac-toe in yours by generalising the algorithm for bigger grids :)
I don't know anything much about Swift apart from ๐Ÿฑ ๐Ÿถ - but I get where you are going with this.
If this were for a client you had an idea might change their mind from the 9 grid spec.
"The size of the sequence needs to be a perfect square bro!!!" ๐Ÿ˜†

My answer had an x win bias on extreme tic-tac-toes, I think yours has a top row bias. I think both are fair considering that is unspecified ๐Ÿ˜‰

e.g. "oooxxx---" I think o would win on yours and x would win on mine.

@kmckinley
Copy link
Author

I think @supernova-at will appreciate the usage of bro.

Yeah my bias top row. To get around a bias I would continue validating the string and check for things like two winners and if 'x' or 'o' have the correct amount of turns. I would add a new state for this called "Invalid game, learn how to play bro"

@paulevans
Copy link

@kmckinley yeah... I put in comments directed at @supernova-at myself ๐Ÿ˜„

Invalid games weren't specified and would complicate things a bit so I left it out too. As you say I think correct number of turns plus checking for two winners would do it.

@aterranova-bv
Copy link

@kmckinley - I did get a good chuckle at it ๐Ÿ‘

I had to stop and think about the loop code for a second to reason about it in my head, so perhaps my only comment would be to add more instructive comments. I'm coming at it from the perspective of you want to make it as easy as possible for someone who has never seen your code before to come in, find, and fix a potential bug.

I like the extrapolation out to any grid size!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment