Browsable history of each game
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Sat, 19 Sep 2009 14:38:29 +0200
changeset 1008b899ed24f9f
parent 99 fed05535725f
child 101 c7244cdd143e
Browsable history of each game
freemarkerdor/pom.xml
freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/UI.java
freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/Bundle.properties
freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/Bundle_cs.properties
freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/game.fmt
webidor/src/main/java/cz/xelfi/quoridor/webidor/Game.java
webidor/src/main/java/cz/xelfi/quoridor/webidor/GameStatus.java
webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/BoardImage.java
webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Games.java
webidor/src/test/java/cz/xelfi/quoridor/webidor/GamesTest.java
webidor/src/test/java/cz/xelfi/quoridor/webidor/QuoridorTest.java
     1.1 --- a/freemarkerdor/pom.xml	Sat Sep 19 12:51:50 2009 +0200
     1.2 +++ b/freemarkerdor/pom.xml	Sat Sep 19 14:38:29 2009 +0200
     1.3 @@ -10,7 +10,7 @@
     1.4    <groupId>org.apidesign</groupId>
     1.5    <artifactId>freemarkerdor</artifactId>
     1.6    <name>freemarkerdor</name>
     1.7 -  <version>1.11</version>
     1.8 +  <version>1.15</version>
     1.9    <url>http://maven.apache.org</url>
    1.10    <dependencies>
    1.11      <dependency>
     2.1 --- a/freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/UI.java	Sat Sep 19 12:51:50 2009 +0200
     2.2 +++ b/freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/UI.java	Sat Sep 19 14:38:29 2009 +0200
     2.3 @@ -140,20 +140,34 @@
     2.4      }
     2.5  
     2.6      private Viewable board(String id) {
     2.7 -        return board(id, "");
     2.8 +        return board(id, "", null);
     2.9      }
    2.10      @GET
    2.11      @Path("games/{id}/")
    2.12      @Produces(MediaType.TEXT_HTML)
    2.13 -    public Viewable board(@PathParam("id") String id, @QueryParam("format") @DefaultValue("") String format) {
    2.14 -        return board(id, null, format);
    2.15 +    public Viewable board(
    2.16 +        @PathParam("id") String id,
    2.17 +        @QueryParam("format") @DefaultValue("") String format,
    2.18 +        @QueryParam("move") @DefaultValue("-1") String move
    2.19 +    ) {
    2.20 +        return board(id, null, format, move);
    2.21      }
    2.22 -    private Viewable board(@PathParam("id") String id, String msg, String format) {
    2.23 +    private Viewable board(@PathParam("id") String id, String msg, String format, String m) {
    2.24          Viewable v = checkLogin();
    2.25          if (v != null) {
    2.26              return v;
    2.27          }
    2.28 -        Document doc = base.path("games").path(id).accept(MediaType.TEXT_XML).get(Document.class);
    2.29 +        int move;
    2.30 +        try {
    2.31 +            move = Integer.parseInt(m);
    2.32 +        } catch (NumberFormatException ex) {
    2.33 +            move = -1;
    2.34 +        }
    2.35 +        WebResource url = base.path("games").path(id);
    2.36 +        if (move >= 0) {
    2.37 +            url = url.queryParam("move", "" + move);
    2.38 +        }
    2.39 +        Document doc = url.accept(MediaType.TEXT_XML).get(Document.class);
    2.40          Board b;
    2.41          try {
    2.42              b = Board.valueOf(doc.getElementsByTagName("board").item(0).getTextContent());
    2.43 @@ -196,7 +210,7 @@
    2.44                  return board(id);
    2.45              }
    2.46          } catch (UniformInterfaceException ex) {
    2.47 -            return board(id, "WRONG_MOVE");
    2.48 +            return board(id, "WRONG_MOVE", "-1");
    2.49          }
    2.50          return board(id);
    2.51      }
     3.1 --- a/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/Bundle.properties	Sat Sep 19 12:51:50 2009 +0200
     3.2 +++ b/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/Bundle.properties	Sat Sep 19 14:38:29 2009 +0200
     3.3 @@ -24,8 +24,14 @@
     3.4  WHITE=White
     3.5  BLACK=Black
     3.6  
     3.7 -BOARD_IMAGE=Image
     3.8 -BOARD_TEXT=Text View
     3.9 +NEXT=Next
    3.10 +PREVIOUS=Prev
    3.11 +LATEST=Last
    3.12 +
    3.13 +BOARD_VIEW=View:
    3.14 +BOARD_IMAGE=Large
    3.15 +BOARD_SMALL=Small
    3.16 +BOARD_TEXT=Text
    3.17  
    3.18  players={0} vs. {1}
    3.19  PLACE=Place!
     4.1 --- a/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/Bundle_cs.properties	Sat Sep 19 12:51:50 2009 +0200
     4.2 +++ b/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/Bundle_cs.properties	Sat Sep 19 14:38:29 2009 +0200
     4.3 @@ -33,8 +33,18 @@
     4.4    432000#{4}-ti dny\
     4.5  }
     4.6  
     4.7 -BOARD_IMAGE=Grafick\u00FD pohled
     4.8 -BOARD_TEXT=Textov\u00FD pohled
     4.9 +NEXT=Dal\u0161\u00ED
    4.10 +PREVIOUS=P\u0159edchoz\u00ED
    4.11 +LATEST=Posledn\u00ED
    4.12 +
    4.13 +NEXT=Dal\u0161\u00ED
    4.14 +PREVIOUS=P\u0159edchoz\u00ED
    4.15 +LATEST=Posledn\u00ED
    4.16 +
    4.17 +BOARD_VIEW=Pohled:
    4.18 +BOARD_IMAGE=Obrovsk\u00FD
    4.19 +BOARD_SMALL=Drobn\u00FD
    4.20 +BOARD_TEXT=Textov\u00FD
    4.21  
    4.22  players={0} proti {1}
    4.23  PLACE=Um\u00EDstit
     5.1 --- a/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/game.fmt	Sat Sep 19 12:51:50 2009 +0200
     5.2 +++ b/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/game.fmt	Sat Sep 19 14:38:29 2009 +0200
     5.3 @@ -29,12 +29,24 @@
     5.4  
     5.5  
     5.6        <p>
     5.7 +          <b>${bundle.MOVENUMBER}: </b> ${(doc.game.@currentMove?number / 2 + 1)?string("0")}<br>
     5.8            <b>${bundle.WHITE}:</b> <@status doc.game.id.@white/> ${bundle("FENCES_LEFT", board.players[0].fences)}<br>
     5.9            <b>${bundle.BLACK}:</b> <@status doc.game.id.@black/> ${bundle("FENCES_LEFT", board.players[1].fences)}<br>
    5.10        </p>
    5.11  
    5.12        <p>
    5.13        <a href="/">${bundle.ROOT}</a>
    5.14 +      <#if (doc.game.@currentMove?number > 0)>
    5.15 +        <a href="/games/${doc.game.id.@id}?move=${doc.game.@currentMove?number - 1}">${bundle.PREVIOUS}</a>
    5.16 +      <#else>
    5.17 +        ${bundle.PREVIOUS}
    5.18 +      </#if>
    5.19 +      <a href="/games/${doc.game.id.@id}"">${bundle.LATEST}</a>
    5.20 +      <#if (doc.game.@currentMove?number < doc.game.moves.*?size)>
    5.21 +        <a href="/games/${doc.game.id.@id}?move=${doc.game.@currentMove?number + 1}">${bundle.NEXT}</a>
    5.22 +      <#else>
    5.23 +        ${bundle.NEXT}
    5.24 +      </#if>
    5.25        </p>
    5.26        
    5.27        <#if message?? >
    5.28 @@ -99,20 +111,38 @@
    5.29                <input type="submit" value="${bundle.RESIGN}" />
    5.30            </form>
    5.31        </#if>
    5.32 -
    5.33 +      <p>
    5.34 +      ${bundle.BOARD_VIEW}
    5.35        <#if format?? && format = "text">
    5.36          <pre>${doc.game.board}</pre>
    5.37 +        <a href="/games/${doc.game.id.@id}?format=small">${bundle.BOARD_SMALL}</a>
    5.38          <a href="/games/${doc.game.id.@id}?format=image">${bundle.BOARD_IMAGE}</a>
    5.39 +        ${bundle.BOARD_TEXT}
    5.40 +      <#elseif format?? && format = "small">
    5.41 +        <p>
    5.42 +            <img src="/api/games/${doc.game.id.@id}?fieldSize=20<#if doc.game.@currentMove??>&move=${doc.game.@currentMove}</#if>" alt="${bundle.BOARD_TEXT}">
    5.43 +        </p>
    5.44 +        ${bundle.BOARD_SMALL}
    5.45 +        <a href="/games/${doc.game.id.@id}?format=image">${bundle.BOARD_IMAGE}</a>
    5.46 +        <a href="/games/${doc.game.id.@id}?format=text">${bundle.BOARD_TEXT}</a>
    5.47        <#else>
    5.48          <p>
    5.49 -        <img src="/api/games/${doc.game.id.@id}" alt="text">
    5.50 +            <img src="/api/games/${doc.game.id.@id}<#if doc.game.@currentMove??>?move=${doc.game.@currentMove}</#if>" alt="${bundle.BOARD_TEXT}">
    5.51          </p>
    5.52 +        <a href="/games/${doc.game.id.@id}?format=small">${bundle.BOARD_SMALL}</a>
    5.53 +        ${bundle.BOARD_IMAGE}
    5.54          <a href="/games/${doc.game.id.@id}?format=text">${bundle.BOARD_TEXT}</a>
    5.55 -
    5.56        </#if>
    5.57  
    5.58 +      <#macro printMove item>
    5.59 +        <#if item.@index = doc.game.@currentMove>
    5.60 +            <b>${item.@move}</b>
    5.61 +        <#else>
    5.62 +            <a href="/games/${doc.game.id.@id}?move=${item.@index}">${item.@move}</a>
    5.63 +        </#if>
    5.64 +      </#macro>
    5.65  
    5.66 -      <h3>${bundle.MOVES}</h3>
    5.67 +      <h3><a href="/games/${doc.game.id.@id}?move=0">${bundle.MOVES}</a></h3>
    5.68  
    5.69        <table border="0">
    5.70            <thead>
    5.71 @@ -125,11 +155,12 @@
    5.72            <tbody>
    5.73                <#assign index = 0>
    5.74                <#list doc.game.moves.* as item>
    5.75 +                
    5.76                  <#if item.@index?number % 2 = 1>
    5.77                      <#assign index = index + 1>
    5.78 -                    <tr><td>${index}</td><td>${item.@move}</td>
    5.79 +                    <tr><td>${index}</td><td><@printMove item/></td>
    5.80                  <#else>
    5.81 -                    <td>${item.@move}</td></tr>
    5.82 +                    <td><@printMove item/></td></tr>
    5.83                  </#if>
    5.84                </#list>
    5.85            </tbody>
     6.1 --- a/webidor/src/main/java/cz/xelfi/quoridor/webidor/Game.java	Sat Sep 19 12:51:50 2009 +0200
     6.2 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/Game.java	Sat Sep 19 14:38:29 2009 +0200
     6.3 @@ -56,6 +56,7 @@
     6.4      @XmlElement
     6.5      @XmlJavaTypeAdapter(MoveAdapter.class)
     6.6      private List<Move> moves;
     6.7 +    private Integer move;
     6.8  
     6.9      Game() {
    6.10      }
    6.11 @@ -115,6 +116,35 @@
    6.12          return moves;
    6.13      }
    6.14  
    6.15 +    @XmlAttribute
    6.16 +    public int getCurrentMove() {
    6.17 +        return move == null ? getMoves().size() : move;
    6.18 +    }
    6.19 +
    6.20 +    public Game snapshot(int move) throws IllegalPositionException {
    6.21 +        Board b = Board.empty();
    6.22 +        int cnt = 0;
    6.23 +        for (Move m : getMoves()) {
    6.24 +            if (move-- <= 0) {
    6.25 +                break;
    6.26 +            }
    6.27 +            b = b.apply(m);
    6.28 +            cnt++;
    6.29 +        }
    6.30 +
    6.31 +        Game g = new Game(
    6.32 +            new GameId(
    6.33 +                id.getId(), id.getWhite(), id.getBlack(),
    6.34 +                new Date(id.getStarted()), new Date(id.getModified()),
    6.35 +                GameStatus.history
    6.36 +            )
    6.37 +        );
    6.38 +        g.board = b;
    6.39 +        g.move = cnt;
    6.40 +        g.moves = new ArrayList<Move>(getMoves());
    6.41 +        return g;
    6.42 +    }
    6.43 +
    6.44      @XmlAccessorType(XmlAccessType.FIELD)
    6.45      private static final class MoveTmp {
    6.46          @XmlAttribute
     7.1 --- a/webidor/src/main/java/cz/xelfi/quoridor/webidor/GameStatus.java	Sat Sep 19 12:51:50 2009 +0200
     7.2 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/GameStatus.java	Sat Sep 19 14:38:29 2009 +0200
     7.3 @@ -36,7 +36,8 @@
     7.4      whiteMove,
     7.5      blackMove,
     7.6      whiteWon,
     7.7 -    blackWon;
     7.8 +    blackWon,
     7.9 +    history;
    7.10  
    7.11      /** Creates appropriate status of the game based on the state
    7.12       * on the board.
     8.1 --- a/webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/BoardImage.java	Sat Sep 19 12:51:50 2009 +0200
     8.2 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/BoardImage.java	Sat Sep 19 14:38:29 2009 +0200
     8.3 @@ -40,13 +40,7 @@
     8.4   * @author Jaroslav Tulach <jtulach@netbeans.org>
     8.5   */
     8.6  final class BoardImage  {
     8.7 -
     8.8 -
     8.9 -    public static Image draw(Board b) {
    8.10 -        return draw(b, 50);
    8.11 -    }
    8.12 -
    8.13 -    private static Image draw(Board b, int fieldSize) {
    8.14 +    static Image draw(Board b, int fieldSize) {
    8.15          int fifth = fieldSize / 10;
    8.16  
    8.17          BufferedImage img = new BufferedImage(fieldSize * 9, fieldSize * 9, BufferedImage.TYPE_INT_ARGB);
     9.1 --- a/webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Games.java	Sat Sep 19 12:51:50 2009 +0200
     9.2 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Games.java	Sat Sep 19 14:38:29 2009 +0200
     9.3 @@ -43,6 +43,7 @@
     9.4  import java.util.List;
     9.5  import java.util.logging.Level;
     9.6  import java.util.logging.Logger;
     9.7 +import javax.ws.rs.DefaultValue;
     9.8  import javax.ws.rs.GET;
     9.9  import javax.ws.rs.POST;
    9.10  import javax.ws.rs.PUT;
    9.11 @@ -121,23 +122,26 @@
    9.12      @GET
    9.13      @Path("{id}")
    9.14      @Produces("image/png")
    9.15 -    public Image getBoardImage(@PathParam("id") String id) {
    9.16 -        Game g = findGame(id);
    9.17 +    public Image getBoardImage(
    9.18 +        @PathParam("id") String id,
    9.19 +        @QueryParam("fieldSize") @DefaultValue("50") int fieldSize,
    9.20 +        @QueryParam("move") @DefaultValue("-1") int move
    9.21 +    ) {
    9.22 +        Game g = findGame(id, move);
    9.23          if (g == null) {
    9.24              throw new IllegalArgumentException("Unknown game " + id);
    9.25          }
    9.26 -        return BoardImage.draw(g.getBoard());
    9.27 +        return BoardImage.draw(g.getBoard(), fieldSize);
    9.28      }
    9.29  
    9.30      @GET
    9.31      @Path("{id}")
    9.32      @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
    9.33 -    public Game getBoardInfo(@PathParam("id") String id) {
    9.34 -        Game g = findGame(id);
    9.35 -        if (g == null) {
    9.36 -            throw new IllegalArgumentException("Unknown game " + id);
    9.37 -        }
    9.38 -        return g;
    9.39 +    public Game getBoardInfo(
    9.40 +        @PathParam("id") String id,
    9.41 +        @QueryParam("move") @DefaultValue("-1") int move
    9.42 +    ) {
    9.43 +        return findGame(id, move);
    9.44      }
    9.45  
    9.46      @PUT
    9.47 @@ -194,6 +198,18 @@
    9.48          }
    9.49          return null;
    9.50      }
    9.51 +    private Game findGame(String id, int move) {
    9.52 +        Game g = findGame(id);
    9.53 +        if (g == null) {
    9.54 +            throw new IllegalArgumentException("Unknown game " + id);
    9.55 +        }
    9.56 +        try {
    9.57 +            return move == -1 ? g : g.snapshot(move);
    9.58 +        } catch (IllegalPositionException ex) {
    9.59 +            Logger.getLogger(Games.class.getName()).log(Level.SEVERE, null, ex);
    9.60 +            return null;
    9.61 +        }
    9.62 +    }
    9.63  
    9.64      private Game readGame(File f) throws IOException {
    9.65          BufferedReader r = new BufferedReader(new FileReader(f));
    10.1 --- a/webidor/src/test/java/cz/xelfi/quoridor/webidor/GamesTest.java	Sat Sep 19 12:51:50 2009 +0200
    10.2 +++ b/webidor/src/test/java/cz/xelfi/quoridor/webidor/GamesTest.java	Sat Sep 19 14:38:29 2009 +0200
    10.3 @@ -82,7 +82,7 @@
    10.4          Thread.sleep(1000);
    10.5  
    10.6          Games games = new Games(dir, new Quoridor());
    10.7 -        Game g = games.getBoardInfo("x");
    10.8 +        Game g = games.getBoardInfo("x", -1);
    10.9          assertNotNull("Game found", g);
   10.10          assertNotNull("Board found", g.getBoard());
   10.11          assertEquals("List of moves has two", 2, g.getMoves().size());
    11.1 --- a/webidor/src/test/java/cz/xelfi/quoridor/webidor/QuoridorTest.java	Sat Sep 19 12:51:50 2009 +0200
    11.2 +++ b/webidor/src/test/java/cz/xelfi/quoridor/webidor/QuoridorTest.java	Sat Sep 19 14:38:29 2009 +0200
    11.3 @@ -161,6 +161,11 @@
    11.4          if (s2.getModified() <= now) {
    11.5              fail("The game is newly modified");
    11.6          }
    11.7 +        Game snapshot = webResource.path("games/" + s.getId()).queryParam("move", "0").accept(MediaType.TEXT_XML).get(Game.class);
    11.8 +        assertEquals("All moves listed", 2, snapshot.getMoves().size());
    11.9 +        assertEquals("Current move", 0, snapshot.getCurrentMove());
   11.10 +        assertEquals("Position 0", 0, snapshot.getBoard().getPlayers().get(0).getRow());
   11.11 +        assertEquals("Position 8", 8, snapshot.getBoard().getPlayers().get(1).getRow());
   11.12  
   11.13          File game = new File(new File(dir, "games"), s2.getId());
   11.14          assertTrue("File for game exists", game.exists());