commit af2f573d60f90915af4381fe847c20b28ff57021 Author: Bram Verhulst Date: Wed Jan 22 00:16:29 2025 +0100 Batman diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8fe00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +out +cmake-** +.idea +.vs +.vscode \ No newline at end of file diff --git a/CmakeLists.txt b/CmakeLists.txt new file mode 100644 index 0000000..9d97137 --- /dev/null +++ b/CmakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 3.24) +project(KevEngine) + +include(FetchContent) + +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +set(DEFAULT_BUILD_TYPE "Release") +if(EXISTS "${CMAKE_SOURCE_DIR}/.git") + set(DEFAULT_BUILD_TYPE "Debug") +endif() +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.") + set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() + + +if(WIN32) # Install dlls in the same directory as the executable on Windows + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +endif() + +# Fetch LUA +# ++++++++++ +FetchContent_Declare( + lua + URL https://github.com/marovira/lua/archive/refs/tags/5.4.4.tar.gz +) +FetchContent_MakeAvailable(lua) + +# Fetch SOL2 +# ++++++++++ +FetchContent_Declare( + sol2 + URL https://github.com/ThePhD/sol2/archive/refs/tags/v3.3.0.tar.gz +) +FetchContent_MakeAvailable(sol2) + +set(USE_NGHTTP2 OFF) + +FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git + GIT_TAG dec9422db3af470641f8b0d90e4b451c4daebf64) # Replace with your desired git commit from: https://github.com/libcpr/cpr/releases +FetchContent_MakeAvailable(cpr) + + +find_library(GDIPLUS_LIBRARY NAMES libgdiplus gdiplus) +set(GDIPLUS_LIBRARY gdiplus) + +set(LIBS XInput) + + +set(SRC_FILES + "src/AbstractGame.cpp" + "src/Game.cpp" + "src/GameDefines.h" + "src/GameEngine.cpp" + "src/GameWinMain.cpp" + "src/resource.h") + +add_executable(${PROJECT_NAME} WIN32 ${SRC_FILES}) + +target_link_libraries(${PROJECT_NAME} PRIVATE cpr::cpr sol2 lua::lua ${LIBS}) + + + +# Copy /lua folder to output directory +add_custom_target(CopyLuaScripts ALL + COMMENT "Copying Lua scripts to output directory" +) + +add_custom_command(TARGET CopyLuaScripts POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/lua + $/lua) + +add_custom_target(CopyResources ALL + COMMENT "Copying resources to output directory" +) + +add_custom_command(TARGET CopyResources POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + $/resources) + + diff --git a/lua/Flappy.lua b/lua/Flappy.lua new file mode 100644 index 0000000..e67b845 --- /dev/null +++ b/lua/Flappy.lua @@ -0,0 +1,133 @@ +-- Flappy Bird Clone using GameEngine + +local vector2 = require("vector2") +local utils = require("utils") + +-- Game Variables +local bird = { + position = vector2.new(100, 200), + width = 30, + height = 30, + velocity = 0, + jumpStrength = -8 +} + +local gravity = 0.5 +local pipes = {} +local pipeWidth = 60 +local pipeGap = 120 +local pipeSpeed = 2 +local score = 0 +local gameRunning = true +local gameStarted = false +local screenWidth = GameEngine:getWidth() +local screenHeight = GameEngine:getHeight() + +-- Function to spawn new pipes +local function spawnPipe() + --GameEngine:messageBox(tostring(screenHeight - pipeGap - 50)) + local pipeHeight = math.random(50, screenHeight - pipeGap - 50) + --local pipeHeight =50 + table.insert(pipes, { x = screenWidth, y = pipeHeight }) +end + +function setup_window() + GameEngine:setTitle("BreakOut") + GameEngine:setWidth(800) + GameEngine:setHeight(600) + GameEngine:setFrameRate(60) +end + +--- the set_keylist function +--- @return string +function set_keylist() + return "R " +end + +--- the start function +--- @return nil +function start() + screenWidth = GameEngine:getWidth() + screenHeight = GameEngine:getHeight() + + -- print(GameEngine:getRequest("https://dummyjson.com/c/3029-d29f-4014-9fb4")) +end + +-- Update game state +function update() + if not gameRunning then return end + + -- Apply gravity + bird.velocity = bird.velocity + gravity + bird.position.y = bird.position.y + bird.velocity + + -- Jumping mechanic + if GameEngine:isKeyDown(" ") then + bird.velocity = bird.jumpStrength + end + + -- Move pipes + for i = #pipes, 1, -1 do + pipes[i].x = pipes[i].x - pipeSpeed + + -- Remove off-screen pipes + if pipes[i].x + pipeWidth < 0 then + table.remove(pipes, i) + score = score + 1 + end + end + + -- Collision detection + for _, pipe in ipairs(pipes) do + if bird.position.x < pipe.x + pipeWidth and bird.position.x + bird.width > pipe.x then + if bird.position.y < pipe.y or bird.position.y + bird.height > pipe.y + pipeGap then + gameRunning = false + end + end + end + + -- Check if bird hits ground or ceiling + if bird.position.y + bird.height >= screenHeight or bird.position.y <= 0 then + gameRunning = false + end + + -- Spawn pipes periodically + if #pipes == 0 or pipes[#pipes].x < screenWidth - 200 then + spawnPipe() + end +end + +-- Draw game elements +function draw() + -- Clear screen + GameEngine:fillScreen(Color.new(135, 206, 250)) -- Sky Blue background + + -- Draw bird + GameEngine:setColor(Color.new(255, 255, 0)) -- Yellow + GameEngine:fillOval(bird.position.x, bird.position.y, bird.width, bird.height) + + -- Draw pipes + GameEngine:setColor(Color.new(0, 255, 0)) -- Green + for _, pipe in ipairs(pipes) do + GameEngine:fillRect(pipe.x, 0, pipeWidth, pipe.y) + GameEngine:fillRect(pipe.x, pipe.y + pipeGap, pipeWidth, screenHeight - pipe.y - pipeGap) + end + + -- Draw score + GameEngine:setColor(Color.new(255, 255, 255)) -- White + GameEngine:drawText(tostring(score), 10, 10) + + -- Game over message + if not gameRunning then + GameEngine:drawText("Game Over! Press R to restart", screenWidth / 2 - 80, screenHeight / 2) + end +end + +-- Restart function +function restart() + bird.position.y = 200 + bird.velocity = 0 + pipes = {} + score = 0 + gameRunning = true +end diff --git a/lua/Pong.lua b/lua/Pong.lua new file mode 100644 index 0000000..77b5b10 --- /dev/null +++ b/lua/Pong.lua @@ -0,0 +1,182 @@ +-- annotation.lua contains EmmyLua annotations for cpp_function & cpp_variable +package.path = package.path .. ";../?.lua" + +local utils = require("utils") +local vector2 = require("vector2") + +local player = { + position = vector2.new(0, 0), + height = 100, + speed = 5 +} + +local ball = { + x = 0, + y = 0, + size = 20, + xspeed = 5, + yspeed = 5, + color = Color.new(0,255,255) +} + +local AI = { + y = 200, + height = 100, + speed = 4 +} + +local player_score = 0 +local ai_score = 0 + +local start_timer = 0 +local start_timer_max = 60 +local next_round = true + +local countdown_text = 3 + + + +function update_player(player) + if GameEngine:isKeyDown("W") then + if (player.position.y >= 0) then + player.position.y = player.position.y - player.speed + end + end + + if GameEngine:isKeyDown("S") then + if player.position.y + player.height < GameEngine:getHeight() then + player.position.y = player.position.y + player.speed + end + end +end + +function update_ai() + if ball.y < AI.y then + if(AI.y > 0) then + AI.y = AI.y - AI.speed + end + end + + if ball.y > AI.y then + if AI.y + AI.height < GameEngine:getHeight() then + AI.y = AI.y + AI.speed + end + end +end + +function update_ball() + ball.x = ball.x + ball.xspeed + ball.y = ball.y + ball.yspeed + + if utils.check_collision(ball.x, ball.y, ball.size, ball.size, player.position.x, player.position.y, 10, player.height) then + ball.xspeed = -ball.xspeed * 1.1 + + end + + if utils.check_collision(ball.x, ball.y, ball.size, ball.size, GameEngine:getWidth() - 10, AI.y, 10, AI.height) then + ball.xspeed = -ball.xspeed * 1.1 + end + + + if ball.x <= 0 then + ai_score = ai_score + 1 + ball.x = math.floor(GameEngine:getWidth() / 2 - ball.size / 2) + ball.y = math.floor(GameEngine:getHeight() / 2 - ball.size / 2) + next_round = true + start_timer = 0 + countdown_text = 3 + end + + if ball.x >= GameEngine:getWidth() - ball.size then + player_score = player_score + 1 + ball.x = math.floor(GameEngine:getWidth() / 2 - ball.size / 2) + ball.y = math.floor(GameEngine:getHeight() / 2 - ball.size / 2) + next_round = true + start_timer = 0 + countdown_text = 3 + end + + if ball.y <= 0 or ball.y >= GameEngine:getHeight() - ball.size then + ball.yspeed = -ball.yspeed + end + + +end + + + +function draw_player() + GameEngine:setColor(Color.new(255,255,255)) + GameEngine:fillRect(0, math.floor(player.position.y), 10, player.height) +end + +function draw_ai() + GameEngine:setColor(Color.new(255,255,0)) + GameEngine:fillRect(GameEngine:getWidth() - 10, AI.y, 10, AI.height) +end + +function draw_ball() + GameEngine:setColor(ball.color) + GameEngine:fillOval(ball.x, ball.y, ball.size, ball.size) +end + +--- the setup function +--- @return nil +function setup_window() + GameEngine:setTitle("BreakOut") + GameEngine:setWidth(800) + GameEngine:setHeight(600) + GameEngine:setFrameRate(60) +end + +--- the set_keylist function +--- @return string +function set_keylist() + return "WASD" +end + +--- the start function +--- @return nil +function start() + ball.x = GameEngine:getWidth() / 2 - ball.size / 2 + ball.y = GameEngine:getHeight() / 2 - ball.size / 2 + +end + +--- the update function +--- @param Engine GameEngine # The GameEngine instance. +--- @return nil +function update() + if (start_timer < start_timer_max and next_round) then + start_timer = start_timer + 1 + if start_timer % 20 == 0 then + countdown_text = countdown_text - 1 + end + return + end + if(next_round) then + ball.xspeed = 5 + ball.yspeed = 5 + next_round = false + end + update_player(player) + update_ball() + update_ai() +end + + +--- the draw function +--- @return nil +function draw() + GameEngine:fillScreen(Color.new(0, 0,0)) + -- draw the score + GameEngine:setColor(Color.new(255,255,255)) + GameEngine:drawText(tostring(player_score), 10, 10) + GameEngine:drawText(tostring(ai_score), GameEngine:getWidth() - 20, 10) + if next_round then + GameEngine:drawText(tostring(countdown_text), GameEngine:getWidth() / 2,GameEngine:getHeight() / 2 - 30) + end + draw_player() + draw_ball() + draw_ai() +end \ No newline at end of file diff --git a/lua/annotation.lua b/lua/annotation.lua new file mode 100644 index 0000000..8a639fb --- /dev/null +++ b/lua/annotation.lua @@ -0,0 +1,236 @@ +---@meta +--- This file provides type annotations for Lua scripts interacting with C++ via SOL2. + + +--- The Color class exposed from C++ +--- @class Color +--- @field r number # The red component of the color. +--- @field g number # The green component of the color. +--- @field b number # The blue component of the color. +Color = {} + +--- constructor +--- @param r number # The red component of the color. +--- @param g number # The green component of the color. +--- @param b number # The blue component of the color. +--- @return Color +function Color.new(r, g, b) end + + +--- The Bitmap class exposed from C++ +--- @class Bitmap +--- @field filename string # The filename of the bitmap. +Bitmap = {} + +--- constructor +--- @param filename string # The filename of the bitmap. +--- @return Bitmap +function Bitmap.new(filename) end + +--- Sets the transparency color +--- @param color Color # The color to set as transparent. +--- @return nil +function Bitmap:SetTransparencyColor(color) end + +--- Sets the Opacity +--- @param opacity number # The opacity to set. +--- @return nil +function Bitmap:SetOpacity(opacity) end + +--- Gets if the bitmap exists +--- @return boolean # True if the bitmap exists, false otherwise. +function Bitmap:Exists() end + +--- Gets the width of the bitmap +--- @return number # The width of the bitmap. +function Bitmap:GetWidth() end + +--- Gets the height of the bitmap +--- @return number # The height of the bitmap. +function Bitmap:GetHeight() end + + +--- The Textbox class exposed from C++ +--- @class Textbox +--- @field text string # The text of the textbox. +Textbox = {} + +--- constructor +--- @param text string # The text of the textbox. +--- @return Textbox +function Textbox.new(text) end + +--- Set the bounds of the textbox +--- @param x number # The x coordinate of the textbox. +--- @param y number # The y coordinate of the textbox. +--- @param width number # The width of the textbox. +--- @param height number # The height of the textbox. +--- @return nil +function Textbox:SetBounds(x, y, width, height) end + +--- Set the text of the textbox +--- @param text string # The text of the textbox. +--- @return nil +function Textbox:SetText(text) end + +--- Sets the background color of the textbox +--- @param color Color # The color to set as the background. +--- @return nil +function Textbox:SetBackgroundColor(color) end + +--- Sets the foreground color of the textbox +--- @param color Color # The color to set as the foreground. +--- @return nil +function Textbox:SetForegroundColor(color) end + +--- Sets the textbox to be enabled +--- @param enabled boolean # True if the textbox is enabled, false otherwise. +--- @return nil +function Textbox:SetEnabled(enabled) end + +--- Shows the textbox +--- @return nil +function Textbox:Show() end + +--- Hides the textbox +--- @return nil +function Textbox:Hide() end + +--- Gets the text in the textbox +--- @return string # The text in the textbox. +function Textbox:GetText() end + + + +--- The Game engine class exposed from C++ +--- @class Engine +GameEngine = {} + +--- @type +GameEngine = GameEngine + + +---Sets the title of the window. +---@param title string # The new title. +---@return nil +function GameEngine:setTitle(title) end + +--- Sets the width of the window. +--- @param width number # The new width. +--- @return nil +function GameEngine:setWidth(width) end + +--- Gets the window width. +--- @return number # The width of the window. +function GameEngine:getWidth() end + +--- Gets the window height. +--- @return number # The height of the window. +function GameEngine:getHeight() end + +--- Sets the height of the window. +--- @param height number # The new height. +--- @return nil +function GameEngine:setHeight(height) end + +--- Sets the frame rate of the window. +--- @param frameRate number # The new frame rate. +--- @return nil +function GameEngine:setFrameRate(frameRate) end + +--- Sets the drawing color +--- @param color Color # The new color. +--- @return nil +function GameEngine:setColor(color) end + +--- Fills the screen +--- @param color Color # The color to fill the screen with. +--- @return nil +function GameEngine:fillScreen(color) end + +--- message box +--- @param message string # The message to display. +--- @return nil +function GameEngine:messageBox(message) end + + +--- Draw a bitmap +--- @param bitmap Bitmap # The bitmap to draw. +--- @param x number # The x coordinate of the bitmap. +--- @param y number # The y coordinate of the bitmap. +function GameEngine:drawBitmap(bitmap, x, y) end + +--- Draws a rectangle +--- @param x number # The x coordinate of the rectangle. +--- @param y number # The y coordinate of the rectangle. +--- @param width number # The width of the rectangle. +--- @param height number # The height of the rectangle. +--- @return nil +function GameEngine:drawRect(x, y, width, height) end + + +--- fills a rectangle +--- @param x number # The x coordinate of the rectangle. +--- @param y number # The y coordinate of the rectangle. +--- @param width number # The width of the rectangle. +--- @param height number # The height of the rectangle. +--- @return nil +function GameEngine:fillRect(x, y, width, height) end + +--- draws an oval +--- @param x number # The x coordinate of the oval. +--- @param y number # The y coordinate of the oval. +--- @param width number # The width of the oval. +--- @param height number # The height of the oval. +function GameEngine:drawOval(x, y, width, height) end + +--- fills an oval +--- @param x number # The x coordinate of the oval. +--- @param y number # The y coordinate of the oval. +--- @param width number # The width of the oval. +--- @param height number # The height of the oval. +function GameEngine:fillOval(x, y, width, height) end + +--- draws text +--- @param text string # The text to draw. +--- @param x number # The x coordinate of the text. +--- @param y number # The y coordinate of the text. +--- @return nil +function GameEngine:drawText(text, x, y,) end + +--- Checks if a key is pressed +--- @param key string # The key to check. +--- @return boolean # True if the key is pressed, false otherwise. +function GameEngine:isKeyDown(key) end + + +--- Gets the mouse Position X +--- @return number # The x coordinate of the mouse. +function GameEngine:getMouseX() end + + +--- Gets the mouse Position Y +--- @return number # The y coordinate of the mouse. +function GameEngine:getMouseY() end + +--- Get if the left mouse button isKeyDown +--- @return boolean # True if the left mouse button is pressed, false otherwise. +function GameEngine:isMouseLeftDown() end; + +--- Get if the right mouse button isKeyDown +--- @return boolean # True if the right mouse button is pressed, false otherwise. +function GameEngine:isMouseRightDown() end; + +--- Preform a Get Request +--- @param url string # The url to get. +--- @return string # The response from the server. +function GameEngine:getRequest(url) end + +--- Preform a Post Request +--- @param url string # The url to post. +--- @param data string # The data to post. +function GameEngine:postRequest(url, data) end + +--- Opens a dialog to enter a string +--- @return string # The string entered by the user. +function GameEngine:GetString() end \ No newline at end of file diff --git a/lua/button.lua b/lua/button.lua new file mode 100644 index 0000000..b668f56 --- /dev/null +++ b/lua/button.lua @@ -0,0 +1,68 @@ +local Button = {} +Button.__index = Button + +--- Constructor +--- @param x number +--- @param y number +--- @param width number +--- @param height number +--- @param text string +--- @param color Color +--- @param callback function | nil +--- @return Button +function Button.new(x, y, width, height, text, color, callback) + local self = setmetatable({}, Button) + self.x = x + self.y = y + self.width = width + self.height = height + self.text = text + self.color = color + self.callback = callback or function() end + self.clicked = false + return self +end + +--- Update function +--- @param engine Engine +function Button:update(engine) + local mouseX = engine:getMouseX() + local mouseY = engine:getMouseY() + local isHovered = mouseX >= self.x and mouseX <= (self.x + self.width) and + mouseY >= self.y and mouseY <= (self.y + self.height) + + if isHovered and engine:isMouseLeftDown() then + if not self.clicked then + self.callback() + self.clicked = true + end + else + self.clicked = false + end +end + +--- Draw function +--- @param engine Engine +function Button:draw(engine) + local mouseX = engine:getMouseX() + local mouseY = engine:getMouseY() + local isHovered = mouseX >= self.x and mouseX <= (self.x + self.width) and + mouseY >= self.y and mouseY <= (self.y + self.height) + + local drawColor = isHovered and Color.new(self.color.r + 50, self.color.g + 50, self.color.b + 50) or self.color + + engine:setColor(drawColor) + engine:fillRect(self.x, self.y, self.width, self.height) + + engine:setColor(Color.new(0, 0, 0)) -- Black text + + local charWidth = 8 + local textWidth = #self.text * charWidth + local textX = self.x + (self.width - textWidth) / 2 + local textY = self.y + (self.height - 8) / 2 + + engine:drawText(self.text, textX, textY) + +end + +return Button diff --git a/lua/script copy.lua b/lua/script copy.lua new file mode 100644 index 0000000..8f54be9 --- /dev/null +++ b/lua/script copy.lua @@ -0,0 +1,82 @@ +-- annotation.lua contains EmmyLua annotations for cpp_function & cpp_variable\ + +function create_button(x, y, width, height, text) + return { + x = x, + y = y, + width = width, + height = height, + text = text, + hovered = false, + was_pressed = false, + onpressed = function() end + } +end + +function draw_button(button) + if button.hovered then + Engine:setColor(Color.new(0, 255, 0)) + else + Engine:setColor(Color.new(255, 0, 0)) + end + Engine:fillRect(button.x, button.y, button.width, button.height) + Engine:setColor(Color.new(0, 0, 0)) + Engine:drawText(button.text, button.x + 10, button.y + 10) +end + +function is_mouse_in_button(button, x, y) + return x >= button.x and x <= button.x + button.width and y >= button.y and y <= button.y + button.height +end + +testButton = create_button(100, 100, 100, 50, "Test Button") + +function update_button(button) + if is_mouse_in_button(button, Engine:getMouseX(), Engine:getMouseY()) then + button.hovered = true + if Engine:isMouseLeftDown() and not button.was_pressed then + button.onpressed() + button.was_pressed = true + elseif not Engine:isMouseLeftDown() then + button.was_pressed = false + end + else + button.hovered = false + button.was_pressed = false + end +end + +--- the setup function +--- @return nil +function setup_window() + Engine:setTitle("Hello World new") + Engine:setWidth(800) + Engine:setHeight(600) + Engine:setFrameRate(60) + + testButton.onpressed = function() + Engine:messageBox("Button Pressed") + end +end + +--- the set_keylist function +--- @return string +function set_keylist() + return "WASD" +end + +--- the update function +--- @param Engine GameEngine # The GameEngine instance. +--- @return nil +function update() + update_button( testButton) +end + + +--- the draw function +--- @return nil +function draw() + Engine:setColor(Color.new(0, 0, 0)) + Engine:fillScreen(Color.new(255, 255, 255)) + + draw_button(testButton) +end \ No newline at end of file diff --git a/lua/script.lua b/lua/script.lua new file mode 100644 index 0000000..dd78e89 --- /dev/null +++ b/lua/script.lua @@ -0,0 +1,501 @@ +-- Tetris Game using GameEngine + +local vector2 = require("vector2") +local button = require("button") + +-- Constants +local GRID_WIDTH = 10 +local GRID_HEIGHT = 19 +local BLOCK_SIZE = 30 +local FALL_SPEED = 0.5 + +local screenWidth = 800 +local screenHeight = 600 + +-- Tetromino shapes and colors +local tetrominoes = { + { shape = {{1, 1, 1, 1}}, color = Color.new(0, 255, 255) }, -- I + { shape = {{1, 1}, {1, 1}}, color = Color.new(255, 255, 0) }, -- O + { shape = {{0, 1, 0}, {1, 1, 1}}, color = Color.new(128, 0, 128) }, -- T + { shape = {{1, 1, 0}, {0, 1, 1}}, color = Color.new(0, 255, 0) }, -- S + { shape = {{0, 1, 1}, {1, 1, 0}}, color = Color.new(255, 0, 0) }, -- Z + { shape = {{1, 1, 1}, {1, 0, 0}}, color = Color.new(255, 165, 0) }, -- L + { shape = {{1, 1, 1}, {0, 0, 1}}, color = Color.new(0, 0, 255) } -- J +} + +-- Game State +local grid = {} +local currentPiece = {} +local pieceX, pieceY = 4, 0 +local fallTimer = 0 +local lastKeyState = { Left = false, Right = false, Down = false, Rotate = false, Space = true } + +-- enum of gameState, Main Menu, Playing, Submitting Name, Game Over + +local MAIN_MENU = 0 +local PLAYING = 1 +local GAME_OVER = 2 +local SUBMITTING_NAME = 3 + +local gameState = MAIN_MENU + +local titleScreenBitmap + + +local nameTextBox +local score = 0 + +-- list of bitmap frames that should be animated +local leaderboardFrames = {} + +local leaderboardFrameTimer = 0 +local leaderboardFrameIndex = 1 +local leaderboardFreamSpeed = 1 + + +local yesButton = button.new(300, 400, 100, 50, "Yes", Color.new(0, 255, 0), function() + print("Sending Post") + local playerName = nameTextBox:GetText() + + if playerName == "" then playerName = "Anonymous" end + + local data = "{ \"player\": \"" .. playerName .. "\", \"score\": " .. score .. " }" + local response = GameEngine:postRequest("https://api.brammie15.dev/leaderboard/tetris", data) + if(response == "Error: Request timed out") then + netError = true + end + print(response) + gameState = GAME_OVER +end) +local noButton = button.new(300, 500, 100, 50, "No", Color.new(255, 0, 0), function() gameState = GAME_OVER end) + + +local hasGottenLeaderBoard = false +local leaderboard = {} + +local netError = false + +local nextPiece = {} + +local pieceBag = {} + + +--- region BagStuff +local function shuffleBag() + pieceBag = {} + local indices = {1, 2, 3, 4, 5, 6, 7} + for i = #indices, 2, -1 do + local j = math.random(i) + indices[i], indices[j] = indices[j], indices[i] + end + for _, index in ipairs(indices) do + table.insert(pieceBag, tetrominoes[index]) + end +end + +local function getNextPiece() + if #pieceBag == 0 then + shuffleBag() + end + local pieceData = table.remove(pieceBag, 1) + return { shape = pieceData.shape, color = pieceData.color } +end + +local function getRandomPiece() + local pieceData = tetrominoes[math.random(#tetrominoes)] + return { shape = pieceData.shape, color = pieceData.color } +end + +--- endregion + +local function checkCollision(px, py, piece) + for y = 1, #piece.shape do + for x = 1, #piece.shape[y] do + if piece.shape[y][x] == 1 then + if px + x < 1 or px + x > GRID_WIDTH or py + y > GRID_HEIGHT or (grid[py + y] and grid[py + y][px + x].value ~= 0) then + return true + end + end + end + end + return false +end + +local function newPiece() + currentPiece = nextPiece or getNextPiece() + nextPiece = getNextPiece() + pieceX, pieceY = 4, 0 + + if checkCollision(pieceX, pieceY, currentPiece) then + gameState = SUBMITTING_NAME + end +end + +--- region GhostPieces +local function getGhostPieceY() + local ghostY = pieceY + while not checkCollision(pieceX, ghostY + 1, currentPiece) do + ghostY = ghostY + 1 + end + return ghostY +end + +local function drawGhostPiece() + local ghostY = getGhostPieceY() + GameEngine:setColor(Color.new(200, 200, 200)) -- Semi-transparent gray + + for y = 1, #currentPiece.shape do + for x = 1, #currentPiece.shape[y] do + if currentPiece.shape[y][x] == 1 then + GameEngine:fillRect((pieceX + x) * BLOCK_SIZE, (ghostY + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) + end + end + end +end + +--- endregion + +local function drawNextPiece() + GameEngine:setColor(Color.new(255, 255, 255)) + GameEngine:drawText("Next:", 650, 100) + + if nextPiece then + for y = 1, #nextPiece.shape do + for x = 1, #nextPiece.shape[y] do + if nextPiece.shape[y][x] == 1 then + GameEngine:setColor(nextPiece.color) + GameEngine:fillRect(650 + x * BLOCK_SIZE, 120 + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) + end + end + end + end +end + +local function clearLines() + local linesCleared = 0 + for y = GRID_HEIGHT, 1, -1 do + local full = true + for x = 1, GRID_WIDTH do + if grid[y][x].value == 0 then + full = false + break + end + end + if full then + table.remove(grid, y) + table.insert(grid, 1, {}) + for x = 1, GRID_WIDTH do + grid[1][x] = { value = 0, color = Color.new(255, 255, 255) } + end + linesCleared = linesCleared + 1 + end + end + + -- Score calculation based on cleared lines + local scoreTable = { 100, 300, 500, 800 } + if linesCleared > 0 then + score = score + scoreTable[linesCleared] or 0 + end +end + +local function freezePiece() + for y = 1, #currentPiece.shape do + for x = 1, #currentPiece.shape[y] do + if currentPiece.shape[y][x] == 1 then + grid[pieceY + y][pieceX + x] = { value = 1, color = currentPiece.color } + end + end + end + clearLines() + newPiece() +end + +local function rotatePiece() + local newShape = {} + for x = 1, #currentPiece.shape[1] do + newShape[x] = {} + for y = 1, #currentPiece.shape do + newShape[x][#currentPiece.shape - y + 1] = currentPiece.shape[y][x] + end + end + if not checkCollision(pieceX, pieceY, { shape = newShape }) then + currentPiece.shape = newShape + end +end + +local function drawGrid() + for y = 1, GRID_HEIGHT do + for x = 1, GRID_WIDTH do + if grid[y][x].value ~= 0 then + GameEngine:setColor(grid[y][x].color) + GameEngine:fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) + end + GameEngine:setColor(Color.new(50, 50, 50)) + GameEngine:drawRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) + end + end + GameEngine:setColor(Color.new(255, 255, 255)) + GameEngine:drawRect(BLOCK_SIZE, BLOCK_SIZE, GRID_WIDTH * BLOCK_SIZE, GRID_HEIGHT * BLOCK_SIZE) +end + +local function drawPiece() + GameEngine:setColor(currentPiece.color) + for y = 1, #currentPiece.shape do + for x = 1, #currentPiece.shape[y] do + if currentPiece.shape[y][x] == 1 then + GameEngine:fillRect((pieceX + x) * BLOCK_SIZE, (pieceY + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) + end + end + end +end + +function setup_window() + GameEngine:setTitle("Tetris") + GameEngine:setWidth(screenWidth) + GameEngine:setHeight(screenHeight) + GameEngine:setFrameRate(60) +end + +--- the set_keylist function +--- @return string +function set_keylist() + return "WASD " +end + +function start() + -- Initialize grid + for y = 1, GRID_HEIGHT do + grid[y] = {} + for x = 1, GRID_WIDTH do + grid[y][x] = { value = 0, color = Color.new(255, 255, 255) } + end + end + + nextPiece = getRandomPiece() + newPiece() + + nameTextBox = Textbox.new("") + nameTextBox:SetBounds(250, 200, 200, 25) + nameTextBox:Hide() + + titleScreenBitmap = Bitmap.new("resources/tetrisLogo.bmp", true) + + + -- load frame0 - 4 + for i = 0, 4 do + print("loading frame" .. i) + local bmp = Bitmap.new("resources/leaderboard/frame" .. i .. ".bmp", true) + bmp:SetTransparencyColor(Color.new(255, 0, 255)) + table.insert(leaderboardFrames, bmp) + end + +end + +function update() + -- print(GameEngine:getMouseX(), GameEngine:getMouseY()) + if gameState == PLAYING then + + fallTimer = fallTimer + 1 / 60 + if fallTimer >= FALL_SPEED then + if not checkCollision(pieceX, pieceY + 1, currentPiece) then + pieceY = pieceY + 1 + else + freezePiece() + end + fallTimer = 0 + end + + + local leftPressed = GameEngine:isKeyDown("A") + local rightPressed = GameEngine:isKeyDown("D") + local downPressed = GameEngine:isKeyDown("S") + local rotatePressed = GameEngine:isKeyDown("W") + local spacePressed = GameEngine:isKeyDown(" ") + + if leftPressed and not lastKeyState.Left and not checkCollision(pieceX - 1, pieceY, currentPiece) then + pieceX = pieceX - 1 + end + if rightPressed and not lastKeyState.Right and not checkCollision(pieceX + 1, pieceY, currentPiece) then + pieceX = pieceX + 1 + end + if downPressed and not lastKeyState.Down and not checkCollision(pieceX, pieceY + 1, currentPiece) then + pieceY = pieceY + 1 + end + + if spacePressed and not lastKeyState.Space then + while not checkCollision(pieceX, pieceY + 1, currentPiece) do + pieceY = pieceY + 1 + end + freezePiece() + end + + + if rotatePressed and not lastKeyState.Rotate then + rotatePiece() + end + + + lastKeyState.Left = leftPressed + lastKeyState.Right = rightPressed + lastKeyState.Down = downPressed + lastKeyState.Rotate = rotatePressed + lastKeyState.Space = spacePressed + end + + if gameState == GAME_OVER then + + --Update leaderboard gif + leaderboardFrameTimer = leaderboardFrameTimer + 1 / 60 + if leaderboardFrameTimer >= leaderboardFreamSpeed then + leaderboardFrameIndex = leaderboardFrameIndex + 1 + if leaderboardFrameIndex > #leaderboardFrames then + leaderboardFrameIndex = 1 + end + leaderboardFrameTimer = 0 + end + + if GameEngine:isKeyDown("R") then + gameState = PLAYING + score = 0 + netError = false + hasGottenLeaderBoard = false + leaderboard = {} + nameTextBox:SetText("") + grid = {} + for y = 1, GRID_HEIGHT do + grid[y] = {} + for x = 1, GRID_WIDTH do + grid[y][x] = { value = 0, color = Color.new(255, 255, 255) } + end + end + newPiece() + end + end + + if gameState == SUBMITTING_NAME then + yesButton:update(GameEngine) + noButton:update(GameEngine) + end + + if gameState == MAIN_MENU then + if GameEngine:isKeyDown(" ") then + gameState = PLAYING + end + end +end + +function drawScoreBoard() + GameEngine:fillScreen(Color.new(0, 0, 0)) + if not hasGottenLeaderBoard then + + local response = GameEngine:getRequest("https://api.brammie15.dev/leaderboard/tetris") + if response == "Error: Request timed out" then + netError = true + end + + print(response) + + -- format is + -- NAME SCORE + + if not netError then + for line in response:gmatch("[^\r\n]+") do + local name, score = line:match("([^%s]+)%s+(%d+)") + table.insert(leaderboard, { name = name, score = score }) + end + table.sort(leaderboard, function(a, b) return a.score > b.score end) + end + hasGottenLeaderBoard = true + end + + + + GameEngine:setColor(Color.new(255, 0, 0)) + GameEngine:drawText("Score: " .. score, 350, 250) + + if hasGottenLeaderBoard then + local NamesX = 600 + local ScoresX = 700 + local Y = 100 + + GameEngine:setColor(Color.new(255, 255, 0)) + -- GameEngine:drawText("Leaderboard", 600, 60) + GameEngine:drawBitmap(leaderboardFrames[leaderboardFrameIndex], 450, 60) + + if netError then + GameEngine:setColor(Color.new(255, 0, 0)) + GameEngine:drawText("Error: Request timed out", 600, 100) + GameEngine:drawText("Or server down", 600, 120) + GameEngine:drawText("Not gonna check :p", 600, 140) + return + end + + for i = 1, math.min(#leaderboard, 20) do + GameEngine:setColor(Color.new(255, 255, 255)) + GameEngine:drawText(leaderboard[i].name, NamesX, Y) + GameEngine:drawText(leaderboard[i].score, ScoresX, Y) + Y = Y + 20 + end + end +end + +function drawGame() + GameEngine:fillScreen(Color.new(0, 0, 0)) + drawGrid() + drawGhostPiece() + drawPiece() + drawNextPiece() -- Draw the next piece preview + GameEngine:setColor(Color.new(255, 255, 255)) + GameEngine:drawText("Score: " .. score, 650, 50) +end + +function drawGameOver() + nameTextBox:Hide() + GameEngine:setColor(Color.new(255, 0, 0)) + GameEngine:fillScreen(Color.new(0, 0, 0)) + GameEngine:drawText("Game Over", 350, 300) + GameEngine:drawText("Press R to restart", 350, 350) + + drawScoreBoard() +end + +function drawSubmitName() + GameEngine:fillScreen(Color.new(0, 0, 0)) + GameEngine:setColor(Color.new(255, 255, 255)) + GameEngine:drawText("Name:", 200, 200) + local charwidth = 8 + local submitText = "Would you like to submit to the leaderboard" + local submitTextWidth = #submitText * charwidth + GameEngine:drawText(submitText, 400 - submitTextWidth / 2, 50) + nameTextBox:Show() + yesButton:draw(GameEngine) + noButton:draw(GameEngine) + +end + +function drawMainMenu() + GameEngine:drawBitmap(titleScreenBitmap, 0, 0) + + --Press space to start + GameEngine:setColor(Color.new(255, 255, 255)) + GameEngine:drawText("Press Space to Start", 350, 500) +end + +function draw() + + if gameState == PLAYING then + drawGame() + end + + if gameState == GAME_OVER then + drawGameOver() + end + + if gameState == SUBMITTING_NAME then + drawSubmitName() + end + + if gameState == MAIN_MENU then + drawMainMenu() + end +end diff --git a/lua/script_old.lua b/lua/script_old.lua new file mode 100644 index 0000000..f540c70 --- /dev/null +++ b/lua/script_old.lua @@ -0,0 +1,131 @@ +-- annotation.lua contains EmmyLua annotations for cpp_function & cpp_variable\ + +-- ball player + +player = { + x = 0, + y = 0, + size = 20 +} + + +avoid_cubes = {} + +timer = 0 +max_timer = 10 + +function create_cube(x, y, size, timer_to_explode, exploded, death_timer) + return { + x = x, + y = y, + size = size, + timer_to_explode = timer_to_explode, + death_timer = death_timer, + exploded = exploded + } +end + +function create_random_cube() + local x = math.random(0, 800) + local y = math.random(0, 600) + local size = math.random(10, 50) + local timer_to_explode = math.random(50, 200) + local exploded = false + local death_timer = math.random(50, 200) + return create_cube(x, y, size, timer_to_explode, exploded, death_timer) +end + + + +--- @param Engine GameEngine +--- @param cube table +function draw_cube(Engine, cube) + if cube.exploded then + Engine:setColor(Color.new(255, 0, 0)) + Engine:fillRect(cube.x, cube.y, cube.size, cube.size) + else + Engine:setColor(Color.new(0, 255, 0)) + Engine:drawRect(cube.x, cube.y, cube.size, cube.size) + end +end + +function update_cubes() + for i, cube in ipairs(avoid_cubes) do + cube.timer_to_explode = cube.timer_to_explode - 1 + if cube.timer_to_explode < 0 then + cube.exploded = true + end + + if cube.exploded then + cube.death_timer = cube.death_timer - 1 + if cube.death_timer < 0 then + table.remove(avoid_cubes, i) + end + end + end +end + +function draw_player(Engine) + Engine:setColor(Color.new(255, 0, 0)) + Engine:fillRect(player.x, player.y, player.size, player.size) +end + +--- the setup function +--- @param Engine GameEngine # The GameEngine instance. +--- @return nil +function setup_window(Engine) + Engine:setTitle("Hello World new") + Engine:setWidth(800) + Engine:setHeight(600) + Engine:setFrameRate(60) +end + +--- the set_keylist function +--- @return string +function set_keylist() + return "WASD" +end + +--- the update function +--- @param Engine GameEngine # The GameEngine instance. +--- @return nil +function update(Engine) + if Engine:isKeyDown("W") then + player.y = player.y - 5 + end + + if Engine:isKeyDown("A") then + player.x = player.x - 5 + end + + if Engine:isKeyDown("S") then + player.y = player.y + 5 + end + + if Engine:isKeyDown("D") then + player.x = player.x + 5 + end + + timer = timer + 1 + if timer > max_timer then + table.insert(avoid_cubes, create_random_cube()) + timer = 0 + end + + update_cubes() + + player.x = Engine:getMouseX() + player.y = Engine:getMouseY() +end + + +--- the draw function +--- @param Engine GameEngine # The GameEngine instance. +--- @return nil +function draw(Engine) + Engine:fillScreen(Color.new(0, 0, 0)) + draw_player(Engine) + for i, cube in ipairs(avoid_cubes) do + draw_cube(Engine, cube) + end +end \ No newline at end of file diff --git a/lua/utils.lua b/lua/utils.lua new file mode 100644 index 0000000..43ec10b --- /dev/null +++ b/lua/utils.lua @@ -0,0 +1,21 @@ + +utils = {} + + +--- @param x1 number +--- @param x2 number +--- @param w1 number +--- @param h1 number +--- @param y1 number +--- @param y2 number +--- @param w2 number +--- @param h2 number +--- @return boolean +function utils.check_collision(x1, y1, w1, h1, x2, y2, w2, h2) + return x1 < x2 + w2 and + x2 < x1 + w1 and + y1 < y2 + h2 and + y2 < y1 + h1 +end + +return utils \ No newline at end of file diff --git a/lua/vector2.lua b/lua/vector2.lua new file mode 100644 index 0000000..25a8e4b --- /dev/null +++ b/lua/vector2.lua @@ -0,0 +1,68 @@ +-- Define 'Vector2' class with shared methods and metamethods +--- @class Vector2 +--- @field x number +--- @field y number +local Vector2 = {} -- Declare Vector2 as a local table first + + + +--- Calculate the dot product of two vectors +--- @param v0 Vector2 +--- @param v1 Vector2 +--- @return number +Vector2.dot = function(v0, v1) + return v0.x * v1.x + v0.y * v1.y +end + +--- Calculate the add of two vectors +--- @param v0 Vector2 +--- @param v1 Vector2 +--- @return Vector2 +Vector2.__add = function(v0, v1) + return Vector2.new(v0.x + v1.x, v0.y + v1.y) +end + + +--- Calculate the sub of two vectors +--- @param v0 Vector2 +--- @param v1 Vector2 +--- @return Vector2 +Vector2.__sub = function(v0, v1) + return Vector2.new(v0.x - v1.x, v0.y - v1.y) +end + +--- Calcuate the length of a vector2 +--- @param v Vector2 +--- @return number +Vector2.__len = function(v) + return math.sqrt(v.x * v.x + v.y * v.y) +end + + +--- Vector2 to string +--- @param v Vector2 +--- @return string +Vector2.__tostring = function(v) + return string.format("Vector2(%g, %g)", v.x, v.y) +end + +Vector2.add = function(self, other_vec) + self.x = self.x + other_vec.x + self.y = self.y + other_vec.y + return self +end + +Vector2.__index = Vector2 -- Enable shared methods via __index + +--- Constructor +--- @param x number +--- @param y number +--- @return Vector2 +function Vector2.new(x, y) + return setmetatable( + {x = x or 0, y = y or 0}, -- Initialize x and y with defaults + Vector2 -- Assign metatable + ) +end + +return Vector2 -- Return the Vector2 table \ No newline at end of file diff --git a/resources/aPoes.bmp b/resources/aPoes.bmp new file mode 100644 index 0000000..9c3f284 Binary files /dev/null and b/resources/aPoes.bmp differ diff --git a/resources/leaderboard/frame0.bmp b/resources/leaderboard/frame0.bmp new file mode 100644 index 0000000..e603923 Binary files /dev/null and b/resources/leaderboard/frame0.bmp differ diff --git a/resources/leaderboard/frame0.png b/resources/leaderboard/frame0.png new file mode 100644 index 0000000..366a34f Binary files /dev/null and b/resources/leaderboard/frame0.png differ diff --git a/resources/leaderboard/frame1.bmp b/resources/leaderboard/frame1.bmp new file mode 100644 index 0000000..e62000a Binary files /dev/null and b/resources/leaderboard/frame1.bmp differ diff --git a/resources/leaderboard/frame1.png b/resources/leaderboard/frame1.png new file mode 100644 index 0000000..6c4f637 Binary files /dev/null and b/resources/leaderboard/frame1.png differ diff --git a/resources/leaderboard/frame2.bmp b/resources/leaderboard/frame2.bmp new file mode 100644 index 0000000..ea60267 Binary files /dev/null and b/resources/leaderboard/frame2.bmp differ diff --git a/resources/leaderboard/frame2.png b/resources/leaderboard/frame2.png new file mode 100644 index 0000000..346ecee Binary files /dev/null and b/resources/leaderboard/frame2.png differ diff --git a/resources/leaderboard/frame3.bmp b/resources/leaderboard/frame3.bmp new file mode 100644 index 0000000..a9ceada Binary files /dev/null and b/resources/leaderboard/frame3.bmp differ diff --git a/resources/leaderboard/frame3.png b/resources/leaderboard/frame3.png new file mode 100644 index 0000000..b1c0184 Binary files /dev/null and b/resources/leaderboard/frame3.png differ diff --git a/resources/leaderboard/frame4.bmp b/resources/leaderboard/frame4.bmp new file mode 100644 index 0000000..c3350bd Binary files /dev/null and b/resources/leaderboard/frame4.bmp differ diff --git a/resources/leaderboard/frame4.png b/resources/leaderboard/frame4.png new file mode 100644 index 0000000..d15e9aa Binary files /dev/null and b/resources/leaderboard/frame4.png differ diff --git a/resources/normal.png b/resources/normal.png new file mode 100644 index 0000000..3a8a103 Binary files /dev/null and b/resources/normal.png differ diff --git a/resources/tetrisLogo.bmp b/resources/tetrisLogo.bmp new file mode 100644 index 0000000..af827bf Binary files /dev/null and b/resources/tetrisLogo.bmp differ diff --git a/resources/tetrisLogo.png b/resources/tetrisLogo.png new file mode 100644 index 0000000..e6d5ea6 Binary files /dev/null and b/resources/tetrisLogo.png differ diff --git a/src/AbstractGame.cpp b/src/AbstractGame.cpp new file mode 100644 index 0000000..d304b1b --- /dev/null +++ b/src/AbstractGame.cpp @@ -0,0 +1,24 @@ +//----------------------------------------------------------------- +// AbstractGame Object +// C++ Source - AbstractGame.cpp - version v8_01 +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- +#include "GameEngine.h" +#include "AbstractGame.h" + +//----------------------------------------------------------------- +// AbstractGame Member Functions +//----------------------------------------------------------------- +void AbstractGame::Initialize() +{ + // Set required values + GAME_ENGINE->SetTitle(_T("Game Engine version 8_01")); + + // Set optional values + GAME_ENGINE->SetWidth(640); + GAME_ENGINE->SetHeight(480); + GAME_ENGINE->SetFrameRate(50); +} \ No newline at end of file diff --git a/src/AbstractGame.h b/src/AbstractGame.h new file mode 100644 index 0000000..04a6d66 --- /dev/null +++ b/src/AbstractGame.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------- +// AbstractGame Object +// C++ Header - AbstractGame.h - version v8_01 +// +// AbstractGame is the abstract class which declares the functions that a +// game class needs to implement to work with the game engine +//----------------------------------------------------------------- +#pragma once + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +//----------------------------------------------------------------- +// AbstractGame Class +//----------------------------------------------------------------- +class AbstractGame +{ +public : + // Constructor(s) and destructor + AbstractGame() = default; + virtual ~AbstractGame() = default; + + // Disabling copy/move constructors and assignment operators + AbstractGame(const AbstractGame& other) = delete; + AbstractGame(AbstractGame&& other) noexcept = delete; + AbstractGame& operator=(const AbstractGame& other) = delete; + AbstractGame& operator=(AbstractGame&& other) noexcept = delete; + + // Game functions + virtual void Initialize (); // implemented in the .cpp file + + virtual void Start () = 0; // pure virtual function + virtual void End () = 0; // pure virtual function + virtual void MouseButtonAction (bool isLeft, bool isDown, int x, int y, WPARAM wParam) = 0; // pure virtual function + virtual void MouseWheelAction (int x, int y, int distance, WPARAM wParam) = 0; // pure virtual function + virtual void MouseMove (int x, int y, WPARAM wParam) = 0; // pure virtual function + virtual void CheckKeyboard () = 0; // pure virtual function + virtual void KeyPressed (TCHAR key) = 0; // pure virtual function + virtual void Paint (RECT rect) const = 0; // pure virtual function + virtual void Tick () = 0; // pure virtual function + virtual void InitialiseBindings () = 0; // pure virtual function +}; \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..232042d --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,306 @@ +//----------------------------------------------------------------- +// Main Game File +// C++ Source - Game.cpp - version v8_01 +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- +#include "Game.h" + +#include + +//----------------------------------------------------------------- +// Game Member Functions +//----------------------------------------------------------------- + +Game::Game(const std::string &fileName): m_fileName(fileName) { + // nothing to create +// cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"}); +// std::cout << r.text << std::endl; +// int result = MessageBox(hWnd, "Enter a string?", "Input Request", MB_OKCANCEL); +// if (result == IDOK) { +// User pressed OK +// } +} + +Game::~Game() { + // nothing to destroy +} + +void Game::Initialize() { + // Code that needs to execute (once) at the start of the game, before the game window is created + + //Check if the file exists using fs + if (!std::filesystem::exists(m_fileName)) { + GAME_ENGINE->MessageBox("File does not exist"); + return; + } + + try{ + const auto scriptResult = m_state.safe_script_file(m_fileName); + if (!scriptResult.valid()) { + //Handle Error + const sol::error err = scriptResult; + std::cerr << "Load Script Error: " << err.what(); + GAME_ENGINE->MessageBox(err.what()); + } + } catch (const sol::error& e) { + std::cerr << "Error loading script: " << e.what() << std::endl; + GAME_ENGINE->MessageBox(e.what()); + } + + + AbstractGame::Initialize(); +// GAME_ENGINE->SetTitle(_T("Game Engine version 8_01")); + + this->FunctionCall("setup_window"); + + // Set the keys that the game needs to listen to + //Get return value from Lua function + auto keyList = this->FunctionCall("set_keylist"); + GAME_ENGINE->SetKeyList(keyList); + +// tstringstream buffer; +// buffer << _T("W"); +// buffer << (char) VK_LEFT; +// buffer << (char) VK_RIGHT; +// GAME_ENGINE->SetKeyList(buffer.str()); + +} + +void Game::Start() { + // Insert code that needs to execute (once) at the start of the game, after the game window is created + this->FunctionCall("start"); +} + +void Game::End() { + // Insert code that needs to execute when the game ends +} + +void Game::Paint(RECT rect) const { + this->FunctionCall("draw"); +} + +void Game::Tick() { + this->FunctionCall("update"); +} + +void Game::MouseButtonAction(bool isLeft, bool isDown, int x, int y, WPARAM wParam) { + // Insert code for a mouse button action + + /* Example: + if (isLeft == true && isDown == true) // is it a left mouse click? + { + if ( x > 261 && x < 261 + 117 ) // check if click lies within x coordinates of choice + { + if ( y > 182 && y < 182 + 33 ) // check if click also lies within y coordinates of choice + { + GAME_ENGINE->MessageBox(_T("Clicked.")); + } + } + } + */ +} + +void Game::MouseWheelAction(int x, int y, int distance, WPARAM wParam) { + // Insert code for a mouse wheel action +} + +void Game::MouseMove(int x, int y, WPARAM wParam) { + // Insert code that needs to execute when the mouse pointer moves across the game window + + /* Example: + if ( x > 261 && x < 261 + 117 ) // check if mouse position is within x coordinates of choice + { + if ( y > 182 && y < 182 + 33 ) // check if mouse position also is within y coordinates of choice + { + GAME_ENGINE->MessageBox("Mouse move."); + } + } + */ + +} + +void Game::CheckKeyboard() { + // Here you can check if a key is pressed down + // Is executed once per frame + +// if (GAME_ENGINE->IsKeyDown(_T('W'))) { +// GAME_ENGINE->MessageBox("W key pressed"); +// } + /* Example: + if (GAME_ENGINE->IsKeyDown(_T('L'))) yIcon += xSpeed; + if (GAME_ENGINE->IsKeyDown(_T('M'))) xIcon += xSpeed; + if (GAME_ENGINE->IsKeyDown(_T('O'))) yIcon -= ySpeed; + */ +} + +void Game::KeyPressed(TCHAR key) { + // DO NOT FORGET to use SetKeyList() !! + + // Insert code that needs to execute when a key is pressed + // The function is executed when the key is *released* + // You need to specify the list of keys with the SetKeyList() function + + + + //* Example: +// switch (key) +// { +// case _T('K'): case VK_LEFT: +// GAME_ENGINE->MessageBox("Moving left."); +// break; +// case _T('L'): case VK_DOWN: +// GAME_ENGINE->MessageBox("Moving down."); +// break; +// case _T('M'): case VK_RIGHT: +// GAME_ENGINE->MessageBox("Moving right."); +// break; +// case _T('O'): case VK_UP: +// GAME_ENGINE->MessageBox("Moving up."); +// break; +// case VK_ESCAPE: +// GAME_ENGINE->MessageBox("Escape menu."); +// } + +// switch(key){ +// case _T('W'): +// GAME_ENGINE->MessageBox("W key pressed"); +// break; +// } +} + +void Game::CallAction(Caller *callerPtr) { + // Insert the code that needs to execute when a Caller (= Button, TextBox, Timer, Audio) executes an action +} + +void Game::InitialiseBindings() { + std::cout << "Initialising bindings" << std::endl; + m_state.open_libraries(sol::lib::base, sol::lib::package, sol::lib::string, sol::lib::table, sol::lib::math, sol::lib::os, sol::lib::io); + + + m_state.new_usertype( + "Color", + sol::constructors(), + "r", &ColorRGB::r, + "g", &ColorRGB::g, + "b", &ColorRGB::b + ); + + m_state.new_usertype("GameEngine", + "setTitle", &GameEngine::SetTitle, + "setWidth", &GameEngine::SetWidth, + "setHeight", &GameEngine::SetHeight, + "getWidth", &GameEngine::GetWidth, + "getHeight", &GameEngine::GetHeight, + "setFrameRate", &GameEngine::SetFrameRate, + "setWindowPosition", &GameEngine::SetWindowPosition, + "setWindowRegion", &GameEngine::SetWindowRegion, + "setKeyList", &GameEngine::SetKeyList, + "setColor", &GameEngine::SetColorRGB, + + "messageBox", [](GameEngine &gameEngine, const std::string &message) { + gameEngine.MessageBox(message.c_str()); + }, + + "drawBitmap", sol::overload( + sol::resolve(&GameEngine::DrawBitmap), + sol::resolve(&GameEngine::DrawBitmap) + ), + + "getMouseX", &GameEngine::GetMouseX, + "getMouseY", &GameEngine::GetMouseY, + "isMouseLeftDown", &GameEngine::IsMouseLeftDown, + "isMouseRightDown", &GameEngine::IsMouseRightDown, + + "drawRect", sol::overload( + sol::resolve(&GameEngine::DrawRect), + sol::resolve(&GameEngine::DrawRect) + ), + "fillRect", sol::overload( + sol::resolve(&GameEngine::FillRect), + sol::resolve(&GameEngine::FillRect) + ), + "drawOval", sol::overload( + sol::resolve(&GameEngine::DrawOval), + sol::resolve(&GameEngine::DrawOval) + ), + "fillOval", sol::overload( + sol::resolve(&GameEngine::FillOval), + sol::resolve(&GameEngine::FillOval) + ), + "fillScreen", &GameEngine::FillWindowRectRGB, + "drawText", sol::overload( + sol::resolve(&GameEngine::DrawString), + sol::resolve(&GameEngine::DrawString) + ), + "isKeyDown", [] (GameEngine &gameEngine, const std::string &key) { + return gameEngine.IsKeyDown(_T(key[0])); + }, + "getRequest", [](GameEngine &gameEngine, const std::string &url) { + return gameEngine.GetRequest(url); + }, + "postRequest", [](GameEngine &gameEngine, const std::string &url, const std::string &data) { + return gameEngine.PostRequest(url, data); + }, + "GetString", [](GameEngine &gameEngine) { + return std::string(gameEngine.AskString()); + } + ); + + m_state.new_usertype( + "Bitmap", + sol::constructors(), + "SetTransparencyColor", [](Bitmap &bitmap, const ColorRGB &color) { + bitmap.SetTransparencyColor(RGB(color.r, color.g, color.b)); + }, + "SetOpacity", &Bitmap::SetOpacity, + "Exists", &Bitmap::Exists, + "GetWidth", &Bitmap::GetWidth, + "GetHeight", &Bitmap::GetHeight, + "GetTransparencyColor", &Bitmap::GetTransparencyColor, + "GetOpacity", &Bitmap::GetOpacity, + "HasAlphaChannel", &Bitmap::HasAlphaChannel, + "SaveToFile", &Bitmap::SaveToFile + ); + m_state.new_usertype( + "Textbox", sol::constructors(), + "SetBounds", &TextBox::SetBounds, + "SetText", &TextBox::SetText, + "SetFont", &TextBox::SetFont, + "SetBackcolor", &TextBox::SetBackcolor, + "SetForecolor", &TextBox::SetForecolor, + "SetEnabled", &TextBox::SetEnabled, + "Show", &TextBox::Show, + "Hide", &TextBox::Hide, + "GetBounds", &TextBox::GetBounds, + "GetText", &TextBox::GetText, + "GetForecolor", &TextBox::GetForecolor + ); + + m_state.set("GameEngine", GAME_ENGINE); +} + +//std::string Game::GetRequest(std::string url) { +// cpr::Response r = cpr::Get(cpr::Url{url}); +// return r.text; +//} + +// +//void Game::FunctionCall(const std::string &functionName) const { +// auto func = m_state[functionName]; +// auto func_return{ func() }; +// +// if (!func_return.valid()) { +// //Handle Error +// const sol::error err = func_return; +// std::cerr << "Function Call Error: " << err.what(); +// GAME_ENGINE->MessageBox(err.what()); +// } +// //Bram Verhulst - 2024 +//} + + + + diff --git a/src/Game.h b/src/Game.h new file mode 100644 index 0000000..ae04d31 --- /dev/null +++ b/src/Game.h @@ -0,0 +1,94 @@ +//----------------------------------------------------------------- +// Main Game File +// C++ Header - Game.h - version v8_01 +//----------------------------------------------------------------- + +#pragma once + +#include + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- + +#include "Resource.h" +#include "GameEngine.h" +#include "AbstractGame.h" + +//----------------------------------------------------------------- +// Game Class +//----------------------------------------------------------------- +class Game : public AbstractGame, public Callable +{ +public: + //--------------------------- + // Constructor(s) and Destructor + //--------------------------- + Game(const std::string &fileName); + + virtual ~Game() override; + + //--------------------------- + // Disabling copy/move constructors and assignment operators + //--------------------------- + Game(const Game& other) = delete; + Game(Game&& other) noexcept = delete; + Game& operator=(const Game& other) = delete; + Game& operator=(Game&& other) noexcept = delete; + + //--------------------------- + // General Member Functions + //--------------------------- + void Initialize () override; + void Start () override; + void End () override; + void Paint (RECT rect) const override; + void Tick () override; + void MouseButtonAction (bool isLeft, bool isDown, int x, int y, WPARAM wParam) override; + void MouseWheelAction (int x, int y, int distance, WPARAM wParam) override; + void MouseMove (int x, int y, WPARAM wParam) override; + void CheckKeyboard () override; + void KeyPressed (TCHAR key) override; + + void CallAction (Caller* callerPtr) override; + void InitialiseBindings () override; + +// std::string GetRequest(std::string url); + + template + ReturnType FunctionCall(const std::string &functionName, Args&&... args) const { + auto func = m_state[functionName]; + sol::protected_function_result func_return; + + if constexpr (sizeof...(Args) == 0) { + func_return = func(); + } else { + func_return = func(std::forward(args)...); + } + + if (!func_return.valid()) { + const sol::error err = func_return; + std::cerr << "Function Call Error: " << err.what(); + GAME_ENGINE->MessageBox(err.what()); + throw std::runtime_error(err.what()); + } + + if constexpr (!std::is_void_v) { + try { + return func_return.get(); + } catch (const sol::error& err) { + std::cerr << "Return Value Error: " << err.what(); + GAME_ENGINE->MessageBox(err.what()); + } + } + + return ReturnType{}; + } +private: + // ------------------------- + // Datamembers + // ------------------------- + + sol::state m_state; + std::string m_fileName; +}; diff --git a/src/Game.rc b/src/Game.rc new file mode 100644 index 0000000..df53e80 --- /dev/null +++ b/src/Game.rc @@ -0,0 +1,24 @@ +//----------------------------------------------------------------- +// Game Skeleton Resources +// RC Source - Game.rc - version v8_01 +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- +#include "Resource.h" + +//----------------------------------------------------------------- +// Icons +//----------------------------------------------------------------- +IDI_BIG ICON "big.ico" +IDI_SMALL ICON "small.ico" + +IDD_NAME_DIALOG DIALOGEX 0, 0, 200, 100 +STYLE WS_CAPTION | WS_SYSMENU + BEGIN +LTEXT "Enter your name:", IDC_STATIC, 10, 10, 180, 10 +EDITTEXT IDC_NAME_EDIT, 10, 30, 180, 20, ES_AUTOHSCROLL + PUSHBUTTON "OK", IDOK, 50, 60, 50, 14 +PUSHBUTTON "Cancel", IDCANCEL, 110, 60, 50, 14 +END \ No newline at end of file diff --git a/src/GameDefines.h b/src/GameDefines.h new file mode 100644 index 0000000..66fea2a --- /dev/null +++ b/src/GameDefines.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _UNICODE // extra unicode defines + #define tstring std::wstring + #define tcin std::wcin + #define tcout std::wcout + #define tstringstream std::wstringstream + #define tofstream std::wofstream + #define tifstream std::wifstream + #define tfstream std::wfstream + #define tostream std::wostream + #define to_tstring std::to_wstring +#else + #define tstring std::string + #define tcin std::cin + #define tcout std::cout + #define tstringstream std::stringstream + #define tofstream std::ofstream + #define tifstream std::ifstream + #define tfstream std::fstream + #define tostream std::ostream + #define to_tstring std::to_string +#endif + +//64 bit defines +#ifdef _WIN64 +#define GWLA_WNDPROC GWLP_WNDPROC +#define GWLA_HINSTANCE GWLP_HINSTANCE +#define GWLA_HWNDPARENT GWLP_HWNDPARENT +#define GWLA_USERDATA GWLP_USERDATA +#else if +#define GWLA_WNDPROC GWL_WNDPROC +#define GWLA_HINSTANCE GWL_HINSTANCE +#define GWLA_HWNDPARENT GWL_HWNDPARENT +#define GWLA_USERDATA GWL_USERDATA +#endif + +// ASSERT macro +#ifndef NDEBUG +#ifdef _WIN64 +#define ASSERT \ +if ( false ) {} \ +else \ +struct LocalAssert { \ + int mLine{}; \ + LocalAssert(int line=__LINE__) : mLine(line) {} \ + LocalAssert(bool isOK, const TCHAR* message=_T("")) { \ + if ( !isOK ) { \ + tstringstream buffer; \ + buffer << _T("ERROR!! Assert failed on line ") << LocalAssert().mLine << _T(" in file '") << __FILE__ << _T("'\nMessage: \"") << message << _T("\"\n"); \ + OutputDebugString(buffer.str().c_str()); \ + } \ + } \ +} myAsserter = LocalAssert +#else +#define ASSERT \ +if ( false ) {} \ +else \ +struct LocalAssert { \ + int mLine{}; \ + LocalAssert(int line=__LINE__) : mLine(line) {} \ + LocalAssert(bool isOK, const TCHAR* message=_T("")) { \ + if ( !isOK ) { \ + tstringstream buffer; \ + buffer << _T("ERROR!! Assert failed on line ") << LocalAssert().mLine << _T(" in file '") << __FILE__ << _T("'\nMessage: \"") << message << _T("\"\n"); \ + OutputDebugString(buffer.str().c_str()); \ + __asm { int 3 } \ + } \ + } \ +} myAsserter = LocalAssert +#endif +#else +#define ASSERT \ +if ( true ) {} else \ +struct NoAssert { \ + NoAssert(bool isOK, const TCHAR* message=_T("")) {} \ +} myAsserter = NoAssert +#endif + diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp new file mode 100644 index 0000000..6fa6e35 --- /dev/null +++ b/src/GameEngine.cpp @@ -0,0 +1,2862 @@ +//----------------------------------------------------------------- +// Game Engine Object +// C++ Source - GameEngine.cpp +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- +#include "GameEngine.h" + +#define _USE_MATH_DEFINES // necessary for including (among other values) PI - see math.h +#include // used in various draw member functions + +#include +#include // used for unicode strings + +#include +#include // using std::vector for tab control logic + +#include // used for gamepad input + +using namespace std; + +//----------------------------------------------------------------- +// Windows Functions +//----------------------------------------------------------------- +LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam) +{ + // Route all Windows messages to the game engine + return GAME_ENGINE->HandleEvent(hWindow, msg, wParam, lParam); +} + +//----------------------------------------------------------------- +// GameEngine Constructor(s)/Destructor +//----------------------------------------------------------------- +GameEngine::GameEngine() +{ + // start GDI+ + Gdiplus::GdiplusStartupInput gpStartupInput{}; + Gdiplus::GdiplusStartup(&m_GDIPlusToken, &gpStartupInput, NULL); +} + +GameEngine::~GameEngine() +{ + // clean up keyboard monitor buffer + delete m_KeyListPtr; + + // clean up the font + if (m_FontDraw != 0) + { + DeleteObject(m_FontDraw); + } + + // shut down GDI+ + Gdiplus::GdiplusShutdown(m_GDIPlusToken); + + // delete the game object + delete m_GamePtr; +} + +//----------------------------------------------------------------- +// Game Engine Member Functions +//----------------------------------------------------------------- +void GameEngine::SetGame(AbstractGame* gamePtr) +{ + m_GamePtr = gamePtr; +} + +void GameEngine::MonitorKeyboard() +{ + if (m_KeyListPtr != nullptr && GetForegroundWindow() == m_Window) + { + int count{}; + int key{ m_KeyListPtr[0] }; + + while (key != '\0' && count < (8 * sizeof(unsigned int))) + { + if ( !(GetAsyncKeyState(key)<0) ) // key is not pressed + { + if (m_KeybMonitor & (0x1 << count)) { + m_GamePtr->KeyPressed(key); // if the bit was 1, this fires a keypress + } + m_KeybMonitor &= ~(0x1 << count); // the bit is set to 0: key is not pressed + } + else m_KeybMonitor |= (0x1 << count); // the bit is set to 1: key is pressed + + key = m_KeyListPtr[++count]; // increase count and get next key + } + } +} + +void GameEngine::SetTitle(const tstring& title) +{ + m_Title = title; +} + +bool GameEngine::Run(HINSTANCE hInstance, int cmdShow) +{ + // set the instance member variable of the game engine + SetInstance(hInstance); + + m_GamePtr->InitialiseBindings(); + // Game initialization + m_GamePtr->Initialize(); + + // Create the game window + if (!CreateGameWindow(cmdShow)) return false; + + // Double buffering code + HDC hDC = GetDC(m_Window); + HDC hBufferDC = CreateCompatibleDC(hDC); + + // Create the buffer + HBITMAP hBufferBmp = CreateCompatibleBitmap(hDC, m_Width, m_Height); + HBITMAP hOldBmp = (HBITMAP)SelectObject(hBufferDC, hBufferBmp); + + m_HdcDraw = hBufferDC; + GetClientRect(m_Window, &m_RectDraw); + + // Framerate control + LARGE_INTEGER tickFrequency, tickTrigger, currentTick; + QueryPerformanceFrequency(&tickFrequency); + int countsPerMillisecond{ int(tickFrequency.LowPart) / 1000}; + QueryPerformanceCounter(¤tTick); + tickTrigger = currentTick; + + // Enter the main message loop + MSG msg; + while (true) + { + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + // Process the message + if (msg.message == WM_QUIT) break; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + else + { + // Get current time stamp + QueryPerformanceCounter(¤tTick); + if (currentTick.QuadPart >= tickTrigger.QuadPart) + { + // Paint the window + HDC hDC = GetDC(m_Window); + PaintDoubleBuffered(hDC); + ReleaseDC(m_Window, hDC); + + // Call the game tick + m_GamePtr->Tick(); + + // Process user input + m_GamePtr->CheckKeyboard(); + MonitorKeyboard(); + + // update the tick trigger + tickTrigger.QuadPart = currentTick.QuadPart + m_FrameDelay * countsPerMillisecond; + } + } + } + + // Reset the old bmp of the buffer + SelectObject(hBufferDC, hOldBmp); + + // Kill the buffer + DeleteObject(hBufferBmp); + DeleteDC(hBufferDC); + + // Exit + return msg.wParam?true:false; +} + +void GameEngine::PaintDoubleBuffered(HDC hDC) +{ + m_IsPainting = true; + m_GamePtr->Paint(m_RectDraw); + m_IsPainting = false; + + // As a last step copy the buffer DC to the window DC + BitBlt(hDC, 0, 0, m_Width, m_Height, m_HdcDraw, 0, 0, SRCCOPY); +} + +void GameEngine::ShowMousePointer(bool value) +{ + // set the value + ShowCursor(value); + + // redraw the screen + InvalidateRect(m_Window, nullptr, true); +} + +bool GameEngine::SetWindowRegion(const HitRegion* regionPtr) +{ + if (m_Fullscreen) return false; + + if (regionPtr == nullptr) + { + // turn off window region + SetWindowRgn(m_Window, NULL, true); + + // delete the buffered window region (if it exists) + delete m_WindowRegionPtr; + m_WindowRegionPtr = nullptr; + } + else + { + // if there is already a window region set, release the buffered region object + if (m_WindowRegionPtr != nullptr) + { + // turn off window region for safety + SetWindowRgn(m_Window, NULL, true); + + // delete the buffered window region + delete m_WindowRegionPtr; + } + + // create a copy of the submitted region (windows will lock the region handle that it receives) + m_WindowRegionPtr = new HitRegion(*regionPtr); + + // translate region coordinates in the client field to window coordinates, taking title bar and frame into account + m_WindowRegionPtr->Move(GetSystemMetrics(SM_CXFIXEDFRAME), GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYCAPTION)); + + // set the window region + SetWindowRgn(m_Window, m_WindowRegionPtr->GetHandle(), true); + } + + return true; +} + +bool GameEngine::HasWindowRegion() const +{ + return (m_WindowRegionPtr?true:false); +} + +bool GameEngine::GoFullscreen() +{ + // exit if already in fullscreen mode + if (m_Fullscreen) return false; + + // turn off window region without redraw + SetWindowRgn(m_Window, NULL, false); + + DEVMODE newSettings{}; + + // request current screen settings + EnumDisplaySettings(nullptr, 0, &newSettings); + + // set desired screen size/res + newSettings.dmPelsWidth = GetWidth(); + newSettings.dmPelsHeight = GetHeight(); + newSettings.dmBitsPerPel = 32; + + //specify which aspects of the screen settings we wish to change + newSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + + // attempt to apply the new settings, exit if failure, else set datamember to fullscreen and return true + if (ChangeDisplaySettings(&newSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL ) return false; + else + { + // store the location of the window + m_OldPosition = GetWindowPosition(); + + // switch off the title bar + DWORD dwStyle = (DWORD) GetWindowLongPtr(m_Window, GWL_STYLE); + dwStyle &= ~WS_CAPTION; + SetWindowLongPtr(m_Window, GWL_STYLE, dwStyle); + + // move the window to (0,0) + SetWindowPos(m_Window, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + InvalidateRect(m_Window, nullptr, true); + + m_Fullscreen = true; + + return true; + } +} + +bool GameEngine::GoWindowedMode() +{ + // exit if already in windowed mode + if (!m_Fullscreen) return false; + + // this resets the screen to the registry-stored values + ChangeDisplaySettings(0, 0); + + // replace the title bar + DWORD dwStyle = (DWORD) GetWindowLongPtr(m_Window, GWL_STYLE); + dwStyle = dwStyle | WS_CAPTION; + SetWindowLongPtr(m_Window, GWL_STYLE, dwStyle); + + // move the window back to its old position + SetWindowPos(m_Window, 0, m_OldPosition.x, m_OldPosition.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + InvalidateRect(m_Window, nullptr, true); + + m_Fullscreen = false; + + return true; +} + +bool GameEngine::IsFullscreen() const +{ + return m_Fullscreen; +} + +bool GameEngine::CreateGameWindow(int cmdShow) +{ + // Create the window class for the main window + WNDCLASSEX wndclass{}; + wndclass.cbSize = sizeof(wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW; + wndclass.lpfnWndProc = WndProc; + wndclass.hInstance = m_Instance; + wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclass.lpszClassName = m_Title.c_str(); + + // Register the window class + if (!RegisterClassEx(&wndclass)) return false; + + // Calculate window dimensions based on client rect + RECT windowRect{0, 0, m_Width, m_Height}; + AdjustWindowRect(&windowRect, WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | WS_CLIPCHILDREN, false); + + // Calculate the window size and position based upon the size + int iWindowWidth = windowRect.right - windowRect.left, + iWindowHeight = windowRect.bottom - windowRect.top; + + if (wndclass.lpszMenuName != NULL) + iWindowHeight += GetSystemMetrics(SM_CYMENU); + + int iXWindowPos = (GetSystemMetrics(SM_CXSCREEN) - iWindowWidth) / 2, + iYWindowPos = (GetSystemMetrics(SM_CYSCREEN) - iWindowHeight) / 2; + + // Create the window, exit if fail + m_Window = CreateWindow(m_Title.c_str(), + m_Title.c_str(), + WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | WS_CLIPCHILDREN, + iXWindowPos, + iYWindowPos, + iWindowWidth, + iWindowHeight, + NULL, + NULL, + m_Instance, + NULL); + + if (!m_Window) return false; + + // Show and update the window + ShowWindow(m_Window, cmdShow); + UpdateWindow(m_Window); + + return true; +} + +bool GameEngine::IsKeyDown(int vKey) const +{ + if (GetAsyncKeyState(vKey) < 0) return true; + else return false; +} + +void GameEngine::SetKeyList(const tstring& keyList) +{ + delete m_KeyListPtr; // clear list if one already exists + + // make keylist if needed + if (keyList.size() > 0) + { + m_KeyListPtr = (TCHAR*)malloc((keyList.size() + 1) * sizeof(TCHAR)); // make place for this amount of keys + 1 + + for (int count{}; count < (int)keyList.size() + 1; ++count) + { + TCHAR key = keyList.c_str()[count]; + m_KeyListPtr[count] = (key > 96 && key < 123) ? key - 32 : key; // insert the key, coverted to uppercase if supplied character is lowercase + } + } +} + +void GameEngine::SetFrameRate(int frameRate) +{ + m_FrameRate = frameRate; + m_FrameDelay = 1000 / frameRate; +} + +void GameEngine::SetWidth(int width) +{ + m_Width = width; +} + +void GameEngine::SetHeight(int height) +{ + m_Height = height; +} + +void GameEngine::Quit() +{ + PostMessage(GameEngine::GetWindow(), WM_DESTROY, NULL, NULL); +} + +bool GameEngine::MessageContinue(const tstring& message) const +{ + // MessageBox define is undef'd at begin of GameEngine.h + #ifdef UNICODE + return MessageBoxW(GetWindow(), message.c_str(), m_Title.c_str(), MB_ICONWARNING | MB_OKCANCEL) == IDOK; + #else + return MessageBoxA(GetWindow(), message.c_str(), m_Title.c_str(), MB_ICONWARNING | MB_OKCANCEL) == IDOK; + #endif +} + +void GameEngine::MessageBox(const tstring& message) const +{ + // MessageBox define is undef'd at begin of GameEngine.h + #ifdef UNICODE + MessageBoxW(GetWindow(), message.c_str(), m_Title.c_str(), MB_ICONEXCLAMATION | MB_OK); + #else + MessageBoxA(GetWindow(), message.c_str(), m_Title.c_str(), MB_ICONEXCLAMATION | MB_OK); + #endif +} + +void GameEngine::MessageBox(const TCHAR* message) const +{ + MessageBox(tstring(message)); +} + +static void CALLBACK EnumInsertChildrenProc(HWND hwnd, LPARAM lParam) +{ + std::vector* rowPtr{ reinterpret_cast*>(lParam) }; + + rowPtr->push_back(hwnd); // fill in every element in the vector +} + +void GameEngine::TabNext(HWND ChildWindow) const +{ + std::vector childWindows; + + EnumChildWindows(m_Window, (WNDENUMPROC) EnumInsertChildrenProc, (LPARAM) &childWindows); + + int position{}; + HWND temp{ childWindows[position] }; + while(temp != ChildWindow) temp = childWindows[++position]; // find the childWindow in the vector + + if (position == childWindows.size() - 1) SetFocus(childWindows[0]); + else SetFocus(childWindows[position + 1]); +} + +void GameEngine::TabPrevious(HWND ChildWindow) const +{ + std::vector childWindows; + + EnumChildWindows(m_Window, (WNDENUMPROC) EnumInsertChildrenProc, (LPARAM) &childWindows); + + int position{ (int)childWindows.size() - 1 }; + HWND temp{ childWindows[position] }; + while(temp != ChildWindow) temp = childWindows[--position]; // find the childWindow in the vector + + if (position == 0) SetFocus(childWindows[childWindows.size() - 1]); + else SetFocus(childWindows[position - 1]); +} + +void GameEngine::SetInstance(HINSTANCE hInstance) +{ + m_Instance = hInstance; +} + +void GameEngine::SetWindow(HWND hWindow) +{ + m_Window = hWindow; +} + +SIZE GameEngine::CalculateTextDimensions(const tstring& text, const Font* fontPtr) const +{ + HDC hdc = GetDC(NULL); + SelectObject(hdc, fontPtr->GetHandle()); //attach font to hdc + + SIZE size; + GetTextExtentPoint32(hdc, text.c_str(), (int) text.size(), &size); + + ReleaseDC(NULL, hdc); + + return size; +} + +SIZE GameEngine::CalculateTextDimensions(const tstring& text, const Font* fontPtr, RECT rect) const +{ + HDC hdc = GetDC(NULL); + SelectObject(hdc, fontPtr->GetHandle()); //attach font to hdc + + SIZE size; + GetTextExtentPoint32(hdc, text.c_str(), (int) text.size(), &size); + + int height = DrawText(hdc, text.c_str(), (int) text.size(), &rect, DT_CALCRECT); + + if (size.cx > rect.right - rect.left) + { + size.cx = rect.right - rect.left; + size.cy = height; + } + + ReleaseDC(NULL, hdc); + + return size; +} + +bool GameEngine::DrawLine(int x1, int y1, int x2, int y2) const +{ + if (m_IsPainting) + { + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + MoveToEx(m_HdcDraw, x1, y1, nullptr); + LineTo(m_HdcDraw, x2, y2); + MoveToEx(m_HdcDraw, 0, 0, nullptr); // reset the position - sees to it that eg. AngleArc draws from 0,0 instead of the last position of DrawLine + SelectObject(m_HdcDraw, hOldPen); + DeleteObject(hNewPen); + + return true; + } + else return false; +} + +bool GameEngine::DrawPolygon(const POINT ptsArr[], int count) const +{ + return DrawPolygon(ptsArr, count, false); +} + +bool GameEngine::DrawPolygon(const POINT ptsArr[], int count, bool close) const +{ + if (m_IsPainting) + { + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + FormPolygon(ptsArr, count, close); + + SelectObject(m_HdcDraw, hOldPen); + DeleteObject(hNewPen); + + return true; + } + else return false; +} + +bool GameEngine::FillPolygon(const POINT ptsArr[], int count) const +{ + return FillPolygon(ptsArr, count, false); +} + +bool GameEngine::FillPolygon(const POINT ptsArr[], int count, bool close) const +{ + if (m_IsPainting) + { + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + HBRUSH hOldBrush, hNewBrush = CreateSolidBrush(m_ColDraw); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + hOldBrush = (HBRUSH)SelectObject(m_HdcDraw, hNewBrush); + + BeginPath(m_HdcDraw); + + FormPolygon(ptsArr, count, close); + + EndPath(m_HdcDraw); + StrokeAndFillPath(m_HdcDraw); + + SelectObject(m_HdcDraw, hOldPen); + SelectObject(m_HdcDraw, hOldBrush); + + DeleteObject(hNewPen); + DeleteObject(hNewBrush); + + return true; + } + else return false; +} + +void GameEngine::FormPolygon(const POINT ptsArr[], int count, bool close) const +{ + if (!close) Polyline(m_HdcDraw, ptsArr, count); + else + { + POINT* newPtsArr= new POINT[count+1]; // interesting case: this code does not work with memory allocation at compile time => demo case for dynamic memory use + for (int index{}; index < count; ++index) newPtsArr[index] = ptsArr[index]; + newPtsArr[count] = ptsArr[0]; + + Polyline(m_HdcDraw, newPtsArr, count+1); + + delete[] newPtsArr; + } +} + +bool GameEngine::DrawRect(int left, int top, int width, int height) const +{ + if (m_IsPainting) + { + int right = left + width; + int bottom = top + height; + + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + POINT pts[4] = { left, top, right - 1, top, right - 1, bottom - 1, left, bottom - 1 }; + DrawPolygon(pts, 4, true); + + SelectObject(m_HdcDraw, hOldPen); + DeleteObject(hNewPen); + + return true; + } + else return false; +} + + +bool GameEngine::DrawRect(float left, float top, float width, float height) const { + return DrawRect((int)left, (int)top, (int)width, (int)height); +} + +bool GameEngine::FillRect(int left, int top, int width, int height) const +{ + if (m_IsPainting) + { + int right = left + width; + int bottom = top + height; + + HBRUSH hOldBrush, hNewBrush = CreateSolidBrush(m_ColDraw); + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + + hOldBrush = (HBRUSH)SelectObject(m_HdcDraw, hNewBrush); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + Rectangle(m_HdcDraw, left, top, right, bottom); + + SelectObject(m_HdcDraw, hOldPen); + SelectObject(m_HdcDraw, hOldBrush); + + DeleteObject(hNewPen); + DeleteObject(hNewBrush); + + return true; + } + else return false; +} + + +bool GameEngine::FillRect(float left, float top, float width, float height) const { + return FillRect((int)left, (int)top, (int)width, (int)height); +} + +bool GameEngine::FillRectO(int left, int top, int right, int bottom, int opacity) const +{ + if (m_IsPainting) + { + HDC tempHdc = CreateCompatibleDC(m_HdcDraw); + BLENDFUNCTION blend = { AC_SRC_OVER, 0, (BYTE)opacity, 0 }; + + RECT dim{}; + dim.right = right - left; + dim.bottom = bottom - top; + + // setup bitmap info + BITMAPINFO bmi{}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = dim.right; + bmi.bmiHeader.biHeight = dim.bottom; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // four 8-bit components + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = dim.right * dim.bottom * 4; + + // create our DIB section and select the bitmap into the dc + HBITMAP hbitmap = CreateDIBSection(tempHdc, &bmi, DIB_RGB_COLORS, nullptr, NULL, 0x0); + SelectObject(tempHdc, hbitmap); + + HBRUSH fillBrush = CreateSolidBrush(m_ColDraw); + ::FillRect(tempHdc, &dim, fillBrush); + + AlphaBlend(m_HdcDraw, left, top, dim.right, dim.bottom, tempHdc, dim.left, dim.top, dim.right, dim.bottom, blend); + + DeleteObject(fillBrush); + DeleteObject(hbitmap); + DeleteObject(tempHdc); + + return true; + } + else return false; +} + +bool GameEngine::DrawRoundRect(int left, int top, int right, int bottom, int radius) const +{ + if (m_IsPainting) + { + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + BeginPath(m_HdcDraw); + + RoundRect(m_HdcDraw, left, top, right, bottom, radius, radius); + + EndPath(m_HdcDraw); + StrokePath(m_HdcDraw); + + SelectObject(m_HdcDraw, hOldPen); + DeleteObject(hNewPen); + + return true; + } + else return false; +} + +bool GameEngine::FillRoundRect(int left, int top, int right, int bottom, int radius) const +{ + if (m_IsPainting) + { + HBRUSH hOldBrush, hNewBrush = CreateSolidBrush(m_ColDraw); + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + + hOldBrush = (HBRUSH)SelectObject(m_HdcDraw, hNewBrush); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + RoundRect(m_HdcDraw, left, top, right, bottom, radius, radius); + + SelectObject(m_HdcDraw, hOldPen); + SelectObject(m_HdcDraw, hOldBrush); + + DeleteObject(hNewPen); + DeleteObject(hNewBrush); + + return true; + } + else return false; +} + +bool GameEngine::DrawOval(int left, int top, int width, int height) const +{ + if (m_IsPainting) + { + int bottom = top + height; + int right = left + width; + + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + Arc(m_HdcDraw, left, top, right, bottom, left, top + (bottom - top) / 2, left, top + (bottom - top) / 2); + + SelectObject(m_HdcDraw, hOldPen); + DeleteObject(hNewPen); + + return true; + } + else return false; +} + +bool GameEngine::DrawOval(float left, float top, float width, float height) const { + return DrawOval((int)left, (int)top, (int)width, (int)height); +} + +bool GameEngine::FillOval(int left, int top, int width, int height) const +{ + if (m_IsPainting) + { + int bottom = top + height; + int right = left + width; + + HBRUSH hOldBrush, hNewBrush = CreateSolidBrush(m_ColDraw); + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + + hOldBrush = (HBRUSH)SelectObject(m_HdcDraw, hNewBrush); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + Ellipse(m_HdcDraw, left, top, right, bottom); + + SelectObject(m_HdcDraw, hOldPen); + SelectObject(m_HdcDraw, hOldBrush); + + DeleteObject(hNewPen); + DeleteObject(hNewBrush); + + return true; + } + else return false; +} + +bool GameEngine::FillOval(float left, float top, float width, float height) const { + return FillOval((int)left, (int)top, (int)width, (int)height); +} + +bool GameEngine::FillOval(int left, int top, int right, int bottom, int opacity) const +{ + if (m_IsPainting) + { + COLORREF color = m_ColDraw; + if (color == RGB(0, 0, 0)) color = RGB(0, 0, 1); + + HDC tempHdc = CreateCompatibleDC(m_HdcDraw); + + RECT dim{}; + dim.right = right - left; + dim.bottom = bottom - top; + + // setup bitmap info + BITMAPINFO bmi{}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = dim.right; + bmi.bmiHeader.biHeight = dim.bottom; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // four 8-bit components + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = dim.right * dim.bottom * 4; + + // create our DIB section and select the bitmap into the dc + int* dataPtr = nullptr; + HBITMAP hbitmap = CreateDIBSection(tempHdc, &bmi, DIB_RGB_COLORS, (void**)&dataPtr, NULL, 0x0); + SelectObject(tempHdc, hbitmap); + + memset(dataPtr, 0, dim.right * dim.bottom); + + HBRUSH hOldBrush, hNewBrush = CreateSolidBrush(color); + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, color); + + hOldBrush = (HBRUSH)SelectObject(tempHdc, hNewBrush); + hOldPen = (HPEN)SelectObject(tempHdc, hNewPen); + + Ellipse(tempHdc, 0, 0, dim.right, dim.bottom); + + for (int count{}; count < dim.right * dim.bottom; ++count) + { + if (dataPtr[count] != 0) + { + // set alpha channel and premultiply + unsigned char* pos = (unsigned char*)&(dataPtr[count]); + pos[0] = (int)pos[0] * opacity / 255; + pos[1] = (int)pos[1] * opacity / 255; + pos[2] = (int)pos[2] * opacity / 255; + pos[3] = opacity; + } + } + + SelectObject(tempHdc, hOldPen); + SelectObject(tempHdc, hOldBrush); + + BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + AlphaBlend(m_HdcDraw, left, top, dim.right, dim.bottom, tempHdc, dim.left, dim.top, dim.right, dim.bottom, blend); + + DeleteObject(hNewPen); + DeleteObject(hNewBrush); + DeleteObject(hbitmap); + DeleteObject(tempHdc); + + return true; + } + else return false; +} + +bool GameEngine::DrawArc(int left, int top, int right, int bottom, int startDegree, int angle) const +{ + if (m_IsPainting) + { + if (angle == 0) return false; + if (angle > 360) { DrawOval(left, top, right, bottom); } + else + { + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + POINT ptStart = AngleToPoint(left, top, right, bottom, startDegree); + POINT ptEnd = AngleToPoint(left, top, right, bottom, startDegree + angle); + + if (angle > 0) Arc(m_HdcDraw, left, top, right, bottom, ptStart.x, ptStart.y, ptEnd.x, ptEnd.y); + else Arc(m_HdcDraw, left, top, right, bottom, ptEnd.x, ptEnd.y, ptStart.x, ptStart.y); + + SelectObject(m_HdcDraw, hOldPen); + DeleteObject(hNewPen); + } + + return true; + } + else return false; +} + +bool GameEngine::FillArc(int left, int top, int right, int bottom, int startDegree, int angle) const +{ + if (m_IsPainting) + { + if (angle == 0) return false; + if (angle > 360) { FillOval(left, top, right, bottom); } + else + { + HBRUSH hOldBrush, hNewBrush = CreateSolidBrush(m_ColDraw); + HPEN hOldPen, hNewPen = CreatePen(PS_SOLID, 1, m_ColDraw); + + hOldBrush = (HBRUSH)SelectObject(m_HdcDraw, hNewBrush); + hOldPen = (HPEN)SelectObject(m_HdcDraw, hNewPen); + + POINT ptStart = AngleToPoint(left, top, right, bottom, startDegree); + POINT ptEnd = AngleToPoint(left, top, right, bottom, startDegree + angle); + + if (angle > 0) Pie(m_HdcDraw, left, top, right, bottom, ptStart.x, ptStart.y, ptEnd.x, ptEnd.y); + else Pie(m_HdcDraw, left, top, right, bottom, ptEnd.x, ptEnd.y, ptStart.x, ptStart.y); + + SelectObject(m_HdcDraw, hOldPen); + SelectObject(m_HdcDraw, hOldBrush); + + DeleteObject(hNewPen); + DeleteObject(hNewBrush); + } + + return true; + } + else return false; +} + +POINT GameEngine::AngleToPoint(int left, int top, int right, int bottom, int angle) const +{ + POINT pt{}; + + const int width { right - left }; + const int height{ bottom - top }; + + // if necessary adjust angle so that it has a value between 0 and 360 degrees + if (angle > 360 || angle < -360) angle = angle % 360; + if (angle < 0) angle += 360; + + // default values for standard angles + if (angle == 0) { pt.x = right; pt.y = top + (height / 2); } + else if (angle == 90) { pt.x = left + (width / 2); pt.y = top; } + else if (angle == 180) { pt.x = left; pt.y = top + (height / 2); } + else if (angle == 270) { pt.x = left + (width / 2); pt.y = top + height; } + // else calculate non-default values + else + { + // point on the ellipse = "stelsel" of the cartesian equation of the ellipse combined with y = tg(alpha) * x + // using the equation for ellipse with 0,0 in the center of the ellipse + double aSquare = pow(width / 2.0, 2); + double bSquare = pow(height / 2.0, 2); + double tangens = tan(angle * M_PI / 180); + double tanSquare = pow(tangens, 2); + + // calculate x + pt.x = (long) sqrt( aSquare * bSquare / (bSquare + tanSquare * aSquare)); + if (angle > 90 && angle < 270) pt.x *= -1; // sqrt returns the positive value of the square, take the negative value if necessary + + // calculate y + pt.y = (long) (tangens * pt.x); + pt.y *= -1; // reverse the sign because of inverted y-axis + + // offset the ellipse into the screen + pt.x += left + (width / 2); + pt.y += top + (height / 2); + } + + return pt; +} + +int GameEngine::DrawStringF(const tstring& text, int left, int top, int right, int bottom) const +{ + if (m_IsPainting) + { + if (m_FontDraw != NULL) + { + HFONT hOldFont = (HFONT)SelectObject(m_HdcDraw, m_FontDraw); + + COLORREF oldColor = SetTextColor(m_HdcDraw, m_ColDraw); + SetBkMode(m_HdcDraw, TRANSPARENT); + + RECT rc = { left, top, right - 1, bottom - 1 }; + int result = DrawText(m_HdcDraw, text.c_str(), -1, &rc, DT_WORDBREAK); + + SetBkMode(m_HdcDraw, OPAQUE); + SetTextColor(m_HdcDraw, oldColor); + + SelectObject(m_HdcDraw, hOldFont); + + return result; + } + else + { + COLORREF oldColor = SetTextColor(m_HdcDraw, m_ColDraw); + SetBkMode(m_HdcDraw, TRANSPARENT); + + RECT rc = { left, top, right - 1, bottom - 1 }; + int result = DrawText(m_HdcDraw, text.c_str(), -1, &rc, DT_WORDBREAK); + + SetBkMode(m_HdcDraw, OPAQUE); + SetTextColor(m_HdcDraw, oldColor); + + return result; + } + } + else return -1; +} + +int GameEngine::DrawString(const tstring& text, int left, int top) const +{ + if (m_IsPainting) + { + if (m_FontDraw != 0) + { + HFONT hOldFont = (HFONT)SelectObject(m_HdcDraw, m_FontDraw); + + COLORREF oldColor = SetTextColor(m_HdcDraw, m_ColDraw); + SetBkMode(m_HdcDraw, TRANSPARENT); + + int result = TextOut(m_HdcDraw, left, top, text.c_str(), (int)text.size()); + + SetBkMode(m_HdcDraw, OPAQUE); + SetTextColor(m_HdcDraw, oldColor); + + SelectObject(m_HdcDraw, hOldFont); + + return result; + } + else + { + COLORREF oldColor = SetTextColor(m_HdcDraw, m_ColDraw); + SetBkMode(m_HdcDraw, TRANSPARENT); + + int result = TextOut(m_HdcDraw, left, top, text.c_str(), (int)text.size()); + + SetBkMode(m_HdcDraw, OPAQUE); + SetTextColor(m_HdcDraw, oldColor); + + return result; + } + } + else return -1; +} + +int GameEngine::DrawString(const tstring &text, float left, float top) const { + return DrawString(text, (int)left, (int)top); +} + +bool GameEngine::DrawBitmap(const Bitmap* bitmapPtr, int left, int top, RECT rect) const +{ + if (m_IsPainting) + { + if (!bitmapPtr->Exists()) return false; + + const int opacity = bitmapPtr->GetOpacity(); + + if (opacity == 0 && bitmapPtr->HasAlphaChannel()) return true; // don't draw if opacity == 0 and opacity is used + + HDC hdcMem = CreateCompatibleDC(m_HdcDraw); + HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, bitmapPtr->GetHandle()); + + if (bitmapPtr->HasAlphaChannel()) + { + BLENDFUNCTION blender = { AC_SRC_OVER, 0, (BYTE)(2.55 * opacity), AC_SRC_ALPHA }; // blend function combines opacity and pixel based transparency + AlphaBlend(m_HdcDraw, left, top, rect.right - rect.left, rect.bottom - rect.top, hdcMem, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, blender); + } + else TransparentBlt(m_HdcDraw, left, top, rect.right - rect.left, rect.bottom - rect.top, hdcMem, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bitmapPtr->GetTransparencyColor()); + + SelectObject(hdcMem, hbmOld); + DeleteDC(hdcMem); + + return true; + } + else return false; +} + +bool GameEngine::DrawBitmap(const Bitmap* bitmapPtr, int left, int top) const +{ + if (m_IsPainting) + { + if (!bitmapPtr->Exists()) return false; + + BITMAP bm; + GetObject(bitmapPtr->GetHandle(), sizeof(bm), &bm); + RECT rect { 0, 0, bm.bmWidth, bm.bmHeight }; + + return DrawBitmap(bitmapPtr, left, top, rect); + } + else return false; +} + +bool GameEngine::FillWindowRect(COLORREF color) const +{ + if (m_IsPainting) + { + COLORREF oldColor = GetDrawColor(); + const_cast(this)->SetColor(color); + FillRect(0, 0, m_RectDraw.right, m_RectDraw.bottom); + const_cast(this)->SetColor(oldColor); + + return true; + } + else return false; +} + +COLORREF GameEngine::GetDrawColor() const +{ + return m_ColDraw; +} + +bool GameEngine::Repaint() const +{ + return InvalidateRect(m_Window, nullptr, true)?true:false; +} + +tstring GameEngine::GetTitle() const +{ + #pragma warning(disable:4244) + return m_Title; + #pragma warning(default:4244) +} + +POINT GameEngine::GetWindowPosition() const +{ + RECT info; + GetWindowRect(m_Window, &info); + + return { info.left, info.top }; +} + +void GameEngine::SetWindowPosition(int left, int top) +{ + SetWindowPos(m_Window, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + InvalidateRect(m_Window, nullptr, TRUE); +} + +void GameEngine::SetColor(COLORREF color) +{ + m_ColDraw = color; +} + +void GameEngine::SetFont(Font* fontPtr) +{ + m_FontDraw = fontPtr->GetHandle(); +} + +LRESULT GameEngine::HandleEvent(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam) +{ + // Route Windows messages to game engine member functions + switch (msg) + { + case WM_CREATE: + // Seed the random number generator + srand((unsigned int) GetTickCount64()); + + // Set the game window + SetWindow(hWindow); + + // Run user defined functions for start of the game + m_GamePtr->Start(); + + return 0; + + case WM_PAINT: + { + // Get window, rectangle and HDC + PAINTSTRUCT ps; + HDC hDC = BeginPaint(hWindow, &ps); + + PaintDoubleBuffered(hDC); + + // end paint + EndPaint(hWindow, &ps); + + return 0; + } + case WM_CTLCOLOREDIT: + return SendMessage((HWND) lParam, WM_CTLCOLOREDIT, wParam, lParam); // delegate this message to the child window + + case WM_CTLCOLORBTN: + return SendMessage((HWND) lParam, WM_CTLCOLOREDIT, wParam, lParam); // delegate this message to the child window + + case WM_LBUTTONDOWN: + m_GamePtr->MouseButtonAction(true, true, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), wParam); + m_mouseLeftDown = true; + return 0; + + case WM_LBUTTONUP: + m_GamePtr->MouseButtonAction(true, false, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), wParam); + m_mouseLeftDown = false; + return 0; + + case WM_RBUTTONDOWN: + m_GamePtr->MouseButtonAction(false, true, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), wParam); + m_mouseRightDown = true; + return 0; + + case WM_RBUTTONUP: + m_GamePtr->MouseButtonAction(false, false, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), wParam); + m_mouseRightDown = false; + return 0; + case WM_MOUSEWHEEL: + m_GamePtr->MouseWheelAction(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (short) HIWORD(wParam), wParam); + return 0; + + case WM_MOUSEMOVE: + m_mouseX = GET_X_LPARAM(lParam); + m_mouseY = GET_Y_LPARAM(lParam); + m_GamePtr->MouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), wParam); + return 0; + + case WM_SYSCOMMAND: // trapping this message prevents a freeze after the ALT key is released + if (wParam == SC_KEYMENU) return 0; // see win32 API : WM_KEYDOWN + else break; + + case WM_DESTROY: + // User defined code for ending the game + m_GamePtr->End(); + + // End and exit the application + PostQuitMessage(0); + + return 0; + + } + return DefWindowProc(hWindow, msg, wParam, lParam); +} + +void GameEngine::SetColorRGB(ColorRGB color) { + SetColor(RGB(color.r, color.g, color.b)); +} + +bool GameEngine::FillWindowRectRGB(ColorRGB color) const { + return FillWindowRect(RGB(color.r, color.g, color.b)); +} + +int GameEngine::GetMouseX() { + return m_mouseX; +} + +int GameEngine::GetMouseY() { + return m_mouseY; +} + +bool GameEngine::IsMouseLeftDown() { + return m_mouseLeftDown; +} + +bool GameEngine::IsMouseRightDown() { + return m_mouseRightDown; +} + +int GameEngine::GetControllersConnected() const { + DWORD result; + int count = 0; + for (DWORD i=0; i< XUSER_MAX_COUNT; i++ ) { + XINPUT_STATE state; + ZeroMemory( &state, sizeof(XINPUT_STATE) ); + + // Simply get the state of the controller from XInput. + result = XInputGetState( i, &state ); + + if( result == ERROR_SUCCESS ) { + count++; + } else { + + } + } + return count; +} + +std::string GameEngine::GetRequest(std::string url) { + cpr::Response r = cpr::Get(cpr::Url{std::move(url)}, cpr::Timeout{5000}); + if( r.error.code == cpr::ErrorCode::OPERATION_TIMEDOUT || r.status_code != 200) { + return "Error: Request timed out"; + } + + return r.text; +} + +std::string GameEngine::PostRequest(std::string url, std::string data) { + cpr::Response r = cpr::Post( + cpr::Url{std::move(url)}, + cpr::Body{std::move(data)}, + cpr::Header{{"Content-Type", "application/json"}}, + cpr::Timeout{5000} // Timeout in milliseconds (5 seconds) + ); + + if (r.error.code == cpr::ErrorCode::OPERATION_TIMEDOUT || r.status_code != 200) { + return "Error: Request timed out"; + } + + return r.text; +} + +const char *GameEngine::AskString() { + static char userInput[256] = {0}; + + WNDCLASS wc = {0}; + wc.lpfnWndProc = [](HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT + { + static HWND hEdit; + switch (msg) + { + case WM_CREATE: + hEdit = CreateWindow("EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, + 20, 20, 200, 25, hWnd, (HMENU)1, NULL, NULL); + CreateWindow("BUTTON", "OK", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + 80, 60, 80, 30, hWnd, (HMENU)2, NULL, NULL); + break; + + case WM_COMMAND: + if (LOWORD(wParam) == 2) // OK Button Clicked + { + GetWindowText(hEdit, userInput, sizeof(userInput)); + DestroyWindow(hWnd); + } + break; + + case WM_CLOSE: + DestroyWindow(hWnd); + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, msg, wParam, lParam); + } + return 0; + }; + + wc.hInstance = m_Instance; + wc.lpszClassName = "SimpleInputDialog"; + RegisterClass(&wc); + + HWND hWnd = CreateWindow("SimpleInputDialog", "Enter Your Name", + WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME, + CW_USEDEFAULT, CW_USEDEFAULT, 250, 150, + NULL, NULL, wc.hInstance, NULL); + + ShowWindow(hWnd, SW_SHOW); + UpdateWindow(hWnd); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + if (msg.message == WM_QUIT) break; // Exit loop when window closes + } + + return userInput; // Return the entered text +} + + + +//----------------------------------------------------------------- +// Caller Member Functions +//----------------------------------------------------------------- +bool Caller::AddActionListener(Callable* targetPtr) +{ + return AddListenerObject(targetPtr); +} + +bool Caller::RemoveActionListener(const Callable* targetPtr) +{ + return RemoveListenerObject(targetPtr); +} + +class CallAllActions +{ +public: + CallAllActions(Caller* callerPtr) : m_CallerPtr(callerPtr) + {} + + void operator()(Callable* callablePtr) + { + callablePtr->CallAction(m_CallerPtr); + } + +private: + Caller* m_CallerPtr; +}; + +bool Caller::CallListeners() +{ + for_each(m_TargetList.begin(), m_TargetList.end(), CallAllActions(this)); + + return (m_TargetList.size() > 0); +} + +bool Caller::AddListenerObject(Callable* targetPtr) +{ + vector::iterator pos = find(m_TargetList.begin(), m_TargetList.end(), targetPtr); + + if (pos != m_TargetList.end()) return false; + + m_TargetList.push_back(targetPtr); + + return true; +} + +bool Caller::RemoveListenerObject(const Callable* targetPtr) +{ + vector::iterator pos = find(m_TargetList.begin(), m_TargetList.end(), targetPtr); + + if (pos == m_TargetList.end()) return false; + + m_TargetList.erase(pos); + + return true; +} + +//----------------------------------------------------------------- +// Bitmap Member Functions +//----------------------------------------------------------------- +// set static datamember to zero +int Bitmap::m_Nr = 0; +// +//Bitmap::Bitmap(HBITMAP hBitmap) : m_TransparencyKey(-1), m_Opacity(100), m_Exists(true), m_IsTarga(false), m_HasAlphaChannel(false), m_hBitmap(hBitmap) +//{ +// // nothing to create +//} + +Bitmap::Bitmap(const tstring& filename, bool createAlphaChannel) : m_HasAlphaChannel(createAlphaChannel) +{ + { // separate block => the file stream will close + tifstream testExists(filename); + if (!testExists.good()) throw FileNotFoundException{filename}; + } + + size_t len{ filename.length() }; + if (len < 5) throw BadFilenameException{ filename }; + + tstring suffix{ filename.substr(len - 4) }; + + // check if the file to load is a png + if (suffix == _T(".png")) + { + m_hBitmap = LoadPNG((TCHAR*) filename.c_str()); + + if (!m_hBitmap) throw CouldNotLoadFileException{ filename }; + } + // else load as bitmap + else if (suffix == _T(".bmp")) + { + m_hBitmap = (HBITMAP) LoadImage(GAME_ENGINE->GetInstance(), filename.c_str(), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + + if (!m_hBitmap) throw CouldNotLoadFileException{ filename }; + + if (createAlphaChannel) CreateAlphaChannel(); + } + else throw UnsupportedFormatException{ filename }; +} + +/* +Bitmap::Bitmap(int IDBitmap, const tstring& type, bool createAlphaChannel): m_TransparencyKey(-1), m_Opacity(100), m_Exists(false) +{ + if (type == _T("BITMAP")) + { + m_IsTarga = false; + m_HasAlphaChannel = createAlphaChannel; + + m_hBitmap = LoadBitmap(GAME_ENGINE->GetInstance(), MAKEINTRESOURCE(IDBitmap)); + + if (m_hBitmap != 0) m_Exists = true; + + if (createAlphaChannel) CreateAlphaChannel(); + } + //else if (type == _T("TGA")) + //{ + // m_IsTarga = true; + // m_HasAlphaChannel = true; + + // tstringstream buffer; + // buffer << "temp\\targa"; + // buffer << m_Nr++; + // buffer << ".tga"; + + // tstring fileName = buffer.str(); + + // Extract(IDBitmap, _T("TGA"), fileName); + + // TargaLoader* targa = new TargaLoader(); + + // if (targa->Load((TCHAR*) fileName.c_str()) == 1) + // { + // m_hBitmap = CreateBitmap(targa->GetWidth(), targa->GetHeight(), 1, targa->GetBPP(), (void*)targa->GetImg()); + // if (m_hBitmap != 0) m_Exists = true; + // } + // + // delete targa; + // + // CreateAlphaChannel(); + //} +} +*/ + +HBITMAP Bitmap::LoadPNG(TCHAR* pFilePath) +{ + Gdiplus::Bitmap* bitmapPtr = Gdiplus::Bitmap::FromFile(reinterpret_cast(pFilePath), false); + + HBITMAP hBitmap{}; + + if (bitmapPtr) + { + bitmapPtr->GetHBITMAP(Gdiplus::Color(0, 0, 0), &hBitmap); + delete bitmapPtr; + } + + return hBitmap; +} + +void Bitmap::CreateAlphaChannel() +{ + BITMAPINFOHEADER bminfoheader{}; + bminfoheader.biSize = sizeof(BITMAPINFOHEADER); + bminfoheader.biWidth = GetWidth(); + bminfoheader.biHeight = GetHeight(); + bminfoheader.biPlanes = 1; + bminfoheader.biBitCount = 32; + bminfoheader.biCompression = BI_RGB; + + HDC windowDC = GetWindowDC(GAME_ENGINE->GetWindow()); + m_PixelsPtr = new unsigned char[this->GetWidth() * this->GetHeight() * 4]; + + GetDIBits(windowDC, m_hBitmap, 0, GetHeight(), m_PixelsPtr, (BITMAPINFO*)&bminfoheader, DIB_RGB_COLORS); // load pixel info + + // add alpha channel values of 255 for every pixel if bmp + for (int count{}; count < GetWidth() * GetHeight(); ++count) + { + m_PixelsPtr[count * 4 + 3] = 255; + } + + SetDIBits(windowDC, m_hBitmap, 0, GetHeight(), m_PixelsPtr, (BITMAPINFO*)&bminfoheader, DIB_RGB_COLORS); +} + +/* +void Bitmap::Premultiply() // Multiply R, G and B with Alpha +{ + //Note that the APIs use premultiplied alpha, which means that the red, + //green and blue channel values in the bitmap must be premultiplied with + //the alpha channel value. For example, if the alpha channel value is x, + //the red, green and blue channels must be multiplied by x and divided by + //0xff prior to the call. + + unsigned long Index,nPixels; + unsigned char *bCur; + short iPixelSize; + + // Set ptr to start of image + bCur=pImage; + + // Calc number of pixels + nPixels=width*height; + + // Get pixel size in bytes + iPixelSize=iBPP/8; + + for(index{};index!=nPixels;++index) // For each pixel + { + + *bCur=(unsigned char)((int)*bCur* (int)*(bCur+3)/0xff); + *(bCur+1)=(unsigned char)((int)*(bCur+1)* (int)*(bCur+3)/0xff); + *(bCur+2)=(unsigned char)((int)*(bCur+2)* (int)*(bCur+3)/0xff); + + bCur+=iPixelSize; // Jump to next pixel + } +} +*/ + +Bitmap::~Bitmap() +{ + if (HasAlphaChannel()) + { + delete[] m_PixelsPtr; + } + + DeleteObject(m_hBitmap); +} + +bool Bitmap::Exists() const +{ + return m_hBitmap ? true : false; +} + +void Bitmap::Extract(WORD id, const tstring& type, const tstring& fileName) const +{ + CreateDirectory(_T("temp\\"), nullptr); + + HRSRC hrsrc = FindResource(NULL, MAKEINTRESOURCE(id), type.c_str()); + HGLOBAL hLoaded = LoadResource(NULL, hrsrc); + LPVOID lpLock = LockResource(hLoaded); + DWORD dwSize = SizeofResource(NULL, hrsrc); + HANDLE hFile = CreateFile(fileName.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + DWORD dwByteWritten; + WriteFile(hFile, lpLock , dwSize , &dwByteWritten , NULL); + CloseHandle(hFile); + FreeResource(hLoaded); +} + +HBITMAP Bitmap::GetHandle() const +{ + return m_hBitmap; +} + +int Bitmap::GetWidth() const +{ + if (!Exists()) return 0; + + BITMAP bm; + GetObject(m_hBitmap, sizeof(bm), &bm); + + return bm.bmWidth; +} + +int Bitmap::GetHeight() const +{ + if (!Exists()) return 0; + + BITMAP bm; + GetObject(m_hBitmap, sizeof(bm), &bm); + + return bm.bmHeight; +} + +void Bitmap::SetTransparencyColor(COLORREF color) // converts transparency value to pixel-based alpha +{ + m_TransparencyKey = color; + + if (HasAlphaChannel()) + { + const int width { GetWidth() }; + const int height{ GetHeight() }; + + BITMAPINFOHEADER bminfoheader{}; + bminfoheader.biSize = sizeof(BITMAPINFOHEADER); + bminfoheader.biWidth = width; + bminfoheader.biHeight = height; + bminfoheader.biPlanes = 1; + bminfoheader.biBitCount = 32; + bminfoheader.biCompression = BI_RGB; + + HDC windowDC = GetWindowDC(GAME_ENGINE->GetWindow()); + + unsigned char* newPixelsPtr = new unsigned char[width * height * 4]; // create 32 bit buffer + + for (int count{}; count < width * height; ++count) + { + if (RGB(m_PixelsPtr[count * 4 + 2], m_PixelsPtr[count * 4 + 1], m_PixelsPtr[count * 4]) == color) // if the color of this pixel == transparency color + { + ((int*) newPixelsPtr)[count] = 0; // set all four values to zero, this assumes sizeof(int) == 4 on this system + // setting values to zero means premultiplying the RGB values to an alpha of 0 + } + else ((int*) newPixelsPtr)[count] = ((int*) m_PixelsPtr)[count]; // copy all four values from m_PixelsPtr to NewPixels + } + + SetDIBits(windowDC, m_hBitmap, 0, height, newPixelsPtr, (BITMAPINFO*) &bminfoheader, DIB_RGB_COLORS); // insert pixels into bitmap + + delete[] newPixelsPtr; //destroy buffer + + ReleaseDC(GAME_ENGINE->GetWindow(), windowDC); // release DC + } +} + +COLORREF Bitmap::GetTransparencyColor() const +{ + return m_TransparencyKey; +} + +void Bitmap::SetOpacity(int opacity) +{ + if (HasAlphaChannel()) + { + if (opacity > 100) m_Opacity = 100; + else if (opacity < 0) m_Opacity = 0; + else m_Opacity = opacity; + } +} + +int Bitmap::GetOpacity() const +{ + return m_Opacity; +} + +//bool Bitmap::IsTarga() const +//{ +// return m_IsTarga; +//} + +bool Bitmap::HasAlphaChannel() const +{ + return m_HasAlphaChannel; +} + +bool Bitmap::SaveToFile(const tstring& filename) const +{ + bool result{ true }; + + HDC hScreenDC = CreateDC(TEXT("DISPLAY"), nullptr, nullptr, nullptr); + + const int width { GetWidth() }; + const int height{ GetHeight() }; + + BYTE* dataPtr = new BYTE[width * height * 4]; + + BITMAPINFO bmi{}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = width * height * 4; + + GetDIBits(hScreenDC, m_hBitmap, 0, width, dataPtr, (BITMAPINFO*) &bmi, DIB_RGB_COLORS); // load pixel info + + const DWORD size{ bmi.bmiHeader.biSizeImage }; + + BITMAPFILEHEADER bfh{}; + bfh.bfType = ('M' << 8) + 'B'; + bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + bfh.bfSize = size + bfh.bfOffBits; + + HANDLE hFile = ::CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) result = false; + else + { + DWORD dw; + WriteFile(hFile, &bfh, sizeof(bfh), &dw, 0); + WriteFile(hFile, &bmi.bmiHeader, sizeof(BITMAPINFOHEADER), &dw, 0); + WriteFile(hFile, dataPtr, size, &dw, 0); + CloseHandle(hFile); + } + + delete[] dataPtr; + + DeleteDC(hScreenDC); + + return result; +} + +//----------------------------------------------------------------- +// Audio Member Functions +//----------------------------------------------------------------- + +// set static datamember to zero +int Audio::m_Nr = 0; + +#pragma warning(disable:4311) +#pragma warning(disable:4312) +Audio::Audio(const tstring& filename) +{ + { // separate block => the file stream will close + tifstream testExists(filename); + if (!testExists.good()) throw FileNotFoundException{ filename }; + } + + size_t len{ filename.length() }; + if (len < 5) throw BadFilenameException{ filename }; + + tstring suffix{ filename.substr(len - 4) }; + if (suffix == _T(".mp3") || suffix == _T(".wav") || suffix == _T(".mid")) + { + tstringstream buffer; + buffer << _T("audio"); + buffer << m_Nr++; + + m_Alias = buffer.str(); + m_Filename = filename; + + Create(filename); + } + else throw UnsupportedFormatException{ filename }; +} + +//Audio::Audio(int IDAudio, const tstring& type) +//{ +// if (type == _T("MP3") || type == _T("WAV") || type == _T("MID")) +// { +// tstringstream buffer; +// buffer << _T("audio"); +// buffer << m_Nr++; +// +// m_Alias = buffer.str(); +// m_Filename = tstring(_T("temp\\")) + m_Alias; +// +// if (type == _T("MP3")) m_Filename += _T(".mp3"); +// else if (type == _T("WAV")) m_Filename += _T(".wav"); +// else m_Filename += _T(".mid"); +// +// Extract(IDAudio, tstringType, m_Filename); +// +// Create(m_Filename); +// } +//} + +void Audio::Create(const tstring& filename) +{ + size_t len{ filename.length() }; + ASSERT(len > 4, _T("Audio name length must be longer than 4 characters!")); + + TCHAR response[100]; + tstringstream buffer; + + tstring suffix{ filename.substr(len - 4) }; + if (suffix == _T(".mp3")) + { + buffer << _T("open \"") + m_Filename + _T("\" type mpegvideo alias "); + buffer << m_Alias; + } + else if (suffix == _T(".wav")) + { + buffer << _T("open \"") + m_Filename + _T("\" type waveaudio alias "); + buffer << m_Alias; + } + else if (suffix == _T(".mid")) + { + buffer << _T("open \"") + m_Filename + _T("\" type sequencer alias "); + buffer << m_Alias; + } + + int result = mciSendString(buffer.str().c_str(), nullptr, 0, NULL); + if (result != 0) return; + + buffer.str(_T("")); + buffer << _T("set ") + m_Alias + _T(" time format milliseconds"); + mciSendString(buffer.str().c_str(), nullptr, 0, NULL); + + buffer.str(_T("")); + buffer << _T("status ") + m_Alias + _T(" length"); + mciSendString(buffer.str().c_str(), response, 100, NULL); + + buffer.str(_T("")); + buffer << response; + buffer >> m_Duration; + + // Create a window to catch the MM_MCINOTIFY message with + m_hWnd = CreateWindow(TEXT("STATIC"), TEXT(""), NULL, 0, 0, 0, 0, NULL, NULL, GAME_ENGINE->GetInstance(), NULL); + SetWindowLongPtr(m_hWnd, GWLA_WNDPROC, (LONG_PTR) AudioProcStatic); // set the custom message loop (subclassing) + SetWindowLongPtr(m_hWnd, GWLA_USERDATA, (LONG_PTR) this); // set this object as the parameter for the Proc +} + +void Audio::Extract(WORD id , const tstring& type, const tstring& filename) const +{ + CreateDirectory(TEXT("temp\\"), nullptr); + + HRSRC hrsrc = FindResource(NULL, MAKEINTRESOURCE(id), type.c_str()); + HGLOBAL hLoaded = LoadResource( NULL, hrsrc); + LPVOID lpLock = LockResource(hLoaded); + DWORD dwSize = SizeofResource(NULL, hrsrc); + HANDLE hFile = CreateFile(filename.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + DWORD dwByteWritten; + WriteFile(hFile, lpLock, dwSize, &dwByteWritten, nullptr); + CloseHandle(hFile); + FreeResource(hLoaded); +} + +#pragma warning(default:4311) +#pragma warning(default:4312) + +Audio::~Audio() +{ + Stop(); + + tstring sendString = tstring(_T("close ")) + m_Alias; + mciSendString(sendString.c_str(), nullptr, 0, NULL); + + // release the window resources if necessary + if (m_hWnd) + { + DestroyWindow(m_hWnd); + } +} + +void Audio::Play(int msecStart, int msecStop) +{ + if (!m_Playing) + { + m_Playing = true; + m_Paused = false; + + if (msecStop == -1) QueuePlayCommand(msecStart); + else QueuePlayCommand(msecStart, msecStop); + } + else if (m_Paused) + { + m_Paused = false; + + QueueResumeCommand(); + } +} + +void Audio::Pause() +{ + if (m_Playing && !m_Paused) + { + m_Paused = true; + + QueuePauseCommand(); + } +} + +void Audio::Stop() +{ + if (m_Playing) + { + m_Playing = false; + m_Paused = false; + + QueueStopCommand(); + } +} + +void Audio::QueuePlayCommand(int msecStart) +{ + tstringstream buffer; + buffer << _T("play ") + m_Alias + _T(" from "); + buffer << msecStart; + buffer << _T(" notify"); + + QueueCommand(buffer.str()); +} + +void Audio::QueuePlayCommand(int msecStart, int msecStop) +{ + tstringstream buffer; + buffer << _T("play ") + m_Alias + _T(" from "); + buffer << msecStart; + buffer << _T(" to "); + buffer << msecStop; + buffer << _T(" notify"); + + QueueCommand(buffer.str()); +} + +void Audio::QueuePauseCommand() +{ + QueueCommand(_T("pause ") + m_Alias); +} + +void Audio::QueueResumeCommand() +{ + QueueCommand(_T("resume ") + m_Alias); +} + +void Audio::QueueStopCommand() +{ + QueueCommand(_T("stop ") + m_Alias); +} + +void Audio::QueueVolumeCommand(int volume) +{ + tstringstream buffer; + buffer << _T("setaudio ") + m_Alias + _T(" volume to "); + buffer << volume * 10; + + QueueCommand(buffer.str()); +} + +void Audio::QueueCommand(const tstring& command) +{ + m_CommandQueue.push(command); +} + +void Audio::Tick() +{ + if (!m_CommandQueue.empty()) + { + SendMCICommand(m_CommandQueue.front()); + m_CommandQueue.pop(); + } +} + +void Audio::SendMCICommand(const tstring& command) +{ + mciSendString(command.c_str(), nullptr, 0, m_hWnd); +} + +const tstring& Audio::GetName() const +{ + return m_Filename; +} + +const tstring& Audio::GetAlias() const +{ + return m_Alias; +} + +bool Audio::IsPlaying() const +{ + return m_Playing; +} + +bool Audio::IsPaused() const +{ + return m_Paused; +} + +void Audio::SwitchPlayingOff() +{ + m_Playing = false; + m_Paused = false; +} + +void Audio::SetRepeat(bool repeat) +{ + m_MustRepeat = repeat; +} + +bool Audio::GetRepeat() const +{ + return m_MustRepeat; +} + +int Audio::GetDuration() const +{ + return m_Duration; +} + +void Audio::SetVolume(int volume) +{ + m_Volume = min(100, max(0, volume)); // values below 0 and above 100 are trimmed to 0 and 100, respectively + + QueueVolumeCommand(volume); +} + +int Audio::GetVolume() const +{ + return m_Volume; +} + +bool Audio::Exists() const +{ + return m_hWnd?true:false; +} + +Caller::Type Audio::GetType() const +{ + return Caller::Type::Audio; +} + +LRESULT Audio::AudioProcStatic(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + #pragma warning(disable: 4312) + Audio* audioPtr = reinterpret_cast(GetWindowLongPtr(hWnd, GWLA_USERDATA)); + #pragma warning(default: 4312) + + switch (msg) + { + case MM_MCINOTIFY: // message received when an audio file has finished playing - used for repeat function + + if (wParam == MCI_NOTIFY_SUCCESSFUL && audioPtr->IsPlaying()) + { + audioPtr->SwitchPlayingOff(); + + if (audioPtr->GetRepeat()) audioPtr->Play(); // repeat the audio + else audioPtr->CallListeners(); // notify listeners that the audio file has stopped + } + } + return 0; +} + +//----------------------------------------------------------------- +// TextBox Member Functions +//----------------------------------------------------------------- + +#pragma warning(disable:4311) +#pragma warning(disable:4312) +TextBox::TextBox(const tstring& text) +{ + // Create the edit box + m_WndEdit = CreateWindow(_T("EDIT"), text.c_str(), WS_BORDER | WS_CHILD | WS_CLIPSIBLINGS | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, 0, 0, 0, 0, GAME_ENGINE->GetWindow(), NULL, GAME_ENGINE->GetInstance(), nullptr); + + // Set the new WNDPROC for the edit box, and store old one + m_ProcOldEdit = (WNDPROC) SetWindowLongPtr(m_WndEdit, GWLA_WNDPROC, (LONG_PTR) EditProcStatic); + + // Set this object as userdata for the static wndproc function of the edit box so that it can call members + SetWindowLongPtr(m_WndEdit, GWLA_USERDATA, (LONG_PTR) this); + + // Set to a default position + SetBounds(m_Bounds.left, m_Bounds.top, m_Bounds.right, m_Bounds.bottom); + + // Show by default + Show(); +} + +TextBox::TextBox() : TextBox(_T("")) +{} +#pragma warning(default:4311) +#pragma warning(default:4312) + +TextBox::~TextBox() +{ + // release the background brush if necessary + if (m_BgColorBrush != NULL) + { + DeleteObject(m_BgColorBrush); + } + + // release the font if necessary + if (m_Font != NULL) + { + SelectObject(GetDC(m_WndEdit), m_OldFont); + DeleteObject(m_Font); + } + + // release the window resources + DestroyWindow(m_WndEdit); +} + +void TextBox::SetBounds(int left, int top, int width, int height) +{ + m_Bounds = {left, top, left + width, top + height }; + + MoveWindow(m_WndEdit, left, top, width, height, true); +} + +RECT TextBox::GetBounds() const +{ + return m_Bounds; +} + +Caller::Type TextBox::GetType() const +{ + return Caller::Type::TextBox; +} + +void TextBox::SetEnabled(bool enable) +{ + EnableWindow(m_WndEdit, enable); +} + +void TextBox::Update() +{ + UpdateWindow(m_WndEdit); +} + +void TextBox::Show() +{ + // Show and update the edit box + ShowWindow(m_WndEdit, SW_SHOW); + + Update(); +} + +void TextBox::Hide() +{ + // Show and update the edit box + ShowWindow(m_WndEdit, SW_HIDE); + + Update(); +} + +tstring TextBox::GetText() const +{ + int textLength = (int) SendMessage(m_WndEdit, WM_GETTEXTLENGTH, NULL, NULL); + + TCHAR* bufferPtr = new TCHAR[textLength + 1]; + + SendMessage(m_WndEdit, (UINT) WM_GETTEXT, (WPARAM) (textLength + 1), (LPARAM) bufferPtr); + + tstring newString(bufferPtr); + + delete[] bufferPtr; + + return newString; +} + +void TextBox::SetText(const tstring& text) +{ + SendMessage(m_WndEdit, WM_SETTEXT, NULL, (LPARAM) text.c_str()); +} + +void TextBox::SetFont(const tstring& fontName, bool bold, bool italic, bool underline, int size) +{ + LOGFONT ft{}; + + for (int counter{}; counter < (int)fontName.size() && counter < LF_FACESIZE; ++counter) + { + ft.lfFaceName[counter] = fontName[counter]; + } + + ft.lfUnderline = underline ? 1 : 0; + ft.lfHeight = size; + ft.lfWeight = bold ? FW_BOLD : 0; + ft.lfItalic = italic ? 1 : 0; + + // clean up if another custom font was already in place + if (m_Font != NULL) { DeleteObject(m_Font); } + + // create the font + m_Font = CreateFontIndirect(&ft); + + // set the font + SendMessage(m_WndEdit, WM_SETFONT, (WPARAM) m_Font, NULL); + + // redraw the textbox + Repaint(); +} + +void TextBox::SetForecolor( COLORREF color ) +{ + m_ForeColor = color; + + Repaint(); +} + +void TextBox::SetBackcolor( COLORREF color ) +{ + m_BgColor = color; + + if (m_BgColorBrush != 0) DeleteObject(m_BgColorBrush); + m_BgColorBrush = CreateSolidBrush( color ); + + Repaint(); +} + +void TextBox::Repaint() +{ + InvalidateRect(m_WndEdit, nullptr, true); +} + +COLORREF TextBox::GetForecolor() const +{ + return m_ForeColor; +} + +COLORREF TextBox::GetBackcolor() const +{ + return m_BgColor; +} + +HBRUSH TextBox::GetBackcolorBrush() const +{ + return m_BgColorBrush; +} + +LRESULT TextBox::EditProcStatic(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + #pragma warning(disable: 4312) + return reinterpret_cast(GetWindowLongPtr(hWnd, GWLA_USERDATA))->EditProc(hWnd, msg, wParam, lParam); + #pragma warning(default: 4312) +} + +LRESULT TextBox::EditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_CTLCOLOREDIT: + SetBkColor((HDC) wParam, GetBackcolor() ); + SetTextColor((HDC) wParam, GetForecolor() ); + + return (LRESULT) GetBackcolorBrush(); + + case WM_CHAR: + if (wParam == VK_TAB || wParam == VK_RETURN) return 0; + break; + + case WM_KEYDOWN : + switch (wParam) + { + case VK_TAB: + if (GAME_ENGINE->IsKeyDown(VK_SHIFT)) GAME_ENGINE->TabPrevious(hWnd); + else GAME_ENGINE->TabNext(hWnd); + return 0; + case VK_ESCAPE: + SetFocus(GetParent(hWnd)); + return 0; + case VK_RETURN: + CallListeners(); + break; + } + } + return CallWindowProc(m_ProcOldEdit, hWnd, msg, wParam, lParam); +} + +//----------------------------------------------------------------- +// Button Member Functions +//----------------------------------------------------------------- + +#pragma warning(disable:4311) +#pragma warning(disable:4312) +Button::Button(const tstring& label) +{ + // Create the button object + m_WndButton = CreateWindow(_T("BUTTON"), label.c_str(), WS_BORDER | WS_CHILD | WS_CLIPSIBLINGS | WS_TABSTOP | BS_PUSHBUTTON, 0, 0, 0, 0, GAME_ENGINE->GetWindow(), NULL, GAME_ENGINE->GetInstance(), nullptr); + + // Set de new WNDPROC for the button, and store the old one + m_ProcOldButton = (WNDPROC) SetWindowLongPtr(m_WndButton, GWLA_WNDPROC, (LONG_PTR) ButtonProcStatic); + + // Store 'this' as data for the Button object so that the static PROC can call the member proc + SetWindowLongPtr(m_WndButton, GWLA_USERDATA, (LONG_PTR) this); + + // Set to a default position + SetBounds(m_Bounds.left, m_Bounds.top, m_Bounds.right, m_Bounds.bottom); + + // Show by default + Show(); +} + +Button::Button() : Button(_T("")) +{} +#pragma warning(default:4311) +#pragma warning(default:4312) + +Button::~Button() +{ + // release the font if necessary + if (m_Font != 0) + { + SelectObject(GetDC(m_WndButton), m_OldFont); + DeleteObject(m_Font); + } + + // release the window resource + DestroyWindow(m_WndButton); +} + +void Button::SetBounds(int left, int top, int right, int bottom) +{ + m_Bounds = { left, top, right, bottom }; + + MoveWindow(m_WndButton, left, top, right - left, bottom - top, true); +} + +RECT Button::GetBounds() const +{ + return m_Bounds; +} + +void Button::SetEnabled(bool enable) +{ + EnableWindow(m_WndButton, enable); +} + +void Button::Update() +{ + UpdateWindow(m_WndButton); +} + +void Button::Show() +{ + // Show and update the button + ShowWindow(m_WndButton, SW_SHOW); + + Update(); +} + +void Button::Hide() +{ + // Show and update the button + ShowWindow(m_WndButton, SW_HIDE); + + Update(); +} + +tstring Button::GetText() const +{ + int textLength = (int) SendMessage(m_WndButton, WM_GETTEXTLENGTH, NULL, NULL); + + TCHAR* bufferPtr = new TCHAR[textLength + 1]; + + SendMessage(m_WndButton, WM_GETTEXT, (WPARAM) (textLength + 1), (LPARAM) bufferPtr); + + tstring newString(bufferPtr); + + delete[] bufferPtr; + + return newString; +} + +Caller::Type Button::GetType() const +{ + return Caller::Type::Button; +} + +void Button::SetText(const tstring& text) +{ + SendMessage(m_WndButton, WM_SETTEXT, NULL, (LPARAM) text.c_str()); +} + +void Button::SetFont(const tstring& fontName, bool bold, bool italic, bool underline, int size) +{ + LOGFONT ft{}; + + for (int counter{}; counter < (int)fontName.size() && counter < LF_FACESIZE; ++counter) + { + ft.lfFaceName[counter] = fontName[counter]; + } + + ft.lfUnderline = underline ? 1 : 0; + ft.lfHeight = size; + ft.lfWeight = bold ? FW_BOLD : 0; + ft.lfItalic = italic ? 1 : 0; + + // clean up if another custom font was already in place + if (m_Font != NULL) { DeleteObject(m_Font); } + + // create the new font. The WM_CTLCOLOREDIT message will set the font when the button is about to redraw + m_Font = CreateFontIndirect(&ft); + + // redraw the button + InvalidateRect(m_WndButton, nullptr, true); +} + +LRESULT Button::ButtonProcStatic(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + #pragma warning(disable: 4312) + return reinterpret_cast(GetWindowLongPtr(hWnd, GWLA_USERDATA))->ButtonProc(hWnd, msg, wParam, lParam); + #pragma warning(default: 4312) +} + +LRESULT Button::ButtonProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_CTLCOLOREDIT: + if (m_Font != NULL) + { + if (m_OldFont == NULL) m_OldFont = (HFONT) SelectObject((HDC) wParam, m_Font); + else SelectObject((HDC) wParam, m_Font); + } + return 0; + + case WM_CHAR: + if (wParam == VK_TAB || wParam == VK_RETURN) return 0; + break; + + case WM_KEYDOWN : + switch (wParam) + { + case VK_TAB: + if (GAME_ENGINE->IsKeyDown(VK_SHIFT)) GAME_ENGINE->TabPrevious(hWnd); + else GAME_ENGINE->TabNext(hWnd); + return 0; + case VK_ESCAPE: + SetFocus(GetParent(hWnd)); + return 0; + case VK_SPACE: + CallListeners(); + break; + } + break; + case WM_LBUTTONDOWN : + case WM_LBUTTONDBLCLK: // clicking fast will throw LBUTTONDBLCLK's as well as LBUTTONDOWN's, you need to capture both to catch all button clicks + m_Armed = true; + break; + case WM_LBUTTONUP : + if (m_Armed) + { + RECT rc; + GetWindowRect(hWnd, &rc); + + POINT pt; + GetCursorPos(&pt); + + if (PtInRect(&rc, pt)) CallListeners(); + + m_Armed = false; + } + } + return CallWindowProc(m_ProcOldButton, hWnd, msg, wParam, lParam); +} + +//----------------------------------------------------------------- +// Timer Member Functions +//----------------------------------------------------------------- + +Timer::Timer(int msec, Callable* targetPtr, bool repeat) : m_Delay{ msec }, m_MustRepeat{repeat} +{ + AddActionListener(targetPtr); +} + +Timer::~Timer() +{ + if (m_IsRunning) Stop(); // stop closes the handle +} + +void Timer::Start() +{ + if (m_IsRunning == false) + { + CreateTimerQueueTimer(&m_TimerHandle, NULL, TimerProcStatic, (void*) this, m_Delay, m_Delay, WT_EXECUTEINTIMERTHREAD); + m_IsRunning = true; + } +} + +void Timer::Stop() +{ + if (m_IsRunning == true) + { + DeleteTimerQueueTimer(NULL, m_TimerHandle, NULL); + //CloseHandle (m_TimerHandle); DeleteTimerQueueTimer automatically closes the handle? MSDN Documentation seems to suggest this + + m_IsRunning = false; + } +} + +bool Timer::IsRunning() const +{ + return m_IsRunning; +} + +void Timer::SetDelay(int msec) +{ + m_Delay = max(msec, 1); // timer will not accept values less than 1 msec + + if (m_IsRunning) + { + Stop(); + Start(); + } +} + +void Timer::SetRepeat(bool repeat) +{ + m_MustRepeat = repeat; +} + +int Timer::GetDelay() const +{ + return m_Delay; +} + +Caller::Type Timer::GetType() const +{ + return Caller::Type::Timer; +} + +void CALLBACK Timer::TimerProcStatic(void* lpParameter, BOOLEAN TimerOrWaitFired) +{ + Timer* timerPtr = reinterpret_cast(lpParameter); + + if (timerPtr->m_IsRunning) timerPtr->CallListeners(); + + if (!timerPtr->m_MustRepeat) timerPtr->Stop(); +} + +//--------------------------- +// HitRegion Member Functions +//--------------------------- +HitRegion::HitRegion(Shape shape, int left, int top, int right, int bottom) +{ + if (shape == HitRegion::Shape::Ellipse) + m_HitRegion = CreateEllipticRgn(left, top, right, bottom); + else + m_HitRegion = CreateRectRgn(left, top, right, bottom); +} + +HitRegion::HitRegion(const POINT* pointsArr, int numberOfPoints) +{ + m_HitRegion = CreatePolygonRgn(pointsArr, numberOfPoints, WINDING); +} + +HitRegion::HitRegion(const Bitmap* bmpPtr, COLORREF cTransparent, COLORREF cTolerance) +{ + HBITMAP hBitmap = bmpPtr->GetHandle(); + + if (!hBitmap) throw BitmapNotLoadedException{}; + else + { + // for some reason, the BitmapToRegion function has R and B switched. Flipping the colors to get the right result. + COLORREF flippedTransparent = RGB(GetBValue(cTransparent), GetGValue(cTransparent), GetRValue(cTransparent)); + COLORREF flippedTolerance = RGB(GetBValue(cTolerance), GetGValue(cTolerance), GetRValue(cTolerance)); + + m_HitRegion = BitmapToRegion(hBitmap, flippedTransparent, flippedTolerance); + + if (!m_HitRegion) throw CouldNotCreateHitregionFromBitmapException{}; + } +} + +HitRegion::~HitRegion() +{ + if (m_HitRegion) + DeleteObject(m_HitRegion); +} + +bool HitRegion::Exists() const +{ + return m_HitRegion; +} + +// BitmapToRegion : Create a region from the "non-transparent" pixels of a bitmap +// Author : Jean-Edouard Lachand-Robert (http://www.geocities.com/Paris/LeftBank/1160/resume.htm), June 1998 +// Some modifications: Kevin Hoefman, Febr 2007 +HRGN HitRegion::BitmapToRegion(HBITMAP hBmp, COLORREF cTransparentColor, COLORREF cTolerance) const +{ + HRGN hRgn{}; + + if (hBmp) + { + // Create a memory DC inside which we will scan the bitmap content + HDC hMemDC = CreateCompatibleDC(NULL); + + if (hMemDC) + { + // Get bitmap siz + BITMAP bm; + GetObject(hBmp, sizeof(bm), &bm); + + // Create a 32 bits depth bitmap and select it into the memory DC + BITMAPINFOHEADER RGB32BitsBitmapInfo{}; + RGB32BitsBitmapInfo.biSize = sizeof(BITMAPINFOHEADER); + RGB32BitsBitmapInfo.biWidth = bm.bmWidth; + RGB32BitsBitmapInfo.biHeight = bm.bmHeight; + RGB32BitsBitmapInfo.biPlanes = 1; + RGB32BitsBitmapInfo.biBitCount = 32; + RGB32BitsBitmapInfo.biCompression = BI_RGB; + + VOID* pbits32; + HBITMAP hbm32 = CreateDIBSection(hMemDC, (BITMAPINFO*) &RGB32BitsBitmapInfo, DIB_RGB_COLORS, &pbits32, NULL, NULL); + + if (hbm32) + { + HBITMAP holdBmp = (HBITMAP)SelectObject(hMemDC, hbm32); + + // Create a DC just to copy the bitmap into the memory D + HDC hDC = CreateCompatibleDC(hMemDC); + + if (hDC) + { + // Get how many bytes per row we have for the bitmap bits (rounded up to 32 bits + BITMAP bm32; + GetObject(hbm32, sizeof(bm32), &bm32); + + while (bm32.bmWidthBytes % 4) bm32.bmWidthBytes++; + + // Copy the bitmap into the memory D + HBITMAP holdBmp = (HBITMAP)SelectObject(hDC, hBmp); + BitBlt(hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, hDC, 0, 0, SRCCOPY); + + // For better performances, we will use the ExtCreateRegion() function to create the + // region. This function take a RGNDATA structure on entry. We will add rectangles b + // amount of ALLOC_UNIT number in this structure + #define ALLOC_UNIT 100 + DWORD maxRects = ALLOC_UNIT; + HANDLE hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects)); + RGNDATA *pData = (RGNDATA *)GlobalLock(hData); + pData->rdh.dwSize = sizeof(RGNDATAHEADER); + pData->rdh.iType = RDH_RECTANGLES; + pData->rdh.nCount = pData->rdh.nRgnSize = 0; + SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0); + + // Keep on hand highest and lowest values for the "transparent" pixel + BYTE lr = GetRValue(cTransparentColor); + BYTE lg = GetGValue(cTransparentColor); + BYTE lb = GetBValue(cTransparentColor); + BYTE hr = min(0xff, lr + GetRValue(cTolerance)); + BYTE hg = min(0xff, lg + GetGValue(cTolerance)); + BYTE hb = min(0xff, lb + GetBValue(cTolerance)); + + // Scan each bitmap row from bottom to top (the bitmap is inverted vertically) + BYTE *p32 = (BYTE *)bm32.bmBits + (bm32.bmHeight - 1) * bm32.bmWidthBytes; + for (int y = 0; y < bm.bmHeight; y++) + { + // Scan each bitmap pixel from left to righ + for (int x = 0; x < bm.bmWidth; x++) + { + // Search for a continuous range of "non transparent pixels" + int x0 = x; + LONG *p = (LONG *)p32 + x; + while (x < bm.bmWidth) + { + BYTE b = GetRValue(*p); + if (b >= lr && b <= hr) + { + b = GetGValue(*p); + if (b >= lg && b <= hg) + { + b = GetBValue(*p); + if (b >= lb && b <= hb) + // This pixel is "transparent" + break; + } + } + p++; + x++; + } + + if (x > x0) + { + // Add the pixels (x0, y) to (x, y+1) as a new rectangle in the regio + if (pData->rdh.nCount >= maxRects) + { + GlobalUnlock(hData); + maxRects += ALLOC_UNIT; + hData = GlobalReAlloc(hData, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), GMEM_MOVEABLE); + pData = (RGNDATA *)GlobalLock(hData); + + } + RECT *pr = (RECT *)&pData->Buffer; + SetRect(&pr[pData->rdh.nCount], x0, y, x, y+1); + if (x0 < pData->rdh.rcBound.left) + pData->rdh.rcBound.left = x0; + if (y < pData->rdh.rcBound.top) + pData->rdh.rcBound.top = y; + if (x > pData->rdh.rcBound.right) + pData->rdh.rcBound.right = x; + if (y+1 > pData->rdh.rcBound.bottom) + pData->rdh.rcBound.bottom = y+1; + pData->rdh.nCount++; + } + } + + // Go to next row (remember, the bitmap is inverted vertically) + p32 -= bm32.bmWidthBytes; + } + + // Create or extend the region with the remaining rectangle + HRGN h = ExtCreateRegion(nullptr, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData); + + if (hRgn) + { + CombineRgn(hRgn, hRgn, h, RGN_OR); + DeleteObject(h); + } + else + hRgn = h; + + // Clean up + SelectObject(hDC, holdBmp); + DeleteDC(hDC); + } + + DeleteObject(SelectObject(hMemDC, holdBmp)); + } + + DeleteDC(hMemDC); + } + } + + return hRgn; +} + +HitRegion::HitRegion(const HitRegion& other) +{ + m_HitRegion = CreateRectRgn(0, 0, 10, 10); // create dummy region + CombineRgn(m_HitRegion, other.m_HitRegion, 0, RGN_COPY); +} + +HitRegion::HitRegion(HitRegion&& other) noexcept +{ + m_HitRegion = other.m_HitRegion; + other.m_HitRegion = NULL; +} + +void HitRegion::Move(int deltaX, int deltaY) +{ + OffsetRgn(m_HitRegion, deltaX, deltaY); +} + +RECT HitRegion::GetBounds() const +{ + RECT boundingbox; + GetRgnBox(m_HitRegion, &boundingbox); + + return boundingbox; +} + +HRGN HitRegion::GetHandle() const +{ + return m_HitRegion; +} + +bool HitRegion::HitTest(int x, int y) const +{ + return PtInRegion(m_HitRegion, x, y) ? true : false; +} + +bool HitRegion::HitTest(const HitRegion* regionPtr) const +{ + HRGN temp = CreateRectRgn(0, 0, 10, 10); // dummy region + bool result = (CombineRgn(temp, m_HitRegion, regionPtr->m_HitRegion, RGN_AND) != NULLREGION); + + DeleteObject(temp); + return result; +} + +POINT HitRegion::CollisionTest(const HitRegion* regionPtr) const +{ + POINT result; + + HRGN temp = CreateRectRgn(0, 0, 10, 10); // dummy region + int overlap = CombineRgn(temp, m_HitRegion, regionPtr->m_HitRegion, RGN_AND); + + if (overlap == NULLREGION) + { + result.x = -1000000; + result.y = -1000000; + } + else + { + RECT boundingbox; + GetRgnBox(temp, &boundingbox); + result.x = boundingbox.left + (boundingbox.right - boundingbox.left)/2; + result.y = boundingbox.top + (boundingbox.bottom - boundingbox.top)/2; + } + + DeleteObject(temp); + + return result; +} + + +//----------------------------------------------------------------- +// Font Member Functions +//----------------------------------------------------------------- + +Font::Font(const tstring& fontName, bool bold, bool italic, bool underline, int size) +{ + LOGFONT ft{}; + + for (int counter{}; counter < (int)fontName.size() && counter < LF_FACESIZE; ++counter) + { + ft.lfFaceName[counter] = fontName[counter]; + } + + ft.lfUnderline = underline ? 1 : 0; + ft.lfHeight = size; + ft.lfWeight = bold ? FW_BOLD : 0; + ft.lfItalic = italic ? 1 : 0; + + m_Font = CreateFontIndirect(&ft); +} + +Font::~Font() +{ + DeleteObject(m_Font); +} + +HFONT Font::GetHandle() const +{ + return m_Font; +} + +//----------------------------------------------------------------- +// Exception Classes Member Functions +//----------------------------------------------------------------- + +BadFilenameException::BadFilenameException(const tstring& filename) : m_Filename{filename} +{} + +tstring BadFilenameException::GetMessage() +{ + return tstring(_T("Bad filename: ")) + m_Filename; +} + +FileNotFoundException::FileNotFoundException(const tstring& filename) : m_Filename{ filename } +{} + +tstring FileNotFoundException::GetMessage() +{ + return tstring(_T("File not found: ")) + m_Filename; +} + +UnsupportedFormatException::UnsupportedFormatException(const tstring& filename) : m_Filename{ filename } +{} + +tstring UnsupportedFormatException::GetMessage() +{ + return tstring(_T("Unsupported format: ")) + m_Filename; +} + +CouldNotLoadFileException::CouldNotLoadFileException(const tstring& filename) : m_Filename{ filename } +{} + +tstring CouldNotLoadFileException::GetMessage() +{ + return tstring(_T("Could not load file: ")) + m_Filename; +} + +//----------------------------------------------------------------- +// OutputDebugString functions +//----------------------------------------------------------------- + +void OutputDebugString(const tstring& text) +{ + OutputDebugString(text.c_str()); +} diff --git a/src/GameEngine.h b/src/GameEngine.h new file mode 100644 index 0000000..705184f --- /dev/null +++ b/src/GameEngine.h @@ -0,0 +1,773 @@ +//----------------------------------------------------------------- +// Game Engine Object +// C++ Header - GameEngine.h - version v8_01 +// 2006-2024 Kevin Hoefman - kevin.hoefman@howest.be +// +//----------------------------------------------------------------- + +#pragma once + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- +#define _WIN32_WINNT 0x0A00 // Windows 10 or 11 +#define WIN32_LEAN_AND_MEAN +#define _WINSOCKAPI_ +#include +#include +#include +#include +#include // winmm.lib header, used for playing sound +#undef MessageBox + +#include +#include + +#include // GDI+ for PNG loading +#include +#include +#include +#include + +#include "AbstractGame.h" // base for all games +#include "GameDefines.h" // common header files and defines / macros + +#include // using std::vector for tab control logic +#include // using std::queue for event system +#include + +//----------------------------------------------------------------- +// Pragma Library includes +//----------------------------------------------------------------- +#pragma comment(lib, "msimg32.lib") // used for transparency +#pragma comment(lib, "winmm.lib") // used for sound +#pragma comment(lib, "Gdiplus.lib") // used for PNG loading + +//----------------------------------------------------------------- +// GameEngine Forward Declarations +//----------------------------------------------------------------- +class Bitmap; +class SoundWave; +class Midi; +class HitRegion; +class Font; + +struct ColorRGB{ + float r{}; + float g{}; + float b{}; + + ColorRGB() = default; + ColorRGB(float r, float g, float b) : r(r), g(g), b(b) {} +}; + +//----------------------------------------------------------------- +// GameEngine Class +//----------------------------------------------------------------- +class GameEngine +{ + friend LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam); + +public: + // Constructor and Destructor + GameEngine(); + virtual ~GameEngine(); + + // Disabling copy/move constructors and assignment operators + GameEngine(const GameEngine& other) = delete; + GameEngine(GameEngine&& other) noexcept = delete; + GameEngine& operator=(const GameEngine& other) = delete; + GameEngine& operator=(GameEngine&& other) noexcept = delete; + + // General Member Functions + void SetGame (AbstractGame* gamePtr); + bool Run (HINSTANCE hInstance, int cmdShow); + + void SetTitle (const tstring& title); // SetTitle automatically sets the window class name + void SetWindowPosition (int left, int top); + bool SetWindowRegion (const HitRegion* regionPtr); + void SetKeyList (const tstring& keyList); + void SetFrameRate (int frameRate); + void SetWidth (int width); + void SetHeight (int height); + + bool GoFullscreen (); + bool GoWindowedMode (); + void ShowMousePointer (bool value); + void Quit (); + + bool HasWindowRegion () const; + bool IsFullscreen () const; + + bool IsKeyDown (int vKey) const; + + void MessageBox (const tstring& message) const; + void MessageBox (const TCHAR* message) const; + template + void MessageBox (MyOtherType value) const { MessageBox(to_tstring(value)); } + + bool MessageContinue (const tstring& message) const; + + // Text Dimensions + SIZE CalculateTextDimensions(const tstring& text, const Font* fontPtr) const; + SIZE CalculateTextDimensions(const tstring& text, const Font* fontPtr, RECT rect) const; + + // Draw Functions + void SetColor (COLORREF color); + void SetColorRGB (ColorRGB color); + void SetFont (Font* fontPtr); + + bool FillWindowRect (COLORREF color) const; + bool FillWindowRectRGB (ColorRGB color) const; + + bool DrawLine (int x1, int y1, int x2, int y2) const; + + bool DrawRect (int left, int top, int width, int height) const; + bool DrawRect (float left, float top, float width, float height) const; + bool FillRect (int left, int top, int width, int height) const; + bool FillRect (float left, float top, float width, float height) const; + bool FillRectO (int left, int top, int right, int bottom, int opacity) const; + bool DrawRoundRect (int left, int top, int right, int bottom, int radius) const; + bool FillRoundRect (int left, int top, int right, int bottom, int radius) const; + bool DrawOval (int left, int top, int width, int height) const; + bool DrawOval (float left, float top, float width, float height) const; + bool FillOval (int left, int top, int width, int height) const; + bool FillOval (float left, float top, float width, float height) const; + bool FillOval (int left, int top, int right, int bottom, int opacity) const; + bool DrawArc (int left, int top, int right, int bottom, int startDegree, int angle) const; + bool FillArc (int left, int top, int right, int bottom, int startDegree, int angle) const; + + int DrawString (const tstring& text, int left, int top) const; + int DrawString (const tstring& text, float left, float top) const; + int DrawStringF (const tstring& text, int left, int top, int right, int bottom) const; + + bool DrawBitmap (const Bitmap* bitmapPtr, int left, int top) const; + bool DrawBitmap (const Bitmap* bitmapPtr, int left, int top, RECT sourceRect) const; + + bool DrawPolygon (const POINT ptsArr[], int count) const; + bool DrawPolygon (const POINT ptsArr[], int count, bool close) const; + bool FillPolygon (const POINT ptsArr[], int count) const; + bool FillPolygon (const POINT ptsArr[], int count, bool close) const; + + COLORREF GetDrawColor () const; + bool Repaint () const; + + // Accessor Member Functions + tstring GetTitle () const; + HINSTANCE GetInstance () const { return m_Instance; } + HWND GetWindow () const { return m_Window; } + int GetWidth () const { return m_Width; } + int GetHeight () const { return m_Height; } + int GetFrameRate () const { return m_FrameRate; } + int GetFrameDelay () const { return m_FrameDelay; } + POINT GetWindowPosition () const; + + // Tab control + void TabNext (HWND ChildWindow) const; + void TabPrevious (HWND ChildWindow) const; + + int GetMouseX (); + int GetMouseY (); + + bool IsMouseLeftDown (); + bool IsMouseRightDown (); + + int GetControllersConnected() const; + + std::string GetRequest(std::string url); + std::string PostRequest(std::string url, std::string data); + + const char* AskString(); + +private: + // Private Member Functions + LRESULT HandleEvent (HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam); + bool CreateGameWindow (int cmdShow); + void MonitorKeyboard (); + + void SetInstance (HINSTANCE hInstance); + void SetWindow (HWND hWindow); + + void PaintDoubleBuffered (HDC hDC); + void FormPolygon (const POINT ptsArr[], int count, bool close) const; + POINT AngleToPoint (int left, int top, int right, int bottom, int angle) const; + + // Member Variables + HINSTANCE m_Instance {}; + HWND m_Window {}; + tstring m_Title {}; + int m_Width {}; + int m_Height {}; + int m_FrameRate { 50 }; + int m_FrameDelay { 1000 / m_FrameRate }; + bool m_RunGameLoop { true }; + TCHAR* m_KeyListPtr {}; + unsigned int m_KeybMonitor {}; + AbstractGame* m_GamePtr {}; + bool m_Fullscreen {}; + + // GDI+ for PNG loading + ULONG_PTR m_GDIPlusToken; + + // Draw assistance variables + HDC m_HdcDraw {}; + RECT m_RectDraw {}; + bool m_IsPainting {}; + COLORREF m_ColDraw {}; + HFONT m_FontDraw {}; + + // Fullscreen assistance variable + POINT m_OldPosition {}; + + // Window Region assistance variable + HitRegion* m_WindowRegionPtr {}; + + int m_mouseX{}; + int m_mouseY{}; + + bool m_mouseLeftDown{}; + bool m_mouseRightDown{}; +}; + +//------------------------------------------------------------------------------------------------ +// Callable Interface +// +// Interface implementation for classes that can be called by "caller" objects +//------------------------------------------------------------------------------------------------ +class Caller; // forward declaration + +class Callable +{ +public: + virtual ~Callable() = default; // virtual destructor for polymorphism + + virtual void CallAction(Caller* callerPtr) = 0; +}; + +//------------------------------------------------------------------------------------------------ +// Caller Base Class +// +// Base Clase implementation for up- and downcasting of "caller" objects: TextBox, Button, Timer, Audio and Video +//------------------------------------------------------------------------------------------------ +class Caller +{ +public: + // ------------------------- + // Enums + // ------------------------- + enum class Type + { + TextBox, Button, Timer, Audio, Video + }; + + // ------------------------- + // Destructor + // ------------------------- + virtual ~Caller() = default; // do not delete the targets! + + // ------------------------- + // Disabling copy/move constructors and assignment operators + // ------------------------- + Caller(const Caller& other) = delete; + Caller(Caller&& other) noexcept = delete; + Caller& operator=(const Caller& other) = delete; + Caller& operator=(Caller&& other) noexcept = delete; + + // ------------------------- + // General Member Functions + // ------------------------- + virtual Type GetType() const = 0; // pure virtual function + + virtual bool AddActionListener (Callable* targetPtr); // public interface method, call is passed on to private method + virtual bool RemoveActionListener (const Callable* targetPtr); // public interface method, call is passed on to private method + +protected: + Caller() {} // constructor only for derived classes + std::vector m_TargetList; + + virtual bool CallListeners(); // placing the event code in a separate method instead of directly in the windows messaging + // function allows inheriting classes to override the event code. + +private: + bool AddListenerObject (Callable* targetPtr); + bool RemoveListenerObject (const Callable* targetPtr); +}; + +//-------------------------------------------------------------------------- +// Timer Class +//-------------------------------------------------------------------------- + +class Timer : public Caller +{ +public: + // ------------------------- + // Constructor(s) and Destructor + // ------------------------- + Timer(int msec, Callable* targetPtr, bool repeat = false); + + virtual ~Timer() override; + + // ------------------------- + // Disabling copy/move constructors and assignment operators + // ------------------------- + Timer(const Timer& other) = delete; + Timer(Timer&& other) noexcept = delete; + Timer& operator=(const Timer& other) = delete; + Timer& operator=(Timer&& other) noexcept = delete; + + // ------------------------- + // General Member Functions + // ------------------------- + void Start (); + void Stop (); + void SetDelay (int msec); + void SetRepeat (bool repeat); + + bool IsRunning () const; + int GetDelay () const; + Type GetType () const; + +private: + // ------------------------- + // Datamembers + // ------------------------- + HANDLE m_TimerHandle {}; + bool m_IsRunning {}; + bool m_MustRepeat; + int m_Delay; + + // ------------------------- + // Handler functions + // ------------------------- + static void CALLBACK TimerProcStatic(void* lpParameter, BOOLEAN TimerOrWaitFired); // proc will call CallListeners() +}; + +//----------------------------------------------------------------- +// TextBox Class +//----------------------------------------------------------------- + +class TextBox : public Caller +{ +public: + // ------------------------- + // Constructor(s) and Destructor + // ------------------------- + TextBox(const tstring& text); + TextBox(); + + virtual ~TextBox() override; + + // ------------------------- + // Disabling copy/move constructors and assignment operators + // ------------------------- + TextBox(const TextBox& other) = delete; + TextBox(TextBox&& other) noexcept = delete; + TextBox& operator=(const TextBox& other) = delete; + TextBox& operator=(TextBox&& other) noexcept = delete; + + // ------------------------- + // General Member Functions + // ------------------------- + void SetBounds (int left, int top, int width, int height); + void SetText (const tstring& text); + void SetFont (const tstring& fontName, bool bold, bool italic, bool underline, int size); + void SetBackcolor (COLORREF color); + void SetForecolor (COLORREF color); + void SetEnabled (bool enable); + void Show (); + void Hide (); + + RECT GetBounds () const; + tstring GetText () const; + COLORREF GetForecolor () const; + COLORREF GetBackcolor () const; + HBRUSH GetBackcolorBrush () const; + Type GetType () const; + +private: + // ------------------------- + // Member Functions + // ------------------------- + void Repaint (); + void Update (); + + // ------------------------- + // Datamembers + // ------------------------- + RECT m_Bounds { 0, 0, 100, 25 }; + HWND m_WndEdit {}; + WNDPROC m_ProcOldEdit {}; + COLORREF m_BgColor { RGB(255, 255, 255) }; + COLORREF m_ForeColor {}; + HBRUSH m_BgColorBrush {}; + HFONT m_Font {}; + HFONT m_OldFont {}; + + // ------------------------- + // Handler functions + // ------------------------- + static LRESULT CALLBACK EditProcStatic(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam); + LRESULT EditProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam); +}; + +//----------------------------------------------------------------- +// Button Class +//----------------------------------------------------------------- +class Button : public Caller +{ +public: + // ------------------------- + // Constructor(s) and Destructor + // ------------------------- + Button(const tstring& label); + Button(); + + virtual ~Button() override; + + // ------------------------- + // Disabling copy/move constructors and assignment operators + // ------------------------- + Button(const Button& other) = delete; + Button(Button&& other) noexcept = delete; + Button& operator=(const Button& other) = delete; + Button& operator=(Button&& other) noexcept = delete; + + // ------------------------- + // General Member Functions + // ------------------------- + void SetBounds (int left, int top, int right, int bottom); + void SetText (const tstring& text); + void SetFont (const tstring& fontName, bool bold, bool italic, bool underline, int size); + void SetEnabled (bool enable); + void Show (); + void Hide (); + + RECT GetBounds () const; + tstring GetText () const; + Type GetType () const; + +private: + // ------------------------- + // Member Functions + // ------------------------- + void Update(); + + // ------------------------- + // Datamembers + // ------------------------- + RECT m_Bounds { 0, 0, 100, 25 }; + HWND m_WndButton {}; + WNDPROC m_ProcOldButton {}; + bool m_Armed {}; + HFONT m_Font {}; + HFONT m_OldFont {}; + + // ------------------------- + // Handler functions + // ------------------------- + static LRESULT CALLBACK ButtonProcStatic(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam); + LRESULT ButtonProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam); +}; + +//----------------------------------------------------------------- +// Audio Class +//----------------------------------------------------------------- +class Audio : public Caller +{ +public: + // ------------------------- + // Constructor(s) and Destructor + // ------------------------- + Audio(const tstring& filename); + //Audio(int IDAudio, const tstring& type); + + virtual ~Audio() override; + + // ------------------------- + // Disabling copy/move constructors and assignment operators + // ------------------------- + Audio(const Audio& other) = delete; + Audio(Audio&& other) noexcept = delete; + Audio& operator=(const Audio& other) = delete; + Audio& operator=(Audio&& other) noexcept = delete; + + // ------------------------- + // General member functions + // ------------------------- + // Commands are queued, and only sent when Tick() is called. Calling Tick() needs to be done on the main thread (mcisendstring isn't thread safe) + void Tick (); + + void Play (int msecStart = 0, int msecStop = -1); + void Pause (); + void Stop (); + void SetVolume (int volume); + void SetRepeat (bool repeat); + + const tstring& GetName () const; + const tstring& GetAlias () const; + int GetDuration () const; + bool IsPlaying () const; + bool IsPaused () const; + bool GetRepeat () const; + bool Exists () const; + int GetVolume () const; + Type GetType () const; + +private: + // ------------------------- + // Datamembers + // ------------------------- + static int m_Nr; + + tstring m_Filename; + tstring m_Alias; + bool m_Playing {}; + bool m_Paused {}; + bool m_MustRepeat {}; + HWND m_hWnd {}; + int m_Duration { -1 }; + int m_Volume { 100 }; + + // ------------------------- + // General Member Functions + // ------------------------- + void Create(const tstring& filename); + void Extract(WORD id, const tstring& type, const tstring& filename) const; + void SwitchPlayingOff(); + + // ------------------------- + // Private mcisendstring command member functions and command queue datamember + // ------------------------- + std::queue m_CommandQueue; + + void QueuePlayCommand (int msecStart); + void QueuePlayCommand (int msecStart, int msecStop); + void QueuePauseCommand (); + void QueueResumeCommand (); + void QueueStopCommand (); + void QueueVolumeCommand (int volume); + void QueueCommand (const tstring& command); + + void SendMCICommand (const tstring& command); + + //void QueuePositionCommand(int x, int y); + + // ------------------------- + // Handler functions + // ------------------------- + static LRESULT CALLBACK AudioProcStatic(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam); +}; + +//----------------------------------------------------------------- +// Bitmap Class +//----------------------------------------------------------------- +class Bitmap final +{ +public: + // ------------------------- + // Constructor(s) and Destructor + // ------------------------- + Bitmap(const tstring& filename, bool createAlphaChannel = true); + //Bitmap(HBITMAP hBitmap); + //Bitmap(int IDBitmap, const tstring& type, bool createAlphaChannel = true); + + ~Bitmap(); + + // ------------------------- + // Disabling copy/move constructors and assignment operators + // ------------------------- + Bitmap(const Bitmap& other) = delete; + Bitmap(Bitmap&& other) noexcept = delete; + Bitmap& operator=(const Bitmap& other) = delete; + Bitmap& operator=(Bitmap&& other) noexcept = delete; + + // ------------------------- + // General Member Functions + // ------------------------- + void SetTransparencyColor (COLORREF color); + void SetOpacity (int); + + bool Exists () const; + int GetWidth () const; + int GetHeight () const; + COLORREF GetTransparencyColor () const; + int GetOpacity () const; + bool HasAlphaChannel () const; + bool SaveToFile (const tstring& filename) const; + + HBITMAP GetHandle () const; + +private: + // ------------------------- + // Datamembers + // ------------------------- + static int m_Nr; + + HBITMAP m_hBitmap {}; + COLORREF m_TransparencyKey {}; + int m_Opacity { 100 }; + unsigned char* m_PixelsPtr {}; + bool m_HasAlphaChannel; + + // ------------------------- + // Member Functions + // ------------------------- + HBITMAP LoadPNG(TCHAR* pFilePath); + void CreateAlphaChannel(); + void Extract(WORD id, const tstring& type, const tstring& fileName) const; +}; + +//----------------------------------------------------------------- +// HitRegion Class +//----------------------------------------------------------------- +class HitRegion final +{ +public: + // ------------------------- + // Enums + // ------------------------- + enum class Shape + { + Ellipse, Rectangle + }; + + //--------------------------- + // Constructor(s) and Destructor + //--------------------------- + HitRegion(Shape shape, int left, int top, int right, int bottom); // Use this create to form a rectangular or elliptic hitregion + HitRegion(const POINT* pointsArr, int numberOfPoints); // Use this create to form a polygonal hitregion + HitRegion(const Bitmap* bmpPtr, COLORREF cTransparent = RGB(255, 0, 255), COLORREF cTolerance = 0); // Use this create to create a hitregion from a bitmap + + ~HitRegion(); + + //--------------------------- + // Implementing copy/move constructors, disabling assignment operators + //--------------------------- + HitRegion(const HitRegion& other); + HitRegion(HitRegion&& other) noexcept; + HitRegion& operator=(const HitRegion& other) = delete; + HitRegion& operator=(HitRegion&& other) noexcept = delete; + + //--------------------------- + // General Member Functions + //--------------------------- + void Move (int deltaX, int deltaY); // Moves the region over the given displacement + + bool HitTest (int x, int y) const; // Returns true if the point that corresponds to the given x- and y-values is in the region, false if not + bool HitTest (const HitRegion* regionPtr) const; // Returns true if the regions overlap, false if they don't + + POINT CollisionTest (const HitRegion* regionPtr) const; // Returns {-1000000, -1000000} if the regions don't overlap, and the center point of the overlapping region if they do overlap + // CollisionTest is useful to determine the hitting point of two forms that barely touch + + RECT GetBounds () const; // Returns the position + width and height of the smallest rectangle that encloses this region (in case of a rectangular region: the region itself) + + bool Exists () const; // Returns true if the hitregion was successfully created, false if not + + HRGN GetHandle () const; // Returns the handle of the region (Win32 stuff) + +private: + //--------------------------- + // Datamembers + //--------------------------- + HRGN m_HitRegion {}; // The region data is stored by means of a Win32 "region" resources (not for 1st semester) + + //--------------------------- + // Private Member Functions + //--------------------------- + HRGN BitmapToRegion(HBITMAP hBmp, COLORREF cTransparentColor, COLORREF cTolerance) const; +}; + +//----------------------------------------------------------------- +// Font Class +//----------------------------------------------------------------- +class Font final +{ +public: + //--------------------------- + // Constructor(s) and Destructor + //--------------------------- + Font(const tstring& fontName, bool bold, bool italic, bool underline, int size); + + ~Font(); + + //--------------------------- + // Disabling copy/move constructors and assignment operators + //--------------------------- + Font(const Font& other) = delete; + Font(Font&& other) noexcept = delete; + Font& operator=(const Font& other) = delete; + Font& operator=(Font&& other) noexcept = delete; + + //--------------------------- + // General Member Functions + //--------------------------- + HFONT GetHandle() const; + +private: + //--------------------------- + // Datamembers + //--------------------------- + HFONT m_Font; +}; + +//----------------------------------------------------------------- +// Exception Classes +//----------------------------------------------------------------- +class BadFilenameException final +{ +public: + BadFilenameException(const tstring& filename); + tstring GetMessage(); + +private: + tstring m_Filename; +}; + +class FileNotFoundException final +{ +public: + FileNotFoundException(const tstring& filename); + tstring GetMessage(); + +private: + tstring m_Filename; +}; + +class UnsupportedFormatException final +{ +public: + UnsupportedFormatException(const tstring& filename); + tstring GetMessage(); + +private: + tstring m_Filename; +}; + +class CouldNotLoadFileException final +{ +public: + CouldNotLoadFileException(const tstring& filename); + tstring GetMessage(); + +private: + tstring m_Filename; +}; + +class BitmapNotLoadedException final {}; +class CouldNotCreateHitregionFromBitmapException final {}; + +//----------------------------------------------------------------- +// Extra OutputDebugString functions +//----------------------------------------------------------------- +void OutputDebugString(const tstring& text); + +//----------------------------------------------------------------- +// Windows Procedure Declarations +//----------------------------------------------------------------- +LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +//----------------------------------------------------------------- +// Extern declaration for GAME_ENGINE global (singleton) object and pointer +//----------------------------------------------------------------- +extern GameEngine myGameEngine; +extern GameEngine* GAME_ENGINE; \ No newline at end of file diff --git a/src/GameWinMain.cpp b/src/GameWinMain.cpp new file mode 100644 index 0000000..0f8c600 --- /dev/null +++ b/src/GameWinMain.cpp @@ -0,0 +1,81 @@ +//----------------------------------------------------------------- +// Game Engine WinMain Function +// C++ Source - GameWinMain.cpp - version v8_01 +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- +#define _WIN32_WINNT 0x0A00 // Windows 10 or 11 +#define _WINSOCKAPI_ +#include +#include +#include +#include +#include // winmm.lib header, used for playing sound +#undef MessageBox + +#include "GameWinMain.h" +#include "GameEngine.h" + +#include "Game.h" + +//----------------------------------------------------------------- +// Create GAME_ENGINE global (singleton) object and pointer +//----------------------------------------------------------------- +GameEngine myGameEngine; +GameEngine* GAME_ENGINE{ &myGameEngine }; + + +void AllocateConsole() +{ + if (AllocConsole()) // Allocate a new console for the application + { + FILE *fp; // Redirect STDOUT to the console + freopen_s(&fp, "CONOUT$", "w", stdout); + setvbuf(stdout, NULL, _IONBF, 0); // Disable buffering for stdout + + freopen_s(&fp, "CONOUT$", "w", stderr); // Redirect STDERR to the console + setvbuf(stderr, NULL, _IONBF, 0); // Disable buffering for stderr + + freopen_s(&fp, "CONIN$", "r", stdin); // Redirect STDIN to the console + setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering for stdin + + std::ios::sync_with_stdio(true); // Sync C++ streams with the console + } +} + +std::string WStringToString(const std::wstring& wstr) { + if (wstr.empty()) return {}; + + int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); + std::string str(size_needed - 1, '\0'); + WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], size_needed, nullptr, nullptr); + return str; +} + + +//----------------------------------------------------------------- +// Main Function +//----------------------------------------------------------------- +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) +{ + AllocateConsole(); + + int argc{ 1 }; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + + if(argc == 2){ + //We dragged and Dropped a lua file on us + std::string convertedString{WStringToString(argv[1])}; + GAME_ENGINE->SetGame(new Game(convertedString)); // any class that implements AbstractGame + + } else { + GAME_ENGINE->SetGame(new Game("./lua/script.lua")); // any class that implements AbstractGame + } + + + return GAME_ENGINE->Run(hInstance, nCmdShow); // here we go + +} + diff --git a/src/GameWinMain.h b/src/GameWinMain.h new file mode 100644 index 0000000..a830c7f --- /dev/null +++ b/src/GameWinMain.h @@ -0,0 +1,17 @@ +//----------------------------------------------------------------- +// Game Engine WinMain Function +// C++ Header - GameWinMain.h - version v8_01 +//----------------------------------------------------------------- + +#pragma once + +//----------------------------------------------------------------- +// Include Files +//----------------------------------------------------------------- +#define WIN32_LEAN_AND_MEAN +#include + +//----------------------------------------------------------------- +// Windows Function Declarations +//----------------------------------------------------------------- +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow); \ No newline at end of file diff --git a/src/big.ico b/src/big.ico new file mode 100644 index 0000000..d551aa3 Binary files /dev/null and b/src/big.ico differ diff --git a/src/resource.h b/src/resource.h new file mode 100644 index 0000000..a3ae20a --- /dev/null +++ b/src/resource.h @@ -0,0 +1,10 @@ +//----------------------------------------------------------------- +// Game Skeleton Resource Identifiers +// C++ Header - Resource.h - version v7_02_firstyear +//----------------------------------------------------------------- + +//----------------------------------------------------------------- +// Icons Range : 1000 - 1999 +//----------------------------------------------------------------- +#define IDI_BIG 1000 +#define IDI_SMALL 1001 diff --git a/src/small.ico b/src/small.ico new file mode 100644 index 0000000..d551aa3 Binary files /dev/null and b/src/small.ico differ