Commit Diff
Commit:
a0b1acd8aa11bf5605815feb7311fde030f6a06e
From:
Omar Polo <op@omarpolo.com>
Date:
Sun Dec 18 18:57:43 2022 UTC
Message:
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
Omar Polo