Permalink
Browse files

added a really stupid AI :)

  • Loading branch information...
1 parent 2e2adfb commit 62e173885f06ac089892cb96cba5b51bd840642f @tux21b committed May 6, 2012
Showing with 156 additions and 45 deletions.
  1. +2 −2 chess.css
  2. +2 −1 chess.html
  3. +1 −1 chess.js
  4. +75 −0 chess/ai.go
  5. +76 −41 main.go
View
@@ -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;
}
View
@@ -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>
@@ -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>
View
@@ -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;
View
@@ -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
+}
View
117 main.go
@@ -7,6 +7,7 @@ package main
import (
"code.google.com/p/go.net/websocket"
+ "expvar"
"flag"
"fmt"
"github.com/tux21b/ChessBuddy/chess"
@@ -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.
@@ -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)
@@ -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")
@@ -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) {
@@ -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)
}
}
@@ -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
@@ -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)

0 comments on commit 62e1738

Please sign in to comment.