Draft of Ultima chess rules; almost OK HalfChess
[vchess.git] / public / javascripts / variants / Magnetic.js
1 class MagneticRules extends ChessRules
2 {
3 getEpSquare(move)
4 {
5 return undefined; //no en-passant
6 }
7
8 getPotentialMovesFrom([x,y])
9 {
10 let standardMoves = super.getPotentialMovesFrom([x,y]);
11 let moves = [];
12 standardMoves.forEach(m => {
13 let newMove_s = this.applyMagneticLaws(m);
14 if (newMove_s.length == 1)
15 moves.push(newMove_s[0]);
16 else //promotion
17 moves = moves.concat(newMove_s);
18 });
19 return moves;
20 }
21
22 // Complete a move with magnetic actions
23 // TODO: job is done multiple times for (normal) promotions.
24 applyMagneticLaws(move)
25 {
26 const V = VariantRules;
27 if (move.appear[0].p == V.KING && move.appear.length==1)
28 return [move]; //kings are not charged
29 const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged
30 const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y];
31 const color = this.turn;
32 const lastRank = (color=="w" ? 0 : 7);
33 const standardMove = JSON.parse(JSON.stringify(move));
34 this.play(standardMove);
35 const [sizeX,sizeY] = V.size;
36 for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
37 {
38 let [i,j] = [x+step[0],y+step[1]];
39 while (i>=0 && i<sizeX && j>=0 && j<sizeY)
40 {
41 if (this.board[i][j] != V.EMPTY)
42 {
43 // Found something. Same color or not?
44 if (this.getColor(i,j) != color)
45 {
46 // Attraction
47 if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING)
48 {
49 move.vanish.push(
50 new PiPo({
51 p:this.getPiece(i,j),
52 c:this.getColor(i,j),
53 x:i,
54 y:j
55 })
56 );
57 move.appear.push(
58 new PiPo({
59 p:this.getPiece(i,j),
60 c:this.getColor(i,j),
61 x:x+step[0],
62 y:y+step[1]
63 })
64 );
65 }
66 }
67 else
68 {
69 // Repulsion
70 if (this.getPiece(i,j) != V.KING)
71 {
72 // Push it until we meet an obstacle or edge of the board
73 let [ii,jj] = [i+step[0],j+step[1]];
74 while (ii>=0 && ii<sizeX && jj>=0 && jj<sizeY)
75 {
76 if (this.board[ii][jj] != V.EMPTY)
77 break;
78 ii += step[0];
79 jj += step[1];
80 }
81 ii -= step[0];
82 jj -= step[1];
83 if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
84 {
85 move.vanish.push(
86 new PiPo({
87 p:this.getPiece(i,j),
88 c:this.getColor(i,j),
89 x:i,
90 y:j
91 })
92 );
93 move.appear.push(
94 new PiPo({
95 p:this.getPiece(i,j),
96 c:this.getColor(i,j),
97 x:ii,
98 y:jj
99 })
100 );
101 }
102 }
103 }
104 break;
105 }
106 i += step[0];
107 j += step[1];
108 }
109 }
110 this.undo(standardMove);
111 let moves = [];
112 // Scan move for pawn (max 1) on 8th rank
113 for (let i=1; i<move.appear.length; i++)
114 {
115 if (move.appear[i].p==V.PAWN && move.appear[i].c==color
116 && move.appear[i].x==lastRank)
117 {
118 move.appear[i].p = V.ROOK;
119 moves.push(move);
120 for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN])
121 {
122 let cmove = JSON.parse(JSON.stringify(move));
123 cmove.appear[i].p = piece;
124 moves.push(cmove);
125 }
126 // Swap appear[i] and appear[0] for moves presentation (TODO: this is awkward)
127 moves.forEach(m => {
128 let tmp = m.appear[0];
129 m.appear[0] = m.appear[i];
130 m.appear[i] = tmp;
131 });
132 break;
133 }
134 }
135 if (moves.length == 0) //no pawn on 8th rank
136 moves.push(move);
137 return moves;
138 }
139
140 // TODO: verify this assertion
141 atLeastOneMove()
142 {
143 return true; //always at least one possible move
144 }
145
146 underCheck(move)
147 {
148 return false; //there is no check
149 }
150
151 getCheckSquares(move)
152 {
153 const c = this.getOppCol(this.turn); //opponent
154 const saveKingPos = this.kingPos[c]; //king might be taken
155 this.play(move);
156 // The only way to be "under check" is to have lost the king (thus game over)
157 let res = this.kingPos[c][0] < 0
158 ? [ JSON.parse(JSON.stringify(saveKingPos)) ]
159 : [ ];
160 this.undo(move);
161 return res;
162 }
163
164 updateVariables(move)
165 {
166 super.updateVariables(move);
167 const c = this.getColor(move.start.x,move.start.y);
168 if (c != this.getColor(move.end.x,move.end.y)
169 && this.board[move.end.x][move.end.y] != VariantRules.EMPTY
170 && this.getPiece(move.end.x,move.end.y) == VariantRules.KING)
171 {
172 // We took opponent king !
173 const oppCol = this.getOppCol(c);
174 this.kingPos[oppCol] = [-1,-1];
175 this.castleFlags[oppCol] = [false,false];
176 }
177 // Did we magnetically move our (init) rooks or opponents' ones ?
178 const firstRank = (c == "w" ? 7 : 0);
179 const oppFirstRank = 7 - firstRank;
180 const oppCol = this.getOppCol(c);
181 move.vanish.forEach(psq => {
182 if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
183 this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
184 else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y))
185 this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false;
186 });
187 }
188
189 unupdateVariables(move)
190 {
191 super.unupdateVariables(move);
192 const c = this.getColor(move.start.x,move.start.y);
193 const oppCol = this.getOppCol(c);
194 if (this.kingPos[oppCol][0] < 0)
195 {
196 // Last move took opponent's king
197 for (let psq of move.vanish)
198 {
199 if (psq.p == 'k')
200 {
201 this.kingPos[oppCol] = [psq.x, psq.y];
202 break;
203 }
204 }
205 }
206 }
207
208 checkGameOver()
209 {
210 if (this.checkRepetition())
211 return "1/2";
212
213 const color = this.turn;
214 // TODO: do we need "atLeastOneMove()"?
215 if (this.atLeastOneMove() && this.kingPos[color][0] >= 0)
216 return "*";
217
218 return this.checkGameEnd();
219 }
220
221 checkGameEnd()
222 {
223 // No valid move: our king disappeared
224 return this.turn == "w" ? "0-1" : "1-0";
225 }
226
227 static get THRESHOLD_MATE() {
228 return 500; //checkmates evals may be slightly below 1000
229 }
230 }