diff options
author | Matt Strapp <strap012@umn.edu> | 2021-04-26 17:06:13 -0500 |
---|---|---|
committer | Matt Strapp <strap012@umn.edu> | 2021-04-26 17:06:13 -0500 |
commit | e58a60ed18bde5db28ba96910df518a61b3999b2 (patch) | |
tree | 3667c6271681ecdf584d5f619246b25e3b26b01f /python/dotsandboxes | |
parent | Finally fix (diff) | |
download | csci4511w-e58a60ed18bde5db28ba96910df518a61b3999b2.tar csci4511w-e58a60ed18bde5db28ba96910df518a61b3999b2.tar.gz csci4511w-e58a60ed18bde5db28ba96910df518a61b3999b2.tar.bz2 csci4511w-e58a60ed18bde5db28ba96910df518a61b3999b2.tar.lz csci4511w-e58a60ed18bde5db28ba96910df518a61b3999b2.tar.xz csci4511w-e58a60ed18bde5db28ba96910df518a61b3999b2.tar.zst csci4511w-e58a60ed18bde5db28ba96910df518a61b3999b2.zip |
Refactor jsut about everything
Diffstat (limited to 'python/dotsandboxes')
-rw-r--r-- | python/dotsandboxes/README.md | 134 | ||||
-rwxr-xr-x | python/dotsandboxes/dotsandboxesagent | 5 | ||||
-rw-r--r-- | python/dotsandboxes/dotsandboxesagent.py | 212 | ||||
-rw-r--r-- | python/dotsandboxes/dotsandboxescompete.py | 212 | ||||
-rw-r--r-- | python/dotsandboxes/dotsandboxesserver.py | 60 | ||||
-rw-r--r-- | python/dotsandboxes/requirements.txt | 1 | ||||
-rw-r--r-- | python/dotsandboxes/static/dotsandboxes.css | 10 | ||||
-rw-r--r-- | python/dotsandboxes/static/dotsandboxes.html | 50 | ||||
-rw-r--r-- | python/dotsandboxes/static/dotsandboxes.js | 454 |
9 files changed, 0 insertions, 1138 deletions
diff --git a/python/dotsandboxes/README.md b/python/dotsandboxes/README.md deleted file mode 100644 index e3f844c..0000000 --- a/python/dotsandboxes/README.md +++ /dev/null @@ -1,134 +0,0 @@ -Dots and Boxes application -========================== - -Live demo: https://people.cs.kuleuven.be/wannes.meert/dotsandboxes/play - -![Screenshot of Dots and Boxes](https://people.cs.kuleuven.be/wannes.meert/dotsandboxes/screenshot.png?v=2) - -This setup is part of the course "Machine Learning: Project" (KU Leuven, -Faculty of engineering, Department of Computer Science, -[DTAI research group](https://dtai.cs.kuleuven.be)). - - -Installation ------------- - -The example agent is designed for Python 3.6 and requires the -[websockets](https://websockets.readthedocs.io) package. Dependencies can be -installed using pip: - - $ pip install -r requirements.txt - - -Start the game GUI ------------------- - -This program shows a web-based GUI to play the Dots and Boxes -game. This supports human-human, agent-human and agent-agent combinations. -It is a simple Javascript based application that runs entirely in the browser. -You can start it by opening the file `static/dotsandboxes.html` in a browser. -Or alternatively, you can start the app using the included simple server: - - $ ./dotsandboxesserver.py 8080 - -The game can then be played by directing your browser to http://127.0.0.1:8080. - - -Start the agent client ----------------------- - -This is the program that runs a game-playing agent. This application listens -to [websocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) -requests that communicate game information and sends back the next action it -wants to play. - -Starting the agent client is done using the following command: - - $ ./dotsandboxesagent <port> - -This starts a websocket on the given port that can receveive JSON messages. - -The JSON messages given below should be handled by your agent. -Take into account the maximal time allowed to reply. - -### Initiate the game - -Both players get a message that a new game has started: - - { - "type": "start", - "player": 1, - "timelimit", 0.5, - "grid": [5, 5], - "game": "123456" - } - -where `player` is the number assigned to this agent, `timelimit` is the -time in seconds in which you need to send your action back to the server, -and `grid` is the grid size in rows and columns. - -If you are player 1, reply with the first action you want to perform: - - { - "type": "action", - "location": [1, 1], - "orientation": "v" - } - -The field `location` is expressed as row and column (zero-based numbering) and -`orientation` is either "v" (vertical) or "h" (horizontal). - - -### Action in the game - -When an action is played, the message sent to both players is: - - { - "type": "action", - "game": "123456", - "player": 1, - "nextplayer": 2, - "score": [0, 0], - "location": [1, 1], - "orientation": "v" - } - - -If it is your turn you should answer with a message that states your next -move: - - { - "type": "action", - "location": [1, 1], - "orientation": "v" - } - - -### Game end - -When the game ends after an action, the message is slightly altered: - - { - "type": "end", - "game": "123456", - "player": 1, - "nextplayer": 0, - "score": [3, 1], - "location": [1, 1], - "orientation": "v", - "winner": 1 - } - -The `type` field becomes `end` and a new field `winner` is set to the player -that has won the game. - - -Contact information -------------------- - -- Wannes Meert, https://people.cs.kuleuven.be/wannes.meert -- Hendrik Blockeel, https://people.cs.kuleuven.be/hendrik.blockeel -- Arne De Brabandere, https://people.cs.kuleuven.be/arne.debrabandere -- Sebastijan Dumančić, https://people.cs.kuleuven.be/sebastijan.dumancic -- Pieter Robberechts, https://people.cs.kuleuven.be/pieter.robberechts - diff --git a/python/dotsandboxes/dotsandboxesagent b/python/dotsandboxes/dotsandboxesagent deleted file mode 100755 index eecf719..0000000 --- a/python/dotsandboxes/dotsandboxesagent +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# It is not necessary to use a shell script for this. Dropping the .py -# extension and including the correct shebang is also correct. -python3 $(dirname "$0")/dotsandboxesagent.py $@ - diff --git a/python/dotsandboxes/dotsandboxesagent.py b/python/dotsandboxes/dotsandboxesagent.py deleted file mode 100644 index abf677b..0000000 --- a/python/dotsandboxes/dotsandboxesagent.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python3 -# encoding: utf-8 -""" -dotsandboxesagent.py - -Template for the Machine Learning Project course at KU Leuven (2017-2018) -of Hendrik Blockeel and Wannes Meert. - -Copyright (c) 2018 KU Leuven. All rights reserved. -""" -import sys -import argparse -import logging -import asyncio -import websockets -import json -from collections import defaultdict -import random - -logger = logging.getLogger(__name__) -games = {} -agentclass = None - - -class DotsAndBoxesAgent: - """Example Dots and Boxes agent implementation base class. - It returns a random next move. - - A DotsAndBoxesAgent object should implement the following methods: - - __init__ - - add_player - - register_action - - next_action - - end_game - - This class does not necessarily use the best data structures for the - approach you want to use. - """ - def __init__(self, player, nb_rows, nb_cols, timelimit): - """Create Dots and Boxes agent. - - :param player: Player number, 1 or 2 - :param nb_rows: Rows in grid - :param nb_cols: Columns in grid - :param timelimit: Maximum time allowed to send a next action. - """ - self.player = {player} - self.timelimit = timelimit - self.ended = False - self.nb_rows = nb_rows - self.nb_cols = nb_cols - rows = [] - for ri in range(nb_rows + 1): - columns = [] - for ci in range(nb_cols + 1): - columns.append({"v": 0, "h": 0}) - rows.append(columns) - self.cells = rows - - def add_player(self, player): - """Use the same agent for multiple players.""" - self.player.add(player) - - def register_action(self, row, column, orientation, player): - """Register action played in game. - - :param row: - :param columns: - :param orientation: "v" or "h" - :param player: 1 or 2 - """ - self.cells[row][column][orientation] = player - - def next_action(self): - """Return the next action this agent wants to perform. - - In this example, the function implements a random move. Replace this - function with your own approach. - - :return: (row, column, orientation) - """ - logger.info("Computing next move (grid={}x{}, player={})"\ - .format(self.nb_rows, self.nb_cols, self.player)) - # Random move - free_lines = [] - for ri in range(len(self.cells)): - row = self.cells[ri] - for ci in range(len(row)): - cell = row[ci] - if ri < (len(self.cells) - 1) and cell["v"] == 0: - free_lines.append((ri, ci, "v")) - if ci < (len(row) - 1) and cell["h"] == 0: - free_lines.append((ri, ci, "h")) - if len(free_lines) == 0: - # Board full - return None - movei = random.randint(0, len(free_lines) - 1) - r, c, o = free_lines[movei] - return r, c, o - - def end_game(self): - self.ended = True - - -## MAIN EVENT LOOP - -async def handler(websocket, path): - logger.info("Start listening") - game = None - # msg = await websocket.recv() - try: - async for msg in websocket: - logger.info("< {}".format(msg)) - try: - msg = json.loads(msg) - except json.decoder.JSONDecodeError as err: - logger.error(err) - return False - game = msg["game"] - answer = None - if msg["type"] == "start": - # Initialize game - if msg["game"] in games: - games[msg["game"]].add_player(msg["player"]) - else: - nb_rows, nb_cols = msg["grid"] - games[msg["game"]] = agentclass(msg["player"], - nb_rows, - nb_cols, - msg["timelimit"]) - if msg["player"] == 1: - # Start the game - nm = games[game].next_action() - print('nm = {}'.format(nm)) - if nm is None: - # Game over - logger.info("Game over") - continue - r, c, o = nm - answer = { - 'type': 'action', - 'location': [r, c], - 'orientation': o - } - else: - # Wait for the opponent - answer = None - - elif msg["type"] == "action": - # An action has been played - r, c = msg["location"] - o = msg["orientation"] - games[game].register_action(r, c, o, msg["player"]) - if msg["nextplayer"] in games[game].player: - # Compute your move - nm = games[game].next_action() - if nm is None: - # Game over - logger.info("Game over") - continue - nr, nc, no = nm - answer = { - 'type': 'action', - 'location': [nr, nc], - 'orientation': no - } - else: - answer = None - - elif msg["type"] == "end": - # End the game - games[msg["game"]].end_game() - answer = None - else: - logger.error("Unknown message type:\n{}".format(msg)) - - if answer is not None: - print(answer) - await websocket.send(json.dumps(answer)) - logger.info("> {}".format(answer)) - except websockets.exceptions.ConnectionClosed as err: - logger.info("Connection closed") - logger.info("Exit handler") - - -def start_server(port): - server = websockets.serve(handler, 'localhost', port) - print("Running on ws://127.0.0.1:{}".format(port)) - asyncio.get_event_loop().run_until_complete(server) - asyncio.get_event_loop().run_forever() - - -## COMMAND LINE INTERFACE - -def main(argv=None): - global agentclass - parser = argparse.ArgumentParser(description='Start agent to play Dots and Boxes') - parser.add_argument('--verbose', '-v', action='count', default=0, help='Verbose output') - parser.add_argument('--quiet', '-q', action='count', default=0, help='Quiet output') - parser.add_argument('port', metavar='PORT', type=int, help='Port to use for server') - args = parser.parse_args(argv) - - logger.setLevel(max(logging.INFO - 10 * (args.verbose - args.quiet), logging.DEBUG)) - logger.addHandler(logging.StreamHandler(sys.stdout)) - - agentclass = DotsAndBoxesAgent - start_server(args.port) - - -if __name__ == "__main__": - sys.exit(main()) - diff --git a/python/dotsandboxes/dotsandboxescompete.py b/python/dotsandboxes/dotsandboxescompete.py deleted file mode 100644 index ee2aee8..0000000 --- a/python/dotsandboxes/dotsandboxescompete.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python3 -# encoding: utf-8 -""" -dotsandboxescompete.py - -Template for the Machine Learning Project course at KU Leuven (2017-2018) -of Hendrik Blockeel and Wannes Meert. - -Copyright (c) 2018 KU Leuven. All rights reserved. -""" - -import sys -import argparse -import logging -import asyncio -import websockets -import json -from collections import defaultdict -import random -import uuid -import time - -logger = logging.getLogger(__name__) - - -def start_competition(address1, address2, nb_rows, nb_cols, timelimit): - asyncio.get_event_loop().run_until_complete(connect_agent(address1, address2, nb_rows, nb_cols, timelimit)) - - -async def connect_agent(uri1, uri2, nb_rows, nb_cols, timelimit): - cur_game = str(uuid.uuid4()) - winner = None - cells = [] - cur_player = 1 - points = [0, 0, 0] - timings = [None, [], []] - - for ri in range(nb_rows + 1): - columns = [] - for ci in range(nb_cols + 1): - columns.append({"v":0, "h":0, "p":0}) - cells.append(columns) - - logger.info("Connecting to {}".format(uri1)) - async with websockets.connect(uri1) as websocket1: - logger.info("Connecting to {}".format(uri2)) - async with websockets.connect(uri2) as websocket2: - logger.info("Connected") - - # Start game - msg = { - "type": "start", - "player": 1, - "timelimit": timelimit, - "game": cur_game, - "grid": [nb_rows, nb_cols] - } - await websocket1.send(json.dumps(msg)) - msg["player"] = 2 - await websocket2.send(json.dumps(msg)) - - # Run game - while winner is None: - ask_time = time.time() - logger.info("Waiting for player {}".format(cur_player)) - if cur_player == 1: - msg = await websocket1.recv() - else: - msg = await websocket2.recv() - recv_time = time.time() - diff_time = recv_time - ask_time - timings[cur_player].append(diff_time) - logger.info("Message received after (s): {}".format(diff_time)) - try: - msg = json.loads(msg) - except json.decoder.JSONDecodeError as err: - logger.debug(err) - continue - if msg["type"] != "action": - logger.error("Unknown message: {}".format(msg)) - continue - r, c = msg["location"] - o = msg["orientation"] - next_player = user_action(r, c, o, cur_player, - cells, points, - nb_rows, nb_cols) - if points[1] + points[2] == nb_cols * nb_rows: - # Game over - winner = 1 - if points[2] == points[1]: - winner = 0 - if points[2] > points[1]: - winner = 2 - else: - msg = { - "type": "action", - "game": cur_game, - "player": cur_player, - "nextplayer": next_player, - "score": [points[1], points[2]], - "location": [r, c], - "orientation": o - } - await websocket1.send(json.dumps(msg)) - await websocket2.send(json.dumps(msg)) - - cur_player = next_player - - # End game - logger.info("Game ended: points1={} - points2={} - winner={}".format(points[1], points[2], winner)) - msg = { - "type": "end", - "game": cur_game, - "player": cur_player, - "nextplayer": 0, - "score": [points[1], points[2]], - "location": [r, c], - "orientation": o, - "winner": winner - } - await websocket1.send(json.dumps(msg)) - await websocket2.send(json.dumps(msg)) - - # Timings - for i in [1, 2]: - logger.info("Timings: player={} - avg={} - min={} - max={}"\ - .format(i, - sum(timings[i])/len(timings[i]), - min(timings[i]), - max(timings[i]))) - - logger.info("Closed connections") - - -def user_action(r, c, o, cur_player, cells, points, nb_rows, nb_cols): - logger.info("User action: player={} - r={} - c={} - o={}".format(cur_player, r, c, o)) - next_player = cur_player - won_cell = False - cell = cells[r][c] - if o == "h": - if cell["h"] != 0: - return cur_player - cell["h"] = cur_player - # Above - if r > 0: - if cells[r - 1][c]["v"] != 0 \ - and cells[r - 1][c + 1]["v"] != 0 \ - and cells[r - 1][c]["h"] != 0 \ - and cells[r][c]["h"] != 0: - won_cell = True - points[cur_player] += 1 - cells[r - 1][c]["p"] = cur_player - # Below - if r < nb_rows: - if cells[r][c]["v"] != 0 \ - and cells[r][c + 1]["v"] != 0 \ - and cells[r][c]["h"] != 0 \ - and cells[r + 1][c]["h"] != 0: - won_cell = True - points[cur_player] += 1 - cells[r][c]["p"] = cur_player - - if o == "v": - if cell["v"] != 0: - return cur_player - cell["v"] = cur_player; - # Left - if c > 0: - if cells[r][c - 1]["v"] != 0 \ - and cells[r][c]["v"] != 0 \ - and cells[r][c - 1]["h"] != 0 \ - and cells[r + 1][c - 1]["h"] != 0: - won_cell = True - points[cur_player] += 1 - cells[r][c - 1]["p"] = cur_player - # Right - if c < nb_cols: - if cells[r][c]["v"] != 0 \ - and cells[r][c + 1]["v"] != 0 \ - and cells[r][c]["h"] != 0 \ - and cells[r + 1][c]["h"] != 0: - won_cell = True - points[cur_player] += 1 - cells[r][c]["p"] = cur_player - - if not won_cell: - next_player = 3 - cur_player - else: - next_player = cur_player - print("Update points: player1={} - player2={}".format(points[1], points[2])) - return next_player - - -def main(argv=None): - parser = argparse.ArgumentParser(description='Start agent to play Dots and Boxes') - parser.add_argument('--verbose', '-v', action='count', default=0, help='Verbose output') - parser.add_argument('--quiet', '-q', action='count', default=0, help='Quiet output') - parser.add_argument('--cols', '-c', type=int, default=2, help='Number of columns') - parser.add_argument('--rows', '-r', type=int, default=2, help='Number of rows') - parser.add_argument('--timelimit', '-t', type=float, default=0.5, help='Time limit per request in seconds') - parser.add_argument('agents', nargs=2, metavar='AGENT', help='Websockets addresses for agents') - args = parser.parse_args(argv) - - logger.setLevel(max(logging.INFO - 10 * (args.verbose - args.quiet), logging.DEBUG)) - logger.addHandler(logging.StreamHandler(sys.stdout)) - - start_competition(args.agents[0], args.agents[1], args.rows, args.cols, args.timelimit) - - -if __name__ == "__main__": - sys.exit(main()) - diff --git a/python/dotsandboxes/dotsandboxesserver.py b/python/dotsandboxes/dotsandboxesserver.py deleted file mode 100644 index 1b66372..0000000 --- a/python/dotsandboxes/dotsandboxesserver.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -# encoding: utf-8 -""" -dotsandboxesserver.py - -Template for the Machine Learning Project course at KU Leuven (2017-2018) -of Hendrik Blockeel and Wannes Meert. - -Copyright (c) 2018 KU Leuven. All rights reserved. -""" - -import sys -import argparse -import logging -import http.server -import socketserver -import json - -logger = logging.getLogger(__name__) - - -class RequestHandler(http.server.SimpleHTTPRequestHandler): - def do_GET(self): - if self.path == "/": - self.send_response(302) - self.send_header("Location", "static/dotsandboxes.html") - self.end_headers() - return super().do_GET() - - def do_PUT(self): - response = { - 'result': 'ok' - } - self.send_response(200) - self.send_header('Content-type', 'application/json') - self.end_headers() - self.wfile.write(json.dumps(response).encode()) - - -def start_server(port): - with socketserver.TCPServer(("", port), RequestHandler) as httpd: - print("Running on http://127.0.0.1:{}".format(port)) - httpd.serve_forever() - - -def main(argv=None): - parser = argparse.ArgumentParser(description='Start server to play Dots and Boxes') - parser.add_argument('--verbose', '-v', action='count', default=0, help='Verbose output') - parser.add_argument('--quiet', '-q', action='count', default=0, help='Quiet output') - parser.add_argument('port', metavar='PORT', type=int, help='Port to use for server') - args = parser.parse_args(argv) - - logger.setLevel(max(logging.INFO - 10 * (args.verbose - args.quiet), logging.DEBUG)) - logger.addHandler(logging.StreamHandler(sys.stdout)) - - start_server(args.port) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/python/dotsandboxes/requirements.txt b/python/dotsandboxes/requirements.txt deleted file mode 100644 index 14774b4..0000000 --- a/python/dotsandboxes/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -websockets diff --git a/python/dotsandboxes/static/dotsandboxes.css b/python/dotsandboxes/static/dotsandboxes.css deleted file mode 100644 index 71b1d3b..0000000 --- a/python/dotsandboxes/static/dotsandboxes.css +++ /dev/null @@ -1,10 +0,0 @@ - -.footer { - color: #B3B3B3; - margin-bottom: 1ex; -} - -.footer a { - color: #87A0B3; -} - diff --git a/python/dotsandboxes/static/dotsandboxes.html b/python/dotsandboxes/static/dotsandboxes.html deleted file mode 100644 index 4e97508..0000000 --- a/python/dotsandboxes/static/dotsandboxes.html +++ /dev/null @@ -1,50 +0,0 @@ -<!DOCTYPE html> -<html> -<html lang="en"> -<meta charset="utf-8"> -<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> -<title>Dots and Boxes</title> -<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> -<link rel="stylesheet" href="dotsandboxes.css"> -</head> -<body> - <div class="container"> - <h1>Dots and Boxes</h1> - <div class="row"> - <div class="col-md"> - <div id="playing-area"></div> - </div> - <div class="col-md"> - <div class="form-group"> - <p>Size of game:</p> - <div class="input-group"> - <div class="input-group-prepend"> - <span class="input-group-text">Rows and Columns</span> - </div> - <input type="number" class="form-control" id="nb-rows" value=6> - <input type="number" class="form-control" id="nb-cols" value=6> - </div> - </div> - <div class="form-group"> - <p>Players:</p> - <div class="input-group mb-3"> - <div class="input-group-prepend"><span class="input-group-text" id="basic-addon3">Agent 1</span></div> - <input type="text" class="form-control" id="agent1" aria-describedby="basic-addon3"> - </div> - <div class="input-group mb-3"> - <div class="input-group-prepend"><span class="input-group-text" id="basic-addon3">Agent 2</span></div> - <input type="text" class="form-control" id="agent2" aria-describedby="basic-addon3"> - </div> - <p>Fill in the address where an agent can be reached using WebSockets (e.g. ws://127.0.0.1:8089). - If a field is empty a human player is assumed. - </p> - <button type="button" class="btn btn-secondary" id="restart-btn">Restart game</button> - </div> - </div> - </div> - </div> - <script src="https://d3js.org/d3.v4.min.js"></script> - <script src="dotsandboxes.js"></script> -</body> -</html> - diff --git a/python/dotsandboxes/static/dotsandboxes.js b/python/dotsandboxes/static/dotsandboxes.js deleted file mode 100644 index 11e9447..0000000 --- a/python/dotsandboxes/static/dotsandboxes.js +++ /dev/null @@ -1,454 +0,0 @@ -/** - * dotsandboxes.js - * - * Template for the Machine Learning Project course at KU Leuven (2017-2018) - * of Hendrik Blockeel and Wannes Meert. - * - * Copyright (c) 2018 KU Leuven. All rights reserved. - **/ - -function generateGuid() { - var result, i, j; - result = ''; - for(j=0; j<32; j++) { - if( j == 8 || j == 12|| j == 16|| j == 20) - result = result + '-'; - i = Math.floor(Math.random()*16).toString(16).toUpperCase(); - result = result + i; - } - return result; -} - -// GAME LOGIC - -var cur_game = generateGuid(); -var cur_player = 1; -var cur_ended = false; -var points = [0, 0, 0]; -var timelimit = 0.5; -var nb_cols = 6; -var nb_rows = 6; -var data = new Array(0); - -function restart_game() { - //console.log("Restarting game"); - cur_game = generateGuid(); - nb_cols = parseInt(document.getElementById('nb-cols').value); - if (nb_cols == "" || isNaN(nb_cols)) { - nb_cols = 6; - } - nb_rows = parseInt(document.getElementById('nb-rows').value); - if (nb_rows == "" || isNaN(nb_rows)) { - nb_rows = 6; - } - cur_ended = false; - console.log("Starting game", cur_game); - points = [0, 0, 0]; - cur_player = 1; - var old_length = 0; - for (var ri=0; ri<nb_rows + 1; ri++) { - if (ri >= data.length) { - data.push(new Array(0)); - } - var row = data[ri]; - for (var ci=0; ci<nb_cols + 1; ci++) { - if (ci >= row.length) { - row.push({l:0, t:0, p:0, r:0, c:0}); - } - var l = 0; - var t = 0; - var p = 0; - if (ri == nb_rows) { - l = undefined; - p = undefined; - } - if (ci == nb_cols) { - t = undefined; - p = undefined - } - var cell = row[ci]; - cell.l = l; - cell.t = t; - cell.p = p; - cell.r = ri; - cell.c = ci; - } - old_length = row.length; - for (var ci=nb_cols + 1; ci<old_length; ci++) { - row.pop(); - } - } - old_length = data.length; - for (var ri=nb_rows + 1; ri<old_length; ri++) { - data.pop(); - } -} - -function user_click(cell, o) { - if (cur_ended) { - //console.log('Game ended, ignoring click'); - return; - } - console.log('User click', cell, o); - var won_cell = false; - var c = cell.c; - var r = cell.r; - var msg = { - type: "action", - game: cur_game, - player: cur_player, - nextplayer: cur_player, - score: [points[1], points[2]], - location: [r, c], - orientation: o - }; - if (o == "h") { - if (cell.t != 0) { - return; - } - cell.t = cur_player; - // Above - if (r > 0) { - if (data[r - 1][c].l != 0 - && data[r - 1][c + 1].l != 0 - && data[r - 1][c].t != 0 - && data[r][c].t != 0) { - won_cell = true; - points[cur_player] += 1; - data[r - 1][c].p = cur_player; - } - } - // Below - if (r < nb_rows) { - if (data[r][c].l != 0 - && data[r][c + 1].l != 0 - && data[r][c].t != 0 - && data[r + 1][c].t != 0) { - won_cell = true; - points[cur_player] += 1; - data[r][c].p = cur_player; - } - } - } - - if (o == "v") { - if (cell.l != 0) { - return; - } - cell.l = cur_player; - // Left - if (c > 0) { - if (data[r][c - 1].l != 0 - && data[r][c].l != 0 - && data[r][c - 1].t != 0 - && data[r + 1][c - 1].t != 0) { - won_cell = true; - points[cur_player] += 1; - data[r][c - 1].p = cur_player; - } - } - // Right - if (c < nb_cols) { - if (data[r][c].l != 0 - && data[r][c + 1].l != 0 - && data[r][c].t != 0 - && data[r + 1][c].t != 0) { - won_cell = true; - points[cur_player] += 1; - data[r][c].p = cur_player; - } - } - } - - msg["score"] = [points[1], points[2]]; - - if (!won_cell) { - cur_player = 3 - cur_player; - msg.nextplayer = cur_player; - } - update_board(); - if (points[1] + points[2] == nb_cols * nb_rows) { - // Game over - var winner = 1 - if (points[2] == points[1]) { - winner = 0; - } - if (points[2] > points[1]) { - winner = 2; - } - cur_ended = true; - msg.type = "end"; - msg.nextplayer = 0; - msg.winner = winner; - } - send_to_agents(msg); -} - -var field_margin = 10; -var cell_width = 40; -var cell_margin = 4; -var player_height = 40; -var width = 400; -var height = 600; -var line_width = 5; - -var player_color = [ - "#E6E6E6", - "#FC6666", - "#0F80FF" -]; - -var svg = d3.select("#playing-area").append("svg") - .attr("width", width) - .attr("height", height) - .append("g") - .attr("transform", "translate("+field_margin+","+field_margin+")"); - -var player = svg.append("g") - .attr("class", "player") - .attr("transform", "translate(0,10)"); - -var field = svg.append("g") - .attr("class", "field") - .attr("transform", "translate(0,"+player_height+")"); - - -function update_board() { - // PLAYERS - enter & update - var player_text = player.selectAll("text") - .data([cur_player, cur_player]); - - player_text = player_text.enter().append("text") - .attr("x", function(c, i) { return i * 100;}) - .merge(player_text) - .text(function(c, i) {return "Player " + (i + 1) + ": "+points[i + 1];}) - .attr("fill", function(c, i) { - if (c == i + 1) { - return player_color[c]; - } else { - return player_color[0]; - } - }); - - // ROWS - enter & update - var rows = field.selectAll(".row") - .data(data) - .attr("fill", function() {return null;}); - - rows.exit().remove(); - - rows = rows.enter().append("g") - .attr("class", "row") - .attr("transform", function(row, i) {return "translate(0," + cell_width * i + ")";}) - .merge(rows); - - // COLS - enter & update - var cols = rows.selectAll(".col") - .data(function(col) {return col;}); - - cols.exit().remove(); - - var cols_enter = cols.enter().append("g") - .attr("class", "col") - .attr("transform", function(col, ri) {return "translate("+cell_width * ri+",0)";}); - - // CELL - enter - cols_enter.append("rect") - .attr("class", "cell") - .attr("rx", cell_margin) - .attr("ry", cell_margin) - .attr("opacity", 0.25) - .attr("x", cell_margin) - .attr("y", cell_margin) - .attr("width", cell_width - 2*cell_margin) - .attr("height", cell_width - 2*cell_margin); - - // HLINE - enter - cols_enter.append("line") - .attr("class", "hline") - .attr("x1", function(cell, ci) {return cell_margin;}) - .attr("x2", function(cell, ci) {return cell_width - cell_margin;}) - .attr("y1", 0) - .attr("y2", 0) - .attr("stroke-linecap", "round") - .attr("stroke", function(cell) {return player_color[cell.t];}); - - cols_enter.append("path") - .attr("d", "M"+cell_margin+",0"+ - "L"+(cell_width/2)+",-"+(cell_width/3)+ - "L"+(cell_width-cell_margin)+",0"+ - "L"+(cell_width/2)+","+(cell_width/3)+"Z") - .attr("stroke", "black") - .attr("stroke-width", 2) - .attr("opacity", "0") - .on("click", function(cell) { - if (agents[cur_player].active == true) { - console.log("Ignoring click, automated agent") - } else { - user_click(cell, "h"); - } - }); - - // VLINE - enter - cols_enter.append("line") - .attr("class", "vline") - .attr("y1", function(cell, ci) {return cell_margin;}) - .attr("y2", function(cell, ci) {return cell_width - cell_margin;}) - .attr("x1", 0) - .attr("x2", 0) - .attr("stroke-linecap", "round") - .attr("stroke", function(cell) {return player_color[cell.l];}); - - cols_enter.append("path") - .attr("d", "M0,"+cell_margin+ - "L-"+(cell_width/3)+","+(cell_width/2)+ - "L0,"+(cell_width-cell_margin)+ - "L"+(cell_width/3)+","+(cell_width/2)+"Z") - .attr("stroke", "black") - .attr("stroke-width", 2) - .attr("opacity", "0") - .on("click", function(cell) { - if (agents[cur_player].active == true) { - console.log("Ignoring click, automated agent"); - } else { - user_click(cell, "v"); - } - }); - - cols = cols_enter - .merge(cols); - - // HLINE - update - cols.selectAll(".hline") - .attr("stroke-width", function(cell) { - if (typeof(cell.t) == "undefined") { - return 0; - } - return line_width; - }) - .attr("stroke", function(cell) {return player_color[cell.t];}); - - // VLINE - update - cols.selectAll(".vline") - .attr("stroke-width", function(cell, ci) { - if (typeof(cell.l) == "undefined") { - return 0; - } - return line_width; - }) - .attr("stroke", function(cell) {return player_color[cell.l];}); - - // CELL - update - cols.selectAll(".cell") - .attr("fill", function(cell) { - if (cell.p == undefined) { - return "white"; - } - return player_color[cell.p]; - }); -} - - -// AGENT CONNECTIONS - -var agents = [ - {}, - {address: undefined, active: false, socket: undefined}, - {address: undefined, active: false, socket: undefined} -]; - -var msg_queue = []; - - -function start_connections() { - for (var i=1; i<3; i++) { - agents[i] = {address:undefined, active: false, socket: undefined}; - var address = document.getElementById('agent'+i).value; - if (address != "") { - //console.log("Starting websocket for agent "+i+" on address "+address); - var agent = agents[i]; - agent.address = address; - agent.socket = new WebSocket(address); - agent.socket.onopen = (function (ii, iagent) { return function(event) { - console.log("Agent "+ii+" connected") - iagent.active = true; - iagent.socket.onmessage = function(event) { - var msg = JSON.parse(event.data); - //console.log("Get msg from agent "+ii, msg); - if (msg.type == "action") { - if (cur_player == ii) { - console.log("Received action from ACTIVE player "+ii, msg); - user_click(data[msg.location[0]][msg.location[1]], msg.orientation); - } else { - console.log("Received action from NON-ACTIVE player "+ii, msg); - } - } - return false; - }; - iagent.socket.onclose = function(event) { - console.log("Closing connection to agent "+ii); - }; - iagent.socket.onerror = function(event) { - console.log("Error on connection to agent "+ii, event); - }; - msg = { - "type": "start", - "player": ii, - "timelimit": timelimit, - "game": cur_game, - "grid": [nb_rows, nb_cols] - }; - iagent.socket.send(JSON.stringify(msg)); - };}(i, agent)); - } - } -} - - -function send_to_agents(msg) { - msg_queue.push(JSON.stringify(msg)); - try_sending_to_agents(); -} - - -function try_sending_to_agents() { - var all_connected = true; - for (var i=1; i<3; i++) { - if (agents[i].address !== undefined && agents[i].active == false) { - all_connected = false; - break; - } - } - if (!all_connected) { - // Wait until all are connected - setTimeout(try_sending_to_agents, 100); - } else { - if (msg_queue.length == 0 ) { - return; - } - var msg = msg_queue.shift(); - console.log("Send msg to agents", msg); - for (var i=1; i<3; i++) { - if (agents[i].active == true) { - agents[i].socket.send(msg); - } - } - } -} - - -// STARTUP - -function restart() { - restart_game(); - update_board(); - start_connections(); -} - -var restartbtn = document.getElementById("restart-btn"); -restartbtn.onclick = function() { - console.log("Restart game"); - restart(); -}; - -restart(); |