tictactoe groovy

18,095 views
18,004 views

Published on

Explores how to write a tic-tac-toe API that meets some interesting static typing constraints. Specifically, programs using the API may fail to compile, depending on the state of play in the game, such as trying to call move() with an already completed game board. The real theme of the presentation is not so much solving the tic-tac-toe problem but, rather, pushing static typing to its limits (and some might argue beyond its useful limits—you will have to judge for yourself).

2 Comments
8 Likes
Statistics
Notes
No Downloads
Views
Total views
18,095
On SlideShare
0
From Embeds
0
Number of Embeds
7
Actions
Shares
0
Downloads
34
Comments
2
Likes
8
Embeds 0
No embeds

No notes for slide

tictactoe groovy

  1. 1. ©ASERT2006-2013 Dr Paul King @paulk_asert Director, ASERT, Brisbane, Australia http:/slideshare.net/paulk_asert/tictactoe-groovy https://github.com/paulk-asert/tictactoe-groovy Coding TicTacToe: A coding style challenge! ** Coming soon!
  2. 2. Topics Introduction • Dynamic solution • Options for increasing type safety • Groovy challenge solution • Other language implementations • Interlude: solving tic tac toe • Going beyond the challenge ©ASERT2006-2013
  3. 3. Tic Tac Toe • Players take it in turn to place “marks” on the board • By convention, Player 1 uses “X” and starts first; Player 2 uses “O” • The game finishes once one of the players has three of their marks in a row • Also called noughts and crosses (and other names) with 4 x 4, 3D and other variants
  4. 4. Challenge: Tic Tac Toe API • move: given a board and position returns a board with the current player at that position – Can only be called on a board that is in-play; calling move on a game board that is finished is a compile-time type error • whoWon: given a finished board indicates if the game was a draw or which player won – Can only be called on a board that is finished; calling whoWon on a game board that is in-play is a compile-time type error • takeBack: takes either a finished board or a board in-play that has had at least one move and returns a board in-play – It is a compile-time type error when used on an empty board • playerAt: takes any board and position and returns the (possible) player at the position The main example for this talk is based on and inspired by: https://github.com/tonymorris/course-answers/blob/master/src/TicTacToe/TicTacToe.md
  5. 5. But what’s this talk really about? • Not really about solving/coding TicTacToe • Looking at the Dynamic <-> Static typing spectrum • Looking at the OO <-> functional style spectrum • Looking at the pros and cons of different programming styles
  6. 6. But what’s this talk really about? • We all have the same goal –Productively writing “bug-free” maintainable code that “solves” some real world users problem –Types are one trick in my toolkit but I also have tests, invariants –Numerous ways to benefit from types –Types are a burden on the programmer –When writing DSLs, certain scripts, certain test code, types may not justify the burden
  7. 7. …DSL example... ©ASERT2006-2012 class FluentApi { def action, what def the(what) { this.what = what; this } def of(arg) { action(what(arg)) } } show = { arg -> println arg } square_root = { Math.sqrt(it) } please = { new FluentApi(action: it) } please show the square_root of 100 // => 10.0 DSL usage
  8. 8. …DSL example... ©ASERT2006-2012 please show the square_root of 100 please(show).the(square_root).of(100) Inspiration for this example came from …
  9. 9. ...DSL example ©ASERT2006-2012 // Japanese DSL using GEP3 rules Object.metaClass.を = Object.metaClass.の = { clos -> clos(delegate) } まず = { it } 表示する = { println it } 平方根 = { Math.sqrt(it) } まず 100 の 平方根 を 表示する // First, show the square root of 100 // => 10.0 source: http://d.hatena.ne.jp/uehaj/20100919/1284906117 also: http://groovyconsole.appspot.com/edit/241001
  10. 10. Topics • Introduction Dynamic solution • Options for increasing type safety • Groovy challenge solution • Other language implementations • Interlude: solving tic tac toe • Going beyond the challenge ©ASERT2006-2013
  11. 11. Show me the code DynamicTTT.groovy
  12. 12. Topics • Introduction • Dynamic solution Options for increasing type safety • Groovy challenge solution • Other language implementations • Interlude: solving tic tac toe • Going beyond the challenge ©ASERT2006-2013
  13. 13. Java Typing limitations long freezingC = 0 // 0 °C long boilingF = 212 // 212 °F long delta = boilingF - freezingC long heavy = 100 // 100 kg
  14. 14. Using JScience @GrabResolver('http://maven.obiba.org/maven2') @Grab('org.jscience:jscience:4.3.1') import ... //@TypeChecked def main() { Amount<Temperature> freezingC = valueOf(0L, CELSIUS) def boilingF = valueOf(212L, FAHRENHEIT) printDifference(boilingF, freezingC) def heavy = valueOf(100L, KILO(GRAM)) printDifference(heavy, boilingF) } def <T> void printDifference(Amount<T> arg1, Amount<T> arg2) { println arg1 - arg2 } (180.00000000000006 ± 1.4E-14) °F javax.measure.converter.ConversionException: °F is not compatible with kg
  15. 15. Using JScience & @TypeChecked ... @TypeChecked def main() { Amount<Temperature> freezingC = valueOf(0L, CELSIUS) def boilingF = valueOf(212L, FAHRENHEIT) printDifference(boilingF, freezingC) def heavy = valueOf(100L, KILO(GRAM)) printDifference(heavy, boilingF) } def <T> void printDifference(Amount<T> arg1, Amount<T> arg2) { println arg1 - arg2 } [Static type checking] - Cannot call ConsoleScript46#printDifference(org.jscience.physics.amount.Amount <T>, org.jscience.physics.amount.Amount <T>) with arguments [org.jscience.physics.amount.Amount <javax.measure.quantity.Mass>, org.jscience.physics.amount.Amount <javax.measure.quantity.Temperature>]
  16. 16. Mars Orbiter (artists impression)
  17. 17. …Typing… import groovy.transform.TypeChecked import experimental.SprintfTypeCheckingVisitor @TypeChecked(visitor=SprintfTypeCheckingVisitor) void main() { sprintf('%s will turn %d on %tF', 'John', new Date(), 21) } [Static type checking] - Parameter types didn't match types expected from the format String: For placeholder 2 [%d] expected 'int' but was 'java.util.Date' For placeholder 3 [%tF] expected 'java.util.Date' but was 'int' sprintf has an Object varargs parameter, hence not normally amenable to further static checking but for constant Strings we can do better using a custom type checking plugin.
  18. 18. Show me the code Type safe builder, phantom types, dependent types: Rocket, HList
  19. 19. ©ASERT2006-2012 GContracts @Grab('org.gcontracts:gcontracts-core:1.2.10') import org.gcontracts.annotations.* @Invariant({ speed >= 0 }) class Rocket { @Requires({ !started }) @Ensures({ started }) def start() { /* ... */ } @Requires({ started }) @Ensures({ old.speed < speed }) def accelerate() { /* ... */ } /* ... */ } def r = new Rocket() r.start() r.accelerate()
  20. 20. Topics • Introduction • Dynamic solution • Options for increasing type safety Groovy challenge solution • Other language implementations • Interlude: solving tic tac toe • Going beyond the challenge ©ASERT2006-2013
  21. 21. Show me the code TicTacToe
  22. 22. Topics • Introduction • Dynamic solution • Options for increasing type safety • Groovy challenge solution Other language implementations • Interlude: solving tic tac toe • Going beyond the challenge ©ASERT2006-2013
  23. 23. Show me the code Haskell, Scala
  24. 24. Topics • Introduction • Dynamic solution • Options for increasing type safety • Groovy challenge solution • Other language implementations Interlude: solving tic tac toe • Going beyond the challenge ©ASERT2006-2013
  25. 25. Interlude: Solving Tic Tac Toe • Use a game tree to map out all possible moves • Solve the game tree using brute force or with various optimisation algorithms
  26. 26. Interlude: Tic Tac Toe game tree Source: http://en.wikipedia.org/wiki/Game_tree
  27. 27. Interlude: Tic Tac Toe game tree • Brute force • Minimax – Reduced lookahead, e.g. 2 layers/plies • Alpha beta pruning – Improved efficiency • Iterative deepening – Often used with alpha beta pruning • But for Tic Tac Toe only 100s of end states and 10s of thousands of paths to get there Source: http://en.wikipedia.org/wiki/Game_tree
  28. 28. Topics • Introduction • Dynamic solution • Options for increasing type safety • Groovy challenge solution • Other language implementations • Interlude: solving tic tac toe Going beyond the challenge ©ASERT2006-2013
  29. 29. Static Type Checking: Pluggable type system… import groovy.transform.TypeChecked import checker.BoringNameEliminator @TypeChecked(visitor=BoringNameEliminator) class Foo { def method1() { 1 } } import groovy.transform.TypeChecked import checker.BoringNameEliminator @TypeChecked(visitor=BoringNameEliminator) class Foo { def method() { 1 } } [Static type checking] - Your method name is boring, I cannot allow it! Groovy 2.1+
  30. 30. …Static Type Checking: Pluggable type system package checker import org.codehaus.groovy.ast.* import org.codehaus.groovy.control.SourceUnit import org.codehaus.groovy.transform.stc.* class BoringNameEliminator extends StaticTypeCheckingVisitor { BoringNameEliminator(SourceUnit source, ClassNode cn, TypeCheckerPluginFactory pluginFactory) { super(source, cn, pluginFactory) } final message = "Your method name is boring, I cannot allow it!" @Override void visitMethod(MethodNode node) { super.visitMethod(node) if ("method".equals(node.name) || "bar".equals(node.name)) { addStaticTypeError(message, node) } } } Groovy 2.1+
  31. 31. …Typing… import groovy.transform.TypeChecked import tictactoe.* Import static tictactoe.Position.* @TypeChecked(visitor=TicTacToeTypeVisitor) void main() { Board.empty().move(NW).move(C).move(W).move(SW).move(SE) } package tictactoe enum Position { NW, N, NE, W, C, E, SW, S, SE } class Board { static Board empty() { new Board() } Board move(Position p) { this } }
  32. 32. …Typing… package tictactoe import fj.* import fj.data.List import fj.data.Option import fj.data.TreeMap import static fj.P.p import static fj.data.List.list import static fj.data.List.nil import static fj.data.Option.none import static tictactoe.GameResult.Draw import static tictactoe.Player.Player1 import static tictactoe.Player.toSymbol import static tictactoe.Position.* final class Board extends BoardLike { private final List<P2<Position, Player>> moves private final TreeMap<Position, Player> m private static final Ord<Position> positionOrder = Ord.comparableOrd() private Board(final List<P2<Position, Player>> moves, final TreeMap<Position, Player> m) { this.moves = moves this.m = m } Player whoseTurn() { moves.head()._2().alternate() } boolean isEmpty() { false } List<Position> occupiedPositions() { m.keys() } int nmoves() { m.size() } Option<Player> playerAt(Position pos) { m.get(pos) } TakenBack takeBack() { moves.isEmpty() ? TakenBack.isEmpty() : TakenBack.isBoard(new Board(moves.tail(), m.delete(moves.head()._1()))) } @SuppressWarnings("unchecked") MoveResult moveTo(final Position pos) { final Player wt = whoseTurn() final Option<Player> j = m.get(pos) final TreeMap<Position, Player> mm = m.set(pos, wt) final Board bb = new Board(moves.cons(p(pos, wt)), mm) final List<P3<Position, Position, Position>> wins = list( p(NW, W, SW), p(N, C, S), p(NE, E, SE), p(NW, N, NE), p(W, C, E), p(SW, S, SE), p(NW, C, SE), p(SW, C, NE) ) final boolean isWin = wins.exists(new F<P3<Position, Position, Position>, Boolean>() { public Boolean f(final P3<Position, Position, Position> abc) { return list(abc._1(), abc._2(), abc._3()).mapMOption(mm.get()).exists(new F<List<Player>, Boolean>() { public Boolean f(final List<Player> ps) { return ps.allEqual(Equal.<Player> anyEqual()) } }) } }) final boolean isDraw = Position.positions().forall(new F<Position, Boolean>() { Boolean f(final Position pos2) { mm.contains(pos2) } }) j.isSome() ? MoveResult.positionAlreadyOccupied() : isWin ? MoveResult.gameOver(new FinishedBoard(bb, GameResult.win(wt))) : isDraw ? MoveResult.gameOver(new FinishedBoard(bb, Draw)) : MoveResult.keepPlaying(bb) } // … // … @Override String toString() { toString(new F2<Option<Player>, Position, Character>() { Character f(final Option<Player> pl, final Position _) { pl.option(p(' '), toSymbol) } }) + "n[ " + whoseTurn().toString() + " to move ]" } static final class EmptyBoard extends BoardLike { private EmptyBoard() {} @SuppressWarnings("unchecked") Board moveTo(final Position pos) { new Board(list(p(pos, Player1)), TreeMap.<Position, Player> empty(positionOrder).set(pos, Player1)) } private static final EmptyBoard e = new EmptyBoard() static EmptyBoard empty() { e } Player whoseTurn() { Player1 } boolean isEmpty() { true } List<Position> occupiedPositions() { nil() } int nmoves() { 0 } Option<Player> playerAt(Position pos) { none() } } static final class FinishedBoard extends BoardLike { private final Board b private final GameResult r private FinishedBoard(final Board b, final GameResult r) { this.b = b this.r = r } Board takeBack() { b.takeBack().fold( Bottom.<Board> error_("Broken invariant: board in-play with empty move list. This is a program bug"), Function.<Board> identity() ) } Player whoseTurn() { b.whoseTurn() } boolean isEmpty() { false } List<Position> occupiedPositions() { b.occupiedPositions() } int nmoves() { b.nmoves() } Option<Player> playerAt(final Position pos) { b.playerAt(pos) } GameResult result() { r } @Override String toString() { b.toString() + "n[[" + r.toString() + " ]]" } } }
  33. 33. …Typing import groovy.transform.TypeChecked import tictactoe.* Import static tictactoe.Position.* @TypeChecked(visitor=TicTacToeTypeVisitor) void main() { Board.empty().move(NW).move(C).move(W).move(SW).move(SE) } package tictactoe enum Position { NW, N, NE, W, C, E, SW, S, SE } [Static type checking] - Attempt to call suboptimal move SE not allowed [HINT: try NE] Custom type checker which fails compilation if programmer attempts to code a suboptimal solution. Where suboptimal means doesn’t agree with what is returned by a minimax, alpha-beta pruning, iterative deepening solving engine.
  34. 34. Show me the code
  35. 35. More Information: Groovy in Action

×