Hexapawn: Difference between revisions

58,841 bytes added ,  5 months ago
m
m (→‎{{header|Wren}}: Minor tidy)
 
(18 intermediate revisions by 8 users not shown)
Line 10:
* Make the board size variable. The player should decide it
* It would be cool to have a trainer, so you don’t need to keep playing until the computer learns it
 
 
; See also
* [https://www.youtube.com/watch?v=c7oc3EemqQk| ATARI ST Hexapawn 19xxErictronics (youtube)]
 
 
=={{header|Go}}==
Line 15 ⟶ 20:
(e.g. no attempt is made to see if a possible move can immediately win).
However, unlike the paper this does ''not'' handle mirror games as the same.
I.e. this implementation will need to learn how to respond to white's openningopening "3 6" move independently from the mirror white opening move of "1 4".
<langsyntaxhighlight lang="go">package main
 
import (
Line 264 ⟶ 269:
}
return black
}</langsyntaxhighlight>
{{out|Sample Output}}
<pre>
Line 294 ⟶ 299:
Note that you can get verbose output about possible moves, considered moves, and training by running with the <tt>-v</tt> command line flag.
 
=={{header|JavaScript}}==
You can try it [http://paulo-jorente.de/tests/hexapawn/ here].
<syntaxhighlight lang="javascript">
var board, playBtn, turn, memory = [], lastMove = {brd: "", mvi: 0},
clicks = {first: null, second: null}, win = {c: 0, p: 0}, score;
 
function getPossibles() {
var pos = [], tp = turn == 0 ? "W" : "B", gp = turn == 0 ? "B" : "W";
for( var j = 0; j < 3; j++ ) {
var jj = j + (turn == 0 ? -1 : 1);
if( jj < 3 && jj > -1 ) {
for( var i = 0; i < 3; i++ ) {
if( board[i][j] == tp ) {
for( var k = -1; k < 2; k++ ) {
if(i + k > -1 && i + k < 3 &&
((board[i + k][jj] == " " && i + k == i) || (board[i + k][jj] == gp && i + k != i))) {
pos.push( {f: i + j * 3, t: i + k + jj * 3});
}
}
}
}
}
}
return pos;
}
function computerMoves() {
var brd = getBoard(), mvs, needSave = false;
 
for( var i = 0; i < memory.length; i++ ) {
if( memory[i].board == brd ) {
mvs = memory[i].moves;
break;
}
}
if( !mvs ) {
mvs = getPossibles();
needSave = true;
}
if( mvs.length == 0 ) return 0;
 
var idx = Math.floor( Math.random() * mvs.length );
lastMove.brd = brd;
lastMove.mvi = idx;
var i = mvs[idx].f % 3, j = Math.floor( mvs[idx].f / 3 ),
ii = mvs[idx].t % 3, jj = Math.floor( mvs[idx].t / 3 );
board[i][j] = " "; board[ii][jj] = "B";
 
if( needSave ) {
memory.push( {board: brd, moves: mvs} );
}
updateBtns();
return -1;
}
function getBoard() {
var str = "";
for( var j = 0; j < 3; j++ ) {
for( var i = 0; i < 3; i++ ) {
str += board[i][j];
}
}
return str;
}
function updateScore() {
score.innerHTML = "Player: " + win.p + " Computer: " + win.c;
}
function finish( r ) {
var str = "The Computer wins!";
if( r == 0 ) {
str = "You win!";
win.p++;
for( var i = 0; i < memory.length; i++ ) {
if( memory[i].board == lastMove.brd ) {
memory[i].moves.splice( lastMove.mvi, 1 );
break;
}
}
} else {
win.c++;
}
playBtn.innerHTML = str + "<br />Click to play.";
playBtn.className = "button long"
updateScore();
}
function checkFinished() {
if( getPossibles().length < 1 ) return turn == 0 ? 1 : 0;
for( var i = 0; i < 3; i++ ) {
if( board[i][0] == "W" ) return 0;
if( board[i][2] == "B" ) return 1;
}
var w = 0, b = 0;
for( var j = 0; j < 3; j++ ) {
for( var i = 0; i < 3; i++ ) {
if( board[i][j] == "W" ) w++;
if( board[i][j] == "B" ) b++;
}
}
if( w == 0 ) return 1;
if( b == 0 ) return 0;
return -1;
}
function nextPlayer() {
var r;
updateBtns();
turn = turn == 0 ? 1 : 0;
r = checkFinished();
if( r > -1 ) {
finish( r );
} else {
if( turn == 1 ) {
r = computerMoves();
if( r < 0 ) nextPlayer();
else finish( r );
}
}
}
function search( o, arr ) {
for( var i = 0; i < arr.length; i++ ) {
if( o.f == arr[i].f && o.t == arr[i].t ) return i;
}
return -1;
}
function btnHandle( e ) {
if( turn > 0 ) return;
if( clicks.first == null && e.target.firstChild.data == "W" ) {
clicks.first = e.target;
clicks.first.className += " marked"
} else if( clicks.first != null && e.target.firstChild.data == "W" ) {
clicks.first.className = clicks.first.className.split(" ")[0];
clicks.first = clicks.second = null;
} else if( clicks.first != null && ( e.target.firstChild.data == " " ||
e.target.firstChild.data == "B" ) ) {
clicks.second = e.target;
var moves = getPossibles( turn ),
i = clicks.first.i, ii = clicks.second.i,
j = clicks.first.j, jj = clicks.second.j,
obj = {f: i + j * 3, t: ii + jj * 3};
if( search( obj, moves ) > -1 ) {
board[i][j] = " "; board[ii][jj] = "W";
clicks.first.className = clicks.first.className.split(" ")[0];
clicks.first = clicks.second = null;
nextPlayer();
}
}
}
function updateBtns() {
var b, v;
for( var j = 0; j < 3; j++ ) {
for( var i = 0; i < 3; i++ ) {
b = document.getElementById( "btn" + ( i + j * 3 ) );
b.innerHTML = ( board[i][j] );
}
}
}
function restart() {
turn = 0;
createBoard();
updateBtns();
playBtn.className += " hide";
}
function createBoard() {
board = new Array( 3 );
for( var i = 0; i < 3; i++ ) {
board[i] = new Array( 3 );
}
for( var j = 0; j < 3; j++ ) {
for( var i = 0; i < 3; i++ ) {
board[i][j] = j == 0 ? "B" : j == 2 ? "W" : " ";
}
}
}
function createBtns() {
var b, d = document.createElement( "div" ), v = false;
d.className += "board";
document.body.appendChild( d );
for( var j = 0; j < 3; j++ ) {
for( var i = 0; i < 3; i++ ) {
b = document.createElement( "button" );
b.id = "btn" + ( i + j * 3 );
b.i = i; b.j = j;
b.addEventListener( "click", btnHandle, false );
b.appendChild( document.createTextNode( "" ) );
d.appendChild( b );
if( v ) b.className = "button"
else b.className = "empty";
v = !v;
}
}
playBtn = document.createElement( "button" );
playBtn.className = "button long hide";
playBtn.addEventListener( "click", restart, false );
score = document.createElement( "p" );
score.className = "txt";
d.appendChild( score );
d.appendChild( playBtn );
updateScore();
}
function init() {
createBtns();
restart();
}
</syntaxhighlight>
HTML (testing)
<pre><!DOCTYPE html>
<html><head><meta charset="UTF-8">
<title>Hexapawn</title>
<script src="hexa.js"></script>
<style>
html,body{padding:0; margin:0;padding-top:6vh;background:#222;color:#111}
.txt{color:#fff;text-align:center;font-size:6vh}
.board{padding:0;margin:auto;width:33vh;height:33vh}
.long, .button, .empty{border:1px solid #000;font-size:9vh;margin:0;padding:0;height:11vh;width:11vh;line-height:5vh;
vertical-align:middle;background:#fff;text-align:center;border-radius:3px;cursor:pointer;float:left}
.empty{background:#ccc}
.hide{display:none}
.marked{border:#f60 3px solid}
.long{width:100%;font-size:3vh}
</style>
</head><body onLoad="init()"></body></html>
</pre>
 
 
=={{header|Julia}}==
Graphical versions, using Gtk. Unicode has chess symbols! See https://www.compart.com/en/unicode/block/U+2600.
===Learning Version===
<syntaxhighlight lang="julia">using Gtk, Base
 
const whitepawn = UInt8('w')
const blackpawn = UInt8('b')
const space = UInt8(' ')
const unipawns = Dict(space => " ", whitepawn => "\u2659", blackpawn => "\u265f")
ispawn(c) = (c == whitepawn || c == blackpawn)
oppositepawn(c1, c2) = ispawn(c1) && ispawn(c2) && c1 != c2
 
mutable struct HState
board::Matrix{UInt8}
whitetomove::Bool
HState(arr, iswhite) = new(reshape(UInt8.(arr), 3, 3), iswhite)
end
string(h::HState) = join([Char(c) for c in h.board], "") * (h.whitetomove ? "|t" : "|f")
 
const avoided = Vector{String}()
 
function legalmoves(board, px, py)
moves = Vector{Pair{Int, Int}}()
c = board[py, px]
newrow = px + (c == whitepawn ? +1 : -1)
if ispawn(c) && 0 < newrow < 4
if py > 1 && oppositepawn(c, board[py - 1, newrow])
push!(moves, Pair(newrow, py - 1))
end
if board[py, newrow] == UInt8(' ')
push!(moves, Pair(newrow, py))
end
if py < 3 && oppositepawn(c, board[py + 1, newrow])
push!(moves, Pair(newrow, py + 1))
end
end
moves
end
 
islegalmove(board, px, py, i, j) = Pair(i, j) in legalmoves(board, px, py)
 
function allavailablemoves(board, forwhite)
allret = Vector{Pair{Vector{Int}, Vector{Pair{Int, Int}}}}()
for i in 1:3, j in 1:3
if (board[j, i] == whitepawn && forwhite) || (board[j, i] == blackpawn && !forwhite)
legmov = legalmoves(board, i, j)
if !isempty(legmov)
push!(allret, [i, j] => legmov)
end
end
end
allret
end
 
function checkforwin(hstate)
if any(x -> hstate.board[x] == whitepawn, 7:9)
return whitepawn # white win
elseif any(x -> hstate.board[x] == blackpawn, 1:3)
return blackpawn # black win
else
if length(allavailablemoves(hstate.board, hstate.whitetomove)) == 0
return hstate.whitetomove ? blackpawn : whitepawn
end
end
UInt8(' ') # hstate is not a winning position
end
 
function hexapawnapp()
win = GtkWindow("Hexapawn Game", 425, 425) |> (GtkFrame() |> (box = GtkBox(:v)))
toolbar = GtkToolbar()
newWgame = GtkToolButton("New Game, Play as White")
set_gtk_property!(newWgame, :label, "New Game, Play as White")
set_gtk_property!(newWgame, :is_important, true)
newBgame = GtkToolButton("New Game, Play as Black")
set_gtk_property!(newBgame, :label, "New Game, Play as Black")
set_gtk_property!(newBgame, :is_important, true)
map(w->push!(toolbar,w),[newWgame, newBgame])
scrwin = GtkScrolledWindow()
grid = GtkGrid()
map(w -> push!(box, w),[toolbar, scrwin])
push!(scrwin, grid)
buttons = Array{Gtk.GtkButtonLeaf, 2}(undef, 3, 3)
stylist = GtkStyleProvider(Gtk.CssProviderLeaf(data="button {font-size:64px;}"))
for i in 1:3, j in 1:3
grid[i, 4-j] = buttons[i, j] = GtkButton()
set_gtk_property!(buttons[i, j], :expand, true)
push!(Gtk.GAccessor.style_context(buttons[i, j]), stylist, 600)
end
 
state = HState(b"www bbb", true)
won = ""
pwhite = true
ptomove = false
ctomove = false
pselected = false
xsel, ysel = 0, 0
laststate = ""
 
function update!()
for i in 1:3, j in 1:3
set_gtk_property!(buttons[i, j], :label, unipawns[state.board[i, j]])
end
if (w = checkforwin(state)) != UInt8(' ')
if pwhite == (w == whitepawn)
push!(avoided, laststate)
end
won = (w == whitepawn) ? "White Has Won" : "Black Has Won"
ptomove, ctomove = false, false
else
won = ""
end
set_gtk_property!(win, :title, "$won Hexapawn Game")
end
 
function initialize!()
state = HState(b"www bbb", true)
won = ""
pselected = false
update!()
end
 
function newgame!(p)
initialize!()
if p == whitepawn
pwhite = true
ptomove, ctomove = true, false
else
pwhite = false
ptomove, ctomove = false, true
end
end
 
function domove!(board, m)
board[m[4], m[3]], board[m[2], m[1]] = board[m[2], m[1]], UInt8(' ')
update!()
end
 
function findrowcol(button)
for i in 1:3, j in 1:3
if buttons[i, j] == button
return i, j
end
end
return 0, 0
end
 
function playerclicked(button)
update!()
if won == "" && ptomove
j, i = findrowcol(button)
if !pselected && i > 0 &&
state.board[j, i] == (pwhite ? whitepawn : blackpawn)
xsel, ysel = i, j
pselected = true
elseif pselected
if islegalmove(state.board, xsel, ysel, i, j)
domove!(state.board, [xsel, ysel, i, j])
xsel, ysel = 0, 0
pselected = false
ptomove = false
ctomove = true
state.whitetomove = !state.whitetomove
else
pselected = false
xsel, ysel = 0, 0
end
end
end
update!()
end
 
function computerplay!()
while true
if won == "" && ctomove
cmoves = Vector{Vector{Int}}()
update!()
if string(state) == "www bbb|t"
push!(cmoves, rand([[1, 1, 2, 1], [1, 2, 2, 2], [1, 3, 2, 3]]))
else
for p in allavailablemoves(state.board, state.whitetomove), m in p[2]
b = deepcopy(state.board)
i1, j1, i2, j2 = p[1][1], p[1][2], m[1], m[2]
b[j1, i1], b[j2, i2] = b[j2, i2], b[j1, i1]
newstate = HState(b, !state.whitetomove)
x = checkforwin(newstate)
if x != space && state.whitetomove == (x == whitepawn)
empty!(cmoves)
push!(cmoves, [i1, j1, i2, j2])
break
elseif !(string(newstate) in avoided)
push!(cmoves, [i1, j1, i2, j2])
end
end
end
cmove = rand(cmoves)
ptomove, ctomove = true, false
state.whitetomove = !state.whitetomove
domove!(state.board, cmove)
laststate = string(state)
end
yield()
sleep(0.2)
end
end
 
for i in 1:3, j in 1:3
signal_connect(playerclicked, buttons[i, j], "clicked")
end
newplayerwhitegame!(w) = newgame!(whitepawn)
newplayerblackgame!(w) = newgame!(blackpawn)
signal_connect(newplayerwhitegame!, newWgame, :clicked)
signal_connect(newplayerblackgame!, newBgame, :clicked)
newplayerwhitegame!(win)
condition = Condition()
endit(w) = notify(condition)
signal_connect(endit, win, :destroy)
showall(win)
@async computerplay!()
wait(condition)
end
 
hexapawnapp()
</syntaxhighlight>
===Pretaught Version===
Unlike full chess, the play here is simple enough to specify perfectly played games with just a 17 move table and one-move lookahead.
<syntaxhighlight lang="julia">using Gtk, Base
 
const whitepawn = UInt8('w')
const blackpawn = UInt8('b')
const space = UInt8(' ')
const unipawns = Dict(space => " ", whitepawn => "\u2659", blackpawn => "\u265f")
ispawn(c) = (c == whitepawn || c == blackpawn)
oppositepawn(c1, c2) = ispawn(c1) && ispawn(c2) && c1 != c2
 
mutable struct HState
board::Matrix{UInt8}
whitetomove::Bool
HState(arr, iswhite) = new(reshape(UInt8.(arr), 3, 3), iswhite)
end
string(h::HState) = join([Char(c) for c in h.board], "") * (h.whitetomove ? "|t" : "|f")
 
const positionmoves = Dict(
"w w b bb|t" => [1, 1, 1, 2], "w w b bb |t" => [1, 3, 2, 3], "w bbb |t" => [1, 1, 2, 2],
" wbb b|t" => [1, 3, 2, 2], "w bbw b |t" => [2, 3, 3, 3], "w bb b|t" => [1, 1, 2, 1],
" wwb b b|t" => [1, 2, 2, 2], " wbb b|t" => [1, 3, 2, 2], " wbb b |t" => [1, 3, 2, 2],
"ww bb b|t" => [1, 2, 2, 2], "w b b|t" => [1, 1, 2, 2], "w bb |t" => [1, 1, 1, 2],
" www bbb|f" => [3, 2, 2, 1], "ww wbbb|f" => [3, 2, 2, 3], "w w w bbb|f" => [3, 1, 2, 2],
" ww b b|f" => [3, 3, 2, 3], "w w bb|f" => [3, 3, 2, 3])
 
function legalmoves(board, px, py)
moves = Vector{Pair{Int, Int}}()
c = board[py, px]
newrow = px + (c == whitepawn ? +1 : -1)
if ispawn(c) && 0 < newrow < 4
if py > 1 && oppositepawn(c, board[py - 1, newrow])
push!(moves, Pair(newrow, py - 1))
end
if board[py, newrow] == UInt8(' ')
push!(moves, Pair(newrow, py))
end
if py < 3 && oppositepawn(c, board[py + 1, newrow])
push!(moves, Pair(newrow, py + 1))
end
end
moves
end
 
islegalmove(board, px, py, i, j) = Pair(i, j) in legalmoves(board, px, py)
 
function allavailablemoves(board, forwhite)
allret = Vector{Pair{Vector{Int}, Vector{Pair{Int, Int}}}}()
for i in 1:3, j in 1:3
if (board[j, i] == whitepawn && forwhite) || (board[j, i] == blackpawn && !forwhite)
legmov = legalmoves(board, i, j)
if !isempty(legmov)
push!(allret, [i, j] => legmov)
end
end
end
allret
end
 
function checkforwin(hstate)
if any(x -> hstate.board[x] == whitepawn, 7:9)
return whitepawn # white win
elseif any(x -> hstate.board[x] == blackpawn, 1:3)
return blackpawn # black win
else
if length(allavailablemoves(hstate.board, hstate.whitetomove)) == 0
return hstate.whitetomove ? blackpawn : whitepawn
end
end
UInt8(' ') # hstate is not a winning position
end
 
function hexapawnapp()
win = GtkWindow("Hexapawn Game", 425, 425) |> (GtkFrame() |> (box = GtkBox(:v)))
toolbar = GtkToolbar()
newWgame = GtkToolButton("New Game, Play as White")
set_gtk_property!(newWgame, :label, "New Game, Play as White")
set_gtk_property!(newWgame, :is_important, true)
newBgame = GtkToolButton("New Game, Play as Black")
set_gtk_property!(newBgame, :label, "New Game, Play as Black")
set_gtk_property!(newBgame, :is_important, true)
map(w->push!(toolbar,w),[newWgame, newBgame])
scrwin = GtkScrolledWindow()
grid = GtkGrid()
map(w -> push!(box, w),[toolbar, scrwin])
push!(scrwin, grid)
buttons = Array{Gtk.GtkButtonLeaf, 2}(undef, 3, 3)
stylist = GtkStyleProvider(Gtk.CssProviderLeaf(data="button {font-size:64px;}"))
for i in 1:3, j in 1:3
grid[i, 4-j] = buttons[i, j] = GtkButton()
set_gtk_property!(buttons[i, j], :expand, true)
push!(Gtk.GAccessor.style_context(buttons[i, j]), stylist, 600)
end
 
state = HState(b"www bbb", true)
won = ""
pwhite = true
ptomove = false
ctomove = false
pselected = false
xsel, ysel = 0, 0
 
function update!()
for i in 1:3, j in 1:3
set_gtk_property!(buttons[i, j], :label, unipawns[state.board[i, j]])
end
if (w = checkforwin(state)) != UInt8(' ')
won = (w == whitepawn) ? "White Has Won" : "Black Has Won"
ptomove, ctomove = false, false
else
won = ""
end
set_gtk_property!(win, :title, "$won Hexapawn Game")
end
 
function initialize!()
state = HState(b"www bbb", true)
won = ""
pselected = false
update!()
end
 
function newgame!(p)
initialize!()
if p == whitepawn
pwhite = true
ptomove, ctomove = true, false
else
pwhite = false
ptomove, ctomove = false, true
end
end
 
function domove!(board, m)
board[m[4], m[3]], board[m[2], m[1]] = board[m[2], m[1]], UInt8(' ')
update!()
end
 
function findrowcol(button)
for i in 1:3, j in 1:3
if buttons[i, j] == button
return i, j
end
end
return 0, 0
end
 
function playerclicked(button)
update!()
if won == "" && ptomove
j, i = findrowcol(button)
if !pselected && i > 0 &&
state.board[j, i] == (pwhite ? whitepawn : blackpawn)
xsel, ysel = i, j
pselected = true
elseif pselected
if islegalmove(state.board, xsel, ysel, i, j)
domove!(state.board, [xsel, ysel, i, j])
xsel, ysel = 0, 0
pselected = false
ptomove = false
ctomove = true
state.whitetomove = !state.whitetomove
else
pselected = false
xsel, ysel = 0, 0
end
end
end
update!()
end
 
function computerplay!()
while true
if won == "" && ctomove
cmove = [0, 0, 0, 0]
update!()
if string(state) == "www bbb|t"
cmove = rand([[1, 1, 2, 1], [1, 2, 2, 2], [1, 3, 2, 3]])
elseif haskey(positionmoves, string(state))
cmove = positionmoves[string(state)]
else
for p in allavailablemoves(state.board, state.whitetomove), m in p[2]
b = deepcopy(state.board)
i1, j1, i2, j2 = p[1][1], p[1][2], m[1], m[2]
b[j1, i1], b[j2, i2] = b[j2, i2], b[j1, i1]
newstate = HState(b, !state.whitetomove)
x = checkforwin(newstate)
if state.whitetomove == (x == whitepawn)
cmove = [i1, j1, i2, j2]
break
end
end
end
if cmove[1] == 0
throw("No known move for position $state")
else
ptomove, ctomove = true, false
state.whitetomove = !state.whitetomove
domove!(state.board, cmove)
end
end
yield()
sleep(0.2)
end
end
 
for i in 1:3, j in 1:3
signal_connect(playerclicked, buttons[i, j], "clicked")
end
newplayerwhitegame!(w) = newgame!(whitepawn)
newplayerblackgame!(w) = newgame!(blackpawn)
signal_connect(newplayerwhitegame!, newWgame, :clicked)
signal_connect(newplayerblackgame!, newBgame, :clicked)
newplayerwhitegame!(win)
condition = Condition()
endit(w) = notify(condition)
signal_connect(endit, win, :destroy)
showall(win)
@async computerplay!()
wait(condition)
end
 
hexapawnapp()
</syntaxhighlight>
 
=={{header|Pascal}}==
Line 306 ⟶ 985:
One reason for writing this programme was to support an essay I had planned on the difference between thinking and computation. For instance, for some board shapes it is apparent (via thinking) that the forced winner is known, even obvious, without playing any game. But, via computation, the programme attains this only via slogging through the computation.
===Source===
<syntaxhighlight lang="pascal">
<lang Pascal>
{$N- No serious use of floating-point stuff.}
{$B- Early and safe resolution of If x <> 0 and 1/x...}
Line 3,048 ⟶ 3,727:
end;
END.
</syntaxhighlight>
</lang>
===Some results===
It is called Pawnplex because it is not for just a three by three board. Invoke from the command line with <code>Pawnplex.exe ?</code> to obtain a description or <code>Pawnplex.exe 3</code> to run for the original 3×3 problem. A windows interface may allow the specification of parameters to a run (say via a "shortcut") but otherwise, ... open a DOS window... As for one player or the other having a winning strategy, some partial results are shown in the following:
Line 3,083 ⟶ 3,762:
</pre>
Which is an array of results, one for each size board - a board having only one row is impossible as the pieces can't be placed. Notice that row 3, column 3 has X, meaning that X has an unbeatable strategy, no matter what O tries, and so on for the other worked-through board sizes. The number of possible games increases very rapidly as the board size increases...
 
=={{header|Perl}}==
Plays in a Tk GUI. Has both trainer capability and variable size boards.
<syntaxhighlight lang="perl">#!/usr/bin/perl
 
use strict; # https://rosettacode.org/wiki/Hexapawn
use warnings;
use List::AllUtils qw( max each_array reduce shuffle );
use Tk;
 
my @argv = @ARGV;
my $size = max 3, shift // 3;
my $train = shift // 1e3;
my $forward = qr/.{$size}/s;
my $take = qr/.{@{[$size - 1]}}(?:..)?/s;
my $message = 'Click on Pawn';
my (@played, %scores, $from, $active);
my $board = my $start = "b\n-\nw\n" =~
s/-\n/$& x ($size - 2)/er =~ s/./$& x $size/ger;
 
my $mw = MainWindow->new;
$mw->geometry( '+600+300' );
$mw->title( 'RosettaCode Hexapawn' );
my $grid = $mw->Frame->pack;
my @squares = map {
my $row = $_;
map {
my $col = $_;
my $g = $grid->Canvas( -width => 100, -height => 100,
-bd => 0, -relief => 'flat', -highlightthickness => 0,
-bg => ($row+$col) % 2 ? 'gray80' : 'gray60',
)->grid( -row => $row, -column => $col, -sticky => 'nsew' );
$g->Tk::bind('<1>' => sub{ click( $col, $row ) } );
$g->Tk::bind("<ButtonRelease-$_>" => sub{$g->yviewMoveto(0)} ) for 4, 5;
$g } 0 .. $size - 1
} 0 .. $size - 1;
my $label = $mw->Label( -textvariable => \$message,
)->pack( -side => 'bottom', -expand => 1, -fill => 'both' );
$mw->Button(-text => 'Exit', -command => sub {$mw->destroy},
)->pack( -side => 'right', -fill => 'x', -expand => 0 );
$mw->Button(-text => 'New Game', -command => \&newgame,
)->pack( -side => 'right', -fill => 'x', -expand => 1 );
$mw->Button(-text => 'Train', -command => \&train,
)->pack( -side => 'right', -fill => 'x', -expand => 0 );
newgame();
MainLoop;
-M $0 < 0 and exec "$0 @argv";
 
sub findwhite
{
my @moves;
$board =~ /(?:-($forward)w|b($take)w)(?{ push @moves, "$`w$+-$'"; })(*FAIL)/;
@moves;
}
 
sub findblack
{
my @moves;
$board =~ /(?:b($forward)-|b($take)w)(?{ push @moves, "$`-$+b$'"; })(*FAIL)/;
@moves;
}
 
sub newgame
{
$board = $start;
@played = ();
$from = undef;
$active = 1;
$message = 'Click on Pawn';
$label->configure( -bg => 'gray85' );
show();
}
 
sub train
{
$message = 'Training';
$label->configure( -bg => 'yellow' );
$mw->update;
for ( 1 .. $train )
{
$board = $start;
my @whitemoves = findwhite;
my @blackmoves;
@played = ();
while( 1 )
{
$board = $whitemoves[rand @whitemoves];;
if( $board =~ /^.*w/ or not (@blackmoves = findblack) )
{
$scores{$_}++ for map {$_, s/.+/ reverse $& /ger } @played;
last;
}
push @played, $board = $blackmoves[rand @blackmoves];
if( $board =~ /b.*$/ or not (@whitemoves = findwhite) )
{
$scores{$_}-- for map {$_, s/.+/ reverse $& /ger } @played;
last;
}
}
}
print "score count: @{[ scalar keys %scores ]}\n";
newgame();
}
 
sub scale { map 100 * $_ >> 3, @_ };
 
sub show
{
my $ea = each_array(@{[ $board =~ /./g ]}, @squares );
while( my ($piece, $canvas) = $ea->() )
{
$canvas->delete('all');
$piece eq '-' and next;
$canvas->createOval(scale(3, 3, 5, 5));
$canvas->createArc(scale(2, 4.8, 6, 9), -start => 0, -extent => 180);
$canvas->itemconfigure('all', -outline => undef,
-fill => $piece eq 'w' ? 'white' : 'black');
}
}
 
sub click
{
my ($col, $row) = @_;
$active or return;
my $pos = $row * ($size + 1) + $col;
if( 'w' eq substr $board, $pos, 1 )
{
$from = $pos;
$message = 'Click on Destination';
}
elsif( defined $from )
{
my $new = $board;
substr $new, $from, 1, '-';
substr $new, $pos, 1, 'w';
if( grep $_ eq $new, findwhite )
{
$board = $new;
my @blackmoves = findblack;
if( $board =~ /^.*w/ or @blackmoves == 0 )
{
$active = 0;
$message = 'White Wins';
$label->configure( -bg => 'green' );
$scores{$_}++ for map {$_, s/.+/ reverse $& /ger } @played;
}
else
{
$from = undef;
$message = 'Blacks Move';
push @played, $board = reduce
{ ($scores{$a} // 0) < ($scores{$b} // 0) ? $a : $b }
shuffle @blackmoves;
if( $board =~ /b.*$/ or not findwhite )
{
$active = 0;
$message = 'Black Wins';
$label->configure( -bg => 'red' );
$scores{$_}-- for map {$_, s/.+/ reverse $& /ger } @played;
}
else
{
$message = 'Click on Pawn';
}
}
show;
}
else
{
$mw->bell;
$message = 'Invalid move';
$mw->after( 500 => sub { $message = 'Click on Destination' } );
}
}
}</syntaxhighlight>
 
=={{header|Phix}}==
{{libheader|Phix/pGUI}}
OTT GUI version.
From reading the 1962 pdf, I felt the only thing that would do it justice would be to show a graphic
representation of the 24 matchboxes, with grey arrows for still-eligible and red for eliminated.
Displays full training data (for black and white), and last game history.
Mouse-operated: Hover on an appropriate sector of one of your pieces until an arrow shows, and click.
<syntaxhighlight lang="phix">-- demo\rosetta\hexapawn.exw
include pGUI.e
 
-- A board is held as eg {"WWW","...","BBB"} (nb displayed upside down)
-- A move is held as {{{y1,x1},{y2,x2}},colour} where x/y are 1..3.
 
enum BOARD, MOVES, BLACK, WHITE
-- The above correspond to board_canvas .. white_canvas.
-- BOARD|MOVES also apply within each of the above, that is
-- data[BOARD][BOARD], data[BOARD][MOVES], data[MOVES][BOARD],
-- etc are all valid uses, however data[BOARD][BLACK] isn't.
 
sequence data = {{{},{}}, -- BOARD -- (current)
{{},{}}, -- MOVES -- (game log)
{{},{}}, -- BLACK -- (training data)
{{},{}}} -- WHITE -- (training data)
 
-- data[BOARD][BOARD] is always a sequence of length 1, for consistency,
-- whereas [other][BOARD] can contain 0..19 of them,
-- where that 19 is just from the highest yet seen.
-- data[BOARD][MOVES] is {} or one move, depending on the mouse hover.
-- data[MOVES][MOVES] has one move per board, except last which is {}.
-- data[BLACK/WHITE][MOVES] can contain 0-3 possible moves, obviously.
 
bool game_over = false
integer bw = 0, ww = 0 -- win counts
 
enum HUMAN, COMPUTER
 
-- The next five values flip on every move:
integer player, opponent, -- 'W' <==> 'B'
player_type, opponent_type, -- HUMAN/COMPUTER
traindx = WHITE -- or BLACK, top-level index to data
 
function possible_moves(sequence board)
sequence moves = {}
integer height = length(board),
width = length(board[1]),
direction = iff(player='W'?+1:-1)
for row=1 to height do
for col=1 to width do
if board[row][col]=player then
integer rd = row+direction
if rd<1 or rd>height then ?9/0 end if -- game_over??
for tgt=max(1,col-1) to min(col+1,width) do
if board[rd][tgt]==iff(tgt==col?'.':opponent) then
moves = append(moves,{{{row,col},{rd,tgt}},CD_DARK_GREY})
end if
end for
end if
end for
end for
return moves
end function
 
-- We use mirroring to approximately halve the training data set sizes.
-- (every possible board/move has an equivalent mirror, some idential)
 
function mirror(sequence board)
-- flip each row, eg {".WW","W..","BBB"} <=> {"WW.","..W","BBB"}
for i=1 to length(board) do
board[i] = reverse(board[i])
end for
return board
end function
 
function mirror_move(sequence move)
-- from/to x := 4-x; y's and colour unchanged.
integer {{{y1,x1},{y2,x2}},colour} = move
move = {{{y1,4-x1},{y2,4-x2}},colour}
return move
end function
 
function find_board(sequence board)
--
-- retrieve an index to the training data (created if rqd)
-- also returns bools flip (move must flip too then), and
-- is_mirror (ie the training data deserves two updates)
--
-- (Also used to init training data, ie show the
-- starting board, with all moves valid/grey.)
--
sequence mb = mirror(board)
bool is_mirror = (mb==board), flip = false
if mb<board then
board = mb
flip = true
end if
integer k = find(board,data[traindx][BOARD])
if k=0 then
sequence moves = possible_moves(board)
data[traindx][BOARD] &= {board}
data[traindx][MOVES] &= {moves}
k = length(data[traindx][BOARD])
end if
return {k,flip,is_mirror}
end function
 
procedure train(integer mdx)
--
-- Learn something from losing a game.
-- player, traindx should already be set.
--
-- nb learn both/propagate are technically not in the original spec,
-- which confiscates max one bead per round. Obviously you could
-- instead hunt for one earlier move when moves[m][2]==CD_RED,
-- and simply play more games to complete the training.
--
data[MOVES][MOVES][mdx][1][2] = CD_RED -- (just the game log)
sequence board = data[MOVES][BOARD][mdx],
move = data[MOVES][MOVES][mdx][1]
{integer k, bool flip, bool is_mirror} = find_board(board)
sequence moves = data[traindx][MOVES][k]
if flip then move = mirror_move(move) end if
integer m = find(move[1],vslice(moves,1))
if moves[m][2]!=CD_RED then
moves[m][2] = CD_RED
if is_mirror then -- (learn both)
m = find(mirror_move(move)[1],vslice(moves,1))
moves[m][2] = CD_RED
end if
data[traindx][MOVES][k] = moves
if mdx>2 and sum(sq_ne(vslice(moves,2),CD_RED))=0 then
train(mdx-2) -- (propagate)
end if
end if
end procedure
 
procedure play_move(sequence move)
if length(move) then
move[2] = CD_DARK_GREY
integer {{{y1,x1},{y2,x2}}/*,colour*/} = move
data[BOARD][BOARD][1][y1][x1] = '.'
data[BOARD][BOARD][1][y2][x2] = player
data[MOVES][MOVES][$] = {move} -- (fill in game log)
data[MOVES][BOARD] &= data[BOARD][BOARD]
data[MOVES][MOVES] &= {{{}}}
{player, opponent} = {opponent, player}
{player_type, opponent_type} = {opponent_type, player_type}
traindx = WHITE+BLACK - traindx -- (toggle b/w)
sequence board = data[BOARD][BOARD][1]
if y2!=2 -- (must be "third row" then, ie 1|3)
or not find(player,flatten(board))
or possible_moves(board)={} then
game_over = true
if player='W' then bw += 1 else ww +=1 end if
train(length(data[MOVES][MOVES])-2)
else
{} = find_board(board)
end if
end if
end procedure
 
procedure opponent_play()
--
-- does nothing if opponent is HUMAN
--
if not game_over
and player_type=COMPUTER then
sequence board = data[BOARD][BOARD][1]
{integer k, bool flip, bool is_mirror} = find_board(board)
sequence moves = data[traindx][MOVES][k], not_red = {}
for m=1 to length(moves) do
if moves[m][2]!=CD_RED then
not_red = append(not_red,moves[m])
end if
end for
if length(not_red) then moves = not_red end if
sequence move = moves[rand(length(moves))]
if flip then move = mirror_move(move) end if
play_move(move)
end if
end procedure
 
procedure new_game()
data[BOARD] = {{{"WWW","...","BBB"}},{{}}}
data[MOVES] = data[BOARD]
game_over = false
{player,opponent} = "WB"
{player_type,opponent_type} = {HUMAN,COMPUTER} -- (or any)
traindx = WHITE
{} = find_board(data[BOARD][BOARD][1]) -- (init training data)
end procedure
 
procedure train_rec()
--
-- Note this might require some more looping if train() is changed
-- to respect max-one-per-round original rules, as mentioned above
--
sequence board = data[BOARD][BOARD][1],
moves = possible_moves(board),
plopt = {player, opponent, traindx, data[MOVES]},
bwww = {bw,ww}
for i=1 to length(moves) do
play_move(moves[i])
if not game_over then train_rec() end if
game_over = false
data[BOARD][BOARD][1] = board
{player, opponent, traindx, data[MOVES]} = plopt
end for
{bw,ww} = bwww
end procedure
 
Ihandle dlg
 
-- saved from draw for mouse hover checks:
sequence board_centres = {}
integer board_radius2 = 0
 
procedure draw_board(cdCanvas cddbuffer, integer dx, i, px, py, mx)
sequence board = data[dx][BOARD][i],
centres = board -- (fully overwritten in draw pieces step)
integer diameter = floor(mx/5),
gap = floor(mx/8), -- (squares are twice the gap)
rhs = 7*gap -- (lhs==gap, rhs/lhs==top/btm)
--
-- draw grid lines
--
cdCanvasSetForeground(cddbuffer,CD_GREY)
cdCanvasLineWidth(cddbuffer,1)
for l=1 to 7 by 2 do
integer l3 = l*gap
cdCanvasLine(cddbuffer, px+gap, py+l3, px+rhs, py+l3)
cdCanvasLine(cddbuffer, px+l3, py+gap, px+l3, py+rhs)
end for
--
-- draw pieces
--
for r=1 to length(board) do
for c=1 to length(board[r]) do
integer cx = px+2*c*gap,
cy = py+2*r*gap
centres[r][c] = {cx,cy}
integer piece = board[r][c]
if piece!='.' then
cdCanvasSetForeground(cddbuffer,iff(piece='B'?CD_BLACK:CD_WHITE))
cdCanvasSector(cddbuffer, cx, cy, diameter, diameter, 0, 360)
cdCanvasSetForeground(cddbuffer,CD_BLACK)
cdCanvasArc(cddbuffer, cx, cy, diameter, diameter, 0, 360)
end if
end for
end for
if dx=BOARD then
-- save for motion_cb
board_centres = centres
board_radius2 = gap*gap
end if
--
-- draw arrows
--
cdCanvasLineWidth(cddbuffer,2)
sequence arrows = data[dx][MOVES][i]
for i=1 to length(arrows) do
if length(arrows[i])=0 then exit end if
integer {{{x1,y1},{x2,y2}},colour} = arrows[i]
{x1,y1} = centres[x1][y1]
{x2,y2} = centres[x2][y2]
if y1=y2 then ?9/0 end if -- horizontal??
integer ax = iff(x1<x2?-1:+1),
ay = iff(y1<y2?-1:+1)
cdCanvasSetForeground(cddbuffer,colour)
if x1!=x2 then -- diagonal:
cdCanvasLine(cddbuffer,x1,y1,x2+ax,y2+ay)
cdCanvasLine(cddbuffer,x2+ax,y2,x2+ax,y2+ay*6)
cdCanvasLine(cddbuffer,x2,y2+ay,x2+ax*6,y2+ay)
else -- vertical:
cdCanvasLine(cddbuffer,x1,y1,x2,y2+ay)
cdCanvasLine(cddbuffer,x2-ax,y2+ay,x2+ax*4,y2+ay*6)
cdCanvasLine(cddbuffer,x2+ax,y2+ay,x2-ax*4,y2+ay*6)
end if
end for
end procedure
function redraw_cb(Ihandle ih, integer /*posx*/, integer /*posy*/)
integer dx = IupGetInt(ih,"DATA"),
{cw,ch} = IupGetIntInt(ih, "DRAWSIZE")
--
-- first, calculate the best nw*nh way to fit them all in:
--
integer nw = 1, nh = 1
while nw*nh<length(data[dx][1]) do
if (cw/(nw+1))>(ch/(nh+1)) then
nw += 1
else
nh += 1
end if
end while
integer mx = min(floor(cw/nw), floor(ch/nh))
--
-- now draw all the boards
--
cdCanvas cddbuffer = IupGetAttributePtr(ih,"DBUFFER")
IupGLMakeCurrent(ih)
cdCanvasActivate(cddbuffer)
cdCanvasClear(cddbuffer)
integer px = floor((cw-nw*mx)/2), -- (set margin)
py = floor((ch-nh*mx)/2), -- ""
this_row = 0
py = ch-mx-py -- (start at the top)
for i=1 to length(data[dx][1]) do
draw_board(cddbuffer,dx,i,px,py,mx)
px += mx
this_row += 1
if this_row>=nw then
this_row = 0
px = floor((cw-nw*mx)/2)
py -= mx
end if
end for
cdCanvasFlush(cddbuffer)
return IUP_DEFAULT
end function
 
function map_cb(Ihandle ih)
IupGLMakeCurrent(ih)
atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
cdCanvas cddbuffer = cdCreateCanvas(CD_GL, "10x10 %g", {res})
IupSetAttributePtr(ih,"DBUFFER",cddbuffer)
cdCanvasSetBackground(cddbuffer, CD_PARCHMENT)
return IUP_DEFAULT
end function
 
function canvas_resize_cb(Ihandle canvas)
cdCanvas cddbuffer = IupGetAttributePtr(canvas,"DBUFFER")
integer {canvas_width, canvas_height} = IupGetIntInt(canvas, "DRAWSIZE")
atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
cdCanvasSetAttribute(cddbuffer, "SIZE", "%dx%d %g", {canvas_width, canvas_height, res})
return IUP_DEFAULT
end function
 
function check_position(integer px, py)
--
-- check if x,y is on (a 45 degree sector of player's piece of) a legal move.
-- notes: a result of {{1,1},{2,2},CD_GREEN} means bottom left takes centre.
-- perfectly fine to invoke for either the black or white player.
-- this can get invoked when board_centres still=={} (returns {}).
--
if not game_over
and player_type=HUMAN
and board_centres!={} then -- (when started with mouse cursor on-board)
integer height = length(board_centres),
width = length(board_centres[1]),
direction = iff(player='W'?+1:-1)
for y=1 to height do
integer yd = y+direction
if yd>=1 and yd<=height then
for x=1 to width do
sequence c = sq_sub(board_centres[4-y][x],{px,py})
integer {cx,cy} = sq_mul(c,{1,direction})
if cy>0 and (cx*cx+cy*cy)<=board_radius2 then
atom angle = -arctan(cx/cy)*CD_RAD2DEG
if angle>=-67.5 and angle<=67.5 then
-- within one of three 45 degree sectors:
-- >22.5 is a move right,
-- 22.5..-22.5 is a move up,
-- <-22.5 is a move left
sequence board = data[BOARD][BOARD][1]
integer move = floor((angle+22.5)/45), -- (-1/0/+1)
tgt = iff(move=0?'.':opponent),
xm = x+move
if xm>=1 and xm<=width
and board[y][x]=player
and board[yd][xm]==tgt then
return {{{y,x},{yd,xm}},CD_GREEN}
end if
end if
return {} -- (we may as well quit now)
end if
end for
end if
end for
end if
return {}
end function
 
function motion_cb(Ihandle board_canvas, integer x, y, atom /*pStatus*/)
data[BOARD][MOVES][1] = {check_position(x,y)}
IupUpdate(board_canvas)
return IUP_CONTINUE
end function
 
procedure redraw_all()
Ihandle frame = IupGetAttributePtr(dlg,"BOARD_FRAME")
string title = iff(game_over?sprintf("%c wins (Black:%d, White:%d)",{opponent,bw,ww}):"Board")
IupSetAttribute(frame, "TITLE", title)
IupUpdate(IupGetAttributePtr(dlg,"BOARD_CANVAS"))
IupUpdate(IupGetAttributePtr(dlg,"MOVES_CANVAS"))
IupUpdate(IupGetAttributePtr(dlg,"WHITE_CANVAS"))
IupUpdate(IupGetAttributePtr(dlg,"BLACK_CANVAS"))
end procedure
 
function button_cb(Ihandle /*board_canvas*/, integer button, pressed, x, y, atom /*pStatus*/)
if button=IUP_BUTTON1 and not pressed then -- (left button released)
play_move(check_position(x,y))
opponent_play()
redraw_all()
end if
return IUP_CONTINUE
end function
 
function new_game_cb(Ihandle /*ih*/)
new_game()
redraw_all()
return IUP_DEFAULT
end function
 
function train_cb(Ihandle /*ih*/)
sequence pott = {player_type, opponent_type}
new_game()
train_rec()
{player_type, opponent_type} = pott -- (restore)
redraw_all()
return IUP_DEFAULT
end function
 
function exit_cb(Ihandle /*ih*/)
return IUP_CLOSE
end function
 
constant help_text = """
Hexapawn may be the simplest game for which a self-learning game AI
is not completely and utterly trivial, or at least non-obvious.
 
Pieces can move forward into an empty square or diagonally to take
an opponent's piece (no initial double or en passant moves).
The game is over if a piece reaches the third row, all opponent
pieces are captured, or the opponent has no valid move.
White always goes first.
 
Hover the mouse over a (45 degree sector of) a piece until an arrow
appears (if that's valid), then click.
 
Either or both players can be computer or human (the machine learns
from both). Note that when both are computer, mouse clicks (anywhere
on the board canvas) are still needed before it makes a move.
 
The "Train" option performs an exhaustive search of all possible moves.
Note the results may turn out slightly different if you manually
teach it a few moves first. Once fully trained, white cannot possibly win.
Interestingly, it never learns anything about winning moves, it just
avoids losing moves, which is enough to make it (/black) unbeatable.
 
In the training data, red arrows indicate losing moves, and grey ones
still potential winners. Red arrows in the game log indicate what has
just been learnt from playing(/losing) the last game.
 
The human-player games quickly become a test to see how fast you
can decipher/fill the training data, which is made slightly more
difficult (for a human) by board/move mirroring. How many games can
you win, and how few to prove that white will (eventually) always
lose? My records are 14 and 3 respectively, however on the latter
you will get/need a few more wins on the way to finishing the full
training of it.
 
Enhancing this game to handle larger boards and other games such as
noughts and crosses/tic-tac-toe and/or checkers/draughts (etc) is
left as an exercise for the reader.
"""
 
function help_cb(Ihandln /*ih*/)
IupMessage("Hexapawn",help_text)
return IUP_DEFAULT
end function
 
function key_cb(Ihandle /*dlg*/, atom c)
if c=K_ESC then return IUP_CLOSE end if
if c=K_F1 then return help_cb(NULL) end if
return IUP_CONTINUE
end function
 
procedure main()
IupOpen()
Ihandle board_canvas = IupGLCanvas("DATA=%d,RASTERSIZE=50x50",{BOARD}),
moves_canvas = IupGLCanvas("DATA=%d,RASTERSIZE=50x50",{MOVES}),
black_canvas = IupGLCanvas("DATA=%d,RASTERSIZE=50x50",{BLACK}),
white_canvas = IupGLCanvas("DATA=%d,RASTERSIZE=50x50",{WHITE}),
board_frame = IupFrame(board_canvas,"TITLE=Board"),
moves_frame = IupFrame(moves_canvas,"TITLE=\"Game log\""),
black_frame = IupFrame(black_canvas,"TITLE=Black"),
white_frame = IupFrame(white_canvas,"TITLE=White"),
left = IupSplit(board_frame,moves_frame,"ORIENTATION=HORIZONTAL,MINMAX=100:900"),
right = IupVbox({black_frame,white_frame}),
full = IupSplit(left,right,"ORIENTATION=VERTICAL,MINMAX=100:900")
dlg = IupDialog(full,"TITLE=Hexapawn,RASTERSIZE=800x800")
IupSetAttributePtr(dlg,"BOARD_FRAME",board_frame)
IupSetAttributePtr(dlg,"BOARD_CANVAS",board_canvas)
IupSetAttributePtr(dlg,"MOVES_CANVAS",moves_canvas)
IupSetAttributePtr(dlg,"WHITE_CANVAS",white_canvas)
IupSetAttributePtr(dlg,"BLACK_CANVAS",black_canvas)
Ihandles c4 = {board_canvas, moves_canvas, black_canvas, white_canvas}
IupSetCallbacks(c4,{"ACTION",Icallback("redraw_cb"),
"MAP_CB",Icallback("map_cb"),
"RESIZE_CB", Icallback("canvas_resize_cb")})
IupSetCallbacks(board_canvas, {"MOTION_CB", Icallback("motion_cb"),
"BUTTON_CB", Icallback("button_cb")})
Ihandle main_menu = IupMenu({IupMenuItem("&New game",Icallback("new_game_cb")),
IupMenuItem("&Train",Icallback("train_cb")),
IupMenuItem("&Help (F1)",Icallback("help_cb")),
IupMenuItem("E&xit", Icallback("exit_cb"))})
IupSetAttributeHandle(dlg,"MENU",main_menu)
IupSetCallback(dlg, "K_ANY", Icallback("key_cb"))
new_game()
IupShowXY(dlg,IUP_CENTER,IUP_CENTER)
IupMainLoop()
IupClose()
end procedure
main()</syntaxhighlight>
There is also a straightforward translation of Go in demo\rosetta\heaxpawn_go.exw if anyone is interested...
 
=={{header|Python}}==
El código es de Matt Hughes - https://github.com/mch/hexapawn
Yo solo lo transcribo.
<syntaxhighlight lang="python">
#!/usr/bin/env python3
import sys
 
black_pawn = " \u265f "
white_pawn = " \u2659 "
empty_square = " "
 
 
def draw_board(board_data):
#bg_black = "\u001b[40m"
bg_black = "\u001b[48;5;237m"
#bg_white = "\u001b[47m"
bg_white = "\u001b[48;5;245m"
 
clear_to_eol = "\u001b[0m\u001b[K\n"
 
board = ["1 ", bg_black, board_data[0][0], bg_white, board_data[0][1], bg_black, board_data[0][2], clear_to_eol,
"2 ", bg_white, board_data[1][0], bg_black, board_data[1][1], bg_white, board_data[1][2], clear_to_eol,
"3 ", bg_black, board_data[2][0], bg_white, board_data[2][1], bg_black, board_data[2][2], clear_to_eol,
" A B C\n"];
 
sys.stdout.write("".join(board))
 
def get_movement_direction(colour):
direction = -1
if colour == black_pawn:
direction = 1
elif colour == white_pawn:
direction = -1
else:
raise ValueError("Invalid piece colour")
 
return direction
 
def get_other_colour(colour):
if colour == black_pawn:
return white_pawn
elif colour == white_pawn:
return black_pawn
else:
raise ValueError("Invalid piece colour")
 
def get_allowed_moves(board_data, row, col):
if board_data[row][col] == empty_square:
return set()
 
colour = board_data[row][col]
other_colour = get_other_colour(colour)
direction = get_movement_direction(colour)
 
if (row + direction < 0 or row + direction > 2):
return set()
 
allowed_moves = set()
if board_data[row + direction][col] == empty_square:
allowed_moves.add('f')
if col > 0 and board_data[row + direction][col - 1] == other_colour:
allowed_moves.add('dl')
if col < 2 and board_data[row + direction][col + 1] == other_colour:
allowed_moves.add('dr')
 
return allowed_moves
 
def get_human_move(board_data, colour):
# The direction the pawns may move depends on the colour; assuming that white starts at the bottom.
direction = get_movement_direction(colour)
 
while True:
piece_posn = input(f'What {colour} do you want to move? ')
valid_inputs = {'a1': (0,0), 'b1': (0,1), 'c1': (0,2),
'a2': (1,0), 'b2': (1,1), 'c2': (1,2),
'a3': (2,0), 'b3': (2,1), 'c3': (2,2)}
if piece_posn not in valid_inputs:
print("LOL that's not a valid position! Try again.")
continue
 
(row, col) = valid_inputs[piece_posn]
piece = board_data[row][col]
if piece == empty_square:
print("What are you trying to pull, there's no piece in that space!")
continue
 
if piece != colour:
print("LOL that's not your piece, try again!")
continue
 
allowed_moves = get_allowed_moves(board_data, row, col)
 
if len(allowed_moves) == 0:
print('LOL nice try. That piece has no valid moves.')
continue
 
move = list(allowed_moves)[0]
if len(allowed_moves) > 1:
move = input(f'What move do you want to make ({",".join(list(allowed_moves))})? ')
if move not in allowed_moves:
print('LOL that move is not allowed. Try again.')
continue
 
if move == 'f':
board_data[row + direction][col] = board_data[row][col]
elif move == 'dl':
board_data[row + direction][col - 1] = board_data[row][col]
elif move == 'dr':
board_data[row + direction][col + 1] = board_data[row][col]
 
board_data[row][col] = empty_square
return board_data
 
 
def is_game_over(board_data):
if board_data[0][0] == white_pawn or board_data[0][1] == white_pawn or board_data[0][2] == white_pawn:
return white_pawn
 
if board_data[2][0] == black_pawn or board_data[2][1] == black_pawn or board_data[2][2] == black_pawn:
return black_pawn
 
white_count = 0
black_count = 0
black_allowed_moves = []
white_allowed_moves = []
for i in range(3):
for j in range(3):
moves = get_allowed_moves(board_data, i, j)
 
if board_data[i][j] == white_pawn:
white_count += 1
if len(moves) > 0:
white_allowed_moves.append((i,j,moves))
if board_data[i][j] == black_pawn:
black_count += 1
if len(moves) > 0:
black_allowed_moves.append((i,j,moves))
 
if white_count == 0 or len(white_allowed_moves) == 0:
return black_pawn
if black_count == 0 or len(black_allowed_moves) == 0:
return white_pawn
 
return "LOL NOPE"
 
def play_game(black_move, white_move):
 
board_data = [[black_pawn, black_pawn, black_pawn],
[empty_square, empty_square, empty_square],
[white_pawn, white_pawn, white_pawn]]
 
last_player = black_pawn
next_player = white_pawn
while is_game_over(board_data) == "LOL NOPE":
draw_board(board_data)
 
if (next_player == black_pawn):
board_data = black_move(board_data, next_player)
else:
board_data = white_move(board_data, next_player)
 
temp = last_player
last_player = next_player
next_player = temp
 
winner = is_game_over(board_data)
print(f'Congratulations {winner}!')
 
play_game(get_human_move, get_human_move)
</syntaxhighlight>
 
=={{header|Wren}}==
{{trans|Python}}
{{libheader|Wren-ioutil}}
{{libheader|Wren-str}}
Tweaked a little - allows input in either lower or upper case and always draws final board position.
<syntaxhighlight lang="wren">import "./ioutil" for Input, Output
import "./str" for Str
 
var blackPawn = " \u265f "
var whitePawn = " \u2659 "
var emptySquare = " "
 
var drawBoard = Fn.new { |boardData|
var bgBlack = "\e[48;5;237m"
var bgWhite = "\e[48;5;245m"
var clearToEol = "\e[0m\e[K\n"
var board = [
"1 ", bgBlack, boardData[0][0], bgWhite, boardData[0][1], bgBlack, boardData[0][2], clearToEol,
"2 ", bgWhite, boardData[1][0], bgBlack, boardData[1][1], bgWhite, boardData[1][2], clearToEol,
"3 ", bgBlack, boardData[2][0], bgWhite, boardData[2][1], bgBlack, boardData[2][2], clearToEol,
" A B C\n"
]
System.print()
Output.fwrite(board.join())
}
 
var getMovementDirection = Fn.new { |color|
var direction = -1
if (color == blackPawn) {
direction = 1
} else if (color != whitePawn) {
Fiber.abort("Invalid piece color")
}
return direction
}
 
var getOtherColor = Fn.new { |color|
if (color == blackPawn) {
return whitePawn
} else if (color == whitePawn) {
return blackPawn
} else {
Fiber.abort("Invalid piece color")
}
}
 
var getAllowedMoves = Fn.new { |boardData, row, col|
var allowedMoves = []
if (boardData[row][col] == emptySquare) return allowedMoves
var color = boardData[row][col]
var otherColor = getOtherColor.call(color)
var direction = getMovementDirection.call(color)
if (row + direction < 0 || row + direction > 2) return allowedMoves
if (boardData[row + direction][col] == emptySquare) allowedMoves.add("f")
if (col > 0 && boardData[row + direction][col - 1] == otherColor) allowedMoves.add("dl")
if (col < 2 && boardData[row + direction][col + 1] == otherColor) allowedMoves.add("dr")
return allowedMoves
}
 
var getHumanMove = Fn.new { |boardData, color|
// The direction the pawns may move depends on the colour; assuming that white starts at the bottom.
var direction = getMovementDirection.call(color)
var validInputs = {
"a1": [0,0], "b1": [0,1], "c1": [0,2],
"a2": [1,0], "b2": [1,1], "c2": [1,2],
"a3": [2,0], "b3": [2,1], "c3": [2,2]
}
var keys = validInputs.keys.toList
while (true) {
var piecePosn = Str.lower(Input.text("What %(color) do you want to move? : ", 2, 2))
if (!keys.contains(piecePosn)) {
System.print("LOL that's not a valid position! Try again.")
continue
}
var rc = validInputs[piecePosn]
var row = rc[0]
var col = rc[1]
var piece = boardData[row][col]
if (piece == emptySquare) {
System.print("What are you trying to pull, there's no piece in that space!")
continue
}
if (piece != color) {
System.print("LOL that's not your piece, try again!")
continue
}
var allowedMoves = getAllowedMoves.call(boardData, row, col)
 
if (allowedMoves.count == 0) {
System.print("LOL nice try. That piece has no valid moves.")
continue
}
var move = allowedMoves.toList[0]
if (allowedMoves.count > 1) {
var options = allowedMoves.join(", ")
move = Str.lower(Input.text("What move do you want to make [%(options)]? : ", 1, 2))
if (!allowedMoves.contains(move)) {
System.print("LOL that move is not allowed. Try again.")
continue
}
}
if (move == "f") {
boardData[row + direction][col] = boardData[row][col]
} else if (move == "dl") {
boardData[row + direction][col - 1] = boardData[row][col]
} else if (move == "dr") {
boardData[row + direction][col + 1] = boardData[row][col]
}
boardData[row][col] = emptySquare
return boardData
}
}
 
var isGameOver = Fn.new { |boardData|
if (boardData[0][0] == whitePawn || boardData[0][1] == whitePawn || boardData[0][2] == whitePawn) {
return whitePawn
}
if (boardData[2][0] == blackPawn || boardData[2][1] == blackPawn || boardData[2][2] == blackPawn) {
return blackPawn
}
var whiteCount = 0
var blackCount = 0
var blackAllowedMoves = []
var whiteAllowedMoves = []
for (i in 0..2) {
for (j in 0..2) {
var moves = getAllowedMoves.call(boardData, i, j)
if (boardData[i][j] == whitePawn) {
whiteCount = whiteCount + 1
if (moves.count > 0) whiteAllowedMoves.add([i, j, moves])
} else if (boardData[i][j] == blackPawn) {
blackCount = blackCount + 1
if (moves.count > 0) blackAllowedMoves.add([i, j, moves])
}
}
}
if (whiteCount == 0 || whiteAllowedMoves.count == 0) return blackPawn
if (blackCount == 0 || blackAllowedMoves.count == 0) return whitePawn
return "LOL NOPE"
}
 
var playGame = Fn.new { |blackMove, whiteMove|
var boardData = [
[blackPawn, blackPawn, blackPawn],
[emptySquare, emptySquare, emptySquare],
[whitePawn, whitePawn, whitePawn]
]
var lastPlayer = blackPawn
var nextPlayer = whitePawn
while (isGameOver.call(boardData) == "LOL NOPE") {
drawBoard.call(boardData)
if (nextPlayer == blackPawn) {
boardData = blackMove.call(boardData, nextPlayer)
} else {
boardData = whiteMove.call(boardData, nextPlayer)
}
var temp = lastPlayer
lastPlayer = nextPlayer
nextPlayer = temp
}
drawBoard.call(boardData)
var winner = isGameOver.call(boardData)
System.print("Congratulations %(winner)!")
}
 
playGame.call(getHumanMove, getHumanMove)</syntaxhighlight>
 
{{out}}
Sample game:
<pre>
1 ♟ ♟ ♟
2
3 ♙ ♙ ♙
A B C
What ♙ do you want to move? : b3
 
1 ♟ ♟ ♟
2 ♙
3 ♙ ♙
A B C
What ♟ do you want to move? : a1
What move do you want to make [f, dr]? : dr
 
1 ♟ ♟
2 ♟
3 ♙ ♙
A B C
What ♙ do you want to move? : c3
What move do you want to make [f, dl]? : dl
 
1 ♟ ♟
2 ♙
3 ♙
A B C
What ♟ do you want to move? : c1
What move do you want to make [f, dl]? : f
 
1 ♟
2 ♙ ♟
3 ♙
A B C
What ♙ do you want to move? : a3
 
1 ♟
2 ♙ ♙ ♟
3
A B C
What ♟ do you want to move? : c2
 
1 ♟
2 ♙ ♙
3 ♟
A B C
Congratulations ♟ !
</pre>
9,476

edits