Finish go
This commit is contained in:
parent
5351850fa1
commit
6125ac5eab
3 changed files with 179 additions and 82 deletions
|
@ -1,14 +1,62 @@
|
|||
|
||||
let stones = document.getElementsByClassName('go-stone-empty');
|
||||
let inputRow = document.getElementById('go-input-row');
|
||||
let inputCol = document.getElementById('go-input-col');
|
||||
let inputPlace = document.getElementById('go-input-place');
|
||||
function setupEventHandler() {
|
||||
let stones = document.getElementsByClassName('go-stone-empty');
|
||||
let inputRow = document.getElementById('go-input-row');
|
||||
let inputCol = document.getElementById('go-input-col');
|
||||
let inputPlace = document.getElementById('go-input-place');
|
||||
|
||||
for (let i = 0; i < stones.length; ++i) {
|
||||
stones[i].onclick = (e) => {
|
||||
console.log('place');
|
||||
inputCol.value = stones[i].attributes['data-col'].value;
|
||||
inputRow.value = stones[i].attributes['data-row'].value;
|
||||
inputPlace.click();
|
||||
};
|
||||
for (let i = 0; i < stones.length; ++i) {
|
||||
stones[i].onclick = (e) => {
|
||||
console.log('place');
|
||||
inputCol.value = stones[i].attributes['data-col'].value;
|
||||
inputRow.value = stones[i].attributes['data-row'].value;
|
||||
inputPlace.click();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let updateInterval = null;
|
||||
if (document.getElementsByClassName('go-territory').length == 0) {
|
||||
setInterval(update, 500);
|
||||
}
|
||||
|
||||
function update() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.addEventListener('load', (ev) => {
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(req.responseText, 'text/html');
|
||||
let currentField = document.querySelector('svg');
|
||||
let newField = doc.querySelector('svg');
|
||||
let currentStones = currentField.getElementsByClassName('go-stone');
|
||||
let newStones = newField.getElementsByClassName('go-stone');
|
||||
let newTerritory = doc.getElementsByClassName('go-territory');
|
||||
let currentInfo = document.getElementById('gameinfo');
|
||||
let newInfo = doc.getElementById('gameinfo');
|
||||
if (newTerritory.length > 0) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < currentStones.length; ++i) {
|
||||
if (doc.getElementById(currentStones[i].id) === null) {
|
||||
currentStones[i].remove();
|
||||
}
|
||||
}
|
||||
if (currentInfo.classList[0] != newInfo.classList[0]) {
|
||||
console.log(newInfo.classList[0]);
|
||||
currentInfo.innerHTML = newInfo.innerHTML;
|
||||
currentInfo.classList.remove(currentInfo.classList[0]);
|
||||
currentInfo.classList.add(newInfo.classList[0]);
|
||||
}
|
||||
for (let i = 0; i < newStones.length; ++i) {
|
||||
if (document.getElementById(newStones[i].id) === null) {
|
||||
currentField.append(newStones[i]);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventHandler();
|
||||
});
|
||||
req.open('GET', window.location.href);
|
||||
req.send();
|
||||
}
|
||||
|
||||
setupEventHandler();
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
{%- block body %}
|
||||
<h1>Go: {{ game.human_id }}</h1>
|
||||
|
||||
<form method="POST">
|
||||
<form method="POST" id="gogame">
|
||||
|
||||
<div class="field">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="{{ 50 * boardsize }}" height="{{ 50 * boardsize }}" version="2.0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="gogamesvg "width="{{ 50 * boardsize }}" height="{{ 50 * boardsize }}" version="2.0">
|
||||
<defs>
|
||||
<radialGradient id="white-stone" fx="40%" fy="25%">
|
||||
<stop offset="0%" stop-color="#ffffff" />
|
||||
|
@ -45,15 +45,15 @@
|
|||
{% for col in range(boardsize) %}
|
||||
<!-- {{ row }} {{ col }} {{ field[row][col] }} -->
|
||||
{% if field[row][col] == 0 and current_player == colormap[player.uuid] and not abandoned and score is none %}
|
||||
<circle class="go-stone go-stone-empty go-stone-{{ colormap[player.uuid].name.lower() }}" data-row="{{ row }}" data-col="{{ col }}" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
<circle id="go-stone-{{ row }}-{{ col }}-empty" class="go-stone go-stone-empty go-stone-{{ colormap[player.uuid].name.lower() }}" data-row="{{ row }}" data-col="{{ col }}" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
{% elif field[row][col] == 1 %}
|
||||
<circle class="go-stone go-stone-white" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
<circle id="go-stone-{{ row }}-{{ col }}-white" class="go-stone go-stone-white" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
{% elif field[row][col] == 2 %}
|
||||
<circle class="go-stone go-stone-black" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
<circle id="go-stone-{{ row }}-{{ col }}-black" class="go-stone go-stone-black" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
{% elif field[row][col] == 5 %}
|
||||
<circle class="go-stone go-stone-dead go-stone-white" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
<circle id="go-stone-{{ row }}-{{ col }}-white-dead" class="go-stone go-stone-dead go-stone-white" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
{% elif field[row][col] == 6 %}
|
||||
<circle class="go-stone go-stone-dead go-stone-black" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
<circle id="go-stone-{{ row }}-{{ col }}-black-dead" class="go-stone go-stone-dead go-stone-black" cx="{{ col * 50 + 25 }}" cy="{{ row * 50 + 25 }}" r="20" />
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
@ -61,65 +61,71 @@
|
|||
{% for col in range(boardsize) %}
|
||||
<!-- {{ territory[row][col] }} -->
|
||||
{% if territory[row][col] == 3 %}
|
||||
<rect class="go-territory go-territory-white" x="{{ col * 50 + 20 }}" y="{{ row * 50 + 20 }}" width="10" height="10" />
|
||||
<rect id="go-territory-{{ row }}-{{ col }}-white" class="go-territory go-territory-white" x="{{ col * 50 + 20 }}" y="{{ row * 50 + 20 }}" width="10" height="10" />
|
||||
{% elif territory[row][col] == 4 %}
|
||||
<rect class="go-territory go-territory-black" x="{{ col * 50 + 20 }}" y="{{ row * 50 + 20 }}" width="10" height="10" />
|
||||
<rect id="go-territory-{{ row }}-{{ col }}-white" class="go-territory go-territory-black" x="{{ col * 50 + 20 }}" y="{{ row * 50 + 20 }}" width="10" height="10" />
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{% if abandoned %}
|
||||
<b>Game abandoned</b>
|
||||
{% elif score is not none %}
|
||||
<b>
|
||||
{% if score > 0 %}
|
||||
Black won with {{ score }} points.
|
||||
{% else %}
|
||||
White won with {{ -score }} points.
|
||||
{% endif %}
|
||||
</b>
|
||||
{% elif current_player == colormap[player.uuid] %}
|
||||
<input id="go-input-pass" name="go-input-submit" type="submit" value="Pass" />
|
||||
<input id="go-input-place" name="go-input-submit" type="submit" value="Place" />
|
||||
<input id="go-input-row" name="go-input-row" type="hidden" value="-1" />
|
||||
<input id="go-input-col" name="go-input-col" type="hidden" value="-1" />
|
||||
{% endif %}
|
||||
<div id="gameinfo" class="{% if abandoned or score is not none %}over{% elif current_player == colormap[player.uuid] %}active{% else %}passive{% endif %}">
|
||||
{% if abandoned %}
|
||||
<b>Game abandoned</b>
|
||||
{% elif score is not none %}
|
||||
<b>
|
||||
{% if score > 0 %}
|
||||
Black won with {{ score }} points.
|
||||
{% else %}
|
||||
White won with {{ -score }} points.
|
||||
{% endif %}
|
||||
</b>
|
||||
{% elif current_player == colormap[player.uuid] %}
|
||||
{% if passed[0] %}White passed{% endif %}
|
||||
{% if passed[1] %}Black passed{% endif %}
|
||||
<input id="go-input-pass" name="go-input-submit" type="submit" value="Pass" />
|
||||
<input id="go-input-place" name="go-input-submit" type="submit" value="Place" />
|
||||
<input id="go-input-row" name="go-input-row" type="hidden" value="-1" />
|
||||
<input id="go-input-col" name="go-input-col" type="hidden" value="-1" />
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<a href="{{ baseurl }}/{{ player.uuid }}/{{ game.uuid }}/play">Refresh</a>
|
||||
|
||||
<ul>
|
||||
{% for p in players.keys() %}
|
||||
<li>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" version="2.0">
|
||||
<defs>
|
||||
<radialGradient id="white-stone" fx="40%" fy="25%">
|
||||
<stop offset="0%" stop-color="#ffffff" />
|
||||
<stop offset="70%" stop-color="#dddddd" />
|
||||
<stop offset="100%" stop-color="#bbbbbb" />
|
||||
</radialGradient>
|
||||
<radialGradient id="black-stone" fx="40%" fy="25%">
|
||||
<stop offset="0%" stop-color="#444444" />
|
||||
<stop offset="70%" stop-color="#222222" />
|
||||
<stop offset="100%" stop-color="#000000" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<circle class="go-stone go-stone-{{ colormap[p].name.lower() }}" cx="10" cy="10" r="9" />
|
||||
</svg>
|
||||
{% if p == player.uuid %}
|
||||
<b>{{ game.players[p].name }}</b>
|
||||
{% elif aiplayer is not none and p == aiplayer.uuid %}
|
||||
GNU Go AI
|
||||
{% else %}
|
||||
{{ game.players[p].name }}
|
||||
{% endif %}
|
||||
: {{ captures[colormap[p].value-1] }}
|
||||
{% if colormap[p].value == 1 %}
|
||||
+ {{ komi }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
|
||||
<a href="{{ baseurl }}/{{ player.uuid }}/{{ game.uuid }}/play">Refresh</a>
|
||||
|
||||
<ul>
|
||||
{% for p in players.keys() %}
|
||||
<li>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" version="2.0">
|
||||
<defs>
|
||||
<radialGradient id="white-stone" fx="40%" fy="25%">
|
||||
<stop offset="0%" stop-color="#ffffff" />
|
||||
<stop offset="70%" stop-color="#dddddd" />
|
||||
<stop offset="100%" stop-color="#bbbbbb" />
|
||||
</radialGradient>
|
||||
<radialGradient id="black-stone" fx="40%" fy="25%">
|
||||
<stop offset="0%" stop-color="#444444" />
|
||||
<stop offset="70%" stop-color="#222222" />
|
||||
<stop offset="100%" stop-color="#000000" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<circle class="go-stone go-stone-{{ colormap[p].name.lower() }}" cx="10" cy="10" r="9" />
|
||||
</svg>
|
||||
{% if p == player.uuid %}
|
||||
<b>{{ game.players[p].name }}</b>
|
||||
{% else %}
|
||||
{{ game.players[p].name }}
|
||||
{% endif %}
|
||||
: {{ captures[colormap[p].value-1] }}
|
||||
{% if colormap[p].value == 1 %}
|
||||
+ {{ komi }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<script src="{{ baseurl }}/static/gogame.js"></script>
|
||||
{%- endblock %}
|
||||
|
|
|
@ -11,6 +11,7 @@ import re
|
|||
from threading import Timer, Lock
|
||||
|
||||
from webgames.puzzle import Puzzle
|
||||
from webgames.player import Player
|
||||
|
||||
class BoardSize(enum.Enum):
|
||||
SMALL = 9
|
||||
|
@ -48,6 +49,7 @@ class GoPuzzle(Puzzle):
|
|||
self._field = None
|
||||
self._territory = None
|
||||
self._abandoned = False
|
||||
self.ai = None
|
||||
self._timer = None
|
||||
self._tlock = Lock()
|
||||
self._captures = [0, 0]
|
||||
|
@ -76,12 +78,16 @@ class GoPuzzle(Puzzle):
|
|||
with self._tlock:
|
||||
self._timer = Timer(ABANDON_TIMEOUT, self._shutdown)
|
||||
self._timer.start()
|
||||
ailevel = int(options.get('go-input-ailevel', 10))
|
||||
self.size = BoardSize(int(options.get('go-input-boardsize', BoardSize.MEDIUM.value)))
|
||||
self.gnugo = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.gnugo.connect('/run/gnugo.sock')
|
||||
self._gtp(f'boardsize {self.size.value}')
|
||||
self._gtp('clear_board')
|
||||
self._gtp(f'komi {self.komi}')
|
||||
if len(self._players) == 1:
|
||||
self.ai = Player('GnuGO AI')
|
||||
self._players[self.ai.uuid] = self.ai.uuid
|
||||
players = list(self._players.keys())
|
||||
random.shuffle(players)
|
||||
self.colormap[players[0]] = FieldState.BLACK
|
||||
|
@ -95,16 +101,33 @@ class GoPuzzle(Puzzle):
|
|||
self._field[i].append(0)
|
||||
self._territory[i].append(0)
|
||||
self._urlbase = urlbase
|
||||
# First move if GnuGO AI is black
|
||||
if self.ai is not None:
|
||||
self._gtp(f'level {ailevel}')
|
||||
if self.colormap[self.ai.uuid] == FieldState.BLACK:
|
||||
self._gtp('genmove_black')
|
||||
self.current_player = FieldState(3 - self.current_player.value)
|
||||
self.update_field()
|
||||
|
||||
def get_extra_options_html(self) -> str:
|
||||
return '''
|
||||
options = '''
|
||||
<label for="go-input-boardsize">Board Size: </label>
|
||||
<select id="go-input-boardsize" name="go-input-boardize">
|
||||
<select id="go-input-boardsize" name="go-input-boardsize">
|
||||
<option value="9">9x9</option>
|
||||
<option value="13">13x13</option>
|
||||
<option value="13" selected="selected">13x13</option>
|
||||
<option value="19">19x19</option>
|
||||
</select>
|
||||
'''
|
||||
if len(self._players) == 1:
|
||||
options += '''
|
||||
<p>
|
||||
Press Play! to play against GNU Go or invite a second player.
|
||||
<label for="go-input-ailevel">GNU Go Level: </label>
|
||||
<input id="go-input-ailevel" name="go-input-ailevel" type="number" min="0" max="10" value="10" />
|
||||
</p>
|
||||
'''
|
||||
return options
|
||||
|
||||
|
||||
def add_player(self, uuid: UUID) -> None:
|
||||
if len(self._players) >= 2:
|
||||
|
@ -128,14 +151,16 @@ class GoPuzzle(Puzzle):
|
|||
for vertex in vertices:
|
||||
if len(vertex.strip()) != 2:
|
||||
continue
|
||||
y, x = vertex.strip()
|
||||
col = int(x) - 1
|
||||
row = self.colnames.index(y.upper())
|
||||
x, y = vertex.strip()
|
||||
row = int(y) - 1
|
||||
col = self.colnames.index(x.upper())
|
||||
if state == 'dead':
|
||||
if self._field[row][col] == FieldState.BLACK.value:
|
||||
self._field[row][col] = FieldState.BLACK_DEAD.value
|
||||
self._territory[row][col] = FieldState.WHITE_TERRITORY.value
|
||||
elif self._field[row][col] == FieldState.WHITE.value:
|
||||
self._field[row][col] = FieldState.WHITE_DEAD.value
|
||||
self._territory[row][col] = FieldState.BLACK_TERRITORY.value
|
||||
elif state == 'black_territory':
|
||||
self._territory[row][col] = FieldState.BLACK_TERRITORY.value
|
||||
elif state == 'white_territory':
|
||||
|
@ -145,11 +170,10 @@ class GoPuzzle(Puzzle):
|
|||
|
||||
|
||||
def process_action(self, player, action) -> None:
|
||||
if self.colormap[player] != self.current_player:
|
||||
if self.colormap[player] != self.current_player or self.gnugo is None:
|
||||
return
|
||||
if 'go-input-submit' not in action:
|
||||
return
|
||||
self.current_player = FieldState(3 - self.current_player.value)
|
||||
with self._tlock:
|
||||
if self._abandoned:
|
||||
return
|
||||
|
@ -173,17 +197,34 @@ class GoPuzzle(Puzzle):
|
|||
try:
|
||||
ret = self._gtp(f'play {player} {self.colnames[col]}{row+1}')
|
||||
except RuntimeError:
|
||||
self.current_player = FieldState[3 - self.current_player.value]
|
||||
self.update_field()
|
||||
return
|
||||
if self.ai is None:
|
||||
self.current_player = FieldState(3 - self.current_player.value)
|
||||
elif self.gnugo is not None:
|
||||
ai_color = self.colormap[self.ai.uuid].name.lower()
|
||||
ai_resp = self._gtp(f'genmove_{ai_color}')
|
||||
if 'pass' in ai_resp.lower():
|
||||
if self.colormap[self.ai.uuid] == FieldState.BLACK:
|
||||
self.black_passed = True
|
||||
else:
|
||||
self.white_passed = True
|
||||
if self.black_passed and self.white_passed:
|
||||
self._score_game()
|
||||
else:
|
||||
self.black_passed = False
|
||||
self.white_passed = False
|
||||
self.update_field()
|
||||
|
||||
def update_field(self):
|
||||
if self.gnugo is None:
|
||||
return
|
||||
ret = self._gtp('showboard')
|
||||
for line in ret.splitlines():
|
||||
|
||||
cm = re.match('.*(BLACK|WHITE) ... has captured (\\d+) stones.*', line)
|
||||
if cm is not None:
|
||||
color = FieldState[cm[1]]
|
||||
self._captures[2-color.value] = int(cm[2])
|
||||
self._captures[color.value-1] = int(cm[2])
|
||||
|
||||
line = line.strip()
|
||||
if len(line) > 0 and line[0] in [str(i) for i in range(10)]:
|
||||
|
@ -194,9 +235,9 @@ class GoPuzzle(Puzzle):
|
|||
if token in ['.', '+']:
|
||||
self._field[row][col] = FieldState.EMPTY.value
|
||||
elif token == 'X':
|
||||
self._field[row][col] = FieldState.WHITE.value
|
||||
elif token == 'O':
|
||||
self._field[row][col] = FieldState.BLACK.value
|
||||
elif token == 'O':
|
||||
self._field[row][col] = FieldState.WHITE.value
|
||||
|
||||
def serialize(self, player):
|
||||
return {
|
||||
|
@ -214,6 +255,7 @@ class GoPuzzle(Puzzle):
|
|||
ser = self.serialize(player)
|
||||
return tmpl.render(player=player,
|
||||
players=self._players,
|
||||
aiplayer=self.ai,
|
||||
komi=self.komi,
|
||||
score=self._score,
|
||||
game=game,
|
||||
|
@ -222,6 +264,7 @@ class GoPuzzle(Puzzle):
|
|||
field=ser['field'],
|
||||
territory=ser['territory'],
|
||||
captures=ser['captures'],
|
||||
passed=[self.white_passed, self.black_passed],
|
||||
boardsize=self.size.value,
|
||||
colormap=self.colormap,
|
||||
current_player=self.current_player,
|
||||
|
|
Loading…
Reference in a new issue