examples: add a simple leapfrog game
authorJean Privat <jean@pryen.org>
Wed, 6 Jun 2012 19:31:12 +0000 (15:31 -0400)
committerJean Privat <jean@pryen.org>
Thu, 25 Oct 2012 15:53:38 +0000 (11:53 -0400)
Signed-off-by: Jean Privat <jean@pryen.org>

examples/leapfrog/Makefile [new file with mode: 0644]
examples/leapfrog/curses.nit [new file with mode: 0644]
examples/leapfrog/curses.nit.c [new file with mode: 0644]
examples/leapfrog/curses.nit.h [new file with mode: 0644]
examples/leapfrog/game.nit [new file with mode: 0644]
examples/leapfrog/leapfrog.nit [new file with mode: 0644]

diff --git a/examples/leapfrog/Makefile b/examples/leapfrog/Makefile
new file mode 100644 (file)
index 0000000..73efac5
--- /dev/null
@@ -0,0 +1,2 @@
+leapfrog:
+       ../../bin/nitc leapfrog.nit --cc-lib-name curses
diff --git a/examples/leapfrog/curses.nit b/examples/leapfrog/curses.nit
new file mode 100644 (file)
index 0000000..af98bcb
--- /dev/null
@@ -0,0 +1,43 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Curses for Nit
+module curses
+
+# A curse windows
+extern Window
+       # Initialize the screen
+       new is extern "initscr"
+
+       # print a string somewhere
+       # NOTE: as with the curses API, the position is (y,x)
+       fun mvaddstr(y,x: Int, str: String) is extern import String::to_cstring
+
+       fun refresh is extern
+       fun wclear is extern
+       fun delwin is extern
+       fun endwin is extern
+end
+
+redef class Sys
+       # Wait a specific number of second and nanoseconds
+       # FIXME: should not be in the curses module
+       fun nanosleep(sec, nanosec: Int) is extern
+end
+
+redef class Stdin
+       # Is these something to read? (non blocking)
+       # FIXME: should not be in the curses module
+       fun poll_in: Bool is extern
+end
diff --git a/examples/leapfrog/curses.nit.c b/examples/leapfrog/curses.nit.c
new file mode 100644 (file)
index 0000000..0e30dbb
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+       Extern implementation of Nit module curses
+       File initially created by nits to customize type of extern classes
+*/
+
+#include "curses.nit.h"
+#include <time.h>
+#include <poll.h>
+
+/*
+C implementation of curses::Window::init
+*/
+Window new_Window___impl(  )
+{
+       WINDOW *res;
+       res = initscr();
+       if (res == NULL) {
+               fprintf(stderr, "Error initialising ncurses.\n");
+               exit(EXIT_FAILURE);
+       }
+       raw();
+       keypad(res, TRUE);
+       noecho();
+}
+
+/*
+C implementation of curses::Window::mvaddstr
+
+Imported methods signatures:
+       char * String_to_cstring( String recv ) for string::String::to_cstring
+*/
+void Window_mvaddstr___impl( Window recv, bigint y, bigint x, String str )
+{
+       char *c_string = String_to_cstring( str );
+       mvaddstr(y, x, c_string);
+}
+
+/*
+C implementation of curses::Window::refresh
+*/
+void Window_refresh___impl( Window recv )
+{
+       refresh();
+}
+
+/*
+C implementation of curses::Window::wclear
+*/
+void Window_wclear___impl( Window recv ) {
+       wclear(recv);
+}
+
+/*
+C implementation of curses::Window::delwin
+*/
+void Window_delwin___impl( Window recv )
+{
+       delwin(recv);
+}
+
+/*
+C implementation of curses::Window::endwin
+*/
+void Window_endwin___impl( Window recv )
+{
+       endwin();
+}
+
+/*
+C implementation of curses::Sys::nanosleep
+*/
+void Sys_nanosleep___impl( Sys recv, bigint sec, bigint nanosec ) {
+       const struct timespec req = {sec, nanosec};
+       nanosleep(&req, NULL);
+}
+
+/*
+C implementation of curses::Stdin::poll_in
+*/
+int Stdin_poll_in___impl( Stdin recv ) {
+       struct pollfd fd = {0, POLLIN, 0};
+       int res = poll(&fd, 1, 0);
+       if (res == -1) {
+               endwin();
+               perror("Error poll stdin");
+               exit(EXIT_FAILURE);
+       }
+       return res > 0;
+}
diff --git a/examples/leapfrog/curses.nit.h b/examples/leapfrog/curses.nit.h
new file mode 100644 (file)
index 0000000..6318fc4
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+       Extern implementation of Nit module curses
+       File initially created by nits to implement extern methods body
+*/
+
+#ifndef CURSES_NIT_H
+#define CURSES_NIT_H
+
+#include <curses._nitni.h>
+#include <curses.h>
+
+#define Window void*
+Window new_Window___impl(  );
+void Window_mvaddstr___impl( Window recv, bigint y, bigint x, String str );
+void Window_refresh___impl( Window recv );
+void Window_wclear___impl( Window recv );
+void Window_delwin___impl( Window recv );
+void Window_endwin___impl( Window recv );
+void Sys_nanosleep___impl( Sys recv, bigint sec, bigint nanosec );
+int Stdin_poll_in___impl( Stdin recv );
+#endif
diff --git a/examples/leapfrog/game.nit b/examples/leapfrog/game.nit
new file mode 100644 (file)
index 0000000..a0e2a83
--- /dev/null
@@ -0,0 +1,115 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# 2D management of game elements
+#
+# FIXME: game is a bad name
+# TODO: collision framework (with quad tree?)
+module game
+
+# The root class of the living objects (sprites, group of sprites, etc.)
+abstract class LiveObject
+       # Compute the position, state and appearance.
+       fun update do end
+
+       # Controls whether `update' and `draw' are automatically called by `LiveGroup'
+       var exists writable = true
+
+       # Redefine this method to asks how to draw on a view
+       fun draw(view: View) is abstract
+end
+
+
+# The basic atomic living and moving object.
+#
+# A sprite has a position and a velocity
+class Sprite
+       super LiveObject
+
+       # x coordinate of the top-left point
+       var x: Int writable = 0
+
+       # y coordinate of the top-left point
+       var y: Int writable = 0
+
+       # width of the sprite
+       var width: Int writable = 100
+
+       # height of the sprite
+       var height: Int writable = 100
+
+       # x velocity (applied by `update')
+       var vx: Int writable = 0
+
+       # y velocity (applied by `update')
+       var vy: Int writable = 0
+
+       redef fun update
+       do
+               self.x += self.vx
+               self.y += self.vy
+       end
+
+       redef fun draw(view) do view.draw_sprite(self)
+
+       # Is self overlaps (or contains) an other sprite
+       # `x', `y', `width', and `height' of both sprites are considered
+       fun overlaps(other: Sprite): Bool
+       do
+               return self.x+self.width > other.x and self.x < other.x+other.width and self.y+self.height > other.y and self.y < other.y+other.width
+       end
+end
+
+# Organizational class to manage groups of sprites and other live objects.
+class LiveGroup[E: LiveObject]
+       super LiveObject
+       super List[E]
+
+       init
+       do
+       end
+
+       # Recursively update each live objects that `exists'
+       redef fun update
+       do
+               for x in self do if x.exists then x.update
+       end
+
+       # Recursively draw each live objects that `exists'
+       redef fun draw(view)
+       do
+               for x in self do if x.exists then x.draw(view)
+       end
+end
+
+# Abstract view do draw sprites
+#
+# Concrete views are specific for each back-end.
+# View can also be used to implements camera and other fun things.
+interface View
+       # Draw a specific sprite on the view
+       #
+       # This method must be implemented for each specific view.
+       # A traditional way of implementation is to use a double-dispatch mechanism
+       #
+       # Exemple:
+       #     class MyView
+       #         redef fun draw_sprite(s) do s.draw_on_myview(self)
+       #     end
+       #     redef class Sprite
+       #         # How to draw a sprite on my specific view
+       #         fun draw_on_myview(myview: MyView) is abstract
+       #     end
+       fun draw_sprite(s: Sprite) is abstract
+end
diff --git a/examples/leapfrog/leapfrog.nit b/examples/leapfrog/leapfrog.nit
new file mode 100644 (file)
index 0000000..cfecc7e
--- /dev/null
@@ -0,0 +1,288 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# game of leapfrog: be a sheep and avoid the duck to grab apples
+#
+# This module is an example of a simple game using a curses backend
+module leapfrog
+
+import curses
+import game
+
+# A simple game view in a curses windows
+class CursesView
+       super View
+
+       # The associated curses window
+       var window: Window
+
+       redef fun draw_sprite(s: Sprite) do s.draw_on_curses(window)
+end
+
+redef class Sprite
+       # Drawing of a sprite in the curse windows
+       fun draw_on_curses(window: Window) is abstract
+end
+
+# A falling apple
+# If the sheep grab it, it scores one point.
+class Apple
+       super Sprite
+
+       redef fun draw_on_curses(window)
+       do
+               var x = self.x/100
+               var y = self.y/100
+               window.mvaddstr(y, x,  "o")
+       end
+end
+
+# Common class for the sheep and the duck
+class Animal
+       super Sprite
+
+       # Is the sprite stunned?
+       # 0 if no, >0 if stunned.
+       # The value indicate the number of step that remain to be stunt
+       #
+       # If a animal is stunned, it cannot move horizontally
+       var stunt_ttl: Int writable = 0
+
+       # Common update for animal
+       # handle stunt and edge collision
+       redef fun update
+       do
+               if stunt_ttl > 0 then
+                       stunt_ttl -= 1
+               else
+                       self.x += self.vx
+               end
+               self.y += self.vy
+
+               if self.x + self.width > 7900 then
+                       self.vx = -self.vx
+                       self.x = 7900 - self.width
+               else if self.x < 0 then
+                       self.vx = -self.vx
+                       self.x = 0
+               end
+       end
+end
+
+# The player sprite
+class Sheep
+       super Animal
+
+       # Which frame to show when walking
+       var leg_state: Int = 0
+
+       # Is the sheep currently jumping (or falling)
+       var is_jumping = false
+
+       init
+       do
+               self.y = 2000 - 200
+               self.vx = 100
+               self.width = 600
+               self.height = 200
+       end
+
+       redef fun update
+       do
+               super
+
+               if is_jumping then
+                       if self.y + self.height > 2000 then
+                               is_jumping = false
+                               self.vy = 0
+                               self.y = 2000 - self.height
+                       else
+                               self.vy += 10 # gravity
+                       end
+               end
+               if not is_jumping then
+                       self.leg_state = 1 - self.leg_state # change leg
+               end
+       end
+
+       # Try to jump is possible (ie if walking and not stunt)
+       fun jump
+       do
+               if is_jumping or stunt_ttl > 0 then return
+               self.vy = -100
+               self.is_jumping = true
+       end
+
+       redef fun draw_on_curses(window)
+       do
+               var x = self.x/100
+               var y = self.y/100
+               if self.is_jumping then
+                       if self.vy > 0 then
+                               # falling
+                               if self.vx > 0 then
+                                       window.mvaddstr(y, x,   "'---@>")
+                                       window.mvaddstr(y+1, x, " \\-\\'")
+                               else
+                                       window.mvaddstr(y, x,   "<@---'")
+                                       window.mvaddstr(y+1, x, " '/-/")
+                               end
+                       else
+                               # jumping
+                               if self.vx > 0 then
+                                       window.mvaddstr(y, x,   ",---@>")
+                                       window.mvaddstr(y+1, x, " /-/'")
+                               else
+                                       window.mvaddstr(y, x,   "<@---,")
+                                       window.mvaddstr(y+1, x, " '\\-\\")
+                               end
+                       end
+               else if self.vx > 0 then
+                       if self.leg_state == 0 then
+                               window.mvaddstr(y, x,   ",---@>")
+                               window.mvaddstr(y+1, x, " /-|'")
+                       else
+                               window.mvaddstr(y, x,   ",---@>")
+                               window.mvaddstr(y+1, x, " |-\\'")
+                       end
+               else
+                       if self.leg_state == 0 then
+                               window.mvaddstr(y, x,   "<@---,")
+                               window.mvaddstr(y+1, x, " '/-|")
+                       else
+                               window.mvaddstr(y, x,   "<@---,")
+                               window.mvaddstr(y+1, x, " '|-\\")
+                       end
+               end
+       end
+end
+
+# The nemesis of the sheep.
+# It just go back and forth
+class Duck
+       super Animal
+       init
+       do
+               self.y = 2000 - 200
+               self.x = 7900 - 400
+               self.vx = -80
+               self.width = 400
+               self.height = 200
+       end
+
+       redef fun draw_on_curses(window)
+       do
+               var x = self.x/100
+               var y = self.y/100
+
+               if self.vx > 0 then
+                       window.mvaddstr(y, x,   "  @<")
+                       window.mvaddstr(y+1, x, "<__)")
+               else
+                       window.mvaddstr(y, x,   ">@")
+                       window.mvaddstr(y+1, x, "(__>")
+               end
+       end
+end
+
+class Game
+       fun run(window: Window)
+       do
+               var main_view = new CursesView(window)
+
+               var apples = new LiveGroup[Apple]
+               var duck = new Duck
+               var sheep = new Sheep
+
+               var score = 0
+
+               var sprites = new LiveGroup[LiveObject]
+               sprites.add(apples)
+               sprites.add(duck)
+               sprites.add(sheep)
+
+               loop
+
+                       # Call update on all sprites
+                       sprites.update
+
+                       # Need a new apple
+                       if 10.rand < 2  then
+                               var a = new Apple
+                               a.x = (60.rand + 10) * 100
+                               a.y = 0
+                               a.vx = 0
+                               a.vy = 70.rand + 30
+                               apples.add(a)
+                       end
+
+                       # Eat apple or fallen apple
+                       for a in apples do
+                               if not a.exists then continue
+                               if a.overlaps(sheep) then
+                                       score += 1
+                                       a.exists = false
+                               end
+                               if a.y > 2000 then
+                                       a.exists = false
+                               end
+                       end
+
+                       # Sheep vs duck
+                       if sheep.overlaps(duck) then
+                               if sheep.is_jumping and sheep.vy > 0 then
+                                       duck.stunt_ttl = 5
+                                       sheep.vy = -150
+                               else
+                                       if sheep.x < duck.x then
+                                               sheep.x = duck.x - sheep.width
+                                       else
+                                               sheep.x = duck.x + duck.width
+                                       end
+                                       sheep.vx = - sheep.vx
+                                       duck.vx = - duck.vx
+                               end
+                       end
+
+                       # Redraw the screen
+                       window.wclear
+                       sprites.draw(main_view)
+                       window.mvaddstr(0, 0,   "'q' to quit - score: {score}")
+                       window.mvaddstr(20, 0,   "#"*80)
+                       window.refresh
+
+                       # Wait the next frame
+                       sys.nanosleep(0, 48000000)
+
+                       # Keyboard input
+                       while stdin.as(Stdin).poll_in do
+                               if stdin.eof then return
+                               var c = stdin.read_char
+                               if c == 'q'.ascii then return
+                               sheep.jump
+                       end
+               end
+       end
+end
+
+var game = new Game
+
+var win = new Window
+
+game.run(win)
+
+win.delwin
+win.endwin
+win.refresh
+