summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaume Delclòs Coll <cosa@cosarara.me>2024-05-07 23:40:54 +0000
committerJaume Delclòs Coll <cosa@cosarara.me>2024-05-08 01:53:14 +0200
commitff6cb0538290005415a435812a2f36c79c0dfc6f (patch)
treed2bc877ff374692310e6741494587b04f8329a26
initial
-rw-r--r--chess.c562
-rw-r--r--mkfile27
-rw-r--r--png/bB.pngbin0 -> 915 bytes
-rw-r--r--png/bK.pngbin0 -> 1780 bytes
-rw-r--r--png/bN.pngbin0 -> 1128 bytes
-rw-r--r--png/bP.pngbin0 -> 621 bytes
-rw-r--r--png/bQ.pngbin0 -> 1596 bytes
-rw-r--r--png/bR.pngbin0 -> 576 bytes
-rw-r--r--png/wB.pngbin0 -> 1314 bytes
-rw-r--r--png/wK.pngbin0 -> 1673 bytes
-rw-r--r--png/wN.pngbin0 -> 1306 bytes
-rw-r--r--png/wP.pngbin0 -> 945 bytes
-rw-r--r--png/wQ.pngbin0 -> 2067 bytes
-rw-r--r--png/wR.pngbin0 -> 851 bytes
14 files changed, 589 insertions, 0 deletions
diff --git a/chess.c b/chess.c
new file mode 100644
index 0000000..1b1f297
--- /dev/null
+++ b/chess.c
@@ -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;
+ }
+ }
+}
diff --git a/mkfile b/mkfile
new file mode 100644
index 0000000..ed083b8
--- /dev/null
+++ b/mkfile
@@ -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
new file mode 100644
index 0000000..3b78023
--- /dev/null
+++ b/png/bB.png
Binary files differ
diff --git a/png/bK.png b/png/bK.png
new file mode 100644
index 0000000..8af8c01
--- /dev/null
+++ b/png/bK.png
Binary files differ
diff --git a/png/bN.png b/png/bN.png
new file mode 100644
index 0000000..76b7615
--- /dev/null
+++ b/png/bN.png
Binary files differ
diff --git a/png/bP.png b/png/bP.png
new file mode 100644
index 0000000..ebd2480
--- /dev/null
+++ b/png/bP.png
Binary files differ
diff --git a/png/bQ.png b/png/bQ.png
new file mode 100644
index 0000000..18d5479
--- /dev/null
+++ b/png/bQ.png
Binary files differ
diff --git a/png/bR.png b/png/bR.png
new file mode 100644
index 0000000..7578040
--- /dev/null
+++ b/png/bR.png
Binary files differ
diff --git a/png/wB.png b/png/wB.png
new file mode 100644
index 0000000..4a0642f
--- /dev/null
+++ b/png/wB.png
Binary files differ
diff --git a/png/wK.png b/png/wK.png
new file mode 100644
index 0000000..471c10c
--- /dev/null
+++ b/png/wK.png
Binary files differ
diff --git a/png/wN.png b/png/wN.png
new file mode 100644
index 0000000..30b2d15
--- /dev/null
+++ b/png/wN.png
Binary files differ
diff --git a/png/wP.png b/png/wP.png
new file mode 100644
index 0000000..3281921
--- /dev/null
+++ b/png/wP.png
Binary files differ
diff --git a/png/wQ.png b/png/wQ.png
new file mode 100644
index 0000000..37b0802
--- /dev/null
+++ b/png/wQ.png
Binary files differ
diff --git a/png/wR.png b/png/wR.png
new file mode 100644
index 0000000..1cc1bc1
--- /dev/null
+++ b/png/wR.png
Binary files differ