Skip to content

Commit dcf6fe2

Browse files
authored
Merge pull request #41 from Mind-Sports-Games/442-backgammon
support lift move and drop for games in chessground
2 parents d99290d + 766bd08 commit dcf6fe2

File tree

4 files changed

+491
-31
lines changed

4 files changed

+491
-31
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chessground",
3-
"version": "7.11.1-pstrat2.41",
3+
"version": "7.11.1-pstrat2.42",
44
"description": "playstrategy.org chess ui, forked from lichess.org",
55
"type": "commonjs",
66
"main": "chessground.js",

src/api.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,11 @@ export function start(state: State, redrawAll: cg.Redraw): Api {
153153
},
154154

155155
lift(dest): void {
156-
anim(state => board.userLift(state, dest), state);
156+
anim(state => board.baseLift(state, dest), state);
157157
},
158158

159159
liftNoAnim(dest): void {
160-
board.userLift(state, dest);
160+
board.baseLift(state, dest);
161161
state.dom.redraw();
162162
},
163163

src/board.ts

+158-28
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import {
1111
callUserFunction,
1212
calculatePieceGroup,
1313
calculateGoScores,
14+
calculateGoCaptures,
15+
calculateBackgammonDropChanges,
1416
oppositeOrientationBG,
17+
owareUpdatePiecesFromMove,
18+
togyzkumalakUpdatePiecesFromMove,
19+
backgammonUpdatePiecesFromMove,
20+
calculateFlippingPieces,
1521
} from './util';
1622
import { premove, queen, knight } from './premove';
1723
import predrop from './predrop';
@@ -104,36 +110,145 @@ function tryAutoCastle(state: HeadlessState, orig: cg.Key, dest: cg.Key): boolea
104110
return true;
105111
}
106112

113+
//update state pieces based on drop for specific game logics
114+
function setDropVariantState(state: HeadlessState, piece: cg.Piece, key: cg.Key): void {
115+
switch (state.variant) {
116+
case 'flipello10':
117+
case 'flipello': {
118+
const flipping = calculateFlippingPieces(state.dimensions, state.pieces, piece, key);
119+
const flipPiece: cg.Piece = { role: 'p-piece', playerIndex: state.turnPlayerIndex };
120+
flipping.forEach(key => state.pieces.set(key, flipPiece));
121+
state.pieces.set(key, piece);
122+
break;
123+
}
124+
case 'go19x19':
125+
case 'go13x13':
126+
case 'go9x9': {
127+
state.pieces.set(key, piece);
128+
const captured: cg.Key[] = calculateGoCaptures(key, state.pieces, state.dimensions);
129+
captured.map(k => state.pieces.delete(k));
130+
break;
131+
}
132+
case 'nackgammon':
133+
case 'backgammon': {
134+
const destPiece = state.pieces.get(key);
135+
const capture = destPiece ? destPiece.playerIndex !== piece.playerIndex : false;
136+
const backgammonPieces: cg.PiecesDiff = calculateBackgammonDropChanges(state.pieces, piece, key);
137+
setPieces(state, backgammonPieces);
138+
updatePocketPieces(state, opposite(piece.playerIndex), true, capture);
139+
break;
140+
}
141+
default:
142+
state.pieces.set(key, piece);
143+
}
144+
}
145+
146+
function updatePocketPieces(
147+
state: HeadlessState,
148+
capturedPiecePlayerIndex: cg.PlayerIndex,
149+
isDrop: boolean,
150+
isCapture: boolean
151+
): void {
152+
let playerCount = 0;
153+
let enemyCount = 0;
154+
155+
state.pocketPieces.forEach(p => {
156+
const pCount = +p.role.split('-')[0].substring(1);
157+
if (p.playerIndex === capturedPiecePlayerIndex) {
158+
enemyCount = pCount;
159+
} else {
160+
playerCount = pCount;
161+
}
162+
});
163+
164+
const newPocketPieces: cg.Piece[] = [];
165+
if (enemyCount > 0 || isCapture) {
166+
const piece = {
167+
role: `s${enemyCount + (isCapture ? 1 : 0)}-piece`,
168+
playerIndex: capturedPiecePlayerIndex,
169+
} as cg.Piece;
170+
newPocketPieces.push(piece);
171+
}
172+
if ((playerCount > 0 && !isDrop) || (isDrop && playerCount > 1)) {
173+
const piece = {
174+
role: `s${playerCount - (isDrop ? 1 : 0)}-piece`,
175+
playerIndex: opposite(capturedPiecePlayerIndex),
176+
} as cg.Piece;
177+
newPocketPieces.push(piece);
178+
}
179+
180+
state.pocketPieces = newPocketPieces;
181+
}
182+
107183
export function baseMove(state: HeadlessState, orig: cg.Key, dest: cg.Key): cg.Piece | boolean {
108184
const origPiece = state.pieces.get(orig),
109185
destPiece = state.pieces.get(dest);
110186
if ((orig === dest && state.variant !== 'togyzkumalak') || !origPiece) return false;
111-
const captured = destPiece && destPiece.playerIndex !== origPiece.playerIndex ? destPiece : undefined;
187+
const captured = isCapture(state.variant, destPiece, origPiece);
112188
if (dest === state.selected) unselect(state);
113189
callUserFunction(state.events.move, orig, dest, captured);
114-
if (!tryAutoCastle(state, orig, dest)) {
115-
state.pieces.set(dest, origPiece);
116-
state.pieces.delete(orig);
190+
191+
switch (state.variant) {
192+
case 'togyzkumalak':
193+
setPieces(state, togyzkumalakUpdatePiecesFromMove(state.dimensions, state.pieces, orig, dest));
194+
break;
195+
case 'oware':
196+
setPieces(state, owareUpdatePiecesFromMove(state.dimensions, state.pieces, orig, dest));
197+
break;
198+
case 'nackgammon':
199+
case 'backgammon':
200+
if (captured) {
201+
updatePocketPieces(state, opposite(origPiece.playerIndex), false, true);
202+
}
203+
setPieces(state, backgammonUpdatePiecesFromMove(state.pieces, orig, dest));
204+
break;
205+
default:
206+
if (!tryAutoCastle(state, orig, dest)) {
207+
state.pieces.set(dest, origPiece);
208+
state.pieces.delete(orig);
209+
}
117210
}
211+
118212
state.lastMove = [orig, dest];
119213
state.check = undefined;
120214
callUserFunction(state.events.change);
121215
return captured || true;
122216
}
123217

218+
function isCapture(variant: cg.Variant, destPiece: cg.Piece | undefined, origPiece: cg.Piece): cg.Piece | undefined {
219+
switch (variant) {
220+
case 'togyzkumalak': {
221+
//TODO account for wrapping around board (simimlar to oware capture)
222+
const count = destPiece && Number(destPiece.role.split('-')[0].substring(1));
223+
return destPiece && destPiece.playerIndex !== origPiece.playerIndex && count && (count === 2 || count % 2 === 1)
224+
? destPiece
225+
: undefined;
226+
}
227+
case 'oware':
228+
//TODO this is more complicated to calculate... (but its only used for sound in lila atm)
229+
return destPiece && destPiece.playerIndex !== origPiece.playerIndex ? destPiece : undefined;
230+
default:
231+
return destPiece && destPiece.playerIndex !== origPiece.playerIndex ? destPiece : undefined;
232+
}
233+
}
234+
124235
export function baseNewPiece(state: HeadlessState, piece: cg.Piece, key: cg.Key, force?: boolean): boolean {
125-
if (state.pieces.has(key)) {
236+
if (state.pieces.has(key) && !(state.variant === 'backgammon' || state.variant === 'nackgammon')) {
126237
if (force) state.pieces.delete(key);
127238
else return false;
128239
}
129240
callUserFunction(state.events.dropNewPiece, piece, key);
130-
state.pieces.set(key, piece);
241+
setDropVariantState(state, piece, key);
242+
131243
state.lastMove = [key];
132244
state.check = undefined;
133245
callUserFunction(state.events.change);
134246
state.movable.dests = undefined;
135247
state.dropmode.dropDests = undefined;
136-
state.turnPlayerIndex = opposite(state.turnPlayerIndex);
248+
if (!(state.variant === 'backgammon' || state.variant === 'nackgammon')) {
249+
//end turn action is used for backgammon games
250+
state.turnPlayerIndex = opposite(state.turnPlayerIndex);
251+
}
137252
return true;
138253
}
139254

@@ -143,12 +258,38 @@ function baseUserMove(state: HeadlessState, orig: cg.Key, dest: cg.Key): cg.Piec
143258
state.movable.dests = undefined;
144259
state.dropmode.dropDests = undefined;
145260
state.liftable.liftDests = undefined;
146-
state.turnPlayerIndex = opposite(state.turnPlayerIndex);
261+
if (!(state.variant === 'backgammon' || state.variant === 'nackgammon')) {
262+
//end turn manually for backgammon games
263+
state.turnPlayerIndex = opposite(state.turnPlayerIndex);
264+
}
147265
state.animation.current = undefined;
148266
}
149267
return result;
150268
}
151269

270+
export function baseLift(state: HeadlessState, dest: cg.Key): boolean {
271+
const piece = state.pieces.get(dest);
272+
if (piece) {
273+
if (state.variant === 'backgammon' || state.variant === 'nackgammon') {
274+
const count = piece.role.split('-')[0].substring(1);
275+
const letter = piece.role.charAt(0);
276+
if (count === '1') {
277+
state.pieces.delete(dest);
278+
} else {
279+
state.pieces.set(dest, {
280+
role: `${letter}${+count - 1}-piece` as cg.Role,
281+
playerIndex: piece.playerIndex,
282+
});
283+
}
284+
} else {
285+
state.pieces.delete(dest);
286+
}
287+
return true;
288+
} else {
289+
return false;
290+
}
291+
}
292+
152293
export function userMove(state: HeadlessState, orig: cg.Key, dest: cg.Key): boolean {
153294
if (canMove(state, orig, dest)) {
154295
const result = baseUserMove(state, orig, dest);
@@ -185,27 +326,16 @@ export function userLift(state: HeadlessState, dest: cg.Key): boolean {
185326
state.liftable.liftDests.includes(dest) &&
186327
state.turnPlayerIndex === piece.playerIndex
187328
) {
188-
if (state.variant === 'backgammon' || state.variant === 'nackgammon') {
189-
const count = piece.role.split('-')[0].substring(1);
190-
const letter = piece.role.charAt(0);
191-
if (count === '1') {
192-
state.pieces.delete(dest);
193-
} else {
194-
state.pieces.set(dest, {
195-
role: `${letter}${+count - 1}-piece` as cg.Role,
196-
playerIndex: piece.playerIndex,
197-
});
198-
}
199-
} else {
200-
state.pieces.delete(dest);
201-
}
202-
state.movable.dests = undefined;
203-
state.dropmode.dropDests = undefined;
204-
state.liftable.liftDests = undefined;
205-
state.animation.current = undefined;
329+
const result = baseLift(state, dest);
330+
if (result) {
331+
state.movable.dests = undefined;
332+
state.dropmode.dropDests = undefined;
333+
state.liftable.liftDests = undefined;
334+
state.animation.current = undefined;
206335

207-
callUserFunction(state.liftable.events.after, dest);
208-
return true;
336+
callUserFunction(state.liftable.events.after, dest);
337+
return true;
338+
}
209339
} else {
210340
unsetPremove(state);
211341
unsetPredrop(state);

0 commit comments

Comments
 (0)