diff options
author | Jaume Delclòs Coll <cosa@cosarara.me> | 2024-05-07 23:40:54 +0000 |
---|---|---|
committer | Jaume Delclòs Coll <cosa@cosarara.me> | 2024-05-08 01:53:14 +0200 |
commit | ff6cb0538290005415a435812a2f36c79c0dfc6f (patch) | |
tree | d2bc877ff374692310e6741494587b04f8329a26 |
initial
-rw-r--r-- | chess.c | 562 | ||||
-rw-r--r-- | mkfile | 27 | ||||
-rw-r--r-- | png/bB.png | bin | 0 -> 915 bytes | |||
-rw-r--r-- | png/bK.png | bin | 0 -> 1780 bytes | |||
-rw-r--r-- | png/bN.png | bin | 0 -> 1128 bytes | |||
-rw-r--r-- | png/bP.png | bin | 0 -> 621 bytes | |||
-rw-r--r-- | png/bQ.png | bin | 0 -> 1596 bytes | |||
-rw-r--r-- | png/bR.png | bin | 0 -> 576 bytes | |||
-rw-r--r-- | png/wB.png | bin | 0 -> 1314 bytes | |||
-rw-r--r-- | png/wK.png | bin | 0 -> 1673 bytes | |||
-rw-r--r-- | png/wN.png | bin | 0 -> 1306 bytes | |||
-rw-r--r-- | png/wP.png | bin | 0 -> 945 bytes | |||
-rw-r--r-- | png/wQ.png | bin | 0 -> 2067 bytes | |||
-rw-r--r-- | png/wR.png | bin | 0 -> 851 bytes |
14 files changed, 589 insertions, 0 deletions
@@ -0,0 +1,562 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <thread.h> +#include <mouse.h> +#include <keyboard.h> + +// Missing features +// display checkmate and stalemate +// r to reset +// arrows to go back and forward in history +// show moves +// save & load games +// save & load position +// free edit board +// detect repetition +// engine play +// online play (FICS? lichess? custom?) + +typedef enum Piece { + empty, + bB, + bK, + bN, + bP, + bQ, + bR, + wB, + wK, + wN, + wP, + wQ, + wR +} Piece; + +char *PIECE_STR[] = { + "", + "bB", + "bK", + "bN", + "bP", + "bQ", + "bR", + "wB", + "wK", + "wN", + "wP", + "wQ", + "wR", +}; + +// I would use the raw letters but D is taken by draw.h :( +typedef enum ChessFile { + fA, + fB, + fC, + fD, + fE, + fF, + fG, + fH, +} ChessFile; + +#define RANK(rank) (8-rank) +#define FILE(file) (file) +#define BRD(file, rank) (board[8-rank][file]) +#define BRDPT(pt) (board[pt.y][pt.x]) + +// originally copied from /sys/src/cmd/page.c +void +resizewin(void) +{ + int wctl; + + if((wctl = open("/dev/wctl", OWRITE)) < 0) + return; + // 10px inner border, 8 50px squares, then rio border + Point size = Pt(20+50*8 + Borderwidth*2 + 200, 20+50*8 + Borderwidth*2); + fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y); + close(wctl); +} + +void resetboard(int board[8][8]) { + // empty it + for (int i=0; i<8*8; i++) ((int*)board)[i] = empty; + // set pawns + for (int x=0; x<8; x++) { + BRD(x, 2) = wP; + BRD(x, 7) = bP; + } + + // set black pieces + BRD(fA, 8) = BRD(fH, 8) = bR; + BRD(fB, 8) = BRD(fG, 8) = bN; + BRD(fC, 8) = BRD(fF, 8) = bB; + BRD(fD, 8) = bQ; + BRD(fE, 8) = bK; + // set white pieces + BRD(fA, 1) = BRD(fH, 1) = wR; + BRD(fB, 1) = BRD(fG, 1) = wN; + BRD(fC, 1) = BRD(fF, 1) = wB; + BRD(fD, 1) = wQ; + BRD(fE, 1) = wK; +} + +typedef enum Player { + White = 0, + Black = 1 +} Player; + +typedef struct Move { + Piece piece; + Point src; + Point dst; +} Move; + +typedef enum MoveType { + Invalid, + Normal, + EnPassant, + BlackShortCastle, + BlackLongCastle, + WhiteShortCastle, + WhiteLongCastle, +} MoveType; + +Player +owner(Piece piece) +{ + if (piece <= bR) + return Black; + return White; +} + +typedef struct CastleRights { + int wK_ever_moved; + int bK_ever_moved; + int wRA_ever_moved; + int bRA_ever_moved; + int wRH_ever_moved; + int bRH_ever_moved; +} CastleRights; + +char dbgstring[50]; + + +MoveType +ismove(int board[8][8], Point src, Point dst, Move prev_move) +{ + Piece actor = BRDPT(src); + Piece target = BRDPT(dst); + if (target && owner(actor) == owner(target)) { + return Invalid; // can't take your own pieces + } + int x, y, dx, dy; + switch (actor) { + case bP: + if (src.y == RANK(7) && src.x == dst.x && dst.y == RANK(5) + && !target && BRD(src.x, 6) == empty) { // double + return Normal; + } + if (dst.y != src.y+1) return Invalid; + if (dst.x == src.x && !target) { // forward + return Normal; + } + if (abs(dst.x - src.x) == 1 && target) { // taking + return Normal; + } + // TODO: en passant + if (dst.y == RANK(3) && abs(dst.x - src.x) == 1 + && prev_move.piece == wP && eqpt(prev_move.src, Pt(dst.x, RANK(2))) + && prev_move.dst.y == RANK(4)) + { + return EnPassant; + } + return Invalid; + case wP: + if (src.y == RANK(2) && src.x == dst.x && dst.y == RANK(4) + && !target && BRD(src.x, 3) == empty) { // double + return Normal; + } + if (dst.y != src.y-1) return Invalid; + if (dst.x == src.x && !target) { // forward + return Normal; + } + if (abs(dst.x - src.x) == 1 && target) { // taking + return Normal; + } + // en passant + if (dst.y == RANK(6) && abs(dst.x - src.x) == 1 + && prev_move.piece == bP && eqpt(prev_move.src, Pt(dst.x, RANK(7))) + && prev_move.dst.y == RANK(5)) + { + return EnPassant; + } + return 0; + case bK: + case wK: + if (abs(dst.x - src.x) <= 1 && abs(dst.y - src.y) <= 1) { + return Normal; + } + // castling + if (actor == wK) { + if (eqpt(src, Pt(fE, RANK(1))) && dst.y == RANK(1)) { + if (dst.x == fG) return WhiteShortCastle; + if (dst.x == fC) return WhiteLongCastle; + } + } else { + if (eqpt(src, Pt(fE, RANK(8))) && dst.y == RANK(8)) { + if (dst.x == fG) return BlackShortCastle; + if (dst.x == fC) return BlackLongCastle; + } + } + return Invalid; + case bR: + case wR: + if (dst.x != src.x && dst.y != src.y) return 0; + dx = 0; + dy = 0; + if (dst.x > src.x) dx = 1; + else if (dst.x < src.x) dx = -1; + else if (dst.y > src.y) dy = 1; + else dy = -1; + + for (x=src.x+dx, y=src.y+dy; x!=dst.x || y!=dst.y; x+=dx, y+=dy) + if (board[y][x]) return Invalid; + return Normal; + case bB: + case wB: + if (abs(dst.x - src.x) != abs(dst.y - src.y)) return 0; + dx = dst.x > src.x ? 1 : -1; + dy = dst.y > src.y ? 1 : -1; + for (x=src.x+dx, y=src.y+dy; x!=dst.x; x+=dx, y+=dy) + if (board[y][x]) return Invalid; + return Normal; + case bN: + case wN: + return ((abs(dst.x - src.x)==1 && abs(dst.y - src.y)==2) || + (abs(dst.x - src.x)==2 && abs(dst.y - src.y)==1)) + ? Normal : Invalid; + case bQ: + case wQ: + if (dst.x == src.x || dst.y == src.y) { // moving like a rook + dx = 0; + dy = 0; + if (dst.x > src.x) dx = 1; + else if (dst.x < src.x) dx = -1; + else if (dst.y > src.y) dy = 1; + else dy = -1; + } else { // moving like a bishop + if (abs(dst.x - src.x) != abs(dst.y - src.y)) return 0; + dx = dst.x > src.x ? 1 : -1; + dy = dst.y > src.y ? 1 : -1; + } + for (x=src.x+dx, y=src.y+dy; x!=dst.x || y!=dst.y; x+=dx, y+=dy) + if (board[y][x]) return Invalid; + return Normal; + } + return Normal; +} + +void +do_move(int board[8][8], Point src, Point dst, MoveType move_type) { + BRDPT(dst) = BRDPT(src); + BRDPT(src) = empty; + if (move_type == EnPassant) { + Point victim = addpt(dst, Pt(0, 1)); + if (BRDPT(dst) == bP) { + victim = subpt(dst, Pt(0, 1)); + } + BRDPT(victim) = empty; + } + Point rook_src, rook_dst; + switch (move_type) { + case BlackLongCastle: + case WhiteLongCastle: + rook_src = Pt(fA, src.y); + rook_dst = Pt(fD, src.y); + BRDPT(rook_dst) = BRDPT(rook_src); + BRDPT(rook_src) = empty; + break; + case BlackShortCastle: + case WhiteShortCastle: + rook_src = Pt(fH, src.y); + rook_dst = Pt(fF, src.y); + BRDPT(rook_dst) = BRDPT(rook_src); + BRDPT(rook_src) = empty; + break; + } +} + +int +is_in_check(int board[8][8], Player current) { + Piece our_king = bK; + if (current == White) our_king = wK; + int x, y; + Point king_pos = Pt(-1, -1); + for (x=0; x<8; x++) for (y=0; y<8; y++) { + if (board[y][x] == our_king) { + king_pos = Pt(x, y); + break; + } + } + if (eqpt(king_pos, Pt(-1, -1))) return 0; // no king found, whatever + Move prev_move = {empty, Pt(-1, -1), Pt(-1, -1)}; // doesn't matter + for (x=0; x<8; x++) for (y=0; y<8; y++) { + if (board[y][x] && ismove(board, Pt(x, y), king_pos, prev_move)) { + // this piece could take the king so we are in check + return 1; + } + } + return 0; +} + +MoveType +islegal(int board[8][8], Point src, Point dst, Move prev_move, CastleRights cr) +{ + int tmpboard[8][8]; + memcpy(tmpboard, board, sizeof(int)*8*8); + MoveType move_type = ismove(board, src, dst, prev_move); + if (move_type == Invalid) { + return Invalid; + } + Point s1, s2; + if (move_type == BlackShortCastle) { + if (cr.bK_ever_moved || cr.bRH_ever_moved) return Invalid; + s1 = Pt(fF, RANK(8)); + s2 = Pt(fG, RANK(8)); + } else if (move_type == BlackLongCastle) { + if (cr.bK_ever_moved || cr.bRA_ever_moved) return Invalid; + s1 = Pt(fD, RANK(8)); + s2 = Pt(fC, RANK(8)); + } else if (move_type == WhiteShortCastle) { + if (cr.wK_ever_moved || cr.wRH_ever_moved) return Invalid; + s1 = Pt(fF, RANK(1)); + s2 = Pt(fG, RANK(1)); + } else if (move_type == WhiteLongCastle) { + if (cr.wK_ever_moved || cr.wRA_ever_moved) return Invalid; + s1 = Pt(fD, RANK(1)); + s2 = Pt(fC, RANK(1)); + } + if (move_type >= BlackShortCastle) { // we check all castling the same + if (islegal(tmpboard, src, s1, prev_move, cr)) { + do_move(tmpboard, src, s1, Normal); + if (islegal(tmpboard, s1, s2, prev_move, cr)) { + // if the king could move there in two steps, + // without going through check, then it's ok + return move_type; + } + } + return Invalid; + } + Player current = owner(BRDPT(src)); + // pretend to play the move, test if we are in check now + do_move(tmpboard, src, dst, move_type); + if (is_in_check(tmpboard, current)) { + return Invalid; + } + return move_type; +} + +void +threadmain(int, char **) +{ + print("starting chess...\n"); + + resizewin(); + + if (initdraw(nil, nil, "chess") < 0) + sysfatal("init failed: %r"); + + resizewin(); + + // FETCH ASSETS + + //int width = Dx(screen->r); + //int height = Dy(screen->r); + Image* pieceimg[12]; + for (int i=bB; i<=wR; i++) { + char* fn = "AA.image"; // build the filename + fn[0] = PIECE_STR[i][0]; + fn[1] = PIECE_STR[i][1]; + int piecefd = open(fn, OREAD); + if (piecefd < 0) + sysfatal("cannot open piece image: %r"); + pieceimg[i] = readimage(display, piecefd, 0); + if (pieceimg[i] == nil) + sysfatal("cannot read piece image: %r"); + close(piecefd); + } + Image* black = allocimagemix(display, DBlack, DBlack); + Image* white = allocimagemix(display, DWhite, DWhite); + Image* dark = allocimagemix(display, DRed, DDarkyellow); + Image* light = allocimagemix(display, DWhite, DWhite); + Image* bordercolor = allocimagemix(display, DWhite, DBlack); + Image* selcolor = allocimagemix(display, DWhite, DRed); + Image* poscolor = allocimagemix(display, DBlue, DWhite); + + /* + int pawnfd = open("pawn.image", OREAD); + Image *pawn = readimage(display, pawnfd, 0); + close(pawnfd); + // invert to make a mask + Rectangle r50 = Rpt(ZP, Pt(50, 50)); + Image* pawnmask = allocimage(display, r50, RGB24, 0, DWhite); + draw(pawnmask, r50, black, pawn, ZP); + */ + + + // SETUP EVENTS + + Mousectl *mousectl; + if ((mousectl = initmouse(nil, screen)) == nil) + sysfatal("cannot init mouse: %r"); + + Keyboardctl *keyboardctl; + if ((keyboardctl = initkeyboard(nil)) == nil) + sysfatal("cannot init keyboard: %r"); + + enum { + Emouse, + Eresize, + Ekbd, + }; + Rune key; + Mouse mouse; + Alt a[] = { + { nil, &mouse, CHANRCV }, + { nil, nil, CHANRCV }, + { nil, &key, CHANRCV }, + { nil, nil, CHANEND } + }; + a[Emouse].c = mousectl->c; + a[Eresize].c = mousectl->resizec; + a[Ekbd].c = keyboardctl->c; + + dbgstring[0] = 0; + + int click = 0; + int prev_click = 0; + + // SETUP GAME STATE + int board[8][8]; + resetboard(board); + Player turn = White; + + Point selected = Pt(-1, -1); + Move prev_move = {empty, Pt(-1, -1), Pt(-1, -1)}; + CastleRights cr; + memset(&cr, 0, sizeof(cr)); + + // MAIN LOOP + for(;;) { + // RENDER + draw(screen, screen->r, white, nil, ZP); + Rectangle board_r = Rpt(addpt(screen->r.min, Pt(10, 10)), + addpt(screen->r.min, Pt(10+50*8, 10+50*8))); + Rectangle border = Rpt(screen->r.min, + addpt(screen->r.min, Pt(20+50*8, 20+50*8))); + draw(screen, border, bordercolor, nil, ZP); + Rectangle r; + for (int y=0; y<8; y++) for (int x=0; x<8; x++) { + r = Rpt(addpt(board_r.min, Pt(50*x, 50*y)), + addpt(board_r.min, Pt(50*x+50, 50*y+50))); + if ((x+y) & 1) + draw(screen, r, dark, nil, ZP); + else + draw(screen, r, light, nil, ZP); + + if (eqpt(Pt(x, y), selected)) { + draw(screen, r, selcolor, nil, ZP); + } + if (!eqpt(selected, Pt(-1, -1)) && islegal(board, selected, Pt(x, y), prev_move, cr)) { + draw(screen, r, poscolor, nil, ZP); + } + + r = Rpt(addpt(r.min, Pt(5, 5)), r.max); + int piece_n = board[y][x]; + if (piece_n) { + Image* piece_i = pieceimg[piece_n]; + draw(screen, r, piece_i, 0, ZP); + } + } + for (int i=0; i<8; i++) { + // draw the rank numbers + char buf; + //sprint(buf, "%d", 8-i); + buf = '8'-i; + stringn(screen, addpt(board_r.min, Pt(2, 50*i+2)), + black, ZP, font, &buf, 1); + // and the file letters + buf = 'A' + i; + stringn(screen, addpt(board_r.min, Pt(50*i+2, 50*7+35)), + black, ZP, font, &buf, 1); + } + string(screen, addpt(screen->r.min, Pt(50*8+25, 5)), + black, ZP, font, dbgstring); + flushimage(display, 1); + + + // PROCESS EVENTS + Point hovered; + switch (alt(a)) { + case Eresize: + if (getwindow(display, Refnone) < 0) + sysfatal("resize failed: %r"); + break; + case Ekbd: + switch (key) { + case 'q': + print("bye!\n"); + threadexitsall(nil); + break; + } + break; + case Emouse: + //dbgstring[0] = 0; + if (ptinrect(mouse.xy, board_r)) { + hovered = divpt(subpt(mouse.xy, board_r.min), 50); + //sprint(dbgstring, "%d, %d, %d", hovered.x, hovered.y, BRDPT(hovered)); + int src_piece = empty; + int dst_piece = empty; + if (!eqpt(selected, Pt(-1, -1))) { + src_piece = BRDPT(selected); + } + if (!eqpt(hovered, Pt(-1, -1))) { + dst_piece = BRDPT(hovered); + } + click = mouse.buttons & 1; + if (click && !prev_click) { + MoveType move_type; + if (eqpt(hovered, selected)) { // unselect + selected = Pt(-1, -1); + } else if (dst_piece && owner(dst_piece) == turn + && (!src_piece || owner(src_piece) == owner(dst_piece))) { + selected = hovered; // select + } else if (src_piece && (move_type = islegal(board, selected, hovered, prev_move, cr))) { + // move + prev_move.piece = src_piece; + prev_move.src = selected; + prev_move.dst = hovered; + do_move(board, selected, hovered, move_type); + selected = Pt(-1, -1); // unselect + if (src_piece == bK) cr.bK_ever_moved = 1; + if (src_piece == wK) cr.wK_ever_moved = 1; + if (eqpt(prev_move.src, Pt(0, 0)) || eqpt(prev_move.dst, Pt(0, 0))) + cr.bRA_ever_moved = 1; + if (eqpt(prev_move.src, Pt(0, 7)) || eqpt(prev_move.dst, Pt(0, 7))) + cr.wRA_ever_moved = 1; + if (eqpt(prev_move.src, Pt(7, 0)) || eqpt(prev_move.dst, Pt(7, 0))) + cr.bRH_ever_moved = 1; + if (eqpt(prev_move.src, Pt(7, 7)) || eqpt(prev_move.dst, Pt(7, 7))) + cr.wRH_ever_moved = 1; + turn = !turn; + } + } + prev_click = click; + } + break; + } + } +} @@ -0,0 +1,27 @@ +</$objtype/mkfile + +CFLAGS=-FTVwN + +TARG=chess +OFILES=\ + chess.$O\ + +HFILES= + +IMAGES=`{ls png | sed 's|png/(..).png|\1.image|'} + +BIN=/$objtype/bin + +</sys/src/cmd/mkone + +$O.out: $OFILES + $LD $LDFLAGS -o $O.out $OFILES + +%.image : png/%.png + png -t -c $prereq > $target + +images:V: $IMAGES + +all:V: $O.out images + +default:V: all
\ No newline at end of file diff --git a/png/bB.png b/png/bB.png Binary files differnew file mode 100644 index 0000000..3b78023 --- /dev/null +++ b/png/bB.png diff --git a/png/bK.png b/png/bK.png Binary files differnew file mode 100644 index 0000000..8af8c01 --- /dev/null +++ b/png/bK.png diff --git a/png/bN.png b/png/bN.png Binary files differnew file mode 100644 index 0000000..76b7615 --- /dev/null +++ b/png/bN.png diff --git a/png/bP.png b/png/bP.png Binary files differnew file mode 100644 index 0000000..ebd2480 --- /dev/null +++ b/png/bP.png diff --git a/png/bQ.png b/png/bQ.png Binary files differnew file mode 100644 index 0000000..18d5479 --- /dev/null +++ b/png/bQ.png diff --git a/png/bR.png b/png/bR.png Binary files differnew file mode 100644 index 0000000..7578040 --- /dev/null +++ b/png/bR.png diff --git a/png/wB.png b/png/wB.png Binary files differnew file mode 100644 index 0000000..4a0642f --- /dev/null +++ b/png/wB.png diff --git a/png/wK.png b/png/wK.png Binary files differnew file mode 100644 index 0000000..471c10c --- /dev/null +++ b/png/wK.png diff --git a/png/wN.png b/png/wN.png Binary files differnew file mode 100644 index 0000000..30b2d15 --- /dev/null +++ b/png/wN.png diff --git a/png/wP.png b/png/wP.png Binary files differnew file mode 100644 index 0000000..3281921 --- /dev/null +++ b/png/wP.png diff --git a/png/wQ.png b/png/wQ.png Binary files differnew file mode 100644 index 0000000..37b0802 --- /dev/null +++ b/png/wQ.png diff --git a/png/wR.png b/png/wR.png Binary files differnew file mode 100644 index 0000000..1cc1bc1 --- /dev/null +++ b/png/wR.png |