Skip to content

Commit

Permalink
Merge pull request #7 from ucfai/demo-ui
Browse files Browse the repository at this point in the history
Demo UI
  • Loading branch information
PedroContipelli authored Feb 8, 2023
2 parents dccb999 + 4a70d2a commit 2c9f619
Show file tree
Hide file tree
Showing 6 changed files with 476 additions and 189 deletions.
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

0 comments on commit 2c9f619

Please sign in to comment.