commit 1aa46678daec493ba48c405f7d6a996a308d6769 Author: Kari Sigurjonsson Date: Mon Aug 16 21:22:07 2021 +0000 Great Job. diff --git a/conf.lua b/conf.lua new file mode 100644 index 0000000..e16389d --- /dev/null +++ b/conf.lua @@ -0,0 +1,44 @@ + +function love.conf(t) + t.identity = "klondike" -- The name of the save directory (string) + t.version = "11.1" -- The LÖVE version this game was made for (string) + t.console = true -- Attach a console (boolean, Windows only) + t.accelerometerjoystick = false -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean) + t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean) + t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean) + + t.window.title = "Klondike" -- The window title (string) + t.window.icon = nil -- Filepath to an image to use as the window's icon (string) + t.window.width = 1920 -- The window width (number) + t.window.height = 1280 -- The window height (number) + t.window.minwidth = t.window.width -- Minimum window width if the window is resizable (number) + t.window.minheight = t.window.height-- Minimum window height if the window is resizable (number) + t.window.borderless = false -- Remove all border visuals from the window (boolean) + t.window.resizable = false -- Let the window be user-resizable (boolean) + t.window.fullscreen = false -- Enable fullscreen (boolean) + t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string) + t.window.vsync = true -- Enable vertical sync (boolean) + t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number) + t.window.display = 1 -- Index of the monitor to show the window in (number) + t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) + t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) + t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) + + t.modules.audio = false -- Enable the audio module (boolean) + t.modules.event = true -- Enable the event module (boolean) + t.modules.graphics = true -- Enable the graphics module (boolean) + t.modules.image = true -- Enable the image module (boolean) + t.modules.joystick = false -- Enable the joystick module (boolean) + t.modules.keyboard = true -- Enable the keyboard module (boolean) + t.modules.math = true -- Enable the math module (boolean) + t.modules.mouse = true -- Enable the mouse module (boolean) + t.modules.physics = false -- Enable the physics module (boolean) + t.modules.sound = false -- Enable the sound module (boolean) + t.modules.system = true -- Enable the system module (boolean) + t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update + t.modules.touch = false -- Enable the touch module (boolean) + t.modules.video = false -- Enable the video module (boolean) + t.modules.window = true -- Enable the window module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) +end + diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..d383cc9 --- /dev/null +++ b/main.lua @@ -0,0 +1,542 @@ +local lg = love.graphics +lg.setDefaultFilter("nearest","nearest") + +local mx, my = 0, 0 +local mb = false + +local cardOffX, cardOffY = 0, 0 + +local marginX = 20 +local marginY = 20 +local padding = 10 + +local cardImage = lg.newImage("mysa.png") +local cardImageW = cardImage:getWidth() +local cardImageH = cardImage:getHeight() + +local CARDW = 200 +local CARDH = 300 + +local slotX = function(v) return (v * CARDW) + ((v + 1) * padding) + marginX end +local slotY = function(v) return (v * CARDH) + ((v + 1) * padding) + marginY end + +love.window.setMode(slotX(8), slotY(5)) + +local cardQuads = {} + +for i = 1, 57 do + cardQuads[i] = lg.newQuad(((i-1) % 13) * CARDW, math.floor((i-1) / 13) * CARDH, CARDW, CARDH, cardImageW, cardImageH) +end + +local backId = 54 + +local Heart = 1 +local Spade = 2 +local Diamond = 3 +local Club = 4 + +local suitNames = { + "Hearts", "Spade", "Diamonds", "Clubs" +} + +local writeln = function(...) + local t = {...} + for k, v in ipairs(t) do + t[k] = tostring(v) + end + io.stderr:write(table.concat(t, "\t"), "\n") +end + +--************************************************************************************************* + +local FaceUp = true +local FaceDown = false + +local Card = { + id = 1, + x = 0, + y = 0, + w = CARDW, + h = CARDH +} + +function Card:show() + self.showing = FaceUp +end + +function Card:move(x, y) + self.x = x + self.y = y +end + +function Card:hitTest(x, y) + return not ( + (x < self.x) or (x > (self.x + self.w)) + or (y < self.y) or (y > (self.y + self.h)) + ) +end + +function Card:intersection(other) + +--[[ +left = max(r1.left, r2.left) +right = min(r1.right, r2.right) +bottom = max(r1.bottom, r2.bottom) +top = min(r1.top, r2.top) + +Then, if intersection is not empty (left < right && bottom < top), +subtract it from the common area of two rectangles: r1.area + r2.area - intersection.area. + +PS: + + Assumption 1: rectangles are aligned by the coordinate axes, that's usually the case. + Assumption 2: y axis here increases upwards, for example, in a graphics application, the y axis increases downwards, you may need to use: + + bottom = min(r1.bottom, r2.bottom) + top = max(r1.top, r2.top) +--]] +end + + +function Card:draw() + lg.setColor(1, 1, 1, 1) + if (self.showing == FaceUp) then + lg.draw(cardImage, cardQuads[self.id], self.x, self.y) + else + lg.draw(cardImage, cardQuads[backId], self.x, self.y) + end + + --lg.setColor(0.05, 0.05, 0.05, 1) + --lg.setColor(0.15, 0.15, 0.15, 1) + lg.setColor(0.25, 0.25, 0.25, 1) + lg.rectangle("line", self.x, self.y, CARDW, CARDH, 4) +end + +function Card:new(id, x, y, showing) + local o = {} + for k, v in pairs(self) do + o[k] = v + end + + o.id = id + o.rank = math.floor(((id - 1) % 13) + 1); + o.suit = math.floor(((id - 1) / 13) + 1); + o.showing = showing + + return o +end + +--************************************************************************************************* + +local Deck = { + x = 0, + y = 0 +} + +function Deck:empty() + local j = #self + for i = 1, j do + self[i] = nil + end +end + +function Deck:push(card) + card.owner = self + card.x = self.x + card.y = self.y + self[#self + 1] = card +end + +function Deck:layout() + if (self.tableau) then + local yoff = self.y + for _, card in ipairs(self) do + card.x = self.x + card.y = yoff + yoff = yoff + ((card.showing == FaceUp) and 38 or 10) + end + else + for _, card in ipairs(self) do + card.x = self.x + card.y = self.y + end + + end +end + +--Fisher–Yates shuffle. +function Deck:shuffle() + for i = #self-1, 1, -1 do + j = math.random(0, i) + + local tc = self[i+1] + self[i+1] = self[j+1] + self[j+1] = tc + end +end + +function Deck:move(x, y) + self.x = x + self.y = y + + for _, card in ipairs(self) do + card.x = x + card.y = y + end + + self:layout() +end + +function Deck:findCardUnderMouse(x, y) + if (#self == 0) then + return + end + + for i = #self, 1, -1 do + local card = self[i] + if (card:hitTest(x, y)) then + return card + end + end +end + +function Deck:hitTest(x, y) + return not ( + (x < self.x) or (x > (self.x + self.w)) + or (y < self.y) or (y > (self.y + self.h)) + ) +end + +function Deck:draw() + if (#self == 0) then + lg.setColor(1, 1, 0, 1) + lg.rectangle("line", self.x, self.y, self.w, self.h, 15) + else + for _, card in ipairs(self) do + card:draw() + end + end +end + +function Deck:topCard() + return self[#self] +end + +function Deck:pop() + local o = self[#self] + self[#self] = nil + return o +end + +function Deck:popAt(atCard) + if (#self == 0) then return end + + local deck = Deck:new(mx, my, true); + + local count = #self + for i = 1, count do + if (self[i] == atCard) then + for j = i, count do + deck:push(self[j]) + self[j] = nil + end + break + end + end + + return deck +end + +function Deck:new(x, y, tableau, cards) + local o = {} + + for k, v in pairs(self) do + o[k] = v + end + + o.x = x + o.y = y + o.w = CARDW + o.h = CARDH + o.tableau = tableau + + if (cards) then + for _, card in ipairs(cards) do + o:push(card) + end + end + + return o +end + +--************************************************************************************************* + +local stock = Deck:new(slotX(0), slotY(0), false) +local waste = Deck:new(slotX(1), slotY(0), false) + +local foundation1 = Deck:new(slotX(3), slotY(0), false) +local foundation2 = Deck:new(slotX(4), slotY(0), false) +local foundation3 = Deck:new(slotX(5), slotY(0), false) +local foundation4 = Deck:new(slotX(6), slotY(0), false) + +local tableau1 = Deck:new(slotX(0), slotY(1), true) +local tableau2 = Deck:new(slotX(1), slotY(1), true) +local tableau3 = Deck:new(slotX(2), slotY(1), true) +local tableau4 = Deck:new(slotX(3), slotY(1), true) +local tableau5 = Deck:new(slotX(4), slotY(1), true) +local tableau6 = Deck:new(slotX(5), slotY(1), true) +local tableau7 = Deck:new(slotX(6), slotY(1), true) + +local handDeck = nil +local undoDeck = nil + +local decks = { + stock, waste, + foundation1, foundation2, foundation3, foundation4, + tableau1, tableau2, tableau3, tableau4, tableau5, tableau6, tableau7, +} + +local findDeckUnderMouse = function(x, y) + for _, deck in ipairs(decks) do + if (#deck > 0) then + local card = deck:findCardUnderMouse(x, y) + if (card) then + return deck, card + end + elseif (deck:hitTest(x, y)) then + return deck + end + end +end + +local undo = function() + if (not handDeck or not undoDeck) then return end + + local count = #handDeck + for _, card in ipairs(handDeck) do + undoDeck:push(card) + end + + undoDeck:layout() + + handDeck = nil + undoDeck = nil +end + +local newGame = function() + for _, deck in ipairs(decks) do + deck:empty() + end + + for i = 1, 52 do + stock:push(Card:new(i, 0, 0, FaceDown)) + end + + stock:shuffle() + + for i = 1, 7 do + for j = 1, i do + local card = stock:pop() + card.showing = (i == j) + decks[6 + i]:push(card) + end + end + + for _, deck in ipairs(decks) do + deck:layout() + end +end + +love.load = function() + math.randomseed(os.time()) + + love.graphics.setLineStyle("rough") + love.graphics.setLineWidth(1) + love.graphics.setPointSize(1) + + newGame() +end + +love.mousemoved = function(x, y) + mx = x + my = y +end + +local tableaus = { tableau1, tableau2, tableau3, tableau4, tableau5, tableau6, tableau7 } +local foundations = { foundation1, foundation2, foundation3, foundation4 } + +local isAnyOf = function(t, o) + for _, v in ipairs(t) do + if (v == o) then + return true + end + end +end + +love.mousepressed = function(x, y, b) + if (b == 1) then + if (hooverDeck == waste) then + if (hooverCard) then + undoDeck = waste + cardOffX = x - hooverCard.x + cardOffY = y - hooverCard.y + handDeck = Deck:new(x - cardOffX, y - cardOffY, true) + local card = hooverDeck:pop() + card.showing = FaceUp + handDeck:push(card) + handDeck:layout() + end + elseif (hooverDeck == stock) then + if (#stock == 0) then + --Click on empty stock restores it from waste. + local card = waste:pop() + while card do + card.showing = FaceDown + stock:push(card) + card = waste:pop() + end + + stock:shuffle() + stock:layout() + waste:layout() + else + --Click on face down card moves it face-up to waste. + local card = stock:pop() + card.showing = FaceUp + waste:push(card) + waste:layout() + stock:layout() + end + + -- handDeck = nil + elseif (hooverCard and (hooverCard == hooverDeck:topCard()) and (hooverCard.showing == FaceDown)) then + --Click on face down card flips it. + hooverCard.showing = FaceUp + hooverDeck:layout() + elseif (hooverCard and hooverCard.showing == FaceUp) then + undoDeck = hooverDeck + cardOffX = x - hooverCard.x + cardOffY = y - hooverCard.y + handDeck = Deck:new(x - cardOffX, y - cardOffY, true, hooverDeck:popAt(hooverCard)) + hooverDeck:layout() + end + elseif (b == 2) then + if (hooverCard and (hooverCard == hooverDeck:topCard()) and (hooverCard.showing == FaceUp)) then + for k, deck in ipairs(foundations) do + local tc = deck:topCard() + if ((#deck == 0 and hooverCard.rank == 1) or (tc and hooverCard.rank == tc.rank + 1 and hooverCard.suit == tc.suit)) then + local card = hooverDeck:pop() + card.showing = FaceUp + deck:push(card) + + return --RETURN + end + end + end + end +end + +local isOddCard = function(card1, card2) + local suit1 = card1.suit + local suit2 = card2.suit + + return ( + (((suit1 == Spade) or (suit1 == Club)) and ((suit2 == Heart) or (suit2 == Diamond))) + or (((suit1 == Heart) or (suit1 == Diamond)) and ((suit2 == Spade) or (suit2 == Club))) + ) +end + +love.mousereleased = function(x, y, b) + if (b ~= 1) then return end + + if (handDeck and hooverDeck) then + if (isAnyOf(tableaus, hooverDeck)) then + if (#hooverDeck == 0) then + --Drop on empty tableau, only king allowed. + if (handDeck[1].rank == 13) then + local count = #handDeck + for _, card in ipairs(handDeck) do + hooverDeck:push(card) + end + else + undo() + end + elseif (hooverDeck:topCard() == hooverCard) then + --Must drop on the top card and only odd-colored down-going cards allowed. + if ( + isOddCard(hooverDeck[#hooverDeck], handDeck[1]) + and (handDeck[1].rank == hooverCard.rank - 1) + ) then + for _, card in ipairs(handDeck) do + hooverDeck:push(card) + end + else + undo() + end + else + undo() + end + + hooverDeck:layout() + elseif (#handDeck == 1 and isAnyOf(foundations, hooverDeck)) then + --Drop on foundation, only one card at a time. Only up-going same sort allowed. + if (#hooverDeck == 0) then + --Drop on empty foundation. Only aces allowed. + if (handDeck[1].rank == 1) then + hooverDeck:push(handDeck[1]) + else + undo() + end + elseif ( + hooverDeck[#hooverDeck].rank == handDeck[1].rank - 1 + and hooverDeck[#hooverDeck].suit == handDeck[1].suit + ) then + hooverDeck:push(handDeck[1]) + else + undo() + end + else + undo() + end + + hooverDeck:layout() + elseif (handDeck) then + undo() + end + + handDeck = nil + undoDeck = nil + hooverDeck = nil + hooverCard = nil +end + +love.keypressed = function(k) + if (k == "f1") then + newGame() + elseif (k == "f3") then + backId = 53 + elseif (k == "f4") then + backId = 54 + end +end + +love.update = function(dt) + hooverDeck, hooverCard = findDeckUnderMouse(mx, my) + if (handDeck) then + handDeck:move(mx - cardOffX, my - cardOffY) + end +end + +love.draw = function() + lg.setColor(1, 1, 1, 1) + lg.setBackgroundColor(0.20, 0.40, 0.20, 1) + + for _, deck in ipairs(decks) do + deck:draw() + end + + if (handDeck) then + handDeck:draw() + end +end + diff --git a/mysa.png b/mysa.png new file mode 100644 index 0000000..984ff1e Binary files /dev/null and b/mysa.png differ