Simplified Chess

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
Greg Strong
Posts: 388
Joined: Sun Dec 21, 2008 6:57 pm
Location: Washington, DC

Simplified Chess

Post by Greg Strong »

In another topic, Lucas said:
lucasart wrote:Typically people invent new variants by adding tons of extremely obscure rules to the game of chess. Often these new rules are so poorly defined that it's really unclear how to implement them. I wish they would instead rethink chess from the start and simplify it rather than just add new rules on top (that badly clash with existing ones).
While I do not think his criticisms apply to all chess variants, I accept the challenge nonetheless. After thinking about it, here's my submission for a chess variant that is simpler than chess...

- NO CHECK OR CHECKMATE. The King is just a normal piece.

- NO TWO-SPACE PAWN MOVE, NO EN PASSANT. Instead, the pawns begin on the third rank.

- NO CASTLING.

- NO PAWN PROMOTION.

That eliminates almost all the wacky rules of Chess except for the 3-fold repetition and 50-move draw rules (which remain in effect.)

Since the King is just a normal piece, the new victory condition is getting a pawn to the eighth rank. Specifically:

- A player who moves a pawn to the last rank wins unless the opponent can capture it immediately (which he would be forced to do.)

- A player who loses his last pawn loses the game unless he can immediately capture the opponent's last pawn (which he would be forced to do, resulting in a drawn game.)

NOTE: I make no claims that this game is an improvement over orthodox chess, but it should be a reasonable, playable game. It is not difficult to program a computer to play, and it is probably an easier game than chess to teach to children...
Isaac
Posts: 265
Joined: Sat Feb 22, 2014 8:37 pm

Re: Simplified Chess

Post by Isaac »

Nice idea.
To me the 2 rules you defined to claim for a winner are more complicated than the checkmate in normal chess. So you simplified the checkmate "rule" by introducing 2 other rules that complicate things.
AlvaroBegue
Posts: 931
Joined: Tue Mar 09, 2010 3:46 pm
Location: New York
Full name: Álvaro Begué (RuyDos)

Re: Simplified Chess

Post by AlvaroBegue »

I like "No two-space pawn move, no en passant; instead, the pawns begin on the third rank" and "No castling". This is particularly good because it gets rid of a lot of game state that is not reflected on the board.

Stalemate is an abomination: "You lose if your king is captured or you don't have any moves available" is a more natural rule, and the game would retain a lot of the flavor of chess.

Three-fold repetition is mathematically inelegant, and you can replace it with "A repeated position with the same side to move is a draw".

The 50-move rule can stay, but I would change it to mean it's an automatic draw, not some right for either side to claim a draw.

Are there any other dark corners of the game that I am forgetting?
User avatar
Evert
Posts: 2929
Joined: Sat Jan 22, 2011 12:42 am
Location: NL

Re: Simplified Chess

Post by Evert »

Greg Strong wrote: - NO CHECK OR CHECKMATE. The King is just a normal piece.
I think having a king and the concept of check or mate is really the defining feature of a chess variant. I wouldn't get rid of it; it's not a particularly cumbersome rule either.
- NO TWO-SPACE PAWN MOVE, NO EN PASSANT. Instead, the pawns begin on the third rank.
This one is interesting.
- NO CASTLING.
As is this, but it probably only works because of the absence of check: otherwise the king is too exposed.
- NO PAWN PROMOTION.
Makes sense given the victory conditions, but personally I'd say that promotions are also an integral part of chess and chess variants...
NOTE: I make no claims that this game is an improvement over orthodox chess, but it should be a reasonable, playable game. It is not difficult to program a computer to play, and it is probably an easier game than chess to teach to children...
That is probably true. I think I can have Sjaak play this fairly easily: I don't remember if what it calls "capture the flag" victory conditions can be applied to a single piece. If it can I can implement it immediately, apart from the "baring rule" that says you lose if you lose your last pawn. But I also think that rule just makes things more complicated again...
ZirconiumX
Posts: 1334
Joined: Sun Jul 17, 2011 11:14 am

Re: Simplified Chess

Post by ZirconiumX »

I modified Sunfish to play Simplified Chess. It probably is not very strong at it, but it will make a suitable sparring partner.

Probably I need to bump the Pawn value up, and the King value down.

Code: Select all

#!/usr/bin/env pypy
# -*- coding: utf-8 -*-

from __future__ import print_function
import sys
from itertools import count
from collections import Counter, OrderedDict, namedtuple

# The table size is the maximum number of elements in the transposition table.
TABLE_SIZE = 1e6

# This constant controls how much time we spend on looking for optimal moves.
NODES_SEARCHED = 1e4

# Win value must be greater than 8*queen + 2*(rook+knight+bishop)
# King value is set to twice this value such that if the opponent is
# 8 queens up, but we got the king, we still exceed WIN_VALUE.
WIN_VALUE = 30000

# Our board is represented as a 120 character string. The padding allows for
# fast detection of moves that don't stay within the board.
A1, H1, A8, H8 = 91, 98, 21, 28
initial = (
    '         \n'  #   0 -  9
    '         \n'  #  10 - 19
    ' rnbqkbnr\n'  #  20 - 29
    ' ........\n'  #  30 - 39
    ' pppppppp\n'  #  40 - 49
    ' ........\n'  #  50 - 59
    ' ........\n'  #  60 - 69
    ' PPPPPPPP\n'  #  70 - 79
    ' ........\n'  #  80 - 89
    ' RNBQKBNR\n'  #  90 - 99
    '         \n'  # 100 -109
    '          '   # 110 -119
)

###############################################################################
# Move and evaluation tables
###############################################################################

N, E, S, W = -10, 1, 10, -1
directions = {
    'P': (N, N+W, N+E),
    'N': (2*N+E, N+2*E, S+2*E, 2*S+E, 2*S+W, S+2*W, N+2*W, 2*N+W),
    'B': (N+E, S+E, S+W, N+W),
    'R': (N, E, S, W),
    'Q': (N, E, S, W, N+E, S+E, S+W, N+W),
    'K': (N, E, S, W, N+E, S+E, S+W, N+W)
}

pst = {
    'P': (0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 198, 198, 198, 198, 198, 198, 198, 198, 0,
        0, 178, 198, 198, 198, 198, 198, 198, 178, 0,
        0, 178, 198, 198, 198, 198, 198, 198, 178, 0,
        0, 178, 198, 208, 218, 218, 208, 198, 178, 0,
        0, 178, 198, 218, 238, 238, 218, 198, 178, 0,
        0, 178, 198, 208, 218, 218, 208, 198, 178, 0,
        0, 178, 198, 198, 198, 198, 198, 198, 178, 0,
        0, 198, 198, 198, 198, 198, 198, 198, 198, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    'B': (
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 797, 824, 817, 808, 808, 817, 824, 797, 0,
        0, 814, 841, 834, 825, 825, 834, 841, 814, 0,
        0, 818, 845, 838, 829, 829, 838, 845, 818, 0,
        0, 824, 851, 844, 835, 835, 844, 851, 824, 0,
        0, 827, 854, 847, 838, 838, 847, 854, 827, 0,
        0, 826, 853, 846, 837, 837, 846, 853, 826, 0,
        0, 817, 844, 837, 828, 828, 837, 844, 817, 0,
        0, 792, 819, 812, 803, 803, 812, 819, 792, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    'N': (0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 627, 762, 786, 798, 798, 786, 762, 627, 0,
        0, 763, 798, 822, 834, 834, 822, 798, 763, 0,
        0, 817, 852, 876, 888, 888, 876, 852, 817, 0,
        0, 797, 832, 856, 868, 868, 856, 832, 797, 0,
        0, 799, 834, 858, 870, 870, 858, 834, 799, 0,
        0, 758, 793, 817, 829, 829, 817, 793, 758, 0,
        0, 739, 774, 798, 810, 810, 798, 774, 739, 0,
        0, 683, 718, 742, 754, 754, 742, 718, 683, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    'R': (0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 1258, 1263, 1268, 1272, 1272, 1268, 1263, 1258, 0,
        0, 1258, 1263, 1268, 1272, 1272, 1268, 1263, 1258, 0,
        0, 1258, 1263, 1268, 1272, 1272, 1268, 1263, 1258, 0,
        0, 1258, 1263, 1268, 1272, 1272, 1268, 1263, 1258, 0,
        0, 1258, 1263, 1268, 1272, 1272, 1268, 1263, 1258, 0,
        0, 1258, 1263, 1268, 1272, 1272, 1268, 1263, 1258, 0,
        0, 1258, 1263, 1268, 1272, 1272, 1268, 1263, 1258, 0,
        0, 1258, 1263, 1268, 1272, 1272, 1268, 1263, 1258, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    'Q': (0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 0,
        0, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 0,
        0, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 0,
        0, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 0,
        0, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 0,
        0, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 0,
        0, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 0,
        0, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 2529, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    'K': (0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 6098, 6132, 6073, 6025, 6025, 6073, 6132, 6098, 0,
        0, 6119, 6153, 6094, 6046, 6046, 6094, 6153, 6119, 0,
        0, 6146, 6180, 6121, 6073, 6073, 6121, 6180, 6146, 0,
        0, 6173, 6207, 6148, 6100, 6100, 6148, 6207, 6173, 0,
        0, 6196, 6230, 6171, 6123, 6123, 6171, 6230, 6196, 0,
        0, 6224, 6258, 6199, 6151, 6151, 6199, 6258, 6224, 0,
        0, 6287, 6321, 6262, 6214, 6214, 6262, 6321, 6287, 0,
        0, 6298, 6332, 6273, 6225, 6225, 6273, 6332, 6298, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
}


###############################################################################
# Chess logic
###############################################################################

class Position(namedtuple('Position', 'board score')):
    """ A state of a chess game
    board -- a 120 char representation of the board
    score -- the board evaluation
    wc -- the castling rights
    bc -- the opponent castling rights
    ep - the en passant square
    kp - the king passant square
    """

    def genMoves(self):
        # For each of our pieces, iterate through each possible 'ray' of moves,
        # as defined in the 'directions' map. The rays are broken e.g. by
        # captures or immediately in case of pieces such as knights.
        for i, p in enumerate(self.board):
            if not p.isupper(): continue
            for d in directions[p]:
                for j in count(i+d, d):
                    q = self.board[j]
                    # Stay inside the board
                    if self.board[j].isspace(): break
                    # No friendly captures
                    if q.isupper(): break
                    # Special pawn stuff
                    if p == 'P' and d in (N+W, N+E) and q == '.': break
                    if p == 'P' and d == N and q != '.': break
                    # Move it
                    yield (i, j)
                    # Stop crawlers from sliding
                    if p in ('P', 'N', 'K'): break
                    # No sliding after captures
                    if q.islower(): break

    def rotate(self):
        return Position(
            self.board[::-1].swapcase(), -self.score)

    def move(self, move):
        i, j = move
        p, q = self.board[i], self.board[j]
        put = lambda board, i, p: board[:i] + p + board[i+1:]
        # Copy variables and reset ep and kp
        board = self.board
        score = self.score + self.value(move)
        # Actual move
        board = put(board, j, board[i])
        board = put(board, i, '.')
        # Special pawn stuff
        if p == 'P':
            if A8 <= j <= H8&#58;
                score += WIN_VALUE
        # We rotate the returned position, so it's ready for the next player
        return Position&#40;board, score&#41;.rotate&#40;)

    def value&#40;self, move&#41;&#58;
        i, j = move
        p, q = self.board&#91;i&#93;, self.board&#91;j&#93;
        # Actual move
        score = pst&#91;p&#93;&#91;j&#93; - pst&#91;p&#93;&#91;i&#93;
        # Capture
        if q.islower&#40;)&#58;
            score += pst&#91;q.upper&#40;)&#93;&#91;j&#93;
        # Special pawn stuff
        if p == 'P'&#58;
            if A8 <= j <= H8&#58;
                score += WIN_VALUE
        return score

Entry = namedtuple&#40;'Entry', 'depth score gamma move')
tp = OrderedDict&#40;)


###############################################################################
# Search logic
###############################################################################

nodes = 0
def bound&#40;pos, gamma, depth&#41;&#58;
    """ returns s&#40;pos&#41; <= r < gamma    if s&#40;pos&#41; < gamma
        returns s&#40;pos&#41; >= r >= gamma   if s&#40;pos&#41; >= gamma """
    global nodes; nodes += 1

    # Look in the table if we have already searched this position before.
    # We use the table value if it was done with at least as deep a search
    # as ours, and the gamma value is compatible.
    entry = tp.get&#40;pos&#41;
    if entry is not None and entry.depth >= depth and (
            entry.score < entry.gamma and entry.score < gamma or
            entry.score >= entry.gamma and entry.score >= gamma&#41;&#58;
        return entry.score

    # Stop searching if we have won/lost.
    if abs&#40;pos.score&#41; >= WIN_VALUE&#58;
        return pos.score

    # Null move. Is also used for stalemate checking
    nullscore = -bound&#40;pos.rotate&#40;), 1-gamma, depth-3&#41; if depth > 0 else pos.score
    if nullscore >= gamma&#58;
        return nullscore

    # We generate all possible, pseudo legal moves and order them to provoke
    # cuts. At the next level of the tree we are going to minimize the score.
    # This can be shown equal to maximizing the negative score, with a slightly
    # adjusted gamma value.
    best, bmove = -3*WIN_VALUE, None
    for move in sorted&#40;pos.genMoves&#40;), key=pos.value, reverse=True&#41;&#58;
        # We check captures with the value function, as it also contains ep and kp
        if depth <= 0 and pos.value&#40;move&#41; < 150&#58;
            break
        score = -bound&#40;pos.move&#40;move&#41;, 1-gamma, depth-1&#41;
        if score > best&#58;
            best = score
            bmove = move
        if score >= gamma&#58;
            break

    # If there are no captures, or just not any good ones, stand pat
    if depth <= 0 and best < nullscore&#58;
        return nullscore
    # Check for stalemate. If best move loses king, but not doing anything
    # would save us. Not at all a perfect check.
    if depth > 0 and best <= -WIN_VALUE is None and nullscore > -WIN_VALUE&#58;
        best = 0

    # We save the found move together with the score, so we can retrieve it in
    # the play loop. We also trim the transposition table in FILO order.
    # We prefer fail-high moves, as they are the ones we can build our pv from.
    if entry is None or depth >= entry.depth and best >= gamma&#58;
        tp&#91;pos&#93; = Entry&#40;depth, best, gamma, bmove&#41;
        if len&#40;tp&#41; > TABLE_SIZE&#58;
            tp.pop&#40;)
    return best


def search&#40;pos, maxn=NODES_SEARCHED&#41;&#58;
    """ Iterative deepening MTD-bi search """
    global nodes; nodes = 0

    # We limit the depth to some constant, so we don't get a stack overflow in
    # the end game.
    for depth in range&#40;1, 99&#41;&#58;
        # The inner loop is a binary search on the score of the position.
        # Inv&#58; lower <= score <= upper
        # However this may be broken by values from the transposition table,
        # as they don't have the same concept of p&#40;score&#41;. Hence we just use
        # 'lower < upper - margin' as the loop condition.
        lower, upper = -3*WIN_VALUE, 3*WIN_VALUE
        while lower < upper - 3&#58;
            gamma = &#40;lower+upper+1&#41;//2
            score = bound&#40;pos, gamma, depth&#41;
            if score >= gamma&#58;
                lower = score
            if score < gamma&#58;
                upper = score
        
        # print&#40;"Searched %d nodes. Depth %d. Score %d&#40;%d/%d&#41;" % &#40;nodes, depth, score, lower, upper&#41;)

        # We stop deepening if the global N counter shows we have spent too
        # long, or if we have already won the game.
        if nodes >= maxn or abs&#40;score&#41; >= WIN_VALUE&#58;
            break

    # If the game hasn't finished we can retrieve our move from the
    # transposition table.
    entry = tp.get&#40;pos&#41;
    if entry is not None&#58;
        return entry.move, score
    return None, score


###############################################################################
# User interface
###############################################################################

# Python 2 compatability
if sys.version_info&#91;0&#93; == 2&#58;
    input = raw_input


def parse&#40;c&#41;&#58;
    fil, rank = ord&#40;c&#91;0&#93;) - ord&#40;'a'), int&#40;c&#91;1&#93;) - 1
    return A1 + fil - 10*rank


def render&#40;i&#41;&#58;
    rank, fil = divmod&#40;i - A1, 10&#41;
    return chr&#40;fil + ord&#40;'a')) + str&#40;-rank + 1&#41;


def main&#40;)&#58;
    pos = Position&#40;initial, 0&#41;
    while True&#58;
        # We add some spaces to the board before we print it.
        # That makes it more readable and pleasing.
        print&#40;' '.join&#40;pos.board&#41;)

        # We query the user until she enters a legal move.
        move = None
        while move not in pos.genMoves&#40;)&#58;
            crdn = input&#40;"Your move&#58; ")
            move = parse&#40;crdn&#91;0&#58;2&#93;), parse&#40;crdn&#91;2&#58;4&#93;)
        pos = pos.move&#40;move&#41;

        # After our move we rotate the board and print it again.
        # This allows us to see the effect of our move.
        print&#40;' '.join&#40;pos.rotate&#40;).board&#41;)

        # Fire up the engine to look for a move.
        move, score = search&#40;pos&#41;
        if score <= -WIN_VALUE&#58;
            print&#40;"You won")
            break
        if score >= WIN_VALUE&#58;
            print&#40;"You lost")
            break

        # The black player moves from a rotated position, so we have to
        # 'back rotate' the move before printing it.
        print&#40;"My move&#58;", render&#40;119-move&#91;0&#93;) + render&#40;119-move&#91;1&#93;))
        pos = pos.move&#40;move&#41;


if __name__ == '__main__'&#58;
    main&#40;)
Matthew:out
Some believe in the almighty dollar.

I believe in the almighty printf statement.
User avatar
Greg Strong
Posts: 388
Joined: Sun Dec 21, 2008 6:57 pm
Location: Washington, DC

Re: Simplified Chess

Post by Greg Strong »

Isaac wrote:Nice idea.
To me the 2 rules you defined to claim for a winner are more complicated than the checkmate in normal chess. So you simplified the checkmate "rule" by introducing 2 other rules that complicate things.
I'm not sure this is that much more complicated than check/checkmate/stalemate ... Get a pawn to the end and win; lose your last pawn and lose. The only complication is that your opponent has one move to resolve the situation (which, although it complicates things, I think is necessary for a balanced game.)

I felt the victory condition needed to be changed when castling was eliminated - otherwise the king is too exposed, and stalemate really needed to be eliminated...
User avatar
Greg Strong
Posts: 388
Joined: Sun Dec 21, 2008 6:57 pm
Location: Washington, DC

Re: Simplified Chess

Post by Greg Strong »

ZirconiumX wrote:I modified Sunfish to play Simplified Chess. It probably is not very strong at it, but it will make a suitable sparring partner.
Wow. Thanks :)
User avatar
Greg Strong
Posts: 388
Joined: Sun Dec 21, 2008 6:57 pm
Location: Washington, DC

Re: Simplified Chess

Post by Greg Strong »

Well, it turns out the name Simplified Chess is already used (http://www.chessvariants.org/index/msdi ... lifiedches). I should have guessed :roll: This version, instead of putting the pawns on the third rank, shortens the board to seven ranks. The victory condition is king capture, but without castling the king is somewhat exposed.

I'll need to rename this game. Basic chess is also taken. Maybe Abridged Chess? Or Stoic Chess? Chessito? I'm open to suggestions.
ZirconiumX
Posts: 1334
Joined: Sun Jul 17, 2011 11:14 am

Re: Simplified Chess

Post by ZirconiumX »

Greg Strong wrote:Well, it turns out the name Simplified Chess is already used (http://www.chessvariants.org/index/msdi ... lifiedches). I should have guessed :roll: This version, instead of putting the pawns on the third rank, shortens the board to seven ranks. The victory condition is king capture, but without castling the king is somewhat exposed.

I'll need to rename this game. Basic chess is also taken. Maybe Abridged Chess? Or Stoic Chess? Chessito? I'm open to suggestions.
Slight starcraft reference - Pawn Rush?

Matthew:out
Some believe in the almighty dollar.

I believe in the almighty printf statement.
Dirt
Posts: 2851
Joined: Wed Mar 08, 2006 10:01 pm
Location: Irvine, CA, USA

Re: Simplified Chess

Post by Dirt »

Greg Strong wrote:I'll need to rename this game. Basic chess is also taken. Maybe Abridged Chess? Or Stoic Chess? Chessito? I'm open to suggestions.
Your winning condition is similar to Arimaa, so Chessima?