Skip to content

Commit

Permalink
added a really stupid AI :)
Browse files Browse the repository at this point in the history
  • Loading branch information
tux21b committed May 6, 2012
1 parent 2e2adfb commit 62e1738
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 45 deletions.
4 changes: 2 additions & 2 deletions chess.css
Expand Up @@ -143,13 +143,13 @@ div.dialog p {
padding: 0 0 .38em;
}

div#dlg-waiting,
div#dlg-result {
height: 5.292em;
margin-top: -2.646em;
}

div#dlg-connect,
div#dlg-waiting {
div#dlg-connect {
height: 3.78em;
margin-top: -1.89em;
}
3 changes: 2 additions & 1 deletion chess.html
Expand Up @@ -43,6 +43,7 @@ <h3 id="connect">Connecting…</h3>
</div>
<div id="dlg-waiting" class="dialog">
<h3>Waiting for another player…</h3>
<p>(or <a href="/ai">play against the computer</a>)</p>
</div>
<div id="dlg-result" class="dialog">
<h3 id="result">Checkmate: White wins!</h3>
Expand All @@ -55,7 +56,7 @@ <h3 id="result">Checkmate: White wins!</h3>
<script type="text/javascript">
var canvas = document.getElementById("board");
var clocks = document.getElementById("clocks");
var chess = new ChessGame(canvas, clocks, "ws://{{.}}/ws");
var chess = new ChessGame(canvas, clocks, "{{.}}");
</script>

<footer>
Expand Down
2 changes: 1 addition & 1 deletion chess.js
Expand Up @@ -411,7 +411,7 @@ ChessGame.prototype.process = function(e) {
this.color = 0;
}
else if (msg.cmd == "ping") {
this.ws.send(JSON.stringify({Cmd: "pong"}));
this.ws.send(JSON.stringify({cmd: "pong"}));
}
else if (msg.cmd == "stat") {
document.getElementById("numPlayers").innerHTML = msg.NumPlayers;
Expand Down
75 changes: 75 additions & 0 deletions chess/ai.go
@@ -0,0 +1,75 @@
// Copyright (c) 2012 by Christoph Hack <christoph@tux21b.org>
// All rights reserved. Distributed under the Simplified BSD License.

package chess

import (
"math"
"math/rand"
)

func (b *Board) MoveAI() (src, dst Square) {
src, dst, _ = b.negaMax(4)
return
}

func (b *Board) negaMax(depth int) (bsrc, bdst Square, max float64) {
if depth <= 0 {
max = b.evaluate()
return
}

max = math.Inf(-1)
src := Square(rand.Intn(64))
for i := 0; i < 64; i++ {
src = (src + 1) % 64
if b.board[src]&ColorMask != b.color {
continue
}
dst := Square(rand.Intn(64))
for j := 0; j < 64; j++ {
dst = (dst + 1) % 64
if b.mayMove(src, dst) {

piece, victim := b.board[src], b.board[dst]
b.board[dst], b.board[src] = piece, 0
b.occupied &^= Bitboard(1) << uint(src)
b.occupied |= Bitboard(1) << uint(dst)

if !b.isCheck() {
b.color ^= ColorMask
_, _, score := b.negaMax(depth - 1)
score = -score
b.color ^= ColorMask

if score > max {
bsrc, bdst, max = src, dst, score
}
}

b.board[src], b.board[dst] = piece, victim
b.occupied |= Bitboard(1) << uint(src)
if victim == 0 {
b.occupied &^= Bitboard(1) << uint(dst)
}
}
}
}
return
}

func (b *Board) evaluate() float64 {
values := []float64{0, 1, 3, 3, 5, 9, 200}
score := 0.0
for p := Square(0); p < 64; p++ {
s := values[b.board[p]&PieceMask]
if (p>>3 == 0 || p>>3 == 7) && b.board[p]|PieceMask == P {
s = 9
}
if b.board[p]&ColorMask != b.color {
s = -s
}
score += s
}
return score
}
117 changes: 76 additions & 41 deletions main.go
Expand Up @@ -7,6 +7,7 @@ package main

import (
"code.google.com/p/go.net/websocket"
"expvar"
"flag"
"fmt"
"github.com/tux21b/ChessBuddy/chess"
Expand Down Expand Up @@ -42,6 +43,7 @@ type Player struct {
Color uint8
Remaining time.Duration
Out chan<- Message
ReqAI chan bool
}

// Check wethever the player is still connected by sending a ping command.
Expand All @@ -66,6 +68,12 @@ func (p *Player) String() string {
return "Unknown"
}

func (p *Player) Send(msg Message) {
if p.Conn != nil {
p.Out <- msg
}
}

// Available Players which are currently looking for a taff opponent.
var available = make(chan *Player, 100)

Expand All @@ -76,21 +84,30 @@ var numPlayers int32 = 0
func hookUp() {
a := <-available
for {
b := <-available
if a.Alive() {
go play(a, b)
select {
case b := <-available:
if a.Alive() {
go play(a, b)
a = <-available
} else {
close(a.Out)
a = b
}
case <-a.ReqAI:
go play(a, &Player{})
a = <-available
} else {
close(a.Out)
a = b
}
}
}

func play(a, b *Player) {
defer func() {
close(a.Out)
close(b.Out)
if a.Conn != nil {
close(a.Out)
}
if b.Conn != nil {
close(b.Out)
}
}()

log.Println("Starting new game")
Expand All @@ -99,38 +116,44 @@ func play(a, b *Player) {
if rand.Float32() > 0.5 {
a, b = b, a
}

a.Color = chess.White
b.Color = chess.Black
a.Remaining = *timeLimit
b.Color = chess.Black
b.Remaining = *timeLimit

a.Out <- Message{Cmd: "start", Color: a.Color, Turn: board.Turn(),
RemainingA: a.Remaining, RemainingB: b.Remaining}
b.Out <- Message{Cmd: "start", Color: b.Color, Turn: board.Turn(),
RemainingA: a.Remaining, RemainingB: b.Remaining}
a.Send(Message{Cmd: "start", Color: a.Color, Turn: board.Turn(),
RemainingA: a.Remaining, RemainingB: b.Remaining})
b.Send(Message{Cmd: "start", Color: b.Color, Turn: board.Turn(),
RemainingA: a.Remaining, RemainingB: b.Remaining})

start := time.Now()
for {
var msg Message
a.Conn.SetReadDeadline(start.Add(a.Remaining))
if err := websocket.JSON.Receive(a.Conn, &msg); err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
a.Remaining = 0
msg = Message{
Cmd: "msg",
Text: fmt.Sprintf("Out of time: %v wins!", b),
}
b.Out <- msg
a.Out <- msg
} else {
msg = Message{
Cmd: "msg",
Text: "Opponent quit... Reload?",
if a.Conn == nil {
msg.Cmd, msg.Turn = "move", board.Turn()
msg.Src, msg.Dst = board.MoveAI()
} else {
a.Conn.SetReadDeadline(start.Add(a.Remaining))
if err := websocket.JSON.Receive(a.Conn, &msg); err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
a.Remaining = 0
msg = Message{
Cmd: "msg",
Text: fmt.Sprintf("Out of time: %v wins!", b),
}
b.Send(msg)
a.Send(msg)
} else {
msg = Message{
Cmd: "msg",
Text: "Opponent quit... Reload?",
}
b.Send(msg)
a.Send(msg)
}
b.Out <- msg
a.Out <- msg
break
}
break
}
if msg.Cmd == "move" && msg.Turn == board.Turn() &&
a.Color == board.Color() && board.Move(msg.Src, msg.Dst) {
Expand All @@ -147,40 +170,43 @@ func play(a, b *Player) {
msg.RemainingA, msg.RemainingB = b.Remaining, a.Remaining
}
a, b = b, a
a.Out <- msg
b.Out <- msg
a.Send(msg)
b.Send(msg)

if board.Checkmate() {
msg = Message{
Cmd: "msg",
Text: fmt.Sprintf("Checkmate: %v wins!", b),
}
b.Out <- msg
a.Out <- msg
b.Send(msg)
a.Send(msg)
return
} else if board.Stalemate() {
msg = Message{
Cmd: "msg",
Text: fmt.Sprintf("Stalemate", b),
Text: "Stalemate",
}
b.Out <- msg
a.Out <- msg
b.Send(msg)
a.Send(msg)
return
}
} else if msg.Cmd == "select" {
msg.Moves = board.Moves(msg.Src)
a.Out <- msg
a.Send(msg)
}
}
}

// Serve the index page.
func handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
wsURL := fmt.Sprintf("ws://%s/ws", r.Host)
if r.URL.Path == "/ai" {
wsURL += "?ai=true"
} else if r.URL.Path != "/" {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
if err := tmpl.Execute(w, r.Host); err != nil {
if err := tmpl.Execute(w, wsURL); err != nil {
log.Printf("tmpl.Execute: %v", err)
}
}
Expand Down Expand Up @@ -232,8 +258,12 @@ func handleWS(ws *websocket.Conn) {

// Add the player to the pool of available players so that he can get
// hooked up
reqAI := make(chan bool, 1)
if ws.Request().FormValue("ai") == "true" {
reqAI <- true
}
out := make(chan Message, 1)
available <- &Player{Conn: ws, Out: out}
available <- &Player{Conn: ws, Out: out, ReqAI: reqAI}

// Send the move commands from the game asynchronously, so that a slow
// internet connection can not be simulated to use up the opponents
Expand All @@ -258,12 +288,17 @@ var listenAddr *string = flag.String("http", ":8000",

func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
rand.Seed(time.Now().UnixNano())
flag.Parse()
if flag.NArg() > 0 {
flag.Usage()
return
}

expvar.Publish("numplayers", expvar.Func(func() interface{} {
return atomic.LoadInt32(&numPlayers)
}))

p, err := build.Default.Import(basePkg, "", build.FindOnly)
if err != nil {
log.Fatalf("Couldn't find ChessBuddy files: %v", err)
Expand Down

0 comments on commit 62e1738

Please sign in to comment.