Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demo UI #7

Merged
merged 3 commits into from
Feb 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 155 additions & 13 deletions backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,67 @@
import numpy as np
import mnk, mcts_demo

print (np.__version__)
# Libraries for SocketIO (Live Connection with Server)
from flask import render_template, session
from flask_socketio import SocketIO


app = Flask(__name__)
CORS(app)
socketio = SocketIO(app)
#CORS(app)

# ======================================================== #
# Auxiliary Functions #
# ======================================================== #

def unpack(dimensions):
"""Given a string of the form "nxm", returns a tuple (n, m).

Args:
dimensions (str): A string of the form "nxm".

Returns:
typle: A tuple (n, m).
"""
size = str(dimensions)
return tuple(map(int, size.split('x')))

def toClientMatrix(board):
"""Given a board object, return its matrix representation
to be interpreted by the client. (1 counter-clockwise rotation)
of the raw board matrix. (1 = 'X', -1 = 'O', 0 = ' ')

Args:
board (mnk.Board): A board object.

Returns:
np.ndarray: Matrix representation of the board.
"""
return np.rot90(board.board).tolist()

@app.route('/', methods=['GET'])

# ======================================================== #
# Main Routing #
# ======================================================== #

@app.route('/')
def index():
return send_from_directory('../frontend/', 'index.html')
return send_from_directory('../frontend/', 'index.html')

@app.route('/<webpage>')
def serve(webpage):
return send_from_directory('../frontend/', webpage)

# Converts size "nxm" to a tuple
def unpack(dimensions):
size = str(dimensions)
return tuple(map(int, size.split('x')))

# ======================================================== #
# Get Boards (APIs) #
# ======================================================== #

@app.route('/board/random/<dimensions>/')
def random_nxm(dimensions):
"""Return an random matrix with 1s, 0s, and -1s of the
given dimensions. <dimensions> = string of the form "nxm".
"""
size = unpack(dimensions)

# Return (n,m) matrix with random numbers [-1, 0, 1]
Expand All @@ -36,16 +77,117 @@ def random_nxm(dimensions):
# Return the matrix as a JSON object, with name "board"
return jsonify(board=matrix)


@app.route('/board/empty/<dimensions>/')
def empty_nxm(dimensions):
"""Return an empty matrix of the given dimensions.
<dimensions> = string of the form "nxm".
"""
size = unpack(dimensions)
return jsonify(board=np.zeros(size).tolist())

@app.route('/play', methods=['POST'])
def play():
currentBoard = request.json
return ''

# ======================================================== #
# User Events (Web Sockets) #
# ======================================================== #

@socketio.on("connection")
def new_connection(json):
print("New Connection")
# route = json['route']
# print(route)

# Start a new game (7x7x3 Default)
session["board"] = mnk.Board(7, 3, 3)

#socketio.emit("board_update", session["board"].board.tolist())

print(session["board"].board)

@socketio.on("new_game")
def new_game(json):
print("New Game")

print(json['k'])
# { m: m, n:n, k: k }
m = int(json['m'])
n = int(json['n'])
k = int(json['k'])

# Start a new game
session["board"] = mnk.Board(m, n, k)
print(session["board"].board)
socketio.emit("board_update", session["board"].board.tolist())


@socketio.on("user_move")
def user_move(json):
board = session["board"]
iterations = 1000 # Hard Coded for now
# (TODO: Change iterations to user input. Send as JSON from client)

# Get the user's move. Json = {i : row, j : column}
i = json["i"]
j = json["j"]

print("User Move:", i, j)
print("Board Max:", board.m, board.n)
# ======================================================== #
# Note: the moves are somewhat messed up, because the board is
# represented as a matrix differently in the server and
# client. To fix it we do the following:

# 1. Invert i <-> j (Could have done this above, but it's a bit more clear this way)
# i, j = j + 1, board.m - i

# 2 Invert the board vertically (i.e. invert j)
#j = board.m - j - 1
#
# End of Note :)
# ======================================================== #
print("User Move (Change):", i, j)
print("Board Max:", board.m, board.n)
# Make the move on the board
board.move(i, j) # <----
print(board)
print(board.board)
print(toClientMatrix(board))

# Check user's move for win
if board.who_won() != 2:
# User won
socketio.emit("win", 1)
return

# Get move from MCTS
print('AI is thinking')
root = mcts_demo.Node()
if root.isLeaf:
root.expand(board.legal_moves())

root = mcts_demo.AI(board, root, iterations)
board.move(*root.last_move)

# Check AI's move for win
if board.who_won() != 2:
socketio.emit("win", 0)

socketio.emit("board_update", board.board.tolist())

# Send the board to the client
# message = {
# "board": toClientMatrix(board),
# "who_won": board.who_won()
# }




# ======================================================== #
# Start Server #
# ======================================================== #
if __name__ == '__main__':
app.run(debug=True)
# app.run(debug=True)
socketio.run(app)


2 changes: 2 additions & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ Flask-Cors==3.0.10
Flask==2.1.1
gunicorn==20.1.0
numpy==1.22.2
simple-websocket===0.5.2
Flask-SocketIO===5.1.1
78 changes: 44 additions & 34 deletions frontend/display-board.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,66 @@
env = 'http://127.0.0.1:5000'
var n = 7
var m = 7
env = "https://team-game-bot.herokuapp.com/";
var m = 7;
var n = 3;
var k = 3;

main();

async function main() {
board = await getEmptyBoard(env, n, m)
displayBoard(board)
board = await getEmptyBoard(env, m, n);
displayBoard(board);

// Just testing this
await postBoard(board)
// Just testing this
await postBoard(board);
}

async function postBoard(board) {
await fetch(`${env}/play`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(board)
})
await fetch(`${env}/play`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(board),
});
}

async function getRandomBoard(env, n, m) {
return (await fetch(`${env}/board/random/${n}x${m}/`).then(response => { return response.json() })).board
async function getRandomBoard(env, m, n) {
return (
await fetch(`${env}/board/random/${m}x${n}/`).then((response) => {
return response.json();
})
).board;
}

async function getEmptyBoard(env, n, m) {
return (await fetch(`${env}/board/empty/${n}x${m}/`).then(response => { return response.json() })).board
async function getEmptyBoard(env, m, n) {
return (
await fetch(`${env}/board/empty/${m}x${n}/`).then((response) => {
return response.json();
})
).board;
}

function displayBoard(board) {
var m = board.length;
var n = board[0].length;

var n = board.length
var m = board[0].length
let cellSize = 100 - Math.min((Math.max(m, n) - 3) * 8, 50);
document.documentElement.style.setProperty("--cell-size", `${cellSize}px`);

let cellSize = 100 - Math.min(((Math.max(n, m) - 3) * 8), 50)
document.documentElement.style.setProperty("--cell-size", `${cellSize}px`)
boardHtml = document.getElementById("board");

boardHtml = document.getElementById('board')
boardHtml.style.setProperty("grid-template-columns", `repeat(${n}, 1fr)`);

boardHtml.style.setProperty("grid-template-columns", `repeat(${m}, 1fr)`)
boardHtml.style.setProperty("height", `${m} * var(--cell-size) + ${m - 1} * var(--gap-size)`);
boardHtml.style.setProperty("width", `${n} * var(--cell-size) + ${n - 1} * var(--gap-size)`);

boardHtml.style.setProperty("width", `${m} * var(--cell-size) + ${m-1} * var(--gap-size)`)
boardHtml.style.setProperty("height", `${n} * var(--cell-size) + ${n-1} * var(--gap-size)`)
let cellType = [" O", "", " X"];
let display = "";

let cellType = [" O", "", " X"]
let display = ""
for (var i = 0; i < m; i++)
for (var j = 0; j < n; j++)
display += `<div id="${i}, ${j}" class="cell${
cellType[board[i][j] + 1]
}" data-cell></div>`;

for (var i = 0; i < n; i++)
for (var j = 0; j < m; j++)
display += `<div class="cell${cellType[board[i][j] + 1]}" data-cell></div>`

boardHtml.innerHTML = display
}
boardHtml.innerHTML = display;
}
75 changes: 59 additions & 16 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Add Bootstrap -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="style.css" />
<script
src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"
integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA=="
crossorigin="anonymous"
></script>
<script type="text/javascript" charset="utf-8">
var socket = io();
socket.on("connect", function () {
socket.emit("connection", { route: location });
});
</script>
<script type="text/javascript" src="display-board.js" defer></script>
<script type="text/javascript" src="script.js" defer></script>
<title>Tic-Tac-Toe</title>
</head>

<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' type='text/css' href='style.css'>
<script type='text/javascript' src='display-board.js' defer></script>
<script type='text/javascript' src='script.js' defer></script>
<title>Tic-Tac-Toe</title>
</head>
<body>
<!-- Add padding to new game with bootstrap -->
<div class="New Game" style="padding: 20px;">

<h2><div class="Size"><img src="https://svgur.com/i/gXX.svg" class="ucfai" alt="Sgv Image">: MNK Game</h2></div>
<!-- User input for M x N x K Game -->
<div class="input-mnk">
<div class="Size" style="display: flex; flex-direction: row; align-items: center;">
<div class="input-group" style="width: 5.5em; margin-right: 1.2em;">
<div class="input-group-prepend">
<span class="input-group-text font-weight-bold" id="basic-addon1">M</span>
<input type="number" class="rounded-right" id="m" placeholder="m" min="1" value="7" style="width: 3em; margin-right: 0.5em;" />
</div>
</div>
<div class="input-group" style="width: 5.5em; margin-right: 1.2em;">
<div class="input-group-prepend">
<span class="input-group-text font-weight-bold" id="basic-addon1">N</span>
<input type="number" class="rounded-right" id="n" placeholder="n" min="1" value="3" style="width: 3em; margin-right: 0.5em;" />
</div>
</div>
<div class="input-group" style="width: 5.5em; margin-right: 1.2em;">
<div class="input-group-prepend">
<span class="input-group-text font-weight-bold" id="basic-addon1">K</span>
<input type="number" class="rounded-right" id="k" placeholder="k" min="1" value="3" style="width: 3em; margin-right: 0.5em;" />
</div>
</div>

<body>
TODO: Win detection
<div class='board X' id='board'></div>
</div>
<button id="start" class="btn btn-info mt-3">Start</button>
</div>
</div>
<!-- <button class="newGameButton">New Game</button> -->
</div>

<div class='winningMessage'>
<div class='winningMessageText'></div>
<button class='newGameButton'>New Game</button>
</div>
</body>
<div class="board X" id="board"></div>

<div class="winningMessage">
<div class="winningMessageText"></div>
<button class="newGameButton">New Game</button>
</div>
</body>
</html>
Loading