parent
e424772020
commit
2d03fc4006
@ -0,0 +1,345 @@ |
|||||||
|
import t from "tap"; |
||||||
|
|
||||||
|
import { Board } from "./board.ts"; |
||||||
|
import { CurrentOutcome, SquareState } from "./datatypes.ts"; |
||||||
|
import { getBoardOutcome, getSequenceOutcome } from "./tictactoe-rules.ts"; |
||||||
|
|
||||||
|
void t.test("getSequenceOutcome", async (t) => { |
||||||
|
void t.test("empty sequence", async (t) => { |
||||||
|
t.equal(getSequenceOutcome([]), null); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("all sequences of length 1", async (t) => { |
||||||
|
t.equal(getSequenceOutcome([SquareState.Unoccupied]), null); |
||||||
|
t.equal(getSequenceOutcome([SquareState.X]), CurrentOutcome.WinX); |
||||||
|
t.equal(getSequenceOutcome([SquareState.O]), CurrentOutcome.WinO); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("all sequences of length 2", async (t) => { |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([SquareState.Unoccupied, SquareState.Unoccupied]), |
||||||
|
null, |
||||||
|
); |
||||||
|
t.equal(getSequenceOutcome([SquareState.Unoccupied, SquareState.X]), null); |
||||||
|
t.equal(getSequenceOutcome([SquareState.Unoccupied, SquareState.O]), null); |
||||||
|
t.equal(getSequenceOutcome([SquareState.X, SquareState.Unoccupied]), null); |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([SquareState.X, SquareState.X]), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal(getSequenceOutcome([SquareState.X, SquareState.O]), null); |
||||||
|
t.equal(getSequenceOutcome([SquareState.O, SquareState.Unoccupied]), null); |
||||||
|
t.equal(getSequenceOutcome([SquareState.O, SquareState.X]), null); |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([SquareState.O, SquareState.O]), |
||||||
|
CurrentOutcome.WinO, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("sequences of length 7", async (t) => { |
||||||
|
void t.test("all X except for the first element", async (t) => { |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([ |
||||||
|
SquareState.Unoccupied, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
]), |
||||||
|
null, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([ |
||||||
|
SquareState.O, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
]), |
||||||
|
null, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("all X except for the last element", async (t) => { |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([ |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.Unoccupied, |
||||||
|
]), |
||||||
|
null, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([ |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.O, |
||||||
|
]), |
||||||
|
null, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("all X except for the middle element", async (t) => { |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([ |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.Unoccupied, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
]), |
||||||
|
null, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([ |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.O, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
]), |
||||||
|
null, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
t.equal( |
||||||
|
getSequenceOutcome([ |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
SquareState.X, |
||||||
|
]), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("getBoardOutcome", async (t) => { |
||||||
|
void t.test("1x1 boards", async (t) => { |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("_")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal(getBoardOutcome(Board.fromSerialized("X")), CurrentOutcome.Draw); |
||||||
|
t.equal(getBoardOutcome(Board.fromSerialized("O")), CurrentOutcome.Draw); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("2x2 boards", async (t) => { |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("__|__")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XX|X_")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XX|XX")), |
||||||
|
CurrentOutcome.Draw, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("OO|OO")), |
||||||
|
CurrentOutcome.Draw, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XO|OX")), |
||||||
|
CurrentOutcome.Draw, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("3x3 boards", async (t) => { |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("___|___|___")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("___|_X_|___")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
|
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XX_|___|___")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("___|___|_XX")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("X__|X__|___")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("___|__X|__X")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("X__|_X_|___")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("___|_X_|__X")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("__X|_X_|___")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("___|_X_|X__")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
|
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XXX|O_O|O_O")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("O_O|O_O|XXX")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XOO|X__|XOO")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("OOX|__X|OOX")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XOO|OXO|OOX")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("OOX|OXO|XOO")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
|
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XXO|XOX|OXX")), |
||||||
|
CurrentOutcome.WinO, |
||||||
|
); |
||||||
|
|
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XOX|XXO|OXO")), |
||||||
|
CurrentOutcome.Draw, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XOX|XXO|OX_")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("5x5 boards", async (t) => { |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_____")), |
||||||
|
CurrentOutcome.Undecided, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("XXX__|_____|_____|_____|_____")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("X____|X____|X____|_____|_____")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("X____|_X___|__X__|_____|_____")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("__X__|_X___|X____|_____|_____")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
|
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|__XXX")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("_____|_____|____X|____X|____X")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("_____|_____|__X__|___X_|____X")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome(Board.fromSerialized("_____|_____|____X|___X_|__X__")), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("6x5 boards", async (t) => { |
||||||
|
t.equal( |
||||||
|
getBoardOutcome( |
||||||
|
Board.fromSerialized("_____|_____|_____|_____|_____|__XXX"), |
||||||
|
), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome( |
||||||
|
Board.fromSerialized("_____|_____|_____|____X|____X|____X"), |
||||||
|
), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome( |
||||||
|
Board.fromSerialized("_____|_____|_____|__X__|___X_|____X"), |
||||||
|
), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome( |
||||||
|
Board.fromSerialized("_____|_____|_____|____X|___X_|__X__"), |
||||||
|
), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
void t.test("5x6 boards", async (t) => { |
||||||
|
t.equal( |
||||||
|
getBoardOutcome( |
||||||
|
Board.fromSerialized("______|______|______|______|___XXX"), |
||||||
|
), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome( |
||||||
|
Board.fromSerialized("______|______|_____X|_____X|_____X"), |
||||||
|
), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome( |
||||||
|
Board.fromSerialized("______|______|___X__|____X_|_____X"), |
||||||
|
), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
t.equal( |
||||||
|
getBoardOutcome( |
||||||
|
Board.fromSerialized("______|______|_____X|____X_|___X__"), |
||||||
|
), |
||||||
|
CurrentOutcome.WinX, |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,89 @@ |
|||||||
|
import { CurrentOutcome, GameRules, SquareState } from "./datatypes.ts"; |
||||||
|
|
||||||
|
export const getSequenceOutcome = (sequence: SquareState[]) => { |
||||||
|
for (let i = 1; i < sequence.length; i++) { |
||||||
|
if (sequence[i - 1] != sequence[i]) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
switch (sequence[0]) { |
||||||
|
case SquareState.X: |
||||||
|
return CurrentOutcome.WinX; |
||||||
|
case SquareState.O: |
||||||
|
return CurrentOutcome.WinO; |
||||||
|
default: |
||||||
|
return null; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
export const getBoardOutcome: GameRules["getBoardOutcome"] = (board) => { |
||||||
|
for (let row = 0; board.hasRow(row); row++) { |
||||||
|
for (let column = 2; board.hasSquare(row, column); column++) { |
||||||
|
const tripleLeft = [ |
||||||
|
board.get(row, column - 2), |
||||||
|
board.get(row, column - 1), |
||||||
|
board.get(row, column - 0), |
||||||
|
]; |
||||||
|
|
||||||
|
const outcome = getSequenceOutcome(tripleLeft); |
||||||
|
if (outcome) { |
||||||
|
return outcome; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (let row = 2; board.hasRow(row); row++) { |
||||||
|
for (let column = 0; board.hasSquare(row, column); column++) { |
||||||
|
const tripleUp = [ |
||||||
|
board.get(row - 2, column), |
||||||
|
board.get(row - 1, column), |
||||||
|
board.get(row - 0, column), |
||||||
|
]; |
||||||
|
|
||||||
|
const outcome = getSequenceOutcome(tripleUp); |
||||||
|
if (outcome) { |
||||||
|
return outcome; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (let row = 2; board.hasRow(row); row++) { |
||||||
|
for (let column = 2; board.hasSquare(row, column); column++) { |
||||||
|
{ |
||||||
|
const tripleUpLeft = [ |
||||||
|
board.get(row - 2, column - 2), |
||||||
|
board.get(row - 1, column - 1), |
||||||
|
board.get(row - 0, column - 0), |
||||||
|
]; |
||||||
|
const upLeftOutcome = getSequenceOutcome(tripleUpLeft); |
||||||
|
if (upLeftOutcome) { |
||||||
|
return upLeftOutcome; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
const tripleCross = [ |
||||||
|
board.get(row - 0, column - 2), |
||||||
|
board.get(row - 1, column - 1), |
||||||
|
board.get(row - 2, column - 0), |
||||||
|
]; |
||||||
|
|
||||||
|
const crossOutcome = getSequenceOutcome(tripleCross); |
||||||
|
if (crossOutcome) { |
||||||
|
return crossOutcome; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (let row = 0; board.hasRow(row); row++) { |
||||||
|
for (let column = 0; board.hasSquare(row, column); column++) { |
||||||
|
if (board.get(row, column) === SquareState.Unoccupied) { |
||||||
|
return CurrentOutcome.Undecided; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return CurrentOutcome.Draw; |
||||||
|
}; |
Loading…
Reference in new issue