001    package core;
002    
003    import java.util.HashMap;
004    import java.util.Map;
005    
006    import rules.Board;
007    import rules.Empty;
008    import rules.GameState;
009    import rules.King;
010    import rules.NoneSquare;
011    import rules.Pawn;
012    import rules.PieceFactory;
013    import rules.Queen;
014    
015    /**
016     * A Move represents a single player's move on a board. It is immutable, and may
017     * mean different things on different boards.
018     * 
019     * A can either be represented by an ordered pair of Positions or by a pass.
020     * 
021     * @specfield ispass : boolean // If the move is a pass (OR)
022     * @specfield fromposition : Position // Where the move originates
023     * @specfield toposition : Position // Where the move ends
024     * @derivedfield boardupdates : Map<Position, Piece> // How the move changes a
025     *               board
026     * 
027     * Abstract invariant: either it is a pass or the positions are valid
028     */
029    public class Move
030    {
031            private Position from;
032            private Position to;
033    
034            // AF:
035            // ispass = (from == null || to == null)
036            // fromposition = from
037            // toposition = to
038            // RI:
039            // fromposition, toposition are both null or both valid positions.
040    
041            /**
042             * @see Piece
043             */
044            public Move(Position from, Position to)
045            {
046                    this.from = from;
047                    this.to = to;
048                    checkRep();
049            }
050    
051            /**
052             * Construct the pass move.
053             */
054            public Move()
055            {
056                    this(null, null);
057            }
058    
059            /**
060             * Checks that the RI is not violated
061             */
062            private void checkRep()
063            {
064                    if (from == null || to == null)
065                    {
066                            assert (from == null && to == null);
067                    } else
068                    {
069                            assert (0 <= from.getX() && from.getX() < 8 && 0 <= from.getY() && from
070                                            .getY() < 8);
071                    }
072            }
073    
074            /**
075             * @returns the Position the move is from
076             */
077            public Position getFrom()
078            {
079                    return from;
080            }
081    
082            /**
083             * @returns the Position the move is to
084             */
085            public Position getTo()
086            {
087                    return to;
088            }
089    
090            /**
091             * @returns whether this is a pass
092             */
093            public boolean isPass()
094            {
095                    return from == null || to == null;
096            }
097    
098            /**
099             * @param s
100             *            is a String representing an Antichess move
101             * @requires s is of the form [position1]-[position2]; that is, two valid
102             *           and distinct positions separated by a hyphen. For example,
103             *           a3-a8 is a legal argument to this method. Or, s.equals("") for
104             *           a pass.
105             * @return a Move representing the same set of changes to the GameState g as
106             *         the String s. Its toString method will return a String equal to
107             *         s.
108             */
109            public static Move fromString(String s)
110            {
111                    // Check that s is a well-formed move string
112                    Move move;
113                    if (s.equals(""))
114                    {
115                            move = new Move();
116                    } else if (!Move.parseableMove(s))
117                    {
118                            throw new RuntimeException("'" + s + "' is not a valid move.");
119                    } else
120                    {
121                            Position from = Position.fromString(s.substring(0, 2));
122                            Position end = Position.fromString(s.substring(3));
123                            move = new Move(from, end);
124                    }
125                    return move;
126            }
127    
128            /**
129             * @param g
130             *            is the gamestate to make this move on
131             * @requires g != null and this move is a valid move candidate; the pieces
132             *           may move as desired, but for possibly not capturing or entering
133             *           into check.
134             * @returns the Map<Position, Piece> of updates to g to perform this move.
135             */
136            public Map<Position, Piece> toMap(GameState g)
137            {
138                    Map<Position, Piece> move = new HashMap<Position, Piece>();
139                    if (isPass())
140                    {
141                            return move;
142                    }
143                    move.put(from, new Empty());
144                    Board board = ((GameState) g).getBoard();
145    
146                    Piece movingPiece = board.get(from);
147                    Piece overriddenPiece = board.get(to);
148                    if (movingPiece instanceof NoneSquare)
149                    {
150                        System.out.println(this.toString());
151                            throw new RuntimeException("Got move from NoneSquare");
152                            // return null;
153                    }
154    
155                    // The "to" square has whatever was in the "from" square before
156                    // but with lastmove incremented
157                    if (movingPiece instanceof Pawn &&
158                        Math.abs(to.getY() - from.getY()) == 2) { //Pawn push
159                        move.put(to, PieceFactory.newPiece(movingPiece, g.getMoveNum() + 1));
160                    } else {
161                        move.put(to, PieceFactory.newPiece(movingPiece));
162                    }
163    
164                    // Must be a castle
165                    if (movingPiece instanceof King && from.getX() == 4
166                                    && Math.abs(to.getX() - from.getX()) == 2)
167                    {
168                            // Don't need to check for Y or moves; legality checking covers
169                            // those.
170                            Position rookFrom, rookTo;
171                            if (to.getX() == 2)
172                            {
173                                    rookFrom = Position.get(0, from.getY());
174                                    rookTo = Position.get(3, from.getY());
175                            } else
176                            {
177                                    rookFrom = Position.get(7, from.getY());
178                                    rookTo = Position.get(5, from.getY());
179                            }
180                            move.put(rookFrom, new Empty());
181                            move.put(rookTo, PieceFactory.newPiece(board.get(rookFrom)));
182                    } else if (movingPiece instanceof Pawn)
183                    {
184                            // Promotion
185                            if (to.getY() == 0 || to.getY() == 7)
186                            {
187                                    // Overrides pawn at the location
188                                    move.put(to, new Queen(movingPiece.getColor(), 1));
189                            }
190                            // En passant
191                            if (to.getX() != from.getX() && overriddenPiece instanceof NoneSquare)
192                            {
193                                    move.put(Position.get(to.getX(), from.getY()), new Empty());
194                            }
195                    }
196                    if (!(movingPiece instanceof King))
197                        overriddenPiece.postMove(g, move, to);
198                    // All the board updates have been added to our map; we can return it.
199                    return move;
200            }
201    
202            /**
203             * @return true if s is of the form [position1]-[position2]; that is, two
204             *         valid and distinct positions separated by a hyphen. For example,
205             *         a3-a8 is a valid move String.
206             */
207            public static boolean parseableMove(String s)
208            {
209                    if (s.equals("pass"))
210                    {
211                            return true;
212                    }
213                    return s.length() == 5 && Position.validPosition(s.substring(0, 2))
214                                    && s.charAt(2) == '-' && Position.validPosition(s.substring(3));
215            }
216    
217            /**
218             * @return a String representation of this, in the form
219             *         [position1]-[position2], where position1 and position2 are the
220             *         old and new positions of the moving piece, respectively. In the
221             *         case of castling, the King is the "moving" piece. In the case of
222             *         en passant, the capturing pawn is the "moving" piece.
223             */
224            public String toString()
225            {
226                    if (isPass())
227                    {
228                            return "";
229                    }
230                    return from.toString() + "-" + to.toString();
231            }
232    
233            /**
234             * @see Object
235             */
236            public boolean equals(Object o)
237            {
238                    if (o != null && o instanceof Move)
239                    {
240                            Move m = (Move) o;
241                            if (isPass()){
242                                return m.isPass();
243                            }
244                            return from.equals(m.from) && to.equals(m.to);
245                    }
246                    return false;
247            }
248    
249            /**
250             * @see Object
251             */
252            public int hashCode()
253            {
254                if (from == null){
255                    return -1;
256                } else {
257                    return from.hashCode() * 64 + to.hashCode();
258                }
259            }
260    }