tictactoe groovy
Post on 22-Apr-2015
16.851 Views
Preview:
DESCRIPTION
TRANSCRIPT
© A
SE
RT
2006-2
013
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!
Topics
Introduction
• Dynamic solution
• Options for increasing type safety
• Groovy challenge solution
• Other language implementations
• Interlude: solving tic tac toe
• Going beyond the challenge
© A
SE
RT
2006-2
013
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
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
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
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
…DSL example...
© A
SE
RT
2006-2
012
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
…DSL example...
© A
SE
RT
2006-2
012
please show the square_root of 100
please(show).the(square_root).of(100)
Inspiration for this example came from …
...DSL example
© A
SE
RT
2006-2
012
// 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
Topics
• Introduction
Dynamic solution
• Options for increasing type safety
• Groovy challenge solution
• Other language implementations
• Interlude: solving tic tac toe
• Going beyond the challenge
© A
SE
RT
2006-2
013
Show me the code
DynamicTTT.groovy
Topics
• Introduction
• Dynamic solution
Options for increasing type safety
• Groovy challenge solution
• Other language implementations
• Interlude: solving tic tac toe
• Going beyond the challenge
© A
SE
RT
2006-2
013
Java Typing limitations
long freezingC = 0 // 0 °C long boilingF = 212 // 212 °F long delta = boilingF - freezingC long heavy = 100 // 100 kg
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
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>]
Mars Orbiter (artists impression)
…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.
Show me the code
Type safe builder, phantom types, dependent types: Rocket, HList
© A
SE
RT
2006-2
012
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()
Topics
• Introduction
• Dynamic solution
• Options for increasing type safety
Groovy challenge solution
• Other language implementations
• Interlude: solving tic tac toe
• Going beyond the challenge
© A
SE
RT
2006-2
013
Show me the code
TicTacToe
Topics
• Introduction
• Dynamic solution
• Options for increasing type safety
• Groovy challenge solution
Other language implementations
• Interlude: solving tic tac toe
• Going beyond the challenge
© A
SE
RT
2006-2
013
Show me the code
Haskell, Scala
Topics
• Introduction
• Dynamic solution
• Options for increasing type safety
• Groovy challenge solution
• Other language implementations
Interlude: solving tic tac toe
• Going beyond the challenge
© A
SE
RT
2006-2
013
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
Interlude: Tic Tac Toe game tree
Source: http://en.wikipedia.org/wiki/Game_tree
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
Topics
• Introduction
• Dynamic solution
• Options for increasing type safety
• Groovy challenge solution
• Other language implementations
• Interlude: solving tic tac toe
Going beyond the challenge
© A
SE
RT
2006-2
013
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+
…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+
…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 } }
…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() + " ]]" } } }
…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.
Show me the code
More Information: Groovy in Action
top related