diff --git a/public/img/free.svg b/public/img/free.svg new file mode 100644 index 0000000..f2873d3 --- /dev/null +++ b/public/img/free.svg @@ -0,0 +1,137 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/o.svg b/public/img/o.svg new file mode 100644 index 0000000..51f363e --- /dev/null +++ b/public/img/o.svg @@ -0,0 +1,61 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/public/img/thinking.svg b/public/img/thinking.svg new file mode 100644 index 0000000..c991d35 --- /dev/null +++ b/public/img/thinking.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/x.svg b/public/img/x.svg new file mode 100644 index 0000000..317bab9 --- /dev/null +++ b/public/img/x.svg @@ -0,0 +1,61 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/public/index.html b/public/index.html index de8ee73..bc1bee6 100644 --- a/public/index.html +++ b/public/index.html @@ -18,7 +18,7 @@ -
+
diff --git a/public/mttt.css b/public/mttt.css index dc30815..a620565 100644 --- a/public/mttt.css +++ b/public/mttt.css @@ -1,3 +1,7 @@ -.mttt-cell:hover{ - background: lightblue; +.debug-bar{ + position: relative; + background: #fff; + display: flex; + justify-content: space-around; + z-index: 100; } \ No newline at end of file diff --git a/src/game.nim b/src/game.nim index ab1cfc9..fd47bf1 100644 --- a/src/game.nim +++ b/src/game.nim @@ -1,4 +1,4 @@ -import dom, strformat, times, jsconsole +import dom, strformat, times, jsconsole, options, pure.strutils import gamelight/[graphics, vec] import libmttt @@ -17,6 +17,12 @@ type Scene {.pure.} = enum MainMenu, Game + BoardSize = object ## Infos on size/position of a game board + x: int ## X position of the top left corner + y: int ## Y position of the top left corner + size: int ## Pixel size of the game board. (The board is assumed to be quadratic) + cellSize: int ## Pixel size of each cell in the board + const gameBgColor = "#eaeaea" metaBoardColor = "#484d4d" @@ -29,45 +35,58 @@ var # TODO this should be dynamic sidebarWidth = 150 ## Size of the left sidebar -proc cellClick(event: Event): void = - ## TODO Process cell clicks from here - let - cX = event.target.getAttribute("x") - cY = event.target.getAttribute("y") - mX = event.target.getAttribute("metaX") - mY = event.target.getAttribute("metaY") - console.debug(fmt"Click: cell ({cX}, {cY}) in mini board ({mX}, {mY})") +proc getGameArea(game: Game): BoardSize = + ## Get size and position of the game board on the canvas + let height = game.renderer.getHeight - 2 * padding + let width = game.renderer.getWidth - sidebarWidth - 3 * padding + result.x = 2 * padding + sidebarWidth + result.y = padding + result.size = min(height, width) + result.cellSize = (result.size / 3).toInt -proc createMiniCells(renderer: Renderer2D, board: Coordinate, start: Point, size: float): seq[Element] = +proc getCellInfo(e: Node): tuple[cell: Coordinate, board: Coordinate] = + ## Parse the game imformation from the HTML attributes of an element + var val: string + val = $e.getAttribute("x") + result.cell.x = val.parseInt + val = $e.getAttribute("y") + result.cell.y = val.parseInt + val = $e.getAttribute("metaX") + result.board.x = val.parseInt + val = $e.getAttribute("metaY") + result.board.y = val.parseInt + +proc cellClick(game: Game, event: Event): void = + ## TODO Process cell clicks from here + let (cell, board) = event.target.getCellInfo() + console.debug(fmt"Click: cell ({cell.x}, {cell.y}) in mini board ({board.x}, {board.y})") + if game.state.currentBoard.isNone: + discard game.state.makeMove(cell,board) + elif board == game.state.currentBoard.get: + discard game.state.makeMove(cell) + +proc createMiniCells(game: Game, board: Coordinate, start: Point, size: float): seq[Element] = ## Create the interactive cells in a miniboard for x in 0 .. 2: for y in 0 .. 2: - let pos = (start.x + x * size, start.y + y * size).toPoint - let cell: Element = renderer.createDivElement(pos, size, size) + let pos = ((start.x + x * size).toInt, (start.y + y * size).toInt).toPoint + let cell: Element = game.renderer.createDivElement(pos, size, size) cell.setAttribute("info", fmt"({x}, {y}) at {start}") cell.setAttribute("X", $x) cell.setAttribute("Y", $y) cell.setAttribute("metaX", $board.x) cell.setAttribute("metaY", $board.y) - cell.onclick = cellClick cell.classList.add("mttt-cell") - result.add cell + cell.addEventListener("click", proc(ev: Event) = cellClick(game, ev)) + result.add cell -proc createCellElements(renderer: Renderer2D): seq[Element] = +proc createCellElements(game: Game): seq[Element] = ## Create the cell elements for the whole game board - # TODO: put those somewhere shared - let - gAreaX = 2 * padding + sidebarWidth - gAreaY = padding - gAreaH = renderer.getHeight - 2 * padding - gAreaW = renderer.getWidth - sidebarWidth - 3 * padding - gSize = min(gAreaH, gAreaW) - bSize = (gSize / 3) - + let area = game.getGameArea() for x in 0 .. 2: for y in 0 .. 2: - let pos = (gAreaX + x * bSize, gAreaY + y * bSize) - result.add renderer.createMiniCells((x, y), pos, bSize / 3) + let pos = (area.x + x * area.cellSize, area.y + y * area.cellSize) + result.add game.createMiniCells((x, y), pos, area.cellSize / 3) proc switchScene(game: Game, scene: Scene) = case scene: @@ -80,8 +99,7 @@ proc switchScene(game: Game, scene: Scene) = let timePos = (padding.toFloat, padding.toFloat + 35.0).toPoint game.timeElement = game.renderer.createTextElement("0", timePos, "#000000", 20, font) - # TODO Save these somewhere??? - let elements = game.renderer.createCellElements() + discard game.createCellElements() proc newGame*(canvasId: string, debug: bool = false): Game = debugMode = debug @@ -115,14 +133,8 @@ proc drawPos(renderer: Renderer2D, x, y: float): void = proc drawPos(renderer: Renderer2D, point: Point): void = renderer.drawPos(point.x.toFloat, point.y.toFloat) -proc drawBoard(game: Game, zero: Point, size: int, color = "#000000", pad = 10): void = - ## Draw a Tic Tac Toe board with the given sizes - if (debugMode): - game.renderer.drawPos(zero) # Top left corner - game.renderer.drawPos((zero.x + size, zero.y).toPoint) # Top right corner - game.renderer.drawPos((zero.x, zero.y + size).toPoint) # Bottom left corner - game.renderer.drawPos((zero.x + size, zero.y + size).toPoint) # Bottom right corner - +proc drawBoardLines(game: Game, zero: Point, size: int, color = "#000000", pad = 10): void = + ## Draws the lines for a tic tac toe board on the canvas for i in 1 .. 2: let offX = ((size / 3) * i.toFloat) + zero.x.toFloat let offY = ((size / 3) * i.toFloat) + zero.y.toFloat @@ -143,28 +155,60 @@ proc drawBoard(game: Game, zero: Point, size: int, color = "#000000", pad = 10): game.renderer.closePath() game.renderer.strokePath(color, 3) +proc drawMark(renderer: Renderer2D, pos: Point, size: int, mark: Mark) = + ## Draw a single mark on the canvas + case mark: + of Mark.Free: + if (debugMode): + renderer.drawImage("img/free.svg", pos, size, size, ImageAlignment.TopLeft) + of Mark.Player1: + renderer.drawImage("img/x.svg", pos, size, size, ImageAlignment.TopLeft) + of Mark.Player2: + renderer.drawImage("img/o.svg", pos, size, size, ImageAlignment.TopLeft) + else: + console.error(fmt"Invalid mark '{mark}'"); + +proc drawPlayerMarks(game: Game, zero: Point, size: int, coord: Coordinate): void = + ## Draw the player marks for all cells in a board + let cellSize = (size / 3).toInt + for x in 0 .. 2: + for y in 0 .. 2: + let pos = ((zero.x + x * cellSize), (zero.y + y * cellSize)).toPoint ## Top left point for each cell in the board + let mark = game.state.board[coord.x][coord.y][x][y] + game.renderer.drawMark(pos, cellSize, mark) + +proc drawBoard(game: Game, zero: Point, size: int, coord: Coordinate, color = "#000000", pad = 10): void = + ## Draw a Tic Tac Toe board with the given sizes, if the game is still open. + ## If not, draw a big mark for the winner or something to show it is drawn + if (debugMode): + game.renderer.drawPos(zero) # Top left corner + game.renderer.drawPos((zero.x + size, zero.y).toPoint) # Top right corner + game.renderer.drawPos((zero.x, zero.y + size).toPoint) # Bottom left corner + game.renderer.drawPos((zero.x + size, zero.y + size).toPoint) # Bottom right corner + + let status = game.state.board[coord.x][coord.y].checkBoard + if status == Mark.Free: + game.drawBoardLines(zero, size, color, pad) + game.drawPlayerMarks(zero, size, coord) + else: + game.renderer.drawMark(zero, size, status) + proc drawGame(game: Game) = ## Redraw the UI elements game.timeElement.innerHTML = fmt"{game.totalTime.inMinutes}m {game.totalTime.inSeconds mod 60}s" - let - gAreaX = 2 * padding + sidebarWidth - gAreay = padding - gAreaH = game.renderer.getHeight - 2 * padding - gAreaW = game.renderer.getWidth - sidebarWidth - 3 * padding - gSize = min(gAreaH, gAreaW) - bSize = (gSize / 3).toInt + let area = game.getGameArea() # Draw a box araound the game board - game.renderer.strokeRect(gAreaX, gAreaY, gSize, gSize, "#0000004d") + game.renderer.strokeRect(area.x, area.y, area.size, area.size, "#0000004d") # Draw the meta board - game.drawBoard((gAreaX, gAreaY).toPoint, gSize, metaBoardColor, 5) + game.drawBoardLines((area.x, area.y).toPoint, area.size, metaBoardColor, 5) # Draw the small boards for x in 0 .. 2: for y in 0 .. 2: - game.drawBoard((gAreaX + x*bSize, gAreaY + y*bSize).toPoint, bSize, miniBoardColor, 15) + game.drawBoard((area.x + x*area.cellSize, area.y + y*area.cellSize).toPoint, area.cellSize, (x, y), miniBoardColor, 15) proc draw(game: Game) = ## Draw the current screen on the canvas