Skip to content

Commit 01656b5

Browse files
meooow25simonmichael
authored andcommitted
Add space-invaders
1 parent 21a45f9 commit 01656b5

File tree

4 files changed

+133
-0
lines changed

4 files changed

+133
-0
lines changed

Diff for: default/space-invaders/Import.hs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Import (module X) where
2+
import Data.Bits as X
3+
import Data.List as X
4+
import Control.Concurrent as X
5+
import System.IO as X
6+
import Data.Bool as X

Diff for: default/space-invaders/README.md

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# space-invaders
2+
3+
A vastly inferior version of the classic
4+
[Space Invaders](https://en.wikipedia.org/wiki/Space_Invaders).
5+
6+
## How to play
7+
8+
Use `runghc space-invaders.hs` to run the game. Tested with GHC 9.2.5.
9+
10+
![](game.png)
11+
12+
Press <kbd>a</kbd> to move left, <kbd>d</kbd> to move right, and
13+
<kbd>Space</kbd> to shoot. You win if you destroy all the aliens. You lose if
14+
you get hit by an alien or the aliens reach the row in front of you.
15+
16+
## Implementation
17+
18+
Nothing too unusual. The game state consists of a time counter, the movement
19+
direction of the aliens, the position of the player, the positions of the
20+
player's bullet, the positions of the aliens' bullets, and the positions of the
21+
aliens. A loop transforms the state with optional user input, draws the state,
22+
sleeps for a while, and repeats.
23+
24+
Here is the same code, with better names and formatting:
25+
26+
```hs
27+
import Control.Concurrent (threadDelay)
28+
import Data.Bits (xor)
29+
import Data.Bool (bool)
30+
import Data.List ((\\), elemIndex, intersect)
31+
import System.IO (hSetBuffering, stdin, hReady, BufferMode(..))
32+
33+
divides :: Int -> Int -> Bool -- !
34+
divides x y = mod y x == 0
35+
36+
input :: IO Char -- i
37+
input = hReady stdin >>= bool (pure '.') (getChar <* input)
38+
39+
type V2 = (Int, Int)
40+
41+
plus :: V2 -> V2 -> V2 -- p
42+
plus (i,j) (i',j') = (i+i', j+j')
43+
44+
type Board = V2 -> Char
45+
46+
setChar :: Char -> V2 -> Board -> Board -- u
47+
setChar c p b q = bool (b q) c (p == q)
48+
49+
-- Board size is 10x35
50+
h, w :: [Int]
51+
h = [0..9]
52+
w = [0..34]
53+
54+
keep :: [V2] -> [V2] -- k
55+
keep = intersect $ (,) <$> h <*> w
56+
57+
clamp :: V2 -> V2 -- c
58+
clamp (i,j) = (min 9 $ max 0 i, min 34 $ max 0 j)
59+
60+
loop :: Int -> Int -> V2 -> [V2] -> [V2] -> [V2] -> IO a -- l
61+
loop time{-t-} dir{-d-} plr{-s-} plrBlt{-b-} alienBlts{-v-} aliens{-a-}
62+
| null aliens = error "WIN"
63+
| or [True | (7,_) <- aliens] || elem plr alienBlts = error "LOSE"
64+
| otherwise = do
65+
c <- input
66+
let -- Move the player based on input
67+
plr2 = clamp $ plus (0, maybe 0 (1-) $ elemIndex c "d a") plr
68+
69+
-- Move the aliens
70+
aliens2 = plus (0, bool 0 dir $ 10 `divides` time) <$> aliens
71+
aliens3 = plus (1,0) <$> aliens
72+
(aliens4, dir2) = bool (aliens3, -dir) (aliens2, dir) (aliens2 == keep aliens2)
73+
74+
-- Move the player bullet or spawn one based on input
75+
plrBlt2 = keep $ map (plus (-1,0)) $ bool plrBlt [plr] $ c == ' ' && null plrBlt
76+
77+
-- Remove hit aliens
78+
aliens5 = aliens4 \\ plrBlt2
79+
80+
-- Move the alien bullets and spawn new ones
81+
alienBlts2 = keep $ [z | z@(i,j) <- aliens5, 307 `divides` (97*i `xor` 97*j `xor` time)]
82+
++ map (plus (mod time 2, 0)) alienBlts
83+
84+
-- Draw the board
85+
board = foldr (setChar '*')
86+
(foldr (setChar 'W')
87+
(foldr (setChar 'v')
88+
(setChar 'A' plr2 (const ' '))
89+
alienBlts2)
90+
aliens5)
91+
plrBlt2
92+
putStrLn $ "\ESC[H" ++ unlines [[board (i,j) | j <- w] ++ "\ESC[K" | i <- h] ++ "\ESC[J"
93+
threadDelay $ 9^5
94+
loop (time+1) dir2 plr2 (plrBlt2 \\ aliens4) alienBlts2 aliens5
95+
96+
main :: IO ()
97+
main = do
98+
hSetBuffering stdin NoBuffering
99+
loop 0 1 (8,15) [] [] $ (,) <$> [1,3,5] <*> [3,6..24]
100+
101+
```
102+
103+
One interesting part is the representation of the board as `V2 -> Char`, which
104+
allowed for a cute definition of `setChar`.
105+
106+
This has been fun!

Diff for: default/space-invaders/game.png

12.4 KB
Loading

Diff for: default/space-invaders/space-invaders.hs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Import;main=do{hSetBuffering stdin NoBuffering;l 0 1(8,15)[][]$(,)<$>[1,3
2+
,5]<*>[3,6..24]};x!y=mod y x==0;i=hReady stdin>>=y(pure '.')(getChar<*i);y=bool
3+
k=intersect$(,)<$>h<*>w;p(i,j)(k,l)=(i+k,j+l);u c p b q=y(b q)c(p==q);w=[0..34]
4+
l t d s b v a|a==[]=error"WIN"|or[0<1|(7,_)<-a]||elem s v=error"LOSE"|let=i>>=
5+
\c->let{a2=p(0,y 0 d$10!t)<$>a;a3=p(1,0)<$>a;(a4,d2)=y(a3,-d)(a2,d)(a2==k a2);
6+
a5=a4\\b2;v2=k$[z|z@(i,j)<-a5,307!(97*i`xor`97*j`xor`t)]++map(p(mod t 2,0))v;
7+
s2=cl$p(0,maybe 0(1-)$elemIndex c"d a")s;b2=k$map(p(-1,0))$y b[s]$c==' '&&b==[];
8+
f=foldr(u '*')(foldr(u 'W')(foldr(u 'v')(u 'A's2(\_->' '))v2)a5)b2;cl(i,j)=(min
9+
9$max 0i,min 34$max 0j);}in do{putStrLn$"\ESC[H"++unlines[[f(i,j)|j<-w]++
10+
"\ESC[K"|i<-h]++"\ESC[J";threadDelay$9^5;l(t+1)d2 s2(b2\\a4)v2 a5};h=[0..9]
11+
-- ^10 ------------------------------------------------------------------ 80> --
12+
{- default-10-80/space-invaders (meooow25)
13+
14+
GHC 9.2.5
15+
16+
runghc space-invaders.hs
17+
18+
a - left
19+
d - right
20+
spc - shoot
21+
-}

0 commit comments

Comments
 (0)