commit a0b1acd8aa11bf5605815feb7311fde030f6a06e from: Omar Polo date: Sun Dec 18 18:57:43 2022 UTC initial import commit - /dev/null commit + a0b1acd8aa11bf5605815feb7311fde030f6a06e blob - /dev/null blob + 7a9681b36a6b2e8e23e8a7615c372217c16924b4 (mode 644) --- /dev/null +++ LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2022 Omar Polo +Copyright (c) 2015 rxi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. blob - /dev/null blob + 8c0e3988bf12bedab0af8f259510032d7f77fec9 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,18 @@ +FENNEL = fennel-5.1 +LOVE = love + +.PHONY: all run clean + +all: run + +.SUFFIXES: .lua .fnl +.fnl.lua: + ${FENNEL} --compile $< > $@ || (rm -f $@ && false) + +main.lua: main.fnl + +run: main.lua + ${LOVE} . + +clean: + rm -f main.lua blob - /dev/null blob + 545b255393ad3e3bd4cc3c2d3252ca89a61bc360 (mode 644) --- /dev/null +++ README.md @@ -0,0 +1,14 @@ +# SN4K3 + +> a dead simple snake reimplementation written in fennel, for love2d + +Needs fennel and love installed. Run: + + $ make + +to "compile" the game and run it. Path to the fennel compiler and +love executable can be provided as: + + $ make FENNEL=/path/to/fennel LOVE=/path/to/love + +and defaults to `fennel-5.1` and `love`. blob - /dev/null blob + d354fd9e6160b6bd1fdd2bc4e19f27b2e11fab41 (mode 644) --- /dev/null +++ main.fnl @@ -0,0 +1,175 @@ +;; sn4k3 +;; +;; Copyright (c) 2022 Omar Polo +;; +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the MIT license. See LICENSE for +;; details. + +(local tick (require :tick)) + +(var game-state :menu) ;; :menu :game :pause :over +(var tick-event nil) + +(var score 0) +(var snake-body []) +(var candies []) + +(var direction :right) + +(local ratio 20) + +(local width 30) +(local height 20) + +(local window-width (* ratio width)) +(local window-height (* ratio height)) + +(fn hit? [x y seq] + "Return the index of the element of seq that is on (x, y), or nil." + (accumulate [index nil + i r (ipairs seq) + &until index] + (if (and (= x (. r :x)) + (= y (. r :y))) + i))) + +(fn place-candy [] + (var done? false) + (while (not done?) + (let [x (math.random (- width 1)) + y (math.random (- height 1))] + (when (and (not (hit? x y snake-body)) + (not (hit? x y candies))) + (set done? true) + (table.insert candies {: x : y}))))) + +(fn wrap [val max] + (if (< val 0) + (- max 1) + (< val max) + val + 0)) + +(fn compute-step [{: x : y}] + (match direction + :up (values x (wrap (- y 1) height)) + :left (values (wrap (- x 1) width) y) + :right (values (wrap (+ x 1) width) y) + :down (values x (wrap (+ y 1) height)))) + +(fn move-snake [] + (let [(x y) (compute-step (. snake-body 1))] + (if (hit? x y snake-body) + (do + (print "GAME OVER!") + (print "score:" score) + (set game-state :over)) + (do + (match (hit? x y candies) + nil (table.remove snake-body) + i (do (table.remove candies i) + (place-candy) + (set score (+ 1 score)))) + (table.insert snake-body 1 {: x : y}))))) + +(fn centered-text [x y rectw recth scale text] + (let [font (love.graphics.getFont) + tw (font:getWidth text) + th (font:getHeight)] + (love.graphics.print text + (+ (* x ratio) (* (/ rectw 2) ratio)) + (+ (* y ratio) (* (/ recth 2) ratio)) + 0 scale scale + (/ tw 2) + (/ th 2)))) + +(fn menu-draw [] + (love.graphics.setColor 0 1 0 1) + (centered-text 0 0 width height 2 "SN4K3") + (centered-text 0 6 width height 1 "press any key to play")) + +(fn game-draw [] + (love.graphics.setColor 255 255 255) + (each [_ r (ipairs snake-body)] + (let [{: x : y} r] + (love.graphics.rectangle :fill (* ratio x) (* ratio y) 20 20))) + + (love.graphics.setColor 255 0 0) + (each [_ r (ipairs candies)] + (let [{: x : y} r] + (love.graphics.rectangle :fill (* ratio x) (* ratio y) 20 20)))) + +(fn pause-draw [] + (game-draw) + (love.graphics.setColor 0 1 0 1) + (centered-text 0 0 width height 2 "PAUSED") + (centered-text 0 6 width height 1 "press any key to resume")) + +(fn over-draw [] + (game-draw) + (love.graphics.setColor 0 1 0 1) + (centered-text 0 0 width height 2 "GAME OVER!") + (centered-text 0 6 width height 1 (string.format "score: %03d" score))) + +(fn game-init [] + (set snake-body [{:x 14 :y 9} + {:x 13 :y 9} + {:x 12 :y 9} + {:x 11 :y 9}]) + (set candies []) + (for [i 1 5] + (place-candy)) + (when tick-event + (tick.remove tick-event)) + (set tick-event (tick.recur move-snake .25))) + +(fn love.load [] + (love.window.updateMode window-width window-height {:fullscreen false})) + +(fn love.draw [] + (love.graphics.setColor 0 0 0) + (love.graphics.rectangle :fill 0 0 window-width window-height) + + (if (= game-state :menu) + (menu-draw) + + (= game-state :game) + (game-draw) + + (= game-state :pause) + (pause-draw) + + (= game-state :over) + (over-draw))) + +(fn key-ok? [key] + (and (not= key "lctrl") + (not= key "lshift") + (not= key "lgui") + (not= key "lalt") + (not= key "rctrl") + (not= key "rshift") + (not= key "rgui") + (not= key "ralt") + (not= key "scrollock"))) + +(fn love.keypressed [key] + (if (or (= game-state :menu) + (= game-state :over)) + (if (= key :q) (love.event.quit 0) + (key-ok? key) (do (set game-state :game) + (game-init))) + + (= game-state :game) + (match key + (where (or :up :down :left :right)) (set direction key) + (where (or :p :escape)) (set game-state :pause)) + + (= game-state :pause) + (when (key-ok? key) + (set game-state :game)))) + +(fn love.update [dt] + (when (= game-state :game) + (tick.update dt))) blob - /dev/null blob + 57e5cb4d8c8fcb00ecf553193c6b04ec205aff17 (mode 644) --- /dev/null +++ tick.lua @@ -0,0 +1,166 @@ +-- +-- tick +-- +-- Copyright (c) 2015 rxi +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. +-- + +local tick = { _version = "0.1.1" } +tick.__index = tick + + +local iscallable = function(x) + if type(x) == "function" then return true end + local mt = getmetatable(x) + return mt and mt.__call ~= nil +end + +local noop = function() +end + + +local event = {} +event.__index = event + +function event.new(parent, fn, delay, recur, err) + err = err or 0 + -- Create and return event + return setmetatable({ + parent = parent, + delay = delay, + timer = delay + err, + fn = fn, + recur = recur, + }, event) +end + + +function event:after(fn, delay) + -- Error check + if self.recur then + error("cannot chain a recurring event") + end + -- Chain event + local oldfn = self.fn + local e = event.new(self.parent, fn, delay, false) + self.fn = function() + oldfn() + e.timer = e.timer + self.parent.err + self.parent:add(e) + end + return e +end + + +function event:stop() + tick.remove(self.parent, self) +end + + + +function tick.group() + return setmetatable({ err = 0 }, tick) +end + + +function tick:add(e) + self[e] = true + table.insert(self, e) + return e +end + + +function tick:remove(e) + if type(e) == "number" then + -- Remove and return event + local idx = e + e = self[idx] + self[e] = nil + self[idx] = self[#self] + table.remove(self) + return e + end + self[e] = false + for i, v in ipairs(self) do + if v == e then + return self:remove(i) + end + end +end + + +function tick:update(dt) + for i = #self, 1, -1 do + local e = self[i] + e.timer = e.timer - dt + while e.timer <= 0 do + if e.recur then + e.timer = e.timer + e.delay + else + self:remove(i) + end + self.err = e.timer + e.fn() + if not e.recur then + break + end + end + end + self.err = 0 +end + + +function tick:event(fn, delay, recur) + delay = tonumber(delay) + -- Error check + if not iscallable(fn) then + error("expected `fn` to be callable") + end + if type(delay) ~= "number" then + error("expected `delay` to be a number") + end + if delay < 0 then + error("expected `delay` of zero or greater") + end + -- If, factoring in the timing error, the event should happen *now* the + -- function is immediately called and the error is temporarily carried + -- through. This assures nested events with delays shorter than the update() + -- delta-time do not accumulate error; several nested events with very small + -- delays may end up being called on the same frame. A dummy event is created + -- and returned so :after() still functions correctly. + local d = delay + self.err + if d < 0 then + local err = self.err + self.err = d + fn() + self.err = err + return self:add(event.new(self, noop, delay, recur, self.err)) + end + -- Create, add and return a normal event + return self:add(event.new(self, fn, delay, recur, self.err)) +end + + +function tick:delay(fn, delay) + return self:event(fn, delay, false) +end + + +function tick:recur(fn, delay) + return self:event(fn, delay, true) +end + + +local group = tick.group() + +local bound = { + update = function(...) return tick.update(group, ...) end, + delay = function(...) return tick.delay (group, ...) end, + recur = function(...) return tick.recur (group, ...) end, + remove = function(...) return tick.remove(group, ...) end, +} +setmetatable(bound, tick) + +return bound