diff --git a/.gitignore b/.gitignore index d08e50a..fce920d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ ### Nim ### nimcache/ public/mttt.js - +.vscode/ # End of https://www.gitignore.io/api/nim diff --git a/mttt.nim b/mttt.nim index f04be80..3ecd828 100644 --- a/mttt.nim +++ b/mttt.nim @@ -1,15 +1,29 @@ -import dom, jsconsole, sugar, times, strformat +import dom, sugar, times, strformat import src/game const - canvasId = "game_canvas" + debug = false ## Debug flag. Set to true to show additional debug info + canvasId = "game_canvas" ## ID of the game canvas element var frameCount = 0 var mtttGame: Game var startTime, now, then: Time var elapsed, fpsInterval: Duration +proc showDebugInfo(): void = + ## Update the debug info bar at the top of the window + var sinceStart = now - startTime; + frameCount.inc + var currentFps = (1000 / (sinceStart.inMilliseconds.int / frameCount) * 100) / 100; + var e: Element + e = dom.document.getElementById("fps") + e.innerHTML = fmt"Current FPS: {currentFps:9.2f}" + e = dom.document.getElementById("fps-interval") + e.innerHTML = fmt"FPS Interval: {fpsInterval}" + e = dom.document.getElementById("then") + e.innerHTML = fmt"Seconds since start: {sinceStart.inSeconds}" + proc animate(): void = # request another frame discard window.requestAnimationFrame((time: float) => animate()) @@ -22,28 +36,23 @@ proc animate(): void = # Get ready for next frame by setting then=now, but also adjust for your # specified fpsInterval not being a multiple of RAF's interval (16.7ms) then = now - fpsInterval - var sinceStart = now - startTime; - frameCount.inc - var currentFps = (1000 / (sinceStart.inMilliseconds.int / frameCount) * 100) / 100; - var e: Element - e = dom.document.getElementById("fps") - e.innerHTML = fmt"Current FPS: {currentFps:9.2f}" - e = dom.document.getElementById("fps-interval") - e.innerHTML = fmt"FPS Interval: {fpsInterval}" - e = dom.document.getElementById("then") - e.innerHTML = fmt"Seconds since start: {sinceStart.inSeconds}" + if (debug): + showDebugInfo() mtttGame.nextFrame(elapsed) proc startAnimating(fps: int): void = + # Make sure we run a 60 FPS fpsInterval = initDuration(milliseconds = (1000 / fps).toInt) then = getTime() startTime = then animate() proc onLoad(event: Event) {.exportc.} = - mtttGame = newGame(canvasId, window.innerWidth, window.innerHeight) + if(not debug): + dom.document.getElementById("debug").style.display = "none"; + mtttGame = newGame(canvasId, debug) startAnimating(60) window.onload = onLoad \ No newline at end of file diff --git a/src/game.nim b/src/game.nim index e033388..4870971 100644 --- a/src/game.nim +++ b/src/game.nim @@ -1,4 +1,4 @@ -import dom, math, strformat, times +import dom, strformat, times, jsconsole import gamelight/[graphics, vec] @@ -19,43 +19,47 @@ type MainMenu, Game const - gameBgColor = "#e6e6e6" + gameBgColor = "#eaeaea" + metaBoardColor = "#484d4d" + miniBoardColor = "#6a6c6c" font = "Helvetica, monospace" padding = 10 # Padding around the game area in pixels var + debugMode = true renderWidth, renderHeight: int ## Canvas render area in pixels - -proc toTimeString(milliseconds: float): string = - let seconds = ((milliseconds / 1000) mod 60).toInt() - let minutes = ((milliseconds / (1000 * 60)) mod 60).toInt() - let hours = ((milliseconds / (1000 * 60 * 60)) mod 24).toInt() - result = fmt"{hours}:{minutes}:{seconds}" + # TODO this should be dynamic + sidebarWidth = 150 ## Size of the left sidebar proc switchScene(game: Game, scene: Scene) = case scene: of Scene.MainMenu: discard of Scene.Game: - var elements: seq[Element] = @[] - let timeTextPos = (padding.toFloat, padding.toFloat).toPoint - elements.add game.renderer.createTextElement("Time", timeTextPos, "#000000", 26, font) + discard game.renderer.createTextElement("Game Time", timeTextPos, "#000000", 26, font) let timePos = (padding.toFloat, padding.toFloat + 35.0).toPoint - game.timeElement = game.renderer.createTextElement("0", timePos, "#000000", 14, font) + game.timeElement = game.renderer.createTextElement("0", timePos, "#000000", 20, font) - -proc newGame*(canvasId: string, width, height: int): Game = +proc newGame*(canvasId: string, debug: bool = false): Game = + debugMode = debug var player1 = Player(name: "Player 1") player2 = Player(name: "Player 2") - renderWidth = width - renderHeight = height + # Update game area size + renderHeight = window.innerHeight + renderWidth = window.innerWidth + + console.debug(fmt"Game area: {renderWidth}x{renderHeight}") + + if debugMode: + renderHeight = renderHeight - 19 ## The size of the debug bar + console.debug(fmt"Game area after debug check: {renderWidth}x{renderHeight}") result = Game( - renderer: newRenderer2D(canvasId, width, height), + renderer: newRenderer2D(canvasId, renderWidth, renderHeight), scene: Scene.Game, state: newGameState(player1, player2), paused: false, @@ -64,7 +68,7 @@ proc newGame*(canvasId: string, width, height: int): Game = switchScene(result, Scene.Game) proc update(game: Game, time: Duration) = - # Update the game logic + ## Update the game logic # Return early if paused. if game.paused or game.scene != Scene.Game: return @@ -73,14 +77,69 @@ proc update(game: Game, time: Duration) = proc drawMainMenu(game: Game) = discard +proc drawPos(renderer: Renderer2D, x, y: float): void = + renderer.fillText(fmt"(x:{x.toInt}, y:{y.toInt})", (x, y).toPoint) + +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 + + for i in 1 .. 2: + let offX = ((size / 3) * i.toFloat) + zero.x.toFloat + let offY = ((size / 3) * i.toFloat) + zero.y.toFloat + + game.renderer.beginPath() + + # horizontal line + game.renderer.moveTo((zero.x+pad).toFloat, offY) + if (debugMode): game.renderer.drawPos(zero.x.toFloat, offY) + game.renderer.lineTo(zero.x + size - pad, offY) + if (debugMode): game.renderer.drawPos((zero.x + size).toFloat, offY) + # vertical line + game.renderer.moveTo(offX, (zero.y+pad).toFloat) + if (debugMode): game.renderer.drawPos(offX, zero.y.toFloat) + game.renderer.lineTo(offX, zero.y + size - pad) + if (debugMode): game.renderer.drawPos(offX, (zero.y + size).toFloat) + + game.renderer.closePath() + game.renderer.strokePath(color, 3) + proc drawGame(game: Game) = - # Draw changing UI Elements + ## 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 = renderHeight - 2 * padding + gAreaW = renderWidth - sidebarWidth - 3 * padding + gSize = min(gAreaH, gAreaW) + bSize = (gSize / 3).toInt + + # Draw a box araound the game board + game.renderer.strokeRect(gAreaX, gAreaY, gSize, gSize, "#0000004d") + + # Draw the meta board + game.drawBoard((gAreaX, gAreaY).toPoint, gSize, 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) + proc draw(game: Game) = - # Draw the current screen on the canvas + ## Draw the current screen on the canvas + # Fill background color. game.renderer.fillRect(0.0, 0.0, renderWidth, renderHeight, gameBgColor) + case game.scene of Scene.MainMenu: drawMainMenu(game) @@ -88,6 +147,7 @@ proc draw(game: Game) = drawGame(game) proc nextFrame*(game: Game, frameTime: Duration) = - # Determine id an update is necessary + ## Prepare the next frame that should be displayed + # Determine if an update is necessary game.update(frameTime) game.draw()