Fix stuff
@@ -48,17 +48,6 @@ 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
|
GIT_TAG dec9422db3af470641f8b0d90e4b451c4daebf64) # Replace with your desired git commit from: https://github.com/libcpr/cpr/releases
|
||||||
FetchContent_MakeAvailable(cpr)
|
FetchContent_MakeAvailable(cpr)
|
||||||
|
|
||||||
# fetch asio
|
|
||||||
FetchContent_Declare(asio
|
|
||||||
GIT_REPOSITORY git@github.com:chriskohlhoff/asio.git
|
|
||||||
GIT_TAG master
|
|
||||||
CONFIGURE_COMMAND ""
|
|
||||||
BUILD_COMMAND ""
|
|
||||||
)
|
|
||||||
FetchContent_GetProperties(asio)
|
|
||||||
if(NOT asio_POPULATED)
|
|
||||||
FetchContent_Populate(asio)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_library(GDIPLUS_LIBRARY NAMES libgdiplus gdiplus)
|
find_library(GDIPLUS_LIBRARY NAMES libgdiplus gdiplus)
|
||||||
set(GDIPLUS_LIBRARY gdiplus)
|
set(GDIPLUS_LIBRARY gdiplus)
|
||||||
@@ -88,7 +77,7 @@ add_custom_target(CopyLuaScripts ALL
|
|||||||
add_custom_command(TARGET CopyLuaScripts POST_BUILD
|
add_custom_command(TARGET CopyLuaScripts POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/lua
|
${CMAKE_CURRENT_SOURCE_DIR}/lua
|
||||||
$<TARGET_FILE_DIR:${PROJECT_NAME}>/lua)
|
$<TARGET_FILE_DIR:${PROJECT_NAME}>/)
|
||||||
|
|
||||||
add_custom_target(CopyResources ALL
|
add_custom_target(CopyResources ALL
|
||||||
COMMENT "Copying resources to output directory"
|
COMMENT "Copying resources to output directory"
|
||||||
|
|||||||
216
lua/Pong.lua
@@ -21,8 +21,8 @@ local ball = {
|
|||||||
|
|
||||||
local AI = {
|
local AI = {
|
||||||
y = 200,
|
y = 200,
|
||||||
height = 100,
|
height = 75,
|
||||||
speed = 4
|
speed = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
local player_score = 0
|
local player_score = 0
|
||||||
@@ -35,8 +35,19 @@ local next_round = true
|
|||||||
local countdown_text = 3
|
local countdown_text = 3
|
||||||
|
|
||||||
|
|
||||||
|
-- Enum for game states
|
||||||
|
local MAIN_MENU = 0
|
||||||
|
local PLAYING = 1
|
||||||
|
local WIN = 2
|
||||||
|
local LOST = 3
|
||||||
|
|
||||||
function update_player(player)
|
local currentGameState = MAIN_MENU
|
||||||
|
|
||||||
|
local mainMenuBitmap
|
||||||
|
local winBitmap
|
||||||
|
local lostBitmap
|
||||||
|
|
||||||
|
function update_player()
|
||||||
if GameEngine:isKeyDown("W") then
|
if GameEngine:isKeyDown("W") then
|
||||||
if (player.position.y >= 0) then
|
if (player.position.y >= 0) then
|
||||||
player.position.y = player.position.y - player.speed
|
player.position.y = player.position.y - player.speed
|
||||||
@@ -51,17 +62,16 @@ function update_player(player)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function update_ai()
|
function update_ai()
|
||||||
if ball.y < AI.y then
|
local predicted_y = ball.y + ball.yspeed * (math.abs(ball.x - (GameEngine:getWidth() - 10)) / math.abs(ball.xspeed))
|
||||||
if(AI.y > 0) then
|
|
||||||
AI.y = AI.y - AI.speed
|
if predicted_y < AI.y + AI.height / 2 then
|
||||||
end
|
AI.y = AI.y - AI.speed
|
||||||
|
elseif predicted_y > AI.y + AI.height / 2 then
|
||||||
|
AI.y = AI.y + AI.speed
|
||||||
end
|
end
|
||||||
|
|
||||||
if ball.y > AI.y then
|
-- Keep AI within bounds
|
||||||
if AI.y + AI.height < GameEngine:getHeight() then
|
AI.y = math.max(0, math.min(AI.y, GameEngine:getHeight() - AI.height))
|
||||||
AI.y = AI.y + AI.speed
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function update_ball()
|
function update_ball()
|
||||||
@@ -99,10 +109,17 @@ function update_ball()
|
|||||||
if ball.y <= 0 or ball.y >= GameEngine:getHeight() - ball.size then
|
if ball.y <= 0 or ball.y >= GameEngine:getHeight() - ball.size then
|
||||||
ball.yspeed = -ball.yspeed
|
ball.yspeed = -ball.yspeed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function update_score()
|
||||||
|
if player_score >= 5 then
|
||||||
|
currentGameState = WIN
|
||||||
|
end
|
||||||
|
|
||||||
|
if ai_score >= 5 then
|
||||||
|
currentGameState = LOST
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function draw_player()
|
function draw_player()
|
||||||
@@ -120,33 +137,34 @@ function draw_ball()
|
|||||||
GameEngine:fillOval(ball.x, ball.y, ball.size, ball.size)
|
GameEngine:fillOval(ball.x, ball.y, ball.size, ball.size)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- the setup function
|
|
||||||
--- @return nil
|
--- region MAIN_MENU
|
||||||
function setup_window()
|
function drawMainMenu()
|
||||||
GameEngine:setTitle("BreakOut")
|
GameEngine:setColor(Color.new(255,255,255))
|
||||||
GameEngine:setWidth(800)
|
-- GameEngine:drawText("Press ENTER to Start", GameEngine:getWidth() / 2 - 50, GameEngine:getHeight() / 2)
|
||||||
GameEngine:setHeight(600)
|
GameEngine:drawBitmap(mainMenuBitmap, 0, 0)
|
||||||
GameEngine:setFrameRate(60)
|
|
||||||
end
|
end
|
||||||
|
function updateMainMenu()
|
||||||
--- the set_keylist function
|
if GameEngine:isKeyDown("RETURN") then
|
||||||
--- @return string
|
currentGameState = PLAYING
|
||||||
function set_keylist()
|
end
|
||||||
return "WASD"
|
|
||||||
end
|
end
|
||||||
|
--- endregion
|
||||||
|
|
||||||
--- the start function
|
--- region PLAYING
|
||||||
--- @return nil
|
function drawGame()
|
||||||
function start()
|
GameEngine:fillScreen(Color.new(0, 0,0))
|
||||||
ball.x = GameEngine:getWidth() / 2 - ball.size / 2
|
GameEngine:setColor(Color.new(255,255,255))
|
||||||
ball.y = GameEngine:getHeight() / 2 - ball.size / 2
|
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
|
end
|
||||||
|
function updateGame()
|
||||||
--- the update function
|
|
||||||
--- @param Engine GameEngine # The GameEngine instance.
|
|
||||||
--- @return nil
|
|
||||||
function update()
|
|
||||||
if (start_timer < start_timer_max and next_round) then
|
if (start_timer < start_timer_max and next_round) then
|
||||||
start_timer = start_timer + 1
|
start_timer = start_timer + 1
|
||||||
if start_timer % 20 == 0 then
|
if start_timer % 20 == 0 then
|
||||||
@@ -162,21 +180,125 @@ function update()
|
|||||||
update_player(player)
|
update_player(player)
|
||||||
update_ball()
|
update_ball()
|
||||||
update_ai()
|
update_ai()
|
||||||
|
update_score()
|
||||||
|
end
|
||||||
|
--- endregion
|
||||||
|
|
||||||
|
|
||||||
|
--- region LOST
|
||||||
|
function drawLost()
|
||||||
|
GameEngine:fillScreen(Color.new(0, 0,0))
|
||||||
|
GameEngine:setColor(Color.new(255,255,255))
|
||||||
|
GameEngine:drawBitmap(lostBitmap, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateLost()
|
||||||
|
if GameEngine:isKeyDown("R") then
|
||||||
|
currentGameState = PLAYING
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- endregion
|
||||||
|
|
||||||
|
--- region WIN
|
||||||
|
function drawWin()
|
||||||
|
GameEngine:fillScreen(Color.new(0, 0,0))
|
||||||
|
GameEngine:setColor(Color.new(255,255,255))
|
||||||
|
GameEngine:drawBitmap(lostBitmap, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateWin()
|
||||||
|
if GameEngine:isKeyDown("R") then
|
||||||
|
currentGameState = PLAYING
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- endregion
|
||||||
|
|
||||||
|
--- the setup function
|
||||||
|
--- @return nil
|
||||||
|
function setup_window()
|
||||||
|
GameEngine:setTitle("Pong")
|
||||||
|
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
|
||||||
|
|
||||||
|
-- Don't blame me for logging :p
|
||||||
|
local name = GameEngine:getName()
|
||||||
|
local data = "{ \"name\": \"" .. name .. "\" }"
|
||||||
|
GameEngine:getRequest("https://api.brammie15.dev/game-open", data)
|
||||||
|
|
||||||
|
mainMenuBitmap = Bitmap.new("resources/pong/mainMenu.bmp", true)
|
||||||
|
mainMenuBitmap:SetTransparencyColor(Color.new(255, 0, 255))
|
||||||
|
|
||||||
|
winBitmap = Bitmap.new("resources/pong/win.bmp", true)
|
||||||
|
winBitmap:SetTransparencyColor(Color.new(255, 0, 255))
|
||||||
|
|
||||||
|
lostBitmap = Bitmap.new("resources/pong/lost.bmp", true)
|
||||||
|
lostBitmap:SetTransparencyColor(Color.new(255, 0, 255))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- the update function
|
||||||
|
--- @return nil
|
||||||
|
function update()
|
||||||
|
|
||||||
|
if GameEngine:isKeyDown("ESCAPE") then
|
||||||
|
GameEngine:quit()
|
||||||
|
end
|
||||||
|
if currentGameState == MAIN_MENU then
|
||||||
|
updateMainMenu()
|
||||||
|
end
|
||||||
|
if currentGameState == PLAYING then
|
||||||
|
updateGame()
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentGameState == LOST then
|
||||||
|
updateLost()
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentGameState == WIN then
|
||||||
|
updateWin()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- the draw function
|
--- the draw function
|
||||||
--- @return nil
|
--- @return nil
|
||||||
function draw()
|
function draw()
|
||||||
GameEngine:fillScreen(Color.new(0, 0,0))
|
if currentGameState == MAIN_MENU then
|
||||||
-- draw the score
|
drawMainMenu()
|
||||||
GameEngine:setColor(Color.new(255,255,255))
|
end
|
||||||
GameEngine:drawText(tostring(player_score), 10, 10)
|
|
||||||
GameEngine:drawText(tostring(ai_score), GameEngine:getWidth() - 20, 10)
|
if currentGameState == PLAYING then
|
||||||
if next_round then
|
drawGame()
|
||||||
GameEngine:drawText(tostring(countdown_text), GameEngine:getWidth() / 2,GameEngine:getHeight() / 2 - 30)
|
end
|
||||||
|
|
||||||
|
if currentGameState == LOST then
|
||||||
|
drawLost()
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentGameState == WIN then
|
||||||
|
drawWin()
|
||||||
end
|
end
|
||||||
draw_player()
|
end
|
||||||
draw_ball()
|
|
||||||
draw_ai()
|
function quit()
|
||||||
|
print("bye")
|
||||||
|
local name = GameEngine:getName()
|
||||||
|
local data = "{ \"name\": \"" .. name .. "\" }"
|
||||||
|
print(data)
|
||||||
|
GameEngine:getRequest("https://api.brammie15.dev/game-close", data)
|
||||||
end
|
end
|
||||||
@@ -115,6 +115,10 @@ GameEngine = GameEngine
|
|||||||
---@return nil
|
---@return nil
|
||||||
function GameEngine:setTitle(title) end
|
function GameEngine:setTitle(title) end
|
||||||
|
|
||||||
|
--- Gets the title of the window.
|
||||||
|
--- @return string # The title of the window.
|
||||||
|
function GameEngine:getName() end
|
||||||
|
|
||||||
--- Sets the width of the window.
|
--- Sets the width of the window.
|
||||||
--- @param width number # The new width.
|
--- @param width number # The new width.
|
||||||
--- @return nil
|
--- @return nil
|
||||||
@@ -138,6 +142,10 @@ function GameEngine:setHeight(height) end
|
|||||||
--- @return nil
|
--- @return nil
|
||||||
function GameEngine:setFrameRate(frameRate) end
|
function GameEngine:setFrameRate(frameRate) end
|
||||||
|
|
||||||
|
--- Quits the game
|
||||||
|
--- @return nil
|
||||||
|
function GameEngine:quit() end
|
||||||
|
|
||||||
--- Sets the drawing color
|
--- Sets the drawing color
|
||||||
--- @param color Color # The new color.
|
--- @param color Color # The new color.
|
||||||
--- @return nil
|
--- @return nil
|
||||||
@@ -221,11 +229,19 @@ function GameEngine:isMouseLeftDown() end;
|
|||||||
--- @return boolean # True if the right mouse button is pressed, false otherwise.
|
--- @return boolean # True if the right mouse button is pressed, false otherwise.
|
||||||
function GameEngine:isMouseRightDown() end;
|
function GameEngine:isMouseRightDown() end;
|
||||||
|
|
||||||
|
--- Preform a Get Request
|
||||||
|
--- @param url string # The url to get.
|
||||||
|
--- @param data string # The data to get.
|
||||||
|
--- @return string # The response from the server.
|
||||||
|
function GameEngine:getRequest(url, data) end
|
||||||
|
|
||||||
|
--- overload without data
|
||||||
--- Preform a Get Request
|
--- Preform a Get Request
|
||||||
--- @param url string # The url to get.
|
--- @param url string # The url to get.
|
||||||
--- @return string # The response from the server.
|
--- @return string # The response from the server.
|
||||||
function GameEngine:getRequest(url) end
|
function GameEngine:getRequest(url) end
|
||||||
|
|
||||||
|
|
||||||
--- Preform a Post Request
|
--- Preform a Post Request
|
||||||
--- @param url string # The url to post.
|
--- @param url string # The url to post.
|
||||||
--- @param data string # The data to post.
|
--- @param data string # The data to post.
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
-- Tetris Game using GameEngine
|
-- Tetris Game using GameEngine
|
||||||
|
|
||||||
|
--- region dependencies
|
||||||
local vector2 = require("vector2")
|
local vector2 = require("vector2")
|
||||||
local button = require("button")
|
local button = require("button")
|
||||||
|
|
||||||
-- Constants
|
--- endregion
|
||||||
|
|
||||||
|
--- region Constants
|
||||||
local GRID_WIDTH = 10
|
local GRID_WIDTH = 10
|
||||||
local GRID_HEIGHT = 19
|
local GRID_HEIGHT = 19
|
||||||
local BLOCK_SIZE = 30
|
local BLOCK_SIZE = 30
|
||||||
@@ -12,7 +15,9 @@ local FALL_SPEED = 0.5
|
|||||||
local screenWidth = 800
|
local screenWidth = 800
|
||||||
local screenHeight = 600
|
local screenHeight = 600
|
||||||
|
|
||||||
-- Tetromino shapes and colors
|
--- endregion
|
||||||
|
|
||||||
|
--- region Tetrominoes
|
||||||
local tetrominoes = {
|
local tetrominoes = {
|
||||||
{ shape = {{1, 1, 1, 1}}, color = Color.new(0, 255, 255) }, -- I
|
{ 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 = {{1, 1}, {1, 1}}, color = Color.new(255, 255, 0) }, -- O
|
||||||
@@ -22,14 +27,21 @@ local tetrominoes = {
|
|||||||
{ shape = {{1, 1, 1}, {1, 0, 0}}, color = Color.new(255, 165, 0) }, -- L
|
{ 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
|
{ shape = {{1, 1, 1}, {0, 0, 1}}, color = Color.new(0, 0, 255) } -- J
|
||||||
}
|
}
|
||||||
|
--- endregion
|
||||||
|
|
||||||
-- Game State
|
-- Game State
|
||||||
local grid = {}
|
local grid = {}
|
||||||
local currentPiece = {}
|
local currentPiece = {}
|
||||||
local pieceX, pieceY = 4, 0
|
local pieceX, pieceY = 4, 0
|
||||||
local fallTimer = 0
|
local fallTimer = 0
|
||||||
local lastKeyState = { Left = false, Right = false, Down = false, Rotate = false, Space = true }
|
local lastKeyState = { Left = false, Right = false, Down = false, Rotate = false, Space = true, Shift = false }
|
||||||
|
local hasGottenLeaderBoard = false
|
||||||
|
local leaderboard = {}
|
||||||
|
local netError = false
|
||||||
|
local nextPiece = {}
|
||||||
|
local pieceBag = {}
|
||||||
|
local heldPiece = nil
|
||||||
|
local canHold = true
|
||||||
-- enum of gameState, Main Menu, Playing, Submitting Name, Game Over
|
-- enum of gameState, Main Menu, Playing, Submitting Name, Game Over
|
||||||
|
|
||||||
local MAIN_MENU = 0
|
local MAIN_MENU = 0
|
||||||
@@ -37,9 +49,8 @@ local PLAYING = 1
|
|||||||
local GAME_OVER = 2
|
local GAME_OVER = 2
|
||||||
local SUBMITTING_NAME = 3
|
local SUBMITTING_NAME = 3
|
||||||
|
|
||||||
local gameState = SUBMITTING_NAME
|
local gameState = MAIN_MENU
|
||||||
|
|
||||||
local titleScreenBitmap
|
|
||||||
|
|
||||||
|
|
||||||
local nameTextBox
|
local nameTextBox
|
||||||
@@ -52,10 +63,16 @@ local leaderboardFrameTimer = 0
|
|||||||
local leaderboardFrameIndex = 1
|
local leaderboardFrameIndex = 1
|
||||||
local leaderboardFreamSpeed = 0.05
|
local leaderboardFreamSpeed = 0.05
|
||||||
|
|
||||||
|
--- region Bitmaps
|
||||||
|
|
||||||
|
local titleScreenBitmap
|
||||||
local gameOverBitmap
|
local gameOverBitmap
|
||||||
local pressRBitmap
|
local pressRBitmap
|
||||||
|
|
||||||
local enterNameBitmap
|
local enterNameBitmap
|
||||||
|
local boardBitmap
|
||||||
|
local controlsBitmap
|
||||||
|
--- endregion
|
||||||
|
|
||||||
local yesButton = button.new(350, 400, 100, 50, "Yes", Color.new(0, 255, 0), function()
|
local yesButton = button.new(350, 400, 100, 50, "Yes", Color.new(0, 255, 0), function()
|
||||||
print("Sending Post")
|
print("Sending Post")
|
||||||
@@ -73,17 +90,6 @@ local yesButton = button.new(350, 400, 100, 50, "Yes", Color.new(0, 255, 0), fun
|
|||||||
end)
|
end)
|
||||||
local noButton = button.new(350, 500, 100, 50, "No", Color.new(255, 0, 0), function() gameState = GAME_OVER end)
|
local noButton = button.new(350, 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
|
--- region BagStuff
|
||||||
local function shuffleBag()
|
local function shuffleBag()
|
||||||
pieceBag = {}
|
pieceBag = {}
|
||||||
@@ -177,7 +183,10 @@ end
|
|||||||
|
|
||||||
local function clearLines()
|
local function clearLines()
|
||||||
local linesCleared = 0
|
local linesCleared = 0
|
||||||
for y = GRID_HEIGHT, 1, -1 do
|
local newGrid = {}
|
||||||
|
|
||||||
|
-- Build a new grid without full lines
|
||||||
|
for y = 1, GRID_HEIGHT do
|
||||||
local full = true
|
local full = true
|
||||||
for x = 1, GRID_WIDTH do
|
for x = 1, GRID_WIDTH do
|
||||||
if grid[y][x].value == 0 then
|
if grid[y][x].value == 0 then
|
||||||
@@ -185,20 +194,29 @@ local function clearLines()
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if full then
|
if not full then
|
||||||
table.remove(grid, y)
|
table.insert(newGrid, grid[y]) -- Keep non-full rows
|
||||||
table.insert(grid, 1, {})
|
else
|
||||||
for x = 1, GRID_WIDTH do
|
|
||||||
grid[1][x] = { value = 0, color = Color.new(255, 255, 255) }
|
|
||||||
end
|
|
||||||
linesCleared = linesCleared + 1
|
linesCleared = linesCleared + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Add empty rows at the top
|
||||||
|
while #newGrid < GRID_HEIGHT do
|
||||||
|
local emptyRow = {}
|
||||||
|
for x = 1, GRID_WIDTH do
|
||||||
|
emptyRow[x] = { value = 0, color = Color.new(255, 255, 255) }
|
||||||
|
end
|
||||||
|
table.insert(newGrid, 1, emptyRow) -- Add empty row at the top
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update the grid reference
|
||||||
|
grid = newGrid
|
||||||
|
|
||||||
-- Score calculation based on cleared lines
|
-- Score calculation based on cleared lines
|
||||||
local scoreTable = { 100, 300, 500, 800 }
|
local scoreTable = { 100, 300, 500, 800 }
|
||||||
if linesCleared > 0 then
|
if linesCleared > 0 then
|
||||||
score = score + scoreTable[linesCleared] or 0
|
score = score + (scoreTable[linesCleared] or 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -212,6 +230,7 @@ local function freezePiece()
|
|||||||
end
|
end
|
||||||
clearLines()
|
clearLines()
|
||||||
newPiece()
|
newPiece()
|
||||||
|
canHold = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function rotatePiece()
|
local function rotatePiece()
|
||||||
@@ -252,6 +271,21 @@ local function drawPiece()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local function drawHeldPiece()
|
||||||
|
GameEngine:setColor(Color.new(255, 255, 255))
|
||||||
|
GameEngine:drawText("Hold:", 650, 300)
|
||||||
|
|
||||||
|
if heldPiece then
|
||||||
|
for y = 1, #heldPiece.shape do
|
||||||
|
for x = 1, #heldPiece.shape[y] do
|
||||||
|
if heldPiece.shape[y][x] == 1 then
|
||||||
|
GameEngine:setColor(heldPiece.color)
|
||||||
|
GameEngine:fillRect(650 + x * BLOCK_SIZE, 320 + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function setup_window()
|
function setup_window()
|
||||||
GameEngine:setTitle("Tetris")
|
GameEngine:setTitle("Tetris")
|
||||||
@@ -275,6 +309,11 @@ function start()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Don't blame me for logging :p
|
||||||
|
local name = GameEngine:getName()
|
||||||
|
local data = "{ \"name\": \"" .. name .. "\" }"
|
||||||
|
GameEngine:getRequest("https://api.brammie15.dev/game-open", data)
|
||||||
|
|
||||||
nextPiece = getRandomPiece()
|
nextPiece = getRandomPiece()
|
||||||
newPiece()
|
newPiece()
|
||||||
|
|
||||||
@@ -287,7 +326,6 @@ function start()
|
|||||||
|
|
||||||
titleScreenBitmap = Bitmap.new("resources/tetrisLogo.bmp", true)
|
titleScreenBitmap = Bitmap.new("resources/tetrisLogo.bmp", true)
|
||||||
|
|
||||||
|
|
||||||
-- load frame0 - 4
|
-- load frame0 - 4
|
||||||
for i = 0, 4 do
|
for i = 0, 4 do
|
||||||
print("loading frame" .. i)
|
print("loading frame" .. i)
|
||||||
@@ -305,10 +343,18 @@ function start()
|
|||||||
enterNameBitmap = Bitmap.new("resources/leaderboard/enter_name.bmp", true)
|
enterNameBitmap = Bitmap.new("resources/leaderboard/enter_name.bmp", true)
|
||||||
enterNameBitmap:SetTransparencyColor(Color.new(255, 0, 255))
|
enterNameBitmap:SetTransparencyColor(Color.new(255, 0, 255))
|
||||||
|
|
||||||
|
boardBitmap = Bitmap.new("resources/board.bmp", true)
|
||||||
|
boardBitmap:SetTransparencyColor(Color.new(255, 0, 255))
|
||||||
|
|
||||||
|
controlsBitmap = Bitmap.new("resources/controls.bmp", true)
|
||||||
|
controlsBitmap:SetTransparencyColor(Color.new(255, 0, 255))
|
||||||
end
|
end
|
||||||
|
|
||||||
function update()
|
function update()
|
||||||
-- print(GameEngine:getMouseX(), GameEngine:getMouseY())
|
-- print(GameEngine:getMouseX(), GameEngine:getMouseY())
|
||||||
|
if GameEngine:isKeyDown("ESCAPE") then
|
||||||
|
GameEngine:quit()
|
||||||
|
end
|
||||||
if gameState == PLAYING then
|
if gameState == PLAYING then
|
||||||
|
|
||||||
fallTimer = fallTimer + 1 / 60
|
fallTimer = fallTimer + 1 / 60
|
||||||
@@ -321,12 +367,12 @@ function update()
|
|||||||
fallTimer = 0
|
fallTimer = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local leftPressed = GameEngine:isKeyDown("A") or GameEngine:isKeyDown("LEFT")
|
||||||
local leftPressed = GameEngine:isKeyDown("A")
|
local rightPressed = GameEngine:isKeyDown("D") or GameEngine:isKeyDown("RIGHT")
|
||||||
local rightPressed = GameEngine:isKeyDown("D")
|
local downPressed = GameEngine:isKeyDown("S") or GameEngine:isKeyDown("DOWN")
|
||||||
local downPressed = GameEngine:isKeyDown("S")
|
local rotatePressed = GameEngine:isKeyDown("W") or GameEngine:isKeyDown("UP")
|
||||||
local rotatePressed = GameEngine:isKeyDown("W")
|
|
||||||
local spacePressed = GameEngine:isKeyDown(" ")
|
local spacePressed = GameEngine:isKeyDown(" ")
|
||||||
|
local shiftPressed = GameEngine:isKeyDown("SHIFT")
|
||||||
|
|
||||||
if leftPressed and not lastKeyState.Left and not checkCollision(pieceX - 1, pieceY, currentPiece) then
|
if leftPressed and not lastKeyState.Left and not checkCollision(pieceX - 1, pieceY, currentPiece) then
|
||||||
pieceX = pieceX - 1
|
pieceX = pieceX - 1
|
||||||
@@ -338,6 +384,18 @@ function update()
|
|||||||
pieceY = pieceY + 1
|
pieceY = pieceY + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if shiftPressed and canHold then
|
||||||
|
if heldPiece then
|
||||||
|
-- Swap current piece with held piece
|
||||||
|
currentPiece, heldPiece = heldPiece, currentPiece
|
||||||
|
else
|
||||||
|
-- Store the current piece and generate a new one
|
||||||
|
heldPiece = currentPiece
|
||||||
|
newPiece()
|
||||||
|
end
|
||||||
|
canHold = false -- Prevent multiple swaps until next piece is placed
|
||||||
|
end
|
||||||
|
|
||||||
if spacePressed and not lastKeyState.Space then
|
if spacePressed and not lastKeyState.Space then
|
||||||
while not checkCollision(pieceX, pieceY + 1, currentPiece) do
|
while not checkCollision(pieceX, pieceY + 1, currentPiece) do
|
||||||
pieceY = pieceY + 1
|
pieceY = pieceY + 1
|
||||||
@@ -345,17 +403,16 @@ function update()
|
|||||||
freezePiece()
|
freezePiece()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
if rotatePressed and not lastKeyState.Rotate then
|
if rotatePressed and not lastKeyState.Rotate then
|
||||||
rotatePiece()
|
rotatePiece()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
lastKeyState.Left = leftPressed
|
lastKeyState.Left = leftPressed
|
||||||
lastKeyState.Right = rightPressed
|
lastKeyState.Right = rightPressed
|
||||||
lastKeyState.Down = downPressed
|
lastKeyState.Down = downPressed
|
||||||
lastKeyState.Rotate = rotatePressed
|
lastKeyState.Rotate = rotatePressed
|
||||||
lastKeyState.Space = spacePressed
|
lastKeyState.Space = spacePressed
|
||||||
|
lastKeyState.Shift = shiftPressed
|
||||||
end
|
end
|
||||||
|
|
||||||
if gameState == GAME_OVER then
|
if gameState == GAME_OVER then
|
||||||
@@ -370,6 +427,28 @@ function update()
|
|||||||
leaderboardFrameTimer = 0
|
leaderboardFrameTimer = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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 = tonumber(score) })
|
||||||
|
end
|
||||||
|
table.sort(leaderboard, function(a, b) return a.score > b.score end)
|
||||||
|
end
|
||||||
|
hasGottenLeaderBoard = true
|
||||||
|
end
|
||||||
|
|
||||||
if GameEngine:isKeyDown("R") then
|
if GameEngine:isKeyDown("R") then
|
||||||
gameState = PLAYING
|
gameState = PLAYING
|
||||||
score = 0
|
score = 0
|
||||||
@@ -401,28 +480,6 @@ function update()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function drawScoreBoard()
|
function drawScoreBoard()
|
||||||
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 = tonumber(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:setColor(Color.new(255, 0, 0))
|
||||||
GameEngine:drawText("Score: " .. score, 350, 400)
|
GameEngine:drawText("Score: " .. score, 350, 400)
|
||||||
|
|
||||||
@@ -449,14 +506,19 @@ function drawScoreBoard()
|
|||||||
GameEngine:drawText(leaderboard[i].score, ScoresX, Y)
|
GameEngine:drawText(leaderboard[i].score, ScoresX, Y)
|
||||||
Y = Y + 20
|
Y = Y + 20
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
GameEngine:setColor(Color.new(255, 255, 255))
|
||||||
|
GameEngine:drawText("Loading Leaderboard...", 600, 100)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawGame()
|
function drawGame()
|
||||||
GameEngine:fillScreen(Color.new(0, 0, 0))
|
GameEngine:fillScreen(Color.new(0, 0, 0))
|
||||||
drawGrid()
|
drawGrid()
|
||||||
|
GameEngine:drawBitmap(boardBitmap, 0, 0)
|
||||||
drawGhostPiece()
|
drawGhostPiece()
|
||||||
drawPiece()
|
drawPiece()
|
||||||
|
drawHeldPiece()
|
||||||
drawNextPiece() -- Draw the next piece preview
|
drawNextPiece() -- Draw the next piece preview
|
||||||
GameEngine:setColor(Color.new(255, 255, 255))
|
GameEngine:setColor(Color.new(255, 255, 255))
|
||||||
GameEngine:drawText("Score: " .. score, 650, 50)
|
GameEngine:drawText("Score: " .. score, 650, 50)
|
||||||
@@ -481,10 +543,7 @@ end
|
|||||||
|
|
||||||
function drawMainMenu()
|
function drawMainMenu()
|
||||||
GameEngine:drawBitmap(titleScreenBitmap, 0, 0)
|
GameEngine:drawBitmap(titleScreenBitmap, 0, 0)
|
||||||
|
GameEngine:drawBitmap(controlsBitmap, 0, 0)
|
||||||
--Press space to start
|
|
||||||
GameEngine:setColor(Color.new(255, 255, 255))
|
|
||||||
GameEngine:drawText("Press Space to Start", 350, 500)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function draw()
|
function draw()
|
||||||
@@ -505,3 +564,11 @@ function draw()
|
|||||||
drawMainMenu()
|
drawMainMenu()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function quit()
|
||||||
|
print("bye")
|
||||||
|
local name = GameEngine:getName()
|
||||||
|
local data = "{ \"name\": \"" .. name .. "\" }"
|
||||||
|
print(data)
|
||||||
|
GameEngine:getRequest("https://api.brammie15.dev/game-close", data)
|
||||||
|
end
|
||||||
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 112 KiB |
BIN
resources/board.bmp
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
resources/controls.bmp
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
resources/pong/lost.bmp
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
resources/pong/mainMenu.bmp
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
resources/pong/win.bmp
Normal file
|
After Width: | Height: | Size: 234 KiB |
36
src/Game.cpp
@@ -22,6 +22,14 @@ Game::Game(const std::string &fileName): m_fileName(fileName) {
|
|||||||
// if (result == IDOK) {
|
// if (result == IDOK) {
|
||||||
// User pressed OK
|
// User pressed OK
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
m_specialKeys["SHIFT"] = VK_SHIFT;
|
||||||
|
m_specialKeys["LEFT"] = VK_LEFT;
|
||||||
|
m_specialKeys["UP"] = VK_UP;
|
||||||
|
m_specialKeys["RIGHT"] = VK_RIGHT;
|
||||||
|
m_specialKeys["DOWN"] = VK_DOWN;
|
||||||
|
m_specialKeys["ESCAPE"] = VK_ESCAPE;
|
||||||
|
m_specialKeys["RETURN"] = VK_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game::~Game() {
|
Game::~Game() {
|
||||||
@@ -59,6 +67,16 @@ void Game::Initialize() {
|
|||||||
// Set the keys that the game needs to listen to
|
// Set the keys that the game needs to listen to
|
||||||
//Get return value from Lua function
|
//Get return value from Lua function
|
||||||
auto keyList = this->FunctionCall<std::string>("set_keylist");
|
auto keyList = this->FunctionCall<std::string>("set_keylist");
|
||||||
|
|
||||||
|
//Somewhat dirty
|
||||||
|
keyList += char(VK_SHIFT);
|
||||||
|
keyList += char(VK_LEFT);
|
||||||
|
keyList += char(VK_RIGHT);
|
||||||
|
keyList += char(VK_UP);
|
||||||
|
keyList += char(VK_DOWN);
|
||||||
|
keyList += char(VK_ESCAPE);
|
||||||
|
keyList += char(VK_RETURN);
|
||||||
|
|
||||||
GAME_ENGINE->SetKeyList(keyList);
|
GAME_ENGINE->SetKeyList(keyList);
|
||||||
|
|
||||||
// tstringstream buffer;
|
// tstringstream buffer;
|
||||||
@@ -76,6 +94,7 @@ void Game::Start() {
|
|||||||
|
|
||||||
void Game::End() {
|
void Game::End() {
|
||||||
// Insert code that needs to execute when the game ends
|
// Insert code that needs to execute when the game ends
|
||||||
|
this->FunctionCall("quit");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::Paint(RECT rect) const {
|
void Game::Paint(RECT rect) const {
|
||||||
@@ -199,7 +218,9 @@ void Game::InitialiseBindings() {
|
|||||||
"setWindowRegion", &GameEngine::SetWindowRegion,
|
"setWindowRegion", &GameEngine::SetWindowRegion,
|
||||||
"setKeyList", &GameEngine::SetKeyList,
|
"setKeyList", &GameEngine::SetKeyList,
|
||||||
"setColor", &GameEngine::SetColorRGB,
|
"setColor", &GameEngine::SetColorRGB,
|
||||||
|
"quit", [](){
|
||||||
|
PostQuitMessage(0);
|
||||||
|
},
|
||||||
"messageBox", [](GameEngine &gameEngine, const std::string &message) {
|
"messageBox", [](GameEngine &gameEngine, const std::string &message) {
|
||||||
gameEngine.MessageBox(message.c_str());
|
gameEngine.MessageBox(message.c_str());
|
||||||
},
|
},
|
||||||
@@ -213,6 +234,7 @@ void Game::InitialiseBindings() {
|
|||||||
"getMouseY", &GameEngine::GetMouseY,
|
"getMouseY", &GameEngine::GetMouseY,
|
||||||
"isMouseLeftDown", &GameEngine::IsMouseLeftDown,
|
"isMouseLeftDown", &GameEngine::IsMouseLeftDown,
|
||||||
"isMouseRightDown", &GameEngine::IsMouseRightDown,
|
"isMouseRightDown", &GameEngine::IsMouseRightDown,
|
||||||
|
"getName", &GameEngine::GetTitle,
|
||||||
|
|
||||||
"drawRect", sol::overload(
|
"drawRect", sol::overload(
|
||||||
sol::resolve<bool(int, int, int, int) const>(&GameEngine::DrawRect),
|
sol::resolve<bool(int, int, int, int) const>(&GameEngine::DrawRect),
|
||||||
@@ -241,11 +263,15 @@ void Game::InitialiseBindings() {
|
|||||||
gameEngine.DrawString(std::to_string(number), x, y);
|
gameEngine.DrawString(std::to_string(number), x, y);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
"isKeyDown", [] (GameEngine &gameEngine, const std::string &key) {
|
"isKeyDown", [&] (GameEngine &gameEngine, const std::string &key) {
|
||||||
return gameEngine.IsKeyDown(_T(key[0]));
|
if(key.size() == 1){
|
||||||
|
return gameEngine.IsKeyDown(_T(key[0]));
|
||||||
|
} else {
|
||||||
|
return gameEngine.IsKeyDown(m_specialKeys[key]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"getRequest", [](GameEngine &gameEngine, const std::string &url) {
|
"getRequest", [](GameEngine &gameEngine, const std::string &url, const std::string& data = "") {
|
||||||
return gameEngine.GetRequest(url);
|
return gameEngine.GetRequest(url, data);
|
||||||
},
|
},
|
||||||
"postRequest", [](GameEngine &gameEngine, const std::string &url, const std::string &data) {
|
"postRequest", [](GameEngine &gameEngine, const std::string &url, const std::string &data) {
|
||||||
return gameEngine.PostRequest(url, data);
|
return gameEngine.PostRequest(url, data);
|
||||||
|
|||||||
@@ -91,4 +91,6 @@ private:
|
|||||||
|
|
||||||
sol::state m_state;
|
sol::state m_state;
|
||||||
std::string m_fileName;
|
std::string m_fileName;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, char> m_specialKeys;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1096,8 +1096,13 @@ bool GameEngine::Repaint() const
|
|||||||
tstring GameEngine::GetTitle() const
|
tstring GameEngine::GetTitle() const
|
||||||
{
|
{
|
||||||
#pragma warning(disable:4244)
|
#pragma warning(disable:4244)
|
||||||
return m_Title;
|
// return m_Title;
|
||||||
#pragma warning(default:4244)
|
#pragma warning(default:4244)
|
||||||
|
|
||||||
|
TCHAR computerName[MAX_COMPUTERNAME_LENGTH + 1];
|
||||||
|
DWORD size = sizeof(computerName) / sizeof(computerName[0]);
|
||||||
|
GetComputerName(computerName, &size);
|
||||||
|
return computerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
POINT GameEngine::GetWindowPosition() const
|
POINT GameEngine::GetWindowPosition() const
|
||||||
@@ -1249,8 +1254,8 @@ int GameEngine::GetControllersConnected() const {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GameEngine::GetRequest(std::string url) {
|
std::string GameEngine::GetRequest(std::string url, const std::string& data) {
|
||||||
cpr::Response r = cpr::Get(cpr::Url{std::move(url)}, cpr::Timeout{5000});
|
cpr::Response r = cpr::Get(cpr::Url{std::move(url)}, cpr::Timeout{5000}, cpr::Header{{"Content-Type", "application/json"}}, cpr::Body{data});
|
||||||
if( r.error.code == cpr::ErrorCode::OPERATION_TIMEDOUT || r.status_code != 200) {
|
if( r.error.code == cpr::ErrorCode::OPERATION_TIMEDOUT || r.status_code != 200) {
|
||||||
return "Error: Request timed out";
|
return "Error: Request timed out";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ public:
|
|||||||
|
|
||||||
int GetControllersConnected() const;
|
int GetControllersConnected() const;
|
||||||
|
|
||||||
std::string GetRequest(std::string url);
|
std::string GetRequest(std::string url, const std::string& data = "");
|
||||||
std::string PostRequest(std::string url, std::string data);
|
std::string PostRequest(std::string url, std::string data);
|
||||||
|
|
||||||
const char* AskString();
|
const char* AskString();
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
|
|||||||
|
|
||||||
int argc{ 1 };
|
int argc{ 1 };
|
||||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||||
|
|
||||||
if(argc == 2){
|
if(argc == 2){
|
||||||
//We dragged and Dropped a lua file on us
|
//We dragged and Dropped a lua file on us
|
||||||
std::string convertedString{WStringToString(argv[1])};
|
std::string convertedString{WStringToString(argv[1])};
|
||||||
|
std::cout << "Loading file: " << convertedString << std::endl;
|
||||||
GAME_ENGINE->SetGame(new Game(convertedString)); // any class that implements AbstractGame
|
GAME_ENGINE->SetGame(new Game(convertedString)); // any class that implements AbstractGame
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
GAME_ENGINE->SetGame(new Game("./lua/script.lua")); // any class that implements AbstractGame
|
GAME_ENGINE->SetGame(new Game("./Pong.lua")); // any class that implements AbstractGame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||